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..a7bb204da4775 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -82,43 +82,12 @@ 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: { - 'react-hooks/exhaustive-deps': 'off', - }, - }, - { - files: ['src/legacy/core_plugins/vis_type_markdown/**/*.{js,ts,tsx}'], - rules: { - 'react-hooks/exhaustive-deps': 'off', - }, - }, - { - files: ['src/legacy/core_plugins/vis_type_metric/**/*.{js,ts,tsx}'], - rules: { - 'jsx-a11y/click-events-have-key-events': 'off', - }, - }, { files: ['src/legacy/core_plugins/vis_type_table/**/*.{js,ts,tsx}'], rules: { 'react-hooks/exhaustive-deps': 'off', }, }, - { - files: ['src/legacy/core_plugins/vis_type_vega/**/*.{js,ts,tsx}'], - rules: { - 'react-hooks/exhaustive-deps': 'off', - }, - }, { files: ['src/legacy/ui/public/vis/**/*.{js,ts,tsx}'], rules: { @@ -177,12 +146,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 +216,7 @@ module.exports = { '!x-pack/test/**/*', '(src|x-pack)/plugins/**/(public|server)/**/*', 'src/core/(public|server)/**/*', + 'examples/**/*', ], from: [ 'src/core/public/**/*', @@ -289,11 +253,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.', @@ -373,9 +341,8 @@ module.exports = { 'src/fixtures/**/*.js', // TODO: this directory needs to be more obviously "public" (or go away) ], settings: { - // instructs import/no-extraneous-dependencies to treat modules - // in plugins/ or ui/ namespace as "core modules" so they don't - // trigger failures for not being listed in package.json + // instructs import/no-extraneous-dependencies to treat certain modules + // as core modules, even if they aren't listed in package.json 'import/core-modules': [ 'plugins', 'legacy/ui', @@ -729,15 +696,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 +710,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 +778,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..acfb7307f49c4 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 @@ -84,6 +86,7 @@ /packages/kbn-es/ @elastic/kibana-operations /packages/kbn-pm/ @elastic/kibana-operations /packages/kbn-test/ @elastic/kibana-operations +/packages/kbn-ui-shared-deps/ @elastic/kibana-operations /src/legacy/server/keystore/ @elastic/kibana-operations /src/legacy/server/pid/ @elastic/kibana-operations /src/legacy/server/sass/ @elastic/kibana-operations @@ -129,6 +132,9 @@ /x-pack/test/alerting_api_integration @elastic/kibana-alerting-services /x-pack/test/plugin_api_integration/plugins/task_manager @elastic/kibana-alerting-services /x-pack/test/plugin_api_integration/test_suites/task_manager @elastic/kibana-alerting-services +/x-pack/legacy/plugins/triggers_actions_ui/ @elastic/kibana-alerting-services +/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/ @elastic/kibana-alerting-services +/x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/ @elastic/kibana-alerting-services # Design **/*.scss @elastic/kibana-design @@ -146,6 +152,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.appbase.chromeless.md b/docs/development/core/public/kibana-plugin-public.appbase.chromeless.md new file mode 100644 index 0000000000000..ddbf9aafbd28a --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.appbase.chromeless.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [AppBase](./kibana-plugin-public.appbase.md) > [chromeless](./kibana-plugin-public.appbase.chromeless.md) + +## AppBase.chromeless property + +Hide the UI chrome when the application is mounted. Defaults to `false`. Takes precedence over chrome service visibility settings. + +Signature: + +```typescript +chromeless?: boolean; +``` diff --git a/docs/development/core/public/kibana-plugin-public.appbase.id.md b/docs/development/core/public/kibana-plugin-public.appbase.id.md index 57daa0c94bdf6..89dd32d296104 100644 --- a/docs/development/core/public/kibana-plugin-public.appbase.id.md +++ b/docs/development/core/public/kibana-plugin-public.appbase.id.md @@ -4,6 +4,8 @@ ## AppBase.id property +The unique identifier of the application + Signature: ```typescript diff --git a/docs/development/core/public/kibana-plugin-public.appbase.md b/docs/development/core/public/kibana-plugin-public.appbase.md index a93a195c559b1..eb6d91cb92488 100644 --- a/docs/development/core/public/kibana-plugin-public.appbase.md +++ b/docs/development/core/public/kibana-plugin-public.appbase.md @@ -16,10 +16,14 @@ export interface AppBase | Property | Type | Description | | --- | --- | --- | | [capabilities](./kibana-plugin-public.appbase.capabilities.md) | Partial<Capabilities> | Custom capabilities defined by the app. | +| [chromeless](./kibana-plugin-public.appbase.chromeless.md) | boolean | Hide the UI chrome when the application is mounted. Defaults to false. Takes precedence over chrome service visibility settings. | | [euiIconType](./kibana-plugin-public.appbase.euiicontype.md) | string | A EUI iconType that will be used for the app's icon. This icon takes precendence over the icon property. | | [icon](./kibana-plugin-public.appbase.icon.md) | string | A URL to an image file used as an icon. Used as a fallback if euiIconType is not provided. | -| [id](./kibana-plugin-public.appbase.id.md) | string | | +| [id](./kibana-plugin-public.appbase.id.md) | string | The unique identifier of the application | +| [navLinkStatus](./kibana-plugin-public.appbase.navlinkstatus.md) | AppNavLinkStatus | The initial status of the application's navLink. Defaulting to visible if status is accessible and hidden if status is inaccessible See [AppNavLinkStatus](./kibana-plugin-public.appnavlinkstatus.md) | | [order](./kibana-plugin-public.appbase.order.md) | number | An ordinal used to sort nav links relative to one another for display. | +| [status](./kibana-plugin-public.appbase.status.md) | AppStatus | The initial status of the application. Defaulting to accessible | | [title](./kibana-plugin-public.appbase.title.md) | string | The title of the application. | -| [tooltip$](./kibana-plugin-public.appbase.tooltip_.md) | Observable<string> | An observable for a tooltip shown when hovering over app link. | +| [tooltip](./kibana-plugin-public.appbase.tooltip.md) | string | A tooltip shown when hovering over app link. | +| [updater$](./kibana-plugin-public.appbase.updater_.md) | Observable<AppUpdater> | An [AppUpdater](./kibana-plugin-public.appupdater.md) observable that can be used to update the application [AppUpdatableFields](./kibana-plugin-public.appupdatablefields.md) at runtime. | diff --git a/docs/development/core/public/kibana-plugin-public.appbase.navlinkstatus.md b/docs/development/core/public/kibana-plugin-public.appbase.navlinkstatus.md new file mode 100644 index 0000000000000..d6744c3e75756 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.appbase.navlinkstatus.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [AppBase](./kibana-plugin-public.appbase.md) > [navLinkStatus](./kibana-plugin-public.appbase.navlinkstatus.md) + +## AppBase.navLinkStatus property + +The initial status of the application's navLink. Defaulting to `visible` if `status` is `accessible` and `hidden` if status is `inaccessible` See [AppNavLinkStatus](./kibana-plugin-public.appnavlinkstatus.md) + +Signature: + +```typescript +navLinkStatus?: AppNavLinkStatus; +``` diff --git a/docs/development/core/public/kibana-plugin-public.appbase.status.md b/docs/development/core/public/kibana-plugin-public.appbase.status.md new file mode 100644 index 0000000000000..a5fbadbeea1ff --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.appbase.status.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [AppBase](./kibana-plugin-public.appbase.md) > [status](./kibana-plugin-public.appbase.status.md) + +## AppBase.status property + +The initial status of the application. Defaulting to `accessible` + +Signature: + +```typescript +status?: AppStatus; +``` diff --git a/docs/development/core/public/kibana-plugin-public.appbase.tooltip.md b/docs/development/core/public/kibana-plugin-public.appbase.tooltip.md new file mode 100644 index 0000000000000..85921a5a321dd --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.appbase.tooltip.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [AppBase](./kibana-plugin-public.appbase.md) > [tooltip](./kibana-plugin-public.appbase.tooltip.md) + +## AppBase.tooltip property + +A tooltip shown when hovering over app link. + +Signature: + +```typescript +tooltip?: string; +``` diff --git a/docs/development/core/public/kibana-plugin-public.appbase.tooltip_.md b/docs/development/core/public/kibana-plugin-public.appbase.tooltip_.md deleted file mode 100644 index 0767ead5f1455..0000000000000 --- a/docs/development/core/public/kibana-plugin-public.appbase.tooltip_.md +++ /dev/null @@ -1,13 +0,0 @@ - - -[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [AppBase](./kibana-plugin-public.appbase.md) > [tooltip$](./kibana-plugin-public.appbase.tooltip_.md) - -## AppBase.tooltip$ property - -An observable for a tooltip shown when hovering over app link. - -Signature: - -```typescript -tooltip$?: Observable; -``` diff --git a/docs/development/core/public/kibana-plugin-public.appbase.updater_.md b/docs/development/core/public/kibana-plugin-public.appbase.updater_.md new file mode 100644 index 0000000000000..3edd357383449 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.appbase.updater_.md @@ -0,0 +1,44 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [AppBase](./kibana-plugin-public.appbase.md) > [updater$](./kibana-plugin-public.appbase.updater_.md) + +## AppBase.updater$ property + +An [AppUpdater](./kibana-plugin-public.appupdater.md) observable that can be used to update the application [AppUpdatableFields](./kibana-plugin-public.appupdatablefields.md) at runtime. + +Signature: + +```typescript +updater$?: Observable; +``` + +## Example + +How to update an application navLink at runtime + +```ts +// inside your plugin's setup function +export class MyPlugin implements Plugin { + private appUpdater = new BehaviorSubject(() => ({})); + + setup({ application }) { + application.register({ + id: 'my-app', + title: 'My App', + updater$: this.appUpdater, + async mount(params) { + const { renderApp } = await import('./application'); + return renderApp(params); + }, + }); + } + + start() { + // later, when the navlink needs to be updated + appUpdater.next(() => { + navLinkStatus: AppNavLinkStatus.disabled, + }) + } + +``` + diff --git a/docs/development/core/public/kibana-plugin-public.appleaveaction.md b/docs/development/core/public/kibana-plugin-public.appleaveaction.md new file mode 100644 index 0000000000000..ae56205f5e45c --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.appleaveaction.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [AppLeaveAction](./kibana-plugin-public.appleaveaction.md) + +## AppLeaveAction type + +Possible actions to return from a [AppLeaveHandler](./kibana-plugin-public.appleavehandler.md) + +See [AppLeaveConfirmAction](./kibana-plugin-public.appleaveconfirmaction.md) and [AppLeaveDefaultAction](./kibana-plugin-public.appleavedefaultaction.md) + +Signature: + +```typescript +export declare type AppLeaveAction = AppLeaveDefaultAction | AppLeaveConfirmAction; +``` diff --git a/docs/development/core/public/kibana-plugin-public.appleaveactiontype.md b/docs/development/core/public/kibana-plugin-public.appleaveactiontype.md new file mode 100644 index 0000000000000..482b2e489b33e --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.appleaveactiontype.md @@ -0,0 +1,21 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [AppLeaveActionType](./kibana-plugin-public.appleaveactiontype.md) + +## AppLeaveActionType enum + +Possible type of actions on application leave. + +Signature: + +```typescript +export declare enum AppLeaveActionType +``` + +## Enumeration Members + +| Member | Value | Description | +| --- | --- | --- | +| confirm | "confirm" | | +| default | "default" | | + diff --git a/docs/development/core/public/kibana-plugin-public.appleaveconfirmaction.md b/docs/development/core/public/kibana-plugin-public.appleaveconfirmaction.md new file mode 100644 index 0000000000000..4cd99dd2a3fb3 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.appleaveconfirmaction.md @@ -0,0 +1,24 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [AppLeaveConfirmAction](./kibana-plugin-public.appleaveconfirmaction.md) + +## AppLeaveConfirmAction interface + +Action to return from a [AppLeaveHandler](./kibana-plugin-public.appleavehandler.md) to show a confirmation message when trying to leave an application. + +See + +Signature: + +```typescript +export interface AppLeaveConfirmAction +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [text](./kibana-plugin-public.appleaveconfirmaction.text.md) | string | | +| [title](./kibana-plugin-public.appleaveconfirmaction.title.md) | string | | +| [type](./kibana-plugin-public.appleaveconfirmaction.type.md) | AppLeaveActionType.confirm | | + diff --git a/docs/development/core/public/kibana-plugin-public.appleaveconfirmaction.text.md b/docs/development/core/public/kibana-plugin-public.appleaveconfirmaction.text.md new file mode 100644 index 0000000000000..dbbd11c6f71f8 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.appleaveconfirmaction.text.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [AppLeaveConfirmAction](./kibana-plugin-public.appleaveconfirmaction.md) > [text](./kibana-plugin-public.appleaveconfirmaction.text.md) + +## AppLeaveConfirmAction.text property + +Signature: + +```typescript +text: string; +``` diff --git a/docs/development/core/public/kibana-plugin-public.appleaveconfirmaction.title.md b/docs/development/core/public/kibana-plugin-public.appleaveconfirmaction.title.md new file mode 100644 index 0000000000000..684ef384a37c3 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.appleaveconfirmaction.title.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [AppLeaveConfirmAction](./kibana-plugin-public.appleaveconfirmaction.md) > [title](./kibana-plugin-public.appleaveconfirmaction.title.md) + +## AppLeaveConfirmAction.title property + +Signature: + +```typescript +title?: string; +``` diff --git a/docs/development/core/public/kibana-plugin-public.appleaveconfirmaction.type.md b/docs/development/core/public/kibana-plugin-public.appleaveconfirmaction.type.md new file mode 100644 index 0000000000000..926cecf893cc8 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.appleaveconfirmaction.type.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [AppLeaveConfirmAction](./kibana-plugin-public.appleaveconfirmaction.md) > [type](./kibana-plugin-public.appleaveconfirmaction.type.md) + +## AppLeaveConfirmAction.type property + +Signature: + +```typescript +type: AppLeaveActionType.confirm; +``` diff --git a/docs/development/core/public/kibana-plugin-public.appleavedefaultaction.md b/docs/development/core/public/kibana-plugin-public.appleavedefaultaction.md new file mode 100644 index 0000000000000..ed2f729a0c648 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.appleavedefaultaction.md @@ -0,0 +1,22 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [AppLeaveDefaultAction](./kibana-plugin-public.appleavedefaultaction.md) + +## AppLeaveDefaultAction interface + +Action to return from a [AppLeaveHandler](./kibana-plugin-public.appleavehandler.md) to execute the default behaviour when leaving the application. + +See + +Signature: + +```typescript +export interface AppLeaveDefaultAction +``` + +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [type](./kibana-plugin-public.appleavedefaultaction.type.md) | AppLeaveActionType.default | | + diff --git a/docs/development/core/public/kibana-plugin-public.appleavedefaultaction.type.md b/docs/development/core/public/kibana-plugin-public.appleavedefaultaction.type.md new file mode 100644 index 0000000000000..ee12393121a5a --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.appleavedefaultaction.type.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [AppLeaveDefaultAction](./kibana-plugin-public.appleavedefaultaction.md) > [type](./kibana-plugin-public.appleavedefaultaction.type.md) + +## AppLeaveDefaultAction.type property + +Signature: + +```typescript +type: AppLeaveActionType.default; +``` diff --git a/docs/development/core/public/kibana-plugin-public.appleavehandler.md b/docs/development/core/public/kibana-plugin-public.appleavehandler.md new file mode 100644 index 0000000000000..e879227961bc6 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.appleavehandler.md @@ -0,0 +1,15 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [AppLeaveHandler](./kibana-plugin-public.appleavehandler.md) + +## AppLeaveHandler type + +A handler that will be executed before leaving the application, either when going to another application or when closing the browser tab or manually changing the url. Should return `confirm` to to prompt a message to the user before leaving the page, or `default` to keep the default behavior (doing nothing). + +See [AppMountParameters](./kibana-plugin-public.appmountparameters.md) for detailed usage examples. + +Signature: + +```typescript +export declare type AppLeaveHandler = (factory: AppLeaveActionFactory) => AppLeaveAction; +``` diff --git a/docs/development/core/public/kibana-plugin-public.applicationsetup.md b/docs/development/core/public/kibana-plugin-public.applicationsetup.md index a63de399c2ecb..cf9bc5189af40 100644 --- a/docs/development/core/public/kibana-plugin-public.applicationsetup.md +++ b/docs/development/core/public/kibana-plugin-public.applicationsetup.md @@ -16,5 +16,6 @@ export interface ApplicationSetup | Method | Description | | --- | --- | | [register(app)](./kibana-plugin-public.applicationsetup.register.md) | Register an mountable application to the system. | +| [registerAppUpdater(appUpdater$)](./kibana-plugin-public.applicationsetup.registerappupdater.md) | Register an application updater that can be used to change the [AppUpdatableFields](./kibana-plugin-public.appupdatablefields.md) fields of all applications at runtime.This is meant to be used by plugins that needs to updates the whole list of applications. To only updates a specific application, use the updater$ property of the registered application instead. | | [registerMountContext(contextName, provider)](./kibana-plugin-public.applicationsetup.registermountcontext.md) | Register a context provider for application mounting. Will only be available to applications that depend on the plugin that registered this context. Deprecated, use [CoreSetup.getStartServices()](./kibana-plugin-public.coresetup.getstartservices.md). | diff --git a/docs/development/core/public/kibana-plugin-public.applicationsetup.registerappupdater.md b/docs/development/core/public/kibana-plugin-public.applicationsetup.registerappupdater.md new file mode 100644 index 0000000000000..39b4f878a3f79 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.applicationsetup.registerappupdater.md @@ -0,0 +1,47 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [ApplicationSetup](./kibana-plugin-public.applicationsetup.md) > [registerAppUpdater](./kibana-plugin-public.applicationsetup.registerappupdater.md) + +## ApplicationSetup.registerAppUpdater() method + +Register an application updater that can be used to change the [AppUpdatableFields](./kibana-plugin-public.appupdatablefields.md) fields of all applications at runtime. + +This is meant to be used by plugins that needs to updates the whole list of applications. To only updates a specific application, use the `updater$` property of the registered application instead. + +Signature: + +```typescript +registerAppUpdater(appUpdater$: Observable): void; +``` + +## Parameters + +| Parameter | Type | Description | +| --- | --- | --- | +| appUpdater$ | Observable<AppUpdater> | | + +Returns: + +`void` + +## Example + +How to register an application updater that disables some applications: + +```ts +// inside your plugin's setup function +export class MyPlugin implements Plugin { + setup({ application }) { + application.registerAppUpdater( + new BehaviorSubject(app => { + if (myPluginApi.shouldDisable(app)) + return { + status: AppStatus.inaccessible, + }; + }) + ); + } +} + +``` + diff --git a/docs/development/core/public/kibana-plugin-public.applicationstart.md b/docs/development/core/public/kibana-plugin-public.applicationstart.md index 4baa4565ff7b0..e36ef3f14f87e 100644 --- a/docs/development/core/public/kibana-plugin-public.applicationstart.md +++ b/docs/development/core/public/kibana-plugin-public.applicationstart.md @@ -22,6 +22,6 @@ export interface ApplicationStart | Method | Description | | --- | --- | | [getUrlForApp(appId, options)](./kibana-plugin-public.applicationstart.geturlforapp.md) | Returns a relative URL to a given app, including the global base path. | -| [navigateToApp(appId, options)](./kibana-plugin-public.applicationstart.navigatetoapp.md) | Navigiate to a given app | +| [navigateToApp(appId, options)](./kibana-plugin-public.applicationstart.navigatetoapp.md) | Navigate to a given app | | [registerMountContext(contextName, provider)](./kibana-plugin-public.applicationstart.registermountcontext.md) | Register a context provider for application mounting. Will only be available to applications that depend on the plugin that registered this context. Deprecated, use [CoreSetup.getStartServices()](./kibana-plugin-public.coresetup.getstartservices.md). | diff --git a/docs/development/core/public/kibana-plugin-public.applicationstart.navigatetoapp.md b/docs/development/core/public/kibana-plugin-public.applicationstart.navigatetoapp.md index eef31fe661f54..3e29d09ea4cd5 100644 --- a/docs/development/core/public/kibana-plugin-public.applicationstart.navigatetoapp.md +++ b/docs/development/core/public/kibana-plugin-public.applicationstart.navigatetoapp.md @@ -4,7 +4,7 @@ ## ApplicationStart.navigateToApp() method -Navigiate to a given app +Navigate to a given app Signature: @@ -12,7 +12,7 @@ Navigiate to a given app navigateToApp(appId: string, options?: { path?: string; state?: any; - }): void; + }): Promise; ``` ## Parameters @@ -24,5 +24,5 @@ navigateToApp(appId: string, options?: { Returns: -`void` +`Promise` diff --git a/docs/development/core/public/kibana-plugin-public.appmountparameters.md b/docs/development/core/public/kibana-plugin-public.appmountparameters.md index aa5ca93ed8ff0..9586eba96a697 100644 --- a/docs/development/core/public/kibana-plugin-public.appmountparameters.md +++ b/docs/development/core/public/kibana-plugin-public.appmountparameters.md @@ -17,4 +17,5 @@ export interface AppMountParameters | --- | --- | --- | | [appBasePath](./kibana-plugin-public.appmountparameters.appbasepath.md) | string | The route path for configuring navigation to the application. This string should not include the base path from HTTP. | | [element](./kibana-plugin-public.appmountparameters.element.md) | HTMLElement | The container element to render the application into. | +| [onAppLeave](./kibana-plugin-public.appmountparameters.onappleave.md) | (handler: AppLeaveHandler) => void | A function that can be used to register a handler that will be called when the user is leaving the current application, allowing to prompt a confirmation message before actually changing the page.This will be called either when the user goes to another application, or when trying to close the tab or manually changing the url. | diff --git a/docs/development/core/public/kibana-plugin-public.appmountparameters.onappleave.md b/docs/development/core/public/kibana-plugin-public.appmountparameters.onappleave.md new file mode 100644 index 0000000000000..55eb840ce1cf6 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.appmountparameters.onappleave.md @@ -0,0 +1,41 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [AppMountParameters](./kibana-plugin-public.appmountparameters.md) > [onAppLeave](./kibana-plugin-public.appmountparameters.onappleave.md) + +## AppMountParameters.onAppLeave property + +A function that can be used to register a handler that will be called when the user is leaving the current application, allowing to prompt a confirmation message before actually changing the page. + +This will be called either when the user goes to another application, or when trying to close the tab or manually changing the url. + +Signature: + +```typescript +onAppLeave: (handler: AppLeaveHandler) => void; +``` + +## Example + + +```ts +// application.tsx +import React from 'react'; +import ReactDOM from 'react-dom'; +import { BrowserRouter, Route } from 'react-router-dom'; + +import { CoreStart, AppMountParams } from 'src/core/public'; +import { MyPluginDepsStart } from './plugin'; + +export renderApp = ({ appBasePath, element, onAppLeave }: AppMountParams) => { + const { renderApp, hasUnsavedChanges } = await import('./application'); + onAppLeave(actions => { + if(hasUnsavedChanges()) { + return actions.confirm('Some changes were not saved. Are you sure you want to leave?'); + } + return actions.default(); + }); + return renderApp(params); +} + +``` + diff --git a/docs/development/core/public/kibana-plugin-public.appnavlinkstatus.md b/docs/development/core/public/kibana-plugin-public.appnavlinkstatus.md new file mode 100644 index 0000000000000..d6b22ac2b9217 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.appnavlinkstatus.md @@ -0,0 +1,23 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [AppNavLinkStatus](./kibana-plugin-public.appnavlinkstatus.md) + +## AppNavLinkStatus enum + +Status of the application's navLink. + +Signature: + +```typescript +export declare enum AppNavLinkStatus +``` + +## Enumeration Members + +| Member | Value | Description | +| --- | --- | --- | +| default | 0 | The application navLink will be visible if the application's [AppStatus](./kibana-plugin-public.appstatus.md) is set to accessible and hidden if the application status is set to inaccessible. | +| disabled | 2 | The application navLink is visible but inactive and not clickable in the navigation bar. | +| hidden | 3 | The application navLink does not appear in the navigation bar. | +| visible | 1 | The application navLink is visible and clickable in the navigation bar. | + diff --git a/docs/development/core/public/kibana-plugin-public.appstatus.md b/docs/development/core/public/kibana-plugin-public.appstatus.md new file mode 100644 index 0000000000000..23fb7186569da --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.appstatus.md @@ -0,0 +1,21 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [AppStatus](./kibana-plugin-public.appstatus.md) + +## AppStatus enum + +Accessibility status of an application. + +Signature: + +```typescript +export declare enum AppStatus +``` + +## Enumeration Members + +| Member | Value | Description | +| --- | --- | --- | +| accessible | 0 | Application is accessible. | +| inaccessible | 1 | Application is not accessible. | + diff --git a/docs/development/core/public/kibana-plugin-public.appupdatablefields.md b/docs/development/core/public/kibana-plugin-public.appupdatablefields.md new file mode 100644 index 0000000000000..b9260c79cd972 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.appupdatablefields.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [AppUpdatableFields](./kibana-plugin-public.appupdatablefields.md) + +## AppUpdatableFields type + +Defines the list of fields that can be updated via an [AppUpdater](./kibana-plugin-public.appupdater.md). + +Signature: + +```typescript +export declare type AppUpdatableFields = Pick; +``` diff --git a/docs/development/core/public/kibana-plugin-public.appupdater.md b/docs/development/core/public/kibana-plugin-public.appupdater.md new file mode 100644 index 0000000000000..f1b965cc2fc22 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.appupdater.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [AppUpdater](./kibana-plugin-public.appupdater.md) + +## AppUpdater type + +Updater for applications. see [ApplicationSetup](./kibana-plugin-public.applicationsetup.md) + +Signature: + +```typescript +export declare type AppUpdater = (app: AppBase) => Partial | undefined; +``` diff --git a/docs/development/core/public/kibana-plugin-public.chromedoctitle.md b/docs/development/core/public/kibana-plugin-public.chromedoctitle.md index 3c6cfab486288..feb3b3ab966ef 100644 --- a/docs/development/core/public/kibana-plugin-public.chromedoctitle.md +++ b/docs/development/core/public/kibana-plugin-public.chromedoctitle.md @@ -12,13 +12,6 @@ APIs for accessing and updating the document title. export interface ChromeDocTitle ``` -## Methods - -| Method | Description | -| --- | --- | -| [change(newTitle)](./kibana-plugin-public.chromedoctitle.change.md) | Changes the current document title. | -| [reset()](./kibana-plugin-public.chromedoctitle.reset.md) | Resets the document title to it's initial value. (meaning the one present in the title meta at application load.) | - ## Example 1 How to change the title of the document @@ -37,3 +30,10 @@ chrome.docTitle.reset() ``` +## Methods + +| Method | Description | +| --- | --- | +| [change(newTitle)](./kibana-plugin-public.chromedoctitle.change.md) | Changes the current document title. | +| [reset()](./kibana-plugin-public.chromedoctitle.reset.md) | Resets the document title to it's initial value. (meaning the one present in the title meta at application load.) | + diff --git a/docs/development/core/public/kibana-plugin-public.chromenavcontrols.md b/docs/development/core/public/kibana-plugin-public.chromenavcontrols.md index a34db9bb33d9d..30b9a6869d1ff 100644 --- a/docs/development/core/public/kibana-plugin-public.chromenavcontrols.md +++ b/docs/development/core/public/kibana-plugin-public.chromenavcontrols.md @@ -12,13 +12,6 @@ export interface ChromeNavControls ``` -## Methods - -| Method | Description | -| --- | --- | -| [registerLeft(navControl)](./kibana-plugin-public.chromenavcontrols.registerleft.md) | Register a nav control to be presented on the left side of the chrome header. | -| [registerRight(navControl)](./kibana-plugin-public.chromenavcontrols.registerright.md) | Register a nav control to be presented on the right side of the chrome header. | - ## Example Register a left-side nav control rendered with React. @@ -33,3 +26,10 @@ chrome.navControls.registerLeft({ ``` +## Methods + +| Method | Description | +| --- | --- | +| [registerLeft(navControl)](./kibana-plugin-public.chromenavcontrols.registerleft.md) | Register a nav control to be presented on the left side of the chrome header. | +| [registerRight(navControl)](./kibana-plugin-public.chromenavcontrols.registerright.md) | Register a nav control to be presented on the right side of the chrome header. | + diff --git a/docs/development/core/public/kibana-plugin-public.chromenavlink.md b/docs/development/core/public/kibana-plugin-public.chromenavlink.md index 93ebbe3653ac4..4cb9080222ac5 100644 --- a/docs/development/core/public/kibana-plugin-public.chromenavlink.md +++ b/docs/development/core/public/kibana-plugin-public.chromenavlink.md @@ -24,7 +24,7 @@ export interface ChromeNavLink | [id](./kibana-plugin-public.chromenavlink.id.md) | string | A unique identifier for looking up links. | | [linkToLastSubUrl](./kibana-plugin-public.chromenavlink.linktolastsuburl.md) | boolean | Whether or not the subUrl feature should be enabled. | | [order](./kibana-plugin-public.chromenavlink.order.md) | number | An ordinal used to sort nav links relative to one another for display. | -| [subUrlBase](./kibana-plugin-public.chromenavlink.suburlbase.md) | string | A url base that legacy apps can set to match deep URLs to an applcation. | +| [subUrlBase](./kibana-plugin-public.chromenavlink.suburlbase.md) | string | A url base that legacy apps can set to match deep URLs to an application. | | [title](./kibana-plugin-public.chromenavlink.title.md) | string | The title of the application. | | [tooltip](./kibana-plugin-public.chromenavlink.tooltip.md) | string | A tooltip shown when hovering over an app link. | | [url](./kibana-plugin-public.chromenavlink.url.md) | string | A url that legacy apps can set to deep link into their applications. | diff --git a/docs/development/core/public/kibana-plugin-public.chromenavlink.suburlbase.md b/docs/development/core/public/kibana-plugin-public.chromenavlink.suburlbase.md index b9d12432a01df..1b8fb0574cf8b 100644 --- a/docs/development/core/public/kibana-plugin-public.chromenavlink.suburlbase.md +++ b/docs/development/core/public/kibana-plugin-public.chromenavlink.suburlbase.md @@ -8,7 +8,7 @@ > > -A url base that legacy apps can set to match deep URLs to an applcation. +A url base that legacy apps can set to match deep URLs to an application. Signature: diff --git a/docs/development/core/public/kibana-plugin-public.chromestart.md b/docs/development/core/public/kibana-plugin-public.chromestart.md index d5d99f3d5be65..4e44e5bf05074 100644 --- a/docs/development/core/public/kibana-plugin-public.chromestart.md +++ b/docs/development/core/public/kibana-plugin-public.chromestart.md @@ -12,6 +12,31 @@ ChromeStart allows plugins to customize the global chrome header UI and enrich t export interface ChromeStart ``` +## Remarks + +While ChromeStart exposes many APIs, they should be used sparingly and the developer should understand how they affect other plugins and applications. + +## Example 1 + +How to add a recently accessed item to the sidebar: + +```ts +core.chrome.recentlyAccessed.add('/app/map/1234', 'Map 1234', '1234'); + +``` + +## Example 2 + +How to set the help dropdown extension: + +```tsx +core.chrome.setHelpExtension(elem => { + ReactDOM.render(, elem); + return () => ReactDOM.unmountComponentAtNode(elem); +}); + +``` + ## Properties | Property | Type | Description | @@ -43,28 +68,3 @@ export interface ChromeStart | [setIsCollapsed(isCollapsed)](./kibana-plugin-public.chromestart.setiscollapsed.md) | Set the collapsed state of the chrome navigation. | | [setIsVisible(isVisible)](./kibana-plugin-public.chromestart.setisvisible.md) | Set the temporary visibility for the chrome. This does nothing if the chrome is hidden by default and should be used to hide the chrome for things like full-screen modes with an exit button. | -## Remarks - -While ChromeStart exposes many APIs, they should be used sparingly and the developer should understand how they affect other plugins and applications. - -## Example 1 - -How to add a recently accessed item to the sidebar: - -```ts -core.chrome.recentlyAccessed.add('/app/map/1234', 'Map 1234', '1234'); - -``` - -## Example 2 - -How to set the help dropdown extension: - -```tsx -core.chrome.setHelpExtension(elem => { - ReactDOM.render(, elem); - return () => ReactDOM.unmountComponentAtNode(elem); -}); - -``` - diff --git a/docs/development/core/public/kibana-plugin-public.contextsetup.md b/docs/development/core/public/kibana-plugin-public.contextsetup.md index a006fa7205ca6..d4399b6ba70c4 100644 --- a/docs/development/core/public/kibana-plugin-public.contextsetup.md +++ b/docs/development/core/public/kibana-plugin-public.contextsetup.md @@ -12,12 +12,6 @@ An object that handles registration of context providers and configuring handler export interface ContextSetup ``` -## Methods - -| Method | Description | -| --- | --- | -| [createContextContainer()](./kibana-plugin-public.contextsetup.createcontextcontainer.md) | Creates a new [IContextContainer](./kibana-plugin-public.icontextcontainer.md) for a service owner. | - ## Remarks A [IContextContainer](./kibana-plugin-public.icontextcontainer.md) can be used by any Core service or plugin (known as the "service owner") which wishes to expose APIs in a handler function. The container object will manage registering context providers and configuring a handler with all of the contexts that should be exposed to the handler's plugin. This is dependent on the dependencies that the handler's plugin declares. @@ -136,3 +130,9 @@ class VizRenderingPlugin { ``` +## Methods + +| Method | Description | +| --- | --- | +| [createContextContainer()](./kibana-plugin-public.contextsetup.createcontextcontainer.md) | Creates a new [IContextContainer](./kibana-plugin-public.icontextcontainer.md) for a service owner. | + diff --git a/docs/development/core/public/kibana-plugin-public.icontextcontainer.md b/docs/development/core/public/kibana-plugin-public.icontextcontainer.md index f16c07b3d7906..7a21df6b93bb5 100644 --- a/docs/development/core/public/kibana-plugin-public.icontextcontainer.md +++ b/docs/development/core/public/kibana-plugin-public.icontextcontainer.md @@ -12,13 +12,6 @@ An object that handles registration of context providers and configuring handler export interface IContextContainer> ``` -## Methods - -| Method | Description | -| --- | --- | -| [createHandler(pluginOpaqueId, handler)](./kibana-plugin-public.icontextcontainer.createhandler.md) | Create a new handler function pre-wired to context for the plugin. | -| [registerContext(pluginOpaqueId, contextName, provider)](./kibana-plugin-public.icontextcontainer.registercontext.md) | Register a new context provider. | - ## Remarks A [IContextContainer](./kibana-plugin-public.icontextcontainer.md) can be used by any Core service or plugin (known as the "service owner") which wishes to expose APIs in a handler function. The container object will manage registering context providers and configuring a handler with all of the contexts that should be exposed to the handler's plugin. This is dependent on the dependencies that the handler's plugin declares. @@ -78,3 +71,10 @@ class MyPlugin { ``` +## Methods + +| Method | Description | +| --- | --- | +| [createHandler(pluginOpaqueId, handler)](./kibana-plugin-public.icontextcontainer.createhandler.md) | Create a new handler function pre-wired to context for the plugin. | +| [registerContext(pluginOpaqueId, contextName, provider)](./kibana-plugin-public.icontextcontainer.registercontext.md) | Register a new context provider. | + diff --git a/docs/development/core/public/kibana-plugin-public.legacycoresetup.md b/docs/development/core/public/kibana-plugin-public.legacycoresetup.md index a753300437c1c..803c96cd0b22c 100644 --- a/docs/development/core/public/kibana-plugin-public.legacycoresetup.md +++ b/docs/development/core/public/kibana-plugin-public.legacycoresetup.md @@ -16,13 +16,13 @@ Setup interface exposed to the legacy platform via the `ui/new_platform` module. export interface LegacyCoreSetup extends CoreSetup ``` +## Remarks + +Some methods are not supported in the legacy platform and while present to make this type compatibile with [CoreSetup](./kibana-plugin-public.coresetup.md), unsupported methods will throw exceptions when called. + ## Properties | Property | Type | Description | | --- | --- | --- | | [injectedMetadata](./kibana-plugin-public.legacycoresetup.injectedmetadata.md) | InjectedMetadataSetup | | -## Remarks - -Some methods are not supported in the legacy platform and while present to make this type compatibile with [CoreSetup](./kibana-plugin-public.coresetup.md), unsupported methods will throw exceptions when called. - diff --git a/docs/development/core/public/kibana-plugin-public.legacycorestart.md b/docs/development/core/public/kibana-plugin-public.legacycorestart.md index 775c3fb1ffe3d..438a3d6110776 100644 --- a/docs/development/core/public/kibana-plugin-public.legacycorestart.md +++ b/docs/development/core/public/kibana-plugin-public.legacycorestart.md @@ -16,13 +16,13 @@ Start interface exposed to the legacy platform via the `ui/new_platform` module. export interface LegacyCoreStart extends CoreStart ``` +## Remarks + +Some methods are not supported in the legacy platform and while present to make this type compatibile with [CoreStart](./kibana-plugin-public.corestart.md), unsupported methods will throw exceptions when called. + ## Properties | Property | Type | Description | | --- | --- | --- | | [injectedMetadata](./kibana-plugin-public.legacycorestart.injectedmetadata.md) | InjectedMetadataStart | | -## Remarks - -Some methods are not supported in the legacy platform and while present to make this type compatibile with [CoreStart](./kibana-plugin-public.corestart.md), unsupported methods will throw exceptions when called. - diff --git a/docs/development/core/public/kibana-plugin-public.md b/docs/development/core/public/kibana-plugin-public.md index e2c2866b57b6b..64cbdd880fed1 100644 --- a/docs/development/core/public/kibana-plugin-public.md +++ b/docs/development/core/public/kibana-plugin-public.md @@ -1,137 +1,151 @@ - - -[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) - -## kibana-plugin-public package - -The Kibana Core APIs for client-side plugins. - -A plugin's `public/index` file must contain a named import, `plugin`, that implements [PluginInitializer](./kibana-plugin-public.plugininitializer.md) which returns an object that implements [Plugin](./kibana-plugin-public.plugin.md). - -The plugin integrates with the core system via lifecycle events: `setup`, `start`, and `stop`. In each lifecycle method, the plugin will receive the corresponding core services available (either [CoreSetup](./kibana-plugin-public.coresetup.md) or [CoreStart](./kibana-plugin-public.corestart.md)) and any interfaces returned by dependency plugins' lifecycle method. Anything returned by the plugin's lifecycle method will be exposed to downstream dependencies when their corresponding lifecycle methods are invoked. - -## Classes - -| Class | Description | -| --- | --- | -| [SavedObjectsClient](./kibana-plugin-public.savedobjectsclient.md) | Saved Objects is Kibana's data persisentence mechanism allowing plugins to use Elasticsearch for storing plugin state. The client-side SavedObjectsClient is a thin convenience library around the SavedObjects HTTP API for interacting with Saved Objects. | -| [SimpleSavedObject](./kibana-plugin-public.simplesavedobject.md) | This class is a very simple wrapper for SavedObjects loaded from the server with the [SavedObjectsClient](./kibana-plugin-public.savedobjectsclient.md).It provides basic functionality for creating/saving/deleting saved objects, but doesn't include any type-specific implementations. | -| [ToastsApi](./kibana-plugin-public.toastsapi.md) | Methods for adding and removing global toast messages. | - -## Interfaces - -| Interface | Description | -| --- | --- | -| [App](./kibana-plugin-public.app.md) | Extension of [common app properties](./kibana-plugin-public.appbase.md) with the mount function. | -| [AppBase](./kibana-plugin-public.appbase.md) | | -| [ApplicationSetup](./kibana-plugin-public.applicationsetup.md) | | -| [ApplicationStart](./kibana-plugin-public.applicationstart.md) | | -| [AppMountContext](./kibana-plugin-public.appmountcontext.md) | The context object received when applications are mounted to the DOM. Deprecated, use [CoreSetup.getStartServices()](./kibana-plugin-public.coresetup.getstartservices.md). | -| [AppMountParameters](./kibana-plugin-public.appmountparameters.md) | | -| [Capabilities](./kibana-plugin-public.capabilities.md) | The read-only set of capabilities available for the current UI session. Capabilities are simple key-value pairs of (string, boolean), where the string denotes the capability ID, and the boolean is a flag indicating if the capability is enabled or disabled. | -| [ChromeBadge](./kibana-plugin-public.chromebadge.md) | | -| [ChromeBrand](./kibana-plugin-public.chromebrand.md) | | -| [ChromeDocTitle](./kibana-plugin-public.chromedoctitle.md) | APIs for accessing and updating the document title. | -| [ChromeHelpExtension](./kibana-plugin-public.chromehelpextension.md) | | -| [ChromeNavControl](./kibana-plugin-public.chromenavcontrol.md) | | -| [ChromeNavControls](./kibana-plugin-public.chromenavcontrols.md) | [APIs](./kibana-plugin-public.chromenavcontrols.md) for registering new controls to be displayed in the navigation bar. | -| [ChromeNavLink](./kibana-plugin-public.chromenavlink.md) | | -| [ChromeNavLinks](./kibana-plugin-public.chromenavlinks.md) | [APIs](./kibana-plugin-public.chromenavlinks.md) for manipulating nav links. | -| [ChromeRecentlyAccessed](./kibana-plugin-public.chromerecentlyaccessed.md) | [APIs](./kibana-plugin-public.chromerecentlyaccessed.md) for recently accessed history. | -| [ChromeRecentlyAccessedHistoryItem](./kibana-plugin-public.chromerecentlyaccessedhistoryitem.md) | | -| [ChromeStart](./kibana-plugin-public.chromestart.md) | ChromeStart allows plugins to customize the global chrome header UI and enrich the UX with additional information about the current location of the browser. | -| [ContextSetup](./kibana-plugin-public.contextsetup.md) | An object that handles registration of context providers and configuring handlers with context. | -| [CoreSetup](./kibana-plugin-public.coresetup.md) | Core services exposed to the Plugin setup lifecycle | -| [CoreStart](./kibana-plugin-public.corestart.md) | Core services exposed to the Plugin start lifecycle | -| [DocLinksStart](./kibana-plugin-public.doclinksstart.md) | | -| [EnvironmentMode](./kibana-plugin-public.environmentmode.md) | | -| [ErrorToastOptions](./kibana-plugin-public.errortoastoptions.md) | Options available for [IToasts](./kibana-plugin-public.itoasts.md) APIs. | -| [FatalErrorInfo](./kibana-plugin-public.fatalerrorinfo.md) | Represents the message and stack of a fatal Error | -| [FatalErrorsSetup](./kibana-plugin-public.fatalerrorssetup.md) | FatalErrors stop the Kibana Public Core and displays a fatal error screen with details about the Kibana build and the error. | -| [HttpErrorRequest](./kibana-plugin-public.httperrorrequest.md) | | -| [HttpErrorResponse](./kibana-plugin-public.httperrorresponse.md) | | -| [HttpFetchOptions](./kibana-plugin-public.httpfetchoptions.md) | All options that may be used with a [HttpHandler](./kibana-plugin-public.httphandler.md). | -| [HttpFetchQuery](./kibana-plugin-public.httpfetchquery.md) | | -| [HttpHandler](./kibana-plugin-public.httphandler.md) | A function for making an HTTP requests to Kibana's backend. See [HttpFetchOptions](./kibana-plugin-public.httpfetchoptions.md) for options and [IHttpResponse](./kibana-plugin-public.ihttpresponse.md) for the response. | -| [HttpHeadersInit](./kibana-plugin-public.httpheadersinit.md) | | -| [HttpInterceptor](./kibana-plugin-public.httpinterceptor.md) | An object that may define global interceptor functions for different parts of the request and response lifecycle. See [IHttpInterceptController](./kibana-plugin-public.ihttpinterceptcontroller.md). | -| [HttpRequestInit](./kibana-plugin-public.httprequestinit.md) | Fetch API options available to [HttpHandler](./kibana-plugin-public.httphandler.md)s. | -| [HttpSetup](./kibana-plugin-public.httpsetup.md) | | -| [I18nStart](./kibana-plugin-public.i18nstart.md) | I18nStart.Context is required by any localizable React component from @kbn/i18n and @elastic/eui packages and is supposed to be used as the topmost component for any i18n-compatible React tree. | -| [IAnonymousPaths](./kibana-plugin-public.ianonymouspaths.md) | APIs for denoting paths as not requiring authentication | -| [IBasePath](./kibana-plugin-public.ibasepath.md) | APIs for manipulating the basePath on URL segments. | -| [IContextContainer](./kibana-plugin-public.icontextcontainer.md) | An object that handles registration of context providers and configuring handlers with context. | -| [IHttpFetchError](./kibana-plugin-public.ihttpfetcherror.md) | | -| [IHttpInterceptController](./kibana-plugin-public.ihttpinterceptcontroller.md) | Used to halt a request Promise chain in a [HttpInterceptor](./kibana-plugin-public.httpinterceptor.md). | -| [IHttpResponse](./kibana-plugin-public.ihttpresponse.md) | | -| [IHttpResponseInterceptorOverrides](./kibana-plugin-public.ihttpresponseinterceptoroverrides.md) | Properties that can be returned by HttpInterceptor.request to override the response. | -| [IUiSettingsClient](./kibana-plugin-public.iuisettingsclient.md) | Client-side client that provides access to the advanced settings stored in elasticsearch. The settings provide control over the behavior of the Kibana application. For example, a user can specify how to display numeric or date fields. Users can adjust the settings via Management UI. [IUiSettingsClient](./kibana-plugin-public.iuisettingsclient.md) | -| [LegacyCoreSetup](./kibana-plugin-public.legacycoresetup.md) | Setup interface exposed to the legacy platform via the ui/new_platform module. | -| [LegacyCoreStart](./kibana-plugin-public.legacycorestart.md) | Start interface exposed to the legacy platform via the ui/new_platform module. | -| [LegacyNavLink](./kibana-plugin-public.legacynavlink.md) | | -| [NotificationsSetup](./kibana-plugin-public.notificationssetup.md) | | -| [NotificationsStart](./kibana-plugin-public.notificationsstart.md) | | -| [OverlayBannersStart](./kibana-plugin-public.overlaybannersstart.md) | | -| [OverlayRef](./kibana-plugin-public.overlayref.md) | Returned by [OverlayStart](./kibana-plugin-public.overlaystart.md) methods for closing a mounted overlay. | -| [OverlayStart](./kibana-plugin-public.overlaystart.md) | | -| [PackageInfo](./kibana-plugin-public.packageinfo.md) | | -| [Plugin](./kibana-plugin-public.plugin.md) | The interface that should be returned by a PluginInitializer. | -| [PluginInitializerContext](./kibana-plugin-public.plugininitializercontext.md) | The available core services passed to a PluginInitializer | -| [SavedObject](./kibana-plugin-public.savedobject.md) | | -| [SavedObjectAttributes](./kibana-plugin-public.savedobjectattributes.md) | The data for a Saved Object is stored as an object in the attributes property. | -| [SavedObjectReference](./kibana-plugin-public.savedobjectreference.md) | A reference to another saved object. | -| [SavedObjectsBaseOptions](./kibana-plugin-public.savedobjectsbaseoptions.md) | | -| [SavedObjectsBatchResponse](./kibana-plugin-public.savedobjectsbatchresponse.md) | | -| [SavedObjectsBulkCreateObject](./kibana-plugin-public.savedobjectsbulkcreateobject.md) | | -| [SavedObjectsBulkCreateOptions](./kibana-plugin-public.savedobjectsbulkcreateoptions.md) | | -| [SavedObjectsBulkUpdateObject](./kibana-plugin-public.savedobjectsbulkupdateobject.md) | | -| [SavedObjectsBulkUpdateOptions](./kibana-plugin-public.savedobjectsbulkupdateoptions.md) | | -| [SavedObjectsCreateOptions](./kibana-plugin-public.savedobjectscreateoptions.md) | | -| [SavedObjectsFindOptions](./kibana-plugin-public.savedobjectsfindoptions.md) | | -| [SavedObjectsFindResponsePublic](./kibana-plugin-public.savedobjectsfindresponsepublic.md) | Return type of the Saved Objects find() method.\*Note\*: this type is different between the Public and Server Saved Objects clients. | -| [SavedObjectsImportConflictError](./kibana-plugin-public.savedobjectsimportconflicterror.md) | Represents a failure to import due to a conflict. | -| [SavedObjectsImportError](./kibana-plugin-public.savedobjectsimporterror.md) | Represents a failure to import. | -| [SavedObjectsImportMissingReferencesError](./kibana-plugin-public.savedobjectsimportmissingreferenceserror.md) | Represents a failure to import due to missing references. | -| [SavedObjectsImportResponse](./kibana-plugin-public.savedobjectsimportresponse.md) | The response describing the result of an import. | -| [SavedObjectsImportRetry](./kibana-plugin-public.savedobjectsimportretry.md) | Describes a retry operation for importing a saved object. | -| [SavedObjectsImportUnknownError](./kibana-plugin-public.savedobjectsimportunknownerror.md) | Represents a failure to import due to an unknown reason. | -| [SavedObjectsImportUnsupportedTypeError](./kibana-plugin-public.savedobjectsimportunsupportedtypeerror.md) | Represents a failure to import due to having an unsupported saved object type. | -| [SavedObjectsMigrationVersion](./kibana-plugin-public.savedobjectsmigrationversion.md) | Information about the migrations that have been applied to this SavedObject. When Kibana starts up, KibanaMigrator detects outdated documents and migrates them based on this value. For each migration that has been applied, the plugin's name is used as a key and the latest migration version as the value. | -| [SavedObjectsStart](./kibana-plugin-public.savedobjectsstart.md) | | -| [SavedObjectsUpdateOptions](./kibana-plugin-public.savedobjectsupdateoptions.md) | | -| [UiSettingsState](./kibana-plugin-public.uisettingsstate.md) | | - -## Type Aliases - -| Type Alias | Description | -| --- | --- | -| [AppMount](./kibana-plugin-public.appmount.md) | A mount function called when the user navigates to this app's route. | -| [AppMountDeprecated](./kibana-plugin-public.appmountdeprecated.md) | A mount function called when the user navigates to this app's route. | -| [AppUnmount](./kibana-plugin-public.appunmount.md) | A function called when an application should be unmounted from the page. This function should be synchronous. | -| [ChromeBreadcrumb](./kibana-plugin-public.chromebreadcrumb.md) | | -| [ChromeHelpExtensionMenuCustomLink](./kibana-plugin-public.chromehelpextensionmenucustomlink.md) | | -| [ChromeHelpExtensionMenuDiscussLink](./kibana-plugin-public.chromehelpextensionmenudiscusslink.md) | | -| [ChromeHelpExtensionMenuDocumentationLink](./kibana-plugin-public.chromehelpextensionmenudocumentationlink.md) | | -| [ChromeHelpExtensionMenuGitHubLink](./kibana-plugin-public.chromehelpextensionmenugithublink.md) | | -| [ChromeHelpExtensionMenuLink](./kibana-plugin-public.chromehelpextensionmenulink.md) | | -| [ChromeNavLinkUpdateableFields](./kibana-plugin-public.chromenavlinkupdateablefields.md) | | -| [HandlerContextType](./kibana-plugin-public.handlercontexttype.md) | Extracts the type of the first argument of a [HandlerFunction](./kibana-plugin-public.handlerfunction.md) to represent the type of the context. | -| [HandlerFunction](./kibana-plugin-public.handlerfunction.md) | A function that accepts a context object and an optional number of additional arguments. Used for the generic types in [IContextContainer](./kibana-plugin-public.icontextcontainer.md) | -| [HandlerParameters](./kibana-plugin-public.handlerparameters.md) | Extracts the types of the additional arguments of a [HandlerFunction](./kibana-plugin-public.handlerfunction.md), excluding the [HandlerContextType](./kibana-plugin-public.handlercontexttype.md). | -| [HttpStart](./kibana-plugin-public.httpstart.md) | See [HttpSetup](./kibana-plugin-public.httpsetup.md) | -| [IContextProvider](./kibana-plugin-public.icontextprovider.md) | A function that returns a context value for a specific key of given context type. | -| [IToasts](./kibana-plugin-public.itoasts.md) | Methods for adding and removing global toast messages. See [ToastsApi](./kibana-plugin-public.toastsapi.md). | -| [MountPoint](./kibana-plugin-public.mountpoint.md) | A function that should mount DOM content inside the provided container element and return a handler to unmount it. | -| [PluginInitializer](./kibana-plugin-public.plugininitializer.md) | The plugin export at the root of a plugin's public directory should conform to this interface. | -| [PluginOpaqueId](./kibana-plugin-public.pluginopaqueid.md) | | -| [RecursiveReadonly](./kibana-plugin-public.recursivereadonly.md) | | -| [SavedObjectAttribute](./kibana-plugin-public.savedobjectattribute.md) | Type definition for a Saved Object attribute value | -| [SavedObjectAttributeSingle](./kibana-plugin-public.savedobjectattributesingle.md) | Don't use this type, it's simply a helper type for [SavedObjectAttribute](./kibana-plugin-public.savedobjectattribute.md) | -| [SavedObjectsClientContract](./kibana-plugin-public.savedobjectsclientcontract.md) | SavedObjectsClientContract as implemented by the [SavedObjectsClient](./kibana-plugin-public.savedobjectsclient.md) | -| [Toast](./kibana-plugin-public.toast.md) | | -| [ToastInput](./kibana-plugin-public.toastinput.md) | Inputs for [IToasts](./kibana-plugin-public.itoasts.md) APIs. | -| [ToastInputFields](./kibana-plugin-public.toastinputfields.md) | Allowed fields for [ToastInput](./kibana-plugin-public.toastinput.md). | -| [ToastsSetup](./kibana-plugin-public.toastssetup.md) | [IToasts](./kibana-plugin-public.itoasts.md) | -| [ToastsStart](./kibana-plugin-public.toastsstart.md) | [IToasts](./kibana-plugin-public.itoasts.md) | -| [UnmountCallback](./kibana-plugin-public.unmountcallback.md) | A function that will unmount the element previously mounted by the associated [MountPoint](./kibana-plugin-public.mountpoint.md) | - + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) + +## kibana-plugin-public package + +The Kibana Core APIs for client-side plugins. + +A plugin's `public/index` file must contain a named import, `plugin`, that implements [PluginInitializer](./kibana-plugin-public.plugininitializer.md) which returns an object that implements [Plugin](./kibana-plugin-public.plugin.md). + +The plugin integrates with the core system via lifecycle events: `setup`, `start`, and `stop`. In each lifecycle method, the plugin will receive the corresponding core services available (either [CoreSetup](./kibana-plugin-public.coresetup.md) or [CoreStart](./kibana-plugin-public.corestart.md)) and any interfaces returned by dependency plugins' lifecycle method. Anything returned by the plugin's lifecycle method will be exposed to downstream dependencies when their corresponding lifecycle methods are invoked. + +## Classes + +| Class | Description | +| --- | --- | +| [SavedObjectsClient](./kibana-plugin-public.savedobjectsclient.md) | Saved Objects is Kibana's data persisentence mechanism allowing plugins to use Elasticsearch for storing plugin state. The client-side SavedObjectsClient is a thin convenience library around the SavedObjects HTTP API for interacting with Saved Objects. | +| [SimpleSavedObject](./kibana-plugin-public.simplesavedobject.md) | This class is a very simple wrapper for SavedObjects loaded from the server with the [SavedObjectsClient](./kibana-plugin-public.savedobjectsclient.md).It provides basic functionality for creating/saving/deleting saved objects, but doesn't include any type-specific implementations. | +| [ToastsApi](./kibana-plugin-public.toastsapi.md) | Methods for adding and removing global toast messages. | + +## Enumerations + +| Enumeration | Description | +| --- | --- | +| [AppLeaveActionType](./kibana-plugin-public.appleaveactiontype.md) | Possible type of actions on application leave. | +| [AppNavLinkStatus](./kibana-plugin-public.appnavlinkstatus.md) | Status of the application's navLink. | +| [AppStatus](./kibana-plugin-public.appstatus.md) | Accessibility status of an application. | + +## Interfaces + +| Interface | Description | +| --- | --- | +| [App](./kibana-plugin-public.app.md) | Extension of [common app properties](./kibana-plugin-public.appbase.md) with the mount function. | +| [AppBase](./kibana-plugin-public.appbase.md) | | +| [AppLeaveConfirmAction](./kibana-plugin-public.appleaveconfirmaction.md) | Action to return from a [AppLeaveHandler](./kibana-plugin-public.appleavehandler.md) to show a confirmation message when trying to leave an application.See | +| [AppLeaveDefaultAction](./kibana-plugin-public.appleavedefaultaction.md) | Action to return from a [AppLeaveHandler](./kibana-plugin-public.appleavehandler.md) to execute the default behaviour when leaving the application.See | +| [ApplicationSetup](./kibana-plugin-public.applicationsetup.md) | | +| [ApplicationStart](./kibana-plugin-public.applicationstart.md) | | +| [AppMountContext](./kibana-plugin-public.appmountcontext.md) | The context object received when applications are mounted to the DOM. Deprecated, use [CoreSetup.getStartServices()](./kibana-plugin-public.coresetup.getstartservices.md). | +| [AppMountParameters](./kibana-plugin-public.appmountparameters.md) | | +| [Capabilities](./kibana-plugin-public.capabilities.md) | The read-only set of capabilities available for the current UI session. Capabilities are simple key-value pairs of (string, boolean), where the string denotes the capability ID, and the boolean is a flag indicating if the capability is enabled or disabled. | +| [ChromeBadge](./kibana-plugin-public.chromebadge.md) | | +| [ChromeBrand](./kibana-plugin-public.chromebrand.md) | | +| [ChromeDocTitle](./kibana-plugin-public.chromedoctitle.md) | APIs for accessing and updating the document title. | +| [ChromeHelpExtension](./kibana-plugin-public.chromehelpextension.md) | | +| [ChromeNavControl](./kibana-plugin-public.chromenavcontrol.md) | | +| [ChromeNavControls](./kibana-plugin-public.chromenavcontrols.md) | [APIs](./kibana-plugin-public.chromenavcontrols.md) for registering new controls to be displayed in the navigation bar. | +| [ChromeNavLink](./kibana-plugin-public.chromenavlink.md) | | +| [ChromeNavLinks](./kibana-plugin-public.chromenavlinks.md) | [APIs](./kibana-plugin-public.chromenavlinks.md) for manipulating nav links. | +| [ChromeRecentlyAccessed](./kibana-plugin-public.chromerecentlyaccessed.md) | [APIs](./kibana-plugin-public.chromerecentlyaccessed.md) for recently accessed history. | +| [ChromeRecentlyAccessedHistoryItem](./kibana-plugin-public.chromerecentlyaccessedhistoryitem.md) | | +| [ChromeStart](./kibana-plugin-public.chromestart.md) | ChromeStart allows plugins to customize the global chrome header UI and enrich the UX with additional information about the current location of the browser. | +| [ContextSetup](./kibana-plugin-public.contextsetup.md) | An object that handles registration of context providers and configuring handlers with context. | +| [CoreSetup](./kibana-plugin-public.coresetup.md) | Core services exposed to the Plugin setup lifecycle | +| [CoreStart](./kibana-plugin-public.corestart.md) | Core services exposed to the Plugin start lifecycle | +| [DocLinksStart](./kibana-plugin-public.doclinksstart.md) | | +| [EnvironmentMode](./kibana-plugin-public.environmentmode.md) | | +| [ErrorToastOptions](./kibana-plugin-public.errortoastoptions.md) | Options available for [IToasts](./kibana-plugin-public.itoasts.md) APIs. | +| [FatalErrorInfo](./kibana-plugin-public.fatalerrorinfo.md) | Represents the message and stack of a fatal Error | +| [FatalErrorsSetup](./kibana-plugin-public.fatalerrorssetup.md) | FatalErrors stop the Kibana Public Core and displays a fatal error screen with details about the Kibana build and the error. | +| [HttpErrorRequest](./kibana-plugin-public.httperrorrequest.md) | | +| [HttpErrorResponse](./kibana-plugin-public.httperrorresponse.md) | | +| [HttpFetchOptions](./kibana-plugin-public.httpfetchoptions.md) | All options that may be used with a [HttpHandler](./kibana-plugin-public.httphandler.md). | +| [HttpFetchQuery](./kibana-plugin-public.httpfetchquery.md) | | +| [HttpHandler](./kibana-plugin-public.httphandler.md) | A function for making an HTTP requests to Kibana's backend. See [HttpFetchOptions](./kibana-plugin-public.httpfetchoptions.md) for options and [IHttpResponse](./kibana-plugin-public.ihttpresponse.md) for the response. | +| [HttpHeadersInit](./kibana-plugin-public.httpheadersinit.md) | | +| [HttpInterceptor](./kibana-plugin-public.httpinterceptor.md) | An object that may define global interceptor functions for different parts of the request and response lifecycle. See [IHttpInterceptController](./kibana-plugin-public.ihttpinterceptcontroller.md). | +| [HttpRequestInit](./kibana-plugin-public.httprequestinit.md) | Fetch API options available to [HttpHandler](./kibana-plugin-public.httphandler.md)s. | +| [HttpSetup](./kibana-plugin-public.httpsetup.md) | | +| [I18nStart](./kibana-plugin-public.i18nstart.md) | I18nStart.Context is required by any localizable React component from @kbn/i18n and @elastic/eui packages and is supposed to be used as the topmost component for any i18n-compatible React tree. | +| [IAnonymousPaths](./kibana-plugin-public.ianonymouspaths.md) | APIs for denoting paths as not requiring authentication | +| [IBasePath](./kibana-plugin-public.ibasepath.md) | APIs for manipulating the basePath on URL segments. | +| [IContextContainer](./kibana-plugin-public.icontextcontainer.md) | An object that handles registration of context providers and configuring handlers with context. | +| [IHttpFetchError](./kibana-plugin-public.ihttpfetcherror.md) | | +| [IHttpInterceptController](./kibana-plugin-public.ihttpinterceptcontroller.md) | Used to halt a request Promise chain in a [HttpInterceptor](./kibana-plugin-public.httpinterceptor.md). | +| [IHttpResponse](./kibana-plugin-public.ihttpresponse.md) | | +| [IHttpResponseInterceptorOverrides](./kibana-plugin-public.ihttpresponseinterceptoroverrides.md) | Properties that can be returned by HttpInterceptor.request to override the response. | +| [IUiSettingsClient](./kibana-plugin-public.iuisettingsclient.md) | Client-side client that provides access to the advanced settings stored in elasticsearch. The settings provide control over the behavior of the Kibana application. For example, a user can specify how to display numeric or date fields. Users can adjust the settings via Management UI. [IUiSettingsClient](./kibana-plugin-public.iuisettingsclient.md) | +| [LegacyCoreSetup](./kibana-plugin-public.legacycoresetup.md) | Setup interface exposed to the legacy platform via the ui/new_platform module. | +| [LegacyCoreStart](./kibana-plugin-public.legacycorestart.md) | Start interface exposed to the legacy platform via the ui/new_platform module. | +| [LegacyNavLink](./kibana-plugin-public.legacynavlink.md) | | +| [NotificationsSetup](./kibana-plugin-public.notificationssetup.md) | | +| [NotificationsStart](./kibana-plugin-public.notificationsstart.md) | | +| [OverlayBannersStart](./kibana-plugin-public.overlaybannersstart.md) | | +| [OverlayRef](./kibana-plugin-public.overlayref.md) | Returned by [OverlayStart](./kibana-plugin-public.overlaystart.md) methods for closing a mounted overlay. | +| [OverlayStart](./kibana-plugin-public.overlaystart.md) | | +| [PackageInfo](./kibana-plugin-public.packageinfo.md) | | +| [Plugin](./kibana-plugin-public.plugin.md) | The interface that should be returned by a PluginInitializer. | +| [PluginInitializerContext](./kibana-plugin-public.plugininitializercontext.md) | The available core services passed to a PluginInitializer | +| [SavedObject](./kibana-plugin-public.savedobject.md) | | +| [SavedObjectAttributes](./kibana-plugin-public.savedobjectattributes.md) | The data for a Saved Object is stored as an object in the attributes property. | +| [SavedObjectReference](./kibana-plugin-public.savedobjectreference.md) | A reference to another saved object. | +| [SavedObjectsBaseOptions](./kibana-plugin-public.savedobjectsbaseoptions.md) | | +| [SavedObjectsBatchResponse](./kibana-plugin-public.savedobjectsbatchresponse.md) | | +| [SavedObjectsBulkCreateObject](./kibana-plugin-public.savedobjectsbulkcreateobject.md) | | +| [SavedObjectsBulkCreateOptions](./kibana-plugin-public.savedobjectsbulkcreateoptions.md) | | +| [SavedObjectsBulkUpdateObject](./kibana-plugin-public.savedobjectsbulkupdateobject.md) | | +| [SavedObjectsBulkUpdateOptions](./kibana-plugin-public.savedobjectsbulkupdateoptions.md) | | +| [SavedObjectsCreateOptions](./kibana-plugin-public.savedobjectscreateoptions.md) | | +| [SavedObjectsFindOptions](./kibana-plugin-public.savedobjectsfindoptions.md) | | +| [SavedObjectsFindResponsePublic](./kibana-plugin-public.savedobjectsfindresponsepublic.md) | Return type of the Saved Objects find() method.\*Note\*: this type is different between the Public and Server Saved Objects clients. | +| [SavedObjectsImportConflictError](./kibana-plugin-public.savedobjectsimportconflicterror.md) | Represents a failure to import due to a conflict. | +| [SavedObjectsImportError](./kibana-plugin-public.savedobjectsimporterror.md) | Represents a failure to import. | +| [SavedObjectsImportMissingReferencesError](./kibana-plugin-public.savedobjectsimportmissingreferenceserror.md) | Represents a failure to import due to missing references. | +| [SavedObjectsImportResponse](./kibana-plugin-public.savedobjectsimportresponse.md) | The response describing the result of an import. | +| [SavedObjectsImportRetry](./kibana-plugin-public.savedobjectsimportretry.md) | Describes a retry operation for importing a saved object. | +| [SavedObjectsImportUnknownError](./kibana-plugin-public.savedobjectsimportunknownerror.md) | Represents a failure to import due to an unknown reason. | +| [SavedObjectsImportUnsupportedTypeError](./kibana-plugin-public.savedobjectsimportunsupportedtypeerror.md) | Represents a failure to import due to having an unsupported saved object type. | +| [SavedObjectsMigrationVersion](./kibana-plugin-public.savedobjectsmigrationversion.md) | Information about the migrations that have been applied to this SavedObject. When Kibana starts up, KibanaMigrator detects outdated documents and migrates them based on this value. For each migration that has been applied, the plugin's name is used as a key and the latest migration version as the value. | +| [SavedObjectsStart](./kibana-plugin-public.savedobjectsstart.md) | | +| [SavedObjectsUpdateOptions](./kibana-plugin-public.savedobjectsupdateoptions.md) | | +| [UiSettingsState](./kibana-plugin-public.uisettingsstate.md) | | + +## Type Aliases + +| Type Alias | Description | +| --- | --- | +| [AppLeaveAction](./kibana-plugin-public.appleaveaction.md) | Possible actions to return from a [AppLeaveHandler](./kibana-plugin-public.appleavehandler.md)See [AppLeaveConfirmAction](./kibana-plugin-public.appleaveconfirmaction.md) and [AppLeaveDefaultAction](./kibana-plugin-public.appleavedefaultaction.md) | +| [AppLeaveHandler](./kibana-plugin-public.appleavehandler.md) | A handler that will be executed before leaving the application, either when going to another application or when closing the browser tab or manually changing the url. Should return confirm to to prompt a message to the user before leaving the page, or default to keep the default behavior (doing nothing).See [AppMountParameters](./kibana-plugin-public.appmountparameters.md) for detailed usage examples. | +| [AppMount](./kibana-plugin-public.appmount.md) | A mount function called when the user navigates to this app's route. | +| [AppMountDeprecated](./kibana-plugin-public.appmountdeprecated.md) | A mount function called when the user navigates to this app's route. | +| [AppUnmount](./kibana-plugin-public.appunmount.md) | A function called when an application should be unmounted from the page. This function should be synchronous. | +| [AppUpdatableFields](./kibana-plugin-public.appupdatablefields.md) | Defines the list of fields that can be updated via an [AppUpdater](./kibana-plugin-public.appupdater.md). | +| [AppUpdater](./kibana-plugin-public.appupdater.md) | Updater for applications. see [ApplicationSetup](./kibana-plugin-public.applicationsetup.md) | +| [ChromeBreadcrumb](./kibana-plugin-public.chromebreadcrumb.md) | | +| [ChromeHelpExtensionMenuCustomLink](./kibana-plugin-public.chromehelpextensionmenucustomlink.md) | | +| [ChromeHelpExtensionMenuDiscussLink](./kibana-plugin-public.chromehelpextensionmenudiscusslink.md) | | +| [ChromeHelpExtensionMenuDocumentationLink](./kibana-plugin-public.chromehelpextensionmenudocumentationlink.md) | | +| [ChromeHelpExtensionMenuGitHubLink](./kibana-plugin-public.chromehelpextensionmenugithublink.md) | | +| [ChromeHelpExtensionMenuLink](./kibana-plugin-public.chromehelpextensionmenulink.md) | | +| [ChromeNavLinkUpdateableFields](./kibana-plugin-public.chromenavlinkupdateablefields.md) | | +| [HandlerContextType](./kibana-plugin-public.handlercontexttype.md) | Extracts the type of the first argument of a [HandlerFunction](./kibana-plugin-public.handlerfunction.md) to represent the type of the context. | +| [HandlerFunction](./kibana-plugin-public.handlerfunction.md) | A function that accepts a context object and an optional number of additional arguments. Used for the generic types in [IContextContainer](./kibana-plugin-public.icontextcontainer.md) | +| [HandlerParameters](./kibana-plugin-public.handlerparameters.md) | Extracts the types of the additional arguments of a [HandlerFunction](./kibana-plugin-public.handlerfunction.md), excluding the [HandlerContextType](./kibana-plugin-public.handlercontexttype.md). | +| [HttpStart](./kibana-plugin-public.httpstart.md) | See [HttpSetup](./kibana-plugin-public.httpsetup.md) | +| [IContextProvider](./kibana-plugin-public.icontextprovider.md) | A function that returns a context value for a specific key of given context type. | +| [IToasts](./kibana-plugin-public.itoasts.md) | Methods for adding and removing global toast messages. See [ToastsApi](./kibana-plugin-public.toastsapi.md). | +| [MountPoint](./kibana-plugin-public.mountpoint.md) | A function that should mount DOM content inside the provided container element and return a handler to unmount it. | +| [PluginInitializer](./kibana-plugin-public.plugininitializer.md) | The plugin export at the root of a plugin's public directory should conform to this interface. | +| [PluginOpaqueId](./kibana-plugin-public.pluginopaqueid.md) | | +| [RecursiveReadonly](./kibana-plugin-public.recursivereadonly.md) | | +| [SavedObjectAttribute](./kibana-plugin-public.savedobjectattribute.md) | Type definition for a Saved Object attribute value | +| [SavedObjectAttributeSingle](./kibana-plugin-public.savedobjectattributesingle.md) | Don't use this type, it's simply a helper type for [SavedObjectAttribute](./kibana-plugin-public.savedobjectattribute.md) | +| [SavedObjectsClientContract](./kibana-plugin-public.savedobjectsclientcontract.md) | SavedObjectsClientContract as implemented by the [SavedObjectsClient](./kibana-plugin-public.savedobjectsclient.md) | +| [Toast](./kibana-plugin-public.toast.md) | | +| [ToastInput](./kibana-plugin-public.toastinput.md) | Inputs for [IToasts](./kibana-plugin-public.itoasts.md) APIs. | +| [ToastInputFields](./kibana-plugin-public.toastinputfields.md) | Allowed fields for [ToastInput](./kibana-plugin-public.toastinput.md). | +| [ToastsSetup](./kibana-plugin-public.toastssetup.md) | [IToasts](./kibana-plugin-public.itoasts.md) | +| [ToastsStart](./kibana-plugin-public.toastsstart.md) | [IToasts](./kibana-plugin-public.itoasts.md) | +| [UnmountCallback](./kibana-plugin-public.unmountcallback.md) | A function that will unmount the element previously mounted by the associated [MountPoint](./kibana-plugin-public.mountpoint.md) | + diff --git a/docs/development/core/public/kibana-plugin-public.overlaystart.md b/docs/development/core/public/kibana-plugin-public.overlaystart.md index 8b6f11bd819f8..a83044763344b 100644 --- a/docs/development/core/public/kibana-plugin-public.overlaystart.md +++ b/docs/development/core/public/kibana-plugin-public.overlaystart.md @@ -16,6 +16,7 @@ export interface OverlayStart | Property | Type | Description | | --- | --- | --- | | [banners](./kibana-plugin-public.overlaystart.banners.md) | OverlayBannersStart | [OverlayBannersStart](./kibana-plugin-public.overlaybannersstart.md) | +| [openConfirm](./kibana-plugin-public.overlaystart.openconfirm.md) | OverlayModalStart['openConfirm'] | | | [openFlyout](./kibana-plugin-public.overlaystart.openflyout.md) | OverlayFlyoutStart['open'] | | | [openModal](./kibana-plugin-public.overlaystart.openmodal.md) | OverlayModalStart['open'] | | diff --git a/docs/development/core/public/kibana-plugin-public.overlaystart.openconfirm.md b/docs/development/core/public/kibana-plugin-public.overlaystart.openconfirm.md new file mode 100644 index 0000000000000..543a69e0b3318 --- /dev/null +++ b/docs/development/core/public/kibana-plugin-public.overlaystart.openconfirm.md @@ -0,0 +1,12 @@ + + +[Home](./index.md) > [kibana-plugin-public](./kibana-plugin-public.md) > [OverlayStart](./kibana-plugin-public.overlaystart.md) > [openConfirm](./kibana-plugin-public.overlaystart.openconfirm.md) + +## OverlayStart.openConfirm property + + +Signature: + +```typescript +openConfirm: OverlayModalStart['openConfirm']; +``` 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 6033c667c1866..88485aa71f7c5 100644 --- a/docs/development/core/public/kibana-plugin-public.savedobjectsclient.md +++ b/docs/development/core/public/kibana-plugin-public.savedobjectsclient.md @@ -12,6 +12,10 @@ Saved Objects is Kibana's data persisentence mechanism allowing plugins to use E export declare class SavedObjectsClient ``` +## Remarks + +The constructor for this class is marked as internal. Third-party code should not call the constructor directly or create subclasses that extend the `SavedObjectsClient` class. + ## Properties | Property | Modifiers | Type | Description | @@ -20,7 +24,7 @@ export declare class SavedObjectsClient | [bulkGet](./kibana-plugin-public.savedobjectsclient.bulkget.md) | | (objects?: {
id: string;
type: string;
}[]) => Promise<SavedObjectsBatchResponse<SavedObjectAttributes>> | Returns an array of objects by id | | [create](./kibana-plugin-public.savedobjectsclient.create.md) | | <T extends SavedObjectAttributes>(type: string, attributes: T, options?: SavedObjectsCreateOptions) => Promise<SimpleSavedObject<T>> | Persists an object | | [delete](./kibana-plugin-public.savedobjectsclient.delete.md) | | (type: string, id: string) => Promise<{}> | Deletes an object | -| [find](./kibana-plugin-public.savedobjectsclient.find.md) | | <T extends SavedObjectAttributes>(options: Pick<SavedObjectFindOptionsServer, "search" | "filter" | "type" | "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 @@ -30,7 +34,3 @@ export declare class SavedObjectsClient | [bulkUpdate(objects)](./kibana-plugin-public.savedobjectsclient.bulkupdate.md) | | Update multiple documents at once | | [update(type, id, attributes, { version, migrationVersion, references })](./kibana-plugin-public.savedobjectsclient.update.md) | | Updates an object | -## Remarks - -The constructor for this class is marked as internal. Third-party code should not call the constructor directly or create subclasses that extend the `SavedObjectsClient` class. - diff --git a/docs/development/core/server/kibana-plugin-server.basepath.get.md b/docs/development/core/server/kibana-plugin-server.basepath.get.md index 6ef7022f10e62..a20bc1a4e3174 100644 --- a/docs/development/core/server/kibana-plugin-server.basepath.get.md +++ b/docs/development/core/server/kibana-plugin-server.basepath.get.md @@ -9,5 +9,5 @@ returns `basePath` value, specific for an incoming request. Signature: ```typescript -get: (request: KibanaRequest | LegacyRequest) => string; +get: (request: LegacyRequest | KibanaRequest) => string; ``` diff --git a/docs/development/core/server/kibana-plugin-server.basepath.md b/docs/development/core/server/kibana-plugin-server.basepath.md index 77f50abc60369..63aeb7f711d97 100644 --- a/docs/development/core/server/kibana-plugin-server.basepath.md +++ b/docs/development/core/server/kibana-plugin-server.basepath.md @@ -12,17 +12,17 @@ Access or manipulate the Kibana base path export declare class BasePath ``` +## Remarks + +The constructor for this class is marked as internal. Third-party code should not call the constructor directly or create subclasses that extend the `BasePath` class. + ## Properties | Property | Modifiers | Type | Description | | --- | --- | --- | --- | -| [get](./kibana-plugin-server.basepath.get.md) | | (request: KibanaRequest<unknown, unknown, unknown, any> | LegacyRequest) => string | returns basePath value, specific for an incoming request. | +| [get](./kibana-plugin-server.basepath.get.md) | | (request: LegacyRequest | KibanaRequest<unknown, unknown, unknown, any>) => string | returns basePath value, specific for an incoming request. | | [prepend](./kibana-plugin-server.basepath.prepend.md) | | (path: string) => string | Prepends path with the basePath. | | [remove](./kibana-plugin-server.basepath.remove.md) | | (path: string) => string | Removes the prepended basePath from the path. | | [serverBasePath](./kibana-plugin-server.basepath.serverbasepath.md) | | string | returns the server's basePathSee [BasePath.get](./kibana-plugin-server.basepath.get.md) for getting the basePath value for a specific request | -| [set](./kibana-plugin-server.basepath.set.md) | | (request: KibanaRequest<unknown, unknown, unknown, any> | LegacyRequest, requestSpecificBasePath: string) => void | sets basePath value, specific for an incoming request. | - -## Remarks - -The constructor for this class is marked as internal. Third-party code should not call the constructor directly or create subclasses that extend the `BasePath` class. +| [set](./kibana-plugin-server.basepath.set.md) | | (request: LegacyRequest | KibanaRequest<unknown, unknown, unknown, any>, requestSpecificBasePath: string) => void | sets basePath value, specific for an incoming request. | diff --git a/docs/development/core/server/kibana-plugin-server.basepath.set.md b/docs/development/core/server/kibana-plugin-server.basepath.set.md index 56a7f644d34cc..ac08baa0bb99e 100644 --- a/docs/development/core/server/kibana-plugin-server.basepath.set.md +++ b/docs/development/core/server/kibana-plugin-server.basepath.set.md @@ -9,5 +9,5 @@ sets `basePath` value, specific for an incoming request. Signature: ```typescript -set: (request: KibanaRequest | LegacyRequest, requestSpecificBasePath: string) => void; +set: (request: LegacyRequest | KibanaRequest, requestSpecificBasePath: string) => void; ``` 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.configdeprecationfactory.md b/docs/development/core/server/kibana-plugin-server.configdeprecationfactory.md index f022d6c1d064a..0302797147cff 100644 --- a/docs/development/core/server/kibana-plugin-server.configdeprecationfactory.md +++ b/docs/development/core/server/kibana-plugin-server.configdeprecationfactory.md @@ -14,15 +14,6 @@ See methods documentation for more detailed examples. export interface ConfigDeprecationFactory ``` -## Methods - -| Method | Description | -| --- | --- | -| [rename(oldKey, newKey)](./kibana-plugin-server.configdeprecationfactory.rename.md) | Rename a configuration property from inside a plugin's configuration path. Will log a deprecation warning if the oldKey was found and deprecation applied. | -| [renameFromRoot(oldKey, newKey)](./kibana-plugin-server.configdeprecationfactory.renamefromroot.md) | Rename a configuration property from the root configuration. Will log a deprecation warning if the oldKey was found and deprecation applied.This should be only used when renaming properties from different configuration's path. To rename properties from inside a plugin's configuration, use 'rename' instead. | -| [unused(unusedKey)](./kibana-plugin-server.configdeprecationfactory.unused.md) | Remove a configuration property from inside a plugin's configuration path. Will log a deprecation warning if the unused key was found and deprecation applied. | -| [unusedFromRoot(unusedKey)](./kibana-plugin-server.configdeprecationfactory.unusedfromroot.md) | Remove a configuration property from the root configuration. Will log a deprecation warning if the unused key was found and deprecation applied.This should be only used when removing properties from outside of a plugin's configuration. To remove properties from inside a plugin's configuration, use 'unused' instead. | - ## Example @@ -34,3 +25,12 @@ const provider: ConfigDeprecationProvider = ({ rename, unused }) => [ ``` +## Methods + +| Method | Description | +| --- | --- | +| [rename(oldKey, newKey)](./kibana-plugin-server.configdeprecationfactory.rename.md) | Rename a configuration property from inside a plugin's configuration path. Will log a deprecation warning if the oldKey was found and deprecation applied. | +| [renameFromRoot(oldKey, newKey)](./kibana-plugin-server.configdeprecationfactory.renamefromroot.md) | Rename a configuration property from the root configuration. Will log a deprecation warning if the oldKey was found and deprecation applied.This should be only used when renaming properties from different configuration's path. To rename properties from inside a plugin's configuration, use 'rename' instead. | +| [unused(unusedKey)](./kibana-plugin-server.configdeprecationfactory.unused.md) | Remove a configuration property from inside a plugin's configuration path. Will log a deprecation warning if the unused key was found and deprecation applied. | +| [unusedFromRoot(unusedKey)](./kibana-plugin-server.configdeprecationfactory.unusedfromroot.md) | Remove a configuration property from the root configuration. Will log a deprecation warning if the unused key was found and deprecation applied.This should be only used when removing properties from outside of a plugin's configuration. To remove properties from inside a plugin's configuration, use 'unused' instead. | + diff --git a/docs/development/core/server/kibana-plugin-server.contextsetup.md b/docs/development/core/server/kibana-plugin-server.contextsetup.md index 1f285efe92b68..1b2a1e2f1b621 100644 --- a/docs/development/core/server/kibana-plugin-server.contextsetup.md +++ b/docs/development/core/server/kibana-plugin-server.contextsetup.md @@ -12,12 +12,6 @@ An object that handles registration of context providers and configuring handler export interface ContextSetup ``` -## Methods - -| Method | Description | -| --- | --- | -| [createContextContainer()](./kibana-plugin-server.contextsetup.createcontextcontainer.md) | Creates a new [IContextContainer](./kibana-plugin-server.icontextcontainer.md) for a service owner. | - ## Remarks A [IContextContainer](./kibana-plugin-server.icontextcontainer.md) can be used by any Core service or plugin (known as the "service owner") which wishes to expose APIs in a handler function. The container object will manage registering context providers and configuring a handler with all of the contexts that should be exposed to the handler's plugin. This is dependent on the dependencies that the handler's plugin declares. @@ -136,3 +130,9 @@ class VizRenderingPlugin { ``` +## Methods + +| Method | Description | +| --- | --- | +| [createContextContainer()](./kibana-plugin-server.contextsetup.createcontextcontainer.md) | Creates a new [IContextContainer](./kibana-plugin-server.icontextcontainer.md) for a service owner. | + diff --git a/docs/development/core/server/kibana-plugin-server.cspconfig.md b/docs/development/core/server/kibana-plugin-server.cspconfig.md index e5276991be404..7e491cb0df912 100644 --- a/docs/development/core/server/kibana-plugin-server.cspconfig.md +++ b/docs/development/core/server/kibana-plugin-server.cspconfig.md @@ -12,6 +12,10 @@ CSP configuration for use in Kibana. export declare class CspConfig implements ICspConfig ``` +## Remarks + +The constructor for this class is marked as internal. Third-party code should not call the constructor directly or create subclasses that extend the `CspConfig` class. + ## Properties | Property | Modifiers | Type | Description | @@ -22,7 +26,3 @@ export declare class CspConfig implements ICspConfig | [strict](./kibana-plugin-server.cspconfig.strict.md) | | boolean | | | [warnLegacyBrowsers](./kibana-plugin-server.cspconfig.warnlegacybrowsers.md) | | boolean | | -## Remarks - -The constructor for this class is marked as internal. Third-party code should not call the constructor directly or create subclasses that extend the `CspConfig` class. - diff --git a/docs/development/core/server/kibana-plugin-server.elasticsearcherrorhelpers.md b/docs/development/core/server/kibana-plugin-server.elasticsearcherrorhelpers.md index c823da392042a..2e615acfeac6b 100644 --- a/docs/development/core/server/kibana-plugin-server.elasticsearcherrorhelpers.md +++ b/docs/development/core/server/kibana-plugin-server.elasticsearcherrorhelpers.md @@ -12,13 +12,6 @@ Helpers for working with errors returned from the Elasticsearch service.Since th export declare class ElasticsearchErrorHelpers ``` -## Methods - -| Method | Modifiers | Description | -| --- | --- | --- | -| [decorateNotAuthorizedError(error, reason)](./kibana-plugin-server.elasticsearcherrorhelpers.decoratenotauthorizederror.md) | static | | -| [isNotAuthorizedError(error)](./kibana-plugin-server.elasticsearcherrorhelpers.isnotauthorizederror.md) | static | | - ## Example Handle errors @@ -33,3 +26,10 @@ try { ``` +## Methods + +| Method | Modifiers | Description | +| --- | --- | --- | +| [decorateNotAuthorizedError(error, reason)](./kibana-plugin-server.elasticsearcherrorhelpers.decoratenotauthorizederror.md) | static | | +| [isNotAuthorizedError(error)](./kibana-plugin-server.elasticsearcherrorhelpers.isnotauthorizederror.md) | static | | + 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.httpservicesetup.md b/docs/development/core/server/kibana-plugin-server.httpservicesetup.md index 99d4caf40c0d3..3b1993841339d 100644 --- a/docs/development/core/server/kibana-plugin-server.httpservicesetup.md +++ b/docs/development/core/server/kibana-plugin-server.httpservicesetup.md @@ -12,21 +12,6 @@ Kibana HTTP Service provides own abstraction for work with HTTP stack. Plugins d export interface HttpServiceSetup ``` -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [basePath](./kibana-plugin-server.httpservicesetup.basepath.md) | IBasePath | Access or manipulate the Kibana base path See [IBasePath](./kibana-plugin-server.ibasepath.md). | -| [createCookieSessionStorageFactory](./kibana-plugin-server.httpservicesetup.createcookiesessionstoragefactory.md) | <T>(cookieOptions: SessionStorageCookieOptions<T>) => Promise<SessionStorageFactory<T>> | Creates cookie based session storage factory [SessionStorageFactory](./kibana-plugin-server.sessionstoragefactory.md) | -| [createRouter](./kibana-plugin-server.httpservicesetup.createrouter.md) | () => IRouter | Provides ability to declare a handler function for a particular path and HTTP request method. | -| [csp](./kibana-plugin-server.httpservicesetup.csp.md) | ICspConfig | The CSP config used for Kibana. | -| [isTlsEnabled](./kibana-plugin-server.httpservicesetup.istlsenabled.md) | boolean | Flag showing whether a server was configured to use TLS connection. | -| [registerAuth](./kibana-plugin-server.httpservicesetup.registerauth.md) | (handler: AuthenticationHandler) => void | To define custom authentication and/or authorization mechanism for incoming requests. | -| [registerOnPostAuth](./kibana-plugin-server.httpservicesetup.registeronpostauth.md) | (handler: OnPostAuthHandler) => void | To define custom logic to perform for incoming requests. | -| [registerOnPreAuth](./kibana-plugin-server.httpservicesetup.registeronpreauth.md) | (handler: OnPreAuthHandler) => void | To define custom logic to perform for incoming requests. | -| [registerOnPreResponse](./kibana-plugin-server.httpservicesetup.registeronpreresponse.md) | (handler: OnPreResponseHandler) => void | To define custom logic to perform for the server response. | -| [registerRouteHandlerContext](./kibana-plugin-server.httpservicesetup.registerroutehandlercontext.md) | <T extends keyof RequestHandlerContext>(contextName: T, provider: RequestHandlerContextProvider<T>) => RequestHandlerContextContainer | Register a context provider for a route handler. | - ## Example To handle an incoming request in your plugin you should: - Create a `Router` instance. @@ -92,3 +77,18 @@ async (context, request, response) => { ``` +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [basePath](./kibana-plugin-server.httpservicesetup.basepath.md) | IBasePath | Access or manipulate the Kibana base path See [IBasePath](./kibana-plugin-server.ibasepath.md). | +| [createCookieSessionStorageFactory](./kibana-plugin-server.httpservicesetup.createcookiesessionstoragefactory.md) | <T>(cookieOptions: SessionStorageCookieOptions<T>) => Promise<SessionStorageFactory<T>> | Creates cookie based session storage factory [SessionStorageFactory](./kibana-plugin-server.sessionstoragefactory.md) | +| [createRouter](./kibana-plugin-server.httpservicesetup.createrouter.md) | () => IRouter | Provides ability to declare a handler function for a particular path and HTTP request method. | +| [csp](./kibana-plugin-server.httpservicesetup.csp.md) | ICspConfig | The CSP config used for Kibana. | +| [isTlsEnabled](./kibana-plugin-server.httpservicesetup.istlsenabled.md) | boolean | Flag showing whether a server was configured to use TLS connection. | +| [registerAuth](./kibana-plugin-server.httpservicesetup.registerauth.md) | (handler: AuthenticationHandler) => void | To define custom authentication and/or authorization mechanism for incoming requests. | +| [registerOnPostAuth](./kibana-plugin-server.httpservicesetup.registeronpostauth.md) | (handler: OnPostAuthHandler) => void | To define custom logic to perform for incoming requests. | +| [registerOnPreAuth](./kibana-plugin-server.httpservicesetup.registeronpreauth.md) | (handler: OnPreAuthHandler) => void | To define custom logic to perform for incoming requests. | +| [registerOnPreResponse](./kibana-plugin-server.httpservicesetup.registeronpreresponse.md) | (handler: OnPreResponseHandler) => void | To define custom logic to perform for the server response. | +| [registerRouteHandlerContext](./kibana-plugin-server.httpservicesetup.registerroutehandlercontext.md) | <T extends keyof RequestHandlerContext>(contextName: T, provider: RequestHandlerContextProvider<T>) => RequestHandlerContextContainer | Register a context provider for a route handler. | + 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.icontextcontainer.md b/docs/development/core/server/kibana-plugin-server.icontextcontainer.md index 114da31442ff9..8235c40131536 100644 --- a/docs/development/core/server/kibana-plugin-server.icontextcontainer.md +++ b/docs/development/core/server/kibana-plugin-server.icontextcontainer.md @@ -12,13 +12,6 @@ An object that handles registration of context providers and configuring handler export interface IContextContainer> ``` -## Methods - -| Method | Description | -| --- | --- | -| [createHandler(pluginOpaqueId, handler)](./kibana-plugin-server.icontextcontainer.createhandler.md) | Create a new handler function pre-wired to context for the plugin. | -| [registerContext(pluginOpaqueId, contextName, provider)](./kibana-plugin-server.icontextcontainer.registercontext.md) | Register a new context provider. | - ## Remarks A [IContextContainer](./kibana-plugin-server.icontextcontainer.md) can be used by any Core service or plugin (known as the "service owner") which wishes to expose APIs in a handler function. The container object will manage registering context providers and configuring a handler with all of the contexts that should be exposed to the handler's plugin. This is dependent on the dependencies that the handler's plugin declares. @@ -78,3 +71,10 @@ class MyPlugin { ``` +## Methods + +| Method | Description | +| --- | --- | +| [createHandler(pluginOpaqueId, handler)](./kibana-plugin-server.icontextcontainer.createhandler.md) | Create a new handler function pre-wired to context for the plugin. | +| [registerContext(pluginOpaqueId, contextName, provider)](./kibana-plugin-server.icontextcontainer.registercontext.md) | Register a new context provider. | + 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..d7511a119fc0f --- /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.kibanaresponsefactory.md b/docs/development/core/server/kibana-plugin-server.kibanaresponsefactory.md index 85874f5ec16ba..2e496aa0c46fc 100644 --- a/docs/development/core/server/kibana-plugin-server.kibanaresponsefactory.md +++ b/docs/development/core/server/kibana-plugin-server.kibanaresponsefactory.md @@ -10,7 +10,7 @@ Set of helpers used to create `KibanaResponse` to form HTTP response on an incom ```typescript kibanaResponseFactory: { - custom: | Buffer | Stream | { + custom: | { message: string | Error; attributes?: Record | undefined; } | undefined>(options: CustomHttpResponseOptions) => KibanaResponse; @@ -21,9 +21,9 @@ kibanaResponseFactory: { conflict: (options?: ErrorHttpResponseOptions) => KibanaResponse; internalError: (options?: ErrorHttpResponseOptions) => KibanaResponse; customError: (options: CustomHttpResponseOptions) => KibanaResponse; - redirected: (options: RedirectResponseOptions) => KibanaResponse | Buffer | Stream>; - ok: (options?: HttpResponseOptions) => KibanaResponse | Buffer | Stream>; - accepted: (options?: HttpResponseOptions) => KibanaResponse | Buffer | Stream>; + redirected: (options: RedirectResponseOptions) => KibanaResponse>; + ok: (options?: HttpResponseOptions) => KibanaResponse>; + accepted: (options?: HttpResponseOptions) => KibanaResponse>; noContent: (options?: HttpResponseOptions) => KibanaResponse; } ``` diff --git a/docs/development/core/server/kibana-plugin-server.md b/docs/development/core/server/kibana-plugin-server.md index 5e7f84c55244d..5e28643843af3 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.pluginconfigdescriptor.md b/docs/development/core/server/kibana-plugin-server.pluginconfigdescriptor.md index 671298a67381a..3d661ac66d2b7 100644 --- a/docs/development/core/server/kibana-plugin-server.pluginconfigdescriptor.md +++ b/docs/development/core/server/kibana-plugin-server.pluginconfigdescriptor.md @@ -12,14 +12,6 @@ Describes a plugin configuration properties. export interface PluginConfigDescriptor ``` -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [deprecations](./kibana-plugin-server.pluginconfigdescriptor.deprecations.md) | ConfigDeprecationProvider | Provider for the [ConfigDeprecation](./kibana-plugin-server.configdeprecation.md) to apply to the plugin configuration. | -| [exposeToBrowser](./kibana-plugin-server.pluginconfigdescriptor.exposetobrowser.md) | {
[P in keyof T]?: boolean;
} | List of configuration properties that will be available on the client-side plugin. | -| [schema](./kibana-plugin-server.pluginconfigdescriptor.schema.md) | PluginConfigSchema<T> | Schema to use to validate the plugin configuration.[PluginConfigSchema](./kibana-plugin-server.pluginconfigschema.md) | - ## Example @@ -48,3 +40,11 @@ export const config: PluginConfigDescriptor = { ``` +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [deprecations](./kibana-plugin-server.pluginconfigdescriptor.deprecations.md) | ConfigDeprecationProvider | Provider for the [ConfigDeprecation](./kibana-plugin-server.configdeprecation.md) to apply to the plugin configuration. | +| [exposeToBrowser](./kibana-plugin-server.pluginconfigdescriptor.exposetobrowser.md) | {
[P in keyof T]?: boolean;
} | List of configuration properties that will be available on the client-side plugin. | +| [schema](./kibana-plugin-server.pluginconfigdescriptor.schema.md) | PluginConfigSchema<T> | Schema to use to validate the plugin configuration.[PluginConfigSchema](./kibana-plugin-server.pluginconfigschema.md) | + diff --git a/docs/development/core/server/kibana-plugin-server.pluginmanifest.md b/docs/development/core/server/kibana-plugin-server.pluginmanifest.md index 4a9498f0e9fab..9bb208a809b22 100644 --- a/docs/development/core/server/kibana-plugin-server.pluginmanifest.md +++ b/docs/development/core/server/kibana-plugin-server.pluginmanifest.md @@ -12,6 +12,10 @@ Describes the set of required and optional properties plugin can define in its m export interface PluginManifest ``` +## Remarks + +Should never be used in code outside of Core but is exported for documentation purposes. + ## Properties | Property | Type | Description | @@ -25,7 +29,3 @@ export interface PluginManifest | [ui](./kibana-plugin-server.pluginmanifest.ui.md) | boolean | Specifies whether plugin includes some client/browser specific functionality that should be included into client bundle via public/ui_plugin.js file. | | [version](./kibana-plugin-server.pluginmanifest.version.md) | string | Version of the plugin. | -## Remarks - -Should never be used in code outside of Core but is exported for documentation purposes. - diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsclient.md b/docs/development/core/server/kibana-plugin-server.savedobjectsclient.md index 17d29bb912c83..e68486ecff874 100644 --- a/docs/development/core/server/kibana-plugin-server.savedobjectsclient.md +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsclient.md @@ -10,6 +10,10 @@ export declare class SavedObjectsClient ``` +## Remarks + +The constructor for this class is marked as internal. Third-party code should not call the constructor directly or create subclasses that extend the `SavedObjectsClient` class. + ## Properties | Property | Modifiers | Type | Description | @@ -30,7 +34,3 @@ export declare class SavedObjectsClient | [get(type, id, options)](./kibana-plugin-server.savedobjectsclient.get.md) | | Retrieves a single object | | [update(type, id, attributes, options)](./kibana-plugin-server.savedobjectsclient.update.md) | | Updates an SavedObject | -## Remarks - -The constructor for this class is marked as internal. Third-party code should not call the constructor directly or create subclasses that extend the `SavedObjectsClient` class. - diff --git a/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.md b/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.md index dd97b45f590e2..95bd817a43da6 100644 --- a/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.md +++ b/docs/development/core/server/kibana-plugin-server.savedobjectsservicesetup.md @@ -12,15 +12,6 @@ Saved Objects is Kibana's data persisentence mechanism allowing plugins to use E export interface SavedObjectsServiceSetup ``` -## Properties - -| Property | Type | Description | -| --- | --- | --- | -| [addClientWrapper](./kibana-plugin-server.savedobjectsservicesetup.addclientwrapper.md) | (priority: number, id: string, factory: SavedObjectsClientWrapperFactory<KibanaRequest>) => void | Add a client wrapper with the given priority. | -| [createInternalRepository](./kibana-plugin-server.savedobjectsservicesetup.createinternalrepository.md) | (extraTypes?: string[]) => ISavedObjectsRepository | Creates a [Saved Objects repository](./kibana-plugin-server.isavedobjectsrepository.md) that uses the internal Kibana user for authenticating with Elasticsearch. | -| [createScopedRepository](./kibana-plugin-server.savedobjectsservicesetup.createscopedrepository.md) | (req: KibanaRequest, extraTypes?: string[]) => ISavedObjectsRepository | Creates a [Saved Objects repository](./kibana-plugin-server.isavedobjectsrepository.md) that uses the credentials from the passed in request to authenticate with Elasticsearch. | -| [setClientFactory](./kibana-plugin-server.savedobjectsservicesetup.setclientfactory.md) | (customClientFactory: SavedObjectsClientFactory<KibanaRequest>) => void | Set a default factory for creating Saved Objects clients. Only one client factory can be set, subsequent calls to this method will fail. | - ## Remarks Note: The Saved Object setup API's should only be used for creating and registering client wrappers. Constructing a Saved Objects client or repository for use within your own plugin won't have any of the registered wrappers applied and is considered an anti-pattern. Use the Saved Objects client from the [SavedObjectsServiceStart\#getScopedClient](./kibana-plugin-server.savedobjectsservicestart.md) method or the [route handler context](./kibana-plugin-server.requesthandlercontext.md) instead. @@ -33,3 +24,12 @@ import {SavedObjectsClient, CoreSetup} from 'src/core/server'; export class Plugin() { setup: (core: CoreSetup) => { core.savedObjects.setClientFactory(({request: KibanaRequest}) => { return new SavedObjectsClient(core.savedObjects.scopedRepository(request)); }) } } +## Properties + +| Property | Type | Description | +| --- | --- | --- | +| [addClientWrapper](./kibana-plugin-server.savedobjectsservicesetup.addclientwrapper.md) | (priority: number, id: string, factory: SavedObjectsClientWrapperFactory<KibanaRequest>) => void | Add a client wrapper with the given priority. | +| [createInternalRepository](./kibana-plugin-server.savedobjectsservicesetup.createinternalrepository.md) | (extraTypes?: string[]) => ISavedObjectsRepository | Creates a [Saved Objects repository](./kibana-plugin-server.isavedobjectsrepository.md) that uses the internal Kibana user for authenticating with Elasticsearch. | +| [createScopedRepository](./kibana-plugin-server.savedobjectsservicesetup.createscopedrepository.md) | (req: KibanaRequest, extraTypes?: string[]) => ISavedObjectsRepository | Creates a [Saved Objects repository](./kibana-plugin-server.isavedobjectsrepository.md) that uses the credentials from the passed in request to authenticate with Elasticsearch. | +| [setClientFactory](./kibana-plugin-server.savedobjectsservicesetup.setclientfactory.md) | (customClientFactory: SavedObjectsClientFactory<KibanaRequest>) => void | Set a default factory for creating Saved Objects clients. Only one client factory can be set, subsequent calls to this method will fail. | + 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/development/core/server/kibana-plugin-server.uisettingsparams.deprecation.md b/docs/development/core/server/kibana-plugin-server.uisettingsparams.deprecation.md new file mode 100644 index 0000000000000..7ad26b85bf81c --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.uisettingsparams.deprecation.md @@ -0,0 +1,13 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [UiSettingsParams](./kibana-plugin-server.uisettingsparams.md) > [deprecation](./kibana-plugin-server.uisettingsparams.deprecation.md) + +## UiSettingsParams.deprecation property + +optional deprecation information. Used to generate a deprecation warning. + +Signature: + +```typescript +deprecation?: DeprecationSettings; +``` diff --git a/docs/development/core/server/kibana-plugin-server.uisettingsparams.md b/docs/development/core/server/kibana-plugin-server.uisettingsparams.md index a38499e8f37dd..fc2f8038f973f 100644 --- a/docs/development/core/server/kibana-plugin-server.uisettingsparams.md +++ b/docs/development/core/server/kibana-plugin-server.uisettingsparams.md @@ -17,6 +17,7 @@ export interface UiSettingsParams | Property | Type | Description | | --- | --- | --- | | [category](./kibana-plugin-server.uisettingsparams.category.md) | string[] | used to group the configured setting in the UI | +| [deprecation](./kibana-plugin-server.uisettingsparams.deprecation.md) | DeprecationSettings | optional deprecation information. Used to generate a deprecation warning. | | [description](./kibana-plugin-server.uisettingsparams.description.md) | string | description provided to a user in UI | | [name](./kibana-plugin-server.uisettingsparams.name.md) | string | title in the UI | | [optionLabels](./kibana-plugin-server.uisettingsparams.optionlabels.md) | Record<string, string> | text labels for 'select' type UI element | @@ -24,5 +25,6 @@ export interface UiSettingsParams | [readonly](./kibana-plugin-server.uisettingsparams.readonly.md) | boolean | a flag indicating that value cannot be changed | | [requiresPageReload](./kibana-plugin-server.uisettingsparams.requirespagereload.md) | boolean | a flag indicating whether new value applying requires page reloading | | [type](./kibana-plugin-server.uisettingsparams.type.md) | UiSettingsType | defines a type of UI element [UiSettingsType](./kibana-plugin-server.uisettingstype.md) | +| [validation](./kibana-plugin-server.uisettingsparams.validation.md) | ImageValidation | StringValidation | | | [value](./kibana-plugin-server.uisettingsparams.value.md) | SavedObjectAttribute | default value to fall back to if a user doesn't provide any | diff --git a/docs/development/core/server/kibana-plugin-server.uisettingsparams.validation.md b/docs/development/core/server/kibana-plugin-server.uisettingsparams.validation.md new file mode 100644 index 0000000000000..f097f36e999ba --- /dev/null +++ b/docs/development/core/server/kibana-plugin-server.uisettingsparams.validation.md @@ -0,0 +1,11 @@ + + +[Home](./index.md) > [kibana-plugin-server](./kibana-plugin-server.md) > [UiSettingsParams](./kibana-plugin-server.uisettingsparams.md) > [validation](./kibana-plugin-server.uisettingsparams.validation.md) + +## UiSettingsParams.validation property + +Signature: + +```typescript +validation?: ImageValidation | StringValidation; +``` 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/advanced-options.asciidoc b/docs/management/advanced-options.asciidoc index 977a65f62202d..757c6f10f2a99 100644 --- a/docs/management/advanced-options.asciidoc +++ b/docs/management/advanced-options.asciidoc @@ -187,7 +187,8 @@ Refresh the page to apply the changes. === Search settings [horizontal] -`courier:batchSearches`:: When disabled, dashboard panels will load individually, and search requests will terminate when +`courier:batchSearches`:: **Deprecated in 7.6. Starting in 8.0, this setting will be optimized internally.** +When disabled, dashboard panels will load individually, and search requests will terminate when users navigate away or update the query. When enabled, dashboard panels will load together when all of the data is loaded, and searches will not terminate. `courier:customRequestPreference`:: {ref}/search-request-body.html#request-body-search-preference[Request preference] 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/management/managing-licenses.asciidoc b/docs/management/managing-licenses.asciidoc index bbe4b3b68e03b..0ad27e68f7fe9 100644 --- a/docs/management/managing-licenses.asciidoc +++ b/docs/management/managing-licenses.asciidoc @@ -1,28 +1,185 @@ [[managing-licenses]] -== License Management +== License management -When you install {kib}, it generates a Basic license -with no expiration date. Go to *Management > License Management* to view the -status of your license, start a 30-day trial, or install a new license. +When you install the default distribution of {kib}, you receive a basic license +with no expiration date. For the full list of free features that are included in +the basic license, see https://www.elastic.co/subscriptions[the subscription page]. -To learn more about the available license levels, -see https://www.elastic.co/subscriptions[the subscription page]. +If you want to try out the full set of platinum features, you can activate a +30-day trial license. Go to *Management > License Management* to view the +status of your license, start a trial, or install a new license. -You can activate a 30-day trial license to try out the full set of -https://www.elastic.co/subscriptions[Platinum features], including machine learning, -advanced security, alerting, graph capabilities, and more. +NOTE: You can start a trial only if your cluster has not already activated a +trial license for the current major product version. For example, if you have +already activated a trial for v6.0, you cannot start a new trial until +v7.0. You can, however, contact `info@elastic.co` to request an extended trial +license. -When you activate a new license level, new features will appear in the left sidebar +When you activate a new license level, new features appear in the left sidebar of the *Management* page. [role="screenshot"] image::images/management-license.png[] -At the end of the trial period, the Platinum features operate in a -{stack-ov}/license-expiration.html[degraded mode]. You can revert to a Basic -license, extend the trial, or purchase a subscription. +At the end of the trial period, the platinum features operate in a +<>. You can revert to a basic license, +extend the trial, or purchase a subscription. +TIP: If {security-features} are enabled, before you revert to a basic license or +install a gold or platinum license, you must configure Transport Layer Security +(TLS) in {es}. See {ref}/encrypting-communications.html[Encrypting communications]. +{kib} and the {ref}/start-basic.html[start basic API] provide a list of all of +the features that will no longer be supported if you revert to a basic license. -TIP: If {security-features} are enabled, before you revert to a Basic license or install -a Gold or Platinum license, you must configure Transport Layer Security (TLS) in {es}. -See {ref}/encrypting-communications.html[Encrypting communications]. \ No newline at end of file +[discrete] +[[update-license]] +=== Update your license + +You can update your license at runtime without shutting down your {es} nodes. +License updates take effect immediately. The license is provided as a _JSON_ +file that you install in {kib} or by using the +{ref}/update-license.html[update license API]. + +TIP: If you are using a basic or trial license, {security-features} are disabled +by default. In all other licenses, {security-features} are enabled by default; +you must secure the {stack} or disable the {security-features}. + +[discrete] +[[license-expiration]] +=== License expiration + +Your license is time based and expires at a future date. If you're using +{monitor-features} and your license will expire within 30 days, a license +expiration warning is displayed prominently. Warnings are also displayed on +startup and written to the {es} log starting 30 days from the expiration date. +These error messages tell you when the license expires and what features will be +disabled if you do not update the license. + +IMPORTANT: You should update your license as soon as possible. You are +essentially flying blind when running with an expired license. Access to the +cluster health and stats APIs is critical for monitoring and managing an {es} +cluster. + +[discrete] +[[expiration-beats]] +==== Beats + +* Beats will continue to poll centrally-managed configuration. + +[discrete] +[[expiration-elasticsearch]] +==== {es} + +// Upgrade API is disabled +* The deprecation API is disabled. +* SQL support is disabled. +* Aggregations provided by the analytics plugin are no longer usable. + +[discrete] +[[expiration-watcher]] +==== {stack} {alert-features} + +* The PUT and GET watch APIs are disabled. The DELETE watch API continues to work. +* Watches execute and write to the history. +* The actions of the watches do not execute. + +[discrete] +[[expiration-graph]] +==== {stack} {graph-features} + +* Graph explore APIs are disabled. + +[discrete] +[[expiration-ml]] +==== {stack} {ml-features} + +* APIs to create {anomaly-jobs}, open jobs, send data to jobs, create {dfeeds}, +and start {dfeeds} are disabled. +* All started {dfeeds} are stopped. +* All open {anomaly-jobs} are closed. +* APIs to create and start {dfanalytics-jobs} are disabled. +* Existing {anomaly-job} and {dfanalytics-job} results continue to be available +by using {kib} or APIs. + +[discrete] +[[expiration-monitoring]] +==== {stack} {monitor-features} + +* The agent stops collecting cluster and indices metrics. +* The agent stops automatically cleaning indices older than +`xpack.monitoring.history.duration`. + +[discrete] +[[expiration-security]] +==== {stack} {security-features} + +* Cluster health, cluster stats, and indices stats operations are blocked. +* All data operations (read and write) continue to work. + +Once the license expires, calls to the cluster health, cluster stats, and index +stats APIs fail with a `security_exception` and return a 403 HTTP status code. + +[source,sh] +----------------------------------------------------- +{ + "error": { + "root_cause": [ + { + "type": "security_exception", + "reason": "current license is non-compliant for [security]", + "license.expired.feature": "security" + } + ], + "type": "security_exception", + "reason": "current license is non-compliant for [security]", + "license.expired.feature": "security" + }, + "status": 403 +} +----------------------------------------------------- + +This message enables automatic monitoring systems to easily detect the license +failure without immediately impacting other users. + +[discrete] +[[expiration-logstash]] +==== {ls} pipeline management + +* Cannot create new pipelines or edit or delete existing pipelines from the UI. +* Cannot list or view existing pipelines from the UI. +* Cannot run Logstash instances which are registered to listen to existing pipelines. +//TBD: * Logstash will continue to poll centrally-managed pipelines + +[discrete] +[[expiration-kibana]] +==== {kib} + +* Users can still log into {kib}. +* {kib} works for data exploration and visualization, but some features +are disabled. +* The license management UI is available to easily upgrade your license. See +<> and <>. + +[discrete] +[[expiration-reporting]] +==== {kib} {report-features} + +* Reporting is no longer available in {kib}. +* Report generation URLs stop working. +* Existing reports are no longer accessible. + +[discrete] +[[expiration-rollups]] +==== {rollups-cap} + +* {rollup-jobs-cap} cannot be created or started. +* Existing {rollup-jobs} can be stopped and deleted. +* The get rollup caps and rollup search APIs continue to function. + +[discrete] +[[expiration-transforms]] +==== {transforms-cap} + +* {transforms-cap} cannot be created, previewed, started, or updated. +* Existing {transforms} can be stopped and deleted. +* Existing {transform} results continue to be available. diff --git a/docs/management/snapshot-restore/index.asciidoc b/docs/management/snapshot-restore/index.asciidoc index f19aaa122675e..dc722c24af76c 100644 --- a/docs/management/snapshot-restore/index.asciidoc +++ b/docs/management/snapshot-restore/index.asciidoc @@ -21,7 +21,7 @@ With this UI, you can: image:management/snapshot-restore/images/snapshot_list.png["Snapshot list"] Before using this feature, you should be familiar with how snapshots work. -{ref}/modules-snapshots.html[Snapshot and Restore] is a good source for +{ref}/snapshot-restore.html[Snapshot and Restore] is a good source for more detailed information. [float] @@ -35,9 +35,9 @@ registering one. {kib} supports three repository types out of the box: shared file system, read-only URL, and source-only. For more information on these repositories and their settings, -see {ref}/modules-snapshots.html#snapshots-repositories[Repositories]. +see {ref}/snapshots-register-repository.html[Repositories]. To use other repositories, such as S3, see -{ref}/modules-snapshots.html#_repository_plugins[Repository plugins]. +{ref}/snapshots-register-repository.html#snapshots-repository-plugins[Repository plugins]. Once you create a repository, it is listed in the *Repositories* @@ -61,7 +61,7 @@ into each snapshot for further investigation. image:management/snapshot-restore/images/snapshot_details.png["Snapshot details"] If you don’t have any snapshots, you can create them from the {kib} <>. The -{ref}//modules-snapshots.html#snapshots-take-snapshot[snapshot API] +{ref}/snapshots-take-snapshot.html[snapshot API] takes the current state and data in your index or cluster, and then saves it to a shared repository. @@ -162,7 +162,7 @@ Ready to try *Snapshot and Restore*? In this tutorial, you'll learn to: This example shows you how to register a shared file system repository and store snapshots. Before you begin, you must register the location of the repository in the -{ref}/modules-snapshots.html#_shared_file_system_repository[path.repo] setting on +{ref}/snapshots-register-repository.html#snapshots-filesystem-repository[path.repo] setting on your master and data nodes. You can do this in one of two ways: * Edit your `elasticsearch.yml` to include the `path.repo` setting. @@ -197,7 +197,7 @@ The repository currently doesn’t have any snapshots. [float] ==== Add a snapshot to the repository -Use the {ref}//modules-snapshots.html#snapshots-take-snapshot[snapshot API] to create a snapshot. +Use the {ref}/snapshots-take-snapshot.html[snapshot API] to create a snapshot. . Go to *Dev Tools > Console*. . Create the snapshot: @@ -206,7 +206,7 @@ Use the {ref}//modules-snapshots.html#snapshots-take-snapshot[snapshot API] to c PUT /_snapshot/my_backup/2019-04-25_snapshot?wait_for_completion=true + In this example, the snapshot name is `2019-04-25_snapshot`. You can also -use {ref}//date-math-index-names.html[date math expression] for the snapshot name. +use {ref}/date-math-index-names.html[date math expression] for the snapshot name. + [role="screenshot"] image:management/snapshot-restore/images/create_snapshot.png["Create snapshot"] 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/settings/reporting-settings.asciidoc b/docs/settings/reporting-settings.asciidoc index a754f91e9f22a..a9fa2bd18d315 100644 --- a/docs/settings/reporting-settings.asciidoc +++ b/docs/settings/reporting-settings.asciidoc @@ -54,6 +54,14 @@ The protocol for accessing Kibana, typically `http` or `https`. `xpack.reporting.kibanaServer.hostname`:: The hostname for accessing {kib}, if different from the `server.host` value. +[NOTE] +============ +Reporting authenticates requests on the Kibana page only when the hostname matches the +`xpack.reporting.kibanaServer.hostname` setting. Therefore Reporting would fail if the +set value redirects to another server. For that reason, `"0"` is an invalid setting +because, in the Reporting browser, it becomes an automatic redirect to `"0.0.0.0"`. +============ + [float] [[reporting-job-queue-settings]] diff --git a/docs/settings/security-settings.asciidoc b/docs/settings/security-settings.asciidoc index d6dd4378da1b7..16d68a7759f77 100644 --- a/docs/settings/security-settings.asciidoc +++ b/docs/settings/security-settings.asciidoc @@ -45,7 +45,7 @@ if this setting isn't the same for all instances of {kib}. `xpack.security.secureCookies`:: Sets the `secure` flag of the session cookie. The default value is `false`. It -is set to `true` if `server.ssl.certificate` and `server.ssl.key` are set. Set +is automatically set to `true` if `server.ssl.enabled` is set to `true`. Set this to `true` if SSL is configured outside of {kib} (for example, you are routing requests through a load balancer or proxy). diff --git a/docs/setup/production.asciidoc b/docs/setup/production.asciidoc index fed4ba4886bf9..eef2b11e53d85 100644 --- a/docs/setup/production.asciidoc +++ b/docs/setup/production.asciidoc @@ -4,7 +4,8 @@ * <> * <> * <> -* <> +* <> +* <> * <> * <> @@ -18,7 +19,7 @@ While Kibana isn't terribly resource intensive, we still recommend running Kiban separate from your Elasticsearch data or master nodes. To distribute Kibana traffic across the nodes in your Elasticsearch cluster, you can run Kibana and an Elasticsearch client node on the same machine. For more information, see -<>. +<>. [float] [[configuring-kibana-shield]] @@ -63,7 +64,7 @@ csp.strict: true See <>. [float] -[[load-balancing]] +[[load-balancing-es]] === Load Balancing Across Multiple Elasticsearch Nodes If you have multiple nodes in your Elasticsearch cluster, the easiest way to distribute Kibana requests across the nodes is to run an Elasticsearch _Coordinating only_ node on the same machine as Kibana. @@ -110,9 +111,40 @@ transport.tcp.port: 9300 - 9400 elasticsearch.hosts: ["http://localhost:9200"] -------- +[float] +[[load-balancing-kibana]] +=== Load balancing across multiple Kibana instances +To serve multiple Kibana installations behind a load balancer, you must change the configuration. See {kibana-ref}/settings.html[Configuring Kibana] for details on each setting. + +Settings unique across each Kibana instance: +-------- +server.uuid +server.name +-------- + +Settings unique across each host (for example, running multiple installations on the same virtual machine): +-------- +logging.dest +path.data +pid.file +server.port +-------- + +Settings that must be the same: +-------- +xpack.security.encryptionKey //decrypting session cookies +xpack.reporting.encryptionKey //decrypting reports stored in Elasticsearch +-------- + +Separate configuration files can be used from the command line by using the `-c` flag: +-------- +bin/kibana -c config/instance1.yml +bin/kibana -c config/instance2.yml +-------- + [float] [[high-availability]] -=== High Availability Across Multiple Elasticsearch Nodes +=== High availability across multiple Elasticsearch nodes Kibana can be configured to connect to multiple Elasticsearch nodes in the same cluster. In situations where a node becomes unavailable, Kibana will transparently connect to an available node and continue operating. Requests to available hosts will be routed in a round robin fashion. diff --git a/docs/setup/settings.asciidoc b/docs/setup/settings.asciidoc index 01e6bd51ea50b..535ad16978217 100644 --- a/docs/setup/settings.asciidoc +++ b/docs/setup/settings.asciidoc @@ -82,31 +82,52 @@ Elasticsearch nodes on startup. `elasticsearch.sniffOnConnectionFault:`:: *Default: false* Update the list of Elasticsearch nodes immediately following a connection fault. -`elasticsearch.ssl.alwaysPresentCertificate:`:: *Default: false* Controls -whether to always present the certificate specified by -`elasticsearch.ssl.certificate` when requested. This applies to all requests to -Elasticsearch, including requests that are proxied for end-users. Setting this -to `true` when Elasticsearch is using certificates to authenticate users can -lead to proxied requests for end-users being executed as the identity tied to -the configured certificate. - -`elasticsearch.ssl.certificate:` and `elasticsearch.ssl.key:`:: Optional -settings that provide the paths to the PEM-format SSL certificate and key files. -These files are used to verify the identity of Kibana to Elasticsearch and are -required when `xpack.security.http.ssl.client_authentication` in Elasticsearch is -set to `required`. - -`elasticsearch.ssl.certificateAuthorities:`:: Optional setting that enables you -to specify a list of paths to the PEM file for the certificate authority for -your Elasticsearch instance. - -`elasticsearch.ssl.keyPassphrase:`:: The passphrase that will be used to decrypt -the private key. This value is optional as the key may not be encrypted. - -`elasticsearch.ssl.verificationMode:`:: *Default: full* Controls the -verification of certificates presented by Elasticsearch. Valid values are `none`, -`certificate`, and `full`. `full` performs hostname verification, and -`certificate` does not. +`elasticsearch.ssl.alwaysPresentCertificate:`:: *Default: false* Controls whether to always present the certificate specified by +`elasticsearch.ssl.certificate` or `elasticsearch.ssl.keystore.path` when requested. This setting applies to all requests to Elasticsearch, +including requests that are proxied for end users. Setting this to `true` when Elasticsearch is using certificates to authenticate users can +lead to proxied requests for end users being executed as the identity tied to the configured certificate. + +`elasticsearch.ssl.certificate:` and `elasticsearch.ssl.key:`:: Paths to a PEM-encoded X.509 certificate and its private key, respectively. +When `xpack.security.http.ssl.client_authentication` in Elasticsearch is set to `required` or `optional`, the certificate and key are used +to prove Kibana's identity when it makes an outbound request to your Elasticsearch cluster. ++ +-- +NOTE: These settings cannot be used in conjunction with `elasticsearch.ssl.keystore.path`. +-- + +`elasticsearch.ssl.certificateAuthorities:`:: Paths to one or more PEM-encoded X.509 certificates. These certificates may consist of a root +certificate authority (CA), and one or more intermediate CAs, which make up a trusted certificate chain for Kibana. This chain is used to +establish trust when Kibana creates an SSL connection with your Elasticsearch cluster. In addition to this setting, trusted certificates may +be specified via `elasticsearch.ssl.keystore.path` and/or `elasticsearch.ssl.truststore.path`. + +`elasticsearch.ssl.keyPassphrase:`:: The passphrase that will be used to decrypt the private key that is specified via +`elasticsearch.ssl.key`. This value is optional, as the key may not be encrypted. + +`elasticsearch.ssl.keystore.path:`:: Path to a PKCS #12 file that contains an X.509 certificate with its private key. When +`xpack.security.http.ssl.client_authentication` in Elasticsearch is set to `required` or `optional`, the certificate and key are used to +prove Kibana's identity when it makes an outbound request to your Elasticsearch cluster. If the file contains any additional certificates, +those will be used as a trusted certificate chain for your Elasticsearch cluster. This chain is used to establish trust when Kibana creates +an SSL connection with your Elasticsearch cluster. In addition to this setting, trusted certificates may be specified via +`elasticsearch.ssl.certificateAuthorities` and/or `elasticsearch.ssl.truststore.path`. ++ +-- +NOTE: This setting cannot be used in conjunction with `elasticsearch.ssl.certificate` or `elasticsearch.ssl.key`. +-- + +`elasticsearch.ssl.keystore.password:`:: The password that will be used to decrypt the key store and its private key. If your key store has +no password, leave this unset. If your key store has an empty password, set this to `""`. + +`elasticsearch.ssl.truststore.path:`:: Path to a PKCS #12 trust store that contains one or more X.509 certificates. This may consist of a +root certificate authority (CA) and one or more intermediate CAs, which make up a trusted certificate chain for your Elasticsearch cluster. +This chain is used to establish trust when Kibana creates an SSL connection with your Elasticsearch cluster. In addition to this setting, +trusted certificates may be specified via `elasticsearch.ssl.certificateAuthorities` and/or `elasticsearch.ssl.keystore.path`. + +`elasticsearch.ssl.truststore.password:`:: The password that will be used to decrypt the trust store. If your trust store has no password, +leave this unset. If your trust store has an empty password, set this to `""`. + +`elasticsearch.ssl.verificationMode:`:: *Default: full* Controls the verification of certificates presented by Elasticsearch. Valid values +are `none`, `certificate`, and `full`. `full` performs hostname verification and `certificate` does not. This setting is used only when +traffic to Elasticsearch is encrypted, which is specified by using the HTTPS protocol in `elasticsearch.hosts`. `elasticsearch.startupTimeout:`:: *Default: 5000* Time in milliseconds to wait for Elasticsearch at Kibana startup before retrying. @@ -325,11 +346,19 @@ default is `true`. `server.socketTimeout:`:: *Default: "120000"* The number of milliseconds to wait before closing an inactive socket. -`server.ssl.certificate:` and `server.ssl.key:`:: Paths to the PEM-format SSL -certificate and SSL key files, respectively. +`server.ssl.certificate:` and `server.ssl.key:`:: Paths to a PEM-encoded X.509 certificate and its private key, respectively. These are used +when enabling SSL for inbound requests from web browsers to the Kibana server. ++ +-- +NOTE: These settings cannot be used in conjunction with `server.ssl.keystore.path`. +-- -`server.ssl.certificateAuthorities:`:: List of paths to PEM encoded certificate -files that should be trusted. +`server.ssl.certificateAuthorities:`:: Paths to one or more PEM-encoded X.509 certificates. These certificates may consist of a root +certificate authority (CA) and one or more intermediate CAs, which make up a trusted certificate chain for Kibana. This chain is used when a +web browser creates an SSL connection with the Kibana server; the certificate chain is sent to the browser along with the end-entity +certificate to establish trust. This chain is also used to determine whether client certificates should be trusted when PKI authentication +is enabled. In addition to this setting, trusted certificates may be specified via `server.ssl.keystore.path` and/or +`server.ssl.truststore.path`. `server.ssl.cipherSuites:`:: *Default: ECDHE-RSA-AES128-GCM-SHA256, ECDHE-ECDSA-AES128-GCM-SHA256, ECDHE-RSA-AES256-GCM-SHA384, ECDHE-ECDSA-AES256-GCM-SHA384, DHE-RSA-AES128-GCM-SHA256, ECDHE-RSA-AES128-SHA256, DHE-RSA-AES128-SHA256, ECDHE-RSA-AES256-SHA384, DHE-RSA-AES256-SHA384, ECDHE-RSA-AES256-SHA256, DHE-RSA-AES256-SHA256, HIGH,!aNULL, !eNULL, !EXPORT, !DES, !RC4, !MD5, !PSK, !SRP, !CAMELLIA*. Details on the format, and the valid options, are available via the @@ -339,12 +368,36 @@ https://www.openssl.org/docs/man1.0.2/apps/ciphers.html#CIPHER-LIST-FORMAT[OpenS connections. Valid values are `required`, `optional`, and `none`. `required` forces a client to present a certificate, while `optional` requests a client certificate but the client is not required to present one. -`server.ssl.enabled:`:: *Default: "false"* Enables SSL for outgoing requests -from the Kibana server to the browser. When set to `true`, -`server.ssl.certificate` and `server.ssl.key` are required. +`server.ssl.enabled:`:: *Default: "false"* Enables SSL for inbound requests from the browser to the Kibana server. When set to `true`, a +certificate and private key must be provided. These can be specified via `server.ssl.keystore.path` or the combination of +`server.ssl.certificate` and `server.ssl.key`. + +`server.ssl.keyPassphrase:`:: The passphrase that will be used to decrypt the private key that is specified via `server.ssl.key`. This value +is optional, as the key may not be encrypted. + +`server.ssl.keystore.path:`:: Path to a PKCS #12 file that contains an X.509 certificate with its private key. These are used when enabling +SSL for inbound requests from web browsers to the Kibana server. If the file contains any additional certificates, those will be used as a +trusted certificate chain for Kibana. This chain is used when a web browser creates an SSL connection with the Kibana server; the +certificate chain is sent to the browser along with the end-entity certificate to establish trust. This chain is also used to determine +whether client certificates should be trusted when PKI authentication is enabled. In addition to this setting, trusted certificates may be +specified via `server.ssl.certificateAuthorities` and/or `server.ssl.truststore.path`. ++ +-- +NOTE: This setting cannot be used in conjunction with `server.ssl.certificate` or `server.ssl.key`. +-- -`server.ssl.keyPassphrase:`:: The passphrase that will be used to decrypt the -private key. This value is optional as the key may not be encrypted. +`server.ssl.keystore.password:`:: The password that will be used to decrypt the key store and its private key. If your key store has no +password, leave this unset. If your key store has an empty password, set this to `""`. + +`server.ssl.truststore.path:`:: Path to a PKCS #12 trust store that contains one or more X.509 certificates. These certificates may consist +of a root certificate authority (CA) and one or more intermediate CAs, which make up a trusted certificate chain for Kibana. This chain is +used when a web browser creates an SSL connection with the Kibana server; the certificate chain is sent to the browser along with the +end-entity certificate to establish trust. This chain is also used to determine whether client certificates should be trusted when PKI +authentication is enabled. In addition to this setting, trusted certificates may be specified via `server.ssl.certificateAuthorities` and/or +`server.ssl.keystore.path`. + +`server.ssl.truststore.password:`:: The password that will be used to decrypt the trust store. If your trust store has no password, leave +this unset. If your trust store has an empty password, set this to `""`. `server.ssl.redirectHttpFromPort:`:: Kibana will bind to this port and redirect all http requests to https over the port configured as `server.port`. @@ -380,6 +433,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/monitoring/monitoring-kibana.asciidoc b/docs/user/monitoring/monitoring-kibana.asciidoc index d7af0d5c420a1..b5d263aed8346 100644 --- a/docs/user/monitoring/monitoring-kibana.asciidoc +++ b/docs/user/monitoring/monitoring-kibana.asciidoc @@ -96,7 +96,8 @@ used when {kib} sends monitoring data to the production cluster. .. Configure {kib} to encrypt communications between the {kib} server and the production cluster. This set up involves generating a server certificate and setting `server.ssl.*` and `elasticsearch.ssl.certificateAuthorities` settings -in the `kibana.yml` file on the {kib} server. For example: +in the `kibana.yml` file on the {kib} server. For example, using a PEM-formatted +certificate and private key: + -- [source,yaml] @@ -105,14 +106,19 @@ server.ssl.key: /path/to/your/server.key server.ssl.certificate: /path/to/your/server.crt -------------------------------------------------------------------------------- -If you are using your own certificate authority to sign certificates, specify -the location of the PEM file in the `kibana.yml` file: +If you are using your own certificate authority (CA) to sign certificates, +specify the location of the PEM file in the `kibana.yml` file: [source,yaml] -------------------------------------------------------------------------------- elasticsearch.ssl.certificateAuthorities: /path/to/your/cacert.pem -------------------------------------------------------------------------------- +NOTE: Alternatively, the PKCS #12 format can be used for the Kibana certificate +and key, along with any included CA certificates, by setting +`server.ssl.keystore.path`. If your CA certificate chain is in a separate trust +store, you can also use `server.ssl.truststore.path`. + For more information, see <>. -- diff --git a/docs/user/reporting/images/shareable-container.png b/docs/user/reporting/images/shareable-container.png new file mode 100644 index 0000000000000..db5a41dcff471 Binary files /dev/null and b/docs/user/reporting/images/shareable-container.png differ diff --git a/docs/user/reporting/index.asciidoc b/docs/user/reporting/index.asciidoc index 06af9e6038445..fde88130a26b4 100644 --- a/docs/user/reporting/index.asciidoc +++ b/docs/user/reporting/index.asciidoc @@ -6,11 +6,11 @@ -- -You can generate a report that contains a {kib} dashboard, visualization, -saved search, or Canvas workpad. Depending on the object type, you can export the data as +You can generate a report that contains a {kib} dashboard, visualization, +saved search, or Canvas workpad. Depending on the object type, you can export the data as a PDF, PNG, or CSV document, which you can keep for yourself, or share with others. -Reporting is available from the *Share* menu +Reporting is available from the *Share* menu in *Discover*, *Visualize*, *Dashboard*, and *Canvas*. [role="screenshot"] @@ -40,9 +40,9 @@ for an example. [[manually-generate-reports]] == Generate a report manually -. Open the dashboard, visualization, Canvas workpad, or saved search that you want to include in the report. +. Open the dashboard, visualization, Canvas workpad, or saved search that you want to include in the report. -. In the {kib} toolbar, click *Share*. If you are working in Canvas, +. In the {kib} toolbar, click *Share*. If you are working in Canvas, click the share icon image:user/reporting/images/canvas-share-button.png["Canvas Share button"]. . Select the option appropriate for your object. You can export: @@ -55,14 +55,36 @@ click the share icon image:user/reporting/images/canvas-share-button.png["Canvas + A notification appears when the report is complete. +[float] +[[reporting-layout-sizing]] +== Layout and sizing +The layout and size of the PDF or PNG image depends on the {kib} app +with which the Reporting plugin is integrated. For Canvas, the +worksheet dimensions determine the size for Reporting. In other apps, +the dimensions are taken on the fly by looking at +the size of the visualization elements or panels on the page. + +The size dimensions are part of the reporting job parameters. Therefore, to +make the report output larger or smaller, you can change the size of the browser. +This resizes the shareable container before generating the +report, so the desired dimensions are passed in the job parameters. + +In the following {kib} dashboard, the shareable container is highlighted. +The shareable container is captured when you click the +*Generate* or *Copy POST URL* button. It might take some trial and error +before you're satisfied with the layout and dimensions in the resulting +PNG or PDF image. + +[role="screenshot"] +image::user/reporting/images/shareable-container.png["Shareable Container"] + + + [float] [[optimize-pdf]] == Optimize PDF for print—dashboard only -By default, {kib} creates a PDF -using the existing layout and size of the dashboard. To create a -printer-friendly PDF with multiple A4 portrait pages and two visualizations -per page, turn on *Optimize for printing*. +To create a printer-friendly PDF with multiple A4 portrait pages and two visualizations per page, turn on *Optimize for printing*. [role="screenshot"] image::user/reporting/images/preserve-layout-switch.png["Share"] @@ -72,8 +94,8 @@ image::user/reporting/images/preserve-layout-switch.png["Share"] [[manage-report-history]] == View and manage report history -For a list of your reports, go to *Management > Reporting*. -From this view, you can monitor the generation of a report and +For a list of your reports, go to *Management > Reporting*. +From this view, you can monitor the generation of a report and download reports that you previously generated. [float] diff --git a/docs/user/reporting/reporting-troubleshooting.asciidoc b/docs/user/reporting/reporting-troubleshooting.asciidoc index ca7fa6abcc9d9..dc4ffdfebdae9 100644 --- a/docs/user/reporting/reporting-troubleshooting.asciidoc +++ b/docs/user/reporting/reporting-troubleshooting.asciidoc @@ -7,12 +7,20 @@ Having trouble? Here are solutions to common problems you might encounter while using Reporting. +* <> +* <> +* <> +* <> +* <> +* <> +* <> + [float] [[reporting-troubleshooting-system-dependencies]] === System dependencies Reporting launches a "headless" web browser called Chromium on the Kibana server. It is a custom build made by Elastic of an open source project, and it is intended to have minimal dependencies on OS libraries. However, the Kibana server OS might still require additional -dependencies for Chromium. +dependencies to run the Chromium executable. Make sure Kibana server OS has the appropriate packages installed for the distribution. @@ -33,19 +41,30 @@ If you are using Ubuntu/Debian systems, install the following packages: * `fonts-liberation` * `libfontconfig1` +If the system is missing dependencies, then Reporting will fail in a non-deterministic way. {kib} runs a self-test at server startup, and +if it encounters errors, logs them in the Console. Unfortunately, the error message does not include +information about why Chromium failed to run. The most common error message is `Error: connect ECONNREFUSED`, which indicates +that {kib} could not connect to the Chromium process. + +To troubleshoot the problem, start the {kib} server with environment variables that tell Chromium to print verbose logs. See the +<> for more information. + [float] -=== Text is rendered incorrectly in generated reports +[[reporting-troubleshooting-text-incorrect]] +=== Text rendered incorrectly in generated reports If a report label is rendered as an empty rectangle, no system fonts are available. Install at least one font package on the system. If the report is missing certain Chinese, Japanese or Korean characters, ensure that a system font with those characters is installed. [float] +[[reporting-troubleshooting-missing-data]] === Missing data in PDF report of data table visualization There is currently a known limitation with the Data Table visualization that only the first page of data rows, which are the only data visible on the screen, are shown in PDF reports. [float] +[[reporting-troubleshooting-file-permissions]] === File permissions Ensure that the `headless_shell` binary located in your Kibana data directory is owned by the user who is running Kibana, that the user has the execute permission, and if applicable, that the filesystem is mounted with the `exec` option. @@ -63,25 +82,25 @@ Whenever possible, a Reporting error message tries to be as self-explanatory as along with the solution. [float] -==== "Max attempts reached" +==== Max attempts reached There are two primary causes of this error: -. You're creating a PDF of a visualization or dashboard that spans a large amount of data and Kibana is hitting the `xpack.reporting.queue.timeout` +* You're creating a PDF of a visualization or dashboard that spans a large amount of data and Kibana is hitting the `xpack.reporting.queue.timeout` -. Kibana is hosted behind a reverse-proxy, and the <> are not configured correctly +* Kibana is hosted behind a reverse-proxy, and the <> are not configured correctly Create a Markdown visualization and then create a PDF report. If this succeeds, increase the `xpack.reporting.queue.timeout` setting. If the PDF report fails with "Max attempts reached," check your <>. [float] [[reporting-troubleshooting-nss-dependency]] -==== "You must install nss for Reporting to work" +==== You must install nss for Reporting to work Reporting using the Chromium browser relies on the Network Security Service libraries (NSS). Install the appropriate nss package for your distribution. [float] [[reporting-troubleshooting-sandbox-dependency]] -==== "Unable to use Chromium sandbox" +==== Unable to use Chromium sandbox Chromium uses sandboxing techniques that are built on top of operating system primitives. The Linux sandbox depends on user namespaces, which were introduced with the 3.8 Linux kernel. However, many distributions don't have user namespaces enabled by default, or they require the CAP_SYS_ADMIN capability. @@ -90,6 +109,7 @@ Elastic recommends that you research the feasibility of enabling unprivileged us is if you are running Kibana in Docker because the container runs in a user namespace with the built-in seccomp/bpf filters. [float] +[[reporting-troubleshooting-verbose-logs]] === Verbose logs {kib} server logs have a lot of useful information for troubleshooting and understanding how things work. If you're having any issues at all, the full logs from Reporting will be the first place to look. In `kibana.yml`: @@ -101,10 +121,12 @@ logging.verbose: true For more information about logging, see <>. +[float] +[[reporting-troubleshooting-puppeteer-debug-logs]] === Puppeteer debug logs The Chromium browser that {kib} launches on the server is driven by a NodeJS library for Chromium called Puppeteer. The Puppeteer library has its own command-line method to generate its own debug logs, which can sometimes be helpful, particularly to figure out if a problem is -caused by Kibana or Chromium. See more at https://github.com/GoogleChrome/puppeteer/blob/v1.19.0/README.md#debugging-tips +caused by Kibana or Chromium. See more at https://github.com/GoogleChrome/puppeteer/blob/v1.19.0/README.md#debugging-tips[debugging tips]. Using Puppeteer's debug method when launching Kibana would look like: ``` @@ -114,3 +136,14 @@ The internal DevTools protocol traffic will be logged via the `debug` module und The Puppeteer logs are very verbose and could possibly contain sensitive information. Handle the generated output with care. + +[float] +[[reporting-troubleshooting-system-requirements]] +=== System requirements +In Elastic Cloud, the {kib} instances that most configurations provide by default is for 1GB of RAM for the instance. That is enough for +{kib} Reporting when the visualization or dashboard is relatively simple, such as a single pie chart or a dashboard with +a few visualizations. However, certain visualization types incur more load than others. For example, a TSVB panel has a lot of network +requests to render. + +If the {kib} instance doesn't have enough memory to run the report, the report fails with an error such as `Error: Page crashed!` +In this case, try increasing the memory for the {kib} instance to 2GB. diff --git a/docs/user/security/authentication/index.asciidoc b/docs/user/security/authentication/index.asciidoc index 2e2aaf688e8b6..05aabfc343be9 100644 --- a/docs/user/security/authentication/index.asciidoc +++ b/docs/user/security/authentication/index.asciidoc @@ -67,6 +67,9 @@ server.ssl.clientAuthentication: required xpack.security.authc.providers: [pki] -------------------------------------------------------------------------------- +NOTE: Trusted CAs can also be specified in a PKCS #12 keystore bundled with your Kibana server certificate/key using +`server.ssl.keystore.path` or in a separate trust store using `server.ssl.truststore.path`. + PKI support in {kib} is designed to be the primary (or sole) authentication method for users of that {kib} instance. However, you can configure both PKI and Basic authentication for the same {kib} instance: [source,yaml] diff --git a/docs/user/security/index.asciidoc b/docs/user/security/index.asciidoc index eab3833b3f5ae..e3d6e0d97c73a 100644 --- a/docs/user/security/index.asciidoc +++ b/docs/user/security/index.asciidoc @@ -37,4 +37,5 @@ cause Kibana's authorization to behave unexpectedly. include::authorization/index.asciidoc[] include::authorization/kibana-privileges.asciidoc[] include::api-keys/index.asciidoc[] +include::role-mappings/index.asciidoc[] include::rbac_tutorial.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/docs/user/security/role-mappings/images/role-mappings-create-step-1.png b/docs/user/security/role-mappings/images/role-mappings-create-step-1.png new file mode 100644 index 0000000000000..2b4ad16459529 Binary files /dev/null and b/docs/user/security/role-mappings/images/role-mappings-create-step-1.png differ diff --git a/docs/user/security/role-mappings/images/role-mappings-create-step-2.gif b/docs/user/security/role-mappings/images/role-mappings-create-step-2.gif new file mode 100644 index 0000000000000..0a10126ea3cce Binary files /dev/null and b/docs/user/security/role-mappings/images/role-mappings-create-step-2.gif differ diff --git a/docs/user/security/role-mappings/images/role-mappings-grid.png b/docs/user/security/role-mappings/images/role-mappings-grid.png new file mode 100644 index 0000000000000..96c9ee8e4cd95 Binary files /dev/null and b/docs/user/security/role-mappings/images/role-mappings-grid.png differ diff --git a/docs/user/security/role-mappings/index.asciidoc b/docs/user/security/role-mappings/index.asciidoc new file mode 100644 index 0000000000000..01028ab4d59e0 --- /dev/null +++ b/docs/user/security/role-mappings/index.asciidoc @@ -0,0 +1,51 @@ +[role="xpack"] +[[role-mappings]] +=== Role mappings + +Role mappings allow you to describe which roles to assign to your users +using a set of rules. Role mappings are required when authenticating via +an external identity provider, such as Active Directory, Kerberos, PKI, OIDC, +or SAML. + +Role mappings have no effect for users inside the `native` or `file` realms. + +To manage your role mappings, use *Management > Security > Role Mappings*. + +With *Role mappings*, you can: + +* View your configured role mappings +* Create/Edit/Delete role mappings + +[role="screenshot"] +image:user/security/role-mappings/images/role-mappings-grid.png["Role mappings"] + + +[float] +=== Create a role mapping + +To create a role mapping, navigate to *Management > Security > Role Mappings*, and click **Create role mapping**. +Give your role mapping a unique name, and choose which roles you wish to assign to your users. +If you need more flexibility, you can use {ref}/security-api-put-role-mapping.html#_role_templates[role templates] instead. + +Next, define the rules describing which users should receive the roles you defined. Rules can optionally grouped and nested, allowing for sophisticated logic to suite complex requirements. +View the {ref}/role-mapping-resources.html[role mapping resources for an overview of the allowed rule types]. + + +[float] +=== Example + +Let's create a `sales-users` role mapping, which assigns a `sales` role to users whose username +starts with `sls_`, *or* belongs to the `executive` group. + +First, we give the role mapping a name, and assign the `sales` role: + +[role="screenshot"] +image:user/security/role-mappings/images/role-mappings-create-step-1.png["Create role mapping, step 1"] + +Next, we define the two rules, making sure to set the group to *Any are true*: + +[role="screenshot"] +image:user/security/role-mappings/images/role-mappings-create-step-2.gif["Create role mapping, step 2"] + +Click *Save role mapping* once you're finished. + diff --git a/docs/user/security/securing-communications/index.asciidoc b/docs/user/security/securing-communications/index.asciidoc index 6917a48909c7b..b370c35905bce 100644 --- a/docs/user/security/securing-communications/index.asciidoc +++ b/docs/user/security/securing-communications/index.asciidoc @@ -4,121 +4,115 @@ Encrypting communications ++++ -{kib} supports Transport Layer Security (TLS/SSL) encryption for client -requests. -//TBD: It is unclear what "client requests" are in this context. Is it just -// communication between the browser and the Kibana server or are we talking -// about other types of clients connecting to the Kibana server? - -If you are using {security} or a proxy that provides an HTTPS endpoint for {es}, -you can configure {kib} to access {es} via HTTPS. Thus, communications between -{kib} and {es} are also encrypted. - -. Configure {kib} to encrypt communications between the browser and the {kib} -server: +{kib} supports Transport Layer Security (TLS/SSL) encryption for all forms of data-in-transit. Browsers send traffic to {kib} and {kib} +sends traffic to {es}. These communications are configured separately. + +[[configuring-tls-browser-kib]] +==== Encrypting traffic between the browser and {kib} + +NOTE: You do not need to enable {security-features} for this type of encryption. + +. Obtain a server certificate and private key for {kib}. + -- -NOTE: You do not need to enable {security} for this type of encryption. +{kib} supports certificates/keys in both PKCS #12 key stores and PEM format. + +When you obtain a certificate, you must do at least one of the following: + +.. Set the certificate's `subjectAltName` to the hostname, fully-qualified domain name (FQDN), or IP address of the {kib} server. + +.. Set the certificate's Common Name (CN) to the {kib} server's hostname or FQDN. Using the server's IP address as the CN does not work. + +You may choose to generate a certificate and private key using {ref}/certutil.html[the {es} certutil tool]. If you already used certutil to +generate a certificate authority (CA), you would generate a certificate/key for Kibana like so (using the `--dns` param to set the +`subjectAltName`): + +[source,sh] +-------------------------------------------------------------------------------- +bin/elasticsearch-certutil cert --ca elastic-stack-ca.p12 --name kibana --dns localhost +-------------------------------------------------------------------------------- + +This will generate a certificate and private key in a PKCS #12 keystore named `kibana.p12`. -- -.. Generate a server certificate for {kib}. +. Enable TLS/SSL in `kibana.yml`: + -- -//TBD: Can we provide more information about how they generate the certificate? -//Would they be able to use something like the elasticsearch-certutil command? -You must either set the certificate's -`subjectAltName` to the hostname, fully-qualified domain name (FQDN), or IP -address of the {kib} server, or set the CN to the {kib} server's hostname -or FQDN. Using the server's IP address as the CN does not work. +[source,yaml] +-------------------------------------------------------------------------------- +server.ssl.enabled: true +-------------------------------------------------------------------------------- -- -.. Set the `server.ssl.enabled`, `server.ssl.key`, and `server.ssl.certificate` -properties in `kibana.yml`: +. Specify your server certificate and private key in `kibana.yml`: + -- +If your certificate and private key are in a PKCS #12 keystore, specify it like so: + [source,yaml] -------------------------------------------------------------------------------- -server.ssl.enabled: true -server.ssl.key: /path/to/your/server.key -server.ssl.certificate: /path/to/your/server.crt +server.ssl.keystore.path: "/path/to/your/keystore.p12" +server.ssl.keystore.password: "optional decryption password" +-------------------------------------------------------------------------------- + +Otherwise, if your certificate/key are in PEM format, specify them like so: + +[source,yaml] +-------------------------------------------------------------------------------- +server.ssl.certificate: "/path/to/your/server.crt" +server.ssl.key: "/path/to/your/server.key" +server.ssl.keyPassphrase: "optional decryption password" -------------------------------------------------------------------------------- After making these changes, you must always access {kib} via HTTPS. For example, https://localhost:5601. -// TBD: The reference information for server.ssl.enabled says it "enables SSL for -// outgoing requests from the Kibana server to the browser". Do we need to -// reiterate here that only one side of the communications is encrypted? - For more information, see <>. -- -. Configure {kib} to connect to {es} via HTTPS: -+ --- +[[configuring-tls-kib-es]] +==== Encrypting traffic between {kib} and {es} + NOTE: To perform this step, you must {ref}/configuring-security.html[enable the {es} {security-features}] or you must have a proxy that provides an HTTPS endpoint for {es}. --- - -.. Specify the HTTPS protocol in the `elasticsearch.hosts` setting in the {kib} -configuration file, `kibana.yml`: +. Specify the HTTPS URL in the `elasticsearch.hosts` setting in the {kib} configuration file, `kibana.yml`: + -- [source,yaml] -------------------------------------------------------------------------------- elasticsearch.hosts: ["https://.com:9200"] -------------------------------------------------------------------------------- --- - -.. If you are using your own CA to sign certificates for {es}, set the -`elasticsearch.ssl.certificateAuthorities` setting in `kibana.yml` to specify -the location of the PEM file. -+ --- -[source,yaml] --------------------------------------------------------------------------------- -elasticsearch.ssl.certificateAuthorities: /path/to/your/cacert.pem --------------------------------------------------------------------------------- -Setting the `certificateAuthorities` property lets you use the default -`verificationMode` option of `full`. -//TBD: Is this still true? It isn't mentioned in https://www.elastic.co/guide/en/kibana/master/settings.html +Using the HTTPS protocol results in a default `elasticsearch.ssl.verificationMode` option of `full`, which utilizes hostname verification. For more information, see <>. -- -. (Optional) If the Elastic {monitor-features} are enabled, configure {kib} to -connect to the {es} monitoring cluster via HTTPS: +. Specify the {es} cluster's CA certificate chain in `kibana.yml`: + -- -NOTE: To perform this step, you must -{ref}/configuring-security.html[enable the {es} {security-features}] or you -must have a proxy that provides an HTTPS endpoint for {es}. --- +If you are using your own CA to sign certificates for {es}, then you need to specify the CA certificate chain in {kib} to properly establish +trust in TLS connections. If your CA certificate chain is contained in a PKCS #12 trust store, specify it like so: -.. Specify the HTTPS URL in the `xpack.monitoring.elasticsearch.hosts` setting in -the {kib} configuration file, `kibana.yml` -+ --- [source,yaml] -------------------------------------------------------------------------------- -xpack.monitoring.elasticsearch.hosts: ["https://:9200"] +elasticsearch.ssl.truststore.path: "/path/to/your/truststore.p12" +elasticsearch.ssl.truststore.password: "optional decryption password" -------------------------------------------------------------------------------- --- -.. Specify the `xpack.monitoring.elasticsearch.ssl.*` settings in the -`kibana.yml` file. -+ --- -For example, if you are using your own certificate authority to sign -certificates, specify the location of the PEM file in the `kibana.yml` file: +Otherwise, if your CA certificate chain is in PEM format, specify each certificate like so: [source,yaml] -------------------------------------------------------------------------------- -xpack.monitoring.elasticsearch.ssl.certificateAuthorities: /path/to/your/cacert.pem +elasticsearch.ssl.certificateAuthorities: ["/path/to/your/cacert1.pem", "/path/to/your/cacert2.pem"] -------------------------------------------------------------------------------- + -- + +. (Optional) If the Elastic {monitor-features} are enabled, configure {kib} to connect to the {es} monitoring cluster via HTTPS. The steps +are the same as above, but each setting is prefixed by `xpack.monitoring.`. For example, `xpack.monitoring.elasticsearch.hosts`, +`xpack.monitoring.elasticsearch.ssl.truststore.path`, etc. diff --git a/docs/visualize/visualize_rollup_data.asciidoc b/docs/visualize/visualize_rollup_data.asciidoc index 110533589cab9..481cbc6e39418 100644 --- a/docs/visualize/visualize_rollup_data.asciidoc +++ b/docs/visualize/visualize_rollup_data.asciidoc @@ -6,7 +6,7 @@ beta[] You can visualize your rolled up data in a variety of charts, tables, maps, and more. Most visualizations support rolled up data, with the exception of -Timelion, TSVB, and Vega visualizations. +Timelion and Vega visualizations. To get started, go to *Management > Kibana > Index patterns.* If a rollup index is detected in the cluster, *Create index pattern* 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/examples/state_containers_examples/kibana.json b/examples/state_containers_examples/kibana.json new file mode 100644 index 0000000000000..9114a414a4da3 --- /dev/null +++ b/examples/state_containers_examples/kibana.json @@ -0,0 +1,10 @@ +{ + "id": "stateContainersExamples", + "version": "0.0.1", + "kibanaVersion": "kibana", + "configPath": ["state_containers_examples"], + "server": false, + "ui": true, + "requiredPlugins": [], + "optionalPlugins": [] +} diff --git a/examples/state_containers_examples/package.json b/examples/state_containers_examples/package.json new file mode 100644 index 0000000000000..b309494a36662 --- /dev/null +++ b/examples/state_containers_examples/package.json @@ -0,0 +1,17 @@ +{ + "name": "state_containers_examples", + "version": "1.0.0", + "main": "target/examples/state_containers_examples", + "kibana": { + "version": "kibana", + "templateVersion": "1.0.0" + }, + "license": "Apache-2.0", + "scripts": { + "kbn": "node ../../scripts/kbn.js", + "build": "rm -rf './target' && tsc" + }, + "devDependencies": { + "typescript": "3.7.2" + } +} diff --git a/examples/state_containers_examples/public/app.tsx b/examples/state_containers_examples/public/app.tsx new file mode 100644 index 0000000000000..319680d07f9bc --- /dev/null +++ b/examples/state_containers_examples/public/app.tsx @@ -0,0 +1,69 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { AppMountParameters } from 'kibana/public'; +import ReactDOM from 'react-dom'; +import React from 'react'; +import { createHashHistory, createBrowserHistory } from 'history'; +import { TodoAppPage } from './todo'; + +export interface AppOptions { + appInstanceId: string; + appTitle: string; + historyType: History; +} + +export enum History { + Browser, + Hash, +} + +export const renderApp = ( + { appBasePath, element }: AppMountParameters, + { appInstanceId, appTitle, historyType }: AppOptions +) => { + const history = + historyType === History.Browser + ? createBrowserHistory({ basename: appBasePath }) + : createHashHistory(); + ReactDOM.render( + { + const stripTrailingSlash = (path: string) => + path.charAt(path.length - 1) === '/' ? path.substr(0, path.length - 1) : path; + const currentAppUrl = stripTrailingSlash(history.createHref(history.location)); + if (historyType === History.Browser) { + // browser history + const basePath = stripTrailingSlash(appBasePath); + return currentAppUrl === basePath && !history.location.search && !history.location.hash; + } else { + // hashed history + return currentAppUrl === '#' && !history.location.search; + } + }} + />, + element + ); + + return () => ReactDOM.unmountComponentAtNode(element); +}; diff --git a/examples/state_containers_examples/public/index.ts b/examples/state_containers_examples/public/index.ts new file mode 100644 index 0000000000000..bc7ad78574ddb --- /dev/null +++ b/examples/state_containers_examples/public/index.ts @@ -0,0 +1,22 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { StateContainersExamplesPlugin } from './plugin'; + +export const plugin = () => new StateContainersExamplesPlugin(); diff --git a/examples/state_containers_examples/public/plugin.ts b/examples/state_containers_examples/public/plugin.ts new file mode 100644 index 0000000000000..beb7b93dbc5b6 --- /dev/null +++ b/examples/state_containers_examples/public/plugin.ts @@ -0,0 +1,52 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { AppMountParameters, CoreSetup, Plugin } from 'kibana/public'; + +export class StateContainersExamplesPlugin implements Plugin { + public setup(core: CoreSetup) { + core.application.register({ + id: 'state-containers-example-browser-history', + title: 'State containers example - browser history routing', + async mount(params: AppMountParameters) { + const { renderApp, History } = await import('./app'); + return renderApp(params, { + appInstanceId: '1', + appTitle: 'Routing with browser history', + historyType: History.Browser, + }); + }, + }); + core.application.register({ + id: 'state-containers-example-hash-history', + title: 'State containers example - hash history routing', + async mount(params: AppMountParameters) { + const { renderApp, History } = await import('./app'); + return renderApp(params, { + appInstanceId: '2', + appTitle: 'Routing with hash history', + historyType: History.Hash, + }); + }, + }); + } + + public start() {} + public stop() {} +} diff --git a/examples/state_containers_examples/public/todo.tsx b/examples/state_containers_examples/public/todo.tsx new file mode 100644 index 0000000000000..84f64f99d0179 --- /dev/null +++ b/examples/state_containers_examples/public/todo.tsx @@ -0,0 +1,324 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React, { useEffect } from 'react'; +import { Link, Route, Router, Switch, useLocation } from 'react-router-dom'; +import { History } from 'history'; +import { + EuiButton, + EuiCheckbox, + EuiFieldText, + EuiPageBody, + EuiPageContent, + EuiPageContentBody, + EuiPageHeader, + EuiPageHeaderSection, + EuiTitle, +} from '@elastic/eui'; +import { + BaseStateContainer, + INullableBaseStateContainer, + createKbnUrlStateStorage, + createSessionStorageStateStorage, + createStateContainer, + createStateContainerReactHelpers, + PureTransition, + syncStates, + getStateFromKbnUrl, + BaseState, +} from '../../../src/plugins/kibana_utils/public'; +import { useUrlTracker } from '../../../src/plugins/kibana_react/public'; +import { + defaultState, + pureTransitions, + TodoActions, + TodoState, +} from '../../../src/plugins/kibana_utils/demos/state_containers/todomvc'; + +interface GlobalState { + text: string; +} +interface GlobalStateAction { + setText: PureTransition; +} +const defaultGlobalState: GlobalState = { text: '' }; +const globalStateContainer = createStateContainer( + defaultGlobalState, + { + setText: state => text => ({ ...state, text }), + } +); + +const GlobalStateHelpers = createStateContainerReactHelpers(); + +const container = createStateContainer(defaultState, pureTransitions); +const { Provider, connect, useTransitions, useState } = createStateContainerReactHelpers< + typeof container +>(); + +interface TodoAppProps { + filter: 'completed' | 'not-completed' | null; +} + +const TodoApp: React.FC = ({ filter }) => { + const { setText } = GlobalStateHelpers.useTransitions(); + const { text } = GlobalStateHelpers.useState(); + const { edit: editTodo, delete: deleteTodo, add: addTodo } = useTransitions(); + const todos = useState().todos; + const filteredTodos = todos.filter(todo => { + if (!filter) return true; + if (filter === 'completed') return todo.completed; + if (filter === 'not-completed') return !todo.completed; + return true; + }); + const location = useLocation(); + return ( + <> +
+ + + All + + + + + Completed + + + + + Not Completed + + +
+
    + {filteredTodos.map(todo => ( +
  • + { + editTodo({ + ...todo, + completed: e.target.checked, + }); + }} + label={todo.text} + /> + { + deleteTodo(todo.id); + }} + > + Delete + +
  • + ))} +
+
{ + const inputRef = (e.target as HTMLFormElement).elements.namedItem( + 'newTodo' + ) as HTMLInputElement; + if (!inputRef || !inputRef.value) return; + addTodo({ + text: inputRef.value, + completed: false, + id: todos.map(todo => todo.id).reduce((a, b) => Math.max(a, b), 0) + 1, + }); + inputRef.value = ''; + e.preventDefault(); + }} + > + + +
+ + setText(e.target.value)} /> +
+ + ); +}; + +const TodoAppConnected = GlobalStateHelpers.connect(() => ({}))( + connect(() => ({}))(TodoApp) +); + +export const TodoAppPage: React.FC<{ + history: History; + appInstanceId: string; + appTitle: string; + appBasePath: string; + isInitialRoute: () => boolean; +}> = props => { + const initialAppUrl = React.useRef(window.location.href); + const [useHashedUrl, setUseHashedUrl] = React.useState(false); + + /** + * Replicates what src/legacy/ui/public/chrome/api/nav.ts did + * Persists the url in sessionStorage and tries to restore it on "componentDidMount" + */ + useUrlTracker(`lastUrlTracker:${props.appInstanceId}`, props.history, urlToRestore => { + // shouldRestoreUrl: + // App decides if it should restore url or not + // In this specific case, restore only if navigated to initial route + if (props.isInitialRoute()) { + // navigated to the base path, so should restore the url + return true; + } else { + // navigated to specific route, so should not restore the url + return false; + } + }); + + useEffect(() => { + // have to sync with history passed to react-router + // history v5 will be singleton and this will not be needed + const kbnUrlStateStorage = createKbnUrlStateStorage({ + useHash: useHashedUrl, + history: props.history, + }); + + const sessionStorageStateStorage = createSessionStorageStateStorage(); + + /** + * Restoring global state: + * State restoration similar to what GlobalState in legacy world did + * It restores state both from url and from session storage + */ + const globalStateKey = `_g`; + const globalStateFromInitialUrl = getStateFromKbnUrl( + globalStateKey, + initialAppUrl.current + ); + const globalStateFromCurrentUrl = kbnUrlStateStorage.get(globalStateKey); + const globalStateFromSessionStorage = sessionStorageStateStorage.get( + globalStateKey + ); + + const initialGlobalState: GlobalState = { + ...defaultGlobalState, + ...globalStateFromCurrentUrl, + ...globalStateFromSessionStorage, + ...globalStateFromInitialUrl, + }; + globalStateContainer.set(initialGlobalState); + kbnUrlStateStorage.set(globalStateKey, initialGlobalState, { replace: true }); + sessionStorageStateStorage.set(globalStateKey, initialGlobalState); + + /** + * Restoring app local state: + * State restoration similar to what AppState in legacy world did + * It restores state both from url + */ + const appStateKey = `_todo-${props.appInstanceId}`; + const initialAppState: TodoState = + getStateFromKbnUrl(appStateKey, initialAppUrl.current) || + kbnUrlStateStorage.get(appStateKey) || + defaultState; + container.set(initialAppState); + kbnUrlStateStorage.set(appStateKey, initialAppState, { replace: true }); + + // start syncing only when made sure, that state in synced + const { stop, start } = syncStates([ + { + stateContainer: withDefaultState(container, defaultState), + storageKey: appStateKey, + stateStorage: kbnUrlStateStorage, + }, + { + stateContainer: withDefaultState(globalStateContainer, defaultGlobalState), + storageKey: globalStateKey, + stateStorage: kbnUrlStateStorage, + }, + { + stateContainer: withDefaultState(globalStateContainer, defaultGlobalState), + storageKey: globalStateKey, + stateStorage: sessionStorageStateStorage, + }, + ]); + + start(); + + return () => { + stop(); + + // reset state containers + container.set(defaultState); + globalStateContainer.set(defaultGlobalState); + }; + }, [props.appInstanceId, props.history, useHashedUrl]); + + return ( + + + + + + + +

+ State sync example. Instance: ${props.appInstanceId}. {props.appTitle} +

+
+ setUseHashedUrl(!useHashedUrl)}> + {useHashedUrl ? 'Use Expanded State' : 'Use Hashed State'} + +
+
+ + + + + + + + + + + + + + + +
+
+
+
+ ); +}; + +function withDefaultState( + stateContainer: BaseStateContainer, + // eslint-disable-next-line no-shadow + defaultState: State +): INullableBaseStateContainer { + return { + ...stateContainer, + set: (state: State | null) => { + stateContainer.set({ + ...defaultState, + ...state, + }); + }, + }; +} diff --git a/examples/state_containers_examples/tsconfig.json b/examples/state_containers_examples/tsconfig.json new file mode 100644 index 0000000000000..091130487791b --- /dev/null +++ b/examples/state_containers_examples/tsconfig.json @@ -0,0 +1,15 @@ +{ + "extends": "../../tsconfig.json", + "compilerOptions": { + "outDir": "./target", + "skipLibCheck": true + }, + "include": [ + "index.ts", + "public/**/*.ts", + "public/**/*.tsx", + "server/**/*.ts", + "../../typings/**/*" + ], + "exclude": [] +} diff --git a/package.json b/package.json index a0f5dd3af14c0..6b9640d214a5e 100644 --- a/package.json +++ b/package.json @@ -88,6 +88,7 @@ "**/isomorphic-git/**/base64-js": "^1.2.1", "**/image-diff/gm/debug": "^2.6.9", "**/react-dom": "^16.12.0", + "**/react": "^16.12.0", "**/react-test-renderer": "^16.12.0", "**/deepmerge": "^4.2.2", "**/serialize-javascript": "^2.1.1" @@ -96,6 +97,7 @@ "packages": [ "packages/*", "x-pack", + "x-pack/plugins/*", "x-pack/legacy/plugins/*", "examples/*", "test/plugin_functional/plugins/*", @@ -113,7 +115,7 @@ "@babel/core": "^7.5.5", "@babel/register": "^7.7.0", "@elastic/apm-rum": "^4.6.0", - "@elastic/charts": "^14.0.0", + "@elastic/charts": "^16.1.0", "@elastic/datemath": "5.0.2", "@elastic/ems-client": "1.0.5", "@elastic/eui": "17.3.1", @@ -132,8 +134,10 @@ "@kbn/pm": "1.0.0", "@kbn/test-subj-selector": "0.2.1", "@kbn/ui-framework": "1.0.0", + "@kbn/ui-shared-deps": "1.0.0", "@types/json-stable-stringify": "^1.0.32", "@types/lodash.clonedeep": "^4.5.4", + "@types/node-forge": "^0.9.0", "@types/react-grid-layout": "^0.16.7", "@types/recompose": "^0.30.5", "JSONStream": "1.3.5", @@ -162,17 +166,18 @@ "custom-event-polyfill": "^0.3.0", "d3": "3.5.17", "d3-cloud": "1.2.5", + "deep-freeze-strict": "^1.1.1", "deepmerge": "^4.2.2", "del": "^5.1.0", "elastic-apm-node": "^3.2.0", "elasticsearch": "^16.5.0", "elasticsearch-browser": "^16.5.0", - "encode-uri-query": "1.0.1", "execa": "^3.2.0", "expiry-js": "0.1.7", "fast-deep-equal": "^3.1.1", "file-loader": "4.2.0", "font-awesome": "4.7.0", + "fp-ts": "^2.3.1", "getos": "^3.1.0", "glob": "^7.1.2", "glob-all": "^3.1.0", @@ -187,6 +192,7 @@ "hoek": "^5.0.4", "http-proxy-agent": "^2.1.0", "https-proxy-agent": "^2.2.2", + "immer": "^1.5.0", "inert": "^5.1.0", "inline-style": "^2.0.0", "joi": "^13.5.2", @@ -214,6 +220,7 @@ "mustache": "2.3.2", "ngreact": "0.5.1", "node-fetch": "1.7.3", + "node-forge": "^0.9.1", "opn": "^5.5.0", "oppsy": "^2.0.0", "pegjs": "0.10.0", @@ -237,7 +244,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", @@ -292,8 +299,8 @@ "@kbn/plugin-generator": "1.0.0", "@kbn/test": "1.0.0", "@kbn/utility-types": "1.0.0", - "@microsoft/api-documenter": "7.4.3", - "@microsoft/api-extractor": "7.4.2", + "@microsoft/api-documenter": "7.7.2", + "@microsoft/api-extractor": "7.7.0", "@percy/agent": "^0.11.0", "@testing-library/react": "^9.3.2", "@testing-library/react-hooks": "^3.2.1", @@ -308,10 +315,11 @@ "@types/classnames": "^2.2.9", "@types/d3": "^3.5.43", "@types/dedent": "^0.7.0", + "@types/deep-freeze-strict": "^1.1.0", "@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", @@ -338,6 +346,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", @@ -350,7 +359,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", @@ -365,8 +374,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", @@ -386,21 +395,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-analytics/src/report.ts b/packages/kbn-analytics/src/report.ts index 1c0b37966355f..16c0a3069e5fd 100644 --- a/packages/kbn-analytics/src/report.ts +++ b/packages/kbn-analytics/src/report.ts @@ -78,6 +78,7 @@ export class ReportManager { } assignReports(newMetrics: Metric | Metric[]) { wrapArray(newMetrics).forEach(newMetric => this.assignReport(this.report, newMetric)); + return { report: this.report }; } static createMetricKey(metric: Metric): string { switch (metric.type) { @@ -101,7 +102,7 @@ export class ReportManager { case METRIC_TYPE.USER_AGENT: { const { appName, type, userAgent } = metric; if (userAgent) { - this.report.userAgent = { + report.userAgent = { [key]: { key, appName, @@ -110,23 +111,22 @@ export class ReportManager { }, }; } + return; } case METRIC_TYPE.CLICK: case METRIC_TYPE.LOADED: case METRIC_TYPE.COUNT: { const { appName, type, eventName, count } = metric; - if (report.uiStatsMetrics) { - const existingStats = (report.uiStatsMetrics[key] || {}).stats; - this.report.uiStatsMetrics = this.report.uiStatsMetrics || {}; - this.report.uiStatsMetrics[key] = { - key, - appName, - eventName, - type, - stats: this.incrementStats(count, existingStats), - }; - } + report.uiStatsMetrics = report.uiStatsMetrics || {}; + const existingStats = (report.uiStatsMetrics[key] || {}).stats; + report.uiStatsMetrics[key] = { + key, + appName, + eventName, + type, + stats: this.incrementStats(count, existingStats), + }; return; } default: 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/certs/README.md b/packages/kbn-dev-utils/certs/README.md new file mode 100644 index 0000000000000..fdf7892789404 --- /dev/null +++ b/packages/kbn-dev-utils/certs/README.md @@ -0,0 +1,62 @@ +# Development certificates + +Kibana includes several development certificates to enable easy setup of TLS-encrypted communications with Elasticsearch. + +_Note: these certificates should **never** be used in production._ + +## Certificate information + +Certificates and keys are provided in multiple formats. These can be used by other packages to set up a new Elastic Stack with Kibana and Elasticsearch. The Certificate Authority (CA) private key is intentionally omitted from this package. + +### PEM + +* `ca.crt` -- A [PEM-formatted](https://tools.ietf.org/html/rfc1421) [X.509](https://tools.ietf.org/html/rfc5280) certificate that is used as a CA. +* `elasticsearch.crt` -- A PEM-formatted X.509 certificate and public key for Elasticsearch. +* `elasticsearch.key` -- A PEM-formatted [PKCS #1](https://tools.ietf.org/html/rfc8017) private key for Elasticsearch. +* `kibana.crt` -- A PEM-formatted X.509 certificate and public key for Kibana. +* `kibana.key` -- A PEM-formatted PKCS #1 private key for Kibana. + +### PKCS #12 + +* `elasticsearch.p12` -- A [PKCS #12](https://tools.ietf.org/html/rfc7292) encrypted key store / trust store that contains `ca.crt`, `elasticsearch.crt`, and a [PKCS #8](https://tools.ietf.org/html/rfc5208) encrypted version of `elasticsearch.key`. +* `kibana.p12` -- A PKCS #12 encrypted key store / trust store that contains `ca.crt`, `kibana.crt`, and a PKCS #8 encrypted version of `kibana.key`. + +The password used for both of these is "storepass". Other copies are also provided for testing purposes: + +* `elasticsearch_emptypassword.p12` -- The same PKCS #12 key store, encrypted with an empty password. +* `elasticsearch_nopassword.p12` -- The same PKCS #12 key store, not encrypted with a password. + +## Certificate generation + +[Elasticsearch cert-util](https://www.elastic.co/guide/en/elasticsearch/reference/current/certutil.html) and [OpenSSL](https://www.openssl.org/) were used to generate these certificates. The following commands were used from the root directory of Elasticsearch: + +``` +# Generate the PKCS #12 keystore for a CA, valid for 50 years +bin/elasticsearch-certutil ca -days 18250 --pass castorepass + +# Generate the PKCS #12 keystore for Elasticsearch and sign it with the CA +bin/elasticsearch-certutil cert -days 18250 --ca elastic-stack-ca.p12 --ca-pass castorepass --name elasticsearch --dns localhost --pass storepass + +# Generate the PKCS #12 keystore for Kibana and sign it with the CA +bin/elasticsearch-certutil cert -days 18250 --ca elastic-stack-ca.p12 --ca-pass castorepass --name kibana --dns localhost --pass storepass + +# Copy the PKCS #12 keystore for Elasticsearch with an empty password +openssl pkcs12 -in elasticsearch.p12 -nodes -passin pass:"storepass" -passout pass:"" | openssl pkcs12 -export -out elasticsearch_emptypassword.p12 -passout pass:"" + +# Manually create "elasticsearch_nopassword.p12" -- this can be done on macOS by importing the P12 key store into the Keychain and exporting it again + +# Extract the PEM-formatted X.509 certificate for the CA +openssl pkcs12 -in elasticsearch.p12 -out ca.crt -cacerts -passin pass:"storepass" -passout pass: + +# Extract the PEM-formatted PKCS #1 private key for Elasticsearch +openssl pkcs12 -in elasticsearch.p12 -nocerts -passin pass:"storepass" -passout pass:"keypass" | openssl rsa -passin pass:keypass -out elasticsearch.key + +# Extract the PEM-formatted X.509 certificate for Elasticsearch +openssl pkcs12 -in elasticsearch.p12 -out elasticsearch.crt -clcerts -passin pass:"storepass" -passout pass: + +# Extract the PEM-formatted PKCS #1 private key for Kibana +openssl pkcs12 -in kibana.p12 -nocerts -passin pass:"storepass" -passout pass:"keypass" | openssl rsa -passin pass:keypass -out kibana.key + +# Extract the PEM-formatted X.509 certificate for Kibana +openssl pkcs12 -in kibana.p12 -out kibana.crt -clcerts -passin pass:"storepass" -passout pass: +``` diff --git a/packages/kbn-dev-utils/certs/ca.crt b/packages/kbn-dev-utils/certs/ca.crt old mode 100755 new mode 100644 index 3e964823c5086..217935b8d83f6 --- a/packages/kbn-dev-utils/certs/ca.crt +++ b/packages/kbn-dev-utils/certs/ca.crt @@ -1,20 +1,29 @@ +Bag Attributes + friendlyName: elasticsearch + localKeyID: 54 69 6D 65 20 31 35 37 37 34 36 36 31 39 38 30 33 37 +Key Attributes: +Bag Attributes + friendlyName: ca + 2.16.840.1.113894.746875.1.1: +subject=/CN=Elastic Certificate Tool Autogenerated CA +issuer=/CN=Elastic Certificate Tool Autogenerated CA -----BEGIN CERTIFICATE----- -MIIDSjCCAjKgAwIBAgIVAOgxLlE1RMGl2fYgTKDznvDL2vboMA0GCSqGSIb3DQEB -CwUAMDQxMjAwBgNVBAMTKUVsYXN0aWMgQ2VydGlmaWNhdGUgVG9vbCBBdXRvZ2Vu -ZXJhdGVkIENBMB4XDTE5MDcxMTE3MzQ0OFoXDTIyMDcxMDE3MzQ0OFowNDEyMDAG -A1UEAxMpRWxhc3RpYyBDZXJ0aWZpY2F0ZSBUb29sIEF1dG9nZW5lcmF0ZWQgQ0Ew -ggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCNImKp/A9l++Ac7U5lvHOA -+fYRb8p7AgdfKBMB0v3bo+bpHjkbkf3vYHjo1xJSg5ls6EPK+Do4owkAgKJdrznI -5/efJOjgA+ylH4rgAfrRIQmiFEWZnAv86vJ+Iq83mfkPELb4dvXCi7AFQkzoM/rY -Lbi97xha5bA2SEmpYp7VhBTM9zWy+q9Tm5odPO8u2n75GpIM2RwipaXlL0ink+06 -/oweQJoivaDgpDOmUXCFPmpV3VCdhUGxDQPyG0upQkF+NbQoei4RmluPEmVz4S7I -TFLWjX7LeZVP63bJkcCgiq6Hm97kDtr9EYlPKhHm7UMWzhNzHbfvySMDzqAJC0KX -AgMBAAGjUzBRMB0GA1UdDgQWBBRKqaaQ/+jT+ipPLJe7qekp1N/zizAfBgNVHSME -GDAWgBRKqaaQ/+jT+ipPLJe7qekp1N/zizAPBgNVHRMBAf8EBTADAQH/MA0GCSqG -SIb3DQEBCwUAA4IBAQA7Gcq8h8yDXvepfKUAcTTMCBZkI+g3qE1gfRwjW7587CIj -xnrzEqANU+Q1lv7IeQ158HiduDUMZfnvpuNwkf0HkqnRWb57RwfVdCAlAeZmzipq -5ZJWlIW4dbmk57nGLg4fCszedi0uSGytZ2/BUdpWyC0fAM97h7Agtr4xGGKMEL67 -uB55ijt61V62HZ5wWXWNO9m+wfmdnt+YQViQJHtpYz1oOmWhY3dpitZLfWs1sLLD -w3CZOhmWX7+P7+HlCkSBF4swzHOCI3THyX61NbLxju8VkTAjwbZPq4EOnVKnO6kr -RdwQVnzKnqG5fxfSGknNahy0pOhJHZlGLwECRlgF +MIIDSzCCAjOgAwIBAgIUW0brhEtYK3tUBYlXnUa+AMmAX6kwDQYJKoZIhvcNAQEL +BQAwNDEyMDAGA1UEAxMpRWxhc3RpYyBDZXJ0aWZpY2F0ZSBUb29sIEF1dG9nZW5l +cmF0ZWQgQ0EwIBcNMTkxMjI3MTcwMjMyWhgPMjA2OTEyMTQxNzAyMzJaMDQxMjAw +BgNVBAMTKUVsYXN0aWMgQ2VydGlmaWNhdGUgVG9vbCBBdXRvZ2VuZXJhdGVkIENB +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAplO5m5Xy8xERyA0/G5SM +Nu2QXkfS+m7ZTFjSmtwqX7BI1I6ISI4Yw8QxzcIgSbEGlSqb7baeT+A/1JQj0gZN +KOnKbazl+ujVRJpsfpt5iUsnQyVPheGekcHkB+9WkZPgZ1oGRENr/4Eb1VImQf+Y +yo/FUj8X939tYW0fficAqYKv8/4NWpBUbeop8wsBtkz738QKlmPkMwC4FbuF2/bN +vNuzQuRbGMVmPeyivZJRfDAMKExoXjCCLmbShdg4dUHsUjVeWQZ6s4vbims+8qF9 +b4bseayScQNNU3hc5mkfhEhSM0KB0lDpSvoCxuXvXzb6bOk7xIdYo+O4vHUhvSkQ +mwIDAQABo1MwUTAdBgNVHQ4EFgQUGu0mDnvDRnBdNBG8DxwPdWArB0kwHwYDVR0j +BBgwFoAUGu0mDnvDRnBdNBG8DxwPdWArB0kwDwYDVR0TAQH/BAUwAwEB/zANBgkq +hkiG9w0BAQsFAAOCAQEASv/FYOwWGnQreH8ulcVupGeZj25dIjZiuKfJmslH8QN/ +pVCIzAxNZjGjCpKxbJoCu5U9USaBylbhigeBJEq4wmYTs/WPu4uYMgDj0MILuHin +RQqgEVG0uADGEgH2nnk8DeY8gQvGpJRQGlXNK8pb+pCsy6F8k/svGOeBND9osHfU +CVEo5nXjfq6JCFt6hPx7kl4h3/j3C4wNy/Dv/QINdpPsl6CnF17Q9R9d60WFv42/ +pkl7W1hszCG9foNJOJabuWfVoPkvKQjoCvPitZt/hCaFZAW49PmAVhK+DAohQ91l +TZhDmYqHoXNiRDQiUT68OS7RlfKgNpr/vMTZXDxpmw== -----END CERTIFICATE----- diff --git a/packages/kbn-dev-utils/certs/elasticsearch.crt b/packages/kbn-dev-utils/certs/elasticsearch.crt old mode 100755 new mode 100644 index b30e11e9bbce1..87ba02019903f --- a/packages/kbn-dev-utils/certs/elasticsearch.crt +++ b/packages/kbn-dev-utils/certs/elasticsearch.crt @@ -1,20 +1,29 @@ +Bag Attributes + friendlyName: elasticsearch + localKeyID: 54 69 6D 65 20 31 35 37 37 34 36 36 31 39 38 30 33 37 +Key Attributes: +Bag Attributes + friendlyName: elasticsearch + localKeyID: 54 69 6D 65 20 31 35 37 37 34 36 36 31 39 38 30 33 37 +subject=/CN=elasticsearch +issuer=/CN=Elastic Certificate Tool Autogenerated CA -----BEGIN CERTIFICATE----- -MIIDRDCCAiygAwIBAgIVAI8V1fwvXKykKtp5k0cLpTOtY+DVMA0GCSqGSIb3DQEB +MIIDQDCCAiigAwIBAgIVAI93OQE6tZffPyzenSg3ljE3JJBzMA0GCSqGSIb3DQEB CwUAMDQxMjAwBgNVBAMTKUVsYXN0aWMgQ2VydGlmaWNhdGUgVG9vbCBBdXRvZ2Vu -ZXJhdGVkIENBMB4XDTE5MDcxMTE3MzUxOFoXDTIyMDcxMDE3MzUxOFowGDEWMBQG -A1UEAxMNZWxhc3RpY3NlYXJjaDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC -ggEBALW+8gV6m6wYmTZmrXzNWKElE+ePkkikCviNfuWonWqxgAoWpAwAx2FvdhP3 -UDFbe38ydJX4oDgXeC25vdIR6z2uqzx+GXSNSybO7luuOUYQOP4Xf5Cj3zzXXMyu -nY1nZTVsChI9jAMz4cZZdUd04f4r4TBNxrFCcVR0uec5RGRXuP8rSQd9AbYFUVYf -jJeLb24asghb2Ku+c2JGvMqPEXFWFGOXFhUoIbRjCJNTDcr1ZXPof3+fO1l6HmhT -QBSqC4IZL8XqANltDT4tCQDD8L9+ckWJD8MP3wPkPUGZId2gLu++hrb9YfiP2upq -N/f3P7l5Fcisw1iwQC4+DGMTyfcCAwEAAaNpMGcwHQYDVR0OBBYEFGuiGk8HLpG2 -MyA24/J+GwxT32ikMB8GA1UdIwQYMBaAFEqpppD/6NP6Kk8sl7up6SnU3/OLMBoG -A1UdEQQTMBGCCWxvY2FsaG9zdIcEfwAAATAJBgNVHRMEAjAAMA0GCSqGSIb3DQEB -CwUAA4IBAQB8yfY0edAgq2KnJNWyl8NpHNfqtM27+/LR2V8OxVwxV1hc4ZilczLu -CXeqP9uqBVjcck6fvLrjy4LhSG0V05j51UMJ1FjFVTBuhlrDcd3j8848yWrmyz8z -vPYYY2vIN9d1NsBgufULwliBT4UJchsYE8xT5ayAzGHKCTlzHGHMTPzYjwac8nbT -nd2u+6h0OQOJn6K4v+RfXtN4EA8ZUrYxUkqHNS3cFB5sxH7JQGi25XJc5MfxyCwY -YOukxbN85ew861N6oVd+W+nGJu8WOLU88/uvCv+dLhnAlnnIOLqvmrD5m7gFsFO9 -Z7Xz/U1SbNipWy9OLOhqq2Ja59j8p9e5 +ZXJhdGVkIENBMCAXDTE5MTIyNzE3MDMxN1oYDzIwNjkxMjE0MTcwMzE3WjAYMRYw +FAYDVQQDEw1lbGFzdGljc2VhcmNoMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIB +CgKCAQEA2EkPfvE3ZNMjHCAQZhpImoXBCIN6KavvJSbVHRtLzAXB4wxige+vFQWb +4umqPeEeVH7FvrsRqn24tUgGIkag9p9AOwYxfcT3vwNqcK/EztIlYFs72pmYg7Ez +s6+qLc/YSLOT3aMoHKDHE93z1jYIDGccyjGbv9NsdgCbLHD0TQuqm+7pKy1MZoJm +0qn4KYw4kXakVNWlxm5GIwr8uqU/w4phrikcOOWqRzsxByoQajypLOA4eD/uWnI2 +zGyPQy7Bkxojiy1ss0CVlrl8fJgcjC4PONpm1ibUSX3SoZ8PopPThR6gvvwoQolR +rYu4+D+rsX7q/ldA6vBOiHBD8r4QoQIDAQABo2MwYTAdBgNVHQ4EFgQUSlIMCYYd +e72A0rUqaCkjVPkGPIwwHwYDVR0jBBgwFoAUGu0mDnvDRnBdNBG8DxwPdWArB0kw +FAYDVR0RBA0wC4IJbG9jYWxob3N0MAkGA1UdEwQCMAAwDQYJKoZIhvcNAQELBQAD +ggEBAImbzBVAEjiLRsNDLP7QAl0k7lVmfQRFz5G95ZTAUSUgbqqymDvry47yInFF +3o12TuI1GxK5zHzi+qzpJLyrnGwGK5JBR+VGxIBBKFVcFh1WNGKV6kSO/zBzO7PO +4Jw4G7By/ImWvS0RBhBUQ9XbQZN3WcVkVVV8UQw5Y7JoKtM+fzyEKXKRCTsvgH+h +3+fUBgqwal2Mz4KPH57Jrtk209dtn7tnQxHTNLo0niHyEcfrpuG3YFqTwekr+5FF +FniIcYHPGjag1WzLIdyhe88FFpuav19mlCaxBACc7t97v+euSVUWnsKpy4dLydpv +NxJiI9eWbJZ7f5VM7o64pm7U1cU= -----END CERTIFICATE----- diff --git a/packages/kbn-dev-utils/certs/elasticsearch.key b/packages/kbn-dev-utils/certs/elasticsearch.key old mode 100755 new mode 100644 index 1013ce3971246..9ae4e314630d1 --- a/packages/kbn-dev-utils/certs/elasticsearch.key +++ b/packages/kbn-dev-utils/certs/elasticsearch.key @@ -1,27 +1,27 @@ -----BEGIN RSA PRIVATE KEY----- -MIIEpAIBAAKCAQEAtb7yBXqbrBiZNmatfM1YoSUT54+SSKQK+I1+5aidarGAChak -DADHYW92E/dQMVt7fzJ0lfigOBd4Lbm90hHrPa6rPH4ZdI1LJs7uW645RhA4/hd/ -kKPfPNdczK6djWdlNWwKEj2MAzPhxll1R3Th/ivhME3GsUJxVHS55zlEZFe4/ytJ -B30BtgVRVh+Ml4tvbhqyCFvYq75zYka8yo8RcVYUY5cWFSghtGMIk1MNyvVlc+h/ -f587WXoeaFNAFKoLghkvxeoA2W0NPi0JAMPwv35yRYkPww/fA+Q9QZkh3aAu776G -tv1h+I/a6mo39/c/uXkVyKzDWLBALj4MYxPJ9wIDAQABAoIBAQCb1ggrjn/gxo7I -yK3FL0XplqNEkCR8SLxndtvyC+w+Schh3hv3dst+zlXOtOZ8C9cOr7KrzS2EKwuP -GY6bi2XL0/NbwTwOZgCkXBahYfgWDV7w8DEfUoPd5UPa9XZ+gsOTVPolvcRKErhq -nNYk2SHWEMXb5zSRVUlbg2LL0pzD88bIuKJX+FwPvWcQc2P4OdVTq77iedcl82zZ -6PqTNqKMep7/odLQeBfX7OapOAviVnPYHe0TA114COOimR/pK8IA1OJymX5rgU7O -Wh+uNBSxdHsTTYTkAvw8Bt5Q8n1WCpQwZoYU3xWuSlu7eJ7kcgdFOu9r9GjSXysT -UYCd8s0BAoGBAPXPpCDRxjqF3/ToZ5x5dorKxxJyrmldzMJaUjqOv7y6kezbdBql -n7p3AJ5UfYUW/N6pgQXaWF4MPSyj7ItHhwHjL+v0Manmi5gq8oA30fplhjUlPre7 -Lx4v7SEmH739EHrkZ2ClIQwY3wKuN8mZKgw6RseFgphczDmhHCqEbjW3AoGBAL1H -fkl0RNdZ3nZg0u7MUVk8ytnqBsp7bNFhEs0zUl7ghu3NLaPt8qhirG638oMSCxqH -FPeM3/DryokQAym+UHYNMwiBziEUB2CKMMj7S5YFFWIldCxFeImCO2EP+y3hmbTZ -yjsznNrDzQtErZGP+JTRZcy9xF0oAfVt0G/O1Q3BAoGAa8bqINW5g6l1Q82uuEXt -evdkB6uu21YcVE8D5Nb4LMjk+KRUKObbvQc2hzVmf7dPklVh0+4jdsEJBYyuR3dK -M8KoHV3JdMQ4CrUx9JQFBjQDf0PgVvDEvQiogTNVEZlm42tIBHECp2o0RdmbblIw -xIG8zPi2BRYTGWWRkvbT18sCgYA+c/B/XBW62LRGavwuPsw4nY5xCH7lIIRvMZB6 -lIyBMaRToneEt2ZxmN08SwWBqdpwDlIkvB7H54UUZGwmwdzaltBX5jyVPX6RpAck -yYXPIi5EDAeg8+sptAbTp+pA4UdOHO5VSlpe9GwbY7XBabejotPsElFQS3sZ9/nm -amByAQKBgQCJWghllys1qk76/6PmeVjwjaK9n8o+94LWhqODXlACmDRyse5dwpYb -BIsMMZrNu1YsqDXlWpU7xNa6A8j4oa+EPnm/01PjdueAvMB/oE1woawM5tSsd8NQ -zeQPDhxjDxzaO5l4oJLZg6FT7iQAprhYZjgb8m1vz0D2Xid0A3Kgpw== +MIIEogIBAAKCAQEA2EkPfvE3ZNMjHCAQZhpImoXBCIN6KavvJSbVHRtLzAXB4wxi +ge+vFQWb4umqPeEeVH7FvrsRqn24tUgGIkag9p9AOwYxfcT3vwNqcK/EztIlYFs7 +2pmYg7Ezs6+qLc/YSLOT3aMoHKDHE93z1jYIDGccyjGbv9NsdgCbLHD0TQuqm+7p +Ky1MZoJm0qn4KYw4kXakVNWlxm5GIwr8uqU/w4phrikcOOWqRzsxByoQajypLOA4 +eD/uWnI2zGyPQy7Bkxojiy1ss0CVlrl8fJgcjC4PONpm1ibUSX3SoZ8PopPThR6g +vvwoQolRrYu4+D+rsX7q/ldA6vBOiHBD8r4QoQIDAQABAoIBAB+s44YV0aUEfvnZ +gE1TwBpRSGn0x2le8tEgFMoEe19P4Itd/vdEoQGVJrVevz38wDJjtpYuU3ICo5B5 +EdznNx+nRwLd71WaCSaCW45RT6Nyh2LLOcLUB9ARnZ7NNUEsVWKgWiF1iaRXr5Ar +S1Ct7RPT7hV2mnbHgfTuNcuWZ1D5BUcqNczNoHsV6guFChiwTr7ZObnKj4qJLwdu +ioYYWno4ZLgsk4SfW6DXUCvfKROfYdDd2rGu0NQ4QxT3Q98AsXlrlUITBQbpQEgy +5GSTEh/4sRYj4NQZqncDpPgXm22kYdU7voBjt/zu66oq1W6kKQ4JwPmyc2SI0haa +/pyCMtkCgYEA/y3vs59RvrM6xpT77lf7WigSBbIBQxeKs9RGNoN0Nn/eR0MlQAUG +SmCkkEOcUGuVMnoo5Kc73IP/Q1+O4UGg7f1Gs8KeFPFQMm/wcSL7obvRWray1Bw6 +ohITJPqZYZrw3hmkOMxkLpvUydivN1Unm7BezjOa+T/+OaV3PyAYufsCgYEA2Psb +S8OQhFiVbOKlMYOebvG+AnhAzJiSVus9R9NcViv20E61PRj2rfA398pYpZ8nxaQp +cWGy+POZbkxRCprZ1GHkwWjaQysgeOCbJv8nQ2oh5C0ZCaGw6lfmi2mN097+Prmx +QE8j8OKj3wVI6bniCF7vzwfG3c5cU73elLTAWRMCgYBoA/eDRlvx2ekJbU1MGDzy +wQann6l4Ca6WIt8D9Y13caPPdIVIlUO9KauqyoR7G39TdgwZODnkZ0Gz2s3I8BGD +MQyS1a/OZZcFGC/wTgw4HvD1gydd4qvbyHZZSnUfHiM0xUr1hAsKHKceJ980NNfS +VJAwiUSQeQ9NvC7hYlnx5QKBgDxESsmZcRuBa0eKEC4Xi7rvBEK1WfI58nOX9TZs ++3mnzm7/XZGxzFp1nWYC2uptsWNQ/H3UkBxbtOMQ6XWTmytFYX9i+zSq1uMcJ5wG +RMaRxQYWjJzDP1tnvM4+LDmL93w+oX/mO2pd2PxKAH2CtshybhNH6rGS7swHsboG +FmLnAoGAYTnTcWD1qiwjbJR5ZdukAjIq39cGcf0YOVJCiaFS+5vTirbw04ARvNyM +rxU8EpVN1sKC411pgNvlm6KZJHwihRRQoY+UI2fn78bHBH991QhlrTPO6TBZx7Aw ++hzyxqAiSBX65dQo0e4C15wZysQO/bdT5Def0+UTDR8j8ZgMAQg= -----END RSA PRIVATE KEY----- diff --git a/packages/kbn-dev-utils/certs/elasticsearch.p12 b/packages/kbn-dev-utils/certs/elasticsearch.p12 new file mode 100644 index 0000000000000..02a9183cd8a50 Binary files /dev/null and b/packages/kbn-dev-utils/certs/elasticsearch.p12 differ diff --git a/packages/kbn-dev-utils/certs/elasticsearch_emptypassword.p12 b/packages/kbn-dev-utils/certs/elasticsearch_emptypassword.p12 new file mode 100644 index 0000000000000..3162982ac635a Binary files /dev/null and b/packages/kbn-dev-utils/certs/elasticsearch_emptypassword.p12 differ diff --git a/packages/kbn-dev-utils/certs/elasticsearch_nopassword.p12 b/packages/kbn-dev-utils/certs/elasticsearch_nopassword.p12 new file mode 100644 index 0000000000000..3a22a58d207df Binary files /dev/null and b/packages/kbn-dev-utils/certs/elasticsearch_nopassword.p12 differ diff --git a/packages/kbn-dev-utils/certs/kibana.crt b/packages/kbn-dev-utils/certs/kibana.crt new file mode 100644 index 0000000000000..1c83be587bff9 --- /dev/null +++ b/packages/kbn-dev-utils/certs/kibana.crt @@ -0,0 +1,29 @@ +Bag Attributes + friendlyName: kibana + localKeyID: 54 69 6D 65 20 31 35 37 37 34 36 36 32 32 33 30 33 39 +Key Attributes: +Bag Attributes + friendlyName: kibana + localKeyID: 54 69 6D 65 20 31 35 37 37 34 36 36 32 32 33 30 33 39 +subject=/CN=kibana +issuer=/CN=Elastic Certificate Tool Autogenerated CA +-----BEGIN CERTIFICATE----- +MIIDOTCCAiGgAwIBAgIVANNWkg9lzNiLqNkMFhFKHcXyaZmqMA0GCSqGSIb3DQEB +CwUAMDQxMjAwBgNVBAMTKUVsYXN0aWMgQ2VydGlmaWNhdGUgVG9vbCBBdXRvZ2Vu +ZXJhdGVkIENBMCAXDTE5MTIyNzE3MDM0MloYDzIwNjkxMjE0MTcwMzQyWjARMQ8w +DQYDVQQDEwZraWJhbmEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCQ +wYYbQtbRBKJ4uNZc2+IgRU+7NNL21ZebQlEIMgK7jAqOMrsW2b5DATz41Fd+GQFU +FUYYjwo+PQj6sJHshOJo/gNb32HrydvMI7YPvevkszkuEGCfXxQ3Dw2RTACLgD0Q +OCkwHvn3TMf0loloV/ePGWaZDYZaXi3a5DdWi/HFFoJysgF0JV2f6XyKhJkGaEfJ +s9pWX269zH/XQvGNx4BEimJpYB8h4JnDYPFIiQdqj+sl2b+kS1hH9kL5gBAMXjFU +vcNnX+PmyTjyJrGo75k0ku+spBf1bMwuQt3uSmM+TQIXkvFDmS0DOVESrpA5EC1T +BUGRz6o/I88Xx4Mud771AgMBAAGjYzBhMB0GA1UdDgQWBBQLB1Eo23M3Ss8MsFaz +V+Twcb3PmDAfBgNVHSMEGDAWgBQa7SYOe8NGcF00EbwPHA91YCsHSTAUBgNVHREE +DTALgglsb2NhbGhvc3QwCQYDVR0TBAIwADANBgkqhkiG9w0BAQsFAAOCAQEAnEl/ +z5IElIjvkK4AgMPrNcRlvIGDt2orEik7b6Jsq6/RiJQ7cSsYTZf7xbqyxNsUOTxv ++frj47MEN448H2nRvUxH29YR3XygV5aEwADSAhwaQWn0QfWTCZbJTmSoNEDtDOzX +TGDlAoCD9s9Xz9S1JpxY4H+WWRZrBSDM6SC1c6CzuEeZRuScNAjYD5mh2v6fOlSy +b8xJWSg0AFlJPCa3ZsA2SKbNqI0uNfJTnkXRm88Z2NHcgtlADbOLKauWfCrpgsCk +cZgo6yAYkOM148h/8wGla1eX+iE1R72NUABGydu8MSQKvc0emWJkGsC1/KqPlf/O +eOUsdwn1yDKHRxDHyA== +-----END CERTIFICATE----- diff --git a/packages/kbn-dev-utils/certs/kibana.key b/packages/kbn-dev-utils/certs/kibana.key new file mode 100644 index 0000000000000..4a4e6b4cb8c36 --- /dev/null +++ b/packages/kbn-dev-utils/certs/kibana.key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAkMGGG0LW0QSieLjWXNviIEVPuzTS9tWXm0JRCDICu4wKjjK7 +Ftm+QwE8+NRXfhkBVBVGGI8KPj0I+rCR7ITiaP4DW99h68nbzCO2D73r5LM5LhBg +n18UNw8NkUwAi4A9EDgpMB7590zH9JaJaFf3jxlmmQ2GWl4t2uQ3VovxxRaCcrIB +dCVdn+l8ioSZBmhHybPaVl9uvcx/10LxjceARIpiaWAfIeCZw2DxSIkHao/rJdm/ +pEtYR/ZC+YAQDF4xVL3DZ1/j5sk48iaxqO+ZNJLvrKQX9WzMLkLd7kpjPk0CF5Lx +Q5ktAzlREq6QORAtUwVBkc+qPyPPF8eDLne+9QIDAQABAoIBAHl9suxWYKz00te3 +alJtSZAEHDLm1tjL034/XnseXiTCGGnYMiWvgnwCIgZFUVlH61GCuV4LT3GFEHA2 +mYKE1PGBn5gQF8MpnAvtPPRhVgaQVUFQBYg86F59h8mWnC545sciG4+DsA/apUem +wJSOn/u+Odni/AwEV0ALolZFBhl+0rccSr+6paJnzJ7QNiIn6EWbgb0n9WXqkhap +TqoPclBHm0ObeBI6lNyfvBZ8HB3hyjWZInNCaAs9DnkNPh4evuttUn/KlOPOVn9r +xz2UYsmVW6E+yPXUpSYkFQN9aaPF6alOz8PIfF8Wit7pmZMmInluGcwi/us9+ZTN +8gNvpoECgYEA0KC7XEoXRsBTN4kPznkGftvj1dtgB35W/HxXNouArQQjCbLhqcsA +jqaK0f+stYzSWZXGsKl9yQU9KA7u/wCHmLep70l7WsYYUKdkhWouK0HU5MeeLwB0 +N4ekQOQuQGqelqMo7IG2hQhTYD9PB4F3G0Sz1FgdObfuGPKfvNFVjckCgYEAsaAA +IY/TpRBWeWZfyXrnkp3atOPzkdpjb6cfT8Kib9bIECXr7ULUxA5QANX05ofodhsW +3+7iW5wicyZ1VNVEsPRL0aw7YUbNpBvob8faBUZ2KEdKQr42IfVOo7TQnvVXtumR +UE+dNvWUL2PbL0wMxD1XbMSmOze/wF8X2CeyDc0CgYBQnLqol2xVBz1gaRJ1emgb +HoXzfVemrZeY6cadKdwnfkC3n6n4fJsTg6CCMiOe5vHkca4bVvJmeSK/Vr3cRG0g +gl8kOaVzVrXQfE2oC3YZes9zMvqZOLivODcsZ77DXy82D4dhk2FeF/B3cR7tTIYk +QDCoLP/l7H8QnrdAMza2mQKBgDODwuX475ncviehUEB/26+DBo4V2ms/mj0kjAk2 +2qNy+DzuspjyHADsYbmMU+WUHxA51Q2HG7ET/E3HJpo+7BgiEecye1pADZ391hCt +Nob3I4eU/W2T+uEoYvFJnIOthg3veYyAOolY+ewwmr4B4WX8oGFUOx3Lklo5ehHf +mV01AoGBAI/c6OoHdcqQsZxKlxDNLyB2bTbowAcccoZIOjkC5fkkbsmMDLfScBfW +Q4YYJsmJBdrWNvo7jCl17Mcc4Is3RlmHDrItRkaZj+ehqAN3ejrnPLdgYeW/5XDK +e7yBj7oJd4oKZc59jVytdHvo5R8K0QohAv9gQEZ/tdypX+xWe+5E +-----END RSA PRIVATE KEY----- diff --git a/packages/kbn-dev-utils/certs/kibana.p12 b/packages/kbn-dev-utils/certs/kibana.p12 new file mode 100644 index 0000000000000..06bbd23881290 Binary files /dev/null and b/packages/kbn-dev-utils/certs/kibana.p12 differ diff --git a/packages/kbn-dev-utils/package.json b/packages/kbn-dev-utils/package.json index 09753afeb120f..cef29f33962cd 100644 --- a/packages/kbn-dev-utils/package.json +++ b/packages/kbn-dev-utils/package.json @@ -15,6 +15,7 @@ "execa": "^3.2.0", "exit-hook": "^2.2.0", "getopts": "^2.2.5", + "load-json-file": "^6.2.0", "moment": "^2.24.0", "rxjs": "^6.5.3", "tree-kill": "^1.2.1", diff --git a/packages/kbn-dev-utils/src/certs.ts b/packages/kbn-dev-utils/src/certs.ts index 0d340e4e8c906..f72e3ee547b5c 100644 --- a/packages/kbn-dev-utils/src/certs.ts +++ b/packages/kbn-dev-utils/src/certs.ts @@ -22,3 +22,14 @@ import { resolve } from 'path'; export const CA_CERT_PATH = resolve(__dirname, '../certs/ca.crt'); export const ES_KEY_PATH = resolve(__dirname, '../certs/elasticsearch.key'); export const ES_CERT_PATH = resolve(__dirname, '../certs/elasticsearch.crt'); +export const ES_P12_PATH = resolve(__dirname, '../certs/elasticsearch.p12'); +export const ES_P12_PASSWORD = 'storepass'; +export const ES_EMPTYPASSWORD_P12_PATH = resolve( + __dirname, + '../certs/elasticsearch_emptypassword.p12' +); +export const ES_NOPASSWORD_P12_PATH = resolve(__dirname, '../certs/elasticsearch_nopassword.p12'); +export const KBN_KEY_PATH = resolve(__dirname, '../certs/kibana.key'); +export const KBN_CERT_PATH = resolve(__dirname, '../certs/kibana.crt'); +export const KBN_P12_PATH = resolve(__dirname, '../certs/kibana.p12'); +export const KBN_P12_PASSWORD = 'storepass'; diff --git a/packages/kbn-dev-utils/src/constants.ts b/packages/kbn-dev-utils/src/constants.ts deleted file mode 100644 index 5d3b57509715e..0000000000000 --- a/packages/kbn-dev-utils/src/constants.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { dirname } from 'path'; - -export const REPO_ROOT = dirname(require.resolve('../../../package.json')); diff --git a/packages/kbn-dev-utils/src/index.ts b/packages/kbn-dev-utils/src/index.ts index 7bd22d73df8d5..2fc29b71b262e 100644 --- a/packages/kbn-dev-utils/src/index.ts +++ b/packages/kbn-dev-utils/src/index.ts @@ -25,8 +25,20 @@ export { ToolingLogCollectingWriter, } from './tooling_log'; export { createAbsolutePathSerializer } from './serializers'; -export { CA_CERT_PATH, ES_KEY_PATH, ES_CERT_PATH } from './certs'; +export { + CA_CERT_PATH, + ES_KEY_PATH, + ES_CERT_PATH, + ES_P12_PATH, + ES_P12_PASSWORD, + ES_EMPTYPASSWORD_P12_PATH, + ES_NOPASSWORD_P12_PATH, + KBN_KEY_PATH, + KBN_CERT_PATH, + KBN_P12_PATH, + KBN_P12_PASSWORD, +} from './certs'; export { run, createFailError, createFlagError, combineErrors, isFailError, Flags } from './run'; -export { REPO_ROOT } from './constants'; +export { REPO_ROOT } from './repo_root'; export { KbnClient } from './kbn_client'; export * from './axios'; diff --git a/packages/kbn-dev-utils/src/repo_root.ts b/packages/kbn-dev-utils/src/repo_root.ts new file mode 100644 index 0000000000000..b33b28d8d6e2f --- /dev/null +++ b/packages/kbn-dev-utils/src/repo_root.ts @@ -0,0 +1,59 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import Path from 'path'; +import Fs from 'fs'; + +import loadJsonFile from 'load-json-file'; + +const isKibanaDir = (dir: string) => { + try { + const path = Path.resolve(dir, 'package.json'); + const json = loadJsonFile.sync(path); + if (json && typeof json === 'object' && 'name' in json && json.name === 'kibana') { + return true; + } + } catch (error) { + if (error && error.code === 'ENOENT') { + return false; + } + + throw error; + } +}; + +// search for the kibana directory, since this file is moved around it might +// not be where we think but should always be a relatively close parent +// of this directory +const startDir = Fs.realpathSync(__dirname); +const { root: rootDir } = Path.parse(startDir); +let cursor = startDir; +while (true) { + if (isKibanaDir(cursor)) { + break; + } + + const parent = Path.dirname(cursor); + if (parent === rootDir) { + throw new Error(`unable to find kibana directory from ${startDir}`); + } + cursor = parent; +} + +export const REPO_ROOT = cursor; 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/cluster.js b/packages/kbn-es/src/cluster.js index 665f80e3802e3..ceb4a5b6aece1 100644 --- a/packages/kbn-es/src/cluster.js +++ b/packages/kbn-es/src/cluster.js @@ -35,7 +35,7 @@ const { createCliError } = require('./errors'); const { promisify } = require('util'); const treeKillAsync = promisify(require('tree-kill')); const { parseSettings, SettingsFilter } = require('./settings'); -const { CA_CERT_PATH, ES_KEY_PATH, ES_CERT_PATH } = require('@kbn/dev-utils'); +const { CA_CERT_PATH, ES_P12_PATH, ES_P12_PASSWORD } = require('@kbn/dev-utils'); const readFile = util.promisify(fs.readFile); // listen to data on stream until map returns anything but undefined @@ -261,9 +261,9 @@ exports.Cluster = class Cluster { const esArgs = [].concat(options.esArgs || []); if (this._ssl) { esArgs.push('xpack.security.http.ssl.enabled=true'); - esArgs.push(`xpack.security.http.ssl.key=${ES_KEY_PATH}`); - esArgs.push(`xpack.security.http.ssl.certificate=${ES_CERT_PATH}`); - esArgs.push(`xpack.security.http.ssl.certificate_authorities=${CA_CERT_PATH}`); + esArgs.push(`xpack.security.http.ssl.keystore.path=${ES_P12_PATH}`); + esArgs.push(`xpack.security.http.ssl.keystore.type=PKCS12`); + esArgs.push(`xpack.security.http.ssl.keystore.password=${ES_P12_PASSWORD}`); } const args = parseSettings(extractConfigFiles(esArgs, installPath, { log: this._log }), { 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-es/src/integration_tests/__fixtures__/es_bin.js b/packages/kbn-es/src/integration_tests/__fixtures__/es_bin.js index d3181a748ffbb..d374abe5db068 100644 --- a/packages/kbn-es/src/integration_tests/__fixtures__/es_bin.js +++ b/packages/kbn-es/src/integration_tests/__fixtures__/es_bin.js @@ -34,6 +34,8 @@ if (!start) { let serverUrl; const server = createServer( { + // Note: the integration uses the ES_P12_PATH, but that keystore contains + // the same key/cert as ES_KEY_PATH and ES_CERT_PATH key: ssl ? fs.readFileSync(ES_KEY_PATH) : undefined, cert: ssl ? fs.readFileSync(ES_CERT_PATH) : undefined, }, diff --git a/packages/kbn-es/src/integration_tests/cluster.test.js b/packages/kbn-es/src/integration_tests/cluster.test.js index dd570e27e3282..dfbc04477bd40 100644 --- a/packages/kbn-es/src/integration_tests/cluster.test.js +++ b/packages/kbn-es/src/integration_tests/cluster.test.js @@ -17,7 +17,7 @@ * under the License. */ -const { ToolingLog, CA_CERT_PATH, ES_KEY_PATH, ES_CERT_PATH } = require('@kbn/dev-utils'); +const { ToolingLog, ES_P12_PATH, ES_P12_PASSWORD } = require('@kbn/dev-utils'); const execa = require('execa'); const { Cluster } = require('../cluster'); const { installSource, installSnapshot, installArchive } = require('../install'); @@ -252,9 +252,9 @@ describe('#start(installPath)', () => { const config = extractConfigFiles.mock.calls[0][0]; expect(config).toContain('xpack.security.http.ssl.enabled=true'); - expect(config).toContain(`xpack.security.http.ssl.key=${ES_KEY_PATH}`); - expect(config).toContain(`xpack.security.http.ssl.certificate=${ES_CERT_PATH}`); - expect(config).toContain(`xpack.security.http.ssl.certificate_authorities=${CA_CERT_PATH}`); + expect(config).toContain(`xpack.security.http.ssl.keystore.path=${ES_P12_PATH}`); + expect(config).toContain(`xpack.security.http.ssl.keystore.type=PKCS12`); + expect(config).toContain(`xpack.security.http.ssl.keystore.password=${ES_P12_PASSWORD}`); }); it(`doesn't setup SSL when disabled`, async () => { @@ -319,9 +319,9 @@ describe('#run()', () => { const config = extractConfigFiles.mock.calls[0][0]; expect(config).toContain('xpack.security.http.ssl.enabled=true'); - expect(config).toContain(`xpack.security.http.ssl.key=${ES_KEY_PATH}`); - expect(config).toContain(`xpack.security.http.ssl.certificate=${ES_CERT_PATH}`); - expect(config).toContain(`xpack.security.http.ssl.certificate_authorities=${CA_CERT_PATH}`); + expect(config).toContain(`xpack.security.http.ssl.keystore.path=${ES_P12_PATH}`); + expect(config).toContain(`xpack.security.http.ssl.keystore.type=PKCS12`); + expect(config).toContain(`xpack.security.http.ssl.keystore.password=${ES_P12_PASSWORD}`); }); it(`doesn't setup SSL when disabled`, async () => { diff --git a/packages/kbn-eslint-import-resolver-kibana/lib/get_webpack_config.js b/packages/kbn-eslint-import-resolver-kibana/lib/get_webpack_config.js index c51168ae2d91c..e02c38494991a 100755 --- a/packages/kbn-eslint-import-resolver-kibana/lib/get_webpack_config.js +++ b/packages/kbn-eslint-import-resolver-kibana/lib/get_webpack_config.js @@ -30,8 +30,6 @@ exports.getWebpackConfig = function(kibanaPath, projectRoot, config) { ui: fromKibana('src/legacy/ui/public'), test_harness: fromKibana('src/test_harness/public'), querystring: 'querystring-browser', - moment$: fromKibana('webpackShims/moment'), - 'moment-timezone$': fromKibana('webpackShims/moment-timezone'), // Dev defaults for test bundle https://github.com/elastic/kibana/blob/6998f074542e8c7b32955db159d15661aca253d7/src/core_plugins/tests_bundle/index.js#L73-L78 ng_mock$: fromKibana('src/test_utils/public/ng_mock'), 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 aea85c13d7f32..a3debf78fb8c8 100644 --- a/packages/kbn-pm/dist/index.js +++ b/packages/kbn-pm/dist/index.js @@ -94,21 +94,21 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var _cli__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(1); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "run", function() { return _cli__WEBPACK_IMPORTED_MODULE_0__["run"]; }); -/* harmony import */ var _production__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(484); +/* harmony import */ var _production__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(703); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "buildProductionProjects", function() { return _production__WEBPACK_IMPORTED_MODULE_1__["buildProductionProjects"]; }); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "prepareExternalProjectDependencies", function() { return _production__WEBPACK_IMPORTED_MODULE_1__["prepareExternalProjectDependencies"]; }); -/* harmony import */ var _utils_projects__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(36); +/* harmony import */ var _utils_projects__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(501); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "getProjects", function() { return _utils_projects__WEBPACK_IMPORTED_MODULE_2__["getProjects"]; }); -/* harmony import */ var _utils_project__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(54); +/* harmony import */ var _utils_project__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(516); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "Project", function() { return _utils_project__WEBPACK_IMPORTED_MODULE_3__["Project"]; }); -/* harmony import */ var _utils_workspaces__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(171); +/* harmony import */ var _utils_workspaces__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(579); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "copyWorkspacePackages", function() { return _utils_workspaces__WEBPACK_IMPORTED_MODULE_4__["copyWorkspacePackages"]; }); -/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(172); +/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(580); /* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "getProjectPaths", function() { return _config__WEBPACK_IMPORTED_MODULE_5__["getProjectPaths"]; }); /* @@ -152,7 +152,7 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(16); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_3__); /* harmony import */ var _commands__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(17); -/* harmony import */ var _run__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(475); +/* harmony import */ var _run__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(689); /* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(34); /* * Licensed to Elasticsearch B.V. under one or more contributor @@ -198,6 +198,7 @@ function help() { -i, --include Include only specified projects. If left unspecified, it defaults to including all projects. --oss Do not include the x-pack when running command. --skip-kibana-plugins Filter all plugins in ./plugins and ../kibana-extra when running command. + --no-cache Disable the bootstrap cache `); } @@ -216,7 +217,10 @@ async function run(argv) { h: 'help', i: 'include' }, - boolean: ['prefer-offline', 'frozen-lockfile'] + default: { + cache: true + }, + boolean: ['prefer-offline', 'frozen-lockfile', 'cache'] }); const args = options._; @@ -2502,9 +2506,9 @@ module.exports = require("path"); __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "commands", function() { return commands; }); /* harmony import */ var _bootstrap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(18); -/* harmony import */ var _clean__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(173); -/* harmony import */ var _run__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(272); -/* harmony import */ var _watch__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(273); +/* harmony import */ var _clean__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(587); +/* harmony import */ var _run__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(686); +/* harmony import */ var _watch__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(687); /* * Licensed to Elasticsearch B.V. under one or more contributor * license agreements. See the NOTICE file distributed with @@ -2545,8 +2549,10 @@ __webpack_require__.r(__webpack_exports__); /* harmony import */ var chalk__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(chalk__WEBPACK_IMPORTED_MODULE_0__); /* harmony import */ var _utils_link_project_executables__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(19); /* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(34); -/* harmony import */ var _utils_parallelize__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(35); -/* harmony import */ var _utils_projects__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(36); +/* harmony import */ var _utils_parallelize__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(500); +/* harmony import */ var _utils_projects__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(501); +/* harmony import */ var _utils_project_checksums__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(581); +/* harmony import */ var _utils_bootstrap_cache_file__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(586); /* * Licensed to Elasticsearch B.V. under one or more contributor * license agreements. See the NOTICE file distributed with @@ -2570,12 +2576,15 @@ __webpack_require__.r(__webpack_exports__); + + const BootstrapCommand = { description: 'Install dependencies and crosslink projects', name: 'bootstrap', async run(projects, projectGraph, { - options + options, + kbn }) { const batchedProjectsByWorkspace = Object(_utils_projects__WEBPACK_IMPORTED_MODULE_4__["topologicallyBatchProjects"])(projects, projectGraph, { batchByWorkspace: true @@ -2609,9 +2618,18 @@ const BootstrapCommand = { */ _utils_log__WEBPACK_IMPORTED_MODULE_2__["log"].write(chalk__WEBPACK_IMPORTED_MODULE_0___default.a.bold('\nLinking executables completed, running `kbn:bootstrap` scripts\n')); - await Object(_utils_parallelize__WEBPACK_IMPORTED_MODULE_3__["parallelizeBatches"])(batchedProjects, async pkg => { - if (pkg.hasScript('kbn:bootstrap')) { - await pkg.runScriptStreaming('kbn:bootstrap'); + const checksums = options.cache ? await Object(_utils_project_checksums__WEBPACK_IMPORTED_MODULE_5__["getAllChecksums"])(kbn, _utils_log__WEBPACK_IMPORTED_MODULE_2__["log"]) : false; + await Object(_utils_parallelize__WEBPACK_IMPORTED_MODULE_3__["parallelizeBatches"])(batchedProjects, async project => { + if (project.hasScript('kbn:bootstrap')) { + const cacheFile = new _utils_bootstrap_cache_file__WEBPACK_IMPORTED_MODULE_6__["BootstrapCacheFile"](kbn, project, checksums); + + if (cacheFile.isValid()) { + _utils_log__WEBPACK_IMPORTED_MODULE_2__["log"].success(`[${project.name}] cache up to date`); + } else { + cacheFile.delete(); + await project.runScriptStreaming('kbn:bootstrap'); + cacheFile.write(); + } } }); _utils_log__WEBPACK_IMPORTED_MODULE_2__["log"].write(chalk__WEBPACK_IMPORTED_MODULE_0___default.a.green.bold('\nBootstrapping completed!\n')); @@ -3084,11 +3102,25 @@ function times(n, ok, cb) { var fs = __webpack_require__(23) var polyfills = __webpack_require__(24) -var legacy = __webpack_require__(27) -var queue = [] +var legacy = __webpack_require__(26) +var clone = __webpack_require__(28) var util = __webpack_require__(29) +/* istanbul ignore next - node 0.x polyfill */ +var gracefulQueue +var previousSymbol + +/* istanbul ignore else - node 0.x polyfill */ +if (typeof Symbol === 'function' && typeof Symbol.for === 'function') { + gracefulQueue = Symbol.for('graceful-fs.queue') + // This is used in testing by future versions + previousSymbol = Symbol.for('graceful-fs.previous') +} else { + gracefulQueue = '___graceful-fs.queue' + previousSymbol = '___graceful-fs.previous' +} + function noop () {} var debug = noop @@ -3101,48 +3133,71 @@ else if (/\bgfs4\b/i.test(process.env.NODE_DEBUG || '')) console.error(m) } -if (/\bgfs4\b/i.test(process.env.NODE_DEBUG || '')) { - process.on('exit', function() { - debug(queue) - __webpack_require__(30).equal(queue.length, 0) +// Once time initialization +if (!global[gracefulQueue]) { + // This queue can be shared by multiple loaded instances + var queue = [] + Object.defineProperty(global, gracefulQueue, { + get: function() { + return queue + } }) -} -module.exports = patch(__webpack_require__(25)) -if (process.env.TEST_GRACEFUL_FS_GLOBAL_PATCH) { - module.exports = patch(fs) -} + // Patch fs.close/closeSync to shared queue version, because we need + // to retry() whenever a close happens *anywhere* in the program. + // This is essential when multiple graceful-fs instances are + // in play at the same time. + fs.close = (function (fs$close) { + function close (fd, cb) { + return fs$close.call(fs, fd, function (err) { + // This function uses the graceful-fs shared queue + if (!err) { + retry() + } -// Always patch fs.close/closeSync, because we want to -// retry() whenever a close happens *anywhere* in the program. -// This is essential when multiple graceful-fs instances are -// in play at the same time. -module.exports.close = -fs.close = (function (fs$close) { return function (fd, cb) { - return fs$close.call(fs, fd, function (err) { - if (!err) + if (typeof cb === 'function') + cb.apply(this, arguments) + }) + } + + Object.defineProperty(close, previousSymbol, { + value: fs$close + }) + return close + })(fs.close) + + fs.closeSync = (function (fs$closeSync) { + function closeSync (fd) { + // This function uses the graceful-fs shared queue + fs$closeSync.apply(fs, arguments) retry() + } - if (typeof cb === 'function') - cb.apply(this, arguments) - }) -}})(fs.close) + Object.defineProperty(closeSync, previousSymbol, { + value: fs$closeSync + }) + return closeSync + })(fs.closeSync) -module.exports.closeSync = -fs.closeSync = (function (fs$closeSync) { return function (fd) { - // Note that graceful-fs also retries when fs.closeSync() fails. - // Looks like a bug to me, although it's probably a harmless one. - var rval = fs$closeSync.apply(fs, arguments) - retry() - return rval -}})(fs.closeSync) + if (/\bgfs4\b/i.test(process.env.NODE_DEBUG || '')) { + process.on('exit', function() { + debug(global[gracefulQueue]) + __webpack_require__(30).equal(global[gracefulQueue].length, 0) + }) + } +} + +module.exports = patch(clone(fs)) +if (process.env.TEST_GRACEFUL_FS_GLOBAL_PATCH && !fs.__patched) { + module.exports = patch(fs) + fs.__patched = true; +} function patch (fs) { // Everything that references the open() function needs to be in here polyfills(fs) fs.gracefulify = patch - fs.FileReadStream = ReadStream; // Legacy name. - fs.FileWriteStream = WriteStream; // Legacy name. + fs.createReadStream = createReadStream fs.createWriteStream = createWriteStream var fs$readFile = fs.readFile @@ -3228,6 +3283,7 @@ function patch (fs) { if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) enqueue([go$readdir, [args]]) + else { if (typeof cb === 'function') cb.apply(this, arguments) @@ -3247,15 +3303,61 @@ function patch (fs) { } var fs$ReadStream = fs.ReadStream - ReadStream.prototype = Object.create(fs$ReadStream.prototype) - ReadStream.prototype.open = ReadStream$open + if (fs$ReadStream) { + ReadStream.prototype = Object.create(fs$ReadStream.prototype) + ReadStream.prototype.open = ReadStream$open + } var fs$WriteStream = fs.WriteStream - WriteStream.prototype = Object.create(fs$WriteStream.prototype) - WriteStream.prototype.open = WriteStream$open + if (fs$WriteStream) { + WriteStream.prototype = Object.create(fs$WriteStream.prototype) + WriteStream.prototype.open = WriteStream$open + } - fs.ReadStream = ReadStream - fs.WriteStream = WriteStream + Object.defineProperty(fs, 'ReadStream', { + get: function () { + return ReadStream + }, + set: function (val) { + ReadStream = val + }, + enumerable: true, + configurable: true + }) + Object.defineProperty(fs, 'WriteStream', { + get: function () { + return WriteStream + }, + set: function (val) { + WriteStream = val + }, + enumerable: true, + configurable: true + }) + + // legacy names + var FileReadStream = ReadStream + Object.defineProperty(fs, 'FileReadStream', { + get: function () { + return FileReadStream + }, + set: function (val) { + FileReadStream = val + }, + enumerable: true, + configurable: true + }) + var FileWriteStream = WriteStream + Object.defineProperty(fs, 'FileWriteStream', { + get: function () { + return FileWriteStream + }, + set: function (val) { + FileWriteStream = val + }, + enumerable: true, + configurable: true + }) function ReadStream (path, options) { if (this instanceof ReadStream) @@ -3301,11 +3403,11 @@ function patch (fs) { } function createReadStream (path, options) { - return new ReadStream(path, options) + return new fs.ReadStream(path, options) } function createWriteStream (path, options) { - return new WriteStream(path, options) + return new fs.WriteStream(path, options) } var fs$open = fs.open @@ -3334,11 +3436,11 @@ function patch (fs) { function enqueue (elem) { debug('ENQUEUE', elem[0].name, elem[1]) - queue.push(elem) + global[gracefulQueue].push(elem) } function retry () { - var elem = queue.shift() + var elem = global[gracefulQueue].shift() if (elem) { debug('RETRY', elem[0].name, elem[1]) elem[0].apply(null, elem[1]) @@ -3356,8 +3458,7 @@ module.exports = require("fs"); /* 24 */ /***/ (function(module, exports, __webpack_require__) { -var fs = __webpack_require__(25) -var constants = __webpack_require__(26) +var constants = __webpack_require__(25) var origCwd = process.cwd var cwd = null @@ -3474,20 +3575,26 @@ function patch (fs) { } // if read() returns EAGAIN, then just try it again. - fs.read = (function (fs$read) { return function (fd, buffer, offset, length, position, callback_) { - var callback - if (callback_ && typeof callback_ === 'function') { - var eagCounter = 0 - callback = function (er, _, __) { - if (er && er.code === 'EAGAIN' && eagCounter < 10) { - eagCounter ++ - return fs$read.call(fs, fd, buffer, offset, length, position, callback) + fs.read = (function (fs$read) { + function read (fd, buffer, offset, length, position, callback_) { + var callback + if (callback_ && typeof callback_ === 'function') { + var eagCounter = 0 + callback = function (er, _, __) { + if (er && er.code === 'EAGAIN' && eagCounter < 10) { + eagCounter ++ + return fs$read.call(fs, fd, buffer, offset, length, position, callback) + } + callback_.apply(this, arguments) } - callback_.apply(this, arguments) } + return fs$read.call(fs, fd, buffer, offset, length, position, callback) } - return fs$read.call(fs, fd, buffer, offset, length, position, callback) - }})(fs.read) + + // This ensures `util.promisify` works as it does for native `fs.read`. + read.__proto__ = fs$read + return read + })(fs.read) fs.readSync = (function (fs$readSync) { return function (fd, buffer, offset, length, position) { var eagCounter = 0 @@ -3503,73 +3610,36 @@ function patch (fs) { } } }})(fs.readSync) -} - -function patchLchmod (fs) { - fs.lchmod = function (path, mode, callback) { - fs.open( path - , constants.O_WRONLY | constants.O_SYMLINK - , mode - , function (err, fd) { - if (err) { - if (callback) callback(err) - return - } - // prefer to return the chmod error, if one occurs, - // but still try to close, and report closing errors if they occur. - fs.fchmod(fd, mode, function (err) { - fs.close(fd, function(err2) { - if (callback) callback(err || err2) - }) - }) - }) - } - - fs.lchmodSync = function (path, mode) { - var fd = fs.openSync(path, constants.O_WRONLY | constants.O_SYMLINK, mode) - // prefer to return the chmod error, if one occurs, - // but still try to close, and report closing errors if they occur. - var threw = true - var ret - try { - ret = fs.fchmodSync(fd, mode) - threw = false - } finally { - if (threw) { - try { - fs.closeSync(fd) - } catch (er) {} - } else { - fs.closeSync(fd) - } - } - return ret - } -} - -function patchLutimes (fs) { - if (constants.hasOwnProperty("O_SYMLINK")) { - fs.lutimes = function (path, at, mt, cb) { - fs.open(path, constants.O_SYMLINK, function (er, fd) { - if (er) { - if (cb) cb(er) + function patchLchmod (fs) { + fs.lchmod = function (path, mode, callback) { + fs.open( path + , constants.O_WRONLY | constants.O_SYMLINK + , mode + , function (err, fd) { + if (err) { + if (callback) callback(err) return } - fs.futimes(fd, at, mt, function (er) { - fs.close(fd, function (er2) { - if (cb) cb(er || er2) + // prefer to return the chmod error, if one occurs, + // but still try to close, and report closing errors if they occur. + fs.fchmod(fd, mode, function (err) { + fs.close(fd, function(err2) { + if (callback) callback(err || err2) }) }) }) } - fs.lutimesSync = function (path, at, mt) { - var fd = fs.openSync(path, constants.O_SYMLINK) - var ret + fs.lchmodSync = function (path, mode) { + var fd = fs.openSync(path, constants.O_WRONLY | constants.O_SYMLINK, mode) + + // prefer to return the chmod error, if one occurs, + // but still try to close, and report closing errors if they occur. var threw = true + var ret try { - ret = fs.futimesSync(fd, at, mt) + ret = fs.fchmodSync(fd, mode) threw = false } finally { if (threw) { @@ -3582,151 +3652,167 @@ function patchLutimes (fs) { } return ret } + } - } else { - fs.lutimes = function (_a, _b, _c, cb) { if (cb) process.nextTick(cb) } - fs.lutimesSync = function () {} + function patchLutimes (fs) { + if (constants.hasOwnProperty("O_SYMLINK")) { + fs.lutimes = function (path, at, mt, cb) { + fs.open(path, constants.O_SYMLINK, function (er, fd) { + if (er) { + if (cb) cb(er) + return + } + fs.futimes(fd, at, mt, function (er) { + fs.close(fd, function (er2) { + if (cb) cb(er || er2) + }) + }) + }) + } + + fs.lutimesSync = function (path, at, mt) { + var fd = fs.openSync(path, constants.O_SYMLINK) + var ret + var threw = true + try { + ret = fs.futimesSync(fd, at, mt) + threw = false + } finally { + if (threw) { + try { + fs.closeSync(fd) + } catch (er) {} + } else { + fs.closeSync(fd) + } + } + return ret + } + + } else { + fs.lutimes = function (_a, _b, _c, cb) { if (cb) process.nextTick(cb) } + fs.lutimesSync = function () {} + } } -} -function chmodFix (orig) { - if (!orig) return orig - return function (target, mode, cb) { - return orig.call(fs, target, mode, function (er) { - if (chownErOk(er)) er = null - if (cb) cb.apply(this, arguments) - }) + function chmodFix (orig) { + if (!orig) return orig + return function (target, mode, cb) { + return orig.call(fs, target, mode, function (er) { + if (chownErOk(er)) er = null + if (cb) cb.apply(this, arguments) + }) + } } -} -function chmodFixSync (orig) { - if (!orig) return orig - return function (target, mode) { - try { - return orig.call(fs, target, mode) - } catch (er) { - if (!chownErOk(er)) throw er + function chmodFixSync (orig) { + if (!orig) return orig + return function (target, mode) { + try { + return orig.call(fs, target, mode) + } catch (er) { + if (!chownErOk(er)) throw er + } } } -} -function chownFix (orig) { - if (!orig) return orig - return function (target, uid, gid, cb) { - return orig.call(fs, target, uid, gid, function (er) { - if (chownErOk(er)) er = null - if (cb) cb.apply(this, arguments) - }) + function chownFix (orig) { + if (!orig) return orig + return function (target, uid, gid, cb) { + return orig.call(fs, target, uid, gid, function (er) { + if (chownErOk(er)) er = null + if (cb) cb.apply(this, arguments) + }) + } } -} -function chownFixSync (orig) { - if (!orig) return orig - return function (target, uid, gid) { - try { - return orig.call(fs, target, uid, gid) - } catch (er) { - if (!chownErOk(er)) throw er + function chownFixSync (orig) { + if (!orig) return orig + return function (target, uid, gid) { + try { + return orig.call(fs, target, uid, gid) + } catch (er) { + if (!chownErOk(er)) throw er + } } } -} + function statFix (orig) { + if (!orig) return orig + // Older versions of Node erroneously returned signed integers for + // uid + gid. + return function (target, options, cb) { + if (typeof options === 'function') { + cb = options + options = null + } + function callback (er, stats) { + if (stats) { + if (stats.uid < 0) stats.uid += 0x100000000 + if (stats.gid < 0) stats.gid += 0x100000000 + } + if (cb) cb.apply(this, arguments) + } + return options ? orig.call(fs, target, options, callback) + : orig.call(fs, target, callback) + } + } -function statFix (orig) { - if (!orig) return orig - // Older versions of Node erroneously returned signed integers for - // uid + gid. - return function (target, cb) { - return orig.call(fs, target, function (er, stats) { - if (!stats) return cb.apply(this, arguments) + function statFixSync (orig) { + if (!orig) return orig + // Older versions of Node erroneously returned signed integers for + // uid + gid. + return function (target, options) { + var stats = options ? orig.call(fs, target, options) + : orig.call(fs, target) if (stats.uid < 0) stats.uid += 0x100000000 if (stats.gid < 0) stats.gid += 0x100000000 - if (cb) cb.apply(this, arguments) - }) + return stats; + } } -} -function statFixSync (orig) { - if (!orig) return orig - // Older versions of Node erroneously returned signed integers for - // uid + gid. - return function (target) { - var stats = orig.call(fs, target) - if (stats.uid < 0) stats.uid += 0x100000000 - if (stats.gid < 0) stats.gid += 0x100000000 - return stats; - } -} + // ENOSYS means that the fs doesn't support the op. Just ignore + // that, because it doesn't matter. + // + // if there's no getuid, or if getuid() is something other + // than 0, and the error is EINVAL or EPERM, then just ignore + // it. + // + // This specific case is a silent failure in cp, install, tar, + // and most other unix tools that manage permissions. + // + // When running as root, or if other types of errors are + // encountered, then it's strict. + function chownErOk (er) { + if (!er) + return true -// ENOSYS means that the fs doesn't support the op. Just ignore -// that, because it doesn't matter. -// -// if there's no getuid, or if getuid() is something other -// than 0, and the error is EINVAL or EPERM, then just ignore -// it. -// -// This specific case is a silent failure in cp, install, tar, -// and most other unix tools that manage permissions. -// -// When running as root, or if other types of errors are -// encountered, then it's strict. -function chownErOk (er) { - if (!er) - return true + if (er.code === "ENOSYS") + return true - if (er.code === "ENOSYS") - return true + var nonroot = !process.getuid || process.getuid() !== 0 + if (nonroot) { + if (er.code === "EINVAL" || er.code === "EPERM") + return true + } - var nonroot = !process.getuid || process.getuid() !== 0 - if (nonroot) { - if (er.code === "EINVAL" || er.code === "EPERM") - return true + return false } - - return false } /***/ }), /* 25 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -var fs = __webpack_require__(23) - -module.exports = clone(fs) - -function clone (obj) { - if (obj === null || typeof obj !== 'object') - return obj - - if (obj instanceof Object) - var copy = { __proto__: obj.__proto__ } - else - var copy = Object.create(null) - - Object.getOwnPropertyNames(obj).forEach(function (key) { - Object.defineProperty(copy, key, Object.getOwnPropertyDescriptor(obj, key)) - }) - - return copy -} - - -/***/ }), -/* 26 */ /***/ (function(module, exports) { module.exports = require("constants"); /***/ }), -/* 27 */ +/* 26 */ /***/ (function(module, exports, __webpack_require__) { -var Stream = __webpack_require__(28).Stream +var Stream = __webpack_require__(27).Stream module.exports = legacy @@ -3847,11 +3933,37 @@ function legacy (fs) { /***/ }), -/* 28 */ +/* 27 */ /***/ (function(module, exports) { module.exports = require("stream"); +/***/ }), +/* 28 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +module.exports = clone + +function clone (obj) { + if (obj === null || typeof obj !== 'object') + return obj + + if (obj instanceof Object) + var copy = { __proto__: obj.__proto__ } + else + var copy = Object.create(null) + + Object.getOwnPropertyNames(obj).forEach(function (key) { + Object.defineProperty(copy, key, Object.getOwnPropertyDescriptor(obj, key)) + }) + + return copy +} + + /***/ }), /* 29 */ /***/ (function(module, exports) { @@ -4300,6 +4412,10 @@ function ncp (source, dest, options, callback) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "log", function() { return log; }); +/* harmony import */ var _kbn_dev_utils__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(35); +/* harmony import */ var _kbn_dev_utils__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_kbn_dev_utils__WEBPACK_IMPORTED_MODULE_0__); +function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } + /* * Licensed to Elasticsearch B.V. under one or more contributor * license agreements. See the NOTICE file distributed with @@ -4318,27 +4434,312 @@ __webpack_require__.r(__webpack_exports__); * specific language governing permissions and limitations * under the License. */ -const log = { + + +class Log extends _kbn_dev_utils__WEBPACK_IMPORTED_MODULE_0__["ToolingLog"] { + constructor() { + super({ + level: 'info', + writeTo: process.stdout + }); + + _defineProperty(this, "testWriter", void 0); + } /** * Log something to the console. Ideally we would use a real logger in * kbn-pm, but that's a pretty big change for now. * @param ...args */ + + write(...args) { // eslint-disable-next-line no-console console.log(...args); } -}; +} + +const log = new Log(); /***/ }), /* 35 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +Object.defineProperty(exports, "__esModule", { value: true }); +const tslib_1 = __webpack_require__(36); +var proc_runner_1 = __webpack_require__(37); +exports.withProcRunner = proc_runner_1.withProcRunner; +var tooling_log_1 = __webpack_require__(415); +exports.ToolingLog = tooling_log_1.ToolingLog; +exports.ToolingLogTextWriter = tooling_log_1.ToolingLogTextWriter; +exports.pickLevelFromFlags = tooling_log_1.pickLevelFromFlags; +exports.ToolingLogCollectingWriter = tooling_log_1.ToolingLogCollectingWriter; +var serializers_1 = __webpack_require__(420); +exports.createAbsolutePathSerializer = serializers_1.createAbsolutePathSerializer; +var certs_1 = __webpack_require__(422); +exports.CA_CERT_PATH = certs_1.CA_CERT_PATH; +exports.ES_KEY_PATH = certs_1.ES_KEY_PATH; +exports.ES_CERT_PATH = certs_1.ES_CERT_PATH; +exports.ES_P12_PATH = certs_1.ES_P12_PATH; +exports.ES_P12_PASSWORD = certs_1.ES_P12_PASSWORD; +exports.ES_EMPTYPASSWORD_P12_PATH = certs_1.ES_EMPTYPASSWORD_P12_PATH; +exports.ES_NOPASSWORD_P12_PATH = certs_1.ES_NOPASSWORD_P12_PATH; +exports.KBN_KEY_PATH = certs_1.KBN_KEY_PATH; +exports.KBN_CERT_PATH = certs_1.KBN_CERT_PATH; +exports.KBN_P12_PATH = certs_1.KBN_P12_PATH; +exports.KBN_P12_PASSWORD = certs_1.KBN_P12_PASSWORD; +var run_1 = __webpack_require__(423); +exports.run = run_1.run; +exports.createFailError = run_1.createFailError; +exports.createFlagError = run_1.createFlagError; +exports.combineErrors = run_1.combineErrors; +exports.isFailError = run_1.isFailError; +var repo_root_1 = __webpack_require__(428); +exports.REPO_ROOT = repo_root_1.REPO_ROOT; +var kbn_client_1 = __webpack_require__(451); +exports.KbnClient = kbn_client_1.KbnClient; +tslib_1.__exportStar(__webpack_require__(493), exports); + + +/***/ }), +/* 36 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "parallelizeBatches", function() { return parallelizeBatches; }); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "parallelize", function() { return parallelize; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__extends", function() { return __extends; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__assign", function() { return __assign; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__rest", function() { return __rest; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__decorate", function() { return __decorate; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__param", function() { return __param; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__metadata", function() { return __metadata; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__awaiter", function() { return __awaiter; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__generator", function() { return __generator; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__exportStar", function() { return __exportStar; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__values", function() { return __values; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__read", function() { return __read; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__spread", function() { return __spread; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__await", function() { return __await; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__asyncGenerator", function() { return __asyncGenerator; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__asyncDelegator", function() { return __asyncDelegator; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__asyncValues", function() { return __asyncValues; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__makeTemplateObject", function() { return __makeTemplateObject; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__importStar", function() { return __importStar; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__importDefault", function() { return __importDefault; }); +/*! ***************************************************************************** +Copyright (c) Microsoft Corporation. All rights reserved. +Licensed under the Apache License, Version 2.0 (the "License"); you may not use +this file except in compliance with the License. You may obtain a copy of the +License at http://www.apache.org/licenses/LICENSE-2.0 + +THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED +WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, +MERCHANTABLITY OR NON-INFRINGEMENT. + +See the Apache Version 2.0 License for specific language governing permissions +and limitations under the License. +***************************************************************************** */ +/* global Reflect, Promise */ + +var extendStatics = function(d, b) { + extendStatics = Object.setPrototypeOf || + ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || + function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; + return extendStatics(d, b); +}; + +function __extends(d, b) { + extendStatics(d, b); + function __() { this.constructor = d; } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); +} + +var __assign = function() { + __assign = Object.assign || function __assign(t) { + for (var s, i = 1, n = arguments.length; i < n; i++) { + s = arguments[i]; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; + } + return t; + } + return __assign.apply(this, arguments); +} + +function __rest(s, e) { + var t = {}; + for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) + t[p] = s[p]; + if (s != null && typeof Object.getOwnPropertySymbols === "function") + for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) if (e.indexOf(p[i]) < 0) + t[p[i]] = s[p[i]]; + return t; +} + +function __decorate(decorators, target, key, desc) { + var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; + if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); + else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; + return c > 3 && r && Object.defineProperty(target, key, r), r; +} + +function __param(paramIndex, decorator) { + return function (target, key) { decorator(target, key, paramIndex); } +} + +function __metadata(metadataKey, metadataValue) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(metadataKey, metadataValue); +} + +function __awaiter(thisArg, _arguments, P, generator) { + return new (P || (P = Promise))(function (resolve, reject) { + function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } + function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } + function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } + step((generator = generator.apply(thisArg, _arguments || [])).next()); + }); +} + +function __generator(thisArg, body) { + var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; + return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; + function verb(n) { return function (v) { return step([n, v]); }; } + function step(op) { + if (f) throw new TypeError("Generator is already executing."); + while (_) try { + if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; + if (y = 0, t) op = [op[0] & 2, t.value]; + switch (op[0]) { + case 0: case 1: t = op; break; + case 4: _.label++; return { value: op[1], done: false }; + case 5: _.label++; y = op[1]; op = [0]; continue; + case 7: op = _.ops.pop(); _.trys.pop(); continue; + default: + if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } + if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } + if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } + if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } + if (t[2]) _.ops.pop(); + _.trys.pop(); continue; + } + op = body.call(thisArg, _); + } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } + if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; + } +} + +function __exportStar(m, exports) { + for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; +} + +function __values(o) { + var m = typeof Symbol === "function" && o[Symbol.iterator], i = 0; + if (m) return m.call(o); + return { + next: function () { + if (o && i >= o.length) o = void 0; + return { value: o && o[i++], done: !o }; + } + }; +} + +function __read(o, n) { + var m = typeof Symbol === "function" && o[Symbol.iterator]; + if (!m) return o; + var i = m.call(o), r, ar = [], e; + try { + while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value); + } + catch (error) { e = { error: error }; } + finally { + try { + if (r && !r.done && (m = i["return"])) m.call(i); + } + finally { if (e) throw e.error; } + } + return ar; +} + +function __spread() { + for (var ar = [], i = 0; i < arguments.length; i++) + ar = ar.concat(__read(arguments[i])); + return ar; +} + +function __await(v) { + return this instanceof __await ? (this.v = v, this) : new __await(v); +} + +function __asyncGenerator(thisArg, _arguments, generator) { + if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); + var g = generator.apply(thisArg, _arguments || []), i, q = []; + return i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i; + function verb(n) { if (g[n]) i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; } + function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } } + function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); } + function fulfill(value) { resume("next", value); } + function reject(value) { resume("throw", value); } + function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); } +} + +function __asyncDelegator(o) { + var i, p; + return i = {}, verb("next"), verb("throw", function (e) { throw e; }), verb("return"), i[Symbol.iterator] = function () { return this; }, i; + function verb(n, f) { i[n] = o[n] ? function (v) { return (p = !p) ? { value: __await(o[n](v)), done: n === "return" } : f ? f(v) : v; } : f; } +} + +function __asyncValues(o) { + if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); + var m = o[Symbol.asyncIterator], i; + return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i); + function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; } + function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); } +} + +function __makeTemplateObject(cooked, raw) { + if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; } + return cooked; +}; + +function __importStar(mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; + result.default = mod; + return result; +} + +function __importDefault(mod) { + return (mod && mod.__esModule) ? mod : { default: mod }; +} + + +/***/ }), +/* 37 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + /* * Licensed to Elasticsearch B.V. under one or more contributor * license agreements. See the NOTICE file distributed with @@ -4357,65 +4758,64 @@ __webpack_require__.r(__webpack_exports__); * specific language governing permissions and limitations * under the License. */ -async function parallelizeBatches(batches, fn) { - for (const batch of batches) { - // We need to make sure the entire batch has completed before we can move on - // to the next batch - await parallelize(batch, fn); - } -} -async function parallelize(items, fn, concurrency = 4) { - if (items.length === 0) { - return; - } +Object.defineProperty(exports, "__esModule", { value: true }); +var with_proc_runner_1 = __webpack_require__(38); +exports.withProcRunner = with_proc_runner_1.withProcRunner; - return new Promise((resolve, reject) => { - let activePromises = 0; - const values = items.slice(0); - async function scheduleItem(item) { - activePromises++; +/***/ }), +/* 38 */ +/***/ (function(module, exports, __webpack_require__) { - try { - await fn(item); - activePromises--; +"use strict"; - if (values.length > 0) { - // We have more work to do, so we schedule the next promise - scheduleItem(values.shift()); - } else if (activePromises === 0) { - // We have no more values left, and all items have completed, so we've - // completed all the work. - resolve(); - } - } catch (error) { - reject(error); - } +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +Object.defineProperty(exports, "__esModule", { value: true }); +const proc_runner_1 = __webpack_require__(39); +/** + * Create a ProcRunner and pass it to an async function. When + * the async function finishes the ProcRunner is torn-down + * automatically + * + * @param {ToolingLog} log + * @param {async Function} fn + * @return {Promise} + */ +async function withProcRunner(log, fn) { + const procs = new proc_runner_1.ProcRunner(log); + try { + await fn(procs); + } + finally { + await procs.teardown(); } - - values.splice(0, concurrency).map(scheduleItem); - }); } +exports.withProcRunner = withProcRunner; + /***/ }), -/* 36 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/* 39 */ +/***/ (function(module, exports, __webpack_require__) { "use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "getProjects", function() { return getProjects; }); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "buildProjectGraph", function() { return buildProjectGraph; }); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "topologicallyBatchProjects", function() { return topologicallyBatchProjects; }); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "includeTransitiveProjects", function() { return includeTransitiveProjects; }); -/* harmony import */ var glob__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(37); -/* harmony import */ var glob__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(glob__WEBPACK_IMPORTED_MODULE_0__); -/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(16); -/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_1__); -/* harmony import */ var util__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(29); -/* harmony import */ var util__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(util__WEBPACK_IMPORTED_MODULE_2__); -/* harmony import */ var _errors__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(53); -/* harmony import */ var _project__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(54); -/* harmony import */ var _workspaces__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(171); + /* * Licensed to Elasticsearch B.V. under one or more contributor * license agreements. See the NOTICE file distributed with @@ -4434,17150 +4834,51825 @@ __webpack_require__.r(__webpack_exports__); * specific language governing permissions and limitations * under the License. */ +Object.defineProperty(exports, "__esModule", { value: true }); +const tslib_1 = __webpack_require__(36); +const moment_1 = tslib_1.__importDefault(__webpack_require__(40)); +const operators_1 = __webpack_require__(169); +const exit_hook_1 = tslib_1.__importDefault(__webpack_require__(348)); +const errors_1 = __webpack_require__(349); +const proc_1 = __webpack_require__(350); +const noop = () => { }; +/** + * Helper for starting and managing processes. In many ways it resembles the + * API from `grunt_run`, processes are named and can be started, waited for, + * backgrounded once they log something matching a RegExp... + * + * @class ProcRunner + */ +class ProcRunner { + constructor(log) { + this.log = log; + this.closing = false; + this.procs = []; + this.signalUnsubscribe = exit_hook_1.default(() => { + this.teardown().catch(error => { + log.error(`ProcRunner teardown error: ${error.stack}`); + }); + }); + } + /** + * Start a process, tracking it by `name` + * @param {String} name + * @param {Object} options + * @property {String} options.cmd executable to run + * @property {Array?} options.args arguments to provide the executable + * @property {String?} options.cwd current working directory for the process + * @property {RegExp|Boolean} options.wait Should start() wait for some time? Use + * `true` will wait until the proc exits, + * a `RegExp` will wait until that log line + * is found + * @return {Promise} + */ + async run(name, options) { + const { cmd, args = [], cwd = process.cwd(), stdin = undefined, wait = false, env = process.env, } = options; + if (this.closing) { + throw new Error('ProcRunner is closing'); + } + if (wait && !(wait instanceof RegExp) && wait !== true) { + throw new TypeError('wait param should either be a RegExp or `true`'); + } + if (!!this.getProc(name)) { + throw new Error(`Process with name "${name}" already running`); + } + const proc = this.startProc(name, { + cmd, + args, + cwd, + env, + stdin, + }); + try { + if (wait instanceof RegExp) { + // wait for process to log matching line + await proc.lines$ + .pipe(operators_1.filter(line => wait.test(line)), operators_1.first(), operators_1.catchError(err => { + if (err.name !== 'EmptyError') { + throw errors_1.createCliError(`[${name}] exited without matching pattern: ${wait}`); + } + else { + throw err; + } + })) + .toPromise(); + } + if (wait === true) { + // wait for process to complete + await proc.outcomePromise; + } + } + finally { + // while the procRunner closes promises will resolve/reject because + // processes and stopping, but consumers of run() shouldn't have to + // prepare for that, so just return a never-resolving promise + if (this.closing) { + await new Promise(noop); + } + } + } + /** + * Stop a named proc + */ + async stop(name, signal = 'SIGTERM') { + const proc = this.getProc(name); + if (proc) { + await proc.stop(signal); + } + else { + this.log.warning('[%s] already stopped', name); + } + } + /** + * Wait for all running processes to stop naturally + * @return {Promise} + */ + async waitForAllToStop() { + await Promise.all(this.procs.map(proc => proc.outcomePromise)); + } + /** + * Close the ProcRunner and stop all running + * processes with `signal` + * + * @param {String} [signal=undefined] + * @return {Promise} + */ + async teardown(signal = 'exit') { + if (this.closing) { + return; + } + this.closing = true; + this.signalUnsubscribe(); + if (!signal && this.procs.length > 0) { + this.log.warning('%d processes left running, stop them with procs.stop(name):', this.procs.length, this.procs.map(proc => proc.name)); + } + await Promise.all(this.procs.map(async (proc) => { + await proc.stop(signal === 'exit' ? 'SIGKILL' : signal); + })); + } + getProc(name) { + return this.procs.find(proc => { + return proc.name === name; + }); + } + startProc(name, options) { + const startMs = Date.now(); + const proc = proc_1.startProc(name, options, this.log); + this.procs.push(proc); + const remove = () => { + this.procs.splice(this.procs.indexOf(proc), 1); + }; + // tie into proc outcome$, remove from _procs on compete + proc.outcome$.subscribe({ + next: code => { + const duration = moment_1.default.duration(Date.now() - startMs); + this.log.info('[%s] exited with %s after %s', name, code, duration.humanize()); + }, + complete: () => { + remove(); + }, + error: error => { + if (this.closing) { + this.log.error(error); + } + remove(); + }, + }); + return proc; + } +} +exports.ProcRunner = ProcRunner; +/***/ }), +/* 40 */ +/***/ (function(module, exports, __webpack_require__) { +/* WEBPACK VAR INJECTION */(function(module) {var require;//! moment.js +;(function (global, factory) { + true ? module.exports = factory() : + undefined +}(this, (function () { 'use strict'; + var hookCallback; -const glob = Object(util__WEBPACK_IMPORTED_MODULE_2__["promisify"])(glob__WEBPACK_IMPORTED_MODULE_0___default.a); -async function getProjects(rootPath, projectsPathsPatterns, { - include = [], - exclude = [] -} = {}) { - const projects = new Map(); - const workspaceProjectsPaths = await Object(_workspaces__WEBPACK_IMPORTED_MODULE_5__["workspacePackagePaths"])(rootPath); + function hooks () { + return hookCallback.apply(null, arguments); + } - for (const pattern of projectsPathsPatterns) { - const pathsToProcess = await packagesFromGlobPattern({ - pattern, - rootPath - }); + // This is done to register the method called with moment() + // without creating circular dependencies. + function setHookCallback (callback) { + hookCallback = callback; + } - for (const filePath of pathsToProcess) { - const projectConfigPath = normalize(filePath); - const projectDir = path__WEBPACK_IMPORTED_MODULE_1___default.a.dirname(projectConfigPath); - const project = await _project__WEBPACK_IMPORTED_MODULE_4__["Project"].fromPath(projectDir); + function isArray(input) { + return input instanceof Array || Object.prototype.toString.call(input) === '[object Array]'; + } - if (workspaceProjectsPaths.indexOf(filePath) >= 0) { - project.isWorkspaceProject = true; - } + function isObject(input) { + // IE8 will treat undefined and null as object if it wasn't for + // input != null + return input != null && Object.prototype.toString.call(input) === '[object Object]'; + } - const excludeProject = exclude.includes(project.name) || include.length > 0 && !include.includes(project.name); + function isObjectEmpty(obj) { + if (Object.getOwnPropertyNames) { + return (Object.getOwnPropertyNames(obj).length === 0); + } else { + var k; + for (k in obj) { + if (obj.hasOwnProperty(k)) { + return false; + } + } + return true; + } + } - if (excludeProject) { - continue; - } + function isUndefined(input) { + return input === void 0; + } - if (projects.has(project.name)) { - throw new _errors__WEBPACK_IMPORTED_MODULE_3__["CliError"](`There are multiple projects with the same name [${project.name}]`, { - name: project.name, - paths: [project.path, projects.get(project.name).path] - }); - } + function isNumber(input) { + return typeof input === 'number' || Object.prototype.toString.call(input) === '[object Number]'; + } - projects.set(project.name, project); + function isDate(input) { + return input instanceof Date || Object.prototype.toString.call(input) === '[object Date]'; } - } - return projects; -} + function map(arr, fn) { + var res = [], i; + for (i = 0; i < arr.length; ++i) { + res.push(fn(arr[i], i)); + } + return res; + } -function packagesFromGlobPattern({ - pattern, - rootPath -}) { - const globOptions = { - cwd: rootPath, - // Should throw in case of unusual errors when reading the file system - strict: true, - // Always returns absolute paths for matched files - absolute: true, - // Do not match ** against multiple filenames - // (This is only specified because we currently don't have a need for it.) - noglobstar: true - }; - return glob(path__WEBPACK_IMPORTED_MODULE_1___default.a.join(pattern, 'package.json'), globOptions); -} // https://github.com/isaacs/node-glob/blob/master/common.js#L104 -// glob always returns "\\" as "/" in windows, so everyone -// gets normalized because we can't have nice things. + function hasOwnProp(a, b) { + return Object.prototype.hasOwnProperty.call(a, b); + } + function extend(a, b) { + for (var i in b) { + if (hasOwnProp(b, i)) { + a[i] = b[i]; + } + } -function normalize(dir) { - return path__WEBPACK_IMPORTED_MODULE_1___default.a.normalize(dir); -} + if (hasOwnProp(b, 'toString')) { + a.toString = b.toString; + } -function buildProjectGraph(projects) { - const projectGraph = new Map(); + if (hasOwnProp(b, 'valueOf')) { + a.valueOf = b.valueOf; + } - for (const project of projects.values()) { - const projectDeps = []; - const dependencies = project.allDependencies; + return a; + } - for (const depName of Object.keys(dependencies)) { - if (projects.has(depName)) { - const dep = projects.get(depName); - const dependentProjectIsInWorkspace = project.isWorkspaceProject || project.json.name === 'kibana'; - project.ensureValidProjectDependency(dep, dependentProjectIsInWorkspace); - projectDeps.push(dep); - } + function createUTC (input, format, locale, strict) { + return createLocalOrUTC(input, format, locale, strict, true).utc(); } - projectGraph.set(project.name, projectDeps); - } + function defaultParsingFlags() { + // We need to deep clone this object. + return { + empty : false, + unusedTokens : [], + unusedInput : [], + overflow : -2, + charsLeftOver : 0, + nullInput : false, + invalidMonth : null, + invalidFormat : false, + userInvalidated : false, + iso : false, + parsedDateParts : [], + meridiem : null, + rfc2822 : false, + weekdayMismatch : false + }; + } - return projectGraph; -} -function topologicallyBatchProjects(projectsToBatch, projectGraph, { - batchByWorkspace = false -} = {}) { - // We're going to be chopping stuff out of this list, so copy it. - const projectsLeftToBatch = new Set(projectsToBatch.keys()); - const batches = []; + function getParsingFlags(m) { + if (m._pf == null) { + m._pf = defaultParsingFlags(); + } + return m._pf; + } - if (batchByWorkspace) { - const workspaceRootProject = Array.from(projectsToBatch.values()).find(p => p.isWorkspaceRoot); + var some; + if (Array.prototype.some) { + some = Array.prototype.some; + } else { + some = function (fun) { + var t = Object(this); + var len = t.length >>> 0; - if (!workspaceRootProject) { - throw new _errors__WEBPACK_IMPORTED_MODULE_3__["CliError"](`There was no yarn workspace root found.`); - } // Push in the workspace root first. + for (var i = 0; i < len; i++) { + if (i in t && fun.call(this, t[i], i, t)) { + return true; + } + } + return false; + }; + } - batches.push([workspaceRootProject]); - projectsLeftToBatch.delete(workspaceRootProject.name); // In the next batch, push in all workspace projects. + function isValid(m) { + if (m._isValid == null) { + var flags = getParsingFlags(m); + var parsedParts = some.call(flags.parsedDateParts, function (i) { + return i != null; + }); + var isNowValid = !isNaN(m._d.getTime()) && + flags.overflow < 0 && + !flags.empty && + !flags.invalidMonth && + !flags.invalidWeekday && + !flags.weekdayMismatch && + !flags.nullInput && + !flags.invalidFormat && + !flags.userInvalidated && + (!flags.meridiem || (flags.meridiem && parsedParts)); + + if (m._strict) { + isNowValid = isNowValid && + flags.charsLeftOver === 0 && + flags.unusedTokens.length === 0 && + flags.bigHour === undefined; + } - const workspaceBatch = []; + if (Object.isFrozen == null || !Object.isFrozen(m)) { + m._isValid = isNowValid; + } + else { + return isNowValid; + } + } + return m._isValid; + } - for (const projectName of projectsLeftToBatch) { - const project = projectsToBatch.get(projectName); + function createInvalid (flags) { + var m = createUTC(NaN); + if (flags != null) { + extend(getParsingFlags(m), flags); + } + else { + getParsingFlags(m).userInvalidated = true; + } - if (project.isWorkspaceProject) { - workspaceBatch.push(project); - projectsLeftToBatch.delete(projectName); - } + return m; } - batches.push(workspaceBatch); - } + // Plugins that add properties should also add the key here (null value), + // so we can properly clone ourselves. + var momentProperties = hooks.momentProperties = []; - while (projectsLeftToBatch.size > 0) { - // Get all projects that have no remaining dependencies within the repo - // that haven't yet been picked. - const batch = []; + function copyConfig(to, from) { + var i, prop, val; - for (const projectName of projectsLeftToBatch) { - const projectDeps = projectGraph.get(projectName); - const needsDependenciesBatched = projectDeps.some(dep => projectsLeftToBatch.has(dep.name)); + if (!isUndefined(from._isAMomentObject)) { + to._isAMomentObject = from._isAMomentObject; + } + if (!isUndefined(from._i)) { + to._i = from._i; + } + if (!isUndefined(from._f)) { + to._f = from._f; + } + if (!isUndefined(from._l)) { + to._l = from._l; + } + if (!isUndefined(from._strict)) { + to._strict = from._strict; + } + if (!isUndefined(from._tzm)) { + to._tzm = from._tzm; + } + if (!isUndefined(from._isUTC)) { + to._isUTC = from._isUTC; + } + if (!isUndefined(from._offset)) { + to._offset = from._offset; + } + if (!isUndefined(from._pf)) { + to._pf = getParsingFlags(from); + } + if (!isUndefined(from._locale)) { + to._locale = from._locale; + } - if (!needsDependenciesBatched) { - batch.push(projectsToBatch.get(projectName)); - } - } // If we weren't able to find a project with no remaining dependencies, - // then we've encountered a cycle in the dependency graph. + if (momentProperties.length > 0) { + for (i = 0; i < momentProperties.length; i++) { + prop = momentProperties[i]; + val = from[prop]; + if (!isUndefined(val)) { + to[prop] = val; + } + } + } + return to; + } - const hasCycles = batch.length === 0; + var updateInProgress = false; - if (hasCycles) { - const cycleProjectNames = [...projectsLeftToBatch]; - const message = 'Encountered a cycle in the dependency graph. Projects in cycle are:\n' + cycleProjectNames.join(', '); - throw new _errors__WEBPACK_IMPORTED_MODULE_3__["CliError"](message); + // Moment prototype object + function Moment(config) { + copyConfig(this, config); + this._d = new Date(config._d != null ? config._d.getTime() : NaN); + if (!this.isValid()) { + this._d = new Date(NaN); + } + // Prevent infinite loop in case updateOffset creates new moment + // objects. + if (updateInProgress === false) { + updateInProgress = true; + hooks.updateOffset(this); + updateInProgress = false; + } } - batches.push(batch); - batch.forEach(project => projectsLeftToBatch.delete(project.name)); - } + function isMoment (obj) { + return obj instanceof Moment || (obj != null && obj._isAMomentObject != null); + } - return batches; -} -function includeTransitiveProjects(subsetOfProjects, allProjects, { - onlyProductionDependencies = false -} = {}) { - const dependentProjects = new Map(); // the current list of packages we are expanding using breadth-first-search + function absFloor (number) { + if (number < 0) { + // -0 -> 0 + return Math.ceil(number) || 0; + } else { + return Math.floor(number); + } + } - const toProcess = [...subsetOfProjects]; + function toInt(argumentForCoercion) { + var coercedNumber = +argumentForCoercion, + value = 0; - while (toProcess.length > 0) { - const project = toProcess.shift(); - const dependencies = onlyProductionDependencies ? project.productionDependencies : project.allDependencies; - Object.keys(dependencies).forEach(dep => { - if (allProjects.has(dep)) { - toProcess.push(allProjects.get(dep)); - } - }); - dependentProjects.set(project.name, project); - } + if (coercedNumber !== 0 && isFinite(coercedNumber)) { + value = absFloor(coercedNumber); + } - return dependentProjects; -} + return value; + } -/***/ }), -/* 37 */ -/***/ (function(module, exports, __webpack_require__) { + // compare two arrays, return the number of differences + function compareArrays(array1, array2, dontConvert) { + var len = Math.min(array1.length, array2.length), + lengthDiff = Math.abs(array1.length - array2.length), + diffs = 0, + i; + for (i = 0; i < len; i++) { + if ((dontConvert && array1[i] !== array2[i]) || + (!dontConvert && toInt(array1[i]) !== toInt(array2[i]))) { + diffs++; + } + } + return diffs + lengthDiff; + } -// Approach: -// -// 1. Get the minimatch set -// 2. For each pattern in the set, PROCESS(pattern, false) -// 3. Store matches per-set, then uniq them -// -// PROCESS(pattern, inGlobStar) -// Get the first [n] items from pattern that are all strings -// Join these together. This is PREFIX. -// If there is no more remaining, then stat(PREFIX) and -// add to matches if it succeeds. END. -// -// If inGlobStar and PREFIX is symlink and points to dir -// set ENTRIES = [] -// else readdir(PREFIX) as ENTRIES -// If fail, END -// -// with ENTRIES -// If pattern[n] is GLOBSTAR -// // handle the case where the globstar match is empty -// // by pruning it out, and testing the resulting pattern -// PROCESS(pattern[0..n] + pattern[n+1 .. $], false) -// // handle other cases. -// for ENTRY in ENTRIES (not dotfiles) -// // attach globstar + tail onto the entry -// // Mark that this entry is a globstar match -// PROCESS(pattern[0..n] + ENTRY + pattern[n .. $], true) -// -// else // not globstar -// for ENTRY in ENTRIES (not dotfiles, unless pattern[n] is dot) -// Test ENTRY against pattern[n] -// If fails, continue -// If passes, PROCESS(pattern[0..n] + item + pattern[n+1 .. $]) -// -// Caveat: -// Cache all stats and readdirs results to minimize syscall. Since all -// we ever care about is existence and directory-ness, we can just keep -// `true` for files, and [children,...] for directories, or `false` for -// things that don't exist. + function warn(msg) { + if (hooks.suppressDeprecationWarnings === false && + (typeof console !== 'undefined') && console.warn) { + console.warn('Deprecation warning: ' + msg); + } + } -module.exports = glob + function deprecate(msg, fn) { + var firstTime = true; -var fs = __webpack_require__(23) -var rp = __webpack_require__(38) -var minimatch = __webpack_require__(40) -var Minimatch = minimatch.Minimatch -var inherits = __webpack_require__(44) -var EE = __webpack_require__(46).EventEmitter -var path = __webpack_require__(16) -var assert = __webpack_require__(30) -var isAbsolute = __webpack_require__(47) -var globSync = __webpack_require__(48) -var common = __webpack_require__(49) -var alphasort = common.alphasort -var alphasorti = common.alphasorti -var setopts = common.setopts -var ownProp = common.ownProp -var inflight = __webpack_require__(50) -var util = __webpack_require__(29) -var childrenIgnored = common.childrenIgnored -var isIgnored = common.isIgnored + return extend(function () { + if (hooks.deprecationHandler != null) { + hooks.deprecationHandler(null, msg); + } + if (firstTime) { + var args = []; + var arg; + for (var i = 0; i < arguments.length; i++) { + arg = ''; + if (typeof arguments[i] === 'object') { + arg += '\n[' + i + '] '; + for (var key in arguments[0]) { + arg += key + ': ' + arguments[0][key] + ', '; + } + arg = arg.slice(0, -2); // Remove trailing comma and space + } else { + arg = arguments[i]; + } + args.push(arg); + } + warn(msg + '\nArguments: ' + Array.prototype.slice.call(args).join('') + '\n' + (new Error()).stack); + firstTime = false; + } + return fn.apply(this, arguments); + }, fn); + } -var once = __webpack_require__(52) + var deprecations = {}; -function glob (pattern, options, cb) { - if (typeof options === 'function') cb = options, options = {} - if (!options) options = {} + function deprecateSimple(name, msg) { + if (hooks.deprecationHandler != null) { + hooks.deprecationHandler(name, msg); + } + if (!deprecations[name]) { + warn(msg); + deprecations[name] = true; + } + } - if (options.sync) { - if (cb) - throw new TypeError('callback provided to sync glob') - return globSync(pattern, options) - } + hooks.suppressDeprecationWarnings = false; + hooks.deprecationHandler = null; - return new Glob(pattern, options, cb) -} + function isFunction(input) { + return input instanceof Function || Object.prototype.toString.call(input) === '[object Function]'; + } -glob.sync = globSync -var GlobSync = glob.GlobSync = globSync.GlobSync + function set (config) { + var prop, i; + for (i in config) { + prop = config[i]; + if (isFunction(prop)) { + this[i] = prop; + } else { + this['_' + i] = prop; + } + } + this._config = config; + // Lenient ordinal parsing accepts just a number in addition to + // number + (possibly) stuff coming from _dayOfMonthOrdinalParse. + // TODO: Remove "ordinalParse" fallback in next major release. + this._dayOfMonthOrdinalParseLenient = new RegExp( + (this._dayOfMonthOrdinalParse.source || this._ordinalParse.source) + + '|' + (/\d{1,2}/).source); + } + + function mergeConfigs(parentConfig, childConfig) { + var res = extend({}, parentConfig), prop; + for (prop in childConfig) { + if (hasOwnProp(childConfig, prop)) { + if (isObject(parentConfig[prop]) && isObject(childConfig[prop])) { + res[prop] = {}; + extend(res[prop], parentConfig[prop]); + extend(res[prop], childConfig[prop]); + } else if (childConfig[prop] != null) { + res[prop] = childConfig[prop]; + } else { + delete res[prop]; + } + } + } + for (prop in parentConfig) { + if (hasOwnProp(parentConfig, prop) && + !hasOwnProp(childConfig, prop) && + isObject(parentConfig[prop])) { + // make sure changes to properties don't modify parent config + res[prop] = extend({}, res[prop]); + } + } + return res; + } -// old api surface -glob.glob = glob + function Locale(config) { + if (config != null) { + this.set(config); + } + } -function extend (origin, add) { - if (add === null || typeof add !== 'object') { - return origin - } + var keys; - var keys = Object.keys(add) - var i = keys.length - while (i--) { - origin[keys[i]] = add[keys[i]] - } - return origin -} + if (Object.keys) { + keys = Object.keys; + } else { + keys = function (obj) { + var i, res = []; + for (i in obj) { + if (hasOwnProp(obj, i)) { + res.push(i); + } + } + return res; + }; + } -glob.hasMagic = function (pattern, options_) { - var options = extend({}, options_) - options.noprocess = true + var defaultCalendar = { + sameDay : '[Today at] LT', + nextDay : '[Tomorrow at] LT', + nextWeek : 'dddd [at] LT', + lastDay : '[Yesterday at] LT', + lastWeek : '[Last] dddd [at] LT', + sameElse : 'L' + }; - var g = new Glob(pattern, options) - var set = g.minimatch.set + function calendar (key, mom, now) { + var output = this._calendar[key] || this._calendar['sameElse']; + return isFunction(output) ? output.call(mom, now) : output; + } - if (!pattern) - return false + var defaultLongDateFormat = { + LTS : 'h:mm:ss A', + LT : 'h:mm A', + L : 'MM/DD/YYYY', + LL : 'MMMM D, YYYY', + LLL : 'MMMM D, YYYY h:mm A', + LLLL : 'dddd, MMMM D, YYYY h:mm A' + }; - if (set.length > 1) - return true + function longDateFormat (key) { + var format = this._longDateFormat[key], + formatUpper = this._longDateFormat[key.toUpperCase()]; - for (var j = 0; j < set[0].length; j++) { - if (typeof set[0][j] !== 'string') - return true - } + if (format || !formatUpper) { + return format; + } - return false -} + this._longDateFormat[key] = formatUpper.replace(/MMMM|MM|DD|dddd/g, function (val) { + return val.slice(1); + }); -glob.Glob = Glob -inherits(Glob, EE) -function Glob (pattern, options, cb) { - if (typeof options === 'function') { - cb = options - options = null - } + return this._longDateFormat[key]; + } - if (options && options.sync) { - if (cb) - throw new TypeError('callback provided to sync glob') - return new GlobSync(pattern, options) - } + var defaultInvalidDate = 'Invalid date'; - if (!(this instanceof Glob)) - return new Glob(pattern, options, cb) + function invalidDate () { + return this._invalidDate; + } - setopts(this, pattern, options) - this._didRealPath = false + var defaultOrdinal = '%d'; + var defaultDayOfMonthOrdinalParse = /\d{1,2}/; - // process each pattern in the minimatch set - var n = this.minimatch.set.length + function ordinal (number) { + return this._ordinal.replace('%d', number); + } - // The matches are stored as {: true,...} so that - // duplicates are automagically pruned. - // Later, we do an Object.keys() on these. - // Keep them as a list so we can fill in when nonull is set. - this.matches = new Array(n) + var defaultRelativeTime = { + future : 'in %s', + past : '%s ago', + s : 'a few seconds', + ss : '%d seconds', + m : 'a minute', + mm : '%d minutes', + h : 'an hour', + hh : '%d hours', + d : 'a day', + dd : '%d days', + M : 'a month', + MM : '%d months', + y : 'a year', + yy : '%d years' + }; - if (typeof cb === 'function') { - cb = once(cb) - this.on('error', cb) - this.on('end', function (matches) { - cb(null, matches) - }) - } + function relativeTime (number, withoutSuffix, string, isFuture) { + var output = this._relativeTime[string]; + return (isFunction(output)) ? + output(number, withoutSuffix, string, isFuture) : + output.replace(/%d/i, number); + } - var self = this - this._processing = 0 + function pastFuture (diff, output) { + var format = this._relativeTime[diff > 0 ? 'future' : 'past']; + return isFunction(format) ? format(output) : format.replace(/%s/i, output); + } - this._emitQueue = [] - this._processQueue = [] - this.paused = false + var aliases = {}; - if (this.noprocess) - return this + function addUnitAlias (unit, shorthand) { + var lowerCase = unit.toLowerCase(); + aliases[lowerCase] = aliases[lowerCase + 's'] = aliases[shorthand] = unit; + } - if (n === 0) - return done() + function normalizeUnits(units) { + return typeof units === 'string' ? aliases[units] || aliases[units.toLowerCase()] : undefined; + } - var sync = true - for (var i = 0; i < n; i ++) { - this._process(this.minimatch.set[i], i, false, done) - } - sync = false + function normalizeObjectUnits(inputObject) { + var normalizedInput = {}, + normalizedProp, + prop; - function done () { - --self._processing - if (self._processing <= 0) { - if (sync) { - process.nextTick(function () { - self._finish() - }) - } else { - self._finish() - } + for (prop in inputObject) { + if (hasOwnProp(inputObject, prop)) { + normalizedProp = normalizeUnits(prop); + if (normalizedProp) { + normalizedInput[normalizedProp] = inputObject[prop]; + } + } + } + + return normalizedInput; } - } -} -Glob.prototype._finish = function () { - assert(this instanceof Glob) - if (this.aborted) - return + var priorities = {}; - if (this.realpath && !this._didRealpath) - return this._realpath() + function addUnitPriority(unit, priority) { + priorities[unit] = priority; + } - common.finish(this) - this.emit('end', this.found) -} + function getPrioritizedUnits(unitsObj) { + var units = []; + for (var u in unitsObj) { + units.push({unit: u, priority: priorities[u]}); + } + units.sort(function (a, b) { + return a.priority - b.priority; + }); + return units; + } -Glob.prototype._realpath = function () { - if (this._didRealpath) - return + function zeroFill(number, targetLength, forceSign) { + var absNumber = '' + Math.abs(number), + zerosToFill = targetLength - absNumber.length, + sign = number >= 0; + return (sign ? (forceSign ? '+' : '') : '-') + + Math.pow(10, Math.max(0, zerosToFill)).toString().substr(1) + absNumber; + } - this._didRealpath = true + var formattingTokens = /(\[[^\[]*\])|(\\)?([Hh]mm(ss)?|Mo|MM?M?M?|Do|DDDo|DD?D?D?|ddd?d?|do?|w[o|w]?|W[o|W]?|Qo?|YYYYYY|YYYYY|YYYY|YY|gg(ggg?)?|GG(GGG?)?|e|E|a|A|hh?|HH?|kk?|mm?|ss?|S{1,9}|x|X|zz?|ZZ?|.)/g; - var n = this.matches.length - if (n === 0) - return this._finish() + var localFormattingTokens = /(\[[^\[]*\])|(\\)?(LTS|LT|LL?L?L?|l{1,4})/g; - var self = this - for (var i = 0; i < this.matches.length; i++) - this._realpathSet(i, next) + var formatFunctions = {}; - function next () { - if (--n === 0) - self._finish() - } -} + var formatTokenFunctions = {}; -Glob.prototype._realpathSet = function (index, cb) { - var matchset = this.matches[index] - if (!matchset) - return cb() + // token: 'M' + // padded: ['MM', 2] + // ordinal: 'Mo' + // callback: function () { this.month() + 1 } + function addFormatToken (token, padded, ordinal, callback) { + var func = callback; + if (typeof callback === 'string') { + func = function () { + return this[callback](); + }; + } + if (token) { + formatTokenFunctions[token] = func; + } + if (padded) { + formatTokenFunctions[padded[0]] = function () { + return zeroFill(func.apply(this, arguments), padded[1], padded[2]); + }; + } + if (ordinal) { + formatTokenFunctions[ordinal] = function () { + return this.localeData().ordinal(func.apply(this, arguments), token); + }; + } + } - var found = Object.keys(matchset) - var self = this - var n = found.length + function removeFormattingTokens(input) { + if (input.match(/\[[\s\S]/)) { + return input.replace(/^\[|\]$/g, ''); + } + return input.replace(/\\/g, ''); + } - if (n === 0) - return cb() + function makeFormatFunction(format) { + var array = format.match(formattingTokens), i, length; - var set = this.matches[index] = Object.create(null) - found.forEach(function (p, i) { - // If there's a problem with the stat, then it means that - // one or more of the links in the realpath couldn't be - // resolved. just return the abs value in that case. - p = self._makeAbs(p) - rp.realpath(p, self.realpathCache, function (er, real) { - if (!er) - set[real] = true - else if (er.syscall === 'stat') - set[p] = true - else - self.emit('error', er) // srsly wtf right here + for (i = 0, length = array.length; i < length; i++) { + if (formatTokenFunctions[array[i]]) { + array[i] = formatTokenFunctions[array[i]]; + } else { + array[i] = removeFormattingTokens(array[i]); + } + } - if (--n === 0) { - self.matches[index] = set - cb() - } - }) - }) -} + return function (mom) { + var output = '', i; + for (i = 0; i < length; i++) { + output += isFunction(array[i]) ? array[i].call(mom, format) : array[i]; + } + return output; + }; + } -Glob.prototype._mark = function (p) { - return common.mark(this, p) -} + // format date using native date object + function formatMoment(m, format) { + if (!m.isValid()) { + return m.localeData().invalidDate(); + } -Glob.prototype._makeAbs = function (f) { - return common.makeAbs(this, f) -} + format = expandFormat(format, m.localeData()); + formatFunctions[format] = formatFunctions[format] || makeFormatFunction(format); -Glob.prototype.abort = function () { - this.aborted = true - this.emit('abort') -} + return formatFunctions[format](m); + } -Glob.prototype.pause = function () { - if (!this.paused) { - this.paused = true - this.emit('pause') - } -} + function expandFormat(format, locale) { + var i = 5; -Glob.prototype.resume = function () { - if (this.paused) { - this.emit('resume') - this.paused = false - if (this._emitQueue.length) { - var eq = this._emitQueue.slice(0) - this._emitQueue.length = 0 - for (var i = 0; i < eq.length; i ++) { - var e = eq[i] - this._emitMatch(e[0], e[1]) - } + function replaceLongDateFormatTokens(input) { + return locale.longDateFormat(input) || input; + } + + localFormattingTokens.lastIndex = 0; + while (i >= 0 && localFormattingTokens.test(format)) { + format = format.replace(localFormattingTokens, replaceLongDateFormatTokens); + localFormattingTokens.lastIndex = 0; + i -= 1; + } + + return format; } - if (this._processQueue.length) { - var pq = this._processQueue.slice(0) - this._processQueue.length = 0 - for (var i = 0; i < pq.length; i ++) { - var p = pq[i] - this._processing-- - this._process(p[0], p[1], p[2], p[3]) - } + + var match1 = /\d/; // 0 - 9 + var match2 = /\d\d/; // 00 - 99 + var match3 = /\d{3}/; // 000 - 999 + var match4 = /\d{4}/; // 0000 - 9999 + var match6 = /[+-]?\d{6}/; // -999999 - 999999 + var match1to2 = /\d\d?/; // 0 - 99 + var match3to4 = /\d\d\d\d?/; // 999 - 9999 + var match5to6 = /\d\d\d\d\d\d?/; // 99999 - 999999 + var match1to3 = /\d{1,3}/; // 0 - 999 + var match1to4 = /\d{1,4}/; // 0 - 9999 + var match1to6 = /[+-]?\d{1,6}/; // -999999 - 999999 + + var matchUnsigned = /\d+/; // 0 - inf + var matchSigned = /[+-]?\d+/; // -inf - inf + + var matchOffset = /Z|[+-]\d\d:?\d\d/gi; // +00:00 -00:00 +0000 -0000 or Z + var matchShortOffset = /Z|[+-]\d\d(?::?\d\d)?/gi; // +00 -00 +00:00 -00:00 +0000 -0000 or Z + + var matchTimestamp = /[+-]?\d+(\.\d{1,3})?/; // 123456789 123456789.123 + + // any word (or two) characters or numbers including two/three word month in arabic. + // includes scottish gaelic two word and hyphenated months + var matchWord = /[0-9]{0,256}['a-z\u00A0-\u05FF\u0700-\uD7FF\uF900-\uFDCF\uFDF0-\uFF07\uFF10-\uFFEF]{1,256}|[\u0600-\u06FF\/]{1,256}(\s*?[\u0600-\u06FF]{1,256}){1,2}/i; + + var regexes = {}; + + function addRegexToken (token, regex, strictRegex) { + regexes[token] = isFunction(regex) ? regex : function (isStrict, localeData) { + return (isStrict && strictRegex) ? strictRegex : regex; + }; } - } -} -Glob.prototype._process = function (pattern, index, inGlobStar, cb) { - assert(this instanceof Glob) - assert(typeof cb === 'function') + function getParseRegexForToken (token, config) { + if (!hasOwnProp(regexes, token)) { + return new RegExp(unescapeFormat(token)); + } - if (this.aborted) - return + return regexes[token](config._strict, config._locale); + } - this._processing++ - if (this.paused) { - this._processQueue.push([pattern, index, inGlobStar, cb]) - return - } + // Code from http://stackoverflow.com/questions/3561493/is-there-a-regexp-escape-function-in-javascript + function unescapeFormat(s) { + return regexEscape(s.replace('\\', '').replace(/\\(\[)|\\(\])|\[([^\]\[]*)\]|\\(.)/g, function (matched, p1, p2, p3, p4) { + return p1 || p2 || p3 || p4; + })); + } - //console.error('PROCESS %d', this._processing, pattern) + function regexEscape(s) { + return s.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&'); + } - // Get the first [n] parts of pattern that are all strings. - var n = 0 - while (typeof pattern[n] === 'string') { - n ++ - } - // now n is the index of the first one that is *not* a string. + var tokens = {}; - // see if there's anything else - var prefix - switch (n) { - // if not, then this is rather simple - case pattern.length: - this._processSimple(pattern.join('/'), index, cb) - return + function addParseToken (token, callback) { + var i, func = callback; + if (typeof token === 'string') { + token = [token]; + } + if (isNumber(callback)) { + func = function (input, array) { + array[callback] = toInt(input); + }; + } + for (i = 0; i < token.length; i++) { + tokens[token[i]] = func; + } + } - case 0: - // pattern *starts* with some non-trivial item. - // going to readdir(cwd), but not include the prefix in matches. - prefix = null - break + function addWeekParseToken (token, callback) { + addParseToken(token, function (input, array, config, token) { + config._w = config._w || {}; + callback(input, config._w, config, token); + }); + } - default: - // pattern has some string bits in the front. - // whatever it starts with, whether that's 'absolute' like /foo/bar, - // or 'relative' like '../baz' - prefix = pattern.slice(0, n).join('/') - break - } + function addTimeToArrayFromToken(token, input, config) { + if (input != null && hasOwnProp(tokens, token)) { + tokens[token](input, config._a, config, token); + } + } - var remain = pattern.slice(n) + var YEAR = 0; + var MONTH = 1; + var DATE = 2; + var HOUR = 3; + var MINUTE = 4; + var SECOND = 5; + var MILLISECOND = 6; + var WEEK = 7; + var WEEKDAY = 8; - // get the list of entries. - var read - if (prefix === null) - read = '.' - else if (isAbsolute(prefix) || isAbsolute(pattern.join('/'))) { - if (!prefix || !isAbsolute(prefix)) - prefix = '/' + prefix - read = prefix - } else - read = prefix + // FORMATTING - var abs = this._makeAbs(read) + addFormatToken('Y', 0, 0, function () { + var y = this.year(); + return y <= 9999 ? '' + y : '+' + y; + }); - //if ignored, skip _processing - if (childrenIgnored(this, read)) - return cb() + addFormatToken(0, ['YY', 2], 0, function () { + return this.year() % 100; + }); - var isGlobStar = remain[0] === minimatch.GLOBSTAR - if (isGlobStar) - this._processGlobStar(prefix, read, abs, remain, index, inGlobStar, cb) - else - this._processReaddir(prefix, read, abs, remain, index, inGlobStar, cb) -} + addFormatToken(0, ['YYYY', 4], 0, 'year'); + addFormatToken(0, ['YYYYY', 5], 0, 'year'); + addFormatToken(0, ['YYYYYY', 6, true], 0, 'year'); -Glob.prototype._processReaddir = function (prefix, read, abs, remain, index, inGlobStar, cb) { - var self = this - this._readdir(abs, inGlobStar, function (er, entries) { - return self._processReaddir2(prefix, read, abs, remain, index, inGlobStar, entries, cb) - }) -} + // ALIASES -Glob.prototype._processReaddir2 = function (prefix, read, abs, remain, index, inGlobStar, entries, cb) { + addUnitAlias('year', 'y'); - // if the abs isn't a dir, then nothing can match! - if (!entries) - return cb() + // PRIORITIES - // It will only match dot entries if it starts with a dot, or if - // dot is set. Stuff like @(.foo|.bar) isn't allowed. - var pn = remain[0] - var negate = !!this.minimatch.negate - var rawGlob = pn._glob - var dotOk = this.dot || rawGlob.charAt(0) === '.' + addUnitPriority('year', 1); - var matchedEntries = [] - for (var i = 0; i < entries.length; i++) { - var e = entries[i] - if (e.charAt(0) !== '.' || dotOk) { - var m - if (negate && !prefix) { - m = !e.match(pn) - } else { - m = e.match(pn) - } - if (m) - matchedEntries.push(e) + // PARSING + + addRegexToken('Y', matchSigned); + addRegexToken('YY', match1to2, match2); + addRegexToken('YYYY', match1to4, match4); + addRegexToken('YYYYY', match1to6, match6); + addRegexToken('YYYYYY', match1to6, match6); + + addParseToken(['YYYYY', 'YYYYYY'], YEAR); + addParseToken('YYYY', function (input, array) { + array[YEAR] = input.length === 2 ? hooks.parseTwoDigitYear(input) : toInt(input); + }); + addParseToken('YY', function (input, array) { + array[YEAR] = hooks.parseTwoDigitYear(input); + }); + addParseToken('Y', function (input, array) { + array[YEAR] = parseInt(input, 10); + }); + + // HELPERS + + function daysInYear(year) { + return isLeapYear(year) ? 366 : 365; } - } - //console.error('prd2', prefix, entries, remain[0]._glob, matchedEntries) + function isLeapYear(year) { + return (year % 4 === 0 && year % 100 !== 0) || year % 400 === 0; + } - var len = matchedEntries.length - // If there are no matched entries, then nothing matches. - if (len === 0) - return cb() + // HOOKS - // if this is the last remaining pattern bit, then no need for - // an additional stat *unless* the user has specified mark or - // stat explicitly. We know they exist, since readdir returned - // them. + hooks.parseTwoDigitYear = function (input) { + return toInt(input) + (toInt(input) > 68 ? 1900 : 2000); + }; - if (remain.length === 1 && !this.mark && !this.stat) { - if (!this.matches[index]) - this.matches[index] = Object.create(null) + // MOMENTS - for (var i = 0; i < len; i ++) { - var e = matchedEntries[i] - if (prefix) { - if (prefix !== '/') - e = prefix + '/' + e - else - e = prefix + e - } + var getSetYear = makeGetSet('FullYear', true); - if (e.charAt(0) === '/' && !this.nomount) { - e = path.join(this.root, e) - } - this._emitMatch(index, e) + function getIsLeapYear () { + return isLeapYear(this.year()); } - // This was the last one, and no stats were needed - return cb() - } - // now test all matched entries as stand-ins for that part - // of the pattern. - remain.shift() - for (var i = 0; i < len; i ++) { - var e = matchedEntries[i] - var newPattern - if (prefix) { - if (prefix !== '/') - e = prefix + '/' + e - else - e = prefix + e + function makeGetSet (unit, keepTime) { + return function (value) { + if (value != null) { + set$1(this, unit, value); + hooks.updateOffset(this, keepTime); + return this; + } else { + return get(this, unit); + } + }; } - this._process([e].concat(remain), index, inGlobStar, cb) - } - cb() -} -Glob.prototype._emitMatch = function (index, e) { - if (this.aborted) - return + function get (mom, unit) { + return mom.isValid() ? + mom._d['get' + (mom._isUTC ? 'UTC' : '') + unit]() : NaN; + } - if (isIgnored(this, e)) - return + function set$1 (mom, unit, value) { + if (mom.isValid() && !isNaN(value)) { + if (unit === 'FullYear' && isLeapYear(mom.year()) && mom.month() === 1 && mom.date() === 29) { + mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](value, mom.month(), daysInMonth(value, mom.month())); + } + else { + mom._d['set' + (mom._isUTC ? 'UTC' : '') + unit](value); + } + } + } - if (this.paused) { - this._emitQueue.push([index, e]) - return - } + // MOMENTS - var abs = isAbsolute(e) ? e : this._makeAbs(e) + function stringGet (units) { + units = normalizeUnits(units); + if (isFunction(this[units])) { + return this[units](); + } + return this; + } - if (this.mark) - e = this._mark(e) - if (this.absolute) - e = abs + function stringSet (units, value) { + if (typeof units === 'object') { + units = normalizeObjectUnits(units); + var prioritized = getPrioritizedUnits(units); + for (var i = 0; i < prioritized.length; i++) { + this[prioritized[i].unit](units[prioritized[i].unit]); + } + } else { + units = normalizeUnits(units); + if (isFunction(this[units])) { + return this[units](value); + } + } + return this; + } - if (this.matches[index][e]) - return + function mod(n, x) { + return ((n % x) + x) % x; + } - if (this.nodir) { - var c = this.cache[abs] - if (c === 'DIR' || Array.isArray(c)) - return - } + var indexOf; - this.matches[index][e] = true + if (Array.prototype.indexOf) { + indexOf = Array.prototype.indexOf; + } else { + indexOf = function (o) { + // I know + var i; + for (i = 0; i < this.length; ++i) { + if (this[i] === o) { + return i; + } + } + return -1; + }; + } - var st = this.statCache[abs] - if (st) - this.emit('stat', e, st) + function daysInMonth(year, month) { + if (isNaN(year) || isNaN(month)) { + return NaN; + } + var modMonth = mod(month, 12); + year += (month - modMonth) / 12; + return modMonth === 1 ? (isLeapYear(year) ? 29 : 28) : (31 - modMonth % 7 % 2); + } - this.emit('match', e) -} + // FORMATTING -Glob.prototype._readdirInGlobStar = function (abs, cb) { - if (this.aborted) - return + addFormatToken('M', ['MM', 2], 'Mo', function () { + return this.month() + 1; + }); - // follow all symlinked directories forever - // just proceed as if this is a non-globstar situation - if (this.follow) - return this._readdir(abs, false, cb) + addFormatToken('MMM', 0, 0, function (format) { + return this.localeData().monthsShort(this, format); + }); - var lstatkey = 'lstat\0' + abs - var self = this - var lstatcb = inflight(lstatkey, lstatcb_) + addFormatToken('MMMM', 0, 0, function (format) { + return this.localeData().months(this, format); + }); - if (lstatcb) - fs.lstat(abs, lstatcb) + // ALIASES - function lstatcb_ (er, lstat) { - if (er && er.code === 'ENOENT') - return cb() + addUnitAlias('month', 'M'); - var isSym = lstat && lstat.isSymbolicLink() - self.symlinks[abs] = isSym + // PRIORITY - // If it's not a symlink or a dir, then it's definitely a regular file. - // don't bother doing a readdir in that case. - if (!isSym && lstat && !lstat.isDirectory()) { - self.cache[abs] = 'FILE' - cb() - } else - self._readdir(abs, false, cb) - } -} + addUnitPriority('month', 8); -Glob.prototype._readdir = function (abs, inGlobStar, cb) { - if (this.aborted) - return + // PARSING - cb = inflight('readdir\0'+abs+'\0'+inGlobStar, cb) - if (!cb) - return + addRegexToken('M', match1to2); + addRegexToken('MM', match1to2, match2); + addRegexToken('MMM', function (isStrict, locale) { + return locale.monthsShortRegex(isStrict); + }); + addRegexToken('MMMM', function (isStrict, locale) { + return locale.monthsRegex(isStrict); + }); - //console.error('RD %j %j', +inGlobStar, abs) - if (inGlobStar && !ownProp(this.symlinks, abs)) - return this._readdirInGlobStar(abs, cb) + addParseToken(['M', 'MM'], function (input, array) { + array[MONTH] = toInt(input) - 1; + }); - if (ownProp(this.cache, abs)) { - var c = this.cache[abs] - if (!c || c === 'FILE') - return cb() + addParseToken(['MMM', 'MMMM'], function (input, array, config, token) { + var month = config._locale.monthsParse(input, token, config._strict); + // if we didn't find a month name, mark the date as invalid. + if (month != null) { + array[MONTH] = month; + } else { + getParsingFlags(config).invalidMonth = input; + } + }); - if (Array.isArray(c)) - return cb(null, c) - } + // LOCALES - var self = this - fs.readdir(abs, readdirCb(this, abs, cb)) -} + var MONTHS_IN_FORMAT = /D[oD]?(\[[^\[\]]*\]|\s)+MMMM?/; + var defaultLocaleMonths = 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_'); + function localeMonths (m, format) { + if (!m) { + return isArray(this._months) ? this._months : + this._months['standalone']; + } + return isArray(this._months) ? this._months[m.month()] : + this._months[(this._months.isFormat || MONTHS_IN_FORMAT).test(format) ? 'format' : 'standalone'][m.month()]; + } -function readdirCb (self, abs, cb) { - return function (er, entries) { - if (er) - self._readdirError(abs, er, cb) - else - self._readdirEntries(abs, entries, cb) - } -} + var defaultLocaleMonthsShort = 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_'); + function localeMonthsShort (m, format) { + if (!m) { + return isArray(this._monthsShort) ? this._monthsShort : + this._monthsShort['standalone']; + } + return isArray(this._monthsShort) ? this._monthsShort[m.month()] : + this._monthsShort[MONTHS_IN_FORMAT.test(format) ? 'format' : 'standalone'][m.month()]; + } -Glob.prototype._readdirEntries = function (abs, entries, cb) { - if (this.aborted) - return + function handleStrictParse(monthName, format, strict) { + var i, ii, mom, llc = monthName.toLocaleLowerCase(); + if (!this._monthsParse) { + // this is not used + this._monthsParse = []; + this._longMonthsParse = []; + this._shortMonthsParse = []; + for (i = 0; i < 12; ++i) { + mom = createUTC([2000, i]); + this._shortMonthsParse[i] = this.monthsShort(mom, '').toLocaleLowerCase(); + this._longMonthsParse[i] = this.months(mom, '').toLocaleLowerCase(); + } + } - // if we haven't asked to stat everything, then just - // assume that everything in there exists, so we can avoid - // having to stat it a second time. - if (!this.mark && !this.stat) { - for (var i = 0; i < entries.length; i ++) { - var e = entries[i] - if (abs === '/') - e = abs + e - else - e = abs + '/' + e - this.cache[e] = true + if (strict) { + if (format === 'MMM') { + ii = indexOf.call(this._shortMonthsParse, llc); + return ii !== -1 ? ii : null; + } else { + ii = indexOf.call(this._longMonthsParse, llc); + return ii !== -1 ? ii : null; + } + } else { + if (format === 'MMM') { + ii = indexOf.call(this._shortMonthsParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._longMonthsParse, llc); + return ii !== -1 ? ii : null; + } else { + ii = indexOf.call(this._longMonthsParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._shortMonthsParse, llc); + return ii !== -1 ? ii : null; + } + } } - } - this.cache[abs] = entries - return cb(null, entries) -} + function localeMonthsParse (monthName, format, strict) { + var i, mom, regex; -Glob.prototype._readdirError = function (f, er, cb) { - if (this.aborted) - return + if (this._monthsParseExact) { + return handleStrictParse.call(this, monthName, format, strict); + } - // handle errors, and cache the information - switch (er.code) { - case 'ENOTSUP': // https://github.com/isaacs/node-glob/issues/205 - case 'ENOTDIR': // totally normal. means it *does* exist. - var abs = this._makeAbs(f) - this.cache[abs] = 'FILE' - if (abs === this.cwdAbs) { - var error = new Error(er.code + ' invalid cwd ' + this.cwd) - error.path = this.cwd - error.code = er.code - this.emit('error', error) - this.abort() - } - break + if (!this._monthsParse) { + this._monthsParse = []; + this._longMonthsParse = []; + this._shortMonthsParse = []; + } - case 'ENOENT': // not terribly unusual - case 'ELOOP': - case 'ENAMETOOLONG': - case 'UNKNOWN': - this.cache[this._makeAbs(f)] = false - break + // TODO: add sorting + // Sorting makes sure if one month (or abbr) is a prefix of another + // see sorting in computeMonthsParse + for (i = 0; i < 12; i++) { + // make the regex if we don't have it already + mom = createUTC([2000, i]); + if (strict && !this._longMonthsParse[i]) { + this._longMonthsParse[i] = new RegExp('^' + this.months(mom, '').replace('.', '') + '$', 'i'); + this._shortMonthsParse[i] = new RegExp('^' + this.monthsShort(mom, '').replace('.', '') + '$', 'i'); + } + if (!strict && !this._monthsParse[i]) { + regex = '^' + this.months(mom, '') + '|^' + this.monthsShort(mom, ''); + this._monthsParse[i] = new RegExp(regex.replace('.', ''), 'i'); + } + // test the regex + if (strict && format === 'MMMM' && this._longMonthsParse[i].test(monthName)) { + return i; + } else if (strict && format === 'MMM' && this._shortMonthsParse[i].test(monthName)) { + return i; + } else if (!strict && this._monthsParse[i].test(monthName)) { + return i; + } + } + } - default: // some unusual error. Treat as failure. - this.cache[this._makeAbs(f)] = false - if (this.strict) { - this.emit('error', er) - // If the error is handled, then we abort - // if not, we threw out of here - this.abort() - } - if (!this.silent) - console.error('glob error', er) - break - } + // MOMENTS - return cb() -} + function setMonth (mom, value) { + var dayOfMonth; -Glob.prototype._processGlobStar = function (prefix, read, abs, remain, index, inGlobStar, cb) { - var self = this - this._readdir(abs, inGlobStar, function (er, entries) { - self._processGlobStar2(prefix, read, abs, remain, index, inGlobStar, entries, cb) - }) -} + if (!mom.isValid()) { + // No op + return mom; + } + if (typeof value === 'string') { + if (/^\d+$/.test(value)) { + value = toInt(value); + } else { + value = mom.localeData().monthsParse(value); + // TODO: Another silent failure? + if (!isNumber(value)) { + return mom; + } + } + } -Glob.prototype._processGlobStar2 = function (prefix, read, abs, remain, index, inGlobStar, entries, cb) { - //console.error('pgs2', prefix, remain[0], entries) + dayOfMonth = Math.min(mom.date(), daysInMonth(mom.year(), value)); + mom._d['set' + (mom._isUTC ? 'UTC' : '') + 'Month'](value, dayOfMonth); + return mom; + } - // no entries means not a dir, so it can never have matches - // foo.txt/** doesn't match foo.txt - if (!entries) - return cb() + function getSetMonth (value) { + if (value != null) { + setMonth(this, value); + hooks.updateOffset(this, true); + return this; + } else { + return get(this, 'Month'); + } + } - // test without the globstar, and with every child both below - // and replacing the globstar. - var remainWithoutGlobStar = remain.slice(1) - var gspref = prefix ? [ prefix ] : [] - var noGlobStar = gspref.concat(remainWithoutGlobStar) + function getDaysInMonth () { + return daysInMonth(this.year(), this.month()); + } - // the noGlobStar pattern exits the inGlobStar state - this._process(noGlobStar, index, false, cb) + var defaultMonthsShortRegex = matchWord; + function monthsShortRegex (isStrict) { + if (this._monthsParseExact) { + if (!hasOwnProp(this, '_monthsRegex')) { + computeMonthsParse.call(this); + } + if (isStrict) { + return this._monthsShortStrictRegex; + } else { + return this._monthsShortRegex; + } + } else { + if (!hasOwnProp(this, '_monthsShortRegex')) { + this._monthsShortRegex = defaultMonthsShortRegex; + } + return this._monthsShortStrictRegex && isStrict ? + this._monthsShortStrictRegex : this._monthsShortRegex; + } + } - var isSym = this.symlinks[abs] - var len = entries.length + var defaultMonthsRegex = matchWord; + function monthsRegex (isStrict) { + if (this._monthsParseExact) { + if (!hasOwnProp(this, '_monthsRegex')) { + computeMonthsParse.call(this); + } + if (isStrict) { + return this._monthsStrictRegex; + } else { + return this._monthsRegex; + } + } else { + if (!hasOwnProp(this, '_monthsRegex')) { + this._monthsRegex = defaultMonthsRegex; + } + return this._monthsStrictRegex && isStrict ? + this._monthsStrictRegex : this._monthsRegex; + } + } + + function computeMonthsParse () { + function cmpLenRev(a, b) { + return b.length - a.length; + } + + var shortPieces = [], longPieces = [], mixedPieces = [], + i, mom; + for (i = 0; i < 12; i++) { + // make the regex if we don't have it already + mom = createUTC([2000, i]); + shortPieces.push(this.monthsShort(mom, '')); + longPieces.push(this.months(mom, '')); + mixedPieces.push(this.months(mom, '')); + mixedPieces.push(this.monthsShort(mom, '')); + } + // Sorting makes sure if one month (or abbr) is a prefix of another it + // will match the longer piece. + shortPieces.sort(cmpLenRev); + longPieces.sort(cmpLenRev); + mixedPieces.sort(cmpLenRev); + for (i = 0; i < 12; i++) { + shortPieces[i] = regexEscape(shortPieces[i]); + longPieces[i] = regexEscape(longPieces[i]); + } + for (i = 0; i < 24; i++) { + mixedPieces[i] = regexEscape(mixedPieces[i]); + } + + this._monthsRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i'); + this._monthsShortRegex = this._monthsRegex; + this._monthsStrictRegex = new RegExp('^(' + longPieces.join('|') + ')', 'i'); + this._monthsShortStrictRegex = new RegExp('^(' + shortPieces.join('|') + ')', 'i'); + } + + function createDate (y, m, d, h, M, s, ms) { + // can't just apply() to create a date: + // https://stackoverflow.com/q/181348 + var date; + // the date constructor remaps years 0-99 to 1900-1999 + if (y < 100 && y >= 0) { + // preserve leap years using a full 400 year cycle, then reset + date = new Date(y + 400, m, d, h, M, s, ms); + if (isFinite(date.getFullYear())) { + date.setFullYear(y); + } + } else { + date = new Date(y, m, d, h, M, s, ms); + } - // If it's a symlink, and we're in a globstar, then stop - if (isSym && inGlobStar) - return cb() + return date; + } - for (var i = 0; i < len; i++) { - var e = entries[i] - if (e.charAt(0) === '.' && !this.dot) - continue + function createUTCDate (y) { + var date; + // the Date.UTC function remaps years 0-99 to 1900-1999 + if (y < 100 && y >= 0) { + var args = Array.prototype.slice.call(arguments); + // preserve leap years using a full 400 year cycle, then reset + args[0] = y + 400; + date = new Date(Date.UTC.apply(null, args)); + if (isFinite(date.getUTCFullYear())) { + date.setUTCFullYear(y); + } + } else { + date = new Date(Date.UTC.apply(null, arguments)); + } - // these two cases enter the inGlobStar state - var instead = gspref.concat(entries[i], remainWithoutGlobStar) - this._process(instead, index, true, cb) + return date; + } - var below = gspref.concat(entries[i], remain) - this._process(below, index, true, cb) - } + // start-of-first-week - start-of-year + function firstWeekOffset(year, dow, doy) { + var // first-week day -- which january is always in the first week (4 for iso, 1 for other) + fwd = 7 + dow - doy, + // first-week day local weekday -- which local weekday is fwd + fwdlw = (7 + createUTCDate(year, 0, fwd).getUTCDay() - dow) % 7; - cb() -} + return -fwdlw + fwd - 1; + } -Glob.prototype._processSimple = function (prefix, index, cb) { - // XXX review this. Shouldn't it be doing the mounting etc - // before doing stat? kinda weird? - var self = this - this._stat(prefix, function (er, exists) { - self._processSimple2(prefix, index, er, exists, cb) - }) -} -Glob.prototype._processSimple2 = function (prefix, index, er, exists, cb) { + // https://en.wikipedia.org/wiki/ISO_week_date#Calculating_a_date_given_the_year.2C_week_number_and_weekday + function dayOfYearFromWeeks(year, week, weekday, dow, doy) { + var localWeekday = (7 + weekday - dow) % 7, + weekOffset = firstWeekOffset(year, dow, doy), + dayOfYear = 1 + 7 * (week - 1) + localWeekday + weekOffset, + resYear, resDayOfYear; - //console.error('ps2', prefix, exists) + if (dayOfYear <= 0) { + resYear = year - 1; + resDayOfYear = daysInYear(resYear) + dayOfYear; + } else if (dayOfYear > daysInYear(year)) { + resYear = year + 1; + resDayOfYear = dayOfYear - daysInYear(year); + } else { + resYear = year; + resDayOfYear = dayOfYear; + } - if (!this.matches[index]) - this.matches[index] = Object.create(null) + return { + year: resYear, + dayOfYear: resDayOfYear + }; + } - // If it doesn't exist, then just mark the lack of results - if (!exists) - return cb() + function weekOfYear(mom, dow, doy) { + var weekOffset = firstWeekOffset(mom.year(), dow, doy), + week = Math.floor((mom.dayOfYear() - weekOffset - 1) / 7) + 1, + resWeek, resYear; - if (prefix && isAbsolute(prefix) && !this.nomount) { - var trail = /[\/\\]$/.test(prefix) - if (prefix.charAt(0) === '/') { - prefix = path.join(this.root, prefix) - } else { - prefix = path.resolve(this.root, prefix) - if (trail) - prefix += '/' + if (week < 1) { + resYear = mom.year() - 1; + resWeek = week + weeksInYear(resYear, dow, doy); + } else if (week > weeksInYear(mom.year(), dow, doy)) { + resWeek = week - weeksInYear(mom.year(), dow, doy); + resYear = mom.year() + 1; + } else { + resYear = mom.year(); + resWeek = week; + } + + return { + week: resWeek, + year: resYear + }; } - } - if (process.platform === 'win32') - prefix = prefix.replace(/\\/g, '/') + function weeksInYear(year, dow, doy) { + var weekOffset = firstWeekOffset(year, dow, doy), + weekOffsetNext = firstWeekOffset(year + 1, dow, doy); + return (daysInYear(year) - weekOffset + weekOffsetNext) / 7; + } - // Mark this as a match - this._emitMatch(index, prefix) - cb() -} + // FORMATTING -// Returns either 'DIR', 'FILE', or false -Glob.prototype._stat = function (f, cb) { - var abs = this._makeAbs(f) - var needDir = f.slice(-1) === '/' + addFormatToken('w', ['ww', 2], 'wo', 'week'); + addFormatToken('W', ['WW', 2], 'Wo', 'isoWeek'); - if (f.length > this.maxLength) - return cb() + // ALIASES - if (!this.stat && ownProp(this.cache, abs)) { - var c = this.cache[abs] + addUnitAlias('week', 'w'); + addUnitAlias('isoWeek', 'W'); - if (Array.isArray(c)) - c = 'DIR' + // PRIORITIES - // It exists, but maybe not how we need it - if (!needDir || c === 'DIR') - return cb(null, c) + addUnitPriority('week', 5); + addUnitPriority('isoWeek', 5); - if (needDir && c === 'FILE') - return cb() + // PARSING - // otherwise we have to stat, because maybe c=true - // if we know it exists, but not what it is. - } + addRegexToken('w', match1to2); + addRegexToken('ww', match1to2, match2); + addRegexToken('W', match1to2); + addRegexToken('WW', match1to2, match2); - var exists - var stat = this.statCache[abs] - if (stat !== undefined) { - if (stat === false) - return cb(null, stat) - else { - var type = stat.isDirectory() ? 'DIR' : 'FILE' - if (needDir && type === 'FILE') - return cb() - else - return cb(null, type, stat) + addWeekParseToken(['w', 'ww', 'W', 'WW'], function (input, week, config, token) { + week[token.substr(0, 1)] = toInt(input); + }); + + // HELPERS + + // LOCALES + + function localeWeek (mom) { + return weekOfYear(mom, this._week.dow, this._week.doy).week; } - } - var self = this - var statcb = inflight('stat\0' + abs, lstatcb_) - if (statcb) - fs.lstat(abs, statcb) + var defaultLocaleWeek = { + dow : 0, // Sunday is the first day of the week. + doy : 6 // The week that contains Jan 6th is the first week of the year. + }; - function lstatcb_ (er, lstat) { - if (lstat && lstat.isSymbolicLink()) { - // If it's a symlink, then treat it as the target, unless - // the target does not exist, then treat it as a file. - return fs.stat(abs, function (er, stat) { - if (er) - self._stat2(f, abs, null, lstat, cb) - else - self._stat2(f, abs, er, stat, cb) - }) - } else { - self._stat2(f, abs, er, lstat, cb) + function localeFirstDayOfWeek () { + return this._week.dow; } - } -} -Glob.prototype._stat2 = function (f, abs, er, stat, cb) { - if (er && (er.code === 'ENOENT' || er.code === 'ENOTDIR')) { - this.statCache[abs] = false - return cb() - } + function localeFirstDayOfYear () { + return this._week.doy; + } - var needDir = f.slice(-1) === '/' - this.statCache[abs] = stat + // MOMENTS - if (abs.slice(-1) === '/' && stat && !stat.isDirectory()) - return cb(null, false, stat) + function getSetWeek (input) { + var week = this.localeData().week(this); + return input == null ? week : this.add((input - week) * 7, 'd'); + } - var c = true - if (stat) - c = stat.isDirectory() ? 'DIR' : 'FILE' - this.cache[abs] = this.cache[abs] || c + function getSetISOWeek (input) { + var week = weekOfYear(this, 1, 4).week; + return input == null ? week : this.add((input - week) * 7, 'd'); + } - if (needDir && c === 'FILE') - return cb() + // FORMATTING - return cb(null, c, stat) -} + addFormatToken('d', 0, 'do', 'day'); + addFormatToken('dd', 0, 0, function (format) { + return this.localeData().weekdaysMin(this, format); + }); -/***/ }), -/* 38 */ -/***/ (function(module, exports, __webpack_require__) { + addFormatToken('ddd', 0, 0, function (format) { + return this.localeData().weekdaysShort(this, format); + }); -module.exports = realpath -realpath.realpath = realpath -realpath.sync = realpathSync -realpath.realpathSync = realpathSync -realpath.monkeypatch = monkeypatch -realpath.unmonkeypatch = unmonkeypatch + addFormatToken('dddd', 0, 0, function (format) { + return this.localeData().weekdays(this, format); + }); -var fs = __webpack_require__(23) -var origRealpath = fs.realpath -var origRealpathSync = fs.realpathSync + addFormatToken('e', 0, 0, 'weekday'); + addFormatToken('E', 0, 0, 'isoWeekday'); -var version = process.version -var ok = /^v[0-5]\./.test(version) -var old = __webpack_require__(39) + // ALIASES -function newError (er) { - return er && er.syscall === 'realpath' && ( - er.code === 'ELOOP' || - er.code === 'ENOMEM' || - er.code === 'ENAMETOOLONG' - ) -} + addUnitAlias('day', 'd'); + addUnitAlias('weekday', 'e'); + addUnitAlias('isoWeekday', 'E'); -function realpath (p, cache, cb) { - if (ok) { - return origRealpath(p, cache, cb) - } + // PRIORITY + addUnitPriority('day', 11); + addUnitPriority('weekday', 11); + addUnitPriority('isoWeekday', 11); - if (typeof cache === 'function') { - cb = cache - cache = null - } - origRealpath(p, cache, function (er, result) { - if (newError(er)) { - old.realpath(p, cache, cb) - } else { - cb(er, result) - } - }) -} + // PARSING -function realpathSync (p, cache) { - if (ok) { - return origRealpathSync(p, cache) - } + addRegexToken('d', match1to2); + addRegexToken('e', match1to2); + addRegexToken('E', match1to2); + addRegexToken('dd', function (isStrict, locale) { + return locale.weekdaysMinRegex(isStrict); + }); + addRegexToken('ddd', function (isStrict, locale) { + return locale.weekdaysShortRegex(isStrict); + }); + addRegexToken('dddd', function (isStrict, locale) { + return locale.weekdaysRegex(isStrict); + }); - try { - return origRealpathSync(p, cache) - } catch (er) { - if (newError(er)) { - return old.realpathSync(p, cache) - } else { - throw er - } - } -} + addWeekParseToken(['dd', 'ddd', 'dddd'], function (input, week, config, token) { + var weekday = config._locale.weekdaysParse(input, token, config._strict); + // if we didn't get a weekday name, mark the date as invalid + if (weekday != null) { + week.d = weekday; + } else { + getParsingFlags(config).invalidWeekday = input; + } + }); -function monkeypatch () { - fs.realpath = realpath - fs.realpathSync = realpathSync -} + addWeekParseToken(['d', 'e', 'E'], function (input, week, config, token) { + week[token] = toInt(input); + }); -function unmonkeypatch () { - fs.realpath = origRealpath - fs.realpathSync = origRealpathSync -} + // HELPERS + function parseWeekday(input, locale) { + if (typeof input !== 'string') { + return input; + } -/***/ }), -/* 39 */ -/***/ (function(module, exports, __webpack_require__) { + if (!isNaN(input)) { + return parseInt(input, 10); + } -// Copyright Joyent, Inc. and other Node contributors. -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the -// "Software"), to deal in the Software without restriction, including -// without limitation the rights to use, copy, modify, merge, publish, -// distribute, sublicense, and/or sell copies of the Software, and to permit -// persons to whom the Software is furnished to do so, subject to the -// following conditions: -// -// The above copyright notice and this permission notice shall be included -// in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS -// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF -// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN -// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, -// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR -// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE -// USE OR OTHER DEALINGS IN THE SOFTWARE. - -var pathModule = __webpack_require__(16); -var isWindows = process.platform === 'win32'; -var fs = __webpack_require__(23); + input = locale.weekdaysParse(input); + if (typeof input === 'number') { + return input; + } -// JavaScript implementation of realpath, ported from node pre-v6 + return null; + } -var DEBUG = process.env.NODE_DEBUG && /fs/.test(process.env.NODE_DEBUG); + function parseIsoWeekday(input, locale) { + if (typeof input === 'string') { + return locale.weekdaysParse(input) % 7 || 7; + } + return isNaN(input) ? null : input; + } -function rethrow() { - // Only enable in debug mode. A backtrace uses ~1000 bytes of heap space and - // is fairly slow to generate. - var callback; - if (DEBUG) { - var backtrace = new Error; - callback = debugCallback; - } else - callback = missingCallback; + // LOCALES + function shiftWeekdays (ws, n) { + return ws.slice(n, 7).concat(ws.slice(0, n)); + } - return callback; + var defaultLocaleWeekdays = 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_'); + function localeWeekdays (m, format) { + var weekdays = isArray(this._weekdays) ? this._weekdays : + this._weekdays[(m && m !== true && this._weekdays.isFormat.test(format)) ? 'format' : 'standalone']; + return (m === true) ? shiftWeekdays(weekdays, this._week.dow) + : (m) ? weekdays[m.day()] : weekdays; + } - function debugCallback(err) { - if (err) { - backtrace.message = err.message; - err = backtrace; - missingCallback(err); + var defaultLocaleWeekdaysShort = 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_'); + function localeWeekdaysShort (m) { + return (m === true) ? shiftWeekdays(this._weekdaysShort, this._week.dow) + : (m) ? this._weekdaysShort[m.day()] : this._weekdaysShort; } - } - function missingCallback(err) { - if (err) { - if (process.throwDeprecation) - throw err; // Forgot a callback but don't know where? Use NODE_DEBUG=fs - else if (!process.noDeprecation) { - var msg = 'fs: missing callback ' + (err.stack || err.message); - if (process.traceDeprecation) - console.trace(msg); - else - console.error(msg); - } + var defaultLocaleWeekdaysMin = 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_'); + function localeWeekdaysMin (m) { + return (m === true) ? shiftWeekdays(this._weekdaysMin, this._week.dow) + : (m) ? this._weekdaysMin[m.day()] : this._weekdaysMin; } - } -} -function maybeCallback(cb) { - return typeof cb === 'function' ? cb : rethrow(); -} + function handleStrictParse$1(weekdayName, format, strict) { + var i, ii, mom, llc = weekdayName.toLocaleLowerCase(); + if (!this._weekdaysParse) { + this._weekdaysParse = []; + this._shortWeekdaysParse = []; + this._minWeekdaysParse = []; -var normalize = pathModule.normalize; + for (i = 0; i < 7; ++i) { + mom = createUTC([2000, 1]).day(i); + this._minWeekdaysParse[i] = this.weekdaysMin(mom, '').toLocaleLowerCase(); + this._shortWeekdaysParse[i] = this.weekdaysShort(mom, '').toLocaleLowerCase(); + this._weekdaysParse[i] = this.weekdays(mom, '').toLocaleLowerCase(); + } + } -// Regexp that finds the next partion of a (partial) path -// result is [base_with_slash, base], e.g. ['somedir/', 'somedir'] -if (isWindows) { - var nextPartRe = /(.*?)(?:[\/\\]+|$)/g; -} else { - var nextPartRe = /(.*?)(?:[\/]+|$)/g; -} + if (strict) { + if (format === 'dddd') { + ii = indexOf.call(this._weekdaysParse, llc); + return ii !== -1 ? ii : null; + } else if (format === 'ddd') { + ii = indexOf.call(this._shortWeekdaysParse, llc); + return ii !== -1 ? ii : null; + } else { + ii = indexOf.call(this._minWeekdaysParse, llc); + return ii !== -1 ? ii : null; + } + } else { + if (format === 'dddd') { + ii = indexOf.call(this._weekdaysParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._shortWeekdaysParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._minWeekdaysParse, llc); + return ii !== -1 ? ii : null; + } else if (format === 'ddd') { + ii = indexOf.call(this._shortWeekdaysParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._weekdaysParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._minWeekdaysParse, llc); + return ii !== -1 ? ii : null; + } else { + ii = indexOf.call(this._minWeekdaysParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._weekdaysParse, llc); + if (ii !== -1) { + return ii; + } + ii = indexOf.call(this._shortWeekdaysParse, llc); + return ii !== -1 ? ii : null; + } + } + } -// Regex to find the device root, including trailing slash. E.g. 'c:\\'. -if (isWindows) { - var splitRootRe = /^(?:[a-zA-Z]:|[\\\/]{2}[^\\\/]+[\\\/][^\\\/]+)?[\\\/]*/; -} else { - var splitRootRe = /^[\/]*/; -} + function localeWeekdaysParse (weekdayName, format, strict) { + var i, mom, regex; -exports.realpathSync = function realpathSync(p, cache) { - // make p is absolute - p = pathModule.resolve(p); + if (this._weekdaysParseExact) { + return handleStrictParse$1.call(this, weekdayName, format, strict); + } - if (cache && Object.prototype.hasOwnProperty.call(cache, p)) { - return cache[p]; - } + if (!this._weekdaysParse) { + this._weekdaysParse = []; + this._minWeekdaysParse = []; + this._shortWeekdaysParse = []; + this._fullWeekdaysParse = []; + } - var original = p, - seenLinks = {}, - knownHard = {}; + for (i = 0; i < 7; i++) { + // make the regex if we don't have it already - // current character position in p - var pos; - // the partial path so far, including a trailing slash if any - var current; - // the partial path without a trailing slash (except when pointing at a root) - var base; - // the partial path scanned in the previous round, with slash - var previous; + mom = createUTC([2000, 1]).day(i); + if (strict && !this._fullWeekdaysParse[i]) { + this._fullWeekdaysParse[i] = new RegExp('^' + this.weekdays(mom, '').replace('.', '\\.?') + '$', 'i'); + this._shortWeekdaysParse[i] = new RegExp('^' + this.weekdaysShort(mom, '').replace('.', '\\.?') + '$', 'i'); + this._minWeekdaysParse[i] = new RegExp('^' + this.weekdaysMin(mom, '').replace('.', '\\.?') + '$', 'i'); + } + if (!this._weekdaysParse[i]) { + regex = '^' + this.weekdays(mom, '') + '|^' + this.weekdaysShort(mom, '') + '|^' + this.weekdaysMin(mom, ''); + this._weekdaysParse[i] = new RegExp(regex.replace('.', ''), 'i'); + } + // test the regex + if (strict && format === 'dddd' && this._fullWeekdaysParse[i].test(weekdayName)) { + return i; + } else if (strict && format === 'ddd' && this._shortWeekdaysParse[i].test(weekdayName)) { + return i; + } else if (strict && format === 'dd' && this._minWeekdaysParse[i].test(weekdayName)) { + return i; + } else if (!strict && this._weekdaysParse[i].test(weekdayName)) { + return i; + } + } + } - start(); + // MOMENTS - function start() { - // Skip over roots - var m = splitRootRe.exec(p); - pos = m[0].length; - current = m[0]; - base = m[0]; - previous = ''; + function getSetDayOfWeek (input) { + if (!this.isValid()) { + return input != null ? this : NaN; + } + var day = this._isUTC ? this._d.getUTCDay() : this._d.getDay(); + if (input != null) { + input = parseWeekday(input, this.localeData()); + return this.add(input - day, 'd'); + } else { + return day; + } + } - // On windows, check that the root exists. On unix there is no need. - if (isWindows && !knownHard[base]) { - fs.lstatSync(base); - knownHard[base] = true; + function getSetLocaleDayOfWeek (input) { + if (!this.isValid()) { + return input != null ? this : NaN; + } + var weekday = (this.day() + 7 - this.localeData()._week.dow) % 7; + return input == null ? weekday : this.add(input - weekday, 'd'); } - } - // walk down the path, swapping out linked pathparts for their real - // values - // NB: p.length changes. - while (pos < p.length) { - // find the next part - nextPartRe.lastIndex = pos; - var result = nextPartRe.exec(p); - previous = current; - current += result[0]; - base = previous + result[1]; - pos = nextPartRe.lastIndex; + function getSetISODayOfWeek (input) { + if (!this.isValid()) { + return input != null ? this : NaN; + } - // continue if not a symlink - if (knownHard[base] || (cache && cache[base] === base)) { - continue; + // behaves the same as moment#day except + // as a getter, returns 7 instead of 0 (1-7 range instead of 0-6) + // as a setter, sunday should belong to the previous week. + + if (input != null) { + var weekday = parseIsoWeekday(input, this.localeData()); + return this.day(this.day() % 7 ? weekday : weekday - 7); + } else { + return this.day() || 7; + } } - var resolvedLink; - if (cache && Object.prototype.hasOwnProperty.call(cache, base)) { - // some known symbolic link. no need to stat again. - resolvedLink = cache[base]; - } else { - var stat = fs.lstatSync(base); - if (!stat.isSymbolicLink()) { - knownHard[base] = true; - if (cache) cache[base] = base; - continue; - } + var defaultWeekdaysRegex = matchWord; + function weekdaysRegex (isStrict) { + if (this._weekdaysParseExact) { + if (!hasOwnProp(this, '_weekdaysRegex')) { + computeWeekdaysParse.call(this); + } + if (isStrict) { + return this._weekdaysStrictRegex; + } else { + return this._weekdaysRegex; + } + } else { + if (!hasOwnProp(this, '_weekdaysRegex')) { + this._weekdaysRegex = defaultWeekdaysRegex; + } + return this._weekdaysStrictRegex && isStrict ? + this._weekdaysStrictRegex : this._weekdaysRegex; + } + } - // read the link if it wasn't read before - // dev/ino always return 0 on windows, so skip the check. - var linkTarget = null; - if (!isWindows) { - var id = stat.dev.toString(32) + ':' + stat.ino.toString(32); - if (seenLinks.hasOwnProperty(id)) { - linkTarget = seenLinks[id]; + var defaultWeekdaysShortRegex = matchWord; + function weekdaysShortRegex (isStrict) { + if (this._weekdaysParseExact) { + if (!hasOwnProp(this, '_weekdaysRegex')) { + computeWeekdaysParse.call(this); + } + if (isStrict) { + return this._weekdaysShortStrictRegex; + } else { + return this._weekdaysShortRegex; + } + } else { + if (!hasOwnProp(this, '_weekdaysShortRegex')) { + this._weekdaysShortRegex = defaultWeekdaysShortRegex; + } + return this._weekdaysShortStrictRegex && isStrict ? + this._weekdaysShortStrictRegex : this._weekdaysShortRegex; } - } - if (linkTarget === null) { - fs.statSync(base); - linkTarget = fs.readlinkSync(base); - } - resolvedLink = pathModule.resolve(previous, linkTarget); - // track this, if given a cache. - if (cache) cache[base] = resolvedLink; - if (!isWindows) seenLinks[id] = linkTarget; } - // resolve the link, then start over - p = pathModule.resolve(resolvedLink, p.slice(pos)); - start(); - } + var defaultWeekdaysMinRegex = matchWord; + function weekdaysMinRegex (isStrict) { + if (this._weekdaysParseExact) { + if (!hasOwnProp(this, '_weekdaysRegex')) { + computeWeekdaysParse.call(this); + } + if (isStrict) { + return this._weekdaysMinStrictRegex; + } else { + return this._weekdaysMinRegex; + } + } else { + if (!hasOwnProp(this, '_weekdaysMinRegex')) { + this._weekdaysMinRegex = defaultWeekdaysMinRegex; + } + return this._weekdaysMinStrictRegex && isStrict ? + this._weekdaysMinStrictRegex : this._weekdaysMinRegex; + } + } - if (cache) cache[original] = p; - return p; -}; + function computeWeekdaysParse () { + function cmpLenRev(a, b) { + return b.length - a.length; + } + var minPieces = [], shortPieces = [], longPieces = [], mixedPieces = [], + i, mom, minp, shortp, longp; + for (i = 0; i < 7; i++) { + // make the regex if we don't have it already + mom = createUTC([2000, 1]).day(i); + minp = this.weekdaysMin(mom, ''); + shortp = this.weekdaysShort(mom, ''); + longp = this.weekdays(mom, ''); + minPieces.push(minp); + shortPieces.push(shortp); + longPieces.push(longp); + mixedPieces.push(minp); + mixedPieces.push(shortp); + mixedPieces.push(longp); + } + // Sorting makes sure if one weekday (or abbr) is a prefix of another it + // will match the longer piece. + minPieces.sort(cmpLenRev); + shortPieces.sort(cmpLenRev); + longPieces.sort(cmpLenRev); + mixedPieces.sort(cmpLenRev); + for (i = 0; i < 7; i++) { + shortPieces[i] = regexEscape(shortPieces[i]); + longPieces[i] = regexEscape(longPieces[i]); + mixedPieces[i] = regexEscape(mixedPieces[i]); + } -exports.realpath = function realpath(p, cache, cb) { - if (typeof cb !== 'function') { - cb = maybeCallback(cache); - cache = null; - } + this._weekdaysRegex = new RegExp('^(' + mixedPieces.join('|') + ')', 'i'); + this._weekdaysShortRegex = this._weekdaysRegex; + this._weekdaysMinRegex = this._weekdaysRegex; - // make p is absolute - p = pathModule.resolve(p); + this._weekdaysStrictRegex = new RegExp('^(' + longPieces.join('|') + ')', 'i'); + this._weekdaysShortStrictRegex = new RegExp('^(' + shortPieces.join('|') + ')', 'i'); + this._weekdaysMinStrictRegex = new RegExp('^(' + minPieces.join('|') + ')', 'i'); + } - if (cache && Object.prototype.hasOwnProperty.call(cache, p)) { - return process.nextTick(cb.bind(null, null, cache[p])); - } + // FORMATTING - var original = p, - seenLinks = {}, - knownHard = {}; + function hFormat() { + return this.hours() % 12 || 12; + } - // current character position in p - var pos; - // the partial path so far, including a trailing slash if any - var current; - // the partial path without a trailing slash (except when pointing at a root) - var base; - // the partial path scanned in the previous round, with slash - var previous; + function kFormat() { + return this.hours() || 24; + } - start(); + addFormatToken('H', ['HH', 2], 0, 'hour'); + addFormatToken('h', ['hh', 2], 0, hFormat); + addFormatToken('k', ['kk', 2], 0, kFormat); - function start() { - // Skip over roots - var m = splitRootRe.exec(p); - pos = m[0].length; - current = m[0]; - base = m[0]; - previous = ''; + addFormatToken('hmm', 0, 0, function () { + return '' + hFormat.apply(this) + zeroFill(this.minutes(), 2); + }); - // On windows, check that the root exists. On unix there is no need. - if (isWindows && !knownHard[base]) { - fs.lstat(base, function(err) { - if (err) return cb(err); - knownHard[base] = true; - LOOP(); - }); - } else { - process.nextTick(LOOP); - } - } + addFormatToken('hmmss', 0, 0, function () { + return '' + hFormat.apply(this) + zeroFill(this.minutes(), 2) + + zeroFill(this.seconds(), 2); + }); - // walk down the path, swapping out linked pathparts for their real - // values - function LOOP() { - // stop if scanned past end of path - if (pos >= p.length) { - if (cache) cache[original] = p; - return cb(null, p); - } + addFormatToken('Hmm', 0, 0, function () { + return '' + this.hours() + zeroFill(this.minutes(), 2); + }); - // find the next part - nextPartRe.lastIndex = pos; - var result = nextPartRe.exec(p); - previous = current; - current += result[0]; - base = previous + result[1]; - pos = nextPartRe.lastIndex; + addFormatToken('Hmmss', 0, 0, function () { + return '' + this.hours() + zeroFill(this.minutes(), 2) + + zeroFill(this.seconds(), 2); + }); - // continue if not a symlink - if (knownHard[base] || (cache && cache[base] === base)) { - return process.nextTick(LOOP); + function meridiem (token, lowercase) { + addFormatToken(token, 0, 0, function () { + return this.localeData().meridiem(this.hours(), this.minutes(), lowercase); + }); } - if (cache && Object.prototype.hasOwnProperty.call(cache, base)) { - // known symbolic link. no need to stat again. - return gotResolvedLink(cache[base]); + meridiem('a', true); + meridiem('A', false); + + // ALIASES + + addUnitAlias('hour', 'h'); + + // PRIORITY + addUnitPriority('hour', 13); + + // PARSING + + function matchMeridiem (isStrict, locale) { + return locale._meridiemParse; } - return fs.lstat(base, gotStat); - } + addRegexToken('a', matchMeridiem); + addRegexToken('A', matchMeridiem); + addRegexToken('H', match1to2); + addRegexToken('h', match1to2); + addRegexToken('k', match1to2); + addRegexToken('HH', match1to2, match2); + addRegexToken('hh', match1to2, match2); + addRegexToken('kk', match1to2, match2); - function gotStat(err, stat) { - if (err) return cb(err); + addRegexToken('hmm', match3to4); + addRegexToken('hmmss', match5to6); + addRegexToken('Hmm', match3to4); + addRegexToken('Hmmss', match5to6); - // if not a symlink, skip to the next path part - if (!stat.isSymbolicLink()) { - knownHard[base] = true; - if (cache) cache[base] = base; - return process.nextTick(LOOP); + addParseToken(['H', 'HH'], HOUR); + addParseToken(['k', 'kk'], function (input, array, config) { + var kInput = toInt(input); + array[HOUR] = kInput === 24 ? 0 : kInput; + }); + addParseToken(['a', 'A'], function (input, array, config) { + config._isPm = config._locale.isPM(input); + config._meridiem = input; + }); + addParseToken(['h', 'hh'], function (input, array, config) { + array[HOUR] = toInt(input); + getParsingFlags(config).bigHour = true; + }); + addParseToken('hmm', function (input, array, config) { + var pos = input.length - 2; + array[HOUR] = toInt(input.substr(0, pos)); + array[MINUTE] = toInt(input.substr(pos)); + getParsingFlags(config).bigHour = true; + }); + addParseToken('hmmss', function (input, array, config) { + var pos1 = input.length - 4; + var pos2 = input.length - 2; + array[HOUR] = toInt(input.substr(0, pos1)); + array[MINUTE] = toInt(input.substr(pos1, 2)); + array[SECOND] = toInt(input.substr(pos2)); + getParsingFlags(config).bigHour = true; + }); + addParseToken('Hmm', function (input, array, config) { + var pos = input.length - 2; + array[HOUR] = toInt(input.substr(0, pos)); + array[MINUTE] = toInt(input.substr(pos)); + }); + addParseToken('Hmmss', function (input, array, config) { + var pos1 = input.length - 4; + var pos2 = input.length - 2; + array[HOUR] = toInt(input.substr(0, pos1)); + array[MINUTE] = toInt(input.substr(pos1, 2)); + array[SECOND] = toInt(input.substr(pos2)); + }); + + // LOCALES + + function localeIsPM (input) { + // IE8 Quirks Mode & IE7 Standards Mode do not allow accessing strings like arrays + // Using charAt should be more compatible. + return ((input + '').toLowerCase().charAt(0) === 'p'); } - // stat & read the link if not read before - // call gotTarget as soon as the link target is known - // dev/ino always return 0 on windows, so skip the check. - if (!isWindows) { - var id = stat.dev.toString(32) + ':' + stat.ino.toString(32); - if (seenLinks.hasOwnProperty(id)) { - return gotTarget(null, seenLinks[id], base); - } + var defaultLocaleMeridiemParse = /[ap]\.?m?\.?/i; + function localeMeridiem (hours, minutes, isLower) { + if (hours > 11) { + return isLower ? 'pm' : 'PM'; + } else { + return isLower ? 'am' : 'AM'; + } } - fs.stat(base, function(err) { - if (err) return cb(err); - fs.readlink(base, function(err, target) { - if (!isWindows) seenLinks[id] = target; - gotTarget(err, target); - }); - }); - } - function gotTarget(err, target, base) { - if (err) return cb(err); + // MOMENTS - var resolvedLink = pathModule.resolve(previous, target); - if (cache) cache[base] = resolvedLink; - gotResolvedLink(resolvedLink); - } + // Setting the hour should keep the time, because the user explicitly + // specified which hour they want. So trying to maintain the same hour (in + // a new timezone) makes sense. Adding/subtracting hours does not follow + // this rule. + var getSetHour = makeGetSet('Hours', true); - function gotResolvedLink(resolvedLink) { - // resolve the link, then start over - p = pathModule.resolve(resolvedLink, p.slice(pos)); - start(); - } -}; + var baseConfig = { + calendar: defaultCalendar, + longDateFormat: defaultLongDateFormat, + invalidDate: defaultInvalidDate, + ordinal: defaultOrdinal, + dayOfMonthOrdinalParse: defaultDayOfMonthOrdinalParse, + relativeTime: defaultRelativeTime, + months: defaultLocaleMonths, + monthsShort: defaultLocaleMonthsShort, -/***/ }), -/* 40 */ -/***/ (function(module, exports, __webpack_require__) { + week: defaultLocaleWeek, -module.exports = minimatch -minimatch.Minimatch = Minimatch + weekdays: defaultLocaleWeekdays, + weekdaysMin: defaultLocaleWeekdaysMin, + weekdaysShort: defaultLocaleWeekdaysShort, -var path = { sep: '/' } -try { - path = __webpack_require__(16) -} catch (er) {} + meridiemParse: defaultLocaleMeridiemParse + }; -var GLOBSTAR = minimatch.GLOBSTAR = Minimatch.GLOBSTAR = {} -var expand = __webpack_require__(41) + // internal storage for locale config files + var locales = {}; + var localeFamilies = {}; + var globalLocale; -var plTypes = { - '!': { open: '(?:(?!(?:', close: '))[^/]*?)'}, - '?': { open: '(?:', close: ')?' }, - '+': { open: '(?:', close: ')+' }, - '*': { open: '(?:', close: ')*' }, - '@': { open: '(?:', close: ')' } -} + function normalizeLocale(key) { + return key ? key.toLowerCase().replace('_', '-') : key; + } -// any single thing other than / -// don't need to escape / when using new RegExp() -var qmark = '[^/]' + // pick the locale from the array + // try ['en-au', 'en-gb'] as 'en-au', 'en-gb', 'en', as in move through the list trying each + // substring from most specific to least, but move to the next array item if it's a more specific variant than the current root + function chooseLocale(names) { + var i = 0, j, next, locale, split; -// * => any number of characters -var star = qmark + '*?' + while (i < names.length) { + split = normalizeLocale(names[i]).split('-'); + j = split.length; + next = normalizeLocale(names[i + 1]); + next = next ? next.split('-') : null; + while (j > 0) { + locale = loadLocale(split.slice(0, j).join('-')); + if (locale) { + return locale; + } + if (next && next.length >= j && compareArrays(split, next, true) >= j - 1) { + //the next array item is better than a shallower substring of this one + break; + } + j--; + } + i++; + } + return globalLocale; + } -// ** when dots are allowed. Anything goes, except .. and . -// not (^ or / followed by one or two dots followed by $ or /), -// followed by anything, any number of times. -var twoStarDot = '(?:(?!(?:\\\/|^)(?:\\.{1,2})($|\\\/)).)*?' + function loadLocale(name) { + var oldLocale = null; + // TODO: Find a better way to register and load all the locales in Node + if (!locales[name] && (typeof module !== 'undefined') && + module && module.exports) { + try { + oldLocale = globalLocale._abbr; + var aliasedRequire = require; + __webpack_require__(41)("./" + name); + getSetGlobalLocale(oldLocale); + } catch (e) {} + } + return locales[name]; + } -// not a ^ or / followed by a dot, -// followed by anything, any number of times. -var twoStarNoDot = '(?:(?!(?:\\\/|^)\\.).)*?' + // This function will load locale and then set the global locale. If + // no arguments are passed in, it will simply return the current global + // locale key. + function getSetGlobalLocale (key, values) { + var data; + if (key) { + if (isUndefined(values)) { + data = getLocale(key); + } + else { + data = defineLocale(key, values); + } -// characters that need to be escaped in RegExp. -var reSpecials = charSet('().*{}+?[]^$\\!') + if (data) { + // moment.duration._locale = moment._locale = data; + globalLocale = data; + } + else { + if ((typeof console !== 'undefined') && console.warn) { + //warn user if arguments are passed but the locale could not be set + console.warn('Locale ' + key + ' not found. Did you forget to load it?'); + } + } + } -// "abc" -> { a:true, b:true, c:true } -function charSet (s) { - return s.split('').reduce(function (set, c) { - set[c] = true - return set - }, {}) -} + return globalLocale._abbr; + } -// normalizes slashes. -var slashSplit = /\/+/ + function defineLocale (name, config) { + if (config !== null) { + var locale, parentConfig = baseConfig; + config.abbr = name; + if (locales[name] != null) { + deprecateSimple('defineLocaleOverride', + 'use moment.updateLocale(localeName, config) to change ' + + 'an existing locale. moment.defineLocale(localeName, ' + + 'config) should only be used for creating a new locale ' + + 'See http://momentjs.com/guides/#/warnings/define-locale/ for more info.'); + parentConfig = locales[name]._config; + } else if (config.parentLocale != null) { + if (locales[config.parentLocale] != null) { + parentConfig = locales[config.parentLocale]._config; + } else { + locale = loadLocale(config.parentLocale); + if (locale != null) { + parentConfig = locale._config; + } else { + if (!localeFamilies[config.parentLocale]) { + localeFamilies[config.parentLocale] = []; + } + localeFamilies[config.parentLocale].push({ + name: name, + config: config + }); + return null; + } + } + } + locales[name] = new Locale(mergeConfigs(parentConfig, config)); -minimatch.filter = filter -function filter (pattern, options) { - options = options || {} - return function (p, i, list) { - return minimatch(p, pattern, options) - } -} + if (localeFamilies[name]) { + localeFamilies[name].forEach(function (x) { + defineLocale(x.name, x.config); + }); + } -function ext (a, b) { - a = a || {} - b = b || {} - var t = {} - Object.keys(b).forEach(function (k) { - t[k] = b[k] - }) - Object.keys(a).forEach(function (k) { - t[k] = a[k] - }) - return t -} + // backwards compat for now: also set the locale + // make sure we set the locale AFTER all child locales have been + // created, so we won't end up with the child locale set. + getSetGlobalLocale(name); -minimatch.defaults = function (def) { - if (!def || !Object.keys(def).length) return minimatch - var orig = minimatch + return locales[name]; + } else { + // useful for testing + delete locales[name]; + return null; + } + } - var m = function minimatch (p, pattern, options) { - return orig.minimatch(p, pattern, ext(def, options)) - } + function updateLocale(name, config) { + if (config != null) { + var locale, tmpLocale, parentConfig = baseConfig; + // MERGE + tmpLocale = loadLocale(name); + if (tmpLocale != null) { + parentConfig = tmpLocale._config; + } + config = mergeConfigs(parentConfig, config); + locale = new Locale(config); + locale.parentLocale = locales[name]; + locales[name] = locale; - m.Minimatch = function Minimatch (pattern, options) { - return new orig.Minimatch(pattern, ext(def, options)) - } + // backwards compat for now: also set the locale + getSetGlobalLocale(name); + } else { + // pass null for config to unupdate, useful for tests + if (locales[name] != null) { + if (locales[name].parentLocale != null) { + locales[name] = locales[name].parentLocale; + } else if (locales[name] != null) { + delete locales[name]; + } + } + } + return locales[name]; + } - return m -} + // returns locale data + function getLocale (key) { + var locale; -Minimatch.defaults = function (def) { - if (!def || !Object.keys(def).length) return Minimatch - return minimatch.defaults(def).Minimatch -} + if (key && key._locale && key._locale._abbr) { + key = key._locale._abbr; + } -function minimatch (p, pattern, options) { - if (typeof pattern !== 'string') { - throw new TypeError('glob pattern string required') - } + if (!key) { + return globalLocale; + } - if (!options) options = {} + if (!isArray(key)) { + //short-circuit everything else + locale = loadLocale(key); + if (locale) { + return locale; + } + key = [key]; + } - // shortcut: comments match nothing. - if (!options.nocomment && pattern.charAt(0) === '#') { - return false - } + return chooseLocale(key); + } - // "" only matches "" - if (pattern.trim() === '') return p === '' + function listLocales() { + return keys(locales); + } - return new Minimatch(pattern, options).match(p) -} + function checkOverflow (m) { + var overflow; + var a = m._a; -function Minimatch (pattern, options) { - if (!(this instanceof Minimatch)) { - return new Minimatch(pattern, options) - } + if (a && getParsingFlags(m).overflow === -2) { + overflow = + a[MONTH] < 0 || a[MONTH] > 11 ? MONTH : + a[DATE] < 1 || a[DATE] > daysInMonth(a[YEAR], a[MONTH]) ? DATE : + a[HOUR] < 0 || a[HOUR] > 24 || (a[HOUR] === 24 && (a[MINUTE] !== 0 || a[SECOND] !== 0 || a[MILLISECOND] !== 0)) ? HOUR : + a[MINUTE] < 0 || a[MINUTE] > 59 ? MINUTE : + a[SECOND] < 0 || a[SECOND] > 59 ? SECOND : + a[MILLISECOND] < 0 || a[MILLISECOND] > 999 ? MILLISECOND : + -1; - if (typeof pattern !== 'string') { - throw new TypeError('glob pattern string required') - } + if (getParsingFlags(m)._overflowDayOfYear && (overflow < YEAR || overflow > DATE)) { + overflow = DATE; + } + if (getParsingFlags(m)._overflowWeeks && overflow === -1) { + overflow = WEEK; + } + if (getParsingFlags(m)._overflowWeekday && overflow === -1) { + overflow = WEEKDAY; + } - if (!options) options = {} - pattern = pattern.trim() + getParsingFlags(m).overflow = overflow; + } - // windows support: need to use /, not \ - if (path.sep !== '/') { - pattern = pattern.split(path.sep).join('/') - } + return m; + } - this.options = options - this.set = [] - this.pattern = pattern - this.regexp = null - this.negate = false - this.comment = false - this.empty = false + // Pick the first defined of two or three arguments. + function defaults(a, b, c) { + if (a != null) { + return a; + } + if (b != null) { + return b; + } + return c; + } - // make the set of regexps etc. - this.make() -} + function currentDateArray(config) { + // hooks is actually the exported moment object + var nowValue = new Date(hooks.now()); + if (config._useUTC) { + return [nowValue.getUTCFullYear(), nowValue.getUTCMonth(), nowValue.getUTCDate()]; + } + return [nowValue.getFullYear(), nowValue.getMonth(), nowValue.getDate()]; + } -Minimatch.prototype.debug = function () {} + // convert an array to a date. + // the array should mirror the parameters below + // note: all values past the year are optional and will default to the lowest possible value. + // [year, month, day , hour, minute, second, millisecond] + function configFromArray (config) { + var i, date, input = [], currentDate, expectedWeekday, yearToUse; -Minimatch.prototype.make = make -function make () { - // don't do it more than once. - if (this._made) return + if (config._d) { + return; + } - var pattern = this.pattern - var options = this.options + currentDate = currentDateArray(config); - // empty patterns and comments match nothing. - if (!options.nocomment && pattern.charAt(0) === '#') { - this.comment = true - return - } - if (!pattern) { - this.empty = true - return - } + //compute day of the year from weeks and weekdays + if (config._w && config._a[DATE] == null && config._a[MONTH] == null) { + dayOfYearFromWeekInfo(config); + } - // step 1: figure out negation, etc. - this.parseNegate() + //if the day of the year is set, figure out what it is + if (config._dayOfYear != null) { + yearToUse = defaults(config._a[YEAR], currentDate[YEAR]); - // step 2: expand braces - var set = this.globSet = this.braceExpand() + if (config._dayOfYear > daysInYear(yearToUse) || config._dayOfYear === 0) { + getParsingFlags(config)._overflowDayOfYear = true; + } - if (options.debug) this.debug = console.error + date = createUTCDate(yearToUse, 0, config._dayOfYear); + config._a[MONTH] = date.getUTCMonth(); + config._a[DATE] = date.getUTCDate(); + } - this.debug(this.pattern, set) + // Default to current date. + // * if no year, month, day of month are given, default to today + // * if day of month is given, default month and year + // * if month is given, default only year + // * if year is given, don't default anything + for (i = 0; i < 3 && config._a[i] == null; ++i) { + config._a[i] = input[i] = currentDate[i]; + } - // step 3: now we have a set, so turn each one into a series of path-portion - // matching patterns. - // These will be regexps, except in the case of "**", which is - // set to the GLOBSTAR object for globstar behavior, - // and will not contain any / characters - set = this.globParts = set.map(function (s) { - return s.split(slashSplit) - }) + // Zero out whatever was not defaulted, including time + for (; i < 7; i++) { + config._a[i] = input[i] = (config._a[i] == null) ? (i === 2 ? 1 : 0) : config._a[i]; + } - this.debug(this.pattern, set) + // Check for 24:00:00.000 + if (config._a[HOUR] === 24 && + config._a[MINUTE] === 0 && + config._a[SECOND] === 0 && + config._a[MILLISECOND] === 0) { + config._nextDay = true; + config._a[HOUR] = 0; + } - // glob --> regexps - set = set.map(function (s, si, set) { - return s.map(this.parse, this) - }, this) + config._d = (config._useUTC ? createUTCDate : createDate).apply(null, input); + expectedWeekday = config._useUTC ? config._d.getUTCDay() : config._d.getDay(); - this.debug(this.pattern, set) + // Apply timezone offset from input. The actual utcOffset can be changed + // with parseZone. + if (config._tzm != null) { + config._d.setUTCMinutes(config._d.getUTCMinutes() - config._tzm); + } - // filter out everything that didn't compile properly. - set = set.filter(function (s) { - return s.indexOf(false) === -1 - }) + if (config._nextDay) { + config._a[HOUR] = 24; + } - this.debug(this.pattern, set) + // check for mismatching day of week + if (config._w && typeof config._w.d !== 'undefined' && config._w.d !== expectedWeekday) { + getParsingFlags(config).weekdayMismatch = true; + } + } - this.set = set -} + function dayOfYearFromWeekInfo(config) { + var w, weekYear, week, weekday, dow, doy, temp, weekdayOverflow; -Minimatch.prototype.parseNegate = parseNegate -function parseNegate () { - var pattern = this.pattern - var negate = false - var options = this.options - var negateOffset = 0 + w = config._w; + if (w.GG != null || w.W != null || w.E != null) { + dow = 1; + doy = 4; - if (options.nonegate) return + // TODO: We need to take the current isoWeekYear, but that depends on + // how we interpret now (local, utc, fixed offset). So create + // a now version of current config (take local/utc/offset flags, and + // create now). + weekYear = defaults(w.GG, config._a[YEAR], weekOfYear(createLocal(), 1, 4).year); + week = defaults(w.W, 1); + weekday = defaults(w.E, 1); + if (weekday < 1 || weekday > 7) { + weekdayOverflow = true; + } + } else { + dow = config._locale._week.dow; + doy = config._locale._week.doy; - for (var i = 0, l = pattern.length - ; i < l && pattern.charAt(i) === '!' - ; i++) { - negate = !negate - negateOffset++ - } + var curWeek = weekOfYear(createLocal(), dow, doy); - if (negateOffset) this.pattern = pattern.substr(negateOffset) - this.negate = negate -} + weekYear = defaults(w.gg, config._a[YEAR], curWeek.year); -// Brace expansion: -// a{b,c}d -> abd acd -// a{b,}c -> abc ac -// a{0..3}d -> a0d a1d a2d a3d -// a{b,c{d,e}f}g -> abg acdfg acefg -// a{b,c}d{e,f}g -> abdeg acdeg abdeg abdfg -// -// Invalid sets are not expanded. -// a{2..}b -> a{2..}b -// a{b}c -> a{b}c -minimatch.braceExpand = function (pattern, options) { - return braceExpand(pattern, options) -} + // Default to current week. + week = defaults(w.w, curWeek.week); -Minimatch.prototype.braceExpand = braceExpand + if (w.d != null) { + // weekday -- low day numbers are considered next week + weekday = w.d; + if (weekday < 0 || weekday > 6) { + weekdayOverflow = true; + } + } else if (w.e != null) { + // local weekday -- counting starts from beginning of week + weekday = w.e + dow; + if (w.e < 0 || w.e > 6) { + weekdayOverflow = true; + } + } else { + // default to beginning of week + weekday = dow; + } + } + if (week < 1 || week > weeksInYear(weekYear, dow, doy)) { + getParsingFlags(config)._overflowWeeks = true; + } else if (weekdayOverflow != null) { + getParsingFlags(config)._overflowWeekday = true; + } else { + temp = dayOfYearFromWeeks(weekYear, week, weekday, dow, doy); + config._a[YEAR] = temp.year; + config._dayOfYear = temp.dayOfYear; + } + } + + // iso 8601 regex + // 0000-00-00 0000-W00 or 0000-W00-0 + T + 00 or 00:00 or 00:00:00 or 00:00:00.000 + +00:00 or +0000 or +00) + var extendedIsoRegex = /^\s*((?:[+-]\d{6}|\d{4})-(?:\d\d-\d\d|W\d\d-\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?::\d\d(?::\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/; + var basicIsoRegex = /^\s*((?:[+-]\d{6}|\d{4})(?:\d\d\d\d|W\d\d\d|W\d\d|\d\d\d|\d\d))(?:(T| )(\d\d(?:\d\d(?:\d\d(?:[.,]\d+)?)?)?)([\+\-]\d\d(?::?\d\d)?|\s*Z)?)?$/; + + var tzRegex = /Z|[+-]\d\d(?::?\d\d)?/; + + var isoDates = [ + ['YYYYYY-MM-DD', /[+-]\d{6}-\d\d-\d\d/], + ['YYYY-MM-DD', /\d{4}-\d\d-\d\d/], + ['GGGG-[W]WW-E', /\d{4}-W\d\d-\d/], + ['GGGG-[W]WW', /\d{4}-W\d\d/, false], + ['YYYY-DDD', /\d{4}-\d{3}/], + ['YYYY-MM', /\d{4}-\d\d/, false], + ['YYYYYYMMDD', /[+-]\d{10}/], + ['YYYYMMDD', /\d{8}/], + // YYYYMM is NOT allowed by the standard + ['GGGG[W]WWE', /\d{4}W\d{3}/], + ['GGGG[W]WW', /\d{4}W\d{2}/, false], + ['YYYYDDD', /\d{7}/] + ]; -function braceExpand (pattern, options) { - if (!options) { - if (this instanceof Minimatch) { - options = this.options - } else { - options = {} - } - } + // iso time formats and regexes + var isoTimes = [ + ['HH:mm:ss.SSSS', /\d\d:\d\d:\d\d\.\d+/], + ['HH:mm:ss,SSSS', /\d\d:\d\d:\d\d,\d+/], + ['HH:mm:ss', /\d\d:\d\d:\d\d/], + ['HH:mm', /\d\d:\d\d/], + ['HHmmss.SSSS', /\d\d\d\d\d\d\.\d+/], + ['HHmmss,SSSS', /\d\d\d\d\d\d,\d+/], + ['HHmmss', /\d\d\d\d\d\d/], + ['HHmm', /\d\d\d\d/], + ['HH', /\d\d/] + ]; - pattern = typeof pattern === 'undefined' - ? this.pattern : pattern + var aspNetJsonRegex = /^\/?Date\((\-?\d+)/i; - if (typeof pattern === 'undefined') { - throw new TypeError('undefined pattern') - } + // date from iso format + function configFromISO(config) { + var i, l, + string = config._i, + match = extendedIsoRegex.exec(string) || basicIsoRegex.exec(string), + allowTime, dateFormat, timeFormat, tzFormat; - if (options.nobrace || - !pattern.match(/\{.*\}/)) { - // shortcut. no need to expand. - return [pattern] - } + if (match) { + getParsingFlags(config).iso = true; - return expand(pattern) -} + for (i = 0, l = isoDates.length; i < l; i++) { + if (isoDates[i][1].exec(match[1])) { + dateFormat = isoDates[i][0]; + allowTime = isoDates[i][2] !== false; + break; + } + } + if (dateFormat == null) { + config._isValid = false; + return; + } + if (match[3]) { + for (i = 0, l = isoTimes.length; i < l; i++) { + if (isoTimes[i][1].exec(match[3])) { + // match[2] should be 'T' or space + timeFormat = (match[2] || ' ') + isoTimes[i][0]; + break; + } + } + if (timeFormat == null) { + config._isValid = false; + return; + } + } + if (!allowTime && timeFormat != null) { + config._isValid = false; + return; + } + if (match[4]) { + if (tzRegex.exec(match[4])) { + tzFormat = 'Z'; + } else { + config._isValid = false; + return; + } + } + config._f = dateFormat + (timeFormat || '') + (tzFormat || ''); + configFromStringAndFormat(config); + } else { + config._isValid = false; + } + } -// parse a component of the expanded set. -// At this point, no pattern may contain "/" in it -// so we're going to return a 2d array, where each entry is the full -// pattern, split on '/', and then turned into a regular expression. -// A regexp is made at the end which joins each array with an -// escaped /, and another full one which joins each regexp with |. -// -// Following the lead of Bash 4.1, note that "**" only has special meaning -// when it is the *only* thing in a path portion. Otherwise, any series -// of * is equivalent to a single *. Globstar behavior is enabled by -// default, and can be disabled by setting options.noglobstar. -Minimatch.prototype.parse = parse -var SUBPARSE = {} -function parse (pattern, isSub) { - if (pattern.length > 1024 * 64) { - throw new TypeError('pattern is too long') - } + // RFC 2822 regex: For details see https://tools.ietf.org/html/rfc2822#section-3.3 + var rfc2822 = /^(?:(Mon|Tue|Wed|Thu|Fri|Sat|Sun),?\s)?(\d{1,2})\s(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)\s(\d{2,4})\s(\d\d):(\d\d)(?::(\d\d))?\s(?:(UT|GMT|[ECMP][SD]T)|([Zz])|([+-]\d{4}))$/; - var options = this.options + function extractFromRFC2822Strings(yearStr, monthStr, dayStr, hourStr, minuteStr, secondStr) { + var result = [ + untruncateYear(yearStr), + defaultLocaleMonthsShort.indexOf(monthStr), + parseInt(dayStr, 10), + parseInt(hourStr, 10), + parseInt(minuteStr, 10) + ]; - // shortcuts - if (!options.noglobstar && pattern === '**') return GLOBSTAR - if (pattern === '') return '' + if (secondStr) { + result.push(parseInt(secondStr, 10)); + } - var re = '' - var hasMagic = !!options.nocase - var escaping = false - // ? => one single character - var patternListStack = [] - var negativeLists = [] - var stateChar - var inClass = false - var reClassStart = -1 - var classStart = -1 - // . and .. never match anything that doesn't start with ., - // even when options.dot is set. - var patternStart = pattern.charAt(0) === '.' ? '' // anything - // not (start or / followed by . or .. followed by / or end) - : options.dot ? '(?!(?:^|\\\/)\\.{1,2}(?:$|\\\/))' - : '(?!\\.)' - var self = this + return result; + } - function clearStateChar () { - if (stateChar) { - // we had some state-tracking character - // that wasn't consumed by this pass. - switch (stateChar) { - case '*': - re += star - hasMagic = true - break - case '?': - re += qmark - hasMagic = true - break - default: - re += '\\' + stateChar - break - } - self.debug('clearStateChar %j %j', stateChar, re) - stateChar = false + function untruncateYear(yearStr) { + var year = parseInt(yearStr, 10); + if (year <= 49) { + return 2000 + year; + } else if (year <= 999) { + return 1900 + year; + } + return year; } - } - for (var i = 0, len = pattern.length, c - ; (i < len) && (c = pattern.charAt(i)) - ; i++) { - this.debug('%s\t%s %s %j', pattern, i, re, c) + function preprocessRFC2822(s) { + // Remove comments and folding whitespace and replace multiple-spaces with a single space + return s.replace(/\([^)]*\)|[\n\t]/g, ' ').replace(/(\s\s+)/g, ' ').replace(/^\s\s*/, '').replace(/\s\s*$/, ''); + } - // skip over any that are escaped. - if (escaping && reSpecials[c]) { - re += '\\' + c - escaping = false - continue + function checkWeekday(weekdayStr, parsedInput, config) { + if (weekdayStr) { + // TODO: Replace the vanilla JS Date object with an indepentent day-of-week check. + var weekdayProvided = defaultLocaleWeekdaysShort.indexOf(weekdayStr), + weekdayActual = new Date(parsedInput[0], parsedInput[1], parsedInput[2]).getDay(); + if (weekdayProvided !== weekdayActual) { + getParsingFlags(config).weekdayMismatch = true; + config._isValid = false; + return false; + } + } + return true; } - switch (c) { - case '/': - // completely not allowed, even escaped. - // Should already be path-split by now. - return false + var obsOffsets = { + UT: 0, + GMT: 0, + EDT: -4 * 60, + EST: -5 * 60, + CDT: -5 * 60, + CST: -6 * 60, + MDT: -6 * 60, + MST: -7 * 60, + PDT: -7 * 60, + PST: -8 * 60 + }; + + function calculateOffset(obsOffset, militaryOffset, numOffset) { + if (obsOffset) { + return obsOffsets[obsOffset]; + } else if (militaryOffset) { + // the only allowed military tz is Z + return 0; + } else { + var hm = parseInt(numOffset, 10); + var m = hm % 100, h = (hm - m) / 100; + return h * 60 + m; + } + } - case '\\': - clearStateChar() - escaping = true - continue + // date and time from ref 2822 format + function configFromRFC2822(config) { + var match = rfc2822.exec(preprocessRFC2822(config._i)); + if (match) { + var parsedArray = extractFromRFC2822Strings(match[4], match[3], match[2], match[5], match[6], match[7]); + if (!checkWeekday(match[1], parsedArray, config)) { + return; + } - // the various stateChar values - // for the "extglob" stuff. - case '?': - case '*': - case '+': - case '@': - case '!': - this.debug('%s\t%s %s %j <-- stateChar', pattern, i, re, c) + config._a = parsedArray; + config._tzm = calculateOffset(match[8], match[9], match[10]); - // all of those are literals inside a class, except that - // the glob [!a] means [^a] in regexp - if (inClass) { - this.debug(' in class') - if (c === '!' && i === classStart + 1) c = '^' - re += c - continue + config._d = createUTCDate.apply(null, config._a); + config._d.setUTCMinutes(config._d.getUTCMinutes() - config._tzm); + + getParsingFlags(config).rfc2822 = true; + } else { + config._isValid = false; } + } - // if we already have a stateChar, then it means - // that there was something like ** or +? in there. - // Handle the stateChar, then proceed with this one. - self.debug('call clearStateChar %j', stateChar) - clearStateChar() - stateChar = c - // if extglob is disabled, then +(asdf|foo) isn't a thing. - // just clear the statechar *now*, rather than even diving into - // the patternList stuff. - if (options.noext) clearStateChar() - continue + // date from iso format or fallback + function configFromString(config) { + var matched = aspNetJsonRegex.exec(config._i); - case '(': - if (inClass) { - re += '(' - continue + if (matched !== null) { + config._d = new Date(+matched[1]); + return; } - if (!stateChar) { - re += '\\(' - continue + configFromISO(config); + if (config._isValid === false) { + delete config._isValid; + } else { + return; } - patternListStack.push({ - type: stateChar, - start: i - 1, - reStart: re.length, - open: plTypes[stateChar].open, - close: plTypes[stateChar].close - }) - // negation is (?:(?!js)[^/]*) - re += stateChar === '!' ? '(?:(?!(?:' : '(?:' - this.debug('plType %j %j', stateChar, re) - stateChar = false - continue - - case ')': - if (inClass || !patternListStack.length) { - re += '\\)' - continue + configFromRFC2822(config); + if (config._isValid === false) { + delete config._isValid; + } else { + return; } - clearStateChar() - hasMagic = true - var pl = patternListStack.pop() - // negation is (?:(?!js)[^/]*) - // The others are (?:) - re += pl.close - if (pl.type === '!') { - negativeLists.push(pl) - } - pl.reEnd = re.length - continue + // Final attempt, use Input Fallback + hooks.createFromInputFallback(config); + } - case '|': - if (inClass || !patternListStack.length || escaping) { - re += '\\|' - escaping = false - continue + hooks.createFromInputFallback = deprecate( + 'value provided is not in a recognized RFC2822 or ISO format. moment construction falls back to js Date(), ' + + 'which is not reliable across all browsers and versions. Non RFC2822/ISO date formats are ' + + 'discouraged and will be removed in an upcoming major release. Please refer to ' + + 'http://momentjs.com/guides/#/warnings/js-date/ for more info.', + function (config) { + config._d = new Date(config._i + (config._useUTC ? ' UTC' : '')); } + ); - clearStateChar() - re += '|' - continue + // constant that refers to the ISO standard + hooks.ISO_8601 = function () {}; - // these are mostly the same in regexp and glob - case '[': - // swallow any state-tracking char before the [ - clearStateChar() + // constant that refers to the RFC 2822 form + hooks.RFC_2822 = function () {}; - if (inClass) { - re += '\\' + c - continue + // date from string and format string + function configFromStringAndFormat(config) { + // TODO: Move this to another part of the creation flow to prevent circular deps + if (config._f === hooks.ISO_8601) { + configFromISO(config); + return; + } + if (config._f === hooks.RFC_2822) { + configFromRFC2822(config); + return; + } + config._a = []; + getParsingFlags(config).empty = true; + + // This array is used to make a Date, either with `new Date` or `Date.UTC` + var string = '' + config._i, + i, parsedInput, tokens, token, skipped, + stringLength = string.length, + totalParsedInputLength = 0; + + tokens = expandFormat(config._f, config._locale).match(formattingTokens) || []; + + for (i = 0; i < tokens.length; i++) { + token = tokens[i]; + parsedInput = (string.match(getParseRegexForToken(token, config)) || [])[0]; + // console.log('token', token, 'parsedInput', parsedInput, + // 'regex', getParseRegexForToken(token, config)); + if (parsedInput) { + skipped = string.substr(0, string.indexOf(parsedInput)); + if (skipped.length > 0) { + getParsingFlags(config).unusedInput.push(skipped); + } + string = string.slice(string.indexOf(parsedInput) + parsedInput.length); + totalParsedInputLength += parsedInput.length; + } + // don't parse if it's not a known token + if (formatTokenFunctions[token]) { + if (parsedInput) { + getParsingFlags(config).empty = false; + } + else { + getParsingFlags(config).unusedTokens.push(token); + } + addTimeToArrayFromToken(token, parsedInput, config); + } + else if (config._strict && !parsedInput) { + getParsingFlags(config).unusedTokens.push(token); + } } - inClass = true - classStart = i - reClassStart = re.length - re += c - continue - - case ']': - // a right bracket shall lose its special - // meaning and represent itself in - // a bracket expression if it occurs - // first in the list. -- POSIX.2 2.8.3.2 - if (i === classStart + 1 || !inClass) { - re += '\\' + c - escaping = false - continue + // add remaining unparsed input length to the string + getParsingFlags(config).charsLeftOver = stringLength - totalParsedInputLength; + if (string.length > 0) { + getParsingFlags(config).unusedInput.push(string); } - // handle the case where we left a class open. - // "[z-a]" is valid, equivalent to "\[z-a\]" - if (inClass) { - // split where the last [ was, make sure we don't have - // an invalid re. if so, re-walk the contents of the - // would-be class to re-translate any characters that - // were passed through as-is - // TODO: It would probably be faster to determine this - // without a try/catch and a new RegExp, but it's tricky - // to do safely. For now, this is safe and works. - var cs = pattern.substring(classStart + 1, i) - try { - RegExp('[' + cs + ']') - } catch (er) { - // not a valid class! - var sp = this.parse(cs, SUBPARSE) - re = re.substr(0, reClassStart) + '\\[' + sp[0] + '\\]' - hasMagic = hasMagic || sp[1] - inClass = false - continue - } + // clear _12h flag if hour is <= 12 + if (config._a[HOUR] <= 12 && + getParsingFlags(config).bigHour === true && + config._a[HOUR] > 0) { + getParsingFlags(config).bigHour = undefined; } - // finish up the class. - hasMagic = true - inClass = false - re += c - continue + getParsingFlags(config).parsedDateParts = config._a.slice(0); + getParsingFlags(config).meridiem = config._meridiem; + // handle meridiem + config._a[HOUR] = meridiemFixWrap(config._locale, config._a[HOUR], config._meridiem); - default: - // swallow any state char that wasn't consumed - clearStateChar() + configFromArray(config); + checkOverflow(config); + } - if (escaping) { - // no need - escaping = false - } else if (reSpecials[c] - && !(c === '^' && inClass)) { - re += '\\' + + function meridiemFixWrap (locale, hour, meridiem) { + var isPm; + + if (meridiem == null) { + // nothing to do + return hour; + } + if (locale.meridiemHour != null) { + return locale.meridiemHour(hour, meridiem); + } else if (locale.isPM != null) { + // Fallback + isPm = locale.isPM(meridiem); + if (isPm && hour < 12) { + hour += 12; + } + if (!isPm && hour === 12) { + hour = 0; + } + return hour; + } else { + // this is not supposed to happen + return hour; } + } - re += c + // date from string and array of format strings + function configFromStringAndArray(config) { + var tempConfig, + bestMoment, - } // switch - } // for + scoreToBeat, + i, + currentScore; - // handle the case where we left a class open. - // "[abc" is valid, equivalent to "\[abc" - if (inClass) { - // split where the last [ was, and escape it - // this is a huge pita. We now have to re-walk - // the contents of the would-be class to re-translate - // any characters that were passed through as-is - cs = pattern.substr(classStart + 1) - sp = this.parse(cs, SUBPARSE) - re = re.substr(0, reClassStart) + '\\[' + sp[0] - hasMagic = hasMagic || sp[1] - } + if (config._f.length === 0) { + getParsingFlags(config).invalidFormat = true; + config._d = new Date(NaN); + return; + } - // handle the case where we had a +( thing at the *end* - // of the pattern. - // each pattern list stack adds 3 chars, and we need to go through - // and escape any | chars that were passed through as-is for the regexp. - // Go through and escape them, taking care not to double-escape any - // | chars that were already escaped. - for (pl = patternListStack.pop(); pl; pl = patternListStack.pop()) { - var tail = re.slice(pl.reStart + pl.open.length) - this.debug('setting tail', re, pl) - // maybe some even number of \, then maybe 1 \, followed by a | - tail = tail.replace(/((?:\\{2}){0,64})(\\?)\|/g, function (_, $1, $2) { - if (!$2) { - // the | isn't already escaped, so escape it. - $2 = '\\' - } + for (i = 0; i < config._f.length; i++) { + currentScore = 0; + tempConfig = copyConfig({}, config); + if (config._useUTC != null) { + tempConfig._useUTC = config._useUTC; + } + tempConfig._f = config._f[i]; + configFromStringAndFormat(tempConfig); - // need to escape all those slashes *again*, without escaping the - // one that we need for escaping the | character. As it works out, - // escaping an even number of slashes can be done by simply repeating - // it exactly after itself. That's why this trick works. - // - // I am sorry that you have to see this. - return $1 + $1 + $2 + '|' - }) + if (!isValid(tempConfig)) { + continue; + } - this.debug('tail=%j\n %s', tail, tail, pl, re) - var t = pl.type === '*' ? star - : pl.type === '?' ? qmark - : '\\' + pl.type + // if there is any input that was not parsed add a penalty for that format + currentScore += getParsingFlags(tempConfig).charsLeftOver; - hasMagic = true - re = re.slice(0, pl.reStart) + t + '\\(' + tail - } + //or tokens + currentScore += getParsingFlags(tempConfig).unusedTokens.length * 10; - // handle trailing things that only matter at the very end. - clearStateChar() - if (escaping) { - // trailing \\ - re += '\\\\' - } + getParsingFlags(tempConfig).score = currentScore; - // only need to apply the nodot start if the re starts with - // something that could conceivably capture a dot - var addPatternStart = false - switch (re.charAt(0)) { - case '.': - case '[': - case '(': addPatternStart = true - } + if (scoreToBeat == null || currentScore < scoreToBeat) { + scoreToBeat = currentScore; + bestMoment = tempConfig; + } + } - // Hack to work around lack of negative lookbehind in JS - // A pattern like: *.!(x).!(y|z) needs to ensure that a name - // like 'a.xyz.yz' doesn't match. So, the first negative - // lookahead, has to look ALL the way ahead, to the end of - // the pattern. - for (var n = negativeLists.length - 1; n > -1; n--) { - var nl = negativeLists[n] + extend(config, bestMoment || tempConfig); + } - var nlBefore = re.slice(0, nl.reStart) - var nlFirst = re.slice(nl.reStart, nl.reEnd - 8) - var nlLast = re.slice(nl.reEnd - 8, nl.reEnd) - var nlAfter = re.slice(nl.reEnd) + function configFromObject(config) { + if (config._d) { + return; + } - nlLast += nlAfter + var i = normalizeObjectUnits(config._i); + config._a = map([i.year, i.month, i.day || i.date, i.hour, i.minute, i.second, i.millisecond], function (obj) { + return obj && parseInt(obj, 10); + }); - // Handle nested stuff like *(*.js|!(*.json)), where open parens - // mean that we should *not* include the ) in the bit that is considered - // "after" the negated section. - var openParensBefore = nlBefore.split('(').length - 1 - var cleanAfter = nlAfter - for (i = 0; i < openParensBefore; i++) { - cleanAfter = cleanAfter.replace(/\)[+*?]?/, '') + configFromArray(config); } - nlAfter = cleanAfter - var dollar = '' - if (nlAfter === '' && isSub !== SUBPARSE) { - dollar = '$' + function createFromConfig (config) { + var res = new Moment(checkOverflow(prepareConfig(config))); + if (res._nextDay) { + // Adding is smart enough around DST + res.add(1, 'd'); + res._nextDay = undefined; + } + + return res; } - var newRe = nlBefore + nlFirst + nlAfter + dollar + nlLast - re = newRe - } - // if the re is not "" at this point, then we need to make sure - // it doesn't match against an empty path part. - // Otherwise a/* will match a/, which it should not. - if (re !== '' && hasMagic) { - re = '(?=.)' + re - } + function prepareConfig (config) { + var input = config._i, + format = config._f; - if (addPatternStart) { - re = patternStart + re - } + config._locale = config._locale || getLocale(config._l); - // parsing just a piece of a larger pattern. - if (isSub === SUBPARSE) { - return [re, hasMagic] - } + if (input === null || (format === undefined && input === '')) { + return createInvalid({nullInput: true}); + } - // skip the regexp for non-magical patterns - // unescape anything in it, though, so that it'll be - // an exact match against a file etc. - if (!hasMagic) { - return globUnescape(pattern) - } + if (typeof input === 'string') { + config._i = input = config._locale.preparse(input); + } - var flags = options.nocase ? 'i' : '' - try { - var regExp = new RegExp('^' + re + '$', flags) - } catch (er) { - // If it was an invalid regular expression, then it can't match - // anything. This trick looks for a character after the end of - // the string, which is of course impossible, except in multi-line - // mode, but it's not a /m regex. - return new RegExp('$.') - } + if (isMoment(input)) { + return new Moment(checkOverflow(input)); + } else if (isDate(input)) { + config._d = input; + } else if (isArray(format)) { + configFromStringAndArray(config); + } else if (format) { + configFromStringAndFormat(config); + } else { + configFromInput(config); + } - regExp._glob = pattern - regExp._src = re + if (!isValid(config)) { + config._d = null; + } - return regExp -} + return config; + } -minimatch.makeRe = function (pattern, options) { - return new Minimatch(pattern, options || {}).makeRe() -} + function configFromInput(config) { + var input = config._i; + if (isUndefined(input)) { + config._d = new Date(hooks.now()); + } else if (isDate(input)) { + config._d = new Date(input.valueOf()); + } else if (typeof input === 'string') { + configFromString(config); + } else if (isArray(input)) { + config._a = map(input.slice(0), function (obj) { + return parseInt(obj, 10); + }); + configFromArray(config); + } else if (isObject(input)) { + configFromObject(config); + } else if (isNumber(input)) { + // from milliseconds + config._d = new Date(input); + } else { + hooks.createFromInputFallback(config); + } + } -Minimatch.prototype.makeRe = makeRe -function makeRe () { - if (this.regexp || this.regexp === false) return this.regexp - - // at this point, this.set is a 2d array of partial - // pattern strings, or "**". - // - // It's better to use .match(). This function shouldn't - // be used, really, but it's pretty convenient sometimes, - // when you just want to work with a regex. - var set = this.set + function createLocalOrUTC (input, format, locale, strict, isUTC) { + var c = {}; - if (!set.length) { - this.regexp = false - return this.regexp - } - var options = this.options + if (locale === true || locale === false) { + strict = locale; + locale = undefined; + } - var twoStar = options.noglobstar ? star - : options.dot ? twoStarDot - : twoStarNoDot - var flags = options.nocase ? 'i' : '' + if ((isObject(input) && isObjectEmpty(input)) || + (isArray(input) && input.length === 0)) { + input = undefined; + } + // object construction must be done this way. + // https://github.com/moment/moment/issues/1423 + c._isAMomentObject = true; + c._useUTC = c._isUTC = isUTC; + c._l = locale; + c._i = input; + c._f = format; + c._strict = strict; - var re = set.map(function (pattern) { - return pattern.map(function (p) { - return (p === GLOBSTAR) ? twoStar - : (typeof p === 'string') ? regExpEscape(p) - : p._src - }).join('\\\/') - }).join('|') + return createFromConfig(c); + } - // must match entire pattern - // ending in a * or ** will make it less strict. - re = '^(?:' + re + ')$' + function createLocal (input, format, locale, strict) { + return createLocalOrUTC(input, format, locale, strict, false); + } - // can match anything, as long as it's not this. - if (this.negate) re = '^(?!' + re + ').*$' + var prototypeMin = deprecate( + 'moment().min is deprecated, use moment.max instead. http://momentjs.com/guides/#/warnings/min-max/', + function () { + var other = createLocal.apply(null, arguments); + if (this.isValid() && other.isValid()) { + return other < this ? this : other; + } else { + return createInvalid(); + } + } + ); - try { - this.regexp = new RegExp(re, flags) - } catch (ex) { - this.regexp = false - } - return this.regexp -} + var prototypeMax = deprecate( + 'moment().max is deprecated, use moment.min instead. http://momentjs.com/guides/#/warnings/min-max/', + function () { + var other = createLocal.apply(null, arguments); + if (this.isValid() && other.isValid()) { + return other > this ? this : other; + } else { + return createInvalid(); + } + } + ); -minimatch.match = function (list, pattern, options) { - options = options || {} - var mm = new Minimatch(pattern, options) - list = list.filter(function (f) { - return mm.match(f) - }) - if (mm.options.nonull && !list.length) { - list.push(pattern) - } - return list -} + // Pick a moment m from moments so that m[fn](other) is true for all + // other. This relies on the function fn to be transitive. + // + // moments should either be an array of moment objects or an array, whose + // first element is an array of moment objects. + function pickBy(fn, moments) { + var res, i; + if (moments.length === 1 && isArray(moments[0])) { + moments = moments[0]; + } + if (!moments.length) { + return createLocal(); + } + res = moments[0]; + for (i = 1; i < moments.length; ++i) { + if (!moments[i].isValid() || moments[i][fn](res)) { + res = moments[i]; + } + } + return res; + } -Minimatch.prototype.match = match -function match (f, partial) { - this.debug('match', f, this.pattern) - // short-circuit in the case of busted things. - // comments, etc. - if (this.comment) return false - if (this.empty) return f === '' + // TODO: Use [].sort instead? + function min () { + var args = [].slice.call(arguments, 0); - if (f === '/' && partial) return true + return pickBy('isBefore', args); + } - var options = this.options + function max () { + var args = [].slice.call(arguments, 0); - // windows: need to use /, not \ - if (path.sep !== '/') { - f = f.split(path.sep).join('/') - } + return pickBy('isAfter', args); + } - // treat the test path as a set of pathparts. - f = f.split(slashSplit) - this.debug(this.pattern, 'split', f) + var now = function () { + return Date.now ? Date.now() : +(new Date()); + }; - // just ONE of the pattern sets in this.set needs to match - // in order for it to be valid. If negating, then just one - // match means that we have failed. - // Either way, return on the first hit. + var ordering = ['year', 'quarter', 'month', 'week', 'day', 'hour', 'minute', 'second', 'millisecond']; - var set = this.set - this.debug(this.pattern, 'set', set) + function isDurationValid(m) { + for (var key in m) { + if (!(indexOf.call(ordering, key) !== -1 && (m[key] == null || !isNaN(m[key])))) { + return false; + } + } - // Find the basename of the path by looking for the last non-empty segment - var filename - var i - for (i = f.length - 1; i >= 0; i--) { - filename = f[i] - if (filename) break - } + var unitHasDecimal = false; + for (var i = 0; i < ordering.length; ++i) { + if (m[ordering[i]]) { + if (unitHasDecimal) { + return false; // only allow non-integers for smallest unit + } + if (parseFloat(m[ordering[i]]) !== toInt(m[ordering[i]])) { + unitHasDecimal = true; + } + } + } - for (i = 0; i < set.length; i++) { - var pattern = set[i] - var file = f - if (options.matchBase && pattern.length === 1) { - file = [filename] + return true; } - var hit = this.matchOne(file, pattern, partial) - if (hit) { - if (options.flipNegate) return true - return !this.negate + + function isValid$1() { + return this._isValid; } - } - // didn't get any hits. this is success if it's a negative - // pattern, failure otherwise. - if (options.flipNegate) return false - return this.negate -} + function createInvalid$1() { + return createDuration(NaN); + } -// set partial to true to test if, for example, -// "/a/b" matches the start of "/*/b/*/d" -// Partial means, if you run out of file before you run -// out of pattern, then that's fine, as long as all -// the parts match. -Minimatch.prototype.matchOne = function (file, pattern, partial) { - var options = this.options + function Duration (duration) { + var normalizedInput = normalizeObjectUnits(duration), + years = normalizedInput.year || 0, + quarters = normalizedInput.quarter || 0, + months = normalizedInput.month || 0, + weeks = normalizedInput.week || normalizedInput.isoWeek || 0, + days = normalizedInput.day || 0, + hours = normalizedInput.hour || 0, + minutes = normalizedInput.minute || 0, + seconds = normalizedInput.second || 0, + milliseconds = normalizedInput.millisecond || 0; - this.debug('matchOne', - { 'this': this, file: file, pattern: pattern }) + this._isValid = isDurationValid(normalizedInput); - this.debug('matchOne', file.length, pattern.length) + // representation for dateAddRemove + this._milliseconds = +milliseconds + + seconds * 1e3 + // 1000 + minutes * 6e4 + // 1000 * 60 + hours * 1000 * 60 * 60; //using 1000 * 60 * 60 instead of 36e5 to avoid floating point rounding errors https://github.com/moment/moment/issues/2978 + // Because of dateAddRemove treats 24 hours as different from a + // day when working around DST, we need to store them separately + this._days = +days + + weeks * 7; + // It is impossible to translate months into days without knowing + // which months you are are talking about, so we have to store + // it separately. + this._months = +months + + quarters * 3 + + years * 12; - for (var fi = 0, - pi = 0, - fl = file.length, - pl = pattern.length - ; (fi < fl) && (pi < pl) - ; fi++, pi++) { - this.debug('matchOne loop') - var p = pattern[pi] - var f = file[fi] + this._data = {}; - this.debug(pattern, p, f) + this._locale = getLocale(); - // should be impossible. - // some invalid regexp stuff in the set. - if (p === false) return false + this._bubble(); + } - if (p === GLOBSTAR) { - this.debug('GLOBSTAR', [pattern, p, f]) + function isDuration (obj) { + return obj instanceof Duration; + } - // "**" - // a/**/b/**/c would match the following: - // a/b/x/y/z/c - // a/x/y/z/b/c - // a/b/x/b/x/c - // a/b/c - // To do this, take the rest of the pattern after - // the **, and see if it would match the file remainder. - // If so, return success. - // If not, the ** "swallows" a segment, and try again. - // This is recursively awful. - // - // a/**/b/**/c matching a/b/x/y/z/c - // - a matches a - // - doublestar - // - matchOne(b/x/y/z/c, b/**/c) - // - b matches b - // - doublestar - // - matchOne(x/y/z/c, c) -> no - // - matchOne(y/z/c, c) -> no - // - matchOne(z/c, c) -> no - // - matchOne(c, c) yes, hit - var fr = fi - var pr = pi + 1 - if (pr === pl) { - this.debug('** at the end') - // a ** at the end will just swallow the rest. - // We have found a match. - // however, it will not swallow /.x, unless - // options.dot is set. - // . and .. are *never* matched by **, for explosively - // exponential reasons. - for (; fi < fl; fi++) { - if (file[fi] === '.' || file[fi] === '..' || - (!options.dot && file[fi].charAt(0) === '.')) return false + function absRound (number) { + if (number < 0) { + return Math.round(-1 * number) * -1; + } else { + return Math.round(number); } - return true - } + } - // ok, let's see if we can swallow whatever we can. - while (fr < fl) { - var swallowee = file[fr] + // FORMATTING - this.debug('\nglobstar while', file, fr, pattern, pr, swallowee) + function offset (token, separator) { + addFormatToken(token, 0, 0, function () { + var offset = this.utcOffset(); + var sign = '+'; + if (offset < 0) { + offset = -offset; + sign = '-'; + } + return sign + zeroFill(~~(offset / 60), 2) + separator + zeroFill(~~(offset) % 60, 2); + }); + } - // XXX remove this slice. Just pass the start index. - if (this.matchOne(file.slice(fr), pattern.slice(pr), partial)) { - this.debug('globstar found match!', fr, fl, swallowee) - // found a match. - return true - } else { - // can't swallow "." or ".." ever. - // can only swallow ".foo" when explicitly asked. - if (swallowee === '.' || swallowee === '..' || - (!options.dot && swallowee.charAt(0) === '.')) { - this.debug('dot detected!', file, fr, pattern, pr) - break - } + offset('Z', ':'); + offset('ZZ', ''); - // ** swallows a segment, and continue. - this.debug('globstar swallow a segment, and continue') - fr++ + // PARSING + + addRegexToken('Z', matchShortOffset); + addRegexToken('ZZ', matchShortOffset); + addParseToken(['Z', 'ZZ'], function (input, array, config) { + config._useUTC = true; + config._tzm = offsetFromString(matchShortOffset, input); + }); + + // HELPERS + + // timezone chunker + // '+10:00' > ['10', '00'] + // '-1530' > ['-15', '30'] + var chunkOffset = /([\+\-]|\d\d)/gi; + + function offsetFromString(matcher, string) { + var matches = (string || '').match(matcher); + + if (matches === null) { + return null; } - } - // no match was found. - // However, in partial mode, we can't say this is necessarily over. - // If there's more *pattern* left, then - if (partial) { - // ran out of file - this.debug('\n>>> no match, partial?', file, fr, pattern, pr) - if (fr === fl) return true - } - return false + var chunk = matches[matches.length - 1] || []; + var parts = (chunk + '').match(chunkOffset) || ['-', 0, 0]; + var minutes = +(parts[1] * 60) + toInt(parts[2]); + + return minutes === 0 ? + 0 : + parts[0] === '+' ? minutes : -minutes; } - // something other than ** - // non-magic patterns just have to match exactly - // patterns with magic have been turned into regexps. - var hit - if (typeof p === 'string') { - if (options.nocase) { - hit = f.toLowerCase() === p.toLowerCase() - } else { - hit = f === p - } - this.debug('string match', p, f, hit) - } else { - hit = f.match(p) - this.debug('pattern match', p, f, hit) + // Return a moment from input, that is local/utc/zone equivalent to model. + function cloneWithOffset(input, model) { + var res, diff; + if (model._isUTC) { + res = model.clone(); + diff = (isMoment(input) || isDate(input) ? input.valueOf() : createLocal(input).valueOf()) - res.valueOf(); + // Use low-level api, because this fn is low-level api. + res._d.setTime(res._d.valueOf() + diff); + hooks.updateOffset(res, false); + return res; + } else { + return createLocal(input).local(); + } } - if (!hit) return false - } + function getDateOffset (m) { + // On Firefox.24 Date#getTimezoneOffset returns a floating point. + // https://github.com/moment/moment/pull/1871 + return -Math.round(m._d.getTimezoneOffset() / 15) * 15; + } - // Note: ending in / means that we'll get a final "" - // at the end of the pattern. This can only match a - // corresponding "" at the end of the file. - // If the file ends in /, then it can only match a - // a pattern that ends in /, unless the pattern just - // doesn't have any more for it. But, a/b/ should *not* - // match "a/b/*", even though "" matches against the - // [^/]*? pattern, except in partial mode, where it might - // simply not be reached yet. - // However, a/b/ should still satisfy a/* + // HOOKS - // now either we fell off the end of the pattern, or we're done. - if (fi === fl && pi === pl) { - // ran out of pattern and filename at the same time. - // an exact hit! - return true - } else if (fi === fl) { - // ran out of file, but still had pattern left. - // this is ok if we're doing the match as part of - // a glob fs traversal. - return partial - } else if (pi === pl) { - // ran out of pattern, still have file left. - // this is only acceptable if we're on the very last - // empty segment of a file with a trailing slash. - // a/* should match a/b/ - var emptyFileEnd = (fi === fl - 1) && (file[fi] === '') - return emptyFileEnd - } + // This function will be called whenever a moment is mutated. + // It is intended to keep the offset in sync with the timezone. + hooks.updateOffset = function () {}; - // should be unreachable. - throw new Error('wtf?') -} + // MOMENTS -// replace stuff like \* with * -function globUnescape (s) { - return s.replace(/\\(.)/g, '$1') -} + // keepLocalTime = true means only change the timezone, without + // affecting the local hour. So 5:31:26 +0300 --[utcOffset(2, true)]--> + // 5:31:26 +0200 It is possible that 5:31:26 doesn't exist with offset + // +0200, so we adjust the time as needed, to be valid. + // + // Keeping the time actually adds/subtracts (one hour) + // from the actual represented time. That is why we call updateOffset + // a second time. In case it wants us to change the offset again + // _changeInProgress == true case, then we have to adjust, because + // there is no such time in the given timezone. + function getSetOffset (input, keepLocalTime, keepMinutes) { + var offset = this._offset || 0, + localAdjust; + if (!this.isValid()) { + return input != null ? this : NaN; + } + if (input != null) { + if (typeof input === 'string') { + input = offsetFromString(matchShortOffset, input); + if (input === null) { + return this; + } + } else if (Math.abs(input) < 16 && !keepMinutes) { + input = input * 60; + } + if (!this._isUTC && keepLocalTime) { + localAdjust = getDateOffset(this); + } + this._offset = input; + this._isUTC = true; + if (localAdjust != null) { + this.add(localAdjust, 'm'); + } + if (offset !== input) { + if (!keepLocalTime || this._changeInProgress) { + addSubtract(this, createDuration(input - offset, 'm'), 1, false); + } else if (!this._changeInProgress) { + this._changeInProgress = true; + hooks.updateOffset(this, true); + this._changeInProgress = null; + } + } + return this; + } else { + return this._isUTC ? offset : getDateOffset(this); + } + } -function regExpEscape (s) { - return s.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&') -} + function getSetZone (input, keepLocalTime) { + if (input != null) { + if (typeof input !== 'string') { + input = -input; + } + this.utcOffset(input, keepLocalTime); -/***/ }), -/* 41 */ -/***/ (function(module, exports, __webpack_require__) { + return this; + } else { + return -this.utcOffset(); + } + } -var concatMap = __webpack_require__(42); -var balanced = __webpack_require__(43); + function setOffsetToUTC (keepLocalTime) { + return this.utcOffset(0, keepLocalTime); + } -module.exports = expandTop; + function setOffsetToLocal (keepLocalTime) { + if (this._isUTC) { + this.utcOffset(0, keepLocalTime); + this._isUTC = false; -var escSlash = '\0SLASH'+Math.random()+'\0'; -var escOpen = '\0OPEN'+Math.random()+'\0'; -var escClose = '\0CLOSE'+Math.random()+'\0'; -var escComma = '\0COMMA'+Math.random()+'\0'; -var escPeriod = '\0PERIOD'+Math.random()+'\0'; + if (keepLocalTime) { + this.subtract(getDateOffset(this), 'm'); + } + } + return this; + } -function numeric(str) { - return parseInt(str, 10) == str - ? parseInt(str, 10) - : str.charCodeAt(0); -} + function setOffsetToParsedOffset () { + if (this._tzm != null) { + this.utcOffset(this._tzm, false, true); + } else if (typeof this._i === 'string') { + var tZone = offsetFromString(matchOffset, this._i); + if (tZone != null) { + this.utcOffset(tZone); + } + else { + this.utcOffset(0, true); + } + } + return this; + } -function escapeBraces(str) { - return str.split('\\\\').join(escSlash) - .split('\\{').join(escOpen) - .split('\\}').join(escClose) - .split('\\,').join(escComma) - .split('\\.').join(escPeriod); -} + function hasAlignedHourOffset (input) { + if (!this.isValid()) { + return false; + } + input = input ? createLocal(input).utcOffset() : 0; -function unescapeBraces(str) { - return str.split(escSlash).join('\\') - .split(escOpen).join('{') - .split(escClose).join('}') - .split(escComma).join(',') - .split(escPeriod).join('.'); -} + return (this.utcOffset() - input) % 60 === 0; + } + function isDaylightSavingTime () { + return ( + this.utcOffset() > this.clone().month(0).utcOffset() || + this.utcOffset() > this.clone().month(5).utcOffset() + ); + } -// Basically just str.split(","), but handling cases -// where we have nested braced sections, which should be -// treated as individual members, like {a,{b,c},d} -function parseCommaParts(str) { - if (!str) - return ['']; + function isDaylightSavingTimeShifted () { + if (!isUndefined(this._isDSTShifted)) { + return this._isDSTShifted; + } - var parts = []; - var m = balanced('{', '}', str); + var c = {}; - if (!m) - return str.split(','); + copyConfig(c, this); + c = prepareConfig(c); - var pre = m.pre; - var body = m.body; - var post = m.post; - var p = pre.split(','); + if (c._a) { + var other = c._isUTC ? createUTC(c._a) : createLocal(c._a); + this._isDSTShifted = this.isValid() && + compareArrays(c._a, other.toArray()) > 0; + } else { + this._isDSTShifted = false; + } - p[p.length-1] += '{' + body + '}'; - var postParts = parseCommaParts(post); - if (post.length) { - p[p.length-1] += postParts.shift(); - p.push.apply(p, postParts); - } + return this._isDSTShifted; + } - parts.push.apply(parts, p); + function isLocal () { + return this.isValid() ? !this._isUTC : false; + } - return parts; -} + function isUtcOffset () { + return this.isValid() ? this._isUTC : false; + } -function expandTop(str) { - if (!str) - return []; + function isUtc () { + return this.isValid() ? this._isUTC && this._offset === 0 : false; + } - // I don't know why Bash 4.3 does this, but it does. - // Anything starting with {} will have the first two bytes preserved - // but *only* at the top level, so {},a}b will not expand to anything, - // but a{},b}c will be expanded to [a}c,abc]. - // One could argue that this is a bug in Bash, but since the goal of - // this module is to match Bash's rules, we escape a leading {} - if (str.substr(0, 2) === '{}') { - str = '\\{\\}' + str.substr(2); - } + // ASP.NET json date format regex + var aspNetRegex = /^(\-|\+)?(?:(\d*)[. ])?(\d+)\:(\d+)(?:\:(\d+)(\.\d*)?)?$/; - return expand(escapeBraces(str), true).map(unescapeBraces); -} + // from http://docs.closure-library.googlecode.com/git/closure_goog_date_date.js.source.html + // somewhat more in line with 4.4.3.2 2004 spec, but allows decimal anywhere + // and further modified to allow for strings containing both week and day + var isoRegex = /^(-|\+)?P(?:([-+]?[0-9,.]*)Y)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)W)?(?:([-+]?[0-9,.]*)D)?(?:T(?:([-+]?[0-9,.]*)H)?(?:([-+]?[0-9,.]*)M)?(?:([-+]?[0-9,.]*)S)?)?$/; -function identity(e) { - return e; -} + function createDuration (input, key) { + var duration = input, + // matching against regexp is expensive, do it on demand + match = null, + sign, + ret, + diffRes; -function embrace(str) { - return '{' + str + '}'; -} -function isPadded(el) { - return /^-?0\d/.test(el); -} + if (isDuration(input)) { + duration = { + ms : input._milliseconds, + d : input._days, + M : input._months + }; + } else if (isNumber(input)) { + duration = {}; + if (key) { + duration[key] = input; + } else { + duration.milliseconds = input; + } + } else if (!!(match = aspNetRegex.exec(input))) { + sign = (match[1] === '-') ? -1 : 1; + duration = { + y : 0, + d : toInt(match[DATE]) * sign, + h : toInt(match[HOUR]) * sign, + m : toInt(match[MINUTE]) * sign, + s : toInt(match[SECOND]) * sign, + ms : toInt(absRound(match[MILLISECOND] * 1000)) * sign // the millisecond decimal point is included in the match + }; + } else if (!!(match = isoRegex.exec(input))) { + sign = (match[1] === '-') ? -1 : 1; + duration = { + y : parseIso(match[2], sign), + M : parseIso(match[3], sign), + w : parseIso(match[4], sign), + d : parseIso(match[5], sign), + h : parseIso(match[6], sign), + m : parseIso(match[7], sign), + s : parseIso(match[8], sign) + }; + } else if (duration == null) {// checks for null or undefined + duration = {}; + } else if (typeof duration === 'object' && ('from' in duration || 'to' in duration)) { + diffRes = momentsDifference(createLocal(duration.from), createLocal(duration.to)); -function lte(i, y) { - return i <= y; -} -function gte(i, y) { - return i >= y; -} + duration = {}; + duration.ms = diffRes.milliseconds; + duration.M = diffRes.months; + } -function expand(str, isTop) { - var expansions = []; + ret = new Duration(duration); - var m = balanced('{', '}', str); - if (!m || /\$$/.test(m.pre)) return [str]; + if (isDuration(input) && hasOwnProp(input, '_locale')) { + ret._locale = input._locale; + } - var isNumericSequence = /^-?\d+\.\.-?\d+(?:\.\.-?\d+)?$/.test(m.body); - var isAlphaSequence = /^[a-zA-Z]\.\.[a-zA-Z](?:\.\.-?\d+)?$/.test(m.body); - var isSequence = isNumericSequence || isAlphaSequence; - var isOptions = m.body.indexOf(',') >= 0; - if (!isSequence && !isOptions) { - // {a},b} - if (m.post.match(/,.*\}/)) { - str = m.pre + '{' + m.body + escClose + m.post; - return expand(str); + return ret; } - return [str]; - } - var n; - if (isSequence) { - n = m.body.split(/\.\./); - } else { - n = parseCommaParts(m.body); - if (n.length === 1) { - // x{{a,b}}y ==> x{a}y x{b}y - n = expand(n[0], false).map(embrace); - if (n.length === 1) { - var post = m.post.length - ? expand(m.post, false) - : ['']; - return post.map(function(p) { - return m.pre + n[0] + p; - }); - } + createDuration.fn = Duration.prototype; + createDuration.invalid = createInvalid$1; + + function parseIso (inp, sign) { + // We'd normally use ~~inp for this, but unfortunately it also + // converts floats to ints. + // inp may be undefined, so careful calling replace on it. + var res = inp && parseFloat(inp.replace(',', '.')); + // apply sign while we're at it + return (isNaN(res) ? 0 : res) * sign; } - } - // at this point, n is the parts, and we know it's not a comma set - // with a single entry. + function positiveMomentsDifference(base, other) { + var res = {}; - // no need to expand pre, since it is guaranteed to be free of brace-sets - var pre = m.pre; - var post = m.post.length - ? expand(m.post, false) - : ['']; + res.months = other.month() - base.month() + + (other.year() - base.year()) * 12; + if (base.clone().add(res.months, 'M').isAfter(other)) { + --res.months; + } - var N; + res.milliseconds = +other - +(base.clone().add(res.months, 'M')); - if (isSequence) { - var x = numeric(n[0]); - var y = numeric(n[1]); - var width = Math.max(n[0].length, n[1].length) - var incr = n.length == 3 - ? Math.abs(numeric(n[2])) - : 1; - var test = lte; - var reverse = y < x; - if (reverse) { - incr *= -1; - test = gte; + return res; } - var pad = n.some(isPadded); - N = []; + function momentsDifference(base, other) { + var res; + if (!(base.isValid() && other.isValid())) { + return {milliseconds: 0, months: 0}; + } - for (var i = x; test(i, y); i += incr) { - var c; - if (isAlphaSequence) { - c = String.fromCharCode(i); - if (c === '\\') - c = ''; - } else { - c = String(i); - if (pad) { - var need = width - c.length; - if (need > 0) { - var z = new Array(need + 1).join('0'); - if (i < 0) - c = '-' + z + c.slice(1); - else - c = z + c; - } + other = cloneWithOffset(other, base); + if (base.isBefore(other)) { + res = positiveMomentsDifference(base, other); + } else { + res = positiveMomentsDifference(other, base); + res.milliseconds = -res.milliseconds; + res.months = -res.months; } - } - N.push(c); - } - } else { - N = concatMap(n, function(el) { return expand(el, false) }); - } - for (var j = 0; j < N.length; j++) { - for (var k = 0; k < post.length; k++) { - var expansion = pre + N[j] + post[k]; - if (!isTop || isSequence || expansion) - expansions.push(expansion); + return res; } - } - return expansions; -} + // TODO: remove 'name' arg after deprecation is removed + function createAdder(direction, name) { + return function (val, period) { + var dur, tmp; + //invert the arguments, but complain about it + if (period !== null && !isNaN(+period)) { + deprecateSimple(name, 'moment().' + name + '(period, number) is deprecated. Please use moment().' + name + '(number, period). ' + + 'See http://momentjs.com/guides/#/warnings/add-inverted-param/ for more info.'); + tmp = val; val = period; period = tmp; + } + val = typeof val === 'string' ? +val : val; + dur = createDuration(val, period); + addSubtract(this, dur, direction); + return this; + }; + } + function addSubtract (mom, duration, isAdding, updateOffset) { + var milliseconds = duration._milliseconds, + days = absRound(duration._days), + months = absRound(duration._months); -/***/ }), -/* 42 */ -/***/ (function(module, exports) { + if (!mom.isValid()) { + // No op + return; + } -module.exports = function (xs, fn) { - var res = []; - for (var i = 0; i < xs.length; i++) { - var x = fn(xs[i], i); - if (isArray(x)) res.push.apply(res, x); - else res.push(x); - } - return res; -}; - -var isArray = Array.isArray || function (xs) { - return Object.prototype.toString.call(xs) === '[object Array]'; -}; + updateOffset = updateOffset == null ? true : updateOffset; + if (months) { + setMonth(mom, get(mom, 'Month') + months * isAdding); + } + if (days) { + set$1(mom, 'Date', get(mom, 'Date') + days * isAdding); + } + if (milliseconds) { + mom._d.setTime(mom._d.valueOf() + milliseconds * isAdding); + } + if (updateOffset) { + hooks.updateOffset(mom, days || months); + } + } -/***/ }), -/* 43 */ -/***/ (function(module, exports, __webpack_require__) { + var add = createAdder(1, 'add'); + var subtract = createAdder(-1, 'subtract'); -"use strict"; + function getCalendarFormat(myMoment, now) { + var diff = myMoment.diff(now, 'days', true); + return diff < -6 ? 'sameElse' : + diff < -1 ? 'lastWeek' : + diff < 0 ? 'lastDay' : + diff < 1 ? 'sameDay' : + diff < 2 ? 'nextDay' : + diff < 7 ? 'nextWeek' : 'sameElse'; + } -module.exports = balanced; -function balanced(a, b, str) { - if (a instanceof RegExp) a = maybeMatch(a, str); - if (b instanceof RegExp) b = maybeMatch(b, str); + function calendar$1 (time, formats) { + // We want to compare the start of today, vs this. + // Getting start-of-today depends on whether we're local/utc/offset or not. + var now = time || createLocal(), + sod = cloneWithOffset(now, this).startOf('day'), + format = hooks.calendarFormat(this, sod) || 'sameElse'; - var r = range(a, b, str); + var output = formats && (isFunction(formats[format]) ? formats[format].call(this, now) : formats[format]); - return r && { - start: r[0], - end: r[1], - pre: str.slice(0, r[0]), - body: str.slice(r[0] + a.length, r[1]), - post: str.slice(r[1] + b.length) - }; -} + return this.format(output || this.localeData().calendar(format, this, createLocal(now))); + } -function maybeMatch(reg, str) { - var m = str.match(reg); - return m ? m[0] : null; -} + function clone () { + return new Moment(this); + } -balanced.range = range; -function range(a, b, str) { - var begs, beg, left, right, result; - var ai = str.indexOf(a); - var bi = str.indexOf(b, ai + 1); - var i = ai; + function isAfter (input, units) { + var localInput = isMoment(input) ? input : createLocal(input); + if (!(this.isValid() && localInput.isValid())) { + return false; + } + units = normalizeUnits(units) || 'millisecond'; + if (units === 'millisecond') { + return this.valueOf() > localInput.valueOf(); + } else { + return localInput.valueOf() < this.clone().startOf(units).valueOf(); + } + } - if (ai >= 0 && bi > 0) { - begs = []; - left = str.length; + function isBefore (input, units) { + var localInput = isMoment(input) ? input : createLocal(input); + if (!(this.isValid() && localInput.isValid())) { + return false; + } + units = normalizeUnits(units) || 'millisecond'; + if (units === 'millisecond') { + return this.valueOf() < localInput.valueOf(); + } else { + return this.clone().endOf(units).valueOf() < localInput.valueOf(); + } + } - while (i >= 0 && !result) { - if (i == ai) { - begs.push(i); - ai = str.indexOf(a, i + 1); - } else if (begs.length == 1) { - result = [ begs.pop(), bi ]; - } else { - beg = begs.pop(); - if (beg < left) { - left = beg; - right = bi; + function isBetween (from, to, units, inclusivity) { + var localFrom = isMoment(from) ? from : createLocal(from), + localTo = isMoment(to) ? to : createLocal(to); + if (!(this.isValid() && localFrom.isValid() && localTo.isValid())) { + return false; } + inclusivity = inclusivity || '()'; + return (inclusivity[0] === '(' ? this.isAfter(localFrom, units) : !this.isBefore(localFrom, units)) && + (inclusivity[1] === ')' ? this.isBefore(localTo, units) : !this.isAfter(localTo, units)); + } - bi = str.indexOf(b, i + 1); - } + function isSame (input, units) { + var localInput = isMoment(input) ? input : createLocal(input), + inputMs; + if (!(this.isValid() && localInput.isValid())) { + return false; + } + units = normalizeUnits(units) || 'millisecond'; + if (units === 'millisecond') { + return this.valueOf() === localInput.valueOf(); + } else { + inputMs = localInput.valueOf(); + return this.clone().startOf(units).valueOf() <= inputMs && inputMs <= this.clone().endOf(units).valueOf(); + } + } - i = ai < bi && ai >= 0 ? ai : bi; + function isSameOrAfter (input, units) { + return this.isSame(input, units) || this.isAfter(input, units); } - if (begs.length) { - result = [ left, right ]; + function isSameOrBefore (input, units) { + return this.isSame(input, units) || this.isBefore(input, units); } - } - return result; -} + function diff (input, units, asFloat) { + var that, + zoneDelta, + output; + if (!this.isValid()) { + return NaN; + } -/***/ }), -/* 44 */ -/***/ (function(module, exports, __webpack_require__) { + that = cloneWithOffset(input, this); -try { - var util = __webpack_require__(29); - if (typeof util.inherits !== 'function') throw ''; - module.exports = util.inherits; -} catch (e) { - module.exports = __webpack_require__(45); -} + if (!that.isValid()) { + return NaN; + } + zoneDelta = (that.utcOffset() - this.utcOffset()) * 6e4; -/***/ }), -/* 45 */ -/***/ (function(module, exports) { + units = normalizeUnits(units); -if (typeof Object.create === 'function') { - // implementation from standard node.js 'util' module - module.exports = function inherits(ctor, superCtor) { - ctor.super_ = superCtor - ctor.prototype = Object.create(superCtor.prototype, { - constructor: { - value: ctor, - enumerable: false, - writable: true, - configurable: true - } - }); - }; -} else { - // old school shim for old browsers - module.exports = function inherits(ctor, superCtor) { - ctor.super_ = superCtor - var TempCtor = function () {} - TempCtor.prototype = superCtor.prototype - ctor.prototype = new TempCtor() - ctor.prototype.constructor = ctor - } -} + switch (units) { + case 'year': output = monthDiff(this, that) / 12; break; + case 'month': output = monthDiff(this, that); break; + case 'quarter': output = monthDiff(this, that) / 3; break; + case 'second': output = (this - that) / 1e3; break; // 1000 + case 'minute': output = (this - that) / 6e4; break; // 1000 * 60 + case 'hour': output = (this - that) / 36e5; break; // 1000 * 60 * 60 + case 'day': output = (this - that - zoneDelta) / 864e5; break; // 1000 * 60 * 60 * 24, negate dst + case 'week': output = (this - that - zoneDelta) / 6048e5; break; // 1000 * 60 * 60 * 24 * 7, negate dst + default: output = this - that; + } + return asFloat ? output : absFloor(output); + } -/***/ }), -/* 46 */ -/***/ (function(module, exports) { + function monthDiff (a, b) { + // difference in months + var wholeMonthDiff = ((b.year() - a.year()) * 12) + (b.month() - a.month()), + // b is in (anchor - 1 month, anchor + 1 month) + anchor = a.clone().add(wholeMonthDiff, 'months'), + anchor2, adjust; -module.exports = require("events"); + if (b - anchor < 0) { + anchor2 = a.clone().add(wholeMonthDiff - 1, 'months'); + // linear across the month + adjust = (b - anchor) / (anchor - anchor2); + } else { + anchor2 = a.clone().add(wholeMonthDiff + 1, 'months'); + // linear across the month + adjust = (b - anchor) / (anchor2 - anchor); + } -/***/ }), -/* 47 */ -/***/ (function(module, exports, __webpack_require__) { + //check for negative zero, return zero if negative zero + return -(wholeMonthDiff + adjust) || 0; + } -"use strict"; + hooks.defaultFormat = 'YYYY-MM-DDTHH:mm:ssZ'; + hooks.defaultFormatUtc = 'YYYY-MM-DDTHH:mm:ss[Z]'; + function toString () { + return this.clone().locale('en').format('ddd MMM DD YYYY HH:mm:ss [GMT]ZZ'); + } -function posix(path) { - return path.charAt(0) === '/'; -} + function toISOString(keepOffset) { + if (!this.isValid()) { + return null; + } + var utc = keepOffset !== true; + var m = utc ? this.clone().utc() : this; + if (m.year() < 0 || m.year() > 9999) { + return formatMoment(m, utc ? 'YYYYYY-MM-DD[T]HH:mm:ss.SSS[Z]' : 'YYYYYY-MM-DD[T]HH:mm:ss.SSSZ'); + } + if (isFunction(Date.prototype.toISOString)) { + // native implementation is ~50x faster, use it when we can + if (utc) { + return this.toDate().toISOString(); + } else { + return new Date(this.valueOf() + this.utcOffset() * 60 * 1000).toISOString().replace('Z', formatMoment(m, 'Z')); + } + } + return formatMoment(m, utc ? 'YYYY-MM-DD[T]HH:mm:ss.SSS[Z]' : 'YYYY-MM-DD[T]HH:mm:ss.SSSZ'); + } -function win32(path) { - // https://github.com/nodejs/node/blob/b3fcc245fb25539909ef1d5eaa01dbf92e168633/lib/path.js#L56 - var splitDeviceRe = /^([a-zA-Z]:|[\\\/]{2}[^\\\/]+[\\\/]+[^\\\/]+)?([\\\/])?([\s\S]*?)$/; - var result = splitDeviceRe.exec(path); - var device = result[1] || ''; - var isUnc = Boolean(device && device.charAt(1) !== ':'); + /** + * Return a human readable representation of a moment that can + * also be evaluated to get a new moment which is the same + * + * @link https://nodejs.org/dist/latest/docs/api/util.html#util_custom_inspect_function_on_objects + */ + function inspect () { + if (!this.isValid()) { + return 'moment.invalid(/* ' + this._i + ' */)'; + } + var func = 'moment'; + var zone = ''; + if (!this.isLocal()) { + func = this.utcOffset() === 0 ? 'moment.utc' : 'moment.parseZone'; + zone = 'Z'; + } + var prefix = '[' + func + '("]'; + var year = (0 <= this.year() && this.year() <= 9999) ? 'YYYY' : 'YYYYYY'; + var datetime = '-MM-DD[T]HH:mm:ss.SSS'; + var suffix = zone + '[")]'; - // UNC paths are always absolute - return Boolean(result[2] || isUnc); -} + return this.format(prefix + year + datetime + suffix); + } -module.exports = process.platform === 'win32' ? win32 : posix; -module.exports.posix = posix; -module.exports.win32 = win32; + function format (inputString) { + if (!inputString) { + inputString = this.isUtc() ? hooks.defaultFormatUtc : hooks.defaultFormat; + } + var output = formatMoment(this, inputString); + return this.localeData().postformat(output); + } + function from (time, withoutSuffix) { + if (this.isValid() && + ((isMoment(time) && time.isValid()) || + createLocal(time).isValid())) { + return createDuration({to: this, from: time}).locale(this.locale()).humanize(!withoutSuffix); + } else { + return this.localeData().invalidDate(); + } + } -/***/ }), -/* 48 */ -/***/ (function(module, exports, __webpack_require__) { + function fromNow (withoutSuffix) { + return this.from(createLocal(), withoutSuffix); + } -module.exports = globSync -globSync.GlobSync = GlobSync + function to (time, withoutSuffix) { + if (this.isValid() && + ((isMoment(time) && time.isValid()) || + createLocal(time).isValid())) { + return createDuration({from: this, to: time}).locale(this.locale()).humanize(!withoutSuffix); + } else { + return this.localeData().invalidDate(); + } + } -var fs = __webpack_require__(23) -var rp = __webpack_require__(38) -var minimatch = __webpack_require__(40) -var Minimatch = minimatch.Minimatch -var Glob = __webpack_require__(37).Glob -var util = __webpack_require__(29) -var path = __webpack_require__(16) -var assert = __webpack_require__(30) -var isAbsolute = __webpack_require__(47) -var common = __webpack_require__(49) -var alphasort = common.alphasort -var alphasorti = common.alphasorti -var setopts = common.setopts -var ownProp = common.ownProp -var childrenIgnored = common.childrenIgnored -var isIgnored = common.isIgnored + function toNow (withoutSuffix) { + return this.to(createLocal(), withoutSuffix); + } -function globSync (pattern, options) { - if (typeof options === 'function' || arguments.length === 3) - throw new TypeError('callback provided to sync glob\n'+ - 'See: https://github.com/isaacs/node-glob/issues/167') + // If passed a locale key, it will set the locale for this + // instance. Otherwise, it will return the locale configuration + // variables for this instance. + function locale (key) { + var newLocaleData; - return new GlobSync(pattern, options).found -} + if (key === undefined) { + return this._locale._abbr; + } else { + newLocaleData = getLocale(key); + if (newLocaleData != null) { + this._locale = newLocaleData; + } + return this; + } + } -function GlobSync (pattern, options) { - if (!pattern) - throw new Error('must provide pattern') + var lang = deprecate( + 'moment().lang() is deprecated. Instead, use moment().localeData() to get the language configuration. Use moment().locale() to change languages.', + function (key) { + if (key === undefined) { + return this.localeData(); + } else { + return this.locale(key); + } + } + ); - if (typeof options === 'function' || arguments.length === 3) - throw new TypeError('callback provided to sync glob\n'+ - 'See: https://github.com/isaacs/node-glob/issues/167') + function localeData () { + return this._locale; + } - if (!(this instanceof GlobSync)) - return new GlobSync(pattern, options) + var MS_PER_SECOND = 1000; + var MS_PER_MINUTE = 60 * MS_PER_SECOND; + var MS_PER_HOUR = 60 * MS_PER_MINUTE; + var MS_PER_400_YEARS = (365 * 400 + 97) * 24 * MS_PER_HOUR; - setopts(this, pattern, options) + // actual modulo - handles negative numbers (for dates before 1970): + function mod$1(dividend, divisor) { + return (dividend % divisor + divisor) % divisor; + } - if (this.noprocess) - return this + function localStartOfDate(y, m, d) { + // the date constructor remaps years 0-99 to 1900-1999 + if (y < 100 && y >= 0) { + // preserve leap years using a full 400 year cycle, then reset + return new Date(y + 400, m, d) - MS_PER_400_YEARS; + } else { + return new Date(y, m, d).valueOf(); + } + } - var n = this.minimatch.set.length - this.matches = new Array(n) - for (var i = 0; i < n; i ++) { - this._process(this.minimatch.set[i], i, false) - } - this._finish() -} + function utcStartOfDate(y, m, d) { + // Date.UTC remaps years 0-99 to 1900-1999 + if (y < 100 && y >= 0) { + // preserve leap years using a full 400 year cycle, then reset + return Date.UTC(y + 400, m, d) - MS_PER_400_YEARS; + } else { + return Date.UTC(y, m, d); + } + } -GlobSync.prototype._finish = function () { - assert(this instanceof GlobSync) - if (this.realpath) { - var self = this - this.matches.forEach(function (matchset, index) { - var set = self.matches[index] = Object.create(null) - for (var p in matchset) { - try { - p = self._makeAbs(p) - var real = rp.realpathSync(p, self.realpathCache) - set[real] = true - } catch (er) { - if (er.syscall === 'stat') - set[self._makeAbs(p)] = true - else - throw er + function startOf (units) { + var time; + units = normalizeUnits(units); + if (units === undefined || units === 'millisecond' || !this.isValid()) { + return this; } - } - }) - } - common.finish(this) -} + var startOfDate = this._isUTC ? utcStartOfDate : localStartOfDate; -GlobSync.prototype._process = function (pattern, index, inGlobStar) { - assert(this instanceof GlobSync) + switch (units) { + case 'year': + time = startOfDate(this.year(), 0, 1); + break; + case 'quarter': + time = startOfDate(this.year(), this.month() - this.month() % 3, 1); + break; + case 'month': + time = startOfDate(this.year(), this.month(), 1); + break; + case 'week': + time = startOfDate(this.year(), this.month(), this.date() - this.weekday()); + break; + case 'isoWeek': + time = startOfDate(this.year(), this.month(), this.date() - (this.isoWeekday() - 1)); + break; + case 'day': + case 'date': + time = startOfDate(this.year(), this.month(), this.date()); + break; + case 'hour': + time = this._d.valueOf(); + time -= mod$1(time + (this._isUTC ? 0 : this.utcOffset() * MS_PER_MINUTE), MS_PER_HOUR); + break; + case 'minute': + time = this._d.valueOf(); + time -= mod$1(time, MS_PER_MINUTE); + break; + case 'second': + time = this._d.valueOf(); + time -= mod$1(time, MS_PER_SECOND); + break; + } - // Get the first [n] parts of pattern that are all strings. - var n = 0 - while (typeof pattern[n] === 'string') { - n ++ - } - // now n is the index of the first one that is *not* a string. + this._d.setTime(time); + hooks.updateOffset(this, true); + return this; + } - // See if there's anything else - var prefix - switch (n) { - // if not, then this is rather simple - case pattern.length: - this._processSimple(pattern.join('/'), index) - return + function endOf (units) { + var time; + units = normalizeUnits(units); + if (units === undefined || units === 'millisecond' || !this.isValid()) { + return this; + } - case 0: - // pattern *starts* with some non-trivial item. - // going to readdir(cwd), but not include the prefix in matches. - prefix = null - break + var startOfDate = this._isUTC ? utcStartOfDate : localStartOfDate; - default: - // pattern has some string bits in the front. - // whatever it starts with, whether that's 'absolute' like /foo/bar, - // or 'relative' like '../baz' - prefix = pattern.slice(0, n).join('/') - break - } + switch (units) { + case 'year': + time = startOfDate(this.year() + 1, 0, 1) - 1; + break; + case 'quarter': + time = startOfDate(this.year(), this.month() - this.month() % 3 + 3, 1) - 1; + break; + case 'month': + time = startOfDate(this.year(), this.month() + 1, 1) - 1; + break; + case 'week': + time = startOfDate(this.year(), this.month(), this.date() - this.weekday() + 7) - 1; + break; + case 'isoWeek': + time = startOfDate(this.year(), this.month(), this.date() - (this.isoWeekday() - 1) + 7) - 1; + break; + case 'day': + case 'date': + time = startOfDate(this.year(), this.month(), this.date() + 1) - 1; + break; + case 'hour': + time = this._d.valueOf(); + time += MS_PER_HOUR - mod$1(time + (this._isUTC ? 0 : this.utcOffset() * MS_PER_MINUTE), MS_PER_HOUR) - 1; + break; + case 'minute': + time = this._d.valueOf(); + time += MS_PER_MINUTE - mod$1(time, MS_PER_MINUTE) - 1; + break; + case 'second': + time = this._d.valueOf(); + time += MS_PER_SECOND - mod$1(time, MS_PER_SECOND) - 1; + break; + } - var remain = pattern.slice(n) + this._d.setTime(time); + hooks.updateOffset(this, true); + return this; + } - // get the list of entries. - var read - if (prefix === null) - read = '.' - else if (isAbsolute(prefix) || isAbsolute(pattern.join('/'))) { - if (!prefix || !isAbsolute(prefix)) - prefix = '/' + prefix - read = prefix - } else - read = prefix + function valueOf () { + return this._d.valueOf() - ((this._offset || 0) * 60000); + } - var abs = this._makeAbs(read) + function unix () { + return Math.floor(this.valueOf() / 1000); + } - //if ignored, skip processing - if (childrenIgnored(this, read)) - return + function toDate () { + return new Date(this.valueOf()); + } - var isGlobStar = remain[0] === minimatch.GLOBSTAR - if (isGlobStar) - this._processGlobStar(prefix, read, abs, remain, index, inGlobStar) - else - this._processReaddir(prefix, read, abs, remain, index, inGlobStar) -} + function toArray () { + var m = this; + return [m.year(), m.month(), m.date(), m.hour(), m.minute(), m.second(), m.millisecond()]; + } + function toObject () { + var m = this; + return { + years: m.year(), + months: m.month(), + date: m.date(), + hours: m.hours(), + minutes: m.minutes(), + seconds: m.seconds(), + milliseconds: m.milliseconds() + }; + } -GlobSync.prototype._processReaddir = function (prefix, read, abs, remain, index, inGlobStar) { - var entries = this._readdir(abs, inGlobStar) + function toJSON () { + // new Date(NaN).toJSON() === null + return this.isValid() ? this.toISOString() : null; + } - // if the abs isn't a dir, then nothing can match! - if (!entries) - return + function isValid$2 () { + return isValid(this); + } - // It will only match dot entries if it starts with a dot, or if - // dot is set. Stuff like @(.foo|.bar) isn't allowed. - var pn = remain[0] - var negate = !!this.minimatch.negate - var rawGlob = pn._glob - var dotOk = this.dot || rawGlob.charAt(0) === '.' + function parsingFlags () { + return extend({}, getParsingFlags(this)); + } - var matchedEntries = [] - for (var i = 0; i < entries.length; i++) { - var e = entries[i] - if (e.charAt(0) !== '.' || dotOk) { - var m - if (negate && !prefix) { - m = !e.match(pn) - } else { - m = e.match(pn) - } - if (m) - matchedEntries.push(e) + function invalidAt () { + return getParsingFlags(this).overflow; } - } - var len = matchedEntries.length - // If there are no matched entries, then nothing matches. - if (len === 0) - return + function creationData() { + return { + input: this._i, + format: this._f, + locale: this._locale, + isUTC: this._isUTC, + strict: this._strict + }; + } - // if this is the last remaining pattern bit, then no need for - // an additional stat *unless* the user has specified mark or - // stat explicitly. We know they exist, since readdir returned - // them. + // FORMATTING - if (remain.length === 1 && !this.mark && !this.stat) { - if (!this.matches[index]) - this.matches[index] = Object.create(null) + addFormatToken(0, ['gg', 2], 0, function () { + return this.weekYear() % 100; + }); - for (var i = 0; i < len; i ++) { - var e = matchedEntries[i] - if (prefix) { - if (prefix.slice(-1) !== '/') - e = prefix + '/' + e - else - e = prefix + e - } + addFormatToken(0, ['GG', 2], 0, function () { + return this.isoWeekYear() % 100; + }); - if (e.charAt(0) === '/' && !this.nomount) { - e = path.join(this.root, e) - } - this._emitMatch(index, e) + function addWeekYearFormatToken (token, getter) { + addFormatToken(0, [token, token.length], 0, getter); } - // This was the last one, and no stats were needed - return - } - // now test all matched entries as stand-ins for that part - // of the pattern. - remain.shift() - for (var i = 0; i < len; i ++) { - var e = matchedEntries[i] - var newPattern - if (prefix) - newPattern = [prefix, e] - else - newPattern = [e] - this._process(newPattern.concat(remain), index, inGlobStar) - } -} + addWeekYearFormatToken('gggg', 'weekYear'); + addWeekYearFormatToken('ggggg', 'weekYear'); + addWeekYearFormatToken('GGGG', 'isoWeekYear'); + addWeekYearFormatToken('GGGGG', 'isoWeekYear'); + // ALIASES -GlobSync.prototype._emitMatch = function (index, e) { - if (isIgnored(this, e)) - return + addUnitAlias('weekYear', 'gg'); + addUnitAlias('isoWeekYear', 'GG'); - var abs = this._makeAbs(e) + // PRIORITY - if (this.mark) - e = this._mark(e) + addUnitPriority('weekYear', 1); + addUnitPriority('isoWeekYear', 1); - if (this.absolute) { - e = abs - } - if (this.matches[index][e]) - return + // PARSING - if (this.nodir) { - var c = this.cache[abs] - if (c === 'DIR' || Array.isArray(c)) - return - } + addRegexToken('G', matchSigned); + addRegexToken('g', matchSigned); + addRegexToken('GG', match1to2, match2); + addRegexToken('gg', match1to2, match2); + addRegexToken('GGGG', match1to4, match4); + addRegexToken('gggg', match1to4, match4); + addRegexToken('GGGGG', match1to6, match6); + addRegexToken('ggggg', match1to6, match6); - this.matches[index][e] = true + addWeekParseToken(['gggg', 'ggggg', 'GGGG', 'GGGGG'], function (input, week, config, token) { + week[token.substr(0, 2)] = toInt(input); + }); - if (this.stat) - this._stat(e) -} + addWeekParseToken(['gg', 'GG'], function (input, week, config, token) { + week[token] = hooks.parseTwoDigitYear(input); + }); + // MOMENTS -GlobSync.prototype._readdirInGlobStar = function (abs) { - // follow all symlinked directories forever - // just proceed as if this is a non-globstar situation - if (this.follow) - return this._readdir(abs, false) + function getSetWeekYear (input) { + return getSetWeekYearHelper.call(this, + input, + this.week(), + this.weekday(), + this.localeData()._week.dow, + this.localeData()._week.doy); + } - var entries - var lstat - var stat - try { - lstat = fs.lstatSync(abs) - } catch (er) { - if (er.code === 'ENOENT') { - // lstat failed, doesn't exist - return null + function getSetISOWeekYear (input) { + return getSetWeekYearHelper.call(this, + input, this.isoWeek(), this.isoWeekday(), 1, 4); } - } - var isSym = lstat && lstat.isSymbolicLink() - this.symlinks[abs] = isSym + function getISOWeeksInYear () { + return weeksInYear(this.year(), 1, 4); + } - // If it's not a symlink or a dir, then it's definitely a regular file. - // don't bother doing a readdir in that case. - if (!isSym && lstat && !lstat.isDirectory()) - this.cache[abs] = 'FILE' - else - entries = this._readdir(abs, false) + function getWeeksInYear () { + var weekInfo = this.localeData()._week; + return weeksInYear(this.year(), weekInfo.dow, weekInfo.doy); + } - return entries -} + function getSetWeekYearHelper(input, week, weekday, dow, doy) { + var weeksTarget; + if (input == null) { + return weekOfYear(this, dow, doy).year; + } else { + weeksTarget = weeksInYear(input, dow, doy); + if (week > weeksTarget) { + week = weeksTarget; + } + return setWeekAll.call(this, input, week, weekday, dow, doy); + } + } -GlobSync.prototype._readdir = function (abs, inGlobStar) { - var entries + function setWeekAll(weekYear, week, weekday, dow, doy) { + var dayOfYearData = dayOfYearFromWeeks(weekYear, week, weekday, dow, doy), + date = createUTCDate(dayOfYearData.year, 0, dayOfYearData.dayOfYear); - if (inGlobStar && !ownProp(this.symlinks, abs)) - return this._readdirInGlobStar(abs) + this.year(date.getUTCFullYear()); + this.month(date.getUTCMonth()); + this.date(date.getUTCDate()); + return this; + } - if (ownProp(this.cache, abs)) { - var c = this.cache[abs] - if (!c || c === 'FILE') - return null + // FORMATTING - if (Array.isArray(c)) - return c - } + addFormatToken('Q', 0, 'Qo', 'quarter'); - try { - return this._readdirEntries(abs, fs.readdirSync(abs)) - } catch (er) { - this._readdirError(abs, er) - return null - } -} + // ALIASES -GlobSync.prototype._readdirEntries = function (abs, entries) { - // if we haven't asked to stat everything, then just - // assume that everything in there exists, so we can avoid - // having to stat it a second time. - if (!this.mark && !this.stat) { - for (var i = 0; i < entries.length; i ++) { - var e = entries[i] - if (abs === '/') - e = abs + e - else - e = abs + '/' + e - this.cache[e] = true - } - } + addUnitAlias('quarter', 'Q'); - this.cache[abs] = entries + // PRIORITY - // mark and cache dir-ness - return entries -} + addUnitPriority('quarter', 7); -GlobSync.prototype._readdirError = function (f, er) { - // handle errors, and cache the information - switch (er.code) { - case 'ENOTSUP': // https://github.com/isaacs/node-glob/issues/205 - case 'ENOTDIR': // totally normal. means it *does* exist. - var abs = this._makeAbs(f) - this.cache[abs] = 'FILE' - if (abs === this.cwdAbs) { - var error = new Error(er.code + ' invalid cwd ' + this.cwd) - error.path = this.cwd - error.code = er.code - throw error - } - break + // PARSING - case 'ENOENT': // not terribly unusual - case 'ELOOP': - case 'ENAMETOOLONG': - case 'UNKNOWN': - this.cache[this._makeAbs(f)] = false - break - - default: // some unusual error. Treat as failure. - this.cache[this._makeAbs(f)] = false - if (this.strict) - throw er - if (!this.silent) - console.error('glob error', er) - break - } -} - -GlobSync.prototype._processGlobStar = function (prefix, read, abs, remain, index, inGlobStar) { - - var entries = this._readdir(abs, inGlobStar) + addRegexToken('Q', match1); + addParseToken('Q', function (input, array) { + array[MONTH] = (toInt(input) - 1) * 3; + }); - // no entries means not a dir, so it can never have matches - // foo.txt/** doesn't match foo.txt - if (!entries) - return + // MOMENTS - // test without the globstar, and with every child both below - // and replacing the globstar. - var remainWithoutGlobStar = remain.slice(1) - var gspref = prefix ? [ prefix ] : [] - var noGlobStar = gspref.concat(remainWithoutGlobStar) + function getSetQuarter (input) { + return input == null ? Math.ceil((this.month() + 1) / 3) : this.month((input - 1) * 3 + this.month() % 3); + } - // the noGlobStar pattern exits the inGlobStar state - this._process(noGlobStar, index, false) + // FORMATTING - var len = entries.length - var isSym = this.symlinks[abs] + addFormatToken('D', ['DD', 2], 'Do', 'date'); - // If it's a symlink, and we're in a globstar, then stop - if (isSym && inGlobStar) - return + // ALIASES - for (var i = 0; i < len; i++) { - var e = entries[i] - if (e.charAt(0) === '.' && !this.dot) - continue + addUnitAlias('date', 'D'); - // these two cases enter the inGlobStar state - var instead = gspref.concat(entries[i], remainWithoutGlobStar) - this._process(instead, index, true) + // PRIORITY + addUnitPriority('date', 9); - var below = gspref.concat(entries[i], remain) - this._process(below, index, true) - } -} + // PARSING -GlobSync.prototype._processSimple = function (prefix, index) { - // XXX review this. Shouldn't it be doing the mounting etc - // before doing stat? kinda weird? - var exists = this._stat(prefix) + addRegexToken('D', match1to2); + addRegexToken('DD', match1to2, match2); + addRegexToken('Do', function (isStrict, locale) { + // TODO: Remove "ordinalParse" fallback in next major release. + return isStrict ? + (locale._dayOfMonthOrdinalParse || locale._ordinalParse) : + locale._dayOfMonthOrdinalParseLenient; + }); - if (!this.matches[index]) - this.matches[index] = Object.create(null) + addParseToken(['D', 'DD'], DATE); + addParseToken('Do', function (input, array) { + array[DATE] = toInt(input.match(match1to2)[0]); + }); - // If it doesn't exist, then just mark the lack of results - if (!exists) - return + // MOMENTS - if (prefix && isAbsolute(prefix) && !this.nomount) { - var trail = /[\/\\]$/.test(prefix) - if (prefix.charAt(0) === '/') { - prefix = path.join(this.root, prefix) - } else { - prefix = path.resolve(this.root, prefix) - if (trail) - prefix += '/' - } - } + var getSetDayOfMonth = makeGetSet('Date', true); - if (process.platform === 'win32') - prefix = prefix.replace(/\\/g, '/') + // FORMATTING - // Mark this as a match - this._emitMatch(index, prefix) -} + addFormatToken('DDD', ['DDDD', 3], 'DDDo', 'dayOfYear'); -// Returns either 'DIR', 'FILE', or false -GlobSync.prototype._stat = function (f) { - var abs = this._makeAbs(f) - var needDir = f.slice(-1) === '/' + // ALIASES - if (f.length > this.maxLength) - return false + addUnitAlias('dayOfYear', 'DDD'); - if (!this.stat && ownProp(this.cache, abs)) { - var c = this.cache[abs] + // PRIORITY + addUnitPriority('dayOfYear', 4); - if (Array.isArray(c)) - c = 'DIR' + // PARSING - // It exists, but maybe not how we need it - if (!needDir || c === 'DIR') - return c + addRegexToken('DDD', match1to3); + addRegexToken('DDDD', match3); + addParseToken(['DDD', 'DDDD'], function (input, array, config) { + config._dayOfYear = toInt(input); + }); - if (needDir && c === 'FILE') - return false + // HELPERS - // otherwise we have to stat, because maybe c=true - // if we know it exists, but not what it is. - } + // MOMENTS - var exists - var stat = this.statCache[abs] - if (!stat) { - var lstat - try { - lstat = fs.lstatSync(abs) - } catch (er) { - if (er && (er.code === 'ENOENT' || er.code === 'ENOTDIR')) { - this.statCache[abs] = false - return false - } + function getSetDayOfYear (input) { + var dayOfYear = Math.round((this.clone().startOf('day') - this.clone().startOf('year')) / 864e5) + 1; + return input == null ? dayOfYear : this.add((input - dayOfYear), 'd'); } - if (lstat && lstat.isSymbolicLink()) { - try { - stat = fs.statSync(abs) - } catch (er) { - stat = lstat - } - } else { - stat = lstat - } - } + // FORMATTING - this.statCache[abs] = stat + addFormatToken('m', ['mm', 2], 0, 'minute'); - var c = true - if (stat) - c = stat.isDirectory() ? 'DIR' : 'FILE' + // ALIASES - this.cache[abs] = this.cache[abs] || c + addUnitAlias('minute', 'm'); - if (needDir && c === 'FILE') - return false + // PRIORITY - return c -} + addUnitPriority('minute', 14); -GlobSync.prototype._mark = function (p) { - return common.mark(this, p) -} + // PARSING -GlobSync.prototype._makeAbs = function (f) { - return common.makeAbs(this, f) -} + addRegexToken('m', match1to2); + addRegexToken('mm', match1to2, match2); + addParseToken(['m', 'mm'], MINUTE); + // MOMENTS -/***/ }), -/* 49 */ -/***/ (function(module, exports, __webpack_require__) { + var getSetMinute = makeGetSet('Minutes', false); -exports.alphasort = alphasort -exports.alphasorti = alphasorti -exports.setopts = setopts -exports.ownProp = ownProp -exports.makeAbs = makeAbs -exports.finish = finish -exports.mark = mark -exports.isIgnored = isIgnored -exports.childrenIgnored = childrenIgnored + // FORMATTING -function ownProp (obj, field) { - return Object.prototype.hasOwnProperty.call(obj, field) -} + addFormatToken('s', ['ss', 2], 0, 'second'); -var path = __webpack_require__(16) -var minimatch = __webpack_require__(40) -var isAbsolute = __webpack_require__(47) -var Minimatch = minimatch.Minimatch + // ALIASES -function alphasorti (a, b) { - return a.toLowerCase().localeCompare(b.toLowerCase()) -} + addUnitAlias('second', 's'); -function alphasort (a, b) { - return a.localeCompare(b) -} + // PRIORITY -function setupIgnores (self, options) { - self.ignore = options.ignore || [] + addUnitPriority('second', 15); - if (!Array.isArray(self.ignore)) - self.ignore = [self.ignore] + // PARSING - if (self.ignore.length) { - self.ignore = self.ignore.map(ignoreMap) - } -} + addRegexToken('s', match1to2); + addRegexToken('ss', match1to2, match2); + addParseToken(['s', 'ss'], SECOND); -// ignore patterns are always in dot:true mode. -function ignoreMap (pattern) { - var gmatcher = null - if (pattern.slice(-3) === '/**') { - var gpattern = pattern.replace(/(\/\*\*)+$/, '') - gmatcher = new Minimatch(gpattern, { dot: true }) - } + // MOMENTS - return { - matcher: new Minimatch(pattern, { dot: true }), - gmatcher: gmatcher - } -} + var getSetSecond = makeGetSet('Seconds', false); -function setopts (self, pattern, options) { - if (!options) - options = {} + // FORMATTING - // base-matching: just use globstar for that. - if (options.matchBase && -1 === pattern.indexOf("/")) { - if (options.noglobstar) { - throw new Error("base matching requires globstar") - } - pattern = "**/" + pattern - } + addFormatToken('S', 0, 0, function () { + return ~~(this.millisecond() / 100); + }); - self.silent = !!options.silent - self.pattern = pattern - self.strict = options.strict !== false - self.realpath = !!options.realpath - self.realpathCache = options.realpathCache || Object.create(null) - self.follow = !!options.follow - self.dot = !!options.dot - self.mark = !!options.mark - self.nodir = !!options.nodir - if (self.nodir) - self.mark = true - self.sync = !!options.sync - self.nounique = !!options.nounique - self.nonull = !!options.nonull - self.nosort = !!options.nosort - self.nocase = !!options.nocase - self.stat = !!options.stat - self.noprocess = !!options.noprocess - self.absolute = !!options.absolute + addFormatToken(0, ['SS', 2], 0, function () { + return ~~(this.millisecond() / 10); + }); - self.maxLength = options.maxLength || Infinity - self.cache = options.cache || Object.create(null) - self.statCache = options.statCache || Object.create(null) - self.symlinks = options.symlinks || Object.create(null) + addFormatToken(0, ['SSS', 3], 0, 'millisecond'); + addFormatToken(0, ['SSSS', 4], 0, function () { + return this.millisecond() * 10; + }); + addFormatToken(0, ['SSSSS', 5], 0, function () { + return this.millisecond() * 100; + }); + addFormatToken(0, ['SSSSSS', 6], 0, function () { + return this.millisecond() * 1000; + }); + addFormatToken(0, ['SSSSSSS', 7], 0, function () { + return this.millisecond() * 10000; + }); + addFormatToken(0, ['SSSSSSSS', 8], 0, function () { + return this.millisecond() * 100000; + }); + addFormatToken(0, ['SSSSSSSSS', 9], 0, function () { + return this.millisecond() * 1000000; + }); - setupIgnores(self, options) - self.changedCwd = false - var cwd = process.cwd() - if (!ownProp(options, "cwd")) - self.cwd = cwd - else { - self.cwd = path.resolve(options.cwd) - self.changedCwd = self.cwd !== cwd - } + // ALIASES + + addUnitAlias('millisecond', 'ms'); + + // PRIORITY + + addUnitPriority('millisecond', 16); + + // PARSING + + addRegexToken('S', match1to3, match1); + addRegexToken('SS', match1to3, match2); + addRegexToken('SSS', match1to3, match3); + + var token; + for (token = 'SSSS'; token.length <= 9; token += 'S') { + addRegexToken(token, matchUnsigned); + } + + function parseMs(input, array) { + array[MILLISECOND] = toInt(('0.' + input) * 1000); + } + + for (token = 'S'; token.length <= 9; token += 'S') { + addParseToken(token, parseMs); + } + // MOMENTS + + var getSetMillisecond = makeGetSet('Milliseconds', false); + + // FORMATTING + + addFormatToken('z', 0, 0, 'zoneAbbr'); + addFormatToken('zz', 0, 0, 'zoneName'); + + // MOMENTS + + function getZoneAbbr () { + return this._isUTC ? 'UTC' : ''; + } + + function getZoneName () { + return this._isUTC ? 'Coordinated Universal Time' : ''; + } + + var proto = Moment.prototype; + + proto.add = add; + proto.calendar = calendar$1; + proto.clone = clone; + proto.diff = diff; + proto.endOf = endOf; + proto.format = format; + proto.from = from; + proto.fromNow = fromNow; + proto.to = to; + proto.toNow = toNow; + proto.get = stringGet; + proto.invalidAt = invalidAt; + proto.isAfter = isAfter; + proto.isBefore = isBefore; + proto.isBetween = isBetween; + proto.isSame = isSame; + proto.isSameOrAfter = isSameOrAfter; + proto.isSameOrBefore = isSameOrBefore; + proto.isValid = isValid$2; + proto.lang = lang; + proto.locale = locale; + proto.localeData = localeData; + proto.max = prototypeMax; + proto.min = prototypeMin; + proto.parsingFlags = parsingFlags; + proto.set = stringSet; + proto.startOf = startOf; + proto.subtract = subtract; + proto.toArray = toArray; + proto.toObject = toObject; + proto.toDate = toDate; + proto.toISOString = toISOString; + proto.inspect = inspect; + proto.toJSON = toJSON; + proto.toString = toString; + proto.unix = unix; + proto.valueOf = valueOf; + proto.creationData = creationData; + proto.year = getSetYear; + proto.isLeapYear = getIsLeapYear; + proto.weekYear = getSetWeekYear; + proto.isoWeekYear = getSetISOWeekYear; + proto.quarter = proto.quarters = getSetQuarter; + proto.month = getSetMonth; + proto.daysInMonth = getDaysInMonth; + proto.week = proto.weeks = getSetWeek; + proto.isoWeek = proto.isoWeeks = getSetISOWeek; + proto.weeksInYear = getWeeksInYear; + proto.isoWeeksInYear = getISOWeeksInYear; + proto.date = getSetDayOfMonth; + proto.day = proto.days = getSetDayOfWeek; + proto.weekday = getSetLocaleDayOfWeek; + proto.isoWeekday = getSetISODayOfWeek; + proto.dayOfYear = getSetDayOfYear; + proto.hour = proto.hours = getSetHour; + proto.minute = proto.minutes = getSetMinute; + proto.second = proto.seconds = getSetSecond; + proto.millisecond = proto.milliseconds = getSetMillisecond; + proto.utcOffset = getSetOffset; + proto.utc = setOffsetToUTC; + proto.local = setOffsetToLocal; + proto.parseZone = setOffsetToParsedOffset; + proto.hasAlignedHourOffset = hasAlignedHourOffset; + proto.isDST = isDaylightSavingTime; + proto.isLocal = isLocal; + proto.isUtcOffset = isUtcOffset; + proto.isUtc = isUtc; + proto.isUTC = isUtc; + proto.zoneAbbr = getZoneAbbr; + proto.zoneName = getZoneName; + proto.dates = deprecate('dates accessor is deprecated. Use date instead.', getSetDayOfMonth); + proto.months = deprecate('months accessor is deprecated. Use month instead', getSetMonth); + proto.years = deprecate('years accessor is deprecated. Use year instead', getSetYear); + proto.zone = deprecate('moment().zone is deprecated, use moment().utcOffset instead. http://momentjs.com/guides/#/warnings/zone/', getSetZone); + proto.isDSTShifted = deprecate('isDSTShifted is deprecated. See http://momentjs.com/guides/#/warnings/dst-shifted/ for more information', isDaylightSavingTimeShifted); + + function createUnix (input) { + return createLocal(input * 1000); + } + + function createInZone () { + return createLocal.apply(null, arguments).parseZone(); + } + + function preParsePostFormat (string) { + return string; + } + + var proto$1 = Locale.prototype; + + proto$1.calendar = calendar; + proto$1.longDateFormat = longDateFormat; + proto$1.invalidDate = invalidDate; + proto$1.ordinal = ordinal; + proto$1.preparse = preParsePostFormat; + proto$1.postformat = preParsePostFormat; + proto$1.relativeTime = relativeTime; + proto$1.pastFuture = pastFuture; + proto$1.set = set; + + proto$1.months = localeMonths; + proto$1.monthsShort = localeMonthsShort; + proto$1.monthsParse = localeMonthsParse; + proto$1.monthsRegex = monthsRegex; + proto$1.monthsShortRegex = monthsShortRegex; + proto$1.week = localeWeek; + proto$1.firstDayOfYear = localeFirstDayOfYear; + proto$1.firstDayOfWeek = localeFirstDayOfWeek; + + proto$1.weekdays = localeWeekdays; + proto$1.weekdaysMin = localeWeekdaysMin; + proto$1.weekdaysShort = localeWeekdaysShort; + proto$1.weekdaysParse = localeWeekdaysParse; + + proto$1.weekdaysRegex = weekdaysRegex; + proto$1.weekdaysShortRegex = weekdaysShortRegex; + proto$1.weekdaysMinRegex = weekdaysMinRegex; + + proto$1.isPM = localeIsPM; + proto$1.meridiem = localeMeridiem; + + function get$1 (format, index, field, setter) { + var locale = getLocale(); + var utc = createUTC().set(setter, index); + return locale[field](utc, format); + } + + function listMonthsImpl (format, index, field) { + if (isNumber(format)) { + index = format; + format = undefined; + } + + format = format || ''; + + if (index != null) { + return get$1(format, index, field, 'month'); + } + + var i; + var out = []; + for (i = 0; i < 12; i++) { + out[i] = get$1(format, i, field, 'month'); + } + return out; + } + + // () + // (5) + // (fmt, 5) + // (fmt) + // (true) + // (true, 5) + // (true, fmt, 5) + // (true, fmt) + function listWeekdaysImpl (localeSorted, format, index, field) { + if (typeof localeSorted === 'boolean') { + if (isNumber(format)) { + index = format; + format = undefined; + } - self.root = options.root || path.resolve(self.cwd, "/") - self.root = path.resolve(self.root) - if (process.platform === "win32") - self.root = self.root.replace(/\\/g, "/") + format = format || ''; + } else { + format = localeSorted; + index = format; + localeSorted = false; - // TODO: is an absolute `cwd` supposed to be resolved against `root`? - // e.g. { cwd: '/test', root: __dirname } === path.join(__dirname, '/test') - self.cwdAbs = isAbsolute(self.cwd) ? self.cwd : makeAbs(self, self.cwd) - if (process.platform === "win32") - self.cwdAbs = self.cwdAbs.replace(/\\/g, "/") - self.nomount = !!options.nomount + if (isNumber(format)) { + index = format; + format = undefined; + } - // disable comments and negation in Minimatch. - // Note that they are not supported in Glob itself anyway. - options.nonegate = true - options.nocomment = true + format = format || ''; + } - self.minimatch = new Minimatch(pattern, options) - self.options = self.minimatch.options -} + var locale = getLocale(), + shift = localeSorted ? locale._week.dow : 0; -function finish (self) { - var nou = self.nounique - var all = nou ? [] : Object.create(null) + if (index != null) { + return get$1(format, (index + shift) % 7, field, 'day'); + } - for (var i = 0, l = self.matches.length; i < l; i ++) { - var matches = self.matches[i] - if (!matches || Object.keys(matches).length === 0) { - if (self.nonull) { - // do like the shell, and spit out the literal glob - var literal = self.minimatch.globSet[i] - if (nou) - all.push(literal) - else - all[literal] = true - } - } else { - // had matches - var m = Object.keys(matches) - if (nou) - all.push.apply(all, m) - else - m.forEach(function (m) { - all[m] = true - }) + var i; + var out = []; + for (i = 0; i < 7; i++) { + out[i] = get$1(format, (i + shift) % 7, field, 'day'); + } + return out; } - } - if (!nou) - all = Object.keys(all) + function listMonths (format, index) { + return listMonthsImpl(format, index, 'months'); + } - if (!self.nosort) - all = all.sort(self.nocase ? alphasorti : alphasort) + function listMonthsShort (format, index) { + return listMonthsImpl(format, index, 'monthsShort'); + } - // at *some* point we statted all of these - if (self.mark) { - for (var i = 0; i < all.length; i++) { - all[i] = self._mark(all[i]) + function listWeekdays (localeSorted, format, index) { + return listWeekdaysImpl(localeSorted, format, index, 'weekdays'); } - if (self.nodir) { - all = all.filter(function (e) { - var notDir = !(/\/$/.test(e)) - var c = self.cache[e] || self.cache[makeAbs(self, e)] - if (notDir && c) - notDir = c !== 'DIR' && !Array.isArray(c) - return notDir - }) + + function listWeekdaysShort (localeSorted, format, index) { + return listWeekdaysImpl(localeSorted, format, index, 'weekdaysShort'); } - } - if (self.ignore.length) - all = all.filter(function(m) { - return !isIgnored(self, m) - }) + function listWeekdaysMin (localeSorted, format, index) { + return listWeekdaysImpl(localeSorted, format, index, 'weekdaysMin'); + } - self.found = all -} + getSetGlobalLocale('en', { + dayOfMonthOrdinalParse: /\d{1,2}(th|st|nd|rd)/, + ordinal : function (number) { + var b = number % 10, + output = (toInt(number % 100 / 10) === 1) ? 'th' : + (b === 1) ? 'st' : + (b === 2) ? 'nd' : + (b === 3) ? 'rd' : 'th'; + return number + output; + } + }); -function mark (self, p) { - var abs = makeAbs(self, p) - var c = self.cache[abs] - var m = p - if (c) { - var isDir = c === 'DIR' || Array.isArray(c) - var slash = p.slice(-1) === '/' + // Side effect imports - if (isDir && !slash) - m += '/' - else if (!isDir && slash) - m = m.slice(0, -1) + hooks.lang = deprecate('moment.lang is deprecated. Use moment.locale instead.', getSetGlobalLocale); + hooks.langData = deprecate('moment.langData is deprecated. Use moment.localeData instead.', getLocale); - if (m !== p) { - var mabs = makeAbs(self, m) - self.statCache[mabs] = self.statCache[abs] - self.cache[mabs] = self.cache[abs] + var mathAbs = Math.abs; + + function abs () { + var data = this._data; + + this._milliseconds = mathAbs(this._milliseconds); + this._days = mathAbs(this._days); + this._months = mathAbs(this._months); + + data.milliseconds = mathAbs(data.milliseconds); + data.seconds = mathAbs(data.seconds); + data.minutes = mathAbs(data.minutes); + data.hours = mathAbs(data.hours); + data.months = mathAbs(data.months); + data.years = mathAbs(data.years); + + return this; } - } - return m -} + function addSubtract$1 (duration, input, value, direction) { + var other = createDuration(input, value); -// lotta situps... -function makeAbs (self, f) { - var abs = f - if (f.charAt(0) === '/') { - abs = path.join(self.root, f) - } else if (isAbsolute(f) || f === '') { - abs = f - } else if (self.changedCwd) { - abs = path.resolve(self.cwd, f) - } else { - abs = path.resolve(f) - } + duration._milliseconds += direction * other._milliseconds; + duration._days += direction * other._days; + duration._months += direction * other._months; - if (process.platform === 'win32') - abs = abs.replace(/\\/g, '/') + return duration._bubble(); + } - return abs -} + // supports only 2.0-style add(1, 's') or add(duration) + function add$1 (input, value) { + return addSubtract$1(this, input, value, 1); + } + // supports only 2.0-style subtract(1, 's') or subtract(duration) + function subtract$1 (input, value) { + return addSubtract$1(this, input, value, -1); + } -// Return true, if pattern ends with globstar '**', for the accompanying parent directory. -// Ex:- If node_modules/** is the pattern, add 'node_modules' to ignore list along with it's contents -function isIgnored (self, path) { - if (!self.ignore.length) - return false + function absCeil (number) { + if (number < 0) { + return Math.floor(number); + } else { + return Math.ceil(number); + } + } - return self.ignore.some(function(item) { - return item.matcher.match(path) || !!(item.gmatcher && item.gmatcher.match(path)) - }) -} + function bubble () { + var milliseconds = this._milliseconds; + var days = this._days; + var months = this._months; + var data = this._data; + var seconds, minutes, hours, years, monthsFromDays; -function childrenIgnored (self, path) { - if (!self.ignore.length) - return false + // if we have a mix of positive and negative values, bubble down first + // check: https://github.com/moment/moment/issues/2166 + if (!((milliseconds >= 0 && days >= 0 && months >= 0) || + (milliseconds <= 0 && days <= 0 && months <= 0))) { + milliseconds += absCeil(monthsToDays(months) + days) * 864e5; + days = 0; + months = 0; + } - return self.ignore.some(function(item) { - return !!(item.gmatcher && item.gmatcher.match(path)) - }) -} + // The following code bubbles up values, see the tests for + // examples of what that means. + data.milliseconds = milliseconds % 1000; + seconds = absFloor(milliseconds / 1000); + data.seconds = seconds % 60; -/***/ }), -/* 50 */ -/***/ (function(module, exports, __webpack_require__) { + minutes = absFloor(seconds / 60); + data.minutes = minutes % 60; -var wrappy = __webpack_require__(51) -var reqs = Object.create(null) -var once = __webpack_require__(52) + hours = absFloor(minutes / 60); + data.hours = hours % 24; -module.exports = wrappy(inflight) + days += absFloor(hours / 24); -function inflight (key, cb) { - if (reqs[key]) { - reqs[key].push(cb) - return null - } else { - reqs[key] = [cb] - return makeres(key) - } -} + // convert days to months + monthsFromDays = absFloor(daysToMonths(days)); + months += monthsFromDays; + days -= absCeil(monthsToDays(monthsFromDays)); -function makeres (key) { - return once(function RES () { - var cbs = reqs[key] - var len = cbs.length - var args = slice(arguments) + // 12 months -> 1 year + years = absFloor(months / 12); + months %= 12; - // XXX It's somewhat ambiguous whether a new callback added in this - // pass should be queued for later execution if something in the - // list of callbacks throws, or if it should just be discarded. - // However, it's such an edge case that it hardly matters, and either - // choice is likely as surprising as the other. - // As it happens, we do go ahead and schedule it for later execution. - try { - for (var i = 0; i < len; i++) { - cbs[i].apply(null, args) - } - } finally { - if (cbs.length > len) { - // added more in the interim. - // de-zalgo, just in case, but don't call again. - cbs.splice(0, len) - process.nextTick(function () { - RES.apply(null, args) - }) - } else { - delete reqs[key] - } + data.days = days; + data.months = months; + data.years = years; + + return this; } - }) -} -function slice (args) { - var length = args.length - var array = [] + function daysToMonths (days) { + // 400 years have 146097 days (taking into account leap year rules) + // 400 years have 12 months === 4800 + return days * 4800 / 146097; + } - for (var i = 0; i < length; i++) array[i] = args[i] - return array -} + function monthsToDays (months) { + // the reverse of daysToMonths + return months * 146097 / 4800; + } + function as (units) { + if (!this.isValid()) { + return NaN; + } + var days; + var months; + var milliseconds = this._milliseconds; -/***/ }), -/* 51 */ -/***/ (function(module, exports) { + units = normalizeUnits(units); -// Returns a wrapper function that returns a wrapped callback -// The wrapper function should do some stuff, and return a -// presumably different callback function. -// This makes sure that own properties are retained, so that -// decorations and such are not lost along the way. -module.exports = wrappy -function wrappy (fn, cb) { - if (fn && cb) return wrappy(fn)(cb) + if (units === 'month' || units === 'quarter' || units === 'year') { + days = this._days + milliseconds / 864e5; + months = this._months + daysToMonths(days); + switch (units) { + case 'month': return months; + case 'quarter': return months / 3; + case 'year': return months / 12; + } + } else { + // handle milliseconds separately because of floating point math errors (issue #1867) + days = this._days + Math.round(monthsToDays(this._months)); + switch (units) { + case 'week' : return days / 7 + milliseconds / 6048e5; + case 'day' : return days + milliseconds / 864e5; + case 'hour' : return days * 24 + milliseconds / 36e5; + case 'minute' : return days * 1440 + milliseconds / 6e4; + case 'second' : return days * 86400 + milliseconds / 1000; + // Math.floor prevents floating point math errors here + case 'millisecond': return Math.floor(days * 864e5) + milliseconds; + default: throw new Error('Unknown unit ' + units); + } + } + } - if (typeof fn !== 'function') - throw new TypeError('need wrapper function') + // TODO: Use this.as('ms')? + function valueOf$1 () { + if (!this.isValid()) { + return NaN; + } + return ( + this._milliseconds + + this._days * 864e5 + + (this._months % 12) * 2592e6 + + toInt(this._months / 12) * 31536e6 + ); + } - Object.keys(fn).forEach(function (k) { - wrapper[k] = fn[k] - }) + function makeAs (alias) { + return function () { + return this.as(alias); + }; + } - return wrapper + var asMilliseconds = makeAs('ms'); + var asSeconds = makeAs('s'); + var asMinutes = makeAs('m'); + var asHours = makeAs('h'); + var asDays = makeAs('d'); + var asWeeks = makeAs('w'); + var asMonths = makeAs('M'); + var asQuarters = makeAs('Q'); + var asYears = makeAs('y'); - function wrapper() { - var args = new Array(arguments.length) - for (var i = 0; i < args.length; i++) { - args[i] = arguments[i] + function clone$1 () { + return createDuration(this); } - var ret = fn.apply(this, args) - var cb = args[args.length-1] - if (typeof ret === 'function' && ret !== cb) { - Object.keys(cb).forEach(function (k) { - ret[k] = cb[k] - }) + + function get$2 (units) { + units = normalizeUnits(units); + return this.isValid() ? this[units + 's']() : NaN; + } + + function makeGetter(name) { + return function () { + return this.isValid() ? this._data[name] : NaN; + }; + } + + var milliseconds = makeGetter('milliseconds'); + var seconds = makeGetter('seconds'); + var minutes = makeGetter('minutes'); + var hours = makeGetter('hours'); + var days = makeGetter('days'); + var months = makeGetter('months'); + var years = makeGetter('years'); + + function weeks () { + return absFloor(this.days() / 7); + } + + var round = Math.round; + var thresholds = { + ss: 44, // a few seconds to seconds + s : 45, // seconds to minute + m : 45, // minutes to hour + h : 22, // hours to day + d : 26, // days to month + M : 11 // months to year + }; + + // helper function for moment.fn.from, moment.fn.fromNow, and moment.duration.fn.humanize + function substituteTimeAgo(string, number, withoutSuffix, isFuture, locale) { + return locale.relativeTime(number || 1, !!withoutSuffix, string, isFuture); + } + + function relativeTime$1 (posNegDuration, withoutSuffix, locale) { + var duration = createDuration(posNegDuration).abs(); + var seconds = round(duration.as('s')); + var minutes = round(duration.as('m')); + var hours = round(duration.as('h')); + var days = round(duration.as('d')); + var months = round(duration.as('M')); + var years = round(duration.as('y')); + + var a = seconds <= thresholds.ss && ['s', seconds] || + seconds < thresholds.s && ['ss', seconds] || + minutes <= 1 && ['m'] || + minutes < thresholds.m && ['mm', minutes] || + hours <= 1 && ['h'] || + hours < thresholds.h && ['hh', hours] || + days <= 1 && ['d'] || + days < thresholds.d && ['dd', days] || + months <= 1 && ['M'] || + months < thresholds.M && ['MM', months] || + years <= 1 && ['y'] || ['yy', years]; + + a[2] = withoutSuffix; + a[3] = +posNegDuration > 0; + a[4] = locale; + return substituteTimeAgo.apply(null, a); + } + + // This function allows you to set the rounding function for relative time strings + function getSetRelativeTimeRounding (roundingFunction) { + if (roundingFunction === undefined) { + return round; + } + if (typeof(roundingFunction) === 'function') { + round = roundingFunction; + return true; + } + return false; } - return ret - } -} + // This function allows you to set a threshold for relative time strings + function getSetRelativeTimeThreshold (threshold, limit) { + if (thresholds[threshold] === undefined) { + return false; + } + if (limit === undefined) { + return thresholds[threshold]; + } + thresholds[threshold] = limit; + if (threshold === 's') { + thresholds.ss = limit - 1; + } + return true; + } + + function humanize (withSuffix) { + if (!this.isValid()) { + return this.localeData().invalidDate(); + } + + var locale = this.localeData(); + var output = relativeTime$1(this, !withSuffix, locale); + + if (withSuffix) { + output = locale.pastFuture(+this, output); + } + + return locale.postformat(output); + } + + var abs$1 = Math.abs; + + function sign(x) { + return ((x > 0) - (x < 0)) || +x; + } + + function toISOString$1() { + // for ISO strings we do not use the normal bubbling rules: + // * milliseconds bubble up until they become hours + // * days do not bubble at all + // * months bubble up until they become years + // This is because there is no context-free conversion between hours and days + // (think of clock changes) + // and also not between days and months (28-31 days per month) + if (!this.isValid()) { + return this.localeData().invalidDate(); + } + + var seconds = abs$1(this._milliseconds) / 1000; + var days = abs$1(this._days); + var months = abs$1(this._months); + var minutes, hours, years; + + // 3600 seconds -> 60 minutes -> 1 hour + minutes = absFloor(seconds / 60); + hours = absFloor(minutes / 60); + seconds %= 60; + minutes %= 60; + + // 12 months -> 1 year + years = absFloor(months / 12); + months %= 12; + + + // inspired by https://github.com/dordille/moment-isoduration/blob/master/moment.isoduration.js + var Y = years; + var M = months; + var D = days; + var h = hours; + var m = minutes; + var s = seconds ? seconds.toFixed(3).replace(/\.?0+$/, '') : ''; + var total = this.asSeconds(); + + if (!total) { + // this is the same as C#'s (Noda) and python (isodate)... + // but not other JS (goog.date) + return 'P0D'; + } + + var totalSign = total < 0 ? '-' : ''; + var ymSign = sign(this._months) !== sign(total) ? '-' : ''; + var daysSign = sign(this._days) !== sign(total) ? '-' : ''; + var hmsSign = sign(this._milliseconds) !== sign(total) ? '-' : ''; + + return totalSign + 'P' + + (Y ? ymSign + Y + 'Y' : '') + + (M ? ymSign + M + 'M' : '') + + (D ? daysSign + D + 'D' : '') + + ((h || m || s) ? 'T' : '') + + (h ? hmsSign + h + 'H' : '') + + (m ? hmsSign + m + 'M' : '') + + (s ? hmsSign + s + 'S' : ''); + } + + var proto$2 = Duration.prototype; + + proto$2.isValid = isValid$1; + proto$2.abs = abs; + proto$2.add = add$1; + proto$2.subtract = subtract$1; + proto$2.as = as; + proto$2.asMilliseconds = asMilliseconds; + proto$2.asSeconds = asSeconds; + proto$2.asMinutes = asMinutes; + proto$2.asHours = asHours; + proto$2.asDays = asDays; + proto$2.asWeeks = asWeeks; + proto$2.asMonths = asMonths; + proto$2.asQuarters = asQuarters; + proto$2.asYears = asYears; + proto$2.valueOf = valueOf$1; + proto$2._bubble = bubble; + proto$2.clone = clone$1; + proto$2.get = get$2; + proto$2.milliseconds = milliseconds; + proto$2.seconds = seconds; + proto$2.minutes = minutes; + proto$2.hours = hours; + proto$2.days = days; + proto$2.weeks = weeks; + proto$2.months = months; + proto$2.years = years; + proto$2.humanize = humanize; + proto$2.toISOString = toISOString$1; + proto$2.toString = toISOString$1; + proto$2.toJSON = toISOString$1; + proto$2.locale = locale; + proto$2.localeData = localeData; + + proto$2.toIsoString = deprecate('toIsoString() is deprecated. Please use toISOString() instead (notice the capitals)', toISOString$1); + proto$2.lang = lang; + + // Side effect imports + + // FORMATTING + + addFormatToken('X', 0, 0, 'unix'); + addFormatToken('x', 0, 0, 'valueOf'); + + // PARSING + + addRegexToken('x', matchSigned); + addRegexToken('X', matchTimestamp); + addParseToken('X', function (input, array, config) { + config._d = new Date(parseFloat(input, 10) * 1000); + }); + addParseToken('x', function (input, array, config) { + config._d = new Date(toInt(input)); + }); + + // Side effect imports + + + hooks.version = '2.24.0'; + + setHookCallback(createLocal); + + hooks.fn = proto; + hooks.min = min; + hooks.max = max; + hooks.now = now; + hooks.utc = createUTC; + hooks.unix = createUnix; + hooks.months = listMonths; + hooks.isDate = isDate; + hooks.locale = getSetGlobalLocale; + hooks.invalid = createInvalid; + hooks.duration = createDuration; + hooks.isMoment = isMoment; + hooks.weekdays = listWeekdays; + hooks.parseZone = createInZone; + hooks.localeData = getLocale; + hooks.isDuration = isDuration; + hooks.monthsShort = listMonthsShort; + hooks.weekdaysMin = listWeekdaysMin; + hooks.defineLocale = defineLocale; + hooks.updateLocale = updateLocale; + hooks.locales = listLocales; + hooks.weekdaysShort = listWeekdaysShort; + hooks.normalizeUnits = normalizeUnits; + hooks.relativeTimeRounding = getSetRelativeTimeRounding; + hooks.relativeTimeThreshold = getSetRelativeTimeThreshold; + hooks.calendarFormat = getCalendarFormat; + hooks.prototype = proto; + + // currently HTML5 input type only supports 24-hour formats + hooks.HTML5_FMT = { + DATETIME_LOCAL: 'YYYY-MM-DDTHH:mm', // + DATETIME_LOCAL_SECONDS: 'YYYY-MM-DDTHH:mm:ss', // + DATETIME_LOCAL_MS: 'YYYY-MM-DDTHH:mm:ss.SSS', // + DATE: 'YYYY-MM-DD', // + TIME: 'HH:mm', // + TIME_SECONDS: 'HH:mm:ss', // + TIME_MS: 'HH:mm:ss.SSS', // + WEEK: 'GGGG-[W]WW', // + MONTH: 'YYYY-MM' // + }; + + return hooks; + +}))); + +/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(5)(module))) /***/ }), -/* 52 */ +/* 41 */ /***/ (function(module, exports, __webpack_require__) { -var wrappy = __webpack_require__(51) -module.exports = wrappy(once) -module.exports.strict = wrappy(onceStrict) +var map = { + "./af": 42, + "./af.js": 42, + "./ar": 43, + "./ar-dz": 44, + "./ar-dz.js": 44, + "./ar-kw": 45, + "./ar-kw.js": 45, + "./ar-ly": 46, + "./ar-ly.js": 46, + "./ar-ma": 47, + "./ar-ma.js": 47, + "./ar-sa": 48, + "./ar-sa.js": 48, + "./ar-tn": 49, + "./ar-tn.js": 49, + "./ar.js": 43, + "./az": 50, + "./az.js": 50, + "./be": 51, + "./be.js": 51, + "./bg": 52, + "./bg.js": 52, + "./bm": 53, + "./bm.js": 53, + "./bn": 54, + "./bn.js": 54, + "./bo": 55, + "./bo.js": 55, + "./br": 56, + "./br.js": 56, + "./bs": 57, + "./bs.js": 57, + "./ca": 58, + "./ca.js": 58, + "./cs": 59, + "./cs.js": 59, + "./cv": 60, + "./cv.js": 60, + "./cy": 61, + "./cy.js": 61, + "./da": 62, + "./da.js": 62, + "./de": 63, + "./de-at": 64, + "./de-at.js": 64, + "./de-ch": 65, + "./de-ch.js": 65, + "./de.js": 63, + "./dv": 66, + "./dv.js": 66, + "./el": 67, + "./el.js": 67, + "./en-SG": 68, + "./en-SG.js": 68, + "./en-au": 69, + "./en-au.js": 69, + "./en-ca": 70, + "./en-ca.js": 70, + "./en-gb": 71, + "./en-gb.js": 71, + "./en-ie": 72, + "./en-ie.js": 72, + "./en-il": 73, + "./en-il.js": 73, + "./en-nz": 74, + "./en-nz.js": 74, + "./eo": 75, + "./eo.js": 75, + "./es": 76, + "./es-do": 77, + "./es-do.js": 77, + "./es-us": 78, + "./es-us.js": 78, + "./es.js": 76, + "./et": 79, + "./et.js": 79, + "./eu": 80, + "./eu.js": 80, + "./fa": 81, + "./fa.js": 81, + "./fi": 82, + "./fi.js": 82, + "./fo": 83, + "./fo.js": 83, + "./fr": 84, + "./fr-ca": 85, + "./fr-ca.js": 85, + "./fr-ch": 86, + "./fr-ch.js": 86, + "./fr.js": 84, + "./fy": 87, + "./fy.js": 87, + "./ga": 88, + "./ga.js": 88, + "./gd": 89, + "./gd.js": 89, + "./gl": 90, + "./gl.js": 90, + "./gom-latn": 91, + "./gom-latn.js": 91, + "./gu": 92, + "./gu.js": 92, + "./he": 93, + "./he.js": 93, + "./hi": 94, + "./hi.js": 94, + "./hr": 95, + "./hr.js": 95, + "./hu": 96, + "./hu.js": 96, + "./hy-am": 97, + "./hy-am.js": 97, + "./id": 98, + "./id.js": 98, + "./is": 99, + "./is.js": 99, + "./it": 100, + "./it-ch": 101, + "./it-ch.js": 101, + "./it.js": 100, + "./ja": 102, + "./ja.js": 102, + "./jv": 103, + "./jv.js": 103, + "./ka": 104, + "./ka.js": 104, + "./kk": 105, + "./kk.js": 105, + "./km": 106, + "./km.js": 106, + "./kn": 107, + "./kn.js": 107, + "./ko": 108, + "./ko.js": 108, + "./ku": 109, + "./ku.js": 109, + "./ky": 110, + "./ky.js": 110, + "./lb": 111, + "./lb.js": 111, + "./lo": 112, + "./lo.js": 112, + "./lt": 113, + "./lt.js": 113, + "./lv": 114, + "./lv.js": 114, + "./me": 115, + "./me.js": 115, + "./mi": 116, + "./mi.js": 116, + "./mk": 117, + "./mk.js": 117, + "./ml": 118, + "./ml.js": 118, + "./mn": 119, + "./mn.js": 119, + "./mr": 120, + "./mr.js": 120, + "./ms": 121, + "./ms-my": 122, + "./ms-my.js": 122, + "./ms.js": 121, + "./mt": 123, + "./mt.js": 123, + "./my": 124, + "./my.js": 124, + "./nb": 125, + "./nb.js": 125, + "./ne": 126, + "./ne.js": 126, + "./nl": 127, + "./nl-be": 128, + "./nl-be.js": 128, + "./nl.js": 127, + "./nn": 129, + "./nn.js": 129, + "./pa-in": 130, + "./pa-in.js": 130, + "./pl": 131, + "./pl.js": 131, + "./pt": 132, + "./pt-br": 133, + "./pt-br.js": 133, + "./pt.js": 132, + "./ro": 134, + "./ro.js": 134, + "./ru": 135, + "./ru.js": 135, + "./sd": 136, + "./sd.js": 136, + "./se": 137, + "./se.js": 137, + "./si": 138, + "./si.js": 138, + "./sk": 139, + "./sk.js": 139, + "./sl": 140, + "./sl.js": 140, + "./sq": 141, + "./sq.js": 141, + "./sr": 142, + "./sr-cyrl": 143, + "./sr-cyrl.js": 143, + "./sr.js": 142, + "./ss": 144, + "./ss.js": 144, + "./sv": 145, + "./sv.js": 145, + "./sw": 146, + "./sw.js": 146, + "./ta": 147, + "./ta.js": 147, + "./te": 148, + "./te.js": 148, + "./tet": 149, + "./tet.js": 149, + "./tg": 150, + "./tg.js": 150, + "./th": 151, + "./th.js": 151, + "./tl-ph": 152, + "./tl-ph.js": 152, + "./tlh": 153, + "./tlh.js": 153, + "./tr": 154, + "./tr.js": 154, + "./tzl": 155, + "./tzl.js": 155, + "./tzm": 156, + "./tzm-latn": 157, + "./tzm-latn.js": 157, + "./tzm.js": 156, + "./ug-cn": 158, + "./ug-cn.js": 158, + "./uk": 159, + "./uk.js": 159, + "./ur": 160, + "./ur.js": 160, + "./uz": 161, + "./uz-latn": 162, + "./uz-latn.js": 162, + "./uz.js": 161, + "./vi": 163, + "./vi.js": 163, + "./x-pseudo": 164, + "./x-pseudo.js": 164, + "./yo": 165, + "./yo.js": 165, + "./zh-cn": 166, + "./zh-cn.js": 166, + "./zh-hk": 167, + "./zh-hk.js": 167, + "./zh-tw": 168, + "./zh-tw.js": 168 +}; + + +function webpackContext(req) { + var id = webpackContextResolve(req); + return __webpack_require__(id); +} +function webpackContextResolve(req) { + if(!__webpack_require__.o(map, req)) { + var e = new Error("Cannot find module '" + req + "'"); + e.code = 'MODULE_NOT_FOUND'; + throw e; + } + return map[req]; +} +webpackContext.keys = function webpackContextKeys() { + return Object.keys(map); +}; +webpackContext.resolve = webpackContextResolve; +module.exports = webpackContext; +webpackContext.id = 41; -once.proto = once(function () { - Object.defineProperty(Function.prototype, 'once', { - value: function () { - return once(this) - }, - configurable: true - }) +/***/ }), +/* 42 */ +/***/ (function(module, exports, __webpack_require__) { - Object.defineProperty(Function.prototype, 'onceStrict', { - value: function () { - return onceStrict(this) - }, - configurable: true - }) -}) +//! moment.js locale configuration -function once (fn) { - var f = function () { - if (f.called) return f.value - f.called = true - return f.value = fn.apply(this, arguments) - } - f.called = false - return f -} +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; -function onceStrict (fn) { - var f = function () { - if (f.called) - throw new Error(f.onceError) - f.called = true - return f.value = fn.apply(this, arguments) - } - var name = fn.name || 'Function wrapped with `once`' - f.onceError = name + " shouldn't be called more than once" - f.called = false - return f -} + var af = moment.defineLocale('af', { + months : 'Januarie_Februarie_Maart_April_Mei_Junie_Julie_Augustus_September_Oktober_November_Desember'.split('_'), + monthsShort : 'Jan_Feb_Mrt_Apr_Mei_Jun_Jul_Aug_Sep_Okt_Nov_Des'.split('_'), + weekdays : 'Sondag_Maandag_Dinsdag_Woensdag_Donderdag_Vrydag_Saterdag'.split('_'), + weekdaysShort : 'Son_Maa_Din_Woe_Don_Vry_Sat'.split('_'), + weekdaysMin : 'So_Ma_Di_Wo_Do_Vr_Sa'.split('_'), + meridiemParse: /vm|nm/i, + isPM : function (input) { + return /^nm$/i.test(input); + }, + meridiem : function (hours, minutes, isLower) { + if (hours < 12) { + return isLower ? 'vm' : 'VM'; + } else { + return isLower ? 'nm' : 'NM'; + } + }, + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY HH:mm', + LLLL : 'dddd, D MMMM YYYY HH:mm' + }, + calendar : { + sameDay : '[Vandag om] LT', + nextDay : '[Môre om] LT', + nextWeek : 'dddd [om] LT', + lastDay : '[Gister om] LT', + lastWeek : '[Laas] dddd [om] LT', + sameElse : 'L' + }, + relativeTime : { + future : 'oor %s', + past : '%s gelede', + s : '\'n paar sekondes', + ss : '%d sekondes', + m : '\'n minuut', + mm : '%d minute', + h : '\'n uur', + hh : '%d ure', + d : '\'n dag', + dd : '%d dae', + M : '\'n maand', + MM : '%d maande', + y : '\'n jaar', + yy : '%d jaar' + }, + dayOfMonthOrdinalParse: /\d{1,2}(ste|de)/, + ordinal : function (number) { + return number + ((number === 1 || number === 8 || number >= 20) ? 'ste' : 'de'); // Thanks to Joris Röling : https://github.com/jjupiter + }, + week : { + dow : 1, // Maandag is die eerste dag van die week. + doy : 4 // Die week wat die 4de Januarie bevat is die eerste week van die jaar. + } + }); -/***/ }), -/* 53 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + return af; -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "CliError", function() { return CliError; }); -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -class CliError extends Error { - constructor(message, meta = {}) { - super(message); - this.meta = meta; - } +}))); -} /***/ }), -/* 54 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/* 43 */ +/***/ (function(module, exports, __webpack_require__) { -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Project", function() { return Project; }); -/* harmony import */ var chalk__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2); -/* harmony import */ var chalk__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(chalk__WEBPACK_IMPORTED_MODULE_0__); -/* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(23); -/* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(fs__WEBPACK_IMPORTED_MODULE_1__); -/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(16); -/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_2__); -/* harmony import */ var util__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(29); -/* harmony import */ var util__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(util__WEBPACK_IMPORTED_MODULE_3__); -/* harmony import */ var _errors__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(53); -/* harmony import */ var _log__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(34); -/* harmony import */ var _package_json__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(55); -/* harmony import */ var _scripts__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(120); -function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var symbolMap = { + '1': '١', + '2': '٢', + '3': '٣', + '4': '٤', + '5': '٥', + '6': '٦', + '7': '٧', + '8': '٨', + '9': '٩', + '0': '٠' + }, numberMap = { + '١': '1', + '٢': '2', + '٣': '3', + '٤': '4', + '٥': '5', + '٦': '6', + '٧': '7', + '٨': '8', + '٩': '9', + '٠': '0' + }, pluralForm = function (n) { + return n === 0 ? 0 : n === 1 ? 1 : n === 2 ? 2 : n % 100 >= 3 && n % 100 <= 10 ? 3 : n % 100 >= 11 ? 4 : 5; + }, plurals = { + s : ['أقل من ثانية', 'ثانية واحدة', ['ثانيتان', 'ثانيتين'], '%d ثوان', '%d ثانية', '%d ثانية'], + m : ['أقل من دقيقة', 'دقيقة واحدة', ['دقيقتان', 'دقيقتين'], '%d دقائق', '%d دقيقة', '%d دقيقة'], + h : ['أقل من ساعة', 'ساعة واحدة', ['ساعتان', 'ساعتين'], '%d ساعات', '%d ساعة', '%d ساعة'], + d : ['أقل من يوم', 'يوم واحد', ['يومان', 'يومين'], '%d أيام', '%d يومًا', '%d يوم'], + M : ['أقل من شهر', 'شهر واحد', ['شهران', 'شهرين'], '%d أشهر', '%d شهرا', '%d شهر'], + y : ['أقل من عام', 'عام واحد', ['عامان', 'عامين'], '%d أعوام', '%d عامًا', '%d عام'] + }, pluralize = function (u) { + return function (number, withoutSuffix, string, isFuture) { + var f = pluralForm(number), + str = plurals[u][pluralForm(number)]; + if (f === 2) { + str = str[withoutSuffix ? 0 : 1]; + } + return str.replace(/%d/i, number); + }; + }, months = [ + 'يناير', + 'فبراير', + 'مارس', + 'أبريل', + 'مايو', + 'يونيو', + 'يوليو', + 'أغسطس', + 'سبتمبر', + 'أكتوبر', + 'نوفمبر', + 'ديسمبر' + ]; -function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(source, true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(source).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } + var ar = moment.defineLocale('ar', { + months : months, + monthsShort : months, + weekdays : 'الأحد_الإثنين_الثلاثاء_الأربعاء_الخميس_الجمعة_السبت'.split('_'), + weekdaysShort : 'أحد_إثنين_ثلاثاء_أربعاء_خميس_جمعة_سبت'.split('_'), + weekdaysMin : 'ح_ن_ث_ر_خ_ج_س'.split('_'), + weekdaysParseExact : true, + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'D/\u200FM/\u200FYYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY HH:mm', + LLLL : 'dddd D MMMM YYYY HH:mm' + }, + meridiemParse: /ص|م/, + isPM : function (input) { + return 'م' === input; + }, + meridiem : function (hour, minute, isLower) { + if (hour < 12) { + return 'ص'; + } else { + return 'م'; + } + }, + calendar : { + sameDay: '[اليوم عند الساعة] LT', + nextDay: '[غدًا عند الساعة] LT', + nextWeek: 'dddd [عند الساعة] LT', + lastDay: '[أمس عند الساعة] LT', + lastWeek: 'dddd [عند الساعة] LT', + sameElse: 'L' + }, + relativeTime : { + future : 'بعد %s', + past : 'منذ %s', + s : pluralize('s'), + ss : pluralize('s'), + m : pluralize('m'), + mm : pluralize('m'), + h : pluralize('h'), + hh : pluralize('h'), + d : pluralize('d'), + dd : pluralize('d'), + M : pluralize('M'), + MM : pluralize('M'), + y : pluralize('y'), + yy : pluralize('y') + }, + preparse: function (string) { + return string.replace(/[١٢٣٤٥٦٧٨٩٠]/g, function (match) { + return numberMap[match]; + }).replace(/،/g, ','); + }, + postformat: function (string) { + return string.replace(/\d/g, function (match) { + return symbolMap[match]; + }).replace(/,/g, '،'); + }, + week : { + dow : 6, // Saturday is the first day of the week. + doy : 12 // The week that contains Jan 12th is the first week of the year. + } + }); -function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } + return ar; -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ +}))); +/***/ }), +/* 44 */ +/***/ (function(module, exports, __webpack_require__) { +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var arDz = moment.defineLocale('ar-dz', { + months : 'جانفي_فيفري_مارس_أفريل_ماي_جوان_جويلية_أوت_سبتمبر_أكتوبر_نوفمبر_ديسمبر'.split('_'), + monthsShort : 'جانفي_فيفري_مارس_أفريل_ماي_جوان_جويلية_أوت_سبتمبر_أكتوبر_نوفمبر_ديسمبر'.split('_'), + weekdays : 'الأحد_الإثنين_الثلاثاء_الأربعاء_الخميس_الجمعة_السبت'.split('_'), + weekdaysShort : 'احد_اثنين_ثلاثاء_اربعاء_خميس_جمعة_سبت'.split('_'), + weekdaysMin : 'أح_إث_ثلا_أر_خم_جم_سب'.split('_'), + weekdaysParseExact : true, + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY HH:mm', + LLLL : 'dddd D MMMM YYYY HH:mm' + }, + calendar : { + sameDay: '[اليوم على الساعة] LT', + nextDay: '[غدا على الساعة] LT', + nextWeek: 'dddd [على الساعة] LT', + lastDay: '[أمس على الساعة] LT', + lastWeek: 'dddd [على الساعة] LT', + sameElse: 'L' + }, + relativeTime : { + future : 'في %s', + past : 'منذ %s', + s : 'ثوان', + ss : '%d ثانية', + m : 'دقيقة', + mm : '%d دقائق', + h : 'ساعة', + hh : '%d ساعات', + d : 'يوم', + dd : '%d أيام', + M : 'شهر', + MM : '%d أشهر', + y : 'سنة', + yy : '%d سنوات' + }, + week : { + dow : 0, // Sunday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } + }); + return arDz; +}))); +/***/ }), +/* 45 */ +/***/ (function(module, exports, __webpack_require__) { -class Project { - static async fromPath(path) { - const pkgJson = await Object(_package_json__WEBPACK_IMPORTED_MODULE_6__["readPackageJson"])(path); - return new Project(pkgJson, path); - } +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var arKw = moment.defineLocale('ar-kw', { + months : 'يناير_فبراير_مارس_أبريل_ماي_يونيو_يوليوز_غشت_شتنبر_أكتوبر_نونبر_دجنبر'.split('_'), + monthsShort : 'يناير_فبراير_مارس_أبريل_ماي_يونيو_يوليوز_غشت_شتنبر_أكتوبر_نونبر_دجنبر'.split('_'), + weekdays : 'الأحد_الإتنين_الثلاثاء_الأربعاء_الخميس_الجمعة_السبت'.split('_'), + weekdaysShort : 'احد_اتنين_ثلاثاء_اربعاء_خميس_جمعة_سبت'.split('_'), + weekdaysMin : 'ح_ن_ث_ر_خ_ج_س'.split('_'), + weekdaysParseExact : true, + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY HH:mm', + LLLL : 'dddd D MMMM YYYY HH:mm' + }, + calendar : { + sameDay: '[اليوم على الساعة] LT', + nextDay: '[غدا على الساعة] LT', + nextWeek: 'dddd [على الساعة] LT', + lastDay: '[أمس على الساعة] LT', + lastWeek: 'dddd [على الساعة] LT', + sameElse: 'L' + }, + relativeTime : { + future : 'في %s', + past : 'منذ %s', + s : 'ثوان', + ss : '%d ثانية', + m : 'دقيقة', + mm : '%d دقائق', + h : 'ساعة', + hh : '%d ساعات', + d : 'يوم', + dd : '%d أيام', + M : 'شهر', + MM : '%d أشهر', + y : 'سنة', + yy : '%d سنوات' + }, + week : { + dow : 0, // Sunday is the first day of the week. + doy : 12 // The week that contains Jan 12th is the first week of the year. + } + }); - constructor(packageJson, projectPath) { - _defineProperty(this, "json", void 0); + return arKw; - _defineProperty(this, "packageJsonLocation", void 0); +}))); - _defineProperty(this, "nodeModulesLocation", void 0); - _defineProperty(this, "targetLocation", void 0); +/***/ }), +/* 46 */ +/***/ (function(module, exports, __webpack_require__) { - _defineProperty(this, "path", void 0); +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var symbolMap = { + '1': '1', + '2': '2', + '3': '3', + '4': '4', + '5': '5', + '6': '6', + '7': '7', + '8': '8', + '9': '9', + '0': '0' + }, pluralForm = function (n) { + return n === 0 ? 0 : n === 1 ? 1 : n === 2 ? 2 : n % 100 >= 3 && n % 100 <= 10 ? 3 : n % 100 >= 11 ? 4 : 5; + }, plurals = { + s : ['أقل من ثانية', 'ثانية واحدة', ['ثانيتان', 'ثانيتين'], '%d ثوان', '%d ثانية', '%d ثانية'], + m : ['أقل من دقيقة', 'دقيقة واحدة', ['دقيقتان', 'دقيقتين'], '%d دقائق', '%d دقيقة', '%d دقيقة'], + h : ['أقل من ساعة', 'ساعة واحدة', ['ساعتان', 'ساعتين'], '%d ساعات', '%d ساعة', '%d ساعة'], + d : ['أقل من يوم', 'يوم واحد', ['يومان', 'يومين'], '%d أيام', '%d يومًا', '%d يوم'], + M : ['أقل من شهر', 'شهر واحد', ['شهران', 'شهرين'], '%d أشهر', '%d شهرا', '%d شهر'], + y : ['أقل من عام', 'عام واحد', ['عامان', 'عامين'], '%d أعوام', '%d عامًا', '%d عام'] + }, pluralize = function (u) { + return function (number, withoutSuffix, string, isFuture) { + var f = pluralForm(number), + str = plurals[u][pluralForm(number)]; + if (f === 2) { + str = str[withoutSuffix ? 0 : 1]; + } + return str.replace(/%d/i, number); + }; + }, months = [ + 'يناير', + 'فبراير', + 'مارس', + 'أبريل', + 'مايو', + 'يونيو', + 'يوليو', + 'أغسطس', + 'سبتمبر', + 'أكتوبر', + 'نوفمبر', + 'ديسمبر' + ]; - _defineProperty(this, "allDependencies", void 0); + var arLy = moment.defineLocale('ar-ly', { + months : months, + monthsShort : months, + weekdays : 'الأحد_الإثنين_الثلاثاء_الأربعاء_الخميس_الجمعة_السبت'.split('_'), + weekdaysShort : 'أحد_إثنين_ثلاثاء_أربعاء_خميس_جمعة_سبت'.split('_'), + weekdaysMin : 'ح_ن_ث_ر_خ_ج_س'.split('_'), + weekdaysParseExact : true, + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'D/\u200FM/\u200FYYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY HH:mm', + LLLL : 'dddd D MMMM YYYY HH:mm' + }, + meridiemParse: /ص|م/, + isPM : function (input) { + return 'م' === input; + }, + meridiem : function (hour, minute, isLower) { + if (hour < 12) { + return 'ص'; + } else { + return 'م'; + } + }, + calendar : { + sameDay: '[اليوم عند الساعة] LT', + nextDay: '[غدًا عند الساعة] LT', + nextWeek: 'dddd [عند الساعة] LT', + lastDay: '[أمس عند الساعة] LT', + lastWeek: 'dddd [عند الساعة] LT', + sameElse: 'L' + }, + relativeTime : { + future : 'بعد %s', + past : 'منذ %s', + s : pluralize('s'), + ss : pluralize('s'), + m : pluralize('m'), + mm : pluralize('m'), + h : pluralize('h'), + hh : pluralize('h'), + d : pluralize('d'), + dd : pluralize('d'), + M : pluralize('M'), + MM : pluralize('M'), + y : pluralize('y'), + yy : pluralize('y') + }, + preparse: function (string) { + return string.replace(/،/g, ','); + }, + postformat: function (string) { + return string.replace(/\d/g, function (match) { + return symbolMap[match]; + }).replace(/,/g, '،'); + }, + week : { + dow : 6, // Saturday is the first day of the week. + doy : 12 // The week that contains Jan 12th is the first week of the year. + } + }); - _defineProperty(this, "productionDependencies", void 0); + return arLy; - _defineProperty(this, "devDependencies", void 0); +}))); - _defineProperty(this, "scripts", void 0); - _defineProperty(this, "isWorkspaceRoot", false); +/***/ }), +/* 47 */ +/***/ (function(module, exports, __webpack_require__) { - _defineProperty(this, "isWorkspaceProject", false); +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var arMa = moment.defineLocale('ar-ma', { + months : 'يناير_فبراير_مارس_أبريل_ماي_يونيو_يوليوز_غشت_شتنبر_أكتوبر_نونبر_دجنبر'.split('_'), + monthsShort : 'يناير_فبراير_مارس_أبريل_ماي_يونيو_يوليوز_غشت_شتنبر_أكتوبر_نونبر_دجنبر'.split('_'), + weekdays : 'الأحد_الإتنين_الثلاثاء_الأربعاء_الخميس_الجمعة_السبت'.split('_'), + weekdaysShort : 'احد_اتنين_ثلاثاء_اربعاء_خميس_جمعة_سبت'.split('_'), + weekdaysMin : 'ح_ن_ث_ر_خ_ج_س'.split('_'), + weekdaysParseExact : true, + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY HH:mm', + LLLL : 'dddd D MMMM YYYY HH:mm' + }, + calendar : { + sameDay: '[اليوم على الساعة] LT', + nextDay: '[غدا على الساعة] LT', + nextWeek: 'dddd [على الساعة] LT', + lastDay: '[أمس على الساعة] LT', + lastWeek: 'dddd [على الساعة] LT', + sameElse: 'L' + }, + relativeTime : { + future : 'في %s', + past : 'منذ %s', + s : 'ثوان', + ss : '%d ثانية', + m : 'دقيقة', + mm : '%d دقائق', + h : 'ساعة', + hh : '%d ساعات', + d : 'يوم', + dd : '%d أيام', + M : 'شهر', + MM : '%d أشهر', + y : 'سنة', + yy : '%d سنوات' + }, + week : { + dow : 6, // Saturday is the first day of the week. + doy : 12 // The week that contains Jan 12th is the first week of the year. + } + }); - this.json = Object.freeze(packageJson); - this.path = projectPath; - this.packageJsonLocation = Object(path__WEBPACK_IMPORTED_MODULE_2__["resolve"])(this.path, 'package.json'); - this.nodeModulesLocation = Object(path__WEBPACK_IMPORTED_MODULE_2__["resolve"])(this.path, 'node_modules'); - this.targetLocation = Object(path__WEBPACK_IMPORTED_MODULE_2__["resolve"])(this.path, 'target'); - this.productionDependencies = this.json.dependencies || {}; - this.devDependencies = this.json.devDependencies || {}; - this.allDependencies = _objectSpread({}, this.devDependencies, {}, this.productionDependencies); - this.isWorkspaceRoot = this.json.hasOwnProperty('workspaces'); - this.scripts = this.json.scripts || {}; - } + return arMa; - get name() { - return this.json.name; - } +}))); - ensureValidProjectDependency(project, dependentProjectIsInWorkspace) { - const versionInPackageJson = this.allDependencies[project.name]; - let expectedVersionInPackageJson; - if (dependentProjectIsInWorkspace) { - expectedVersionInPackageJson = project.json.version; - } else { - const relativePathToProject = normalizePath(Object(path__WEBPACK_IMPORTED_MODULE_2__["relative"])(this.path, project.path)); - expectedVersionInPackageJson = `link:${relativePathToProject}`; - } // No issues! +/***/ }), +/* 48 */ +/***/ (function(module, exports, __webpack_require__) { +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var symbolMap = { + '1': '١', + '2': '٢', + '3': '٣', + '4': '٤', + '5': '٥', + '6': '٦', + '7': '٧', + '8': '٨', + '9': '٩', + '0': '٠' + }, numberMap = { + '١': '1', + '٢': '2', + '٣': '3', + '٤': '4', + '٥': '5', + '٦': '6', + '٧': '7', + '٨': '8', + '٩': '9', + '٠': '0' + }; + + var arSa = moment.defineLocale('ar-sa', { + months : 'يناير_فبراير_مارس_أبريل_مايو_يونيو_يوليو_أغسطس_سبتمبر_أكتوبر_نوفمبر_ديسمبر'.split('_'), + monthsShort : 'يناير_فبراير_مارس_أبريل_مايو_يونيو_يوليو_أغسطس_سبتمبر_أكتوبر_نوفمبر_ديسمبر'.split('_'), + weekdays : 'الأحد_الإثنين_الثلاثاء_الأربعاء_الخميس_الجمعة_السبت'.split('_'), + weekdaysShort : 'أحد_إثنين_ثلاثاء_أربعاء_خميس_جمعة_سبت'.split('_'), + weekdaysMin : 'ح_ن_ث_ر_خ_ج_س'.split('_'), + weekdaysParseExact : true, + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY HH:mm', + LLLL : 'dddd D MMMM YYYY HH:mm' + }, + meridiemParse: /ص|م/, + isPM : function (input) { + return 'م' === input; + }, + meridiem : function (hour, minute, isLower) { + if (hour < 12) { + return 'ص'; + } else { + return 'م'; + } + }, + calendar : { + sameDay: '[اليوم على الساعة] LT', + nextDay: '[غدا على الساعة] LT', + nextWeek: 'dddd [على الساعة] LT', + lastDay: '[أمس على الساعة] LT', + lastWeek: 'dddd [على الساعة] LT', + sameElse: 'L' + }, + relativeTime : { + future : 'في %s', + past : 'منذ %s', + s : 'ثوان', + ss : '%d ثانية', + m : 'دقيقة', + mm : '%d دقائق', + h : 'ساعة', + hh : '%d ساعات', + d : 'يوم', + dd : '%d أيام', + M : 'شهر', + MM : '%d أشهر', + y : 'سنة', + yy : '%d سنوات' + }, + preparse: function (string) { + return string.replace(/[١٢٣٤٥٦٧٨٩٠]/g, function (match) { + return numberMap[match]; + }).replace(/،/g, ','); + }, + postformat: function (string) { + return string.replace(/\d/g, function (match) { + return symbolMap[match]; + }).replace(/,/g, '،'); + }, + week : { + dow : 0, // Sunday is the first day of the week. + doy : 6 // The week that contains Jan 6th is the first week of the year. + } + }); - if (versionInPackageJson === expectedVersionInPackageJson) { - return; - } + return arSa; - let problemMsg; +}))); - if (Object(_package_json__WEBPACK_IMPORTED_MODULE_6__["isLinkDependency"])(versionInPackageJson) && dependentProjectIsInWorkspace) { - problemMsg = `but should be using a workspace`; - } else if (Object(_package_json__WEBPACK_IMPORTED_MODULE_6__["isLinkDependency"])(versionInPackageJson)) { - problemMsg = `using 'link:', but the path is wrong`; - } else { - problemMsg = `but it's not using the local package`; - } - throw new _errors__WEBPACK_IMPORTED_MODULE_4__["CliError"](`[${this.name}] depends on [${project.name}] ${problemMsg}. Update its package.json to the expected value below.`, { - actual: `"${project.name}": "${versionInPackageJson}"`, - expected: `"${project.name}": "${expectedVersionInPackageJson}"`, - package: `${this.name} (${this.packageJsonLocation})` +/***/ }), +/* 49 */ +/***/ (function(module, exports, __webpack_require__) { + +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var arTn = moment.defineLocale('ar-tn', { + months: 'جانفي_فيفري_مارس_أفريل_ماي_جوان_جويلية_أوت_سبتمبر_أكتوبر_نوفمبر_ديسمبر'.split('_'), + monthsShort: 'جانفي_فيفري_مارس_أفريل_ماي_جوان_جويلية_أوت_سبتمبر_أكتوبر_نوفمبر_ديسمبر'.split('_'), + weekdays: 'الأحد_الإثنين_الثلاثاء_الأربعاء_الخميس_الجمعة_السبت'.split('_'), + weekdaysShort: 'أحد_إثنين_ثلاثاء_أربعاء_خميس_جمعة_سبت'.split('_'), + weekdaysMin: 'ح_ن_ث_ر_خ_ج_س'.split('_'), + weekdaysParseExact : true, + longDateFormat: { + LT: 'HH:mm', + LTS: 'HH:mm:ss', + L: 'DD/MM/YYYY', + LL: 'D MMMM YYYY', + LLL: 'D MMMM YYYY HH:mm', + LLLL: 'dddd D MMMM YYYY HH:mm' + }, + calendar: { + sameDay: '[اليوم على الساعة] LT', + nextDay: '[غدا على الساعة] LT', + nextWeek: 'dddd [على الساعة] LT', + lastDay: '[أمس على الساعة] LT', + lastWeek: 'dddd [على الساعة] LT', + sameElse: 'L' + }, + relativeTime: { + future: 'في %s', + past: 'منذ %s', + s: 'ثوان', + ss : '%d ثانية', + m: 'دقيقة', + mm: '%d دقائق', + h: 'ساعة', + hh: '%d ساعات', + d: 'يوم', + dd: '%d أيام', + M: 'شهر', + MM: '%d أشهر', + y: 'سنة', + yy: '%d سنوات' + }, + week: { + dow: 1, // Monday is the first day of the week. + doy: 4 // The week that contains Jan 4th is the first week of the year. + } }); - } - getBuildConfig() { - return this.json.kibana && this.json.kibana.build || {}; - } - /** - * Returns the directory that should be copied into the Kibana build artifact. - * This config can be specified to only include the project's build artifacts - * instead of everything located in the project directory. - */ + return arTn; +}))); - getIntermediateBuildDirectory() { - return Object(path__WEBPACK_IMPORTED_MODULE_2__["resolve"])(this.path, this.getBuildConfig().intermediateBuildDirectory || '.'); - } - getCleanConfig() { - return this.json.kibana && this.json.kibana.clean || {}; - } +/***/ }), +/* 50 */ +/***/ (function(module, exports, __webpack_require__) { - hasScript(name) { - return name in this.scripts; - } +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var suffixes = { + 1: '-inci', + 5: '-inci', + 8: '-inci', + 70: '-inci', + 80: '-inci', + 2: '-nci', + 7: '-nci', + 20: '-nci', + 50: '-nci', + 3: '-üncü', + 4: '-üncü', + 100: '-üncü', + 6: '-ncı', + 9: '-uncu', + 10: '-uncu', + 30: '-uncu', + 60: '-ıncı', + 90: '-ıncı' + }; + + var az = moment.defineLocale('az', { + months : 'yanvar_fevral_mart_aprel_may_iyun_iyul_avqust_sentyabr_oktyabr_noyabr_dekabr'.split('_'), + monthsShort : 'yan_fev_mar_apr_may_iyn_iyl_avq_sen_okt_noy_dek'.split('_'), + weekdays : 'Bazar_Bazar ertəsi_Çərşənbə axşamı_Çərşənbə_Cümə axşamı_Cümə_Şənbə'.split('_'), + weekdaysShort : 'Baz_BzE_ÇAx_Çər_CAx_Cüm_Şən'.split('_'), + weekdaysMin : 'Bz_BE_ÇA_Çə_CA_Cü_Şə'.split('_'), + weekdaysParseExact : true, + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD.MM.YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY HH:mm', + LLLL : 'dddd, D MMMM YYYY HH:mm' + }, + calendar : { + sameDay : '[bugün saat] LT', + nextDay : '[sabah saat] LT', + nextWeek : '[gələn həftə] dddd [saat] LT', + lastDay : '[dünən] LT', + lastWeek : '[keçən həftə] dddd [saat] LT', + sameElse : 'L' + }, + relativeTime : { + future : '%s sonra', + past : '%s əvvəl', + s : 'birneçə saniyə', + ss : '%d saniyə', + m : 'bir dəqiqə', + mm : '%d dəqiqə', + h : 'bir saat', + hh : '%d saat', + d : 'bir gün', + dd : '%d gün', + M : 'bir ay', + MM : '%d ay', + y : 'bir il', + yy : '%d il' + }, + meridiemParse: /gecə|səhər|gündüz|axşam/, + isPM : function (input) { + return /^(gündüz|axşam)$/.test(input); + }, + meridiem : function (hour, minute, isLower) { + if (hour < 4) { + return 'gecə'; + } else if (hour < 12) { + return 'səhər'; + } else if (hour < 17) { + return 'gündüz'; + } else { + return 'axşam'; + } + }, + dayOfMonthOrdinalParse: /\d{1,2}-(ıncı|inci|nci|üncü|ncı|uncu)/, + ordinal : function (number) { + if (number === 0) { // special case for zero + return number + '-ıncı'; + } + var a = number % 10, + b = number % 100 - a, + c = number >= 100 ? 100 : null; + return number + (suffixes[a] || suffixes[b] || suffixes[c]); + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 7 // The week that contains Jan 7th is the first week of the year. + } + }); - getExecutables() { - const raw = this.json.bin; + return az; - if (!raw) { - return {}; - } +}))); - if (typeof raw === 'string') { - return { - [this.name]: Object(path__WEBPACK_IMPORTED_MODULE_2__["resolve"])(this.path, raw) - }; - } - if (typeof raw === 'object') { - const binsConfig = {}; +/***/ }), +/* 51 */ +/***/ (function(module, exports, __webpack_require__) { - for (const binName of Object.keys(raw)) { - binsConfig[binName] = Object(path__WEBPACK_IMPORTED_MODULE_2__["resolve"])(this.path, raw[binName]); - } +//! moment.js locale configuration - return binsConfig; +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + function plural(word, num) { + var forms = word.split('_'); + return num % 10 === 1 && num % 100 !== 11 ? forms[0] : (num % 10 >= 2 && num % 10 <= 4 && (num % 100 < 10 || num % 100 >= 20) ? forms[1] : forms[2]); + } + function relativeTimeWithPlural(number, withoutSuffix, key) { + var format = { + 'ss': withoutSuffix ? 'секунда_секунды_секунд' : 'секунду_секунды_секунд', + 'mm': withoutSuffix ? 'хвіліна_хвіліны_хвілін' : 'хвіліну_хвіліны_хвілін', + 'hh': withoutSuffix ? 'гадзіна_гадзіны_гадзін' : 'гадзіну_гадзіны_гадзін', + 'dd': 'дзень_дні_дзён', + 'MM': 'месяц_месяцы_месяцаў', + 'yy': 'год_гады_гадоў' + }; + if (key === 'm') { + return withoutSuffix ? 'хвіліна' : 'хвіліну'; + } + else if (key === 'h') { + return withoutSuffix ? 'гадзіна' : 'гадзіну'; + } + else { + return number + ' ' + plural(format[key], +number); + } } - throw new _errors__WEBPACK_IMPORTED_MODULE_4__["CliError"](`[${this.name}] has an invalid "bin" field in its package.json, ` + `expected an object or a string`, { - binConfig: Object(util__WEBPACK_IMPORTED_MODULE_3__["inspect"])(raw), - package: `${this.name} (${this.packageJsonLocation})` + var be = moment.defineLocale('be', { + months : { + format: 'студзеня_лютага_сакавіка_красавіка_траўня_чэрвеня_ліпеня_жніўня_верасня_кастрычніка_лістапада_снежня'.split('_'), + standalone: 'студзень_люты_сакавік_красавік_травень_чэрвень_ліпень_жнівень_верасень_кастрычнік_лістапад_снежань'.split('_') + }, + monthsShort : 'студ_лют_сак_крас_трав_чэрв_ліп_жнів_вер_каст_ліст_снеж'.split('_'), + weekdays : { + format: 'нядзелю_панядзелак_аўторак_сераду_чацвер_пятніцу_суботу'.split('_'), + standalone: 'нядзеля_панядзелак_аўторак_серада_чацвер_пятніца_субота'.split('_'), + isFormat: /\[ ?[Ууў] ?(?:мінулую|наступную)? ?\] ?dddd/ + }, + weekdaysShort : 'нд_пн_ат_ср_чц_пт_сб'.split('_'), + weekdaysMin : 'нд_пн_ат_ср_чц_пт_сб'.split('_'), + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD.MM.YYYY', + LL : 'D MMMM YYYY г.', + LLL : 'D MMMM YYYY г., HH:mm', + LLLL : 'dddd, D MMMM YYYY г., HH:mm' + }, + calendar : { + sameDay: '[Сёння ў] LT', + nextDay: '[Заўтра ў] LT', + lastDay: '[Учора ў] LT', + nextWeek: function () { + return '[У] dddd [ў] LT'; + }, + lastWeek: function () { + switch (this.day()) { + case 0: + case 3: + case 5: + case 6: + return '[У мінулую] dddd [ў] LT'; + case 1: + case 2: + case 4: + return '[У мінулы] dddd [ў] LT'; + } + }, + sameElse: 'L' + }, + relativeTime : { + future : 'праз %s', + past : '%s таму', + s : 'некалькі секунд', + m : relativeTimeWithPlural, + mm : relativeTimeWithPlural, + h : relativeTimeWithPlural, + hh : relativeTimeWithPlural, + d : 'дзень', + dd : relativeTimeWithPlural, + M : 'месяц', + MM : relativeTimeWithPlural, + y : 'год', + yy : relativeTimeWithPlural + }, + meridiemParse: /ночы|раніцы|дня|вечара/, + isPM : function (input) { + return /^(дня|вечара)$/.test(input); + }, + meridiem : function (hour, minute, isLower) { + if (hour < 4) { + return 'ночы'; + } else if (hour < 12) { + return 'раніцы'; + } else if (hour < 17) { + return 'дня'; + } else { + return 'вечара'; + } + }, + dayOfMonthOrdinalParse: /\d{1,2}-(і|ы|га)/, + ordinal: function (number, period) { + switch (period) { + case 'M': + case 'd': + case 'DDD': + case 'w': + case 'W': + return (number % 10 === 2 || number % 10 === 3) && (number % 100 !== 12 && number % 100 !== 13) ? number + '-і' : number + '-ы'; + case 'D': + return number + '-га'; + default: + return number; + } + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 7 // The week that contains Jan 7th is the first week of the year. + } }); - } - async runScript(scriptName, args = []) { - _log__WEBPACK_IMPORTED_MODULE_5__["log"].write(chalk__WEBPACK_IMPORTED_MODULE_0___default.a.bold(`\n\nRunning script [${chalk__WEBPACK_IMPORTED_MODULE_0___default.a.green(scriptName)}] in [${chalk__WEBPACK_IMPORTED_MODULE_0___default.a.green(this.name)}]:\n`)); - return Object(_scripts__WEBPACK_IMPORTED_MODULE_7__["runScriptInPackage"])(scriptName, args, this); - } + return be; - runScriptStreaming(scriptName, args = []) { - return Object(_scripts__WEBPACK_IMPORTED_MODULE_7__["runScriptInPackageStreaming"])(scriptName, args, this); - } +}))); - hasDependencies() { - return Object.keys(this.allDependencies).length > 0; - } - async installDependencies({ - extraArgs - }) { - _log__WEBPACK_IMPORTED_MODULE_5__["log"].write(chalk__WEBPACK_IMPORTED_MODULE_0___default.a.bold(`\n\nInstalling dependencies in [${chalk__WEBPACK_IMPORTED_MODULE_0___default.a.green(this.name)}]:\n`)); - await Object(_scripts__WEBPACK_IMPORTED_MODULE_7__["installInDir"])(this.path, extraArgs); - await this.removeExtraneousNodeModules(); - } - /** - * Yarn workspaces symlinks workspace projects to the root node_modules, even - * when there is no depenency on the project. This results in unnecicary, and - * often duplicated code in the build archives. - */ +/***/ }), +/* 52 */ +/***/ (function(module, exports, __webpack_require__) { +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var bg = moment.defineLocale('bg', { + months : 'януари_февруари_март_април_май_юни_юли_август_септември_октомври_ноември_декември'.split('_'), + monthsShort : 'янр_фев_мар_апр_май_юни_юли_авг_сеп_окт_ное_дек'.split('_'), + weekdays : 'неделя_понеделник_вторник_сряда_четвъртък_петък_събота'.split('_'), + weekdaysShort : 'нед_пон_вто_сря_чет_пет_съб'.split('_'), + weekdaysMin : 'нд_пн_вт_ср_чт_пт_сб'.split('_'), + longDateFormat : { + LT : 'H:mm', + LTS : 'H:mm:ss', + L : 'D.MM.YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY H:mm', + LLLL : 'dddd, D MMMM YYYY H:mm' + }, + calendar : { + sameDay : '[Днес в] LT', + nextDay : '[Утре в] LT', + nextWeek : 'dddd [в] LT', + lastDay : '[Вчера в] LT', + lastWeek : function () { + switch (this.day()) { + case 0: + case 3: + case 6: + return '[В изминалата] dddd [в] LT'; + case 1: + case 2: + case 4: + case 5: + return '[В изминалия] dddd [в] LT'; + } + }, + sameElse : 'L' + }, + relativeTime : { + future : 'след %s', + past : 'преди %s', + s : 'няколко секунди', + ss : '%d секунди', + m : 'минута', + mm : '%d минути', + h : 'час', + hh : '%d часа', + d : 'ден', + dd : '%d дни', + M : 'месец', + MM : '%d месеца', + y : 'година', + yy : '%d години' + }, + dayOfMonthOrdinalParse: /\d{1,2}-(ев|ен|ти|ви|ри|ми)/, + ordinal : function (number) { + var lastDigit = number % 10, + last2Digits = number % 100; + if (number === 0) { + return number + '-ев'; + } else if (last2Digits === 0) { + return number + '-ен'; + } else if (last2Digits > 10 && last2Digits < 20) { + return number + '-ти'; + } else if (lastDigit === 1) { + return number + '-ви'; + } else if (lastDigit === 2) { + return number + '-ри'; + } else if (lastDigit === 7 || lastDigit === 8) { + return number + '-ми'; + } else { + return number + '-ти'; + } + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 7 // The week that contains Jan 7th is the first week of the year. + } + }); - async removeExtraneousNodeModules() { - // this is only relevant for the root workspace - if (!this.isWorkspaceRoot) { - return; - } + return bg; - const workspacesInfo = await Object(_scripts__WEBPACK_IMPORTED_MODULE_7__["yarnWorkspacesInfo"])(this.path); - const unusedWorkspaces = new Set(Object.keys(workspacesInfo)); // check for any cross-project dependency +}))); - for (const name of Object.keys(workspacesInfo)) { - const workspace = workspacesInfo[name]; - workspace.workspaceDependencies.forEach(w => unusedWorkspaces.delete(w)); - } - unusedWorkspaces.forEach(name => { - const { - dependencies, - devDependencies - } = this.json; - const nodeModulesPath = Object(path__WEBPACK_IMPORTED_MODULE_2__["resolve"])(this.nodeModulesLocation, name); - const isDependency = dependencies && dependencies.hasOwnProperty(name); - const isDevDependency = devDependencies && devDependencies.hasOwnProperty(name); +/***/ }), +/* 53 */ +/***/ (function(module, exports, __webpack_require__) { - if (!isDependency && !isDevDependency && fs__WEBPACK_IMPORTED_MODULE_1___default.a.existsSync(nodeModulesPath)) { - _log__WEBPACK_IMPORTED_MODULE_5__["log"].write(`No dependency on ${name}, removing link in node_modules`); - fs__WEBPACK_IMPORTED_MODULE_1___default.a.unlinkSync(nodeModulesPath); - } +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var bm = moment.defineLocale('bm', { + months : 'Zanwuyekalo_Fewuruyekalo_Marisikalo_Awirilikalo_Mɛkalo_Zuwɛnkalo_Zuluyekalo_Utikalo_Sɛtanburukalo_ɔkutɔburukalo_Nowanburukalo_Desanburukalo'.split('_'), + monthsShort : 'Zan_Few_Mar_Awi_Mɛ_Zuw_Zul_Uti_Sɛt_ɔku_Now_Des'.split('_'), + weekdays : 'Kari_Ntɛnɛn_Tarata_Araba_Alamisa_Juma_Sibiri'.split('_'), + weekdaysShort : 'Kar_Ntɛ_Tar_Ara_Ala_Jum_Sib'.split('_'), + weekdaysMin : 'Ka_Nt_Ta_Ar_Al_Ju_Si'.split('_'), + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD/MM/YYYY', + LL : 'MMMM [tile] D [san] YYYY', + LLL : 'MMMM [tile] D [san] YYYY [lɛrɛ] HH:mm', + LLLL : 'dddd MMMM [tile] D [san] YYYY [lɛrɛ] HH:mm' + }, + calendar : { + sameDay : '[Bi lɛrɛ] LT', + nextDay : '[Sini lɛrɛ] LT', + nextWeek : 'dddd [don lɛrɛ] LT', + lastDay : '[Kunu lɛrɛ] LT', + lastWeek : 'dddd [tɛmɛnen lɛrɛ] LT', + sameElse : 'L' + }, + relativeTime : { + future : '%s kɔnɔ', + past : 'a bɛ %s bɔ', + s : 'sanga dama dama', + ss : 'sekondi %d', + m : 'miniti kelen', + mm : 'miniti %d', + h : 'lɛrɛ kelen', + hh : 'lɛrɛ %d', + d : 'tile kelen', + dd : 'tile %d', + M : 'kalo kelen', + MM : 'kalo %d', + y : 'san kelen', + yy : 'san %d' + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } }); - } -} // We normalize all path separators to `/` in generated files + return bm; + +}))); -function normalizePath(path) { - return path.replace(/[\\\/]+/g, '/'); -} /***/ }), -/* 55 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/* 54 */ +/***/ (function(module, exports, __webpack_require__) { -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "readPackageJson", function() { return readPackageJson; }); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "writePackageJson", function() { return writePackageJson; }); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "isLinkDependency", function() { return isLinkDependency; }); -/* harmony import */ var read_pkg__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(56); -/* harmony import */ var read_pkg__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(read_pkg__WEBPACK_IMPORTED_MODULE_0__); -/* harmony import */ var write_pkg__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(98); -/* harmony import */ var write_pkg__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(write_pkg__WEBPACK_IMPORTED_MODULE_1__); -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var symbolMap = { + '1': '১', + '2': '২', + '3': '৩', + '4': '৪', + '5': '৫', + '6': '৬', + '7': '৭', + '8': '৮', + '9': '৯', + '0': '০' + }, + numberMap = { + '১': '1', + '২': '2', + '৩': '3', + '৪': '4', + '৫': '5', + '৬': '6', + '৭': '7', + '৮': '8', + '৯': '9', + '০': '0' + }; + + var bn = moment.defineLocale('bn', { + months : 'জানুয়ারী_ফেব্রুয়ারি_মার্চ_এপ্রিল_মে_জুন_জুলাই_আগস্ট_সেপ্টেম্বর_অক্টোবর_নভেম্বর_ডিসেম্বর'.split('_'), + monthsShort : 'জানু_ফেব_মার্চ_এপ্র_মে_জুন_জুল_আগ_সেপ্ট_অক্টো_নভে_ডিসে'.split('_'), + weekdays : 'রবিবার_সোমবার_মঙ্গলবার_বুধবার_বৃহস্পতিবার_শুক্রবার_শনিবার'.split('_'), + weekdaysShort : 'রবি_সোম_মঙ্গল_বুধ_বৃহস্পতি_শুক্র_শনি'.split('_'), + weekdaysMin : 'রবি_সোম_মঙ্গ_বুধ_বৃহঃ_শুক্র_শনি'.split('_'), + longDateFormat : { + LT : 'A h:mm সময়', + LTS : 'A h:mm:ss সময়', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY, A h:mm সময়', + LLLL : 'dddd, D MMMM YYYY, A h:mm সময়' + }, + calendar : { + sameDay : '[আজ] LT', + nextDay : '[আগামীকাল] LT', + nextWeek : 'dddd, LT', + lastDay : '[গতকাল] LT', + lastWeek : '[গত] dddd, LT', + sameElse : 'L' + }, + relativeTime : { + future : '%s পরে', + past : '%s আগে', + s : 'কয়েক সেকেন্ড', + ss : '%d সেকেন্ড', + m : 'এক মিনিট', + mm : '%d মিনিট', + h : 'এক ঘন্টা', + hh : '%d ঘন্টা', + d : 'এক দিন', + dd : '%d দিন', + M : 'এক মাস', + MM : '%d মাস', + y : 'এক বছর', + yy : '%d বছর' + }, + preparse: function (string) { + return string.replace(/[১২৩৪৫৬৭৮৯০]/g, function (match) { + return numberMap[match]; + }); + }, + postformat: function (string) { + return string.replace(/\d/g, function (match) { + return symbolMap[match]; + }); + }, + meridiemParse: /রাত|সকাল|দুপুর|বিকাল|রাত/, + meridiemHour : function (hour, meridiem) { + if (hour === 12) { + hour = 0; + } + if ((meridiem === 'রাত' && hour >= 4) || + (meridiem === 'দুপুর' && hour < 5) || + meridiem === 'বিকাল') { + return hour + 12; + } else { + return hour; + } + }, + meridiem : function (hour, minute, isLower) { + if (hour < 4) { + return 'রাত'; + } else if (hour < 10) { + return 'সকাল'; + } else if (hour < 17) { + return 'দুপুর'; + } else if (hour < 20) { + return 'বিকাল'; + } else { + return 'রাত'; + } + }, + week : { + dow : 0, // Sunday is the first day of the week. + doy : 6 // The week that contains Jan 6th is the first week of the year. + } + }); + return bn; + +}))); -function readPackageJson(cwd) { - return read_pkg__WEBPACK_IMPORTED_MODULE_0___default()({ - cwd, - normalize: false - }); -} -function writePackageJson(path, json) { - return write_pkg__WEBPACK_IMPORTED_MODULE_1___default()(path, json); -} -const isLinkDependency = depVersion => depVersion.startsWith('link:'); /***/ }), -/* 56 */ +/* 55 */ /***/ (function(module, exports, __webpack_require__) { -"use strict"; +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var symbolMap = { + '1': '༡', + '2': '༢', + '3': '༣', + '4': '༤', + '5': '༥', + '6': '༦', + '7': '༧', + '8': '༨', + '9': '༩', + '0': '༠' + }, + numberMap = { + '༡': '1', + '༢': '2', + '༣': '3', + '༤': '4', + '༥': '5', + '༦': '6', + '༧': '7', + '༨': '8', + '༩': '9', + '༠': '0' + }; + + var bo = moment.defineLocale('bo', { + months : 'ཟླ་བ་དང་པོ_ཟླ་བ་གཉིས་པ_ཟླ་བ་གསུམ་པ_ཟླ་བ་བཞི་པ_ཟླ་བ་ལྔ་པ_ཟླ་བ་དྲུག་པ_ཟླ་བ་བདུན་པ_ཟླ་བ་བརྒྱད་པ_ཟླ་བ་དགུ་པ_ཟླ་བ་བཅུ་པ_ཟླ་བ་བཅུ་གཅིག་པ_ཟླ་བ་བཅུ་གཉིས་པ'.split('_'), + monthsShort : 'ཟླ་བ་དང་པོ_ཟླ་བ་གཉིས་པ_ཟླ་བ་གསུམ་པ_ཟླ་བ་བཞི་པ_ཟླ་བ་ལྔ་པ_ཟླ་བ་དྲུག་པ_ཟླ་བ་བདུན་པ_ཟླ་བ་བརྒྱད་པ_ཟླ་བ་དགུ་པ_ཟླ་བ་བཅུ་པ_ཟླ་བ་བཅུ་གཅིག་པ_ཟླ་བ་བཅུ་གཉིས་པ'.split('_'), + weekdays : 'གཟའ་ཉི་མ་_གཟའ་ཟླ་བ་_གཟའ་མིག་དམར་_གཟའ་ལྷག་པ་_གཟའ་ཕུར་བུ_གཟའ་པ་སངས་_གཟའ་སྤེན་པ་'.split('_'), + weekdaysShort : 'ཉི་མ་_ཟླ་བ་_མིག་དམར་_ལྷག་པ་_ཕུར་བུ_པ་སངས་_སྤེན་པ་'.split('_'), + weekdaysMin : 'ཉི་མ་_ཟླ་བ་_མིག་དམར་_ལྷག་པ་_ཕུར་བུ_པ་སངས་_སྤེན་པ་'.split('_'), + longDateFormat : { + LT : 'A h:mm', + LTS : 'A h:mm:ss', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY, A h:mm', + LLLL : 'dddd, D MMMM YYYY, A h:mm' + }, + calendar : { + sameDay : '[དི་རིང] LT', + nextDay : '[སང་ཉིན] LT', + nextWeek : '[བདུན་ཕྲག་རྗེས་མ], LT', + lastDay : '[ཁ་སང] LT', + lastWeek : '[བདུན་ཕྲག་མཐའ་མ] dddd, LT', + sameElse : 'L' + }, + relativeTime : { + future : '%s ལ་', + past : '%s སྔན་ལ', + s : 'ལམ་སང', + ss : '%d སྐར་ཆ།', + m : 'སྐར་མ་གཅིག', + mm : '%d སྐར་མ', + h : 'ཆུ་ཚོད་གཅིག', + hh : '%d ཆུ་ཚོད', + d : 'ཉིན་གཅིག', + dd : '%d ཉིན་', + M : 'ཟླ་བ་གཅིག', + MM : '%d ཟླ་བ', + y : 'ལོ་གཅིག', + yy : '%d ལོ' + }, + preparse: function (string) { + return string.replace(/[༡༢༣༤༥༦༧༨༩༠]/g, function (match) { + return numberMap[match]; + }); + }, + postformat: function (string) { + return string.replace(/\d/g, function (match) { + return symbolMap[match]; + }); + }, + meridiemParse: /མཚན་མོ|ཞོགས་ཀས|ཉིན་གུང|དགོང་དག|མཚན་མོ/, + meridiemHour : function (hour, meridiem) { + if (hour === 12) { + hour = 0; + } + if ((meridiem === 'མཚན་མོ' && hour >= 4) || + (meridiem === 'ཉིན་གུང' && hour < 5) || + meridiem === 'དགོང་དག') { + return hour + 12; + } else { + return hour; + } + }, + meridiem : function (hour, minute, isLower) { + if (hour < 4) { + return 'མཚན་མོ'; + } else if (hour < 10) { + return 'ཞོགས་ཀས'; + } else if (hour < 17) { + return 'ཉིན་གུང'; + } else if (hour < 20) { + return 'དགོང་དག'; + } else { + return 'མཚན་མོ'; + } + }, + week : { + dow : 0, // Sunday is the first day of the week. + doy : 6 // The week that contains Jan 6th is the first week of the year. + } + }); -const {promisify} = __webpack_require__(29); -const fs = __webpack_require__(23); -const path = __webpack_require__(16); -const parseJson = __webpack_require__(57); + return bo; -const readFileAsync = promisify(fs.readFile); +}))); -module.exports = async options => { - options = { - cwd: process.cwd(), - normalize: true, - ...options - }; - const filePath = path.resolve(options.cwd, 'package.json'); - const json = parseJson(await readFileAsync(filePath, 'utf8')); +/***/ }), +/* 56 */ +/***/ (function(module, exports, __webpack_require__) { - if (options.normalize) { - __webpack_require__(73)(json); - } +//! moment.js locale configuration - return json; -}; +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; -module.exports.sync = options => { - options = { - cwd: process.cwd(), - normalize: true, - ...options - }; - const filePath = path.resolve(options.cwd, 'package.json'); - const json = parseJson(fs.readFileSync(filePath, 'utf8')); + function relativeTimeWithMutation(number, withoutSuffix, key) { + var format = { + 'mm': 'munutenn', + 'MM': 'miz', + 'dd': 'devezh' + }; + return number + ' ' + mutation(format[key], number); + } + function specialMutationForYears(number) { + switch (lastNumber(number)) { + case 1: + case 3: + case 4: + case 5: + case 9: + return number + ' bloaz'; + default: + return number + ' vloaz'; + } + } + function lastNumber(number) { + if (number > 9) { + return lastNumber(number % 10); + } + return number; + } + function mutation(text, number) { + if (number === 2) { + return softMutation(text); + } + return text; + } + function softMutation(text) { + var mutationTable = { + 'm': 'v', + 'b': 'v', + 'd': 'z' + }; + if (mutationTable[text.charAt(0)] === undefined) { + return text; + } + return mutationTable[text.charAt(0)] + text.substring(1); + } + + var br = moment.defineLocale('br', { + months : 'Genver_C\'hwevrer_Meurzh_Ebrel_Mae_Mezheven_Gouere_Eost_Gwengolo_Here_Du_Kerzu'.split('_'), + monthsShort : 'Gen_C\'hwe_Meu_Ebr_Mae_Eve_Gou_Eos_Gwe_Her_Du_Ker'.split('_'), + weekdays : 'Sul_Lun_Meurzh_Merc\'her_Yaou_Gwener_Sadorn'.split('_'), + weekdaysShort : 'Sul_Lun_Meu_Mer_Yao_Gwe_Sad'.split('_'), + weekdaysMin : 'Su_Lu_Me_Mer_Ya_Gw_Sa'.split('_'), + weekdaysParseExact : true, + longDateFormat : { + LT : 'h[e]mm A', + LTS : 'h[e]mm:ss A', + L : 'DD/MM/YYYY', + LL : 'D [a viz] MMMM YYYY', + LLL : 'D [a viz] MMMM YYYY h[e]mm A', + LLLL : 'dddd, D [a viz] MMMM YYYY h[e]mm A' + }, + calendar : { + sameDay : '[Hiziv da] LT', + nextDay : '[Warc\'hoazh da] LT', + nextWeek : 'dddd [da] LT', + lastDay : '[Dec\'h da] LT', + lastWeek : 'dddd [paset da] LT', + sameElse : 'L' + }, + relativeTime : { + future : 'a-benn %s', + past : '%s \'zo', + s : 'un nebeud segondennoù', + ss : '%d eilenn', + m : 'ur vunutenn', + mm : relativeTimeWithMutation, + h : 'un eur', + hh : '%d eur', + d : 'un devezh', + dd : relativeTimeWithMutation, + M : 'ur miz', + MM : relativeTimeWithMutation, + y : 'ur bloaz', + yy : specialMutationForYears + }, + dayOfMonthOrdinalParse: /\d{1,2}(añ|vet)/, + ordinal : function (number) { + var output = (number === 1) ? 'añ' : 'vet'; + return number + output; + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } + }); - if (options.normalize) { - __webpack_require__(73)(json); - } + return br; - return json; -}; +}))); /***/ }), /* 57 */ /***/ (function(module, exports, __webpack_require__) { -"use strict"; - -const errorEx = __webpack_require__(58); -const fallback = __webpack_require__(60); -const {default: LinesAndColumns} = __webpack_require__(61); -const {codeFrameColumns} = __webpack_require__(62); +//! moment.js locale configuration -const JSONError = errorEx('JSONError', { - fileName: errorEx.append('in %s'), - codeFrame: errorEx.append('\n\n%s\n') -}); - -module.exports = (string, reviver, filename) => { - if (typeof reviver === 'string') { - filename = reviver; - reviver = null; - } +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; - try { - try { - return JSON.parse(string, reviver); - } catch (error) { - fallback(string, reviver); - throw error; - } - } catch (error) { - error.message = error.message.replace(/\n/g, ''); - const indexMatch = error.message.match(/in JSON at position (\d+) while parsing near/); - const jsonError = new JSONError(error); - if (filename) { - jsonError.fileName = filename; - } - - if (indexMatch && indexMatch.length > 0) { - const lines = new LinesAndColumns(string); - const index = Number(indexMatch[1]); - const location = lines.locationForIndex(index); - - const codeFrame = codeFrameColumns( - string, - {start: {line: location.line + 1, column: location.column + 1}}, - {highlightCode: true} - ); + function translate(number, withoutSuffix, key) { + var result = number + ' '; + switch (key) { + case 'ss': + if (number === 1) { + result += 'sekunda'; + } else if (number === 2 || number === 3 || number === 4) { + result += 'sekunde'; + } else { + result += 'sekundi'; + } + return result; + case 'm': + return withoutSuffix ? 'jedna minuta' : 'jedne minute'; + case 'mm': + if (number === 1) { + result += 'minuta'; + } else if (number === 2 || number === 3 || number === 4) { + result += 'minute'; + } else { + result += 'minuta'; + } + return result; + case 'h': + return withoutSuffix ? 'jedan sat' : 'jednog sata'; + case 'hh': + if (number === 1) { + result += 'sat'; + } else if (number === 2 || number === 3 || number === 4) { + result += 'sata'; + } else { + result += 'sati'; + } + return result; + case 'dd': + if (number === 1) { + result += 'dan'; + } else { + result += 'dana'; + } + return result; + case 'MM': + if (number === 1) { + result += 'mjesec'; + } else if (number === 2 || number === 3 || number === 4) { + result += 'mjeseca'; + } else { + result += 'mjeseci'; + } + return result; + case 'yy': + if (number === 1) { + result += 'godina'; + } else if (number === 2 || number === 3 || number === 4) { + result += 'godine'; + } else { + result += 'godina'; + } + return result; + } + } + + var bs = moment.defineLocale('bs', { + months : 'januar_februar_mart_april_maj_juni_juli_august_septembar_oktobar_novembar_decembar'.split('_'), + monthsShort : 'jan._feb._mar._apr._maj._jun._jul._aug._sep._okt._nov._dec.'.split('_'), + monthsParseExact: true, + weekdays : 'nedjelja_ponedjeljak_utorak_srijeda_četvrtak_petak_subota'.split('_'), + weekdaysShort : 'ned._pon._uto._sri._čet._pet._sub.'.split('_'), + weekdaysMin : 'ne_po_ut_sr_če_pe_su'.split('_'), + weekdaysParseExact : true, + longDateFormat : { + LT : 'H:mm', + LTS : 'H:mm:ss', + L : 'DD.MM.YYYY', + LL : 'D. MMMM YYYY', + LLL : 'D. MMMM YYYY H:mm', + LLLL : 'dddd, D. MMMM YYYY H:mm' + }, + calendar : { + sameDay : '[danas u] LT', + nextDay : '[sutra u] LT', + nextWeek : function () { + switch (this.day()) { + case 0: + return '[u] [nedjelju] [u] LT'; + case 3: + return '[u] [srijedu] [u] LT'; + case 6: + return '[u] [subotu] [u] LT'; + case 1: + case 2: + case 4: + case 5: + return '[u] dddd [u] LT'; + } + }, + lastDay : '[jučer u] LT', + lastWeek : function () { + switch (this.day()) { + case 0: + case 3: + return '[prošlu] dddd [u] LT'; + case 6: + return '[prošle] [subote] [u] LT'; + case 1: + case 2: + case 4: + case 5: + return '[prošli] dddd [u] LT'; + } + }, + sameElse : 'L' + }, + relativeTime : { + future : 'za %s', + past : 'prije %s', + s : 'par sekundi', + ss : translate, + m : translate, + mm : translate, + h : translate, + hh : translate, + d : 'dan', + dd : translate, + M : 'mjesec', + MM : translate, + y : 'godinu', + yy : translate + }, + dayOfMonthOrdinalParse: /\d{1,2}\./, + ordinal : '%d.', + week : { + dow : 1, // Monday is the first day of the week. + doy : 7 // The week that contains Jan 7th is the first week of the year. + } + }); - jsonError.codeFrame = codeFrame; - } + return bs; - throw jsonError; - } -}; +}))); /***/ }), /* 58 */ /***/ (function(module, exports, __webpack_require__) { -"use strict"; +//! moment.js locale configuration +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; -var util = __webpack_require__(29); -var isArrayish = __webpack_require__(59); -var errorEx = function errorEx(name, properties) { - if (!name || name.constructor !== String) { - properties = name || {}; - name = Error.name; - } + var ca = moment.defineLocale('ca', { + months : { + standalone: 'gener_febrer_març_abril_maig_juny_juliol_agost_setembre_octubre_novembre_desembre'.split('_'), + format: 'de gener_de febrer_de març_d\'abril_de maig_de juny_de juliol_d\'agost_de setembre_d\'octubre_de novembre_de desembre'.split('_'), + isFormat: /D[oD]?(\s)+MMMM/ + }, + monthsShort : 'gen._febr._març_abr._maig_juny_jul._ag._set._oct._nov._des.'.split('_'), + monthsParseExact : true, + weekdays : 'diumenge_dilluns_dimarts_dimecres_dijous_divendres_dissabte'.split('_'), + weekdaysShort : 'dg._dl._dt._dc._dj._dv._ds.'.split('_'), + weekdaysMin : 'dg_dl_dt_dc_dj_dv_ds'.split('_'), + weekdaysParseExact : true, + longDateFormat : { + LT : 'H:mm', + LTS : 'H:mm:ss', + L : 'DD/MM/YYYY', + LL : 'D MMMM [de] YYYY', + ll : 'D MMM YYYY', + LLL : 'D MMMM [de] YYYY [a les] H:mm', + lll : 'D MMM YYYY, H:mm', + LLLL : 'dddd D MMMM [de] YYYY [a les] H:mm', + llll : 'ddd D MMM YYYY, H:mm' + }, + calendar : { + sameDay : function () { + return '[avui a ' + ((this.hours() !== 1) ? 'les' : 'la') + '] LT'; + }, + nextDay : function () { + return '[demà a ' + ((this.hours() !== 1) ? 'les' : 'la') + '] LT'; + }, + nextWeek : function () { + return 'dddd [a ' + ((this.hours() !== 1) ? 'les' : 'la') + '] LT'; + }, + lastDay : function () { + return '[ahir a ' + ((this.hours() !== 1) ? 'les' : 'la') + '] LT'; + }, + lastWeek : function () { + return '[el] dddd [passat a ' + ((this.hours() !== 1) ? 'les' : 'la') + '] LT'; + }, + sameElse : 'L' + }, + relativeTime : { + future : 'd\'aquí %s', + past : 'fa %s', + s : 'uns segons', + ss : '%d segons', + m : 'un minut', + mm : '%d minuts', + h : 'una hora', + hh : '%d hores', + d : 'un dia', + dd : '%d dies', + M : 'un mes', + MM : '%d mesos', + y : 'un any', + yy : '%d anys' + }, + dayOfMonthOrdinalParse: /\d{1,2}(r|n|t|è|a)/, + ordinal : function (number, period) { + var output = (number === 1) ? 'r' : + (number === 2) ? 'n' : + (number === 3) ? 'r' : + (number === 4) ? 't' : 'è'; + if (period === 'w' || period === 'W') { + output = 'a'; + } + return number + output; + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } + }); - var errorExError = function ErrorEXError(message) { - if (!this) { - return new ErrorEXError(message); - } + return ca; - message = message instanceof Error - ? message.message - : (message || this.message); +}))); - Error.call(this, message); - Error.captureStackTrace(this, errorExError); - this.name = name; +/***/ }), +/* 59 */ +/***/ (function(module, exports, __webpack_require__) { - Object.defineProperty(this, 'message', { - configurable: true, - enumerable: false, - get: function () { - var newMessage = message.split(/\r?\n/g); +//! moment.js locale configuration - for (var key in properties) { - if (!properties.hasOwnProperty(key)) { - continue; - } +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; - var modifier = properties[key]; - if ('message' in modifier) { - newMessage = modifier.message(this[key], newMessage) || newMessage; - if (!isArrayish(newMessage)) { - newMessage = [newMessage]; - } - } - } + var months = 'leden_únor_březen_duben_květen_červen_červenec_srpen_září_říjen_listopad_prosinec'.split('_'), + monthsShort = 'led_úno_bře_dub_kvě_čvn_čvc_srp_zář_říj_lis_pro'.split('_'); - return newMessage.join('\n'); - }, - set: function (v) { - message = v; - } - }); + var monthsParse = [/^led/i, /^úno/i, /^bře/i, /^dub/i, /^kvě/i, /^(čvn|červen$|června)/i, /^(čvc|červenec|července)/i, /^srp/i, /^zář/i, /^říj/i, /^lis/i, /^pro/i]; + // NOTE: 'červen' is substring of 'červenec'; therefore 'červenec' must precede 'červen' in the regex to be fully matched. + // Otherwise parser matches '1. červenec' as '1. červen' + 'ec'. + var monthsRegex = /^(leden|únor|březen|duben|květen|červenec|července|červen|června|srpen|září|říjen|listopad|prosinec|led|úno|bře|dub|kvě|čvn|čvc|srp|zář|říj|lis|pro)/i; - var stackDescriptor = Object.getOwnPropertyDescriptor(this, 'stack'); - var stackGetter = stackDescriptor.get; - var stackValue = stackDescriptor.value; - delete stackDescriptor.value; - delete stackDescriptor.writable; + function plural(n) { + return (n > 1) && (n < 5) && (~~(n / 10) !== 1); + } + function translate(number, withoutSuffix, key, isFuture) { + var result = number + ' '; + switch (key) { + case 's': // a few seconds / in a few seconds / a few seconds ago + return (withoutSuffix || isFuture) ? 'pár sekund' : 'pár sekundami'; + case 'ss': // 9 seconds / in 9 seconds / 9 seconds ago + if (withoutSuffix || isFuture) { + return result + (plural(number) ? 'sekundy' : 'sekund'); + } else { + return result + 'sekundami'; + } + break; + case 'm': // a minute / in a minute / a minute ago + return withoutSuffix ? 'minuta' : (isFuture ? 'minutu' : 'minutou'); + case 'mm': // 9 minutes / in 9 minutes / 9 minutes ago + if (withoutSuffix || isFuture) { + return result + (plural(number) ? 'minuty' : 'minut'); + } else { + return result + 'minutami'; + } + break; + case 'h': // an hour / in an hour / an hour ago + return withoutSuffix ? 'hodina' : (isFuture ? 'hodinu' : 'hodinou'); + case 'hh': // 9 hours / in 9 hours / 9 hours ago + if (withoutSuffix || isFuture) { + return result + (plural(number) ? 'hodiny' : 'hodin'); + } else { + return result + 'hodinami'; + } + break; + case 'd': // a day / in a day / a day ago + return (withoutSuffix || isFuture) ? 'den' : 'dnem'; + case 'dd': // 9 days / in 9 days / 9 days ago + if (withoutSuffix || isFuture) { + return result + (plural(number) ? 'dny' : 'dní'); + } else { + return result + 'dny'; + } + break; + case 'M': // a month / in a month / a month ago + return (withoutSuffix || isFuture) ? 'měsíc' : 'měsícem'; + case 'MM': // 9 months / in 9 months / 9 months ago + if (withoutSuffix || isFuture) { + return result + (plural(number) ? 'měsíce' : 'měsíců'); + } else { + return result + 'měsíci'; + } + break; + case 'y': // a year / in a year / a year ago + return (withoutSuffix || isFuture) ? 'rok' : 'rokem'; + case 'yy': // 9 years / in 9 years / 9 years ago + if (withoutSuffix || isFuture) { + return result + (plural(number) ? 'roky' : 'let'); + } else { + return result + 'lety'; + } + break; + } + } - stackDescriptor.get = function () { - var stack = (stackGetter) - ? stackGetter.call(this).split(/\r?\n+/g) - : stackValue.split(/\r?\n+/g); + var cs = moment.defineLocale('cs', { + months : months, + monthsShort : monthsShort, + monthsRegex : monthsRegex, + monthsShortRegex : monthsRegex, + // NOTE: 'červen' is substring of 'červenec'; therefore 'červenec' must precede 'červen' in the regex to be fully matched. + // Otherwise parser matches '1. červenec' as '1. červen' + 'ec'. + monthsStrictRegex : /^(leden|ledna|února|únor|březen|března|duben|dubna|květen|května|červenec|července|červen|června|srpen|srpna|září|říjen|října|listopadu|listopad|prosinec|prosince)/i, + monthsShortStrictRegex : /^(led|úno|bře|dub|kvě|čvn|čvc|srp|zář|říj|lis|pro)/i, + monthsParse : monthsParse, + longMonthsParse : monthsParse, + shortMonthsParse : monthsParse, + weekdays : 'neděle_pondělí_úterý_středa_čtvrtek_pátek_sobota'.split('_'), + weekdaysShort : 'ne_po_út_st_čt_pá_so'.split('_'), + weekdaysMin : 'ne_po_út_st_čt_pá_so'.split('_'), + longDateFormat : { + LT: 'H:mm', + LTS : 'H:mm:ss', + L : 'DD.MM.YYYY', + LL : 'D. MMMM YYYY', + LLL : 'D. MMMM YYYY H:mm', + LLLL : 'dddd D. MMMM YYYY H:mm', + l : 'D. M. YYYY' + }, + calendar : { + sameDay: '[dnes v] LT', + nextDay: '[zítra v] LT', + nextWeek: function () { + switch (this.day()) { + case 0: + return '[v neděli v] LT'; + case 1: + case 2: + return '[v] dddd [v] LT'; + case 3: + return '[ve středu v] LT'; + case 4: + return '[ve čtvrtek v] LT'; + case 5: + return '[v pátek v] LT'; + case 6: + return '[v sobotu v] LT'; + } + }, + lastDay: '[včera v] LT', + lastWeek: function () { + switch (this.day()) { + case 0: + return '[minulou neděli v] LT'; + case 1: + case 2: + return '[minulé] dddd [v] LT'; + case 3: + return '[minulou středu v] LT'; + case 4: + case 5: + return '[minulý] dddd [v] LT'; + case 6: + return '[minulou sobotu v] LT'; + } + }, + sameElse: 'L' + }, + relativeTime : { + future : 'za %s', + past : 'před %s', + s : translate, + ss : translate, + m : translate, + mm : translate, + h : translate, + hh : translate, + d : translate, + dd : translate, + M : translate, + MM : translate, + y : translate, + yy : translate + }, + dayOfMonthOrdinalParse : /\d{1,2}\./, + ordinal : '%d.', + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } + }); - // starting in Node 7, the stack builder caches the message. - // just replace it. - stack[0] = this.name + ': ' + this.message; + return cs; - var lineCount = 1; - for (var key in properties) { - if (!properties.hasOwnProperty(key)) { - continue; - } +}))); - var modifier = properties[key]; - if ('line' in modifier) { - var line = modifier.line(this[key]); - if (line) { - stack.splice(lineCount++, 0, ' ' + line); - } - } +/***/ }), +/* 60 */ +/***/ (function(module, exports, __webpack_require__) { - if ('stack' in modifier) { - modifier.stack(this[key], stack); - } - } +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var cv = moment.defineLocale('cv', { + months : 'кӑрлач_нарӑс_пуш_ака_май_ҫӗртме_утӑ_ҫурла_авӑн_юпа_чӳк_раштав'.split('_'), + monthsShort : 'кӑр_нар_пуш_ака_май_ҫӗр_утӑ_ҫур_авн_юпа_чӳк_раш'.split('_'), + weekdays : 'вырсарникун_тунтикун_ытларикун_юнкун_кӗҫнерникун_эрнекун_шӑматкун'.split('_'), + weekdaysShort : 'выр_тун_ытл_юн_кӗҫ_эрн_шӑм'.split('_'), + weekdaysMin : 'вр_тн_ыт_юн_кҫ_эр_шм'.split('_'), + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD-MM-YYYY', + LL : 'YYYY [ҫулхи] MMMM [уйӑхӗн] D[-мӗшӗ]', + LLL : 'YYYY [ҫулхи] MMMM [уйӑхӗн] D[-мӗшӗ], HH:mm', + LLLL : 'dddd, YYYY [ҫулхи] MMMM [уйӑхӗн] D[-мӗшӗ], HH:mm' + }, + calendar : { + sameDay: '[Паян] LT [сехетре]', + nextDay: '[Ыран] LT [сехетре]', + lastDay: '[Ӗнер] LT [сехетре]', + nextWeek: '[Ҫитес] dddd LT [сехетре]', + lastWeek: '[Иртнӗ] dddd LT [сехетре]', + sameElse: 'L' + }, + relativeTime : { + future : function (output) { + var affix = /сехет$/i.exec(output) ? 'рен' : /ҫул$/i.exec(output) ? 'тан' : 'ран'; + return output + affix; + }, + past : '%s каялла', + s : 'пӗр-ик ҫеккунт', + ss : '%d ҫеккунт', + m : 'пӗр минут', + mm : '%d минут', + h : 'пӗр сехет', + hh : '%d сехет', + d : 'пӗр кун', + dd : '%d кун', + M : 'пӗр уйӑх', + MM : '%d уйӑх', + y : 'пӗр ҫул', + yy : '%d ҫул' + }, + dayOfMonthOrdinalParse: /\d{1,2}-мӗш/, + ordinal : '%d-мӗш', + week : { + dow : 1, // Monday is the first day of the week. + doy : 7 // The week that contains Jan 7th is the first week of the year. + } + }); - return stack.join('\n'); - }; + return cv; - Object.defineProperty(this, 'stack', stackDescriptor); - }; +}))); - if (Object.setPrototypeOf) { - Object.setPrototypeOf(errorExError.prototype, Error.prototype); - Object.setPrototypeOf(errorExError, Error); - } else { - util.inherits(errorExError, Error); - } - return errorExError; -}; +/***/ }), +/* 61 */ +/***/ (function(module, exports, __webpack_require__) { -errorEx.append = function (str, def) { - return { - message: function (v, message) { - v = v || def; +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var cy = moment.defineLocale('cy', { + months: 'Ionawr_Chwefror_Mawrth_Ebrill_Mai_Mehefin_Gorffennaf_Awst_Medi_Hydref_Tachwedd_Rhagfyr'.split('_'), + monthsShort: 'Ion_Chwe_Maw_Ebr_Mai_Meh_Gor_Aws_Med_Hyd_Tach_Rhag'.split('_'), + weekdays: 'Dydd Sul_Dydd Llun_Dydd Mawrth_Dydd Mercher_Dydd Iau_Dydd Gwener_Dydd Sadwrn'.split('_'), + weekdaysShort: 'Sul_Llun_Maw_Mer_Iau_Gwe_Sad'.split('_'), + weekdaysMin: 'Su_Ll_Ma_Me_Ia_Gw_Sa'.split('_'), + weekdaysParseExact : true, + // time formats are the same as en-gb + longDateFormat: { + LT: 'HH:mm', + LTS : 'HH:mm:ss', + L: 'DD/MM/YYYY', + LL: 'D MMMM YYYY', + LLL: 'D MMMM YYYY HH:mm', + LLLL: 'dddd, D MMMM YYYY HH:mm' + }, + calendar: { + sameDay: '[Heddiw am] LT', + nextDay: '[Yfory am] LT', + nextWeek: 'dddd [am] LT', + lastDay: '[Ddoe am] LT', + lastWeek: 'dddd [diwethaf am] LT', + sameElse: 'L' + }, + relativeTime: { + future: 'mewn %s', + past: '%s yn ôl', + s: 'ychydig eiliadau', + ss: '%d eiliad', + m: 'munud', + mm: '%d munud', + h: 'awr', + hh: '%d awr', + d: 'diwrnod', + dd: '%d diwrnod', + M: 'mis', + MM: '%d mis', + y: 'blwyddyn', + yy: '%d flynedd' + }, + dayOfMonthOrdinalParse: /\d{1,2}(fed|ain|af|il|ydd|ed|eg)/, + // traditional ordinal numbers above 31 are not commonly used in colloquial Welsh + ordinal: function (number) { + var b = number, + output = '', + lookup = [ + '', 'af', 'il', 'ydd', 'ydd', 'ed', 'ed', 'ed', 'fed', 'fed', 'fed', // 1af to 10fed + 'eg', 'fed', 'eg', 'eg', 'fed', 'eg', 'eg', 'fed', 'eg', 'fed' // 11eg to 20fed + ]; + if (b > 20) { + if (b === 40 || b === 50 || b === 60 || b === 80 || b === 100) { + output = 'fed'; // not 30ain, 70ain or 90ain + } else { + output = 'ain'; + } + } else if (b > 0) { + output = lookup[b]; + } + return number + output; + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } + }); - if (v) { - message[0] += ' ' + str.replace('%s', v.toString()); - } + return cy; - return message; - } - }; -}; +}))); -errorEx.line = function (str, def) { - return { - line: function (v) { - v = v || def; - if (v) { - return str.replace('%s', v.toString()); - } +/***/ }), +/* 62 */ +/***/ (function(module, exports, __webpack_require__) { - return null; - } - }; -}; +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var da = moment.defineLocale('da', { + months : 'januar_februar_marts_april_maj_juni_juli_august_september_oktober_november_december'.split('_'), + monthsShort : 'jan_feb_mar_apr_maj_jun_jul_aug_sep_okt_nov_dec'.split('_'), + weekdays : 'søndag_mandag_tirsdag_onsdag_torsdag_fredag_lørdag'.split('_'), + weekdaysShort : 'søn_man_tir_ons_tor_fre_lør'.split('_'), + weekdaysMin : 'sø_ma_ti_on_to_fr_lø'.split('_'), + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD.MM.YYYY', + LL : 'D. MMMM YYYY', + LLL : 'D. MMMM YYYY HH:mm', + LLLL : 'dddd [d.] D. MMMM YYYY [kl.] HH:mm' + }, + calendar : { + sameDay : '[i dag kl.] LT', + nextDay : '[i morgen kl.] LT', + nextWeek : 'på dddd [kl.] LT', + lastDay : '[i går kl.] LT', + lastWeek : '[i] dddd[s kl.] LT', + sameElse : 'L' + }, + relativeTime : { + future : 'om %s', + past : '%s siden', + s : 'få sekunder', + ss : '%d sekunder', + m : 'et minut', + mm : '%d minutter', + h : 'en time', + hh : '%d timer', + d : 'en dag', + dd : '%d dage', + M : 'en måned', + MM : '%d måneder', + y : 'et år', + yy : '%d år' + }, + dayOfMonthOrdinalParse: /\d{1,2}\./, + ordinal : '%d.', + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } + }); -module.exports = errorEx; + return da; + +}))); /***/ }), -/* 59 */ +/* 63 */ /***/ (function(module, exports, __webpack_require__) { -"use strict"; +//! moment.js locale configuration +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; -module.exports = function isArrayish(obj) { - if (!obj) { - return false; - } - return obj instanceof Array || Array.isArray(obj) || - (obj.length >= 0 && obj.splice instanceof Function); -}; + function processRelativeTime(number, withoutSuffix, key, isFuture) { + var format = { + 'm': ['eine Minute', 'einer Minute'], + 'h': ['eine Stunde', 'einer Stunde'], + 'd': ['ein Tag', 'einem Tag'], + 'dd': [number + ' Tage', number + ' Tagen'], + 'M': ['ein Monat', 'einem Monat'], + 'MM': [number + ' Monate', number + ' Monaten'], + 'y': ['ein Jahr', 'einem Jahr'], + 'yy': [number + ' Jahre', number + ' Jahren'] + }; + return withoutSuffix ? format[key][0] : format[key][1]; + } + + var de = moment.defineLocale('de', { + months : 'Januar_Februar_März_April_Mai_Juni_Juli_August_September_Oktober_November_Dezember'.split('_'), + monthsShort : 'Jan._Feb._März_Apr._Mai_Juni_Juli_Aug._Sep._Okt._Nov._Dez.'.split('_'), + monthsParseExact : true, + weekdays : 'Sonntag_Montag_Dienstag_Mittwoch_Donnerstag_Freitag_Samstag'.split('_'), + weekdaysShort : 'So._Mo._Di._Mi._Do._Fr._Sa.'.split('_'), + weekdaysMin : 'So_Mo_Di_Mi_Do_Fr_Sa'.split('_'), + weekdaysParseExact : true, + longDateFormat : { + LT: 'HH:mm', + LTS: 'HH:mm:ss', + L : 'DD.MM.YYYY', + LL : 'D. MMMM YYYY', + LLL : 'D. MMMM YYYY HH:mm', + LLLL : 'dddd, D. MMMM YYYY HH:mm' + }, + calendar : { + sameDay: '[heute um] LT [Uhr]', + sameElse: 'L', + nextDay: '[morgen um] LT [Uhr]', + nextWeek: 'dddd [um] LT [Uhr]', + lastDay: '[gestern um] LT [Uhr]', + lastWeek: '[letzten] dddd [um] LT [Uhr]' + }, + relativeTime : { + future : 'in %s', + past : 'vor %s', + s : 'ein paar Sekunden', + ss : '%d Sekunden', + m : processRelativeTime, + mm : '%d Minuten', + h : processRelativeTime, + hh : '%d Stunden', + d : processRelativeTime, + dd : processRelativeTime, + M : processRelativeTime, + MM : processRelativeTime, + y : processRelativeTime, + yy : processRelativeTime + }, + dayOfMonthOrdinalParse: /\d{1,2}\./, + ordinal : '%d.', + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } + }); + + return de; + +}))); /***/ }), -/* 60 */ +/* 64 */ /***/ (function(module, exports, __webpack_require__) { -"use strict"; +//! moment.js locale configuration +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; -module.exports = parseJson -function parseJson (txt, reviver, context) { - context = context || 20 - try { - return JSON.parse(txt, reviver) - } catch (e) { - const syntaxErr = e.message.match(/^Unexpected token.*position\s+(\d+)/i) - const errIdx = syntaxErr - ? +syntaxErr[1] - : e.message.match(/^Unexpected end of JSON.*/i) - ? txt.length - 1 - : null - if (errIdx != null) { - const start = errIdx <= context - ? 0 - : errIdx - context - const end = errIdx + context >= txt.length - ? txt.length - : errIdx + context - e.message += ` while parsing near '${ - start === 0 ? '' : '...' - }${txt.slice(start, end)}${ - end === txt.length ? '' : '...' - }'` - } else { - e.message += ` while parsing '${txt.slice(0, context * 2)}'` - } - throw e - } -} + function processRelativeTime(number, withoutSuffix, key, isFuture) { + var format = { + 'm': ['eine Minute', 'einer Minute'], + 'h': ['eine Stunde', 'einer Stunde'], + 'd': ['ein Tag', 'einem Tag'], + 'dd': [number + ' Tage', number + ' Tagen'], + 'M': ['ein Monat', 'einem Monat'], + 'MM': [number + ' Monate', number + ' Monaten'], + 'y': ['ein Jahr', 'einem Jahr'], + 'yy': [number + ' Jahre', number + ' Jahren'] + }; + return withoutSuffix ? format[key][0] : format[key][1]; + } + + var deAt = moment.defineLocale('de-at', { + months : 'Jänner_Februar_März_April_Mai_Juni_Juli_August_September_Oktober_November_Dezember'.split('_'), + monthsShort : 'Jän._Feb._März_Apr._Mai_Juni_Juli_Aug._Sep._Okt._Nov._Dez.'.split('_'), + monthsParseExact : true, + weekdays : 'Sonntag_Montag_Dienstag_Mittwoch_Donnerstag_Freitag_Samstag'.split('_'), + weekdaysShort : 'So._Mo._Di._Mi._Do._Fr._Sa.'.split('_'), + weekdaysMin : 'So_Mo_Di_Mi_Do_Fr_Sa'.split('_'), + weekdaysParseExact : true, + longDateFormat : { + LT: 'HH:mm', + LTS: 'HH:mm:ss', + L : 'DD.MM.YYYY', + LL : 'D. MMMM YYYY', + LLL : 'D. MMMM YYYY HH:mm', + LLLL : 'dddd, D. MMMM YYYY HH:mm' + }, + calendar : { + sameDay: '[heute um] LT [Uhr]', + sameElse: 'L', + nextDay: '[morgen um] LT [Uhr]', + nextWeek: 'dddd [um] LT [Uhr]', + lastDay: '[gestern um] LT [Uhr]', + lastWeek: '[letzten] dddd [um] LT [Uhr]' + }, + relativeTime : { + future : 'in %s', + past : 'vor %s', + s : 'ein paar Sekunden', + ss : '%d Sekunden', + m : processRelativeTime, + mm : '%d Minuten', + h : processRelativeTime, + hh : '%d Stunden', + d : processRelativeTime, + dd : processRelativeTime, + M : processRelativeTime, + MM : processRelativeTime, + y : processRelativeTime, + yy : processRelativeTime + }, + dayOfMonthOrdinalParse: /\d{1,2}\./, + ordinal : '%d.', + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } + }); -/***/ }), -/* 61 */ -/***/ (function(__webpack_module__, __webpack_exports__, __webpack_require__) { + return deAt; -"use strict"; -__webpack_require__.r(__webpack_exports__); -var LF = '\n'; -var CR = '\r'; -var LinesAndColumns = (function () { - function LinesAndColumns(string) { - this.string = string; - var offsets = [0]; - for (var offset = 0; offset < string.length;) { - switch (string[offset]) { - case LF: - offset += LF.length; - offsets.push(offset); - break; - case CR: - offset += CR.length; - if (string[offset] === LF) { - offset += LF.length; - } - offsets.push(offset); - break; - default: - offset++; - break; - } - } - this.offsets = offsets; - } - LinesAndColumns.prototype.locationForIndex = function (index) { - if (index < 0 || index > this.string.length) { - return null; - } - var line = 0; - var offsets = this.offsets; - while (offsets[line + 1] <= index) { - line++; - } - var column = index - offsets[line]; - return { line: line, column: column }; - }; - LinesAndColumns.prototype.indexForLocation = function (location) { - var line = location.line, column = location.column; - if (line < 0 || line >= this.offsets.length) { - return null; - } - if (column < 0 || column > this.lengthOfLine(line)) { - return null; - } - return this.offsets[line] + column; - }; - LinesAndColumns.prototype.lengthOfLine = function (line) { - var offset = this.offsets[line]; - var nextOffset = line === this.offsets.length - 1 ? this.string.length : this.offsets[line + 1]; - return nextOffset - offset; - }; - return LinesAndColumns; -}()); -/* harmony default export */ __webpack_exports__["default"] = (LinesAndColumns); +}))); /***/ }), -/* 62 */ +/* 65 */ /***/ (function(module, exports, __webpack_require__) { -"use strict"; +//! moment.js locale configuration +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.codeFrameColumns = codeFrameColumns; -exports.default = _default; -function _highlight() { - const data = _interopRequireWildcard(__webpack_require__(63)); + function processRelativeTime(number, withoutSuffix, key, isFuture) { + var format = { + 'm': ['eine Minute', 'einer Minute'], + 'h': ['eine Stunde', 'einer Stunde'], + 'd': ['ein Tag', 'einem Tag'], + 'dd': [number + ' Tage', number + ' Tagen'], + 'M': ['ein Monat', 'einem Monat'], + 'MM': [number + ' Monate', number + ' Monaten'], + 'y': ['ein Jahr', 'einem Jahr'], + 'yy': [number + ' Jahre', number + ' Jahren'] + }; + return withoutSuffix ? format[key][0] : format[key][1]; + } + + var deCh = moment.defineLocale('de-ch', { + months : 'Januar_Februar_März_April_Mai_Juni_Juli_August_September_Oktober_November_Dezember'.split('_'), + monthsShort : 'Jan._Feb._März_Apr._Mai_Juni_Juli_Aug._Sep._Okt._Nov._Dez.'.split('_'), + monthsParseExact : true, + weekdays : 'Sonntag_Montag_Dienstag_Mittwoch_Donnerstag_Freitag_Samstag'.split('_'), + weekdaysShort : 'So_Mo_Di_Mi_Do_Fr_Sa'.split('_'), + weekdaysMin : 'So_Mo_Di_Mi_Do_Fr_Sa'.split('_'), + weekdaysParseExact : true, + longDateFormat : { + LT: 'HH:mm', + LTS: 'HH:mm:ss', + L : 'DD.MM.YYYY', + LL : 'D. MMMM YYYY', + LLL : 'D. MMMM YYYY HH:mm', + LLLL : 'dddd, D. MMMM YYYY HH:mm' + }, + calendar : { + sameDay: '[heute um] LT [Uhr]', + sameElse: 'L', + nextDay: '[morgen um] LT [Uhr]', + nextWeek: 'dddd [um] LT [Uhr]', + lastDay: '[gestern um] LT [Uhr]', + lastWeek: '[letzten] dddd [um] LT [Uhr]' + }, + relativeTime : { + future : 'in %s', + past : 'vor %s', + s : 'ein paar Sekunden', + ss : '%d Sekunden', + m : processRelativeTime, + mm : '%d Minuten', + h : processRelativeTime, + hh : '%d Stunden', + d : processRelativeTime, + dd : processRelativeTime, + M : processRelativeTime, + MM : processRelativeTime, + y : processRelativeTime, + yy : processRelativeTime + }, + dayOfMonthOrdinalParse: /\d{1,2}\./, + ordinal : '%d.', + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } + }); - _highlight = function () { - return data; - }; + return deCh; - return data; -} +}))); -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; return newObj; } } -let deprecationWarningShown = false; +/***/ }), +/* 66 */ +/***/ (function(module, exports, __webpack_require__) { -function getDefs(chalk) { - return { - gutter: chalk.grey, - marker: chalk.red.bold, - message: chalk.red.bold - }; -} +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var months = [ + 'ޖެނުއަރީ', + 'ފެބްރުއަރީ', + 'މާރިޗު', + 'އޭޕްރީލު', + 'މޭ', + 'ޖޫން', + 'ޖުލައި', + 'އޯގަސްޓު', + 'ސެޕްޓެމްބަރު', + 'އޮކްޓޯބަރު', + 'ނޮވެމްބަރު', + 'ޑިސެމްބަރު' + ], weekdays = [ + 'އާދިއްތަ', + 'ހޯމަ', + 'އަންގާރަ', + 'ބުދަ', + 'ބުރާސްފަތި', + 'ހުކުރު', + 'ހޮނިހިރު' + ]; -const NEWLINE = /\r\n|[\n\r\u2028\u2029]/; + var dv = moment.defineLocale('dv', { + months : months, + monthsShort : months, + weekdays : weekdays, + weekdaysShort : weekdays, + weekdaysMin : 'އާދި_ހޯމަ_އަން_ބުދަ_ބުރާ_ހުކު_ހޮނި'.split('_'), + longDateFormat : { + + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'D/M/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY HH:mm', + LLLL : 'dddd D MMMM YYYY HH:mm' + }, + meridiemParse: /މކ|މފ/, + isPM : function (input) { + return 'މފ' === input; + }, + meridiem : function (hour, minute, isLower) { + if (hour < 12) { + return 'މކ'; + } else { + return 'މފ'; + } + }, + calendar : { + sameDay : '[މިއަދު] LT', + nextDay : '[މާދަމާ] LT', + nextWeek : 'dddd LT', + lastDay : '[އިއްޔެ] LT', + lastWeek : '[ފާއިތުވި] dddd LT', + sameElse : 'L' + }, + relativeTime : { + future : 'ތެރޭގައި %s', + past : 'ކުރިން %s', + s : 'ސިކުންތުކޮޅެއް', + ss : 'd% ސިކުންތު', + m : 'މިނިޓެއް', + mm : 'މިނިޓު %d', + h : 'ގަޑިއިރެއް', + hh : 'ގަޑިއިރު %d', + d : 'ދުވަހެއް', + dd : 'ދުވަސް %d', + M : 'މަހެއް', + MM : 'މަސް %d', + y : 'އަހަރެއް', + yy : 'އަހަރު %d' + }, + preparse: function (string) { + return string.replace(/،/g, ','); + }, + postformat: function (string) { + return string.replace(/,/g, '،'); + }, + week : { + dow : 7, // Sunday is the first day of the week. + doy : 12 // The week that contains Jan 12th is the first week of the year. + } + }); -function getMarkerLines(loc, source, opts) { - const startLoc = Object.assign({ - column: 0, - line: -1 - }, loc.start); - const endLoc = Object.assign({}, startLoc, loc.end); - const { - linesAbove = 2, - linesBelow = 3 - } = opts || {}; - const startLine = startLoc.line; - const startColumn = startLoc.column; - const endLine = endLoc.line; - const endColumn = endLoc.column; - let start = Math.max(startLine - (linesAbove + 1), 0); - let end = Math.min(source.length, endLine + linesBelow); + return dv; - if (startLine === -1) { - start = 0; - } +}))); - if (endLine === -1) { - end = source.length; - } - const lineDiff = endLine - startLine; - const markerLines = {}; +/***/ }), +/* 67 */ +/***/ (function(module, exports, __webpack_require__) { - if (lineDiff) { - for (let i = 0; i <= lineDiff; i++) { - const lineNumber = i + startLine; +//! moment.js locale configuration - if (!startColumn) { - markerLines[lineNumber] = true; - } else if (i === 0) { - const sourceLength = source[lineNumber - 1].length; - markerLines[lineNumber] = [startColumn, sourceLength - startColumn + 1]; - } else if (i === lineDiff) { - markerLines[lineNumber] = [0, endColumn]; - } else { - const sourceLength = source[lineNumber - i].length; - markerLines[lineNumber] = [0, sourceLength]; - } - } - } else { - if (startColumn === endColumn) { - if (startColumn) { - markerLines[startLine] = [startColumn, 0]; - } else { - markerLines[startLine] = true; - } - } else { - markerLines[startLine] = [startColumn, endColumn - startColumn]; +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + function isFunction(input) { + return input instanceof Function || Object.prototype.toString.call(input) === '[object Function]'; } - } - return { - start, - end, - markerLines - }; -} -function codeFrameColumns(rawLines, loc, opts = {}) { - const highlighted = (opts.highlightCode || opts.forceColor) && (0, _highlight().shouldHighlight)(opts); - const chalk = (0, _highlight().getChalk)(opts); - const defs = getDefs(chalk); + var el = moment.defineLocale('el', { + monthsNominativeEl : 'Ιανουάριος_Φεβρουάριος_Μάρτιος_Απρίλιος_Μάιος_Ιούνιος_Ιούλιος_Αύγουστος_Σεπτέμβριος_Οκτώβριος_Νοέμβριος_Δεκέμβριος'.split('_'), + monthsGenitiveEl : 'Ιανουαρίου_Φεβρουαρίου_Μαρτίου_Απριλίου_Μαΐου_Ιουνίου_Ιουλίου_Αυγούστου_Σεπτεμβρίου_Οκτωβρίου_Νοεμβρίου_Δεκεμβρίου'.split('_'), + months : function (momentToFormat, format) { + if (!momentToFormat) { + return this._monthsNominativeEl; + } else if (typeof format === 'string' && /D/.test(format.substring(0, format.indexOf('MMMM')))) { // if there is a day number before 'MMMM' + return this._monthsGenitiveEl[momentToFormat.month()]; + } else { + return this._monthsNominativeEl[momentToFormat.month()]; + } + }, + monthsShort : 'Ιαν_Φεβ_Μαρ_Απρ_Μαϊ_Ιουν_Ιουλ_Αυγ_Σεπ_Οκτ_Νοε_Δεκ'.split('_'), + weekdays : 'Κυριακή_Δευτέρα_Τρίτη_Τετάρτη_Πέμπτη_Παρασκευή_Σάββατο'.split('_'), + weekdaysShort : 'Κυρ_Δευ_Τρι_Τετ_Πεμ_Παρ_Σαβ'.split('_'), + weekdaysMin : 'Κυ_Δε_Τρ_Τε_Πε_Πα_Σα'.split('_'), + meridiem : function (hours, minutes, isLower) { + if (hours > 11) { + return isLower ? 'μμ' : 'ΜΜ'; + } else { + return isLower ? 'πμ' : 'ΠΜ'; + } + }, + isPM : function (input) { + return ((input + '').toLowerCase()[0] === 'μ'); + }, + meridiemParse : /[ΠΜ]\.?Μ?\.?/i, + longDateFormat : { + LT : 'h:mm A', + LTS : 'h:mm:ss A', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY h:mm A', + LLLL : 'dddd, D MMMM YYYY h:mm A' + }, + calendarEl : { + sameDay : '[Σήμερα {}] LT', + nextDay : '[Αύριο {}] LT', + nextWeek : 'dddd [{}] LT', + lastDay : '[Χθες {}] LT', + lastWeek : function () { + switch (this.day()) { + case 6: + return '[το προηγούμενο] dddd [{}] LT'; + default: + return '[την προηγούμενη] dddd [{}] LT'; + } + }, + sameElse : 'L' + }, + calendar : function (key, mom) { + var output = this._calendarEl[key], + hours = mom && mom.hours(); + if (isFunction(output)) { + output = output.apply(mom); + } + return output.replace('{}', (hours % 12 === 1 ? 'στη' : 'στις')); + }, + relativeTime : { + future : 'σε %s', + past : '%s πριν', + s : 'λίγα δευτερόλεπτα', + ss : '%d δευτερόλεπτα', + m : 'ένα λεπτό', + mm : '%d λεπτά', + h : 'μία ώρα', + hh : '%d ώρες', + d : 'μία μέρα', + dd : '%d μέρες', + M : 'ένας μήνας', + MM : '%d μήνες', + y : 'ένας χρόνος', + yy : '%d χρόνια' + }, + dayOfMonthOrdinalParse: /\d{1,2}η/, + ordinal: '%dη', + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4st is the first week of the year. + } + }); - const maybeHighlight = (chalkFn, string) => { - return highlighted ? chalkFn(string) : string; - }; + return el; - const lines = rawLines.split(NEWLINE); - const { - start, - end, - markerLines - } = getMarkerLines(loc, lines, opts); - const hasColumns = loc.start && typeof loc.start.column === "number"; - const numberMaxWidth = String(end).length; - const highlightedLines = highlighted ? (0, _highlight().default)(rawLines, opts) : rawLines; - let frame = highlightedLines.split(NEWLINE).slice(start, end).map((line, index) => { - const number = start + 1 + index; - const paddedNumber = ` ${number}`.slice(-numberMaxWidth); - const gutter = ` ${paddedNumber} | `; - const hasMarker = markerLines[number]; - const lastMarkerLine = !markerLines[number + 1]; +}))); - if (hasMarker) { - let markerLine = ""; - if (Array.isArray(hasMarker)) { - const markerSpacing = line.slice(0, Math.max(hasMarker[0] - 1, 0)).replace(/[^\t]/g, " "); - const numberOfMarkers = hasMarker[1] || 1; - markerLine = ["\n ", maybeHighlight(defs.gutter, gutter.replace(/\d/g, " ")), markerSpacing, maybeHighlight(defs.marker, "^").repeat(numberOfMarkers)].join(""); +/***/ }), +/* 68 */ +/***/ (function(module, exports, __webpack_require__) { - if (lastMarkerLine && opts.message) { - markerLine += " " + maybeHighlight(defs.message, opts.message); +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var enSG = moment.defineLocale('en-SG', { + months : 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_'), + monthsShort : 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_'), + weekdays : 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_'), + weekdaysShort : 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_'), + weekdaysMin : 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_'), + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY HH:mm', + LLLL : 'dddd, D MMMM YYYY HH:mm' + }, + calendar : { + sameDay : '[Today at] LT', + nextDay : '[Tomorrow at] LT', + nextWeek : 'dddd [at] LT', + lastDay : '[Yesterday at] LT', + lastWeek : '[Last] dddd [at] LT', + sameElse : 'L' + }, + relativeTime : { + future : 'in %s', + past : '%s ago', + s : 'a few seconds', + ss : '%d seconds', + m : 'a minute', + mm : '%d minutes', + h : 'an hour', + hh : '%d hours', + d : 'a day', + dd : '%d days', + M : 'a month', + MM : '%d months', + y : 'a year', + yy : '%d years' + }, + dayOfMonthOrdinalParse: /\d{1,2}(st|nd|rd|th)/, + ordinal : function (number) { + var b = number % 10, + output = (~~(number % 100 / 10) === 1) ? 'th' : + (b === 1) ? 'st' : + (b === 2) ? 'nd' : + (b === 3) ? 'rd' : 'th'; + return number + output; + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. } - } + }); - return [maybeHighlight(defs.marker, ">"), maybeHighlight(defs.gutter, gutter), line, markerLine].join(""); - } else { - return ` ${maybeHighlight(defs.gutter, gutter)}${line}`; - } - }).join("\n"); + return enSG; - if (opts.message && !hasColumns) { - frame = `${" ".repeat(numberMaxWidth + 1)}${opts.message}\n${frame}`; - } +}))); - if (highlighted) { - return chalk.reset(frame); - } else { - return frame; - } -} -function _default(rawLines, lineNumber, colNumber, opts = {}) { - if (!deprecationWarningShown) { - deprecationWarningShown = true; - const message = "Passing lineNumber and colNumber is deprecated to @babel/code-frame. Please use `codeFrameColumns`."; +/***/ }), +/* 69 */ +/***/ (function(module, exports, __webpack_require__) { - if (process.emitWarning) { - process.emitWarning(message, "DeprecationWarning"); - } else { - const deprecationError = new Error(message); - deprecationError.name = "DeprecationWarning"; - console.warn(new Error(message)); - } - } +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var enAu = moment.defineLocale('en-au', { + months : 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_'), + monthsShort : 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_'), + weekdays : 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_'), + weekdaysShort : 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_'), + weekdaysMin : 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_'), + longDateFormat : { + LT : 'h:mm A', + LTS : 'h:mm:ss A', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY h:mm A', + LLLL : 'dddd, D MMMM YYYY h:mm A' + }, + calendar : { + sameDay : '[Today at] LT', + nextDay : '[Tomorrow at] LT', + nextWeek : 'dddd [at] LT', + lastDay : '[Yesterday at] LT', + lastWeek : '[Last] dddd [at] LT', + sameElse : 'L' + }, + relativeTime : { + future : 'in %s', + past : '%s ago', + s : 'a few seconds', + ss : '%d seconds', + m : 'a minute', + mm : '%d minutes', + h : 'an hour', + hh : '%d hours', + d : 'a day', + dd : '%d days', + M : 'a month', + MM : '%d months', + y : 'a year', + yy : '%d years' + }, + dayOfMonthOrdinalParse: /\d{1,2}(st|nd|rd|th)/, + ordinal : function (number) { + var b = number % 10, + output = (~~(number % 100 / 10) === 1) ? 'th' : + (b === 1) ? 'st' : + (b === 2) ? 'nd' : + (b === 3) ? 'rd' : 'th'; + return number + output; + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } + }); - colNumber = Math.max(colNumber, 0); - const location = { - start: { - column: colNumber, - line: lineNumber - } - }; - return codeFrameColumns(rawLines, location, opts); -} + return enAu; -/***/ }), -/* 63 */ -/***/ (function(module, exports, __webpack_require__) { +}))); -"use strict"; +/***/ }), +/* 70 */ +/***/ (function(module, exports, __webpack_require__) { -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.shouldHighlight = shouldHighlight; -exports.getChalk = getChalk; -exports.default = highlight; +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var enCa = moment.defineLocale('en-ca', { + months : 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_'), + monthsShort : 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_'), + weekdays : 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_'), + weekdaysShort : 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_'), + weekdaysMin : 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_'), + longDateFormat : { + LT : 'h:mm A', + LTS : 'h:mm:ss A', + L : 'YYYY-MM-DD', + LL : 'MMMM D, YYYY', + LLL : 'MMMM D, YYYY h:mm A', + LLLL : 'dddd, MMMM D, YYYY h:mm A' + }, + calendar : { + sameDay : '[Today at] LT', + nextDay : '[Tomorrow at] LT', + nextWeek : 'dddd [at] LT', + lastDay : '[Yesterday at] LT', + lastWeek : '[Last] dddd [at] LT', + sameElse : 'L' + }, + relativeTime : { + future : 'in %s', + past : '%s ago', + s : 'a few seconds', + ss : '%d seconds', + m : 'a minute', + mm : '%d minutes', + h : 'an hour', + hh : '%d hours', + d : 'a day', + dd : '%d days', + M : 'a month', + MM : '%d months', + y : 'a year', + yy : '%d years' + }, + dayOfMonthOrdinalParse: /\d{1,2}(st|nd|rd|th)/, + ordinal : function (number) { + var b = number % 10, + output = (~~(number % 100 / 10) === 1) ? 'th' : + (b === 1) ? 'st' : + (b === 2) ? 'nd' : + (b === 3) ? 'rd' : 'th'; + return number + output; + } + }); -function _jsTokens() { - const data = _interopRequireWildcard(__webpack_require__(64)); + return enCa; - _jsTokens = function () { - return data; - }; +}))); - return data; -} -function _esutils() { - const data = _interopRequireDefault(__webpack_require__(65)); +/***/ }), +/* 71 */ +/***/ (function(module, exports, __webpack_require__) { - _esutils = function () { - return data; - }; +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var enGb = moment.defineLocale('en-gb', { + months : 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_'), + monthsShort : 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_'), + weekdays : 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_'), + weekdaysShort : 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_'), + weekdaysMin : 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_'), + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY HH:mm', + LLLL : 'dddd, D MMMM YYYY HH:mm' + }, + calendar : { + sameDay : '[Today at] LT', + nextDay : '[Tomorrow at] LT', + nextWeek : 'dddd [at] LT', + lastDay : '[Yesterday at] LT', + lastWeek : '[Last] dddd [at] LT', + sameElse : 'L' + }, + relativeTime : { + future : 'in %s', + past : '%s ago', + s : 'a few seconds', + ss : '%d seconds', + m : 'a minute', + mm : '%d minutes', + h : 'an hour', + hh : '%d hours', + d : 'a day', + dd : '%d days', + M : 'a month', + MM : '%d months', + y : 'a year', + yy : '%d years' + }, + dayOfMonthOrdinalParse: /\d{1,2}(st|nd|rd|th)/, + ordinal : function (number) { + var b = number % 10, + output = (~~(number % 100 / 10) === 1) ? 'th' : + (b === 1) ? 'st' : + (b === 2) ? 'nd' : + (b === 3) ? 'rd' : 'th'; + return number + output; + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } + }); - return data; -} + return enGb; -function _chalk() { - const data = _interopRequireDefault(__webpack_require__(69)); +}))); - _chalk = function () { - return data; - }; - return data; -} +/***/ }), +/* 72 */ +/***/ (function(module, exports, __webpack_require__) { -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var enIe = moment.defineLocale('en-ie', { + months : 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_'), + monthsShort : 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_'), + weekdays : 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_'), + weekdaysShort : 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_'), + weekdaysMin : 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_'), + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY HH:mm', + LLLL : 'dddd D MMMM YYYY HH:mm' + }, + calendar : { + sameDay : '[Today at] LT', + nextDay : '[Tomorrow at] LT', + nextWeek : 'dddd [at] LT', + lastDay : '[Yesterday at] LT', + lastWeek : '[Last] dddd [at] LT', + sameElse : 'L' + }, + relativeTime : { + future : 'in %s', + past : '%s ago', + s : 'a few seconds', + ss : '%d seconds', + m : 'a minute', + mm : '%d minutes', + h : 'an hour', + hh : '%d hours', + d : 'a day', + dd : '%d days', + M : 'a month', + MM : '%d months', + y : 'a year', + yy : '%d years' + }, + dayOfMonthOrdinalParse: /\d{1,2}(st|nd|rd|th)/, + ordinal : function (number) { + var b = number % 10, + output = (~~(number % 100 / 10) === 1) ? 'th' : + (b === 1) ? 'st' : + (b === 2) ? 'nd' : + (b === 3) ? 'rd' : 'th'; + return number + output; + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } + }); -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; return newObj; } } + return enIe; -function getDefs(chalk) { - return { - keyword: chalk.cyan, - capitalized: chalk.yellow, - jsx_tag: chalk.yellow, - punctuator: chalk.yellow, - number: chalk.magenta, - string: chalk.green, - regex: chalk.magenta, - comment: chalk.grey, - invalid: chalk.white.bgRed.bold - }; -} +}))); -const NEWLINE = /\r\n|[\n\r\u2028\u2029]/; -const JSX_TAG = /^[a-z][\w-]*$/i; -const BRACKET = /^[()[\]{}]$/; -function getTokenType(match) { - const [offset, text] = match.slice(-2); - const token = (0, _jsTokens().matchToToken)(match); +/***/ }), +/* 73 */ +/***/ (function(module, exports, __webpack_require__) { - if (token.type === "name") { - if (_esutils().default.keyword.isReservedWordES6(token.value)) { - return "keyword"; - } +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var enIl = moment.defineLocale('en-il', { + months : 'January_February_March_April_May_June_July_August_September_October_November_December'.split('_'), + monthsShort : 'Jan_Feb_Mar_Apr_May_Jun_Jul_Aug_Sep_Oct_Nov_Dec'.split('_'), + weekdays : 'Sunday_Monday_Tuesday_Wednesday_Thursday_Friday_Saturday'.split('_'), + weekdaysShort : 'Sun_Mon_Tue_Wed_Thu_Fri_Sat'.split('_'), + weekdaysMin : 'Su_Mo_Tu_We_Th_Fr_Sa'.split('_'), + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY HH:mm', + LLLL : 'dddd, D MMMM YYYY HH:mm' + }, + calendar : { + sameDay : '[Today at] LT', + nextDay : '[Tomorrow at] LT', + nextWeek : 'dddd [at] LT', + lastDay : '[Yesterday at] LT', + lastWeek : '[Last] dddd [at] LT', + sameElse : 'L' + }, + relativeTime : { + future : 'in %s', + past : '%s ago', + s : 'a few seconds', + m : 'a minute', + mm : '%d minutes', + h : 'an hour', + hh : '%d hours', + d : 'a day', + dd : '%d days', + M : 'a month', + MM : '%d months', + y : 'a year', + yy : '%d years' + }, + dayOfMonthOrdinalParse: /\d{1,2}(st|nd|rd|th)/, + ordinal : function (number) { + var b = number % 10, + output = (~~(number % 100 / 10) === 1) ? 'th' : + (b === 1) ? 'st' : + (b === 2) ? 'nd' : + (b === 3) ? 'rd' : 'th'; + return number + output; + } + }); - if (JSX_TAG.test(token.value) && (text[offset - 1] === "<" || text.substr(offset - 2, 2) == " colorize(str)).join("\n"); - } else { - return args[0]; - } - }); -} +}))); -function shouldHighlight(options) { - return _chalk().default.supportsColor || options.forceColor; -} -function getChalk(options) { - let chalk = _chalk().default; +/***/ }), +/* 75 */ +/***/ (function(module, exports, __webpack_require__) { - if (options.forceColor) { - chalk = new (_chalk().default.constructor)({ - enabled: true, - level: 1 +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var eo = moment.defineLocale('eo', { + months : 'januaro_februaro_marto_aprilo_majo_junio_julio_aŭgusto_septembro_oktobro_novembro_decembro'.split('_'), + monthsShort : 'jan_feb_mar_apr_maj_jun_jul_aŭg_sep_okt_nov_dec'.split('_'), + weekdays : 'dimanĉo_lundo_mardo_merkredo_ĵaŭdo_vendredo_sabato'.split('_'), + weekdaysShort : 'dim_lun_mard_merk_ĵaŭ_ven_sab'.split('_'), + weekdaysMin : 'di_lu_ma_me_ĵa_ve_sa'.split('_'), + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'YYYY-MM-DD', + LL : 'D[-a de] MMMM, YYYY', + LLL : 'D[-a de] MMMM, YYYY HH:mm', + LLLL : 'dddd, [la] D[-a de] MMMM, YYYY HH:mm' + }, + meridiemParse: /[ap]\.t\.m/i, + isPM: function (input) { + return input.charAt(0).toLowerCase() === 'p'; + }, + meridiem : function (hours, minutes, isLower) { + if (hours > 11) { + return isLower ? 'p.t.m.' : 'P.T.M.'; + } else { + return isLower ? 'a.t.m.' : 'A.T.M.'; + } + }, + calendar : { + sameDay : '[Hodiaŭ je] LT', + nextDay : '[Morgaŭ je] LT', + nextWeek : 'dddd [je] LT', + lastDay : '[Hieraŭ je] LT', + lastWeek : '[pasinta] dddd [je] LT', + sameElse : 'L' + }, + relativeTime : { + future : 'post %s', + past : 'antaŭ %s', + s : 'sekundoj', + ss : '%d sekundoj', + m : 'minuto', + mm : '%d minutoj', + h : 'horo', + hh : '%d horoj', + d : 'tago',//ne 'diurno', ĉar estas uzita por proksimumo + dd : '%d tagoj', + M : 'monato', + MM : '%d monatoj', + y : 'jaro', + yy : '%d jaroj' + }, + dayOfMonthOrdinalParse: /\d{1,2}a/, + ordinal : '%da', + week : { + dow : 1, // Monday is the first day of the week. + doy : 7 // The week that contains Jan 7th is the first week of the year. + } }); - } - return chalk; -} + return eo; + +}))); -function highlight(code, options = {}) { - if (shouldHighlight(options)) { - const chalk = getChalk(options); - const defs = getDefs(chalk); - return highlightTokens(defs, code); - } else { - return code; - } -} /***/ }), -/* 64 */ -/***/ (function(module, exports) { +/* 76 */ +/***/ (function(module, exports, __webpack_require__) { -// Copyright 2014, 2015, 2016, 2017, 2018 Simon Lydell -// License: MIT. (See LICENSE.) +//! moment.js locale configuration -Object.defineProperty(exports, "__esModule", { - value: true -}) +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; -// This regex comes from regex.coffee, and is inserted here by generate-index.js -// (run `npm run build`). -exports.default = /((['"])(?:(?!\2|\\).|\\(?:\r\n|[\s\S]))*(\2)?|`(?:[^`\\$]|\\[\s\S]|\$(?!\{)|\$\{(?:[^{}]|\{[^}]*\}?)*\}?)*(`)?)|(\/\/.*)|(\/\*(?:[^*]|\*(?!\/))*(\*\/)?)|(\/(?!\*)(?:\[(?:(?![\]\\]).|\\.)*\]|(?![\/\]\\]).|\\.)+\/(?:(?!\s*(?:\b|[\u0080-\uFFFF$\\'"~({]|[+\-!](?!=)|\.?\d))|[gmiyus]{1,6}\b(?![\u0080-\uFFFF$\\]|\s*(?:[+\-*%&|^<>!=?({]|\/(?![\/*])))))|(0[xX][\da-fA-F]+|0[oO][0-7]+|0[bB][01]+|(?:\d*\.\d+|\d+\.?)(?:[eE][+-]?\d+)?)|((?!\d)(?:(?!\s)[$\w\u0080-\uFFFF]|\\u[\da-fA-F]{4}|\\u\{[\da-fA-F]+\})+)|(--|\+\+|&&|\|\||=>|\.{3}|(?:[+\-\/%&|^]|\*{1,2}|<{1,2}|>{1,3}|!=?|={1,2})=?|[?~.,:;[\](){}])|(\s+)|(^$|[\s\S])/g -exports.matchToToken = function(match) { - var token = {type: "invalid", value: match[0], closed: undefined} - if (match[ 1]) token.type = "string" , token.closed = !!(match[3] || match[4]) - else if (match[ 5]) token.type = "comment" - else if (match[ 6]) token.type = "comment", token.closed = !!match[7] - else if (match[ 8]) token.type = "regex" - else if (match[ 9]) token.type = "number" - else if (match[10]) token.type = "name" - else if (match[11]) token.type = "punctuator" - else if (match[12]) token.type = "whitespace" - return token -} + var monthsShortDot = 'ene._feb._mar._abr._may._jun._jul._ago._sep._oct._nov._dic.'.split('_'), + monthsShort = 'ene_feb_mar_abr_may_jun_jul_ago_sep_oct_nov_dic'.split('_'); + + var monthsParse = [/^ene/i, /^feb/i, /^mar/i, /^abr/i, /^may/i, /^jun/i, /^jul/i, /^ago/i, /^sep/i, /^oct/i, /^nov/i, /^dic/i]; + var monthsRegex = /^(enero|febrero|marzo|abril|mayo|junio|julio|agosto|septiembre|octubre|noviembre|diciembre|ene\.?|feb\.?|mar\.?|abr\.?|may\.?|jun\.?|jul\.?|ago\.?|sep\.?|oct\.?|nov\.?|dic\.?)/i; + + var es = moment.defineLocale('es', { + months : 'enero_febrero_marzo_abril_mayo_junio_julio_agosto_septiembre_octubre_noviembre_diciembre'.split('_'), + monthsShort : function (m, format) { + if (!m) { + return monthsShortDot; + } else if (/-MMM-/.test(format)) { + return monthsShort[m.month()]; + } else { + return monthsShortDot[m.month()]; + } + }, + monthsRegex : monthsRegex, + monthsShortRegex : monthsRegex, + monthsStrictRegex : /^(enero|febrero|marzo|abril|mayo|junio|julio|agosto|septiembre|octubre|noviembre|diciembre)/i, + monthsShortStrictRegex : /^(ene\.?|feb\.?|mar\.?|abr\.?|may\.?|jun\.?|jul\.?|ago\.?|sep\.?|oct\.?|nov\.?|dic\.?)/i, + monthsParse : monthsParse, + longMonthsParse : monthsParse, + shortMonthsParse : monthsParse, + weekdays : 'domingo_lunes_martes_miércoles_jueves_viernes_sábado'.split('_'), + weekdaysShort : 'dom._lun._mar._mié._jue._vie._sáb.'.split('_'), + weekdaysMin : 'do_lu_ma_mi_ju_vi_sá'.split('_'), + weekdaysParseExact : true, + longDateFormat : { + LT : 'H:mm', + LTS : 'H:mm:ss', + L : 'DD/MM/YYYY', + LL : 'D [de] MMMM [de] YYYY', + LLL : 'D [de] MMMM [de] YYYY H:mm', + LLLL : 'dddd, D [de] MMMM [de] YYYY H:mm' + }, + calendar : { + sameDay : function () { + return '[hoy a la' + ((this.hours() !== 1) ? 's' : '') + '] LT'; + }, + nextDay : function () { + return '[mañana a la' + ((this.hours() !== 1) ? 's' : '') + '] LT'; + }, + nextWeek : function () { + return 'dddd [a la' + ((this.hours() !== 1) ? 's' : '') + '] LT'; + }, + lastDay : function () { + return '[ayer a la' + ((this.hours() !== 1) ? 's' : '') + '] LT'; + }, + lastWeek : function () { + return '[el] dddd [pasado a la' + ((this.hours() !== 1) ? 's' : '') + '] LT'; + }, + sameElse : 'L' + }, + relativeTime : { + future : 'en %s', + past : 'hace %s', + s : 'unos segundos', + ss : '%d segundos', + m : 'un minuto', + mm : '%d minutos', + h : 'una hora', + hh : '%d horas', + d : 'un día', + dd : '%d días', + M : 'un mes', + MM : '%d meses', + y : 'un año', + yy : '%d años' + }, + dayOfMonthOrdinalParse : /\d{1,2}º/, + ordinal : '%dº', + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } + }); + + return es; + +}))); /***/ }), -/* 65 */ +/* 77 */ /***/ (function(module, exports, __webpack_require__) { -/* - Copyright (C) 2013 Yusuke Suzuki +//! moment.js locale configuration - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY - DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ + var monthsShortDot = 'ene._feb._mar._abr._may._jun._jul._ago._sep._oct._nov._dic.'.split('_'), + monthsShort = 'ene_feb_mar_abr_may_jun_jul_ago_sep_oct_nov_dic'.split('_'); + var monthsParse = [/^ene/i, /^feb/i, /^mar/i, /^abr/i, /^may/i, /^jun/i, /^jul/i, /^ago/i, /^sep/i, /^oct/i, /^nov/i, /^dic/i]; + var monthsRegex = /^(enero|febrero|marzo|abril|mayo|junio|julio|agosto|septiembre|octubre|noviembre|diciembre|ene\.?|feb\.?|mar\.?|abr\.?|may\.?|jun\.?|jul\.?|ago\.?|sep\.?|oct\.?|nov\.?|dic\.?)/i; -(function () { - 'use strict'; + var esDo = moment.defineLocale('es-do', { + months : 'enero_febrero_marzo_abril_mayo_junio_julio_agosto_septiembre_octubre_noviembre_diciembre'.split('_'), + monthsShort : function (m, format) { + if (!m) { + return monthsShortDot; + } else if (/-MMM-/.test(format)) { + return monthsShort[m.month()]; + } else { + return monthsShortDot[m.month()]; + } + }, + monthsRegex: monthsRegex, + monthsShortRegex: monthsRegex, + monthsStrictRegex: /^(enero|febrero|marzo|abril|mayo|junio|julio|agosto|septiembre|octubre|noviembre|diciembre)/i, + monthsShortStrictRegex: /^(ene\.?|feb\.?|mar\.?|abr\.?|may\.?|jun\.?|jul\.?|ago\.?|sep\.?|oct\.?|nov\.?|dic\.?)/i, + monthsParse: monthsParse, + longMonthsParse: monthsParse, + shortMonthsParse: monthsParse, + weekdays : 'domingo_lunes_martes_miércoles_jueves_viernes_sábado'.split('_'), + weekdaysShort : 'dom._lun._mar._mié._jue._vie._sáb.'.split('_'), + weekdaysMin : 'do_lu_ma_mi_ju_vi_sá'.split('_'), + weekdaysParseExact : true, + longDateFormat : { + LT : 'h:mm A', + LTS : 'h:mm:ss A', + L : 'DD/MM/YYYY', + LL : 'D [de] MMMM [de] YYYY', + LLL : 'D [de] MMMM [de] YYYY h:mm A', + LLLL : 'dddd, D [de] MMMM [de] YYYY h:mm A' + }, + calendar : { + sameDay : function () { + return '[hoy a la' + ((this.hours() !== 1) ? 's' : '') + '] LT'; + }, + nextDay : function () { + return '[mañana a la' + ((this.hours() !== 1) ? 's' : '') + '] LT'; + }, + nextWeek : function () { + return 'dddd [a la' + ((this.hours() !== 1) ? 's' : '') + '] LT'; + }, + lastDay : function () { + return '[ayer a la' + ((this.hours() !== 1) ? 's' : '') + '] LT'; + }, + lastWeek : function () { + return '[el] dddd [pasado a la' + ((this.hours() !== 1) ? 's' : '') + '] LT'; + }, + sameElse : 'L' + }, + relativeTime : { + future : 'en %s', + past : 'hace %s', + s : 'unos segundos', + ss : '%d segundos', + m : 'un minuto', + mm : '%d minutos', + h : 'una hora', + hh : '%d horas', + d : 'un día', + dd : '%d días', + M : 'un mes', + MM : '%d meses', + y : 'un año', + yy : '%d años' + }, + dayOfMonthOrdinalParse : /\d{1,2}º/, + ordinal : '%dº', + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } + }); - exports.ast = __webpack_require__(66); - exports.code = __webpack_require__(67); - exports.keyword = __webpack_require__(68); -}()); -/* vim: set sw=4 ts=4 et tw=80 : */ + return esDo; + +}))); /***/ }), -/* 66 */ -/***/ (function(module, exports) { +/* 78 */ +/***/ (function(module, exports, __webpack_require__) { -/* - Copyright (C) 2013 Yusuke Suzuki +//! moment.js locale configuration - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS IS' - AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY - DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ + var monthsShortDot = 'ene._feb._mar._abr._may._jun._jul._ago._sep._oct._nov._dic.'.split('_'), + monthsShort = 'ene_feb_mar_abr_may_jun_jul_ago_sep_oct_nov_dic'.split('_'); -(function () { - 'use strict'; + var monthsParse = [/^ene/i, /^feb/i, /^mar/i, /^abr/i, /^may/i, /^jun/i, /^jul/i, /^ago/i, /^sep/i, /^oct/i, /^nov/i, /^dic/i]; + var monthsRegex = /^(enero|febrero|marzo|abril|mayo|junio|julio|agosto|septiembre|octubre|noviembre|diciembre|ene\.?|feb\.?|mar\.?|abr\.?|may\.?|jun\.?|jul\.?|ago\.?|sep\.?|oct\.?|nov\.?|dic\.?)/i; - function isExpression(node) { - if (node == null) { return false; } - switch (node.type) { - case 'ArrayExpression': - case 'AssignmentExpression': - case 'BinaryExpression': - case 'CallExpression': - case 'ConditionalExpression': - case 'FunctionExpression': - case 'Identifier': - case 'Literal': - case 'LogicalExpression': - case 'MemberExpression': - case 'NewExpression': - case 'ObjectExpression': - case 'SequenceExpression': - case 'ThisExpression': - case 'UnaryExpression': - case 'UpdateExpression': - return true; + var esUs = moment.defineLocale('es-us', { + months : 'enero_febrero_marzo_abril_mayo_junio_julio_agosto_septiembre_octubre_noviembre_diciembre'.split('_'), + monthsShort : function (m, format) { + if (!m) { + return monthsShortDot; + } else if (/-MMM-/.test(format)) { + return monthsShort[m.month()]; + } else { + return monthsShortDot[m.month()]; + } + }, + monthsRegex: monthsRegex, + monthsShortRegex: monthsRegex, + monthsStrictRegex: /^(enero|febrero|marzo|abril|mayo|junio|julio|agosto|septiembre|octubre|noviembre|diciembre)/i, + monthsShortStrictRegex: /^(ene\.?|feb\.?|mar\.?|abr\.?|may\.?|jun\.?|jul\.?|ago\.?|sep\.?|oct\.?|nov\.?|dic\.?)/i, + monthsParse: monthsParse, + longMonthsParse: monthsParse, + shortMonthsParse: monthsParse, + weekdays : 'domingo_lunes_martes_miércoles_jueves_viernes_sábado'.split('_'), + weekdaysShort : 'dom._lun._mar._mié._jue._vie._sáb.'.split('_'), + weekdaysMin : 'do_lu_ma_mi_ju_vi_sá'.split('_'), + weekdaysParseExact : true, + longDateFormat : { + LT : 'h:mm A', + LTS : 'h:mm:ss A', + L : 'MM/DD/YYYY', + LL : 'D [de] MMMM [de] YYYY', + LLL : 'D [de] MMMM [de] YYYY h:mm A', + LLLL : 'dddd, D [de] MMMM [de] YYYY h:mm A' + }, + calendar : { + sameDay : function () { + return '[hoy a la' + ((this.hours() !== 1) ? 's' : '') + '] LT'; + }, + nextDay : function () { + return '[mañana a la' + ((this.hours() !== 1) ? 's' : '') + '] LT'; + }, + nextWeek : function () { + return 'dddd [a la' + ((this.hours() !== 1) ? 's' : '') + '] LT'; + }, + lastDay : function () { + return '[ayer a la' + ((this.hours() !== 1) ? 's' : '') + '] LT'; + }, + lastWeek : function () { + return '[el] dddd [pasado a la' + ((this.hours() !== 1) ? 's' : '') + '] LT'; + }, + sameElse : 'L' + }, + relativeTime : { + future : 'en %s', + past : 'hace %s', + s : 'unos segundos', + ss : '%d segundos', + m : 'un minuto', + mm : '%d minutos', + h : 'una hora', + hh : '%d horas', + d : 'un día', + dd : '%d días', + M : 'un mes', + MM : '%d meses', + y : 'un año', + yy : '%d años' + }, + dayOfMonthOrdinalParse : /\d{1,2}º/, + ordinal : '%dº', + week : { + dow : 0, // Sunday is the first day of the week. + doy : 6 // The week that contains Jan 6th is the first week of the year. } - return false; - } + }); - function isIterationStatement(node) { - if (node == null) { return false; } - switch (node.type) { - case 'DoWhileStatement': - case 'ForInStatement': - case 'ForStatement': - case 'WhileStatement': - return true; - } - return false; - } + return esUs; - function isStatement(node) { - if (node == null) { return false; } - switch (node.type) { - case 'BlockStatement': - case 'BreakStatement': - case 'ContinueStatement': - case 'DebuggerStatement': - case 'DoWhileStatement': - case 'EmptyStatement': - case 'ExpressionStatement': - case 'ForInStatement': - case 'ForStatement': - case 'IfStatement': - case 'LabeledStatement': - case 'ReturnStatement': - case 'SwitchStatement': - case 'ThrowStatement': - case 'TryStatement': - case 'VariableDeclaration': - case 'WhileStatement': - case 'WithStatement': - return true; - } - return false; - } +}))); - function isSourceElement(node) { - return isStatement(node) || node != null && node.type === 'FunctionDeclaration'; - } - function trailingStatement(node) { - switch (node.type) { - case 'IfStatement': - if (node.alternate != null) { - return node.alternate; - } - return node.consequent; +/***/ }), +/* 79 */ +/***/ (function(module, exports, __webpack_require__) { - case 'LabeledStatement': - case 'ForStatement': - case 'ForInStatement': - case 'WhileStatement': - case 'WithStatement': - return node.body; +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + function processRelativeTime(number, withoutSuffix, key, isFuture) { + var format = { + 's' : ['mõne sekundi', 'mõni sekund', 'paar sekundit'], + 'ss': [number + 'sekundi', number + 'sekundit'], + 'm' : ['ühe minuti', 'üks minut'], + 'mm': [number + ' minuti', number + ' minutit'], + 'h' : ['ühe tunni', 'tund aega', 'üks tund'], + 'hh': [number + ' tunni', number + ' tundi'], + 'd' : ['ühe päeva', 'üks päev'], + 'M' : ['kuu aja', 'kuu aega', 'üks kuu'], + 'MM': [number + ' kuu', number + ' kuud'], + 'y' : ['ühe aasta', 'aasta', 'üks aasta'], + 'yy': [number + ' aasta', number + ' aastat'] + }; + if (withoutSuffix) { + return format[key][2] ? format[key][2] : format[key][1]; + } + return isFuture ? format[key][0] : format[key][1]; + } + + var et = moment.defineLocale('et', { + months : 'jaanuar_veebruar_märts_aprill_mai_juuni_juuli_august_september_oktoober_november_detsember'.split('_'), + monthsShort : 'jaan_veebr_märts_apr_mai_juuni_juuli_aug_sept_okt_nov_dets'.split('_'), + weekdays : 'pühapäev_esmaspäev_teisipäev_kolmapäev_neljapäev_reede_laupäev'.split('_'), + weekdaysShort : 'P_E_T_K_N_R_L'.split('_'), + weekdaysMin : 'P_E_T_K_N_R_L'.split('_'), + longDateFormat : { + LT : 'H:mm', + LTS : 'H:mm:ss', + L : 'DD.MM.YYYY', + LL : 'D. MMMM YYYY', + LLL : 'D. MMMM YYYY H:mm', + LLLL : 'dddd, D. MMMM YYYY H:mm' + }, + calendar : { + sameDay : '[Täna,] LT', + nextDay : '[Homme,] LT', + nextWeek : '[Järgmine] dddd LT', + lastDay : '[Eile,] LT', + lastWeek : '[Eelmine] dddd LT', + sameElse : 'L' + }, + relativeTime : { + future : '%s pärast', + past : '%s tagasi', + s : processRelativeTime, + ss : processRelativeTime, + m : processRelativeTime, + mm : processRelativeTime, + h : processRelativeTime, + hh : processRelativeTime, + d : processRelativeTime, + dd : '%d päeva', + M : processRelativeTime, + MM : processRelativeTime, + y : processRelativeTime, + yy : processRelativeTime + }, + dayOfMonthOrdinalParse: /\d{1,2}\./, + ordinal : '%d.', + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. } - return null; - } + }); - function isProblematicIfStatement(node) { - var current; + return et; - if (node.type !== 'IfStatement') { - return false; - } - if (node.alternate == null) { - return false; - } - current = node.consequent; - do { - if (current.type === 'IfStatement') { - if (current.alternate == null) { - return true; - } - } - current = trailingStatement(current); - } while (current); +}))); - return false; - } - module.exports = { - isExpression: isExpression, - isStatement: isStatement, - isIterationStatement: isIterationStatement, - isSourceElement: isSourceElement, - isProblematicIfStatement: isProblematicIfStatement, +/***/ }), +/* 80 */ +/***/ (function(module, exports, __webpack_require__) { - trailingStatement: trailingStatement - }; -}()); -/* vim: set sw=4 ts=4 et tw=80 : */ +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var eu = moment.defineLocale('eu', { + months : 'urtarrila_otsaila_martxoa_apirila_maiatza_ekaina_uztaila_abuztua_iraila_urria_azaroa_abendua'.split('_'), + monthsShort : 'urt._ots._mar._api._mai._eka._uzt._abu._ira._urr._aza._abe.'.split('_'), + monthsParseExact : true, + weekdays : 'igandea_astelehena_asteartea_asteazkena_osteguna_ostirala_larunbata'.split('_'), + weekdaysShort : 'ig._al._ar._az._og._ol._lr.'.split('_'), + weekdaysMin : 'ig_al_ar_az_og_ol_lr'.split('_'), + weekdaysParseExact : true, + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'YYYY-MM-DD', + LL : 'YYYY[ko] MMMM[ren] D[a]', + LLL : 'YYYY[ko] MMMM[ren] D[a] HH:mm', + LLLL : 'dddd, YYYY[ko] MMMM[ren] D[a] HH:mm', + l : 'YYYY-M-D', + ll : 'YYYY[ko] MMM D[a]', + lll : 'YYYY[ko] MMM D[a] HH:mm', + llll : 'ddd, YYYY[ko] MMM D[a] HH:mm' + }, + calendar : { + sameDay : '[gaur] LT[etan]', + nextDay : '[bihar] LT[etan]', + nextWeek : 'dddd LT[etan]', + lastDay : '[atzo] LT[etan]', + lastWeek : '[aurreko] dddd LT[etan]', + sameElse : 'L' + }, + relativeTime : { + future : '%s barru', + past : 'duela %s', + s : 'segundo batzuk', + ss : '%d segundo', + m : 'minutu bat', + mm : '%d minutu', + h : 'ordu bat', + hh : '%d ordu', + d : 'egun bat', + dd : '%d egun', + M : 'hilabete bat', + MM : '%d hilabete', + y : 'urte bat', + yy : '%d urte' + }, + dayOfMonthOrdinalParse: /\d{1,2}\./, + ordinal : '%d.', + week : { + dow : 1, // Monday is the first day of the week. + doy : 7 // The week that contains Jan 7th is the first week of the year. + } + }); + + return eu; + +}))); /***/ }), -/* 67 */ -/***/ (function(module, exports) { +/* 81 */ +/***/ (function(module, exports, __webpack_require__) { -/* - Copyright (C) 2013-2014 Yusuke Suzuki - Copyright (C) 2014 Ivan Nikulin +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var symbolMap = { + '1': '۱', + '2': '۲', + '3': '۳', + '4': '۴', + '5': '۵', + '6': '۶', + '7': '۷', + '8': '۸', + '9': '۹', + '0': '۰' + }, numberMap = { + '۱': '1', + '۲': '2', + '۳': '3', + '۴': '4', + '۵': '5', + '۶': '6', + '۷': '7', + '۸': '8', + '۹': '9', + '۰': '0' + }; + + var fa = moment.defineLocale('fa', { + months : 'ژانویه_فوریه_مارس_آوریل_مه_ژوئن_ژوئیه_اوت_سپتامبر_اکتبر_نوامبر_دسامبر'.split('_'), + monthsShort : 'ژانویه_فوریه_مارس_آوریل_مه_ژوئن_ژوئیه_اوت_سپتامبر_اکتبر_نوامبر_دسامبر'.split('_'), + weekdays : 'یک\u200cشنبه_دوشنبه_سه\u200cشنبه_چهارشنبه_پنج\u200cشنبه_جمعه_شنبه'.split('_'), + weekdaysShort : 'یک\u200cشنبه_دوشنبه_سه\u200cشنبه_چهارشنبه_پنج\u200cشنبه_جمعه_شنبه'.split('_'), + weekdaysMin : 'ی_د_س_چ_پ_ج_ش'.split('_'), + weekdaysParseExact : true, + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY HH:mm', + LLLL : 'dddd, D MMMM YYYY HH:mm' + }, + meridiemParse: /قبل از ظهر|بعد از ظهر/, + isPM: function (input) { + return /بعد از ظهر/.test(input); + }, + meridiem : function (hour, minute, isLower) { + if (hour < 12) { + return 'قبل از ظهر'; + } else { + return 'بعد از ظهر'; + } + }, + calendar : { + sameDay : '[امروز ساعت] LT', + nextDay : '[فردا ساعت] LT', + nextWeek : 'dddd [ساعت] LT', + lastDay : '[دیروز ساعت] LT', + lastWeek : 'dddd [پیش] [ساعت] LT', + sameElse : 'L' + }, + relativeTime : { + future : 'در %s', + past : '%s پیش', + s : 'چند ثانیه', + ss : 'ثانیه d%', + m : 'یک دقیقه', + mm : '%d دقیقه', + h : 'یک ساعت', + hh : '%d ساعت', + d : 'یک روز', + dd : '%d روز', + M : 'یک ماه', + MM : '%d ماه', + y : 'یک سال', + yy : '%d سال' + }, + preparse: function (string) { + return string.replace(/[۰-۹]/g, function (match) { + return numberMap[match]; + }).replace(/،/g, ','); + }, + postformat: function (string) { + return string.replace(/\d/g, function (match) { + return symbolMap[match]; + }).replace(/,/g, '،'); + }, + dayOfMonthOrdinalParse: /\d{1,2}م/, + ordinal : '%dم', + week : { + dow : 6, // Saturday is the first day of the week. + doy : 12 // The week that contains Jan 12th is the first week of the year. + } + }); - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: + return fa; - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. +}))); - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY - DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ -(function () { - 'use strict'; +/***/ }), +/* 82 */ +/***/ (function(module, exports, __webpack_require__) { - var ES6Regex, ES5Regex, NON_ASCII_WHITESPACES, IDENTIFIER_START, IDENTIFIER_PART, ch; +//! moment.js locale configuration - // See `tools/generate-identifier-regex.js`. - ES5Regex = { - // ECMAScript 5.1/Unicode v7.0.0 NonAsciiIdentifierStart: - NonAsciiIdentifierStart: /[\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0-\u08B2\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0980\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58\u0C59\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D60\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F8\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19C1-\u19C7\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2160-\u2188\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCC\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA66E\uA67F-\uA69D\uA6A0-\uA6EF\uA717-\uA71F\uA722-\uA788\uA78B-\uA78E\uA790-\uA7AD\uA7B0\uA7B1\uA7F7-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uA9E0-\uA9E4\uA9E6-\uA9EF\uA9FA-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB5F\uAB64\uAB65\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]/, - // ECMAScript 5.1/Unicode v7.0.0 NonAsciiIdentifierPart: - NonAsciiIdentifierPart: /[\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0300-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u0483-\u0487\u048A-\u052F\u0531-\u0556\u0559\u0561-\u0587\u0591-\u05BD\u05BF\u05C1\u05C2\u05C4\u05C5\u05C7\u05D0-\u05EA\u05F0-\u05F2\u0610-\u061A\u0620-\u0669\u066E-\u06D3\u06D5-\u06DC\u06DF-\u06E8\u06EA-\u06FC\u06FF\u0710-\u074A\u074D-\u07B1\u07C0-\u07F5\u07FA\u0800-\u082D\u0840-\u085B\u08A0-\u08B2\u08E4-\u0963\u0966-\u096F\u0971-\u0983\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BC-\u09C4\u09C7\u09C8\u09CB-\u09CE\u09D7\u09DC\u09DD\u09DF-\u09E3\u09E6-\u09F1\u0A01-\u0A03\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A3C\u0A3E-\u0A42\u0A47\u0A48\u0A4B-\u0A4D\u0A51\u0A59-\u0A5C\u0A5E\u0A66-\u0A75\u0A81-\u0A83\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABC-\u0AC5\u0AC7-\u0AC9\u0ACB-\u0ACD\u0AD0\u0AE0-\u0AE3\u0AE6-\u0AEF\u0B01-\u0B03\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3C-\u0B44\u0B47\u0B48\u0B4B-\u0B4D\u0B56\u0B57\u0B5C\u0B5D\u0B5F-\u0B63\u0B66-\u0B6F\u0B71\u0B82\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BBE-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCD\u0BD0\u0BD7\u0BE6-\u0BEF\u0C00-\u0C03\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D-\u0C44\u0C46-\u0C48\u0C4A-\u0C4D\u0C55\u0C56\u0C58\u0C59\u0C60-\u0C63\u0C66-\u0C6F\u0C81-\u0C83\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBC-\u0CC4\u0CC6-\u0CC8\u0CCA-\u0CCD\u0CD5\u0CD6\u0CDE\u0CE0-\u0CE3\u0CE6-\u0CEF\u0CF1\u0CF2\u0D01-\u0D03\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D-\u0D44\u0D46-\u0D48\u0D4A-\u0D4E\u0D57\u0D60-\u0D63\u0D66-\u0D6F\u0D7A-\u0D7F\u0D82\u0D83\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0DCA\u0DCF-\u0DD4\u0DD6\u0DD8-\u0DDF\u0DE6-\u0DEF\u0DF2\u0DF3\u0E01-\u0E3A\u0E40-\u0E4E\u0E50-\u0E59\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB9\u0EBB-\u0EBD\u0EC0-\u0EC4\u0EC6\u0EC8-\u0ECD\u0ED0-\u0ED9\u0EDC-\u0EDF\u0F00\u0F18\u0F19\u0F20-\u0F29\u0F35\u0F37\u0F39\u0F3E-\u0F47\u0F49-\u0F6C\u0F71-\u0F84\u0F86-\u0F97\u0F99-\u0FBC\u0FC6\u1000-\u1049\u1050-\u109D\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u135D-\u135F\u1380-\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F8\u1700-\u170C\u170E-\u1714\u1720-\u1734\u1740-\u1753\u1760-\u176C\u176E-\u1770\u1772\u1773\u1780-\u17D3\u17D7\u17DC\u17DD\u17E0-\u17E9\u180B-\u180D\u1810-\u1819\u1820-\u1877\u1880-\u18AA\u18B0-\u18F5\u1900-\u191E\u1920-\u192B\u1930-\u193B\u1946-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u19D0-\u19D9\u1A00-\u1A1B\u1A20-\u1A5E\u1A60-\u1A7C\u1A7F-\u1A89\u1A90-\u1A99\u1AA7\u1AB0-\u1ABD\u1B00-\u1B4B\u1B50-\u1B59\u1B6B-\u1B73\u1B80-\u1BF3\u1C00-\u1C37\u1C40-\u1C49\u1C4D-\u1C7D\u1CD0-\u1CD2\u1CD4-\u1CF6\u1CF8\u1CF9\u1D00-\u1DF5\u1DFC-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u200C\u200D\u203F\u2040\u2054\u2071\u207F\u2090-\u209C\u20D0-\u20DC\u20E1\u20E5-\u20F0\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2160-\u2188\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D7F-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2DE0-\u2DFF\u2E2F\u3005-\u3007\u3021-\u302F\u3031-\u3035\u3038-\u303C\u3041-\u3096\u3099\u309A\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCC\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA62B\uA640-\uA66F\uA674-\uA67D\uA67F-\uA69D\uA69F-\uA6F1\uA717-\uA71F\uA722-\uA788\uA78B-\uA78E\uA790-\uA7AD\uA7B0\uA7B1\uA7F7-\uA827\uA840-\uA873\uA880-\uA8C4\uA8D0-\uA8D9\uA8E0-\uA8F7\uA8FB\uA900-\uA92D\uA930-\uA953\uA960-\uA97C\uA980-\uA9C0\uA9CF-\uA9D9\uA9E0-\uA9FE\uAA00-\uAA36\uAA40-\uAA4D\uAA50-\uAA59\uAA60-\uAA76\uAA7A-\uAAC2\uAADB-\uAADD\uAAE0-\uAAEF\uAAF2-\uAAF6\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB5F\uAB64\uAB65\uABC0-\uABEA\uABEC\uABED\uABF0-\uABF9\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE00-\uFE0F\uFE20-\uFE2D\uFE33\uFE34\uFE4D-\uFE4F\uFE70-\uFE74\uFE76-\uFEFC\uFF10-\uFF19\uFF21-\uFF3A\uFF3F\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]/ - }; +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; - ES6Regex = { - // ECMAScript 6/Unicode v7.0.0 NonAsciiIdentifierStart: - NonAsciiIdentifierStart: /[\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0-\u08B2\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0980\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58\u0C59\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D60\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F8\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19C1-\u19C7\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2118-\u211D\u2124\u2126\u2128\u212A-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2160-\u2188\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303C\u3041-\u3096\u309B-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCC\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA66E\uA67F-\uA69D\uA6A0-\uA6EF\uA717-\uA71F\uA722-\uA788\uA78B-\uA78E\uA790-\uA7AD\uA7B0\uA7B1\uA7F7-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uA9E0-\uA9E4\uA9E6-\uA9EF\uA9FA-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB5F\uAB64\uAB65\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]|\uD800[\uDC00-\uDC0B\uDC0D-\uDC26\uDC28-\uDC3A\uDC3C\uDC3D\uDC3F-\uDC4D\uDC50-\uDC5D\uDC80-\uDCFA\uDD40-\uDD74\uDE80-\uDE9C\uDEA0-\uDED0\uDF00-\uDF1F\uDF30-\uDF4A\uDF50-\uDF75\uDF80-\uDF9D\uDFA0-\uDFC3\uDFC8-\uDFCF\uDFD1-\uDFD5]|\uD801[\uDC00-\uDC9D\uDD00-\uDD27\uDD30-\uDD63\uDE00-\uDF36\uDF40-\uDF55\uDF60-\uDF67]|\uD802[\uDC00-\uDC05\uDC08\uDC0A-\uDC35\uDC37\uDC38\uDC3C\uDC3F-\uDC55\uDC60-\uDC76\uDC80-\uDC9E\uDD00-\uDD15\uDD20-\uDD39\uDD80-\uDDB7\uDDBE\uDDBF\uDE00\uDE10-\uDE13\uDE15-\uDE17\uDE19-\uDE33\uDE60-\uDE7C\uDE80-\uDE9C\uDEC0-\uDEC7\uDEC9-\uDEE4\uDF00-\uDF35\uDF40-\uDF55\uDF60-\uDF72\uDF80-\uDF91]|\uD803[\uDC00-\uDC48]|\uD804[\uDC03-\uDC37\uDC83-\uDCAF\uDCD0-\uDCE8\uDD03-\uDD26\uDD50-\uDD72\uDD76\uDD83-\uDDB2\uDDC1-\uDDC4\uDDDA\uDE00-\uDE11\uDE13-\uDE2B\uDEB0-\uDEDE\uDF05-\uDF0C\uDF0F\uDF10\uDF13-\uDF28\uDF2A-\uDF30\uDF32\uDF33\uDF35-\uDF39\uDF3D\uDF5D-\uDF61]|\uD805[\uDC80-\uDCAF\uDCC4\uDCC5\uDCC7\uDD80-\uDDAE\uDE00-\uDE2F\uDE44\uDE80-\uDEAA]|\uD806[\uDCA0-\uDCDF\uDCFF\uDEC0-\uDEF8]|\uD808[\uDC00-\uDF98]|\uD809[\uDC00-\uDC6E]|[\uD80C\uD840-\uD868\uD86A-\uD86C][\uDC00-\uDFFF]|\uD80D[\uDC00-\uDC2E]|\uD81A[\uDC00-\uDE38\uDE40-\uDE5E\uDED0-\uDEED\uDF00-\uDF2F\uDF40-\uDF43\uDF63-\uDF77\uDF7D-\uDF8F]|\uD81B[\uDF00-\uDF44\uDF50\uDF93-\uDF9F]|\uD82C[\uDC00\uDC01]|\uD82F[\uDC00-\uDC6A\uDC70-\uDC7C\uDC80-\uDC88\uDC90-\uDC99]|\uD835[\uDC00-\uDC54\uDC56-\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD1E-\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD52-\uDEA5\uDEA8-\uDEC0\uDEC2-\uDEDA\uDEDC-\uDEFA\uDEFC-\uDF14\uDF16-\uDF34\uDF36-\uDF4E\uDF50-\uDF6E\uDF70-\uDF88\uDF8A-\uDFA8\uDFAA-\uDFC2\uDFC4-\uDFCB]|\uD83A[\uDC00-\uDCC4]|\uD83B[\uDE00-\uDE03\uDE05-\uDE1F\uDE21\uDE22\uDE24\uDE27\uDE29-\uDE32\uDE34-\uDE37\uDE39\uDE3B\uDE42\uDE47\uDE49\uDE4B\uDE4D-\uDE4F\uDE51\uDE52\uDE54\uDE57\uDE59\uDE5B\uDE5D\uDE5F\uDE61\uDE62\uDE64\uDE67-\uDE6A\uDE6C-\uDE72\uDE74-\uDE77\uDE79-\uDE7C\uDE7E\uDE80-\uDE89\uDE8B-\uDE9B\uDEA1-\uDEA3\uDEA5-\uDEA9\uDEAB-\uDEBB]|\uD869[\uDC00-\uDED6\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF34\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D]|\uD87E[\uDC00-\uDE1D]/, - // ECMAScript 6/Unicode v7.0.0 NonAsciiIdentifierPart: - NonAsciiIdentifierPart: /[\xAA\xB5\xB7\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0300-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u0483-\u0487\u048A-\u052F\u0531-\u0556\u0559\u0561-\u0587\u0591-\u05BD\u05BF\u05C1\u05C2\u05C4\u05C5\u05C7\u05D0-\u05EA\u05F0-\u05F2\u0610-\u061A\u0620-\u0669\u066E-\u06D3\u06D5-\u06DC\u06DF-\u06E8\u06EA-\u06FC\u06FF\u0710-\u074A\u074D-\u07B1\u07C0-\u07F5\u07FA\u0800-\u082D\u0840-\u085B\u08A0-\u08B2\u08E4-\u0963\u0966-\u096F\u0971-\u0983\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BC-\u09C4\u09C7\u09C8\u09CB-\u09CE\u09D7\u09DC\u09DD\u09DF-\u09E3\u09E6-\u09F1\u0A01-\u0A03\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A3C\u0A3E-\u0A42\u0A47\u0A48\u0A4B-\u0A4D\u0A51\u0A59-\u0A5C\u0A5E\u0A66-\u0A75\u0A81-\u0A83\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABC-\u0AC5\u0AC7-\u0AC9\u0ACB-\u0ACD\u0AD0\u0AE0-\u0AE3\u0AE6-\u0AEF\u0B01-\u0B03\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3C-\u0B44\u0B47\u0B48\u0B4B-\u0B4D\u0B56\u0B57\u0B5C\u0B5D\u0B5F-\u0B63\u0B66-\u0B6F\u0B71\u0B82\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BBE-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCD\u0BD0\u0BD7\u0BE6-\u0BEF\u0C00-\u0C03\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D-\u0C44\u0C46-\u0C48\u0C4A-\u0C4D\u0C55\u0C56\u0C58\u0C59\u0C60-\u0C63\u0C66-\u0C6F\u0C81-\u0C83\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBC-\u0CC4\u0CC6-\u0CC8\u0CCA-\u0CCD\u0CD5\u0CD6\u0CDE\u0CE0-\u0CE3\u0CE6-\u0CEF\u0CF1\u0CF2\u0D01-\u0D03\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D-\u0D44\u0D46-\u0D48\u0D4A-\u0D4E\u0D57\u0D60-\u0D63\u0D66-\u0D6F\u0D7A-\u0D7F\u0D82\u0D83\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0DCA\u0DCF-\u0DD4\u0DD6\u0DD8-\u0DDF\u0DE6-\u0DEF\u0DF2\u0DF3\u0E01-\u0E3A\u0E40-\u0E4E\u0E50-\u0E59\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB9\u0EBB-\u0EBD\u0EC0-\u0EC4\u0EC6\u0EC8-\u0ECD\u0ED0-\u0ED9\u0EDC-\u0EDF\u0F00\u0F18\u0F19\u0F20-\u0F29\u0F35\u0F37\u0F39\u0F3E-\u0F47\u0F49-\u0F6C\u0F71-\u0F84\u0F86-\u0F97\u0F99-\u0FBC\u0FC6\u1000-\u1049\u1050-\u109D\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u135D-\u135F\u1369-\u1371\u1380-\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F8\u1700-\u170C\u170E-\u1714\u1720-\u1734\u1740-\u1753\u1760-\u176C\u176E-\u1770\u1772\u1773\u1780-\u17D3\u17D7\u17DC\u17DD\u17E0-\u17E9\u180B-\u180D\u1810-\u1819\u1820-\u1877\u1880-\u18AA\u18B0-\u18F5\u1900-\u191E\u1920-\u192B\u1930-\u193B\u1946-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u19D0-\u19DA\u1A00-\u1A1B\u1A20-\u1A5E\u1A60-\u1A7C\u1A7F-\u1A89\u1A90-\u1A99\u1AA7\u1AB0-\u1ABD\u1B00-\u1B4B\u1B50-\u1B59\u1B6B-\u1B73\u1B80-\u1BF3\u1C00-\u1C37\u1C40-\u1C49\u1C4D-\u1C7D\u1CD0-\u1CD2\u1CD4-\u1CF6\u1CF8\u1CF9\u1D00-\u1DF5\u1DFC-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u200C\u200D\u203F\u2040\u2054\u2071\u207F\u2090-\u209C\u20D0-\u20DC\u20E1\u20E5-\u20F0\u2102\u2107\u210A-\u2113\u2115\u2118-\u211D\u2124\u2126\u2128\u212A-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2160-\u2188\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D7F-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2DE0-\u2DFF\u3005-\u3007\u3021-\u302F\u3031-\u3035\u3038-\u303C\u3041-\u3096\u3099-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCC\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA62B\uA640-\uA66F\uA674-\uA67D\uA67F-\uA69D\uA69F-\uA6F1\uA717-\uA71F\uA722-\uA788\uA78B-\uA78E\uA790-\uA7AD\uA7B0\uA7B1\uA7F7-\uA827\uA840-\uA873\uA880-\uA8C4\uA8D0-\uA8D9\uA8E0-\uA8F7\uA8FB\uA900-\uA92D\uA930-\uA953\uA960-\uA97C\uA980-\uA9C0\uA9CF-\uA9D9\uA9E0-\uA9FE\uAA00-\uAA36\uAA40-\uAA4D\uAA50-\uAA59\uAA60-\uAA76\uAA7A-\uAAC2\uAADB-\uAADD\uAAE0-\uAAEF\uAAF2-\uAAF6\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB5F\uAB64\uAB65\uABC0-\uABEA\uABEC\uABED\uABF0-\uABF9\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE00-\uFE0F\uFE20-\uFE2D\uFE33\uFE34\uFE4D-\uFE4F\uFE70-\uFE74\uFE76-\uFEFC\uFF10-\uFF19\uFF21-\uFF3A\uFF3F\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]|\uD800[\uDC00-\uDC0B\uDC0D-\uDC26\uDC28-\uDC3A\uDC3C\uDC3D\uDC3F-\uDC4D\uDC50-\uDC5D\uDC80-\uDCFA\uDD40-\uDD74\uDDFD\uDE80-\uDE9C\uDEA0-\uDED0\uDEE0\uDF00-\uDF1F\uDF30-\uDF4A\uDF50-\uDF7A\uDF80-\uDF9D\uDFA0-\uDFC3\uDFC8-\uDFCF\uDFD1-\uDFD5]|\uD801[\uDC00-\uDC9D\uDCA0-\uDCA9\uDD00-\uDD27\uDD30-\uDD63\uDE00-\uDF36\uDF40-\uDF55\uDF60-\uDF67]|\uD802[\uDC00-\uDC05\uDC08\uDC0A-\uDC35\uDC37\uDC38\uDC3C\uDC3F-\uDC55\uDC60-\uDC76\uDC80-\uDC9E\uDD00-\uDD15\uDD20-\uDD39\uDD80-\uDDB7\uDDBE\uDDBF\uDE00-\uDE03\uDE05\uDE06\uDE0C-\uDE13\uDE15-\uDE17\uDE19-\uDE33\uDE38-\uDE3A\uDE3F\uDE60-\uDE7C\uDE80-\uDE9C\uDEC0-\uDEC7\uDEC9-\uDEE6\uDF00-\uDF35\uDF40-\uDF55\uDF60-\uDF72\uDF80-\uDF91]|\uD803[\uDC00-\uDC48]|\uD804[\uDC00-\uDC46\uDC66-\uDC6F\uDC7F-\uDCBA\uDCD0-\uDCE8\uDCF0-\uDCF9\uDD00-\uDD34\uDD36-\uDD3F\uDD50-\uDD73\uDD76\uDD80-\uDDC4\uDDD0-\uDDDA\uDE00-\uDE11\uDE13-\uDE37\uDEB0-\uDEEA\uDEF0-\uDEF9\uDF01-\uDF03\uDF05-\uDF0C\uDF0F\uDF10\uDF13-\uDF28\uDF2A-\uDF30\uDF32\uDF33\uDF35-\uDF39\uDF3C-\uDF44\uDF47\uDF48\uDF4B-\uDF4D\uDF57\uDF5D-\uDF63\uDF66-\uDF6C\uDF70-\uDF74]|\uD805[\uDC80-\uDCC5\uDCC7\uDCD0-\uDCD9\uDD80-\uDDB5\uDDB8-\uDDC0\uDE00-\uDE40\uDE44\uDE50-\uDE59\uDE80-\uDEB7\uDEC0-\uDEC9]|\uD806[\uDCA0-\uDCE9\uDCFF\uDEC0-\uDEF8]|\uD808[\uDC00-\uDF98]|\uD809[\uDC00-\uDC6E]|[\uD80C\uD840-\uD868\uD86A-\uD86C][\uDC00-\uDFFF]|\uD80D[\uDC00-\uDC2E]|\uD81A[\uDC00-\uDE38\uDE40-\uDE5E\uDE60-\uDE69\uDED0-\uDEED\uDEF0-\uDEF4\uDF00-\uDF36\uDF40-\uDF43\uDF50-\uDF59\uDF63-\uDF77\uDF7D-\uDF8F]|\uD81B[\uDF00-\uDF44\uDF50-\uDF7E\uDF8F-\uDF9F]|\uD82C[\uDC00\uDC01]|\uD82F[\uDC00-\uDC6A\uDC70-\uDC7C\uDC80-\uDC88\uDC90-\uDC99\uDC9D\uDC9E]|\uD834[\uDD65-\uDD69\uDD6D-\uDD72\uDD7B-\uDD82\uDD85-\uDD8B\uDDAA-\uDDAD\uDE42-\uDE44]|\uD835[\uDC00-\uDC54\uDC56-\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD1E-\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD52-\uDEA5\uDEA8-\uDEC0\uDEC2-\uDEDA\uDEDC-\uDEFA\uDEFC-\uDF14\uDF16-\uDF34\uDF36-\uDF4E\uDF50-\uDF6E\uDF70-\uDF88\uDF8A-\uDFA8\uDFAA-\uDFC2\uDFC4-\uDFCB\uDFCE-\uDFFF]|\uD83A[\uDC00-\uDCC4\uDCD0-\uDCD6]|\uD83B[\uDE00-\uDE03\uDE05-\uDE1F\uDE21\uDE22\uDE24\uDE27\uDE29-\uDE32\uDE34-\uDE37\uDE39\uDE3B\uDE42\uDE47\uDE49\uDE4B\uDE4D-\uDE4F\uDE51\uDE52\uDE54\uDE57\uDE59\uDE5B\uDE5D\uDE5F\uDE61\uDE62\uDE64\uDE67-\uDE6A\uDE6C-\uDE72\uDE74-\uDE77\uDE79-\uDE7C\uDE7E\uDE80-\uDE89\uDE8B-\uDE9B\uDEA1-\uDEA3\uDEA5-\uDEA9\uDEAB-\uDEBB]|\uD869[\uDC00-\uDED6\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF34\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D]|\uD87E[\uDC00-\uDE1D]|\uDB40[\uDD00-\uDDEF]/ - }; - function isDecimalDigit(ch) { - return 0x30 <= ch && ch <= 0x39; // 0..9 + var numbersPast = 'nolla yksi kaksi kolme neljä viisi kuusi seitsemän kahdeksan yhdeksän'.split(' '), + numbersFuture = [ + 'nolla', 'yhden', 'kahden', 'kolmen', 'neljän', 'viiden', 'kuuden', + numbersPast[7], numbersPast[8], numbersPast[9] + ]; + function translate(number, withoutSuffix, key, isFuture) { + var result = ''; + switch (key) { + case 's': + return isFuture ? 'muutaman sekunnin' : 'muutama sekunti'; + case 'ss': + return isFuture ? 'sekunnin' : 'sekuntia'; + case 'm': + return isFuture ? 'minuutin' : 'minuutti'; + case 'mm': + result = isFuture ? 'minuutin' : 'minuuttia'; + break; + case 'h': + return isFuture ? 'tunnin' : 'tunti'; + case 'hh': + result = isFuture ? 'tunnin' : 'tuntia'; + break; + case 'd': + return isFuture ? 'päivän' : 'päivä'; + case 'dd': + result = isFuture ? 'päivän' : 'päivää'; + break; + case 'M': + return isFuture ? 'kuukauden' : 'kuukausi'; + case 'MM': + result = isFuture ? 'kuukauden' : 'kuukautta'; + break; + case 'y': + return isFuture ? 'vuoden' : 'vuosi'; + case 'yy': + result = isFuture ? 'vuoden' : 'vuotta'; + break; + } + result = verbalNumber(number, isFuture) + ' ' + result; + return result; } + function verbalNumber(number, isFuture) { + return number < 10 ? (isFuture ? numbersFuture[number] : numbersPast[number]) : number; + } + + var fi = moment.defineLocale('fi', { + months : 'tammikuu_helmikuu_maaliskuu_huhtikuu_toukokuu_kesäkuu_heinäkuu_elokuu_syyskuu_lokakuu_marraskuu_joulukuu'.split('_'), + monthsShort : 'tammi_helmi_maalis_huhti_touko_kesä_heinä_elo_syys_loka_marras_joulu'.split('_'), + weekdays : 'sunnuntai_maanantai_tiistai_keskiviikko_torstai_perjantai_lauantai'.split('_'), + weekdaysShort : 'su_ma_ti_ke_to_pe_la'.split('_'), + weekdaysMin : 'su_ma_ti_ke_to_pe_la'.split('_'), + longDateFormat : { + LT : 'HH.mm', + LTS : 'HH.mm.ss', + L : 'DD.MM.YYYY', + LL : 'Do MMMM[ta] YYYY', + LLL : 'Do MMMM[ta] YYYY, [klo] HH.mm', + LLLL : 'dddd, Do MMMM[ta] YYYY, [klo] HH.mm', + l : 'D.M.YYYY', + ll : 'Do MMM YYYY', + lll : 'Do MMM YYYY, [klo] HH.mm', + llll : 'ddd, Do MMM YYYY, [klo] HH.mm' + }, + calendar : { + sameDay : '[tänään] [klo] LT', + nextDay : '[huomenna] [klo] LT', + nextWeek : 'dddd [klo] LT', + lastDay : '[eilen] [klo] LT', + lastWeek : '[viime] dddd[na] [klo] LT', + sameElse : 'L' + }, + relativeTime : { + future : '%s päästä', + past : '%s sitten', + s : translate, + ss : translate, + m : translate, + mm : translate, + h : translate, + hh : translate, + d : translate, + dd : translate, + M : translate, + MM : translate, + y : translate, + yy : translate + }, + dayOfMonthOrdinalParse: /\d{1,2}\./, + ordinal : '%d.', + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } + }); - function isHexDigit(ch) { - return 0x30 <= ch && ch <= 0x39 || // 0..9 - 0x61 <= ch && ch <= 0x66 || // a..f - 0x41 <= ch && ch <= 0x46; // A..F - } + return fi; - function isOctalDigit(ch) { - return ch >= 0x30 && ch <= 0x37; // 0..7 - } +}))); - // 7.2 White Space - NON_ASCII_WHITESPACES = [ - 0x1680, 0x180E, - 0x2000, 0x2001, 0x2002, 0x2003, 0x2004, 0x2005, 0x2006, 0x2007, 0x2008, 0x2009, 0x200A, - 0x202F, 0x205F, - 0x3000, - 0xFEFF - ]; +/***/ }), +/* 83 */ +/***/ (function(module, exports, __webpack_require__) { - function isWhiteSpace(ch) { - return ch === 0x20 || ch === 0x09 || ch === 0x0B || ch === 0x0C || ch === 0xA0 || - ch >= 0x1680 && NON_ASCII_WHITESPACES.indexOf(ch) >= 0; - } +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var fo = moment.defineLocale('fo', { + months : 'januar_februar_mars_apríl_mai_juni_juli_august_september_oktober_november_desember'.split('_'), + monthsShort : 'jan_feb_mar_apr_mai_jun_jul_aug_sep_okt_nov_des'.split('_'), + weekdays : 'sunnudagur_mánadagur_týsdagur_mikudagur_hósdagur_fríggjadagur_leygardagur'.split('_'), + weekdaysShort : 'sun_mán_týs_mik_hós_frí_ley'.split('_'), + weekdaysMin : 'su_má_tý_mi_hó_fr_le'.split('_'), + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY HH:mm', + LLLL : 'dddd D. MMMM, YYYY HH:mm' + }, + calendar : { + sameDay : '[Í dag kl.] LT', + nextDay : '[Í morgin kl.] LT', + nextWeek : 'dddd [kl.] LT', + lastDay : '[Í gjár kl.] LT', + lastWeek : '[síðstu] dddd [kl] LT', + sameElse : 'L' + }, + relativeTime : { + future : 'um %s', + past : '%s síðani', + s : 'fá sekund', + ss : '%d sekundir', + m : 'ein minuttur', + mm : '%d minuttir', + h : 'ein tími', + hh : '%d tímar', + d : 'ein dagur', + dd : '%d dagar', + M : 'ein mánaður', + MM : '%d mánaðir', + y : 'eitt ár', + yy : '%d ár' + }, + dayOfMonthOrdinalParse: /\d{1,2}\./, + ordinal : '%d.', + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } + }); - // 7.3 Line Terminators + return fo; - function isLineTerminator(ch) { - return ch === 0x0A || ch === 0x0D || ch === 0x2028 || ch === 0x2029; - } +}))); - // 7.6 Identifier Names and Identifiers - function fromCodePoint(cp) { - if (cp <= 0xFFFF) { return String.fromCharCode(cp); } - var cu1 = String.fromCharCode(Math.floor((cp - 0x10000) / 0x400) + 0xD800); - var cu2 = String.fromCharCode(((cp - 0x10000) % 0x400) + 0xDC00); - return cu1 + cu2; - } +/***/ }), +/* 84 */ +/***/ (function(module, exports, __webpack_require__) { - IDENTIFIER_START = new Array(0x80); - for(ch = 0; ch < 0x80; ++ch) { - IDENTIFIER_START[ch] = - ch >= 0x61 && ch <= 0x7A || // a..z - ch >= 0x41 && ch <= 0x5A || // A..Z - ch === 0x24 || ch === 0x5F; // $ (dollar) and _ (underscore) - } +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var fr = moment.defineLocale('fr', { + months : 'janvier_février_mars_avril_mai_juin_juillet_août_septembre_octobre_novembre_décembre'.split('_'), + monthsShort : 'janv._févr._mars_avr._mai_juin_juil._août_sept._oct._nov._déc.'.split('_'), + monthsParseExact : true, + weekdays : 'dimanche_lundi_mardi_mercredi_jeudi_vendredi_samedi'.split('_'), + weekdaysShort : 'dim._lun._mar._mer._jeu._ven._sam.'.split('_'), + weekdaysMin : 'di_lu_ma_me_je_ve_sa'.split('_'), + weekdaysParseExact : true, + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY HH:mm', + LLLL : 'dddd D MMMM YYYY HH:mm' + }, + calendar : { + sameDay : '[Aujourd’hui à] LT', + nextDay : '[Demain à] LT', + nextWeek : 'dddd [à] LT', + lastDay : '[Hier à] LT', + lastWeek : 'dddd [dernier à] LT', + sameElse : 'L' + }, + relativeTime : { + future : 'dans %s', + past : 'il y a %s', + s : 'quelques secondes', + ss : '%d secondes', + m : 'une minute', + mm : '%d minutes', + h : 'une heure', + hh : '%d heures', + d : 'un jour', + dd : '%d jours', + M : 'un mois', + MM : '%d mois', + y : 'un an', + yy : '%d ans' + }, + dayOfMonthOrdinalParse: /\d{1,2}(er|)/, + ordinal : function (number, period) { + switch (period) { + // TODO: Return 'e' when day of month > 1. Move this case inside + // block for masculine words below. + // See https://github.com/moment/moment/issues/3375 + case 'D': + return number + (number === 1 ? 'er' : ''); + + // Words with masculine grammatical gender: mois, trimestre, jour + default: + case 'M': + case 'Q': + case 'DDD': + case 'd': + return number + (number === 1 ? 'er' : 'e'); + + // Words with feminine grammatical gender: semaine + case 'w': + case 'W': + return number + (number === 1 ? 're' : 'e'); + } + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } + }); - IDENTIFIER_PART = new Array(0x80); - for(ch = 0; ch < 0x80; ++ch) { - IDENTIFIER_PART[ch] = - ch >= 0x61 && ch <= 0x7A || // a..z - ch >= 0x41 && ch <= 0x5A || // A..Z - ch >= 0x30 && ch <= 0x39 || // 0..9 - ch === 0x24 || ch === 0x5F; // $ (dollar) and _ (underscore) - } + return fr; - function isIdentifierStartES5(ch) { - return ch < 0x80 ? IDENTIFIER_START[ch] : ES5Regex.NonAsciiIdentifierStart.test(fromCodePoint(ch)); - } +}))); - function isIdentifierPartES5(ch) { - return ch < 0x80 ? IDENTIFIER_PART[ch] : ES5Regex.NonAsciiIdentifierPart.test(fromCodePoint(ch)); - } - function isIdentifierStartES6(ch) { - return ch < 0x80 ? IDENTIFIER_START[ch] : ES6Regex.NonAsciiIdentifierStart.test(fromCodePoint(ch)); - } +/***/ }), +/* 85 */ +/***/ (function(module, exports, __webpack_require__) { - function isIdentifierPartES6(ch) { - return ch < 0x80 ? IDENTIFIER_PART[ch] : ES6Regex.NonAsciiIdentifierPart.test(fromCodePoint(ch)); - } +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var frCa = moment.defineLocale('fr-ca', { + months : 'janvier_février_mars_avril_mai_juin_juillet_août_septembre_octobre_novembre_décembre'.split('_'), + monthsShort : 'janv._févr._mars_avr._mai_juin_juil._août_sept._oct._nov._déc.'.split('_'), + monthsParseExact : true, + weekdays : 'dimanche_lundi_mardi_mercredi_jeudi_vendredi_samedi'.split('_'), + weekdaysShort : 'dim._lun._mar._mer._jeu._ven._sam.'.split('_'), + weekdaysMin : 'di_lu_ma_me_je_ve_sa'.split('_'), + weekdaysParseExact : true, + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'YYYY-MM-DD', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY HH:mm', + LLLL : 'dddd D MMMM YYYY HH:mm' + }, + calendar : { + sameDay : '[Aujourd’hui à] LT', + nextDay : '[Demain à] LT', + nextWeek : 'dddd [à] LT', + lastDay : '[Hier à] LT', + lastWeek : 'dddd [dernier à] LT', + sameElse : 'L' + }, + relativeTime : { + future : 'dans %s', + past : 'il y a %s', + s : 'quelques secondes', + ss : '%d secondes', + m : 'une minute', + mm : '%d minutes', + h : 'une heure', + hh : '%d heures', + d : 'un jour', + dd : '%d jours', + M : 'un mois', + MM : '%d mois', + y : 'un an', + yy : '%d ans' + }, + dayOfMonthOrdinalParse: /\d{1,2}(er|e)/, + ordinal : function (number, period) { + switch (period) { + // Words with masculine grammatical gender: mois, trimestre, jour + default: + case 'M': + case 'Q': + case 'D': + case 'DDD': + case 'd': + return number + (number === 1 ? 'er' : 'e'); + + // Words with feminine grammatical gender: semaine + case 'w': + case 'W': + return number + (number === 1 ? 're' : 'e'); + } + } + }); - module.exports = { - isDecimalDigit: isDecimalDigit, - isHexDigit: isHexDigit, - isOctalDigit: isOctalDigit, - isWhiteSpace: isWhiteSpace, - isLineTerminator: isLineTerminator, - isIdentifierStartES5: isIdentifierStartES5, - isIdentifierPartES5: isIdentifierPartES5, - isIdentifierStartES6: isIdentifierStartES6, - isIdentifierPartES6: isIdentifierPartES6 - }; -}()); -/* vim: set sw=4 ts=4 et tw=80 : */ + return frCa; + +}))); /***/ }), -/* 68 */ +/* 86 */ /***/ (function(module, exports, __webpack_require__) { -/* - Copyright (C) 2013 Yusuke Suzuki +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var frCh = moment.defineLocale('fr-ch', { + months : 'janvier_février_mars_avril_mai_juin_juillet_août_septembre_octobre_novembre_décembre'.split('_'), + monthsShort : 'janv._févr._mars_avr._mai_juin_juil._août_sept._oct._nov._déc.'.split('_'), + monthsParseExact : true, + weekdays : 'dimanche_lundi_mardi_mercredi_jeudi_vendredi_samedi'.split('_'), + weekdaysShort : 'dim._lun._mar._mer._jeu._ven._sam.'.split('_'), + weekdaysMin : 'di_lu_ma_me_je_ve_sa'.split('_'), + weekdaysParseExact : true, + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD.MM.YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY HH:mm', + LLLL : 'dddd D MMMM YYYY HH:mm' + }, + calendar : { + sameDay : '[Aujourd’hui à] LT', + nextDay : '[Demain à] LT', + nextWeek : 'dddd [à] LT', + lastDay : '[Hier à] LT', + lastWeek : 'dddd [dernier à] LT', + sameElse : 'L' + }, + relativeTime : { + future : 'dans %s', + past : 'il y a %s', + s : 'quelques secondes', + ss : '%d secondes', + m : 'une minute', + mm : '%d minutes', + h : 'une heure', + hh : '%d heures', + d : 'un jour', + dd : '%d jours', + M : 'un mois', + MM : '%d mois', + y : 'un an', + yy : '%d ans' + }, + dayOfMonthOrdinalParse: /\d{1,2}(er|e)/, + ordinal : function (number, period) { + switch (period) { + // Words with masculine grammatical gender: mois, trimestre, jour + default: + case 'M': + case 'Q': + case 'D': + case 'DDD': + case 'd': + return number + (number === 1 ? 'er' : 'e'); + + // Words with feminine grammatical gender: semaine + case 'w': + case 'W': + return number + (number === 1 ? 're' : 'e'); + } + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } + }); - Redistribution and use in source and binary forms, with or without - modification, are permitted provided that the following conditions are met: + return frCh; - * Redistributions of source code must retain the above copyright - notice, this list of conditions and the following disclaimer. - * Redistributions in binary form must reproduce the above copyright - notice, this list of conditions and the following disclaimer in the - documentation and/or other materials provided with the distribution. +}))); - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" - AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE - IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE - ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY - DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES - (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; - LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND - ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT - (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF - THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -*/ -(function () { - 'use strict'; +/***/ }), +/* 87 */ +/***/ (function(module, exports, __webpack_require__) { - var code = __webpack_require__(67); +//! moment.js locale configuration - function isStrictModeReservedWordES6(id) { - switch (id) { - case 'implements': - case 'interface': - case 'package': - case 'private': - case 'protected': - case 'public': - case 'static': - case 'let': - return true; - default: - return false; - } - } +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; - function isKeywordES5(id, strict) { - // yield should not be treated as keyword under non-strict mode. - if (!strict && id === 'yield') { - return false; - } - return isKeywordES6(id, strict); - } - function isKeywordES6(id, strict) { - if (strict && isStrictModeReservedWordES6(id)) { - return true; - } + var monthsShortWithDots = 'jan._feb._mrt._apr._mai_jun._jul._aug._sep._okt._nov._des.'.split('_'), + monthsShortWithoutDots = 'jan_feb_mrt_apr_mai_jun_jul_aug_sep_okt_nov_des'.split('_'); - switch (id.length) { - case 2: - return (id === 'if') || (id === 'in') || (id === 'do'); - case 3: - return (id === 'var') || (id === 'for') || (id === 'new') || (id === 'try'); - case 4: - return (id === 'this') || (id === 'else') || (id === 'case') || - (id === 'void') || (id === 'with') || (id === 'enum'); - case 5: - return (id === 'while') || (id === 'break') || (id === 'catch') || - (id === 'throw') || (id === 'const') || (id === 'yield') || - (id === 'class') || (id === 'super'); - case 6: - return (id === 'return') || (id === 'typeof') || (id === 'delete') || - (id === 'switch') || (id === 'export') || (id === 'import'); - case 7: - return (id === 'default') || (id === 'finally') || (id === 'extends'); - case 8: - return (id === 'function') || (id === 'continue') || (id === 'debugger'); - case 10: - return (id === 'instanceof'); - default: - return false; + var fy = moment.defineLocale('fy', { + months : 'jannewaris_febrewaris_maart_april_maaie_juny_july_augustus_septimber_oktober_novimber_desimber'.split('_'), + monthsShort : function (m, format) { + if (!m) { + return monthsShortWithDots; + } else if (/-MMM-/.test(format)) { + return monthsShortWithoutDots[m.month()]; + } else { + return monthsShortWithDots[m.month()]; + } + }, + monthsParseExact : true, + weekdays : 'snein_moandei_tiisdei_woansdei_tongersdei_freed_sneon'.split('_'), + weekdaysShort : 'si._mo._ti._wo._to._fr._so.'.split('_'), + weekdaysMin : 'Si_Mo_Ti_Wo_To_Fr_So'.split('_'), + weekdaysParseExact : true, + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD-MM-YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY HH:mm', + LLLL : 'dddd D MMMM YYYY HH:mm' + }, + calendar : { + sameDay: '[hjoed om] LT', + nextDay: '[moarn om] LT', + nextWeek: 'dddd [om] LT', + lastDay: '[juster om] LT', + lastWeek: '[ôfrûne] dddd [om] LT', + sameElse: 'L' + }, + relativeTime : { + future : 'oer %s', + past : '%s lyn', + s : 'in pear sekonden', + ss : '%d sekonden', + m : 'ien minút', + mm : '%d minuten', + h : 'ien oere', + hh : '%d oeren', + d : 'ien dei', + dd : '%d dagen', + M : 'ien moanne', + MM : '%d moannen', + y : 'ien jier', + yy : '%d jierren' + }, + dayOfMonthOrdinalParse: /\d{1,2}(ste|de)/, + ordinal : function (number) { + return number + ((number === 1 || number === 8 || number >= 20) ? 'ste' : 'de'); + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. } - } + }); - function isReservedWordES5(id, strict) { - return id === 'null' || id === 'true' || id === 'false' || isKeywordES5(id, strict); - } + return fy; - function isReservedWordES6(id, strict) { - return id === 'null' || id === 'true' || id === 'false' || isKeywordES6(id, strict); - } +}))); - function isRestrictedWord(id) { - return id === 'eval' || id === 'arguments'; - } - function isIdentifierNameES5(id) { - var i, iz, ch; +/***/ }), +/* 88 */ +/***/ (function(module, exports, __webpack_require__) { - if (id.length === 0) { return false; } +//! moment.js locale configuration - ch = id.charCodeAt(0); - if (!code.isIdentifierStartES5(ch)) { - return false; - } +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; - for (i = 1, iz = id.length; i < iz; ++i) { - ch = id.charCodeAt(i); - if (!code.isIdentifierPartES5(ch)) { - return false; - } - } - return true; - } - function decodeUtf16(lead, trail) { - return (lead - 0xD800) * 0x400 + (trail - 0xDC00) + 0x10000; - } - function isIdentifierNameES6(id) { - var i, iz, ch, lowCh, check; + var months = [ + 'Eanáir', 'Feabhra', 'Márta', 'Aibreán', 'Bealtaine', 'Méitheamh', 'Iúil', 'Lúnasa', 'Meán Fómhair', 'Deaireadh Fómhair', 'Samhain', 'Nollaig' + ]; - if (id.length === 0) { return false; } + var monthsShort = ['Eaná', 'Feab', 'Márt', 'Aibr', 'Beal', 'Méit', 'Iúil', 'Lúna', 'Meán', 'Deai', 'Samh', 'Noll']; - check = code.isIdentifierStartES6; - for (i = 0, iz = id.length; i < iz; ++i) { - ch = id.charCodeAt(i); - if (0xD800 <= ch && ch <= 0xDBFF) { - ++i; - if (i >= iz) { return false; } - lowCh = id.charCodeAt(i); - if (!(0xDC00 <= lowCh && lowCh <= 0xDFFF)) { - return false; - } - ch = decodeUtf16(ch, lowCh); - } - if (!check(ch)) { - return false; - } - check = code.isIdentifierPartES6; - } - return true; - } + var weekdays = ['Dé Domhnaigh', 'Dé Luain', 'Dé Máirt', 'Dé Céadaoin', 'Déardaoin', 'Dé hAoine', 'Dé Satharn']; - function isIdentifierES5(id, strict) { - return isIdentifierNameES5(id) && !isReservedWordES5(id, strict); - } + var weekdaysShort = ['Dom', 'Lua', 'Mái', 'Céa', 'Déa', 'hAo', 'Sat']; - function isIdentifierES6(id, strict) { - return isIdentifierNameES6(id) && !isReservedWordES6(id, strict); - } + var weekdaysMin = ['Do', 'Lu', 'Má', 'Ce', 'Dé', 'hA', 'Sa']; - module.exports = { - isKeywordES5: isKeywordES5, - isKeywordES6: isKeywordES6, - isReservedWordES5: isReservedWordES5, - isReservedWordES6: isReservedWordES6, - isRestrictedWord: isRestrictedWord, - isIdentifierNameES5: isIdentifierNameES5, - isIdentifierNameES6: isIdentifierNameES6, - isIdentifierES5: isIdentifierES5, - isIdentifierES6: isIdentifierES6 - }; -}()); -/* vim: set sw=4 ts=4 et tw=80 : */ + var ga = moment.defineLocale('ga', { + months: months, + monthsShort: monthsShort, + monthsParseExact: true, + weekdays: weekdays, + weekdaysShort: weekdaysShort, + weekdaysMin: weekdaysMin, + longDateFormat: { + LT: 'HH:mm', + LTS: 'HH:mm:ss', + L: 'DD/MM/YYYY', + LL: 'D MMMM YYYY', + LLL: 'D MMMM YYYY HH:mm', + LLLL: 'dddd, D MMMM YYYY HH:mm' + }, + calendar: { + sameDay: '[Inniu ag] LT', + nextDay: '[Amárach ag] LT', + nextWeek: 'dddd [ag] LT', + lastDay: '[Inné aig] LT', + lastWeek: 'dddd [seo caite] [ag] LT', + sameElse: 'L' + }, + relativeTime: { + future: 'i %s', + past: '%s ó shin', + s: 'cúpla soicind', + ss: '%d soicind', + m: 'nóiméad', + mm: '%d nóiméad', + h: 'uair an chloig', + hh: '%d uair an chloig', + d: 'lá', + dd: '%d lá', + M: 'mí', + MM: '%d mí', + y: 'bliain', + yy: '%d bliain' + }, + dayOfMonthOrdinalParse: /\d{1,2}(d|na|mh)/, + ordinal: function (number) { + var output = number === 1 ? 'd' : number % 10 === 2 ? 'na' : 'mh'; + return number + output; + }, + week: { + dow: 1, // Monday is the first day of the week. + doy: 4 // The week that contains Jan 4th is the first week of the year. + } + }); + + return ga; + +}))); /***/ }), -/* 69 */ +/* 89 */ /***/ (function(module, exports, __webpack_require__) { -"use strict"; +//! moment.js locale configuration -const escapeStringRegexp = __webpack_require__(3); -const ansiStyles = __webpack_require__(70); -const stdoutColor = __webpack_require__(71).stdout; +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; -const template = __webpack_require__(72); -const isSimpleWindowsTerm = process.platform === 'win32' && !(process.env.TERM || '').toLowerCase().startsWith('xterm'); + var months = [ + 'Am Faoilleach', 'An Gearran', 'Am Màrt', 'An Giblean', 'An Cèitean', 'An t-Ògmhios', 'An t-Iuchar', 'An Lùnastal', 'An t-Sultain', 'An Dàmhair', 'An t-Samhain', 'An Dùbhlachd' + ]; -// `supportsColor.level` → `ansiStyles.color[name]` mapping -const levelMapping = ['ansi', 'ansi', 'ansi256', 'ansi16m']; + var monthsShort = ['Faoi', 'Gear', 'Màrt', 'Gibl', 'Cèit', 'Ògmh', 'Iuch', 'Lùn', 'Sult', 'Dàmh', 'Samh', 'Dùbh']; -// `color-convert` models to exclude from the Chalk API due to conflicts and such -const skipModels = new Set(['gray']); + var weekdays = ['Didòmhnaich', 'Diluain', 'Dimàirt', 'Diciadain', 'Diardaoin', 'Dihaoine', 'Disathairne']; -const styles = Object.create(null); + var weekdaysShort = ['Did', 'Dil', 'Dim', 'Dic', 'Dia', 'Dih', 'Dis']; -function applyOptions(obj, options) { - options = options || {}; + var weekdaysMin = ['Dò', 'Lu', 'Mà', 'Ci', 'Ar', 'Ha', 'Sa']; - // Detect level if not set manually - const scLevel = stdoutColor ? stdoutColor.level : 0; - obj.level = options.level === undefined ? scLevel : options.level; - obj.enabled = 'enabled' in options ? options.enabled : obj.level > 0; -} + var gd = moment.defineLocale('gd', { + months : months, + monthsShort : monthsShort, + monthsParseExact : true, + weekdays : weekdays, + weekdaysShort : weekdaysShort, + weekdaysMin : weekdaysMin, + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY HH:mm', + LLLL : 'dddd, D MMMM YYYY HH:mm' + }, + calendar : { + sameDay : '[An-diugh aig] LT', + nextDay : '[A-màireach aig] LT', + nextWeek : 'dddd [aig] LT', + lastDay : '[An-dè aig] LT', + lastWeek : 'dddd [seo chaidh] [aig] LT', + sameElse : 'L' + }, + relativeTime : { + future : 'ann an %s', + past : 'bho chionn %s', + s : 'beagan diogan', + ss : '%d diogan', + m : 'mionaid', + mm : '%d mionaidean', + h : 'uair', + hh : '%d uairean', + d : 'latha', + dd : '%d latha', + M : 'mìos', + MM : '%d mìosan', + y : 'bliadhna', + yy : '%d bliadhna' + }, + dayOfMonthOrdinalParse : /\d{1,2}(d|na|mh)/, + ordinal : function (number) { + var output = number === 1 ? 'd' : number % 10 === 2 ? 'na' : 'mh'; + return number + output; + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } + }); -function Chalk(options) { - // We check for this.template here since calling `chalk.constructor()` - // by itself will have a `this` of a previously constructed chalk object - if (!this || !(this instanceof Chalk) || this.template) { - const chalk = {}; - applyOptions(chalk, options); + return gd; - chalk.template = function () { - const args = [].slice.call(arguments); - return chalkTag.apply(null, [chalk.template].concat(args)); - }; +}))); - Object.setPrototypeOf(chalk, Chalk.prototype); - Object.setPrototypeOf(chalk.template, chalk); - chalk.template.constructor = Chalk; +/***/ }), +/* 90 */ +/***/ (function(module, exports, __webpack_require__) { - return chalk.template; - } +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var gl = moment.defineLocale('gl', { + months : 'xaneiro_febreiro_marzo_abril_maio_xuño_xullo_agosto_setembro_outubro_novembro_decembro'.split('_'), + monthsShort : 'xan._feb._mar._abr._mai._xuñ._xul._ago._set._out._nov._dec.'.split('_'), + monthsParseExact: true, + weekdays : 'domingo_luns_martes_mércores_xoves_venres_sábado'.split('_'), + weekdaysShort : 'dom._lun._mar._mér._xov._ven._sáb.'.split('_'), + weekdaysMin : 'do_lu_ma_mé_xo_ve_sá'.split('_'), + weekdaysParseExact : true, + longDateFormat : { + LT : 'H:mm', + LTS : 'H:mm:ss', + L : 'DD/MM/YYYY', + LL : 'D [de] MMMM [de] YYYY', + LLL : 'D [de] MMMM [de] YYYY H:mm', + LLLL : 'dddd, D [de] MMMM [de] YYYY H:mm' + }, + calendar : { + sameDay : function () { + return '[hoxe ' + ((this.hours() !== 1) ? 'ás' : 'á') + '] LT'; + }, + nextDay : function () { + return '[mañá ' + ((this.hours() !== 1) ? 'ás' : 'á') + '] LT'; + }, + nextWeek : function () { + return 'dddd [' + ((this.hours() !== 1) ? 'ás' : 'a') + '] LT'; + }, + lastDay : function () { + return '[onte ' + ((this.hours() !== 1) ? 'á' : 'a') + '] LT'; + }, + lastWeek : function () { + return '[o] dddd [pasado ' + ((this.hours() !== 1) ? 'ás' : 'a') + '] LT'; + }, + sameElse : 'L' + }, + relativeTime : { + future : function (str) { + if (str.indexOf('un') === 0) { + return 'n' + str; + } + return 'en ' + str; + }, + past : 'hai %s', + s : 'uns segundos', + ss : '%d segundos', + m : 'un minuto', + mm : '%d minutos', + h : 'unha hora', + hh : '%d horas', + d : 'un día', + dd : '%d días', + M : 'un mes', + MM : '%d meses', + y : 'un ano', + yy : '%d anos' + }, + dayOfMonthOrdinalParse : /\d{1,2}º/, + ordinal : '%dº', + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } + }); - applyOptions(this, options); -} + return gl; -// Use bright blue on Windows as the normal blue color is illegible -if (isSimpleWindowsTerm) { - ansiStyles.blue.open = '\u001B[94m'; -} +}))); -for (const key of Object.keys(ansiStyles)) { - ansiStyles[key].closeRe = new RegExp(escapeStringRegexp(ansiStyles[key].close), 'g'); - styles[key] = { - get() { - const codes = ansiStyles[key]; - return build.call(this, this._styles ? this._styles.concat(codes) : [codes], this._empty, key); - } - }; -} +/***/ }), +/* 91 */ +/***/ (function(module, exports, __webpack_require__) { -styles.visible = { - get() { - return build.call(this, this._styles || [], true, 'visible'); - } -}; +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + function processRelativeTime(number, withoutSuffix, key, isFuture) { + var format = { + 's': ['thodde secondanim', 'thodde second'], + 'ss': [number + ' secondanim', number + ' second'], + 'm': ['eka mintan', 'ek minute'], + 'mm': [number + ' mintanim', number + ' mintam'], + 'h': ['eka voran', 'ek vor'], + 'hh': [number + ' voranim', number + ' voram'], + 'd': ['eka disan', 'ek dis'], + 'dd': [number + ' disanim', number + ' dis'], + 'M': ['eka mhoinean', 'ek mhoino'], + 'MM': [number + ' mhoineanim', number + ' mhoine'], + 'y': ['eka vorsan', 'ek voros'], + 'yy': [number + ' vorsanim', number + ' vorsam'] + }; + return withoutSuffix ? format[key][0] : format[key][1]; + } + + var gomLatn = moment.defineLocale('gom-latn', { + months : 'Janer_Febrer_Mars_Abril_Mai_Jun_Julai_Agost_Setembr_Otubr_Novembr_Dezembr'.split('_'), + monthsShort : 'Jan._Feb._Mars_Abr._Mai_Jun_Jul._Ago._Set._Otu._Nov._Dez.'.split('_'), + monthsParseExact : true, + weekdays : 'Aitar_Somar_Mongllar_Budvar_Brestar_Sukrar_Son\'var'.split('_'), + weekdaysShort : 'Ait._Som._Mon._Bud._Bre._Suk._Son.'.split('_'), + weekdaysMin : 'Ai_Sm_Mo_Bu_Br_Su_Sn'.split('_'), + weekdaysParseExact : true, + longDateFormat : { + LT : 'A h:mm [vazta]', + LTS : 'A h:mm:ss [vazta]', + L : 'DD-MM-YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY A h:mm [vazta]', + LLLL : 'dddd, MMMM[achea] Do, YYYY, A h:mm [vazta]', + llll: 'ddd, D MMM YYYY, A h:mm [vazta]' + }, + calendar : { + sameDay: '[Aiz] LT', + nextDay: '[Faleam] LT', + nextWeek: '[Ieta to] dddd[,] LT', + lastDay: '[Kal] LT', + lastWeek: '[Fatlo] dddd[,] LT', + sameElse: 'L' + }, + relativeTime : { + future : '%s', + past : '%s adim', + s : processRelativeTime, + ss : processRelativeTime, + m : processRelativeTime, + mm : processRelativeTime, + h : processRelativeTime, + hh : processRelativeTime, + d : processRelativeTime, + dd : processRelativeTime, + M : processRelativeTime, + MM : processRelativeTime, + y : processRelativeTime, + yy : processRelativeTime + }, + dayOfMonthOrdinalParse : /\d{1,2}(er)/, + ordinal : function (number, period) { + switch (period) { + // the ordinal 'er' only applies to day of the month + case 'D': + return number + 'er'; + default: + case 'M': + case 'Q': + case 'DDD': + case 'd': + case 'w': + case 'W': + return number; + } + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + }, + meridiemParse: /rati|sokalli|donparam|sanje/, + meridiemHour : function (hour, meridiem) { + if (hour === 12) { + hour = 0; + } + if (meridiem === 'rati') { + return hour < 4 ? hour : hour + 12; + } else if (meridiem === 'sokalli') { + return hour; + } else if (meridiem === 'donparam') { + return hour > 12 ? hour : hour + 12; + } else if (meridiem === 'sanje') { + return hour + 12; + } + }, + meridiem : function (hour, minute, isLower) { + if (hour < 4) { + return 'rati'; + } else if (hour < 12) { + return 'sokalli'; + } else if (hour < 16) { + return 'donparam'; + } else if (hour < 20) { + return 'sanje'; + } else { + return 'rati'; + } + } + }); -ansiStyles.color.closeRe = new RegExp(escapeStringRegexp(ansiStyles.color.close), 'g'); -for (const model of Object.keys(ansiStyles.color.ansi)) { - if (skipModels.has(model)) { - continue; - } + return gomLatn; - styles[model] = { - get() { - const level = this.level; - return function () { - const open = ansiStyles.color[levelMapping[level]][model].apply(null, arguments); - const codes = { - open, - close: ansiStyles.color.close, - closeRe: ansiStyles.color.closeRe - }; - return build.call(this, this._styles ? this._styles.concat(codes) : [codes], this._empty, model); - }; - } - }; -} +}))); -ansiStyles.bgColor.closeRe = new RegExp(escapeStringRegexp(ansiStyles.bgColor.close), 'g'); -for (const model of Object.keys(ansiStyles.bgColor.ansi)) { - if (skipModels.has(model)) { - continue; - } - const bgModel = 'bg' + model[0].toUpperCase() + model.slice(1); - styles[bgModel] = { - get() { - const level = this.level; - return function () { - const open = ansiStyles.bgColor[levelMapping[level]][model].apply(null, arguments); - const codes = { - open, - close: ansiStyles.bgColor.close, - closeRe: ansiStyles.bgColor.closeRe - }; - return build.call(this, this._styles ? this._styles.concat(codes) : [codes], this._empty, model); - }; - } - }; -} +/***/ }), +/* 92 */ +/***/ (function(module, exports, __webpack_require__) { -const proto = Object.defineProperties(() => {}, styles); +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var symbolMap = { + '1': '૧', + '2': '૨', + '3': '૩', + '4': '૪', + '5': '૫', + '6': '૬', + '7': '૭', + '8': '૮', + '9': '૯', + '0': '૦' + }, + numberMap = { + '૧': '1', + '૨': '2', + '૩': '3', + '૪': '4', + '૫': '5', + '૬': '6', + '૭': '7', + '૮': '8', + '૯': '9', + '૦': '0' + }; -function build(_styles, _empty, key) { - const builder = function () { - return applyStyle.apply(builder, arguments); - }; + var gu = moment.defineLocale('gu', { + months: 'જાન્યુઆરી_ફેબ્રુઆરી_માર્ચ_એપ્રિલ_મે_જૂન_જુલાઈ_ઑગસ્ટ_સપ્ટેમ્બર_ઑક્ટ્બર_નવેમ્બર_ડિસેમ્બર'.split('_'), + monthsShort: 'જાન્યુ._ફેબ્રુ._માર્ચ_એપ્રિ._મે_જૂન_જુલા._ઑગ._સપ્ટે._ઑક્ટ્._નવે._ડિસે.'.split('_'), + monthsParseExact: true, + weekdays: 'રવિવાર_સોમવાર_મંગળવાર_બુધ્વાર_ગુરુવાર_શુક્રવાર_શનિવાર'.split('_'), + weekdaysShort: 'રવિ_સોમ_મંગળ_બુધ્_ગુરુ_શુક્ર_શનિ'.split('_'), + weekdaysMin: 'ર_સો_મં_બુ_ગુ_શુ_શ'.split('_'), + longDateFormat: { + LT: 'A h:mm વાગ્યે', + LTS: 'A h:mm:ss વાગ્યે', + L: 'DD/MM/YYYY', + LL: 'D MMMM YYYY', + LLL: 'D MMMM YYYY, A h:mm વાગ્યે', + LLLL: 'dddd, D MMMM YYYY, A h:mm વાગ્યે' + }, + calendar: { + sameDay: '[આજ] LT', + nextDay: '[કાલે] LT', + nextWeek: 'dddd, LT', + lastDay: '[ગઇકાલે] LT', + lastWeek: '[પાછલા] dddd, LT', + sameElse: 'L' + }, + relativeTime: { + future: '%s મા', + past: '%s પેહલા', + s: 'અમુક પળો', + ss: '%d સેકંડ', + m: 'એક મિનિટ', + mm: '%d મિનિટ', + h: 'એક કલાક', + hh: '%d કલાક', + d: 'એક દિવસ', + dd: '%d દિવસ', + M: 'એક મહિનો', + MM: '%d મહિનો', + y: 'એક વર્ષ', + yy: '%d વર્ષ' + }, + preparse: function (string) { + return string.replace(/[૧૨૩૪૫૬૭૮૯૦]/g, function (match) { + return numberMap[match]; + }); + }, + postformat: function (string) { + return string.replace(/\d/g, function (match) { + return symbolMap[match]; + }); + }, + // Gujarati notation for meridiems are quite fuzzy in practice. While there exists + // a rigid notion of a 'Pahar' it is not used as rigidly in modern Gujarati. + meridiemParse: /રાત|બપોર|સવાર|સાંજ/, + meridiemHour: function (hour, meridiem) { + if (hour === 12) { + hour = 0; + } + if (meridiem === 'રાત') { + return hour < 4 ? hour : hour + 12; + } else if (meridiem === 'સવાર') { + return hour; + } else if (meridiem === 'બપોર') { + return hour >= 10 ? hour : hour + 12; + } else if (meridiem === 'સાંજ') { + return hour + 12; + } + }, + meridiem: function (hour, minute, isLower) { + if (hour < 4) { + return 'રાત'; + } else if (hour < 10) { + return 'સવાર'; + } else if (hour < 17) { + return 'બપોર'; + } else if (hour < 20) { + return 'સાંજ'; + } else { + return 'રાત'; + } + }, + week: { + dow: 0, // Sunday is the first day of the week. + doy: 6 // The week that contains Jan 6th is the first week of the year. + } + }); - builder._styles = _styles; - builder._empty = _empty; + return gu; - const self = this; +}))); - Object.defineProperty(builder, 'level', { - enumerable: true, - get() { - return self.level; - }, - set(level) { - self.level = level; - } - }); - Object.defineProperty(builder, 'enabled', { - enumerable: true, - get() { - return self.enabled; - }, - set(enabled) { - self.enabled = enabled; - } - }); +/***/ }), +/* 93 */ +/***/ (function(module, exports, __webpack_require__) { - // See below for fix regarding invisible grey/dim combination on Windows - builder.hasGrey = this.hasGrey || key === 'gray' || key === 'grey'; +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var he = moment.defineLocale('he', { + months : 'ינואר_פברואר_מרץ_אפריל_מאי_יוני_יולי_אוגוסט_ספטמבר_אוקטובר_נובמבר_דצמבר'.split('_'), + monthsShort : 'ינו׳_פבר׳_מרץ_אפר׳_מאי_יוני_יולי_אוג׳_ספט׳_אוק׳_נוב׳_דצמ׳'.split('_'), + weekdays : 'ראשון_שני_שלישי_רביעי_חמישי_שישי_שבת'.split('_'), + weekdaysShort : 'א׳_ב׳_ג׳_ד׳_ה׳_ו׳_ש׳'.split('_'), + weekdaysMin : 'א_ב_ג_ד_ה_ו_ש'.split('_'), + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD/MM/YYYY', + LL : 'D [ב]MMMM YYYY', + LLL : 'D [ב]MMMM YYYY HH:mm', + LLLL : 'dddd, D [ב]MMMM YYYY HH:mm', + l : 'D/M/YYYY', + ll : 'D MMM YYYY', + lll : 'D MMM YYYY HH:mm', + llll : 'ddd, D MMM YYYY HH:mm' + }, + calendar : { + sameDay : '[היום ב־]LT', + nextDay : '[מחר ב־]LT', + nextWeek : 'dddd [בשעה] LT', + lastDay : '[אתמול ב־]LT', + lastWeek : '[ביום] dddd [האחרון בשעה] LT', + sameElse : 'L' + }, + relativeTime : { + future : 'בעוד %s', + past : 'לפני %s', + s : 'מספר שניות', + ss : '%d שניות', + m : 'דקה', + mm : '%d דקות', + h : 'שעה', + hh : function (number) { + if (number === 2) { + return 'שעתיים'; + } + return number + ' שעות'; + }, + d : 'יום', + dd : function (number) { + if (number === 2) { + return 'יומיים'; + } + return number + ' ימים'; + }, + M : 'חודש', + MM : function (number) { + if (number === 2) { + return 'חודשיים'; + } + return number + ' חודשים'; + }, + y : 'שנה', + yy : function (number) { + if (number === 2) { + return 'שנתיים'; + } else if (number % 10 === 0 && number !== 10) { + return number + ' שנה'; + } + return number + ' שנים'; + } + }, + meridiemParse: /אחה"צ|לפנה"צ|אחרי הצהריים|לפני הצהריים|לפנות בוקר|בבוקר|בערב/i, + isPM : function (input) { + return /^(אחה"צ|אחרי הצהריים|בערב)$/.test(input); + }, + meridiem : function (hour, minute, isLower) { + if (hour < 5) { + return 'לפנות בוקר'; + } else if (hour < 10) { + return 'בבוקר'; + } else if (hour < 12) { + return isLower ? 'לפנה"צ' : 'לפני הצהריים'; + } else if (hour < 18) { + return isLower ? 'אחה"צ' : 'אחרי הצהריים'; + } else { + return 'בערב'; + } + } + }); - // `__proto__` is used because we must return a function, but there is - // no way to create a function with a different prototype - builder.__proto__ = proto; // eslint-disable-line no-proto + return he; - return builder; -} +}))); -function applyStyle() { - // Support varags, but simply cast to string in case there's only one arg - const args = arguments; - const argsLen = args.length; - let str = String(arguments[0]); - if (argsLen === 0) { - return ''; - } +/***/ }), +/* 94 */ +/***/ (function(module, exports, __webpack_require__) { - if (argsLen > 1) { - // Don't slice `arguments`, it prevents V8 optimizations - for (let a = 1; a < argsLen; a++) { - str += ' ' + args[a]; - } - } +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var symbolMap = { + '1': '१', + '2': '२', + '3': '३', + '4': '४', + '5': '५', + '6': '६', + '7': '७', + '8': '८', + '9': '९', + '0': '०' + }, + numberMap = { + '१': '1', + '२': '2', + '३': '3', + '४': '4', + '५': '5', + '६': '6', + '७': '7', + '८': '8', + '९': '9', + '०': '0' + }; + + var hi = moment.defineLocale('hi', { + months : 'जनवरी_फ़रवरी_मार्च_अप्रैल_मई_जून_जुलाई_अगस्त_सितम्बर_अक्टूबर_नवम्बर_दिसम्बर'.split('_'), + monthsShort : 'जन._फ़र._मार्च_अप्रै._मई_जून_जुल._अग._सित._अक्टू._नव._दिस.'.split('_'), + monthsParseExact: true, + weekdays : 'रविवार_सोमवार_मंगलवार_बुधवार_गुरूवार_शुक्रवार_शनिवार'.split('_'), + weekdaysShort : 'रवि_सोम_मंगल_बुध_गुरू_शुक्र_शनि'.split('_'), + weekdaysMin : 'र_सो_मं_बु_गु_शु_श'.split('_'), + longDateFormat : { + LT : 'A h:mm बजे', + LTS : 'A h:mm:ss बजे', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY, A h:mm बजे', + LLLL : 'dddd, D MMMM YYYY, A h:mm बजे' + }, + calendar : { + sameDay : '[आज] LT', + nextDay : '[कल] LT', + nextWeek : 'dddd, LT', + lastDay : '[कल] LT', + lastWeek : '[पिछले] dddd, LT', + sameElse : 'L' + }, + relativeTime : { + future : '%s में', + past : '%s पहले', + s : 'कुछ ही क्षण', + ss : '%d सेकंड', + m : 'एक मिनट', + mm : '%d मिनट', + h : 'एक घंटा', + hh : '%d घंटे', + d : 'एक दिन', + dd : '%d दिन', + M : 'एक महीने', + MM : '%d महीने', + y : 'एक वर्ष', + yy : '%d वर्ष' + }, + preparse: function (string) { + return string.replace(/[१२३४५६७८९०]/g, function (match) { + return numberMap[match]; + }); + }, + postformat: function (string) { + return string.replace(/\d/g, function (match) { + return symbolMap[match]; + }); + }, + // Hindi notation for meridiems are quite fuzzy in practice. While there exists + // a rigid notion of a 'Pahar' it is not used as rigidly in modern Hindi. + meridiemParse: /रात|सुबह|दोपहर|शाम/, + meridiemHour : function (hour, meridiem) { + if (hour === 12) { + hour = 0; + } + if (meridiem === 'रात') { + return hour < 4 ? hour : hour + 12; + } else if (meridiem === 'सुबह') { + return hour; + } else if (meridiem === 'दोपहर') { + return hour >= 10 ? hour : hour + 12; + } else if (meridiem === 'शाम') { + return hour + 12; + } + }, + meridiem : function (hour, minute, isLower) { + if (hour < 4) { + return 'रात'; + } else if (hour < 10) { + return 'सुबह'; + } else if (hour < 17) { + return 'दोपहर'; + } else if (hour < 20) { + return 'शाम'; + } else { + return 'रात'; + } + }, + week : { + dow : 0, // Sunday is the first day of the week. + doy : 6 // The week that contains Jan 6th is the first week of the year. + } + }); - if (!this.enabled || this.level <= 0 || !str) { - return this._empty ? '' : str; - } + return hi; - // Turns out that on Windows dimmed gray text becomes invisible in cmd.exe, - // see https://github.com/chalk/chalk/issues/58 - // If we're on Windows and we're dealing with a gray color, temporarily make 'dim' a noop. - const originalDim = ansiStyles.dim.open; - if (isSimpleWindowsTerm && this.hasGrey) { - ansiStyles.dim.open = ''; - } +}))); - for (const code of this._styles.slice().reverse()) { - // Replace any instances already present with a re-opening code - // otherwise only the part of the string until said closing code - // will be colored, and the rest will simply be 'plain'. - str = code.open + str.replace(code.closeRe, code.open) + code.close; - // Close the styling before a linebreak and reopen - // after next line to fix a bleed issue on macOS - // https://github.com/chalk/chalk/pull/92 - str = str.replace(/\r?\n/g, `${code.close}$&${code.open}`); - } - - // Reset the original `dim` if we changed it to work around the Windows dimmed gray issue - ansiStyles.dim.open = originalDim; +/***/ }), +/* 95 */ +/***/ (function(module, exports, __webpack_require__) { - return str; -} +//! moment.js locale configuration -function chalkTag(chalk, strings) { - if (!Array.isArray(strings)) { - // If chalk() was called by itself or with a string, - // return the string itself as a string. - return [].slice.call(arguments, 1).join(' '); - } +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; - const args = [].slice.call(arguments, 2); - const parts = [strings.raw[0]]; - for (let i = 1; i < strings.length; i++) { - parts.push(String(args[i - 1]).replace(/[{}\\]/g, '\\$&')); - parts.push(String(strings.raw[i])); - } + function translate(number, withoutSuffix, key) { + var result = number + ' '; + switch (key) { + case 'ss': + if (number === 1) { + result += 'sekunda'; + } else if (number === 2 || number === 3 || number === 4) { + result += 'sekunde'; + } else { + result += 'sekundi'; + } + return result; + case 'm': + return withoutSuffix ? 'jedna minuta' : 'jedne minute'; + case 'mm': + if (number === 1) { + result += 'minuta'; + } else if (number === 2 || number === 3 || number === 4) { + result += 'minute'; + } else { + result += 'minuta'; + } + return result; + case 'h': + return withoutSuffix ? 'jedan sat' : 'jednog sata'; + case 'hh': + if (number === 1) { + result += 'sat'; + } else if (number === 2 || number === 3 || number === 4) { + result += 'sata'; + } else { + result += 'sati'; + } + return result; + case 'dd': + if (number === 1) { + result += 'dan'; + } else { + result += 'dana'; + } + return result; + case 'MM': + if (number === 1) { + result += 'mjesec'; + } else if (number === 2 || number === 3 || number === 4) { + result += 'mjeseca'; + } else { + result += 'mjeseci'; + } + return result; + case 'yy': + if (number === 1) { + result += 'godina'; + } else if (number === 2 || number === 3 || number === 4) { + result += 'godine'; + } else { + result += 'godina'; + } + return result; + } + } - return template(chalk, parts.join('')); -} + var hr = moment.defineLocale('hr', { + months : { + format: 'siječnja_veljače_ožujka_travnja_svibnja_lipnja_srpnja_kolovoza_rujna_listopada_studenoga_prosinca'.split('_'), + standalone: 'siječanj_veljača_ožujak_travanj_svibanj_lipanj_srpanj_kolovoz_rujan_listopad_studeni_prosinac'.split('_') + }, + monthsShort : 'sij._velj._ožu._tra._svi._lip._srp._kol._ruj._lis._stu._pro.'.split('_'), + monthsParseExact: true, + weekdays : 'nedjelja_ponedjeljak_utorak_srijeda_četvrtak_petak_subota'.split('_'), + weekdaysShort : 'ned._pon._uto._sri._čet._pet._sub.'.split('_'), + weekdaysMin : 'ne_po_ut_sr_če_pe_su'.split('_'), + weekdaysParseExact : true, + longDateFormat : { + LT : 'H:mm', + LTS : 'H:mm:ss', + L : 'DD.MM.YYYY', + LL : 'D. MMMM YYYY', + LLL : 'D. MMMM YYYY H:mm', + LLLL : 'dddd, D. MMMM YYYY H:mm' + }, + calendar : { + sameDay : '[danas u] LT', + nextDay : '[sutra u] LT', + nextWeek : function () { + switch (this.day()) { + case 0: + return '[u] [nedjelju] [u] LT'; + case 3: + return '[u] [srijedu] [u] LT'; + case 6: + return '[u] [subotu] [u] LT'; + case 1: + case 2: + case 4: + case 5: + return '[u] dddd [u] LT'; + } + }, + lastDay : '[jučer u] LT', + lastWeek : function () { + switch (this.day()) { + case 0: + case 3: + return '[prošlu] dddd [u] LT'; + case 6: + return '[prošle] [subote] [u] LT'; + case 1: + case 2: + case 4: + case 5: + return '[prošli] dddd [u] LT'; + } + }, + sameElse : 'L' + }, + relativeTime : { + future : 'za %s', + past : 'prije %s', + s : 'par sekundi', + ss : translate, + m : translate, + mm : translate, + h : translate, + hh : translate, + d : 'dan', + dd : translate, + M : 'mjesec', + MM : translate, + y : 'godinu', + yy : translate + }, + dayOfMonthOrdinalParse: /\d{1,2}\./, + ordinal : '%d.', + week : { + dow : 1, // Monday is the first day of the week. + doy : 7 // The week that contains Jan 7th is the first week of the year. + } + }); -Object.defineProperties(Chalk.prototype, styles); + return hr; -module.exports = Chalk(); // eslint-disable-line new-cap -module.exports.supportsColor = stdoutColor; -module.exports.default = module.exports; // For TypeScript +}))); /***/ }), -/* 70 */ +/* 96 */ /***/ (function(module, exports, __webpack_require__) { -"use strict"; -/* WEBPACK VAR INJECTION */(function(module) { -const colorConvert = __webpack_require__(6); +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var weekEndings = 'vasárnap hétfőn kedden szerdán csütörtökön pénteken szombaton'.split(' '); + function translate(number, withoutSuffix, key, isFuture) { + var num = number; + switch (key) { + case 's': + return (isFuture || withoutSuffix) ? 'néhány másodperc' : 'néhány másodperce'; + case 'ss': + return num + (isFuture || withoutSuffix) ? ' másodperc' : ' másodperce'; + case 'm': + return 'egy' + (isFuture || withoutSuffix ? ' perc' : ' perce'); + case 'mm': + return num + (isFuture || withoutSuffix ? ' perc' : ' perce'); + case 'h': + return 'egy' + (isFuture || withoutSuffix ? ' óra' : ' órája'); + case 'hh': + return num + (isFuture || withoutSuffix ? ' óra' : ' órája'); + case 'd': + return 'egy' + (isFuture || withoutSuffix ? ' nap' : ' napja'); + case 'dd': + return num + (isFuture || withoutSuffix ? ' nap' : ' napja'); + case 'M': + return 'egy' + (isFuture || withoutSuffix ? ' hónap' : ' hónapja'); + case 'MM': + return num + (isFuture || withoutSuffix ? ' hónap' : ' hónapja'); + case 'y': + return 'egy' + (isFuture || withoutSuffix ? ' év' : ' éve'); + case 'yy': + return num + (isFuture || withoutSuffix ? ' év' : ' éve'); + } + return ''; + } + function week(isFuture) { + return (isFuture ? '' : '[múlt] ') + '[' + weekEndings[this.day()] + '] LT[-kor]'; + } + + var hu = moment.defineLocale('hu', { + months : 'január_február_március_április_május_június_július_augusztus_szeptember_október_november_december'.split('_'), + monthsShort : 'jan_feb_márc_ápr_máj_jún_júl_aug_szept_okt_nov_dec'.split('_'), + weekdays : 'vasárnap_hétfő_kedd_szerda_csütörtök_péntek_szombat'.split('_'), + weekdaysShort : 'vas_hét_kedd_sze_csüt_pén_szo'.split('_'), + weekdaysMin : 'v_h_k_sze_cs_p_szo'.split('_'), + longDateFormat : { + LT : 'H:mm', + LTS : 'H:mm:ss', + L : 'YYYY.MM.DD.', + LL : 'YYYY. MMMM D.', + LLL : 'YYYY. MMMM D. H:mm', + LLLL : 'YYYY. MMMM D., dddd H:mm' + }, + meridiemParse: /de|du/i, + isPM: function (input) { + return input.charAt(1).toLowerCase() === 'u'; + }, + meridiem : function (hours, minutes, isLower) { + if (hours < 12) { + return isLower === true ? 'de' : 'DE'; + } else { + return isLower === true ? 'du' : 'DU'; + } + }, + calendar : { + sameDay : '[ma] LT[-kor]', + nextDay : '[holnap] LT[-kor]', + nextWeek : function () { + return week.call(this, true); + }, + lastDay : '[tegnap] LT[-kor]', + lastWeek : function () { + return week.call(this, false); + }, + sameElse : 'L' + }, + relativeTime : { + future : '%s múlva', + past : '%s', + s : translate, + ss : translate, + m : translate, + mm : translate, + h : translate, + hh : translate, + d : translate, + dd : translate, + M : translate, + MM : translate, + y : translate, + yy : translate + }, + dayOfMonthOrdinalParse: /\d{1,2}\./, + ordinal : '%d.', + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } + }); -const wrapAnsi16 = (fn, offset) => function () { - const code = fn.apply(colorConvert, arguments); - return `\u001B[${code + offset}m`; -}; + return hu; -const wrapAnsi256 = (fn, offset) => function () { - const code = fn.apply(colorConvert, arguments); - return `\u001B[${38 + offset};5;${code}m`; -}; +}))); -const wrapAnsi16m = (fn, offset) => function () { - const rgb = fn.apply(colorConvert, arguments); - return `\u001B[${38 + offset};2;${rgb[0]};${rgb[1]};${rgb[2]}m`; -}; -function assembleStyles() { - const codes = new Map(); - const styles = { - modifier: { - reset: [0, 0], - // 21 isn't widely supported and 22 does the same thing - bold: [1, 22], - dim: [2, 22], - italic: [3, 23], - underline: [4, 24], - inverse: [7, 27], - hidden: [8, 28], - strikethrough: [9, 29] - }, - color: { - black: [30, 39], - red: [31, 39], - green: [32, 39], - yellow: [33, 39], - blue: [34, 39], - magenta: [35, 39], - cyan: [36, 39], - white: [37, 39], - gray: [90, 39], +/***/ }), +/* 97 */ +/***/ (function(module, exports, __webpack_require__) { - // Bright color - redBright: [91, 39], - greenBright: [92, 39], - yellowBright: [93, 39], - blueBright: [94, 39], - magentaBright: [95, 39], - cyanBright: [96, 39], - whiteBright: [97, 39] - }, - bgColor: { - bgBlack: [40, 49], - bgRed: [41, 49], - bgGreen: [42, 49], - bgYellow: [43, 49], - bgBlue: [44, 49], - bgMagenta: [45, 49], - bgCyan: [46, 49], - bgWhite: [47, 49], +//! moment.js locale configuration - // Bright color - bgBlackBright: [100, 49], - bgRedBright: [101, 49], - bgGreenBright: [102, 49], - bgYellowBright: [103, 49], - bgBlueBright: [104, 49], - bgMagentaBright: [105, 49], - bgCyanBright: [106, 49], - bgWhiteBright: [107, 49] - } - }; +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; - // Fix humans - styles.color.grey = styles.color.gray; - for (const groupName of Object.keys(styles)) { - const group = styles[groupName]; + var hyAm = moment.defineLocale('hy-am', { + months : { + format: 'հունվարի_փետրվարի_մարտի_ապրիլի_մայիսի_հունիսի_հուլիսի_օգոստոսի_սեպտեմբերի_հոկտեմբերի_նոյեմբերի_դեկտեմբերի'.split('_'), + standalone: 'հունվար_փետրվար_մարտ_ապրիլ_մայիս_հունիս_հուլիս_օգոստոս_սեպտեմբեր_հոկտեմբեր_նոյեմբեր_դեկտեմբեր'.split('_') + }, + monthsShort : 'հնվ_փտր_մրտ_ապր_մյս_հնս_հլս_օգս_սպտ_հկտ_նմբ_դկտ'.split('_'), + weekdays : 'կիրակի_երկուշաբթի_երեքշաբթի_չորեքշաբթի_հինգշաբթի_ուրբաթ_շաբաթ'.split('_'), + weekdaysShort : 'կրկ_երկ_երք_չրք_հնգ_ուրբ_շբթ'.split('_'), + weekdaysMin : 'կրկ_երկ_երք_չրք_հնգ_ուրբ_շբթ'.split('_'), + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD.MM.YYYY', + LL : 'D MMMM YYYY թ.', + LLL : 'D MMMM YYYY թ., HH:mm', + LLLL : 'dddd, D MMMM YYYY թ., HH:mm' + }, + calendar : { + sameDay: '[այսօր] LT', + nextDay: '[վաղը] LT', + lastDay: '[երեկ] LT', + nextWeek: function () { + return 'dddd [օրը ժամը] LT'; + }, + lastWeek: function () { + return '[անցած] dddd [օրը ժամը] LT'; + }, + sameElse: 'L' + }, + relativeTime : { + future : '%s հետո', + past : '%s առաջ', + s : 'մի քանի վայրկյան', + ss : '%d վայրկյան', + m : 'րոպե', + mm : '%d րոպե', + h : 'ժամ', + hh : '%d ժամ', + d : 'օր', + dd : '%d օր', + M : 'ամիս', + MM : '%d ամիս', + y : 'տարի', + yy : '%d տարի' + }, + meridiemParse: /գիշերվա|առավոտվա|ցերեկվա|երեկոյան/, + isPM: function (input) { + return /^(ցերեկվա|երեկոյան)$/.test(input); + }, + meridiem : function (hour) { + if (hour < 4) { + return 'գիշերվա'; + } else if (hour < 12) { + return 'առավոտվա'; + } else if (hour < 17) { + return 'ցերեկվա'; + } else { + return 'երեկոյան'; + } + }, + dayOfMonthOrdinalParse: /\d{1,2}|\d{1,2}-(ին|րդ)/, + ordinal: function (number, period) { + switch (period) { + case 'DDD': + case 'w': + case 'W': + case 'DDDo': + if (number === 1) { + return number + '-ին'; + } + return number + '-րդ'; + default: + return number; + } + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 7 // The week that contains Jan 7th is the first week of the year. + } + }); - for (const styleName of Object.keys(group)) { - const style = group[styleName]; + return hyAm; - styles[styleName] = { - open: `\u001B[${style[0]}m`, - close: `\u001B[${style[1]}m` - }; +}))); - group[styleName] = styles[styleName]; - codes.set(style[0], style[1]); - } +/***/ }), +/* 98 */ +/***/ (function(module, exports, __webpack_require__) { - Object.defineProperty(styles, groupName, { - value: group, - enumerable: false - }); +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var id = moment.defineLocale('id', { + months : 'Januari_Februari_Maret_April_Mei_Juni_Juli_Agustus_September_Oktober_November_Desember'.split('_'), + monthsShort : 'Jan_Feb_Mar_Apr_Mei_Jun_Jul_Agt_Sep_Okt_Nov_Des'.split('_'), + weekdays : 'Minggu_Senin_Selasa_Rabu_Kamis_Jumat_Sabtu'.split('_'), + weekdaysShort : 'Min_Sen_Sel_Rab_Kam_Jum_Sab'.split('_'), + weekdaysMin : 'Mg_Sn_Sl_Rb_Km_Jm_Sb'.split('_'), + longDateFormat : { + LT : 'HH.mm', + LTS : 'HH.mm.ss', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY [pukul] HH.mm', + LLLL : 'dddd, D MMMM YYYY [pukul] HH.mm' + }, + meridiemParse: /pagi|siang|sore|malam/, + meridiemHour : function (hour, meridiem) { + if (hour === 12) { + hour = 0; + } + if (meridiem === 'pagi') { + return hour; + } else if (meridiem === 'siang') { + return hour >= 11 ? hour : hour + 12; + } else if (meridiem === 'sore' || meridiem === 'malam') { + return hour + 12; + } + }, + meridiem : function (hours, minutes, isLower) { + if (hours < 11) { + return 'pagi'; + } else if (hours < 15) { + return 'siang'; + } else if (hours < 19) { + return 'sore'; + } else { + return 'malam'; + } + }, + calendar : { + sameDay : '[Hari ini pukul] LT', + nextDay : '[Besok pukul] LT', + nextWeek : 'dddd [pukul] LT', + lastDay : '[Kemarin pukul] LT', + lastWeek : 'dddd [lalu pukul] LT', + sameElse : 'L' + }, + relativeTime : { + future : 'dalam %s', + past : '%s yang lalu', + s : 'beberapa detik', + ss : '%d detik', + m : 'semenit', + mm : '%d menit', + h : 'sejam', + hh : '%d jam', + d : 'sehari', + dd : '%d hari', + M : 'sebulan', + MM : '%d bulan', + y : 'setahun', + yy : '%d tahun' + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 7 // The week that contains Jan 7th is the first week of the year. + } + }); - Object.defineProperty(styles, 'codes', { - value: codes, - enumerable: false - }); - } + return id; - const ansi2ansi = n => n; - const rgb2rgb = (r, g, b) => [r, g, b]; +}))); - styles.color.close = '\u001B[39m'; - styles.bgColor.close = '\u001B[49m'; - styles.color.ansi = { - ansi: wrapAnsi16(ansi2ansi, 0) - }; - styles.color.ansi256 = { - ansi256: wrapAnsi256(ansi2ansi, 0) - }; - styles.color.ansi16m = { - rgb: wrapAnsi16m(rgb2rgb, 0) - }; +/***/ }), +/* 99 */ +/***/ (function(module, exports, __webpack_require__) { - styles.bgColor.ansi = { - ansi: wrapAnsi16(ansi2ansi, 10) - }; - styles.bgColor.ansi256 = { - ansi256: wrapAnsi256(ansi2ansi, 10) - }; - styles.bgColor.ansi16m = { - rgb: wrapAnsi16m(rgb2rgb, 10) - }; +//! moment.js locale configuration - for (let key of Object.keys(colorConvert)) { - if (typeof colorConvert[key] !== 'object') { - continue; - } +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; - const suite = colorConvert[key]; - if (key === 'ansi16') { - key = 'ansi'; - } + function plural(n) { + if (n % 100 === 11) { + return true; + } else if (n % 10 === 1) { + return false; + } + return true; + } + function translate(number, withoutSuffix, key, isFuture) { + var result = number + ' '; + switch (key) { + case 's': + return withoutSuffix || isFuture ? 'nokkrar sekúndur' : 'nokkrum sekúndum'; + case 'ss': + if (plural(number)) { + return result + (withoutSuffix || isFuture ? 'sekúndur' : 'sekúndum'); + } + return result + 'sekúnda'; + case 'm': + return withoutSuffix ? 'mínúta' : 'mínútu'; + case 'mm': + if (plural(number)) { + return result + (withoutSuffix || isFuture ? 'mínútur' : 'mínútum'); + } else if (withoutSuffix) { + return result + 'mínúta'; + } + return result + 'mínútu'; + case 'hh': + if (plural(number)) { + return result + (withoutSuffix || isFuture ? 'klukkustundir' : 'klukkustundum'); + } + return result + 'klukkustund'; + case 'd': + if (withoutSuffix) { + return 'dagur'; + } + return isFuture ? 'dag' : 'degi'; + case 'dd': + if (plural(number)) { + if (withoutSuffix) { + return result + 'dagar'; + } + return result + (isFuture ? 'daga' : 'dögum'); + } else if (withoutSuffix) { + return result + 'dagur'; + } + return result + (isFuture ? 'dag' : 'degi'); + case 'M': + if (withoutSuffix) { + return 'mánuður'; + } + return isFuture ? 'mánuð' : 'mánuði'; + case 'MM': + if (plural(number)) { + if (withoutSuffix) { + return result + 'mánuðir'; + } + return result + (isFuture ? 'mánuði' : 'mánuðum'); + } else if (withoutSuffix) { + return result + 'mánuður'; + } + return result + (isFuture ? 'mánuð' : 'mánuði'); + case 'y': + return withoutSuffix || isFuture ? 'ár' : 'ári'; + case 'yy': + if (plural(number)) { + return result + (withoutSuffix || isFuture ? 'ár' : 'árum'); + } + return result + (withoutSuffix || isFuture ? 'ár' : 'ári'); + } + } + + var is = moment.defineLocale('is', { + months : 'janúar_febrúar_mars_apríl_maí_júní_júlí_ágúst_september_október_nóvember_desember'.split('_'), + monthsShort : 'jan_feb_mar_apr_maí_jún_júl_ágú_sep_okt_nóv_des'.split('_'), + weekdays : 'sunnudagur_mánudagur_þriðjudagur_miðvikudagur_fimmtudagur_föstudagur_laugardagur'.split('_'), + weekdaysShort : 'sun_mán_þri_mið_fim_fös_lau'.split('_'), + weekdaysMin : 'Su_Má_Þr_Mi_Fi_Fö_La'.split('_'), + longDateFormat : { + LT : 'H:mm', + LTS : 'H:mm:ss', + L : 'DD.MM.YYYY', + LL : 'D. MMMM YYYY', + LLL : 'D. MMMM YYYY [kl.] H:mm', + LLLL : 'dddd, D. MMMM YYYY [kl.] H:mm' + }, + calendar : { + sameDay : '[í dag kl.] LT', + nextDay : '[á morgun kl.] LT', + nextWeek : 'dddd [kl.] LT', + lastDay : '[í gær kl.] LT', + lastWeek : '[síðasta] dddd [kl.] LT', + sameElse : 'L' + }, + relativeTime : { + future : 'eftir %s', + past : 'fyrir %s síðan', + s : translate, + ss : translate, + m : translate, + mm : translate, + h : 'klukkustund', + hh : translate, + d : translate, + dd : translate, + M : translate, + MM : translate, + y : translate, + yy : translate + }, + dayOfMonthOrdinalParse: /\d{1,2}\./, + ordinal : '%d.', + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } + }); - if ('ansi16' in suite) { - styles.color.ansi[key] = wrapAnsi16(suite.ansi16, 0); - styles.bgColor.ansi[key] = wrapAnsi16(suite.ansi16, 10); - } + return is; - if ('ansi256' in suite) { - styles.color.ansi256[key] = wrapAnsi256(suite.ansi256, 0); - styles.bgColor.ansi256[key] = wrapAnsi256(suite.ansi256, 10); - } +}))); - if ('rgb' in suite) { - styles.color.ansi16m[key] = wrapAnsi16m(suite.rgb, 0); - styles.bgColor.ansi16m[key] = wrapAnsi16m(suite.rgb, 10); - } - } - return styles; -} +/***/ }), +/* 100 */ +/***/ (function(module, exports, __webpack_require__) { -// Make the export immutable -Object.defineProperty(module, 'exports', { - enumerable: true, - get: assembleStyles -}); +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var it = moment.defineLocale('it', { + months : 'gennaio_febbraio_marzo_aprile_maggio_giugno_luglio_agosto_settembre_ottobre_novembre_dicembre'.split('_'), + monthsShort : 'gen_feb_mar_apr_mag_giu_lug_ago_set_ott_nov_dic'.split('_'), + weekdays : 'domenica_lunedì_martedì_mercoledì_giovedì_venerdì_sabato'.split('_'), + weekdaysShort : 'dom_lun_mar_mer_gio_ven_sab'.split('_'), + weekdaysMin : 'do_lu_ma_me_gi_ve_sa'.split('_'), + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY HH:mm', + LLLL : 'dddd D MMMM YYYY HH:mm' + }, + calendar : { + sameDay: '[Oggi alle] LT', + nextDay: '[Domani alle] LT', + nextWeek: 'dddd [alle] LT', + lastDay: '[Ieri alle] LT', + lastWeek: function () { + switch (this.day()) { + case 0: + return '[la scorsa] dddd [alle] LT'; + default: + return '[lo scorso] dddd [alle] LT'; + } + }, + sameElse: 'L' + }, + relativeTime : { + future : function (s) { + return ((/^[0-9].+$/).test(s) ? 'tra' : 'in') + ' ' + s; + }, + past : '%s fa', + s : 'alcuni secondi', + ss : '%d secondi', + m : 'un minuto', + mm : '%d minuti', + h : 'un\'ora', + hh : '%d ore', + d : 'un giorno', + dd : '%d giorni', + M : 'un mese', + MM : '%d mesi', + y : 'un anno', + yy : '%d anni' + }, + dayOfMonthOrdinalParse : /\d{1,2}º/, + ordinal: '%dº', + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } + }); + + return it; + +}))); -/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(5)(module))) /***/ }), -/* 71 */ +/* 101 */ /***/ (function(module, exports, __webpack_require__) { -"use strict"; +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var itCh = moment.defineLocale('it-ch', { + months : 'gennaio_febbraio_marzo_aprile_maggio_giugno_luglio_agosto_settembre_ottobre_novembre_dicembre'.split('_'), + monthsShort : 'gen_feb_mar_apr_mag_giu_lug_ago_set_ott_nov_dic'.split('_'), + weekdays : 'domenica_lunedì_martedì_mercoledì_giovedì_venerdì_sabato'.split('_'), + weekdaysShort : 'dom_lun_mar_mer_gio_ven_sab'.split('_'), + weekdaysMin : 'do_lu_ma_me_gi_ve_sa'.split('_'), + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD.MM.YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY HH:mm', + LLLL : 'dddd D MMMM YYYY HH:mm' + }, + calendar : { + sameDay: '[Oggi alle] LT', + nextDay: '[Domani alle] LT', + nextWeek: 'dddd [alle] LT', + lastDay: '[Ieri alle] LT', + lastWeek: function () { + switch (this.day()) { + case 0: + return '[la scorsa] dddd [alle] LT'; + default: + return '[lo scorso] dddd [alle] LT'; + } + }, + sameElse: 'L' + }, + relativeTime : { + future : function (s) { + return ((/^[0-9].+$/).test(s) ? 'tra' : 'in') + ' ' + s; + }, + past : '%s fa', + s : 'alcuni secondi', + ss : '%d secondi', + m : 'un minuto', + mm : '%d minuti', + h : 'un\'ora', + hh : '%d ore', + d : 'un giorno', + dd : '%d giorni', + M : 'un mese', + MM : '%d mesi', + y : 'un anno', + yy : '%d anni' + }, + dayOfMonthOrdinalParse : /\d{1,2}º/, + ordinal: '%dº', + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } + }); -const os = __webpack_require__(11); -const hasFlag = __webpack_require__(12); + return itCh; -const env = process.env; +}))); -let forceColor; -if (hasFlag('no-color') || - hasFlag('no-colors') || - hasFlag('color=false')) { - forceColor = false; -} else if (hasFlag('color') || - hasFlag('colors') || - hasFlag('color=true') || - hasFlag('color=always')) { - forceColor = true; -} -if ('FORCE_COLOR' in env) { - forceColor = env.FORCE_COLOR.length === 0 || parseInt(env.FORCE_COLOR, 10) !== 0; -} -function translateLevel(level) { - if (level === 0) { - return false; - } +/***/ }), +/* 102 */ +/***/ (function(module, exports, __webpack_require__) { - return { - level, - hasBasic: true, - has256: level >= 2, - has16m: level >= 3 - }; -} +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var ja = moment.defineLocale('ja', { + months : '一月_二月_三月_四月_五月_六月_七月_八月_九月_十月_十一月_十二月'.split('_'), + monthsShort : '1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月'.split('_'), + weekdays : '日曜日_月曜日_火曜日_水曜日_木曜日_金曜日_土曜日'.split('_'), + weekdaysShort : '日_月_火_水_木_金_土'.split('_'), + weekdaysMin : '日_月_火_水_木_金_土'.split('_'), + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'YYYY/MM/DD', + LL : 'YYYY年M月D日', + LLL : 'YYYY年M月D日 HH:mm', + LLLL : 'YYYY年M月D日 dddd HH:mm', + l : 'YYYY/MM/DD', + ll : 'YYYY年M月D日', + lll : 'YYYY年M月D日 HH:mm', + llll : 'YYYY年M月D日(ddd) HH:mm' + }, + meridiemParse: /午前|午後/i, + isPM : function (input) { + return input === '午後'; + }, + meridiem : function (hour, minute, isLower) { + if (hour < 12) { + return '午前'; + } else { + return '午後'; + } + }, + calendar : { + sameDay : '[今日] LT', + nextDay : '[明日] LT', + nextWeek : function (now) { + if (now.week() < this.week()) { + return '[来週]dddd LT'; + } else { + return 'dddd LT'; + } + }, + lastDay : '[昨日] LT', + lastWeek : function (now) { + if (this.week() < now.week()) { + return '[先週]dddd LT'; + } else { + return 'dddd LT'; + } + }, + sameElse : 'L' + }, + dayOfMonthOrdinalParse : /\d{1,2}日/, + ordinal : function (number, period) { + switch (period) { + case 'd': + case 'D': + case 'DDD': + return number + '日'; + default: + return number; + } + }, + relativeTime : { + future : '%s後', + past : '%s前', + s : '数秒', + ss : '%d秒', + m : '1分', + mm : '%d分', + h : '1時間', + hh : '%d時間', + d : '1日', + dd : '%d日', + M : '1ヶ月', + MM : '%dヶ月', + y : '1年', + yy : '%d年' + } + }); -function supportsColor(stream) { - if (forceColor === false) { - return 0; - } + return ja; - if (hasFlag('color=16m') || - hasFlag('color=full') || - hasFlag('color=truecolor')) { - return 3; - } +}))); - if (hasFlag('color=256')) { - return 2; - } - if (stream && !stream.isTTY && forceColor !== true) { - // VS code debugger doesn't have isTTY set - if (env.VSCODE_PID) { - return 1; - } - return 0; - } +/***/ }), +/* 103 */ +/***/ (function(module, exports, __webpack_require__) { - const min = forceColor ? 1 : 0; +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var jv = moment.defineLocale('jv', { + months : 'Januari_Februari_Maret_April_Mei_Juni_Juli_Agustus_September_Oktober_Nopember_Desember'.split('_'), + monthsShort : 'Jan_Feb_Mar_Apr_Mei_Jun_Jul_Ags_Sep_Okt_Nop_Des'.split('_'), + weekdays : 'Minggu_Senen_Seloso_Rebu_Kemis_Jemuwah_Septu'.split('_'), + weekdaysShort : 'Min_Sen_Sel_Reb_Kem_Jem_Sep'.split('_'), + weekdaysMin : 'Mg_Sn_Sl_Rb_Km_Jm_Sp'.split('_'), + longDateFormat : { + LT : 'HH.mm', + LTS : 'HH.mm.ss', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY [pukul] HH.mm', + LLLL : 'dddd, D MMMM YYYY [pukul] HH.mm' + }, + meridiemParse: /enjing|siyang|sonten|ndalu/, + meridiemHour : function (hour, meridiem) { + if (hour === 12) { + hour = 0; + } + if (meridiem === 'enjing') { + return hour; + } else if (meridiem === 'siyang') { + return hour >= 11 ? hour : hour + 12; + } else if (meridiem === 'sonten' || meridiem === 'ndalu') { + return hour + 12; + } + }, + meridiem : function (hours, minutes, isLower) { + if (hours < 11) { + return 'enjing'; + } else if (hours < 15) { + return 'siyang'; + } else if (hours < 19) { + return 'sonten'; + } else { + return 'ndalu'; + } + }, + calendar : { + sameDay : '[Dinten puniko pukul] LT', + nextDay : '[Mbenjang pukul] LT', + nextWeek : 'dddd [pukul] LT', + lastDay : '[Kala wingi pukul] LT', + lastWeek : 'dddd [kepengker pukul] LT', + sameElse : 'L' + }, + relativeTime : { + future : 'wonten ing %s', + past : '%s ingkang kepengker', + s : 'sawetawis detik', + ss : '%d detik', + m : 'setunggal menit', + mm : '%d menit', + h : 'setunggal jam', + hh : '%d jam', + d : 'sedinten', + dd : '%d dinten', + M : 'sewulan', + MM : '%d wulan', + y : 'setaun', + yy : '%d taun' + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 7 // The week that contains Jan 7th is the first week of the year. + } + }); - if (process.platform === 'win32') { - // Node.js 7.5.0 is the first version of Node.js to include a patch to - // libuv that enables 256 color output on Windows. Anything earlier and it - // won't work. However, here we target Node.js 8 at minimum as it is an LTS - // release, and Node.js 7 is not. Windows 10 build 10586 is the first Windows - // release that supports 256 colors. Windows 10 build 14931 is the first release - // that supports 16m/TrueColor. - const osRelease = os.release().split('.'); - if ( - Number(process.versions.node.split('.')[0]) >= 8 && - Number(osRelease[0]) >= 10 && - Number(osRelease[2]) >= 10586 - ) { - return Number(osRelease[2]) >= 14931 ? 3 : 2; - } + return jv; - return 1; - } +}))); - if ('CI' in env) { - if (['TRAVIS', 'CIRCLECI', 'APPVEYOR', 'GITLAB_CI'].some(sign => sign in env) || env.CI_NAME === 'codeship') { - return 1; - } - return min; - } +/***/ }), +/* 104 */ +/***/ (function(module, exports, __webpack_require__) { - if ('TEAMCITY_VERSION' in env) { - return /^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(env.TEAMCITY_VERSION) ? 1 : 0; - } +//! moment.js locale configuration - if (env.COLORTERM === 'truecolor') { - return 3; - } +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; - if ('TERM_PROGRAM' in env) { - const version = parseInt((env.TERM_PROGRAM_VERSION || '').split('.')[0], 10); - switch (env.TERM_PROGRAM) { - case 'iTerm.app': - return version >= 3 ? 3 : 2; - case 'Apple_Terminal': - return 2; - // No default - } - } + var ka = moment.defineLocale('ka', { + months : { + standalone: 'იანვარი_თებერვალი_მარტი_აპრილი_მაისი_ივნისი_ივლისი_აგვისტო_სექტემბერი_ოქტომბერი_ნოემბერი_დეკემბერი'.split('_'), + format: 'იანვარს_თებერვალს_მარტს_აპრილის_მაისს_ივნისს_ივლისს_აგვისტს_სექტემბერს_ოქტომბერს_ნოემბერს_დეკემბერს'.split('_') + }, + monthsShort : 'იან_თებ_მარ_აპრ_მაი_ივნ_ივლ_აგვ_სექ_ოქტ_ნოე_დეკ'.split('_'), + weekdays : { + standalone: 'კვირა_ორშაბათი_სამშაბათი_ოთხშაბათი_ხუთშაბათი_პარასკევი_შაბათი'.split('_'), + format: 'კვირას_ორშაბათს_სამშაბათს_ოთხშაბათს_ხუთშაბათს_პარასკევს_შაბათს'.split('_'), + isFormat: /(წინა|შემდეგ)/ + }, + weekdaysShort : 'კვი_ორშ_სამ_ოთხ_ხუთ_პარ_შაბ'.split('_'), + weekdaysMin : 'კვ_ორ_სა_ოთ_ხუ_პა_შა'.split('_'), + longDateFormat : { + LT : 'h:mm A', + LTS : 'h:mm:ss A', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY h:mm A', + LLLL : 'dddd, D MMMM YYYY h:mm A' + }, + calendar : { + sameDay : '[დღეს] LT[-ზე]', + nextDay : '[ხვალ] LT[-ზე]', + lastDay : '[გუშინ] LT[-ზე]', + nextWeek : '[შემდეგ] dddd LT[-ზე]', + lastWeek : '[წინა] dddd LT-ზე', + sameElse : 'L' + }, + relativeTime : { + future : function (s) { + return (/(წამი|წუთი|საათი|წელი)/).test(s) ? + s.replace(/ი$/, 'ში') : + s + 'ში'; + }, + past : function (s) { + if ((/(წამი|წუთი|საათი|დღე|თვე)/).test(s)) { + return s.replace(/(ი|ე)$/, 'ის წინ'); + } + if ((/წელი/).test(s)) { + return s.replace(/წელი$/, 'წლის წინ'); + } + }, + s : 'რამდენიმე წამი', + ss : '%d წამი', + m : 'წუთი', + mm : '%d წუთი', + h : 'საათი', + hh : '%d საათი', + d : 'დღე', + dd : '%d დღე', + M : 'თვე', + MM : '%d თვე', + y : 'წელი', + yy : '%d წელი' + }, + dayOfMonthOrdinalParse: /0|1-ლი|მე-\d{1,2}|\d{1,2}-ე/, + ordinal : function (number) { + if (number === 0) { + return number; + } + if (number === 1) { + return number + '-ლი'; + } + if ((number < 20) || (number <= 100 && (number % 20 === 0)) || (number % 100 === 0)) { + return 'მე-' + number; + } + return number + '-ე'; + }, + week : { + dow : 1, + doy : 7 + } + }); - if (/-256(color)?$/i.test(env.TERM)) { - return 2; - } + return ka; - if (/^screen|^xterm|^vt100|^rxvt|color|ansi|cygwin|linux/i.test(env.TERM)) { - return 1; - } +}))); - if ('COLORTERM' in env) { - return 1; - } - if (env.TERM === 'dumb') { - return min; - } +/***/ }), +/* 105 */ +/***/ (function(module, exports, __webpack_require__) { - return min; -} +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var suffixes = { + 0: '-ші', + 1: '-ші', + 2: '-ші', + 3: '-ші', + 4: '-ші', + 5: '-ші', + 6: '-шы', + 7: '-ші', + 8: '-ші', + 9: '-шы', + 10: '-шы', + 20: '-шы', + 30: '-шы', + 40: '-шы', + 50: '-ші', + 60: '-шы', + 70: '-ші', + 80: '-ші', + 90: '-шы', + 100: '-ші' + }; + + var kk = moment.defineLocale('kk', { + months : 'қаңтар_ақпан_наурыз_сәуір_мамыр_маусым_шілде_тамыз_қыркүйек_қазан_қараша_желтоқсан'.split('_'), + monthsShort : 'қаң_ақп_нау_сәу_мам_мау_шіл_там_қыр_қаз_қар_жел'.split('_'), + weekdays : 'жексенбі_дүйсенбі_сейсенбі_сәрсенбі_бейсенбі_жұма_сенбі'.split('_'), + weekdaysShort : 'жек_дүй_сей_сәр_бей_жұм_сен'.split('_'), + weekdaysMin : 'жк_дй_сй_ср_бй_жм_сн'.split('_'), + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD.MM.YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY HH:mm', + LLLL : 'dddd, D MMMM YYYY HH:mm' + }, + calendar : { + sameDay : '[Бүгін сағат] LT', + nextDay : '[Ертең сағат] LT', + nextWeek : 'dddd [сағат] LT', + lastDay : '[Кеше сағат] LT', + lastWeek : '[Өткен аптаның] dddd [сағат] LT', + sameElse : 'L' + }, + relativeTime : { + future : '%s ішінде', + past : '%s бұрын', + s : 'бірнеше секунд', + ss : '%d секунд', + m : 'бір минут', + mm : '%d минут', + h : 'бір сағат', + hh : '%d сағат', + d : 'бір күн', + dd : '%d күн', + M : 'бір ай', + MM : '%d ай', + y : 'бір жыл', + yy : '%d жыл' + }, + dayOfMonthOrdinalParse: /\d{1,2}-(ші|шы)/, + ordinal : function (number) { + var a = number % 10, + b = number >= 100 ? 100 : null; + return number + (suffixes[number] || suffixes[a] || suffixes[b]); + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 7 // The week that contains Jan 7th is the first week of the year. + } + }); -function getSupportLevel(stream) { - const level = supportsColor(stream); - return translateLevel(level); -} + return kk; -module.exports = { - supportsColor: getSupportLevel, - stdout: getSupportLevel(process.stdout), - stderr: getSupportLevel(process.stderr) -}; +}))); /***/ }), -/* 72 */ +/* 106 */ /***/ (function(module, exports, __webpack_require__) { -"use strict"; - -const TEMPLATE_REGEX = /(?:\\(u[a-f\d]{4}|x[a-f\d]{2}|.))|(?:\{(~)?(\w+(?:\([^)]*\))?(?:\.\w+(?:\([^)]*\))?)*)(?:[ \t]|(?=\r?\n)))|(\})|((?:.|[\r\n\f])+?)/gi; -const STYLE_REGEX = /(?:^|\.)(\w+)(?:\(([^)]*)\))?/g; -const STRING_REGEX = /^(['"])((?:\\.|(?!\1)[^\\])*)\1$/; -const ESCAPE_REGEX = /\\(u[a-f\d]{4}|x[a-f\d]{2}|.)|([^\\])/gi; +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var symbolMap = { + '1': '១', + '2': '២', + '3': '៣', + '4': '៤', + '5': '៥', + '6': '៦', + '7': '៧', + '8': '៨', + '9': '៩', + '0': '០' + }, numberMap = { + '១': '1', + '២': '2', + '៣': '3', + '៤': '4', + '៥': '5', + '៦': '6', + '៧': '7', + '៨': '8', + '៩': '9', + '០': '0' + }; + + var km = moment.defineLocale('km', { + months: 'មករា_កុម្ភៈ_មីនា_មេសា_ឧសភា_មិថុនា_កក្កដា_សីហា_កញ្ញា_តុលា_វិច្ឆិកា_ធ្នូ'.split( + '_' + ), + monthsShort: 'មករា_កុម្ភៈ_មីនា_មេសា_ឧសភា_មិថុនា_កក្កដា_សីហា_កញ្ញា_តុលា_វិច្ឆិកា_ធ្នូ'.split( + '_' + ), + weekdays: 'អាទិត្យ_ច័ន្ទ_អង្គារ_ពុធ_ព្រហស្បតិ៍_សុក្រ_សៅរ៍'.split('_'), + weekdaysShort: 'អា_ច_អ_ព_ព្រ_សុ_ស'.split('_'), + weekdaysMin: 'អា_ច_អ_ព_ព្រ_សុ_ស'.split('_'), + weekdaysParseExact: true, + longDateFormat: { + LT: 'HH:mm', + LTS: 'HH:mm:ss', + L: 'DD/MM/YYYY', + LL: 'D MMMM YYYY', + LLL: 'D MMMM YYYY HH:mm', + LLLL: 'dddd, D MMMM YYYY HH:mm' + }, + meridiemParse: /ព្រឹក|ល្ងាច/, + isPM: function (input) { + return input === 'ល្ងាច'; + }, + meridiem: function (hour, minute, isLower) { + if (hour < 12) { + return 'ព្រឹក'; + } else { + return 'ល្ងាច'; + } + }, + calendar: { + sameDay: '[ថ្ងៃនេះ ម៉ោង] LT', + nextDay: '[ស្អែក ម៉ោង] LT', + nextWeek: 'dddd [ម៉ោង] LT', + lastDay: '[ម្សិលមិញ ម៉ោង] LT', + lastWeek: 'dddd [សប្តាហ៍មុន] [ម៉ោង] LT', + sameElse: 'L' + }, + relativeTime: { + future: '%sទៀត', + past: '%sមុន', + s: 'ប៉ុន្មានវិនាទី', + ss: '%d វិនាទី', + m: 'មួយនាទី', + mm: '%d នាទី', + h: 'មួយម៉ោង', + hh: '%d ម៉ោង', + d: 'មួយថ្ងៃ', + dd: '%d ថ្ងៃ', + M: 'មួយខែ', + MM: '%d ខែ', + y: 'មួយឆ្នាំ', + yy: '%d ឆ្នាំ' + }, + dayOfMonthOrdinalParse : /ទី\d{1,2}/, + ordinal : 'ទី%d', + preparse: function (string) { + return string.replace(/[១២៣៤៥៦៧៨៩០]/g, function (match) { + return numberMap[match]; + }); + }, + postformat: function (string) { + return string.replace(/\d/g, function (match) { + return symbolMap[match]; + }); + }, + week: { + dow: 1, // Monday is the first day of the week. + doy: 4 // The week that contains Jan 4th is the first week of the year. + } + }); -const ESCAPES = new Map([ - ['n', '\n'], - ['r', '\r'], - ['t', '\t'], - ['b', '\b'], - ['f', '\f'], - ['v', '\v'], - ['0', '\0'], - ['\\', '\\'], - ['e', '\u001B'], - ['a', '\u0007'] -]); + return km; -function unescape(c) { - if ((c[0] === 'u' && c.length === 5) || (c[0] === 'x' && c.length === 3)) { - return String.fromCharCode(parseInt(c.slice(1), 16)); - } +}))); - return ESCAPES.get(c) || c; -} -function parseArguments(name, args) { - const results = []; - const chunks = args.trim().split(/\s*,\s*/g); - let matches; +/***/ }), +/* 107 */ +/***/ (function(module, exports, __webpack_require__) { - for (const chunk of chunks) { - if (!isNaN(chunk)) { - results.push(Number(chunk)); - } else if ((matches = chunk.match(STRING_REGEX))) { - results.push(matches[2].replace(ESCAPE_REGEX, (m, escape, chr) => escape ? unescape(escape) : chr)); - } else { - throw new Error(`Invalid Chalk template style argument: ${chunk} (in style '${name}')`); - } - } +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var symbolMap = { + '1': '೧', + '2': '೨', + '3': '೩', + '4': '೪', + '5': '೫', + '6': '೬', + '7': '೭', + '8': '೮', + '9': '೯', + '0': '೦' + }, + numberMap = { + '೧': '1', + '೨': '2', + '೩': '3', + '೪': '4', + '೫': '5', + '೬': '6', + '೭': '7', + '೮': '8', + '೯': '9', + '೦': '0' + }; + + var kn = moment.defineLocale('kn', { + months : 'ಜನವರಿ_ಫೆಬ್ರವರಿ_ಮಾರ್ಚ್_ಏಪ್ರಿಲ್_ಮೇ_ಜೂನ್_ಜುಲೈ_ಆಗಸ್ಟ್_ಸೆಪ್ಟೆಂಬರ್_ಅಕ್ಟೋಬರ್_ನವೆಂಬರ್_ಡಿಸೆಂಬರ್'.split('_'), + monthsShort : 'ಜನ_ಫೆಬ್ರ_ಮಾರ್ಚ್_ಏಪ್ರಿಲ್_ಮೇ_ಜೂನ್_ಜುಲೈ_ಆಗಸ್ಟ್_ಸೆಪ್ಟೆಂ_ಅಕ್ಟೋ_ನವೆಂ_ಡಿಸೆಂ'.split('_'), + monthsParseExact: true, + weekdays : 'ಭಾನುವಾರ_ಸೋಮವಾರ_ಮಂಗಳವಾರ_ಬುಧವಾರ_ಗುರುವಾರ_ಶುಕ್ರವಾರ_ಶನಿವಾರ'.split('_'), + weekdaysShort : 'ಭಾನು_ಸೋಮ_ಮಂಗಳ_ಬುಧ_ಗುರು_ಶುಕ್ರ_ಶನಿ'.split('_'), + weekdaysMin : 'ಭಾ_ಸೋ_ಮಂ_ಬು_ಗು_ಶು_ಶ'.split('_'), + longDateFormat : { + LT : 'A h:mm', + LTS : 'A h:mm:ss', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY, A h:mm', + LLLL : 'dddd, D MMMM YYYY, A h:mm' + }, + calendar : { + sameDay : '[ಇಂದು] LT', + nextDay : '[ನಾಳೆ] LT', + nextWeek : 'dddd, LT', + lastDay : '[ನಿನ್ನೆ] LT', + lastWeek : '[ಕೊನೆಯ] dddd, LT', + sameElse : 'L' + }, + relativeTime : { + future : '%s ನಂತರ', + past : '%s ಹಿಂದೆ', + s : 'ಕೆಲವು ಕ್ಷಣಗಳು', + ss : '%d ಸೆಕೆಂಡುಗಳು', + m : 'ಒಂದು ನಿಮಿಷ', + mm : '%d ನಿಮಿಷ', + h : 'ಒಂದು ಗಂಟೆ', + hh : '%d ಗಂಟೆ', + d : 'ಒಂದು ದಿನ', + dd : '%d ದಿನ', + M : 'ಒಂದು ತಿಂಗಳು', + MM : '%d ತಿಂಗಳು', + y : 'ಒಂದು ವರ್ಷ', + yy : '%d ವರ್ಷ' + }, + preparse: function (string) { + return string.replace(/[೧೨೩೪೫೬೭೮೯೦]/g, function (match) { + return numberMap[match]; + }); + }, + postformat: function (string) { + return string.replace(/\d/g, function (match) { + return symbolMap[match]; + }); + }, + meridiemParse: /ರಾತ್ರಿ|ಬೆಳಿಗ್ಗೆ|ಮಧ್ಯಾಹ್ನ|ಸಂಜೆ/, + meridiemHour : function (hour, meridiem) { + if (hour === 12) { + hour = 0; + } + if (meridiem === 'ರಾತ್ರಿ') { + return hour < 4 ? hour : hour + 12; + } else if (meridiem === 'ಬೆಳಿಗ್ಗೆ') { + return hour; + } else if (meridiem === 'ಮಧ್ಯಾಹ್ನ') { + return hour >= 10 ? hour : hour + 12; + } else if (meridiem === 'ಸಂಜೆ') { + return hour + 12; + } + }, + meridiem : function (hour, minute, isLower) { + if (hour < 4) { + return 'ರಾತ್ರಿ'; + } else if (hour < 10) { + return 'ಬೆಳಿಗ್ಗೆ'; + } else if (hour < 17) { + return 'ಮಧ್ಯಾಹ್ನ'; + } else if (hour < 20) { + return 'ಸಂಜೆ'; + } else { + return 'ರಾತ್ರಿ'; + } + }, + dayOfMonthOrdinalParse: /\d{1,2}(ನೇ)/, + ordinal : function (number) { + return number + 'ನೇ'; + }, + week : { + dow : 0, // Sunday is the first day of the week. + doy : 6 // The week that contains Jan 6th is the first week of the year. + } + }); - return results; -} + return kn; -function parseStyle(style) { - STYLE_REGEX.lastIndex = 0; +}))); - const results = []; - let matches; - while ((matches = STYLE_REGEX.exec(style)) !== null) { - const name = matches[1]; +/***/ }), +/* 108 */ +/***/ (function(module, exports, __webpack_require__) { - if (matches[2]) { - const args = parseArguments(name, matches[2]); - results.push([name].concat(args)); - } else { - results.push([name]); - } - } +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var ko = moment.defineLocale('ko', { + months : '1월_2월_3월_4월_5월_6월_7월_8월_9월_10월_11월_12월'.split('_'), + monthsShort : '1월_2월_3월_4월_5월_6월_7월_8월_9월_10월_11월_12월'.split('_'), + weekdays : '일요일_월요일_화요일_수요일_목요일_금요일_토요일'.split('_'), + weekdaysShort : '일_월_화_수_목_금_토'.split('_'), + weekdaysMin : '일_월_화_수_목_금_토'.split('_'), + longDateFormat : { + LT : 'A h:mm', + LTS : 'A h:mm:ss', + L : 'YYYY.MM.DD.', + LL : 'YYYY년 MMMM D일', + LLL : 'YYYY년 MMMM D일 A h:mm', + LLLL : 'YYYY년 MMMM D일 dddd A h:mm', + l : 'YYYY.MM.DD.', + ll : 'YYYY년 MMMM D일', + lll : 'YYYY년 MMMM D일 A h:mm', + llll : 'YYYY년 MMMM D일 dddd A h:mm' + }, + calendar : { + sameDay : '오늘 LT', + nextDay : '내일 LT', + nextWeek : 'dddd LT', + lastDay : '어제 LT', + lastWeek : '지난주 dddd LT', + sameElse : 'L' + }, + relativeTime : { + future : '%s 후', + past : '%s 전', + s : '몇 초', + ss : '%d초', + m : '1분', + mm : '%d분', + h : '한 시간', + hh : '%d시간', + d : '하루', + dd : '%d일', + M : '한 달', + MM : '%d달', + y : '일 년', + yy : '%d년' + }, + dayOfMonthOrdinalParse : /\d{1,2}(일|월|주)/, + ordinal : function (number, period) { + switch (period) { + case 'd': + case 'D': + case 'DDD': + return number + '일'; + case 'M': + return number + '월'; + case 'w': + case 'W': + return number + '주'; + default: + return number; + } + }, + meridiemParse : /오전|오후/, + isPM : function (token) { + return token === '오후'; + }, + meridiem : function (hour, minute, isUpper) { + return hour < 12 ? '오전' : '오후'; + } + }); - return results; -} + return ko; -function buildStyle(chalk, styles) { - const enabled = {}; +}))); - for (const layer of styles) { - for (const style of layer.styles) { - enabled[style[0]] = layer.inverse ? null : style.slice(1); - } - } - let current = chalk; - for (const styleName of Object.keys(enabled)) { - if (Array.isArray(enabled[styleName])) { - if (!(styleName in current)) { - throw new Error(`Unknown Chalk style: ${styleName}`); - } +/***/ }), +/* 109 */ +/***/ (function(module, exports, __webpack_require__) { - if (enabled[styleName].length > 0) { - current = current[styleName].apply(current, enabled[styleName]); - } else { - current = current[styleName]; - } - } - } +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var symbolMap = { + '1': '١', + '2': '٢', + '3': '٣', + '4': '٤', + '5': '٥', + '6': '٦', + '7': '٧', + '8': '٨', + '9': '٩', + '0': '٠' + }, numberMap = { + '١': '1', + '٢': '2', + '٣': '3', + '٤': '4', + '٥': '5', + '٦': '6', + '٧': '7', + '٨': '8', + '٩': '9', + '٠': '0' + }, + months = [ + 'کانونی دووەم', + 'شوبات', + 'ئازار', + 'نیسان', + 'ئایار', + 'حوزەیران', + 'تەمموز', + 'ئاب', + 'ئەیلوول', + 'تشرینی یەكەم', + 'تشرینی دووەم', + 'كانونی یەکەم' + ]; - return current; -} -module.exports = (chalk, tmp) => { - const styles = []; - const chunks = []; - let chunk = []; + var ku = moment.defineLocale('ku', { + months : months, + monthsShort : months, + weekdays : 'یه‌كشه‌ممه‌_دووشه‌ممه‌_سێشه‌ممه‌_چوارشه‌ممه‌_پێنجشه‌ممه‌_هه‌ینی_شه‌ممه‌'.split('_'), + weekdaysShort : 'یه‌كشه‌م_دووشه‌م_سێشه‌م_چوارشه‌م_پێنجشه‌م_هه‌ینی_شه‌ممه‌'.split('_'), + weekdaysMin : 'ی_د_س_چ_پ_ه_ش'.split('_'), + weekdaysParseExact : true, + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY HH:mm', + LLLL : 'dddd, D MMMM YYYY HH:mm' + }, + meridiemParse: /ئێواره‌|به‌یانی/, + isPM: function (input) { + return /ئێواره‌/.test(input); + }, + meridiem : function (hour, minute, isLower) { + if (hour < 12) { + return 'به‌یانی'; + } else { + return 'ئێواره‌'; + } + }, + calendar : { + sameDay : '[ئه‌مرۆ كاتژمێر] LT', + nextDay : '[به‌یانی كاتژمێر] LT', + nextWeek : 'dddd [كاتژمێر] LT', + lastDay : '[دوێنێ كاتژمێر] LT', + lastWeek : 'dddd [كاتژمێر] LT', + sameElse : 'L' + }, + relativeTime : { + future : 'له‌ %s', + past : '%s', + s : 'چه‌ند چركه‌یه‌ك', + ss : 'چركه‌ %d', + m : 'یه‌ك خوله‌ك', + mm : '%d خوله‌ك', + h : 'یه‌ك كاتژمێر', + hh : '%d كاتژمێر', + d : 'یه‌ك ڕۆژ', + dd : '%d ڕۆژ', + M : 'یه‌ك مانگ', + MM : '%d مانگ', + y : 'یه‌ك ساڵ', + yy : '%d ساڵ' + }, + preparse: function (string) { + return string.replace(/[١٢٣٤٥٦٧٨٩٠]/g, function (match) { + return numberMap[match]; + }).replace(/،/g, ','); + }, + postformat: function (string) { + return string.replace(/\d/g, function (match) { + return symbolMap[match]; + }).replace(/,/g, '،'); + }, + week : { + dow : 6, // Saturday is the first day of the week. + doy : 12 // The week that contains Jan 12th is the first week of the year. + } + }); - // eslint-disable-next-line max-params - tmp.replace(TEMPLATE_REGEX, (m, escapeChar, inverse, style, close, chr) => { - if (escapeChar) { - chunk.push(unescape(escapeChar)); - } else if (style) { - const str = chunk.join(''); - chunk = []; - chunks.push(styles.length === 0 ? str : buildStyle(chalk, styles)(str)); - styles.push({inverse, styles: parseStyle(style)}); - } else if (close) { - if (styles.length === 0) { - throw new Error('Found extraneous } in Chalk template literal'); - } + return ku; - chunks.push(buildStyle(chalk, styles)(chunk.join(''))); - chunk = []; - styles.pop(); - } else { - chunk.push(chr); - } - }); +}))); - chunks.push(chunk.join('')); - if (styles.length > 0) { - const errMsg = `Chalk template literal is missing ${styles.length} closing bracket${styles.length === 1 ? '' : 's'} (\`}\`)`; - throw new Error(errMsg); - } +/***/ }), +/* 110 */ +/***/ (function(module, exports, __webpack_require__) { - return chunks.join(''); -}; +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var suffixes = { + 0: '-чү', + 1: '-чи', + 2: '-чи', + 3: '-чү', + 4: '-чү', + 5: '-чи', + 6: '-чы', + 7: '-чи', + 8: '-чи', + 9: '-чу', + 10: '-чу', + 20: '-чы', + 30: '-чу', + 40: '-чы', + 50: '-чү', + 60: '-чы', + 70: '-чи', + 80: '-чи', + 90: '-чу', + 100: '-чү' + }; + + var ky = moment.defineLocale('ky', { + months : 'январь_февраль_март_апрель_май_июнь_июль_август_сентябрь_октябрь_ноябрь_декабрь'.split('_'), + monthsShort : 'янв_фев_март_апр_май_июнь_июль_авг_сен_окт_ноя_дек'.split('_'), + weekdays : 'Жекшемби_Дүйшөмбү_Шейшемби_Шаршемби_Бейшемби_Жума_Ишемби'.split('_'), + weekdaysShort : 'Жек_Дүй_Шей_Шар_Бей_Жум_Ише'.split('_'), + weekdaysMin : 'Жк_Дй_Шй_Шр_Бй_Жм_Иш'.split('_'), + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD.MM.YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY HH:mm', + LLLL : 'dddd, D MMMM YYYY HH:mm' + }, + calendar : { + sameDay : '[Бүгүн саат] LT', + nextDay : '[Эртең саат] LT', + nextWeek : 'dddd [саат] LT', + lastDay : '[Кечээ саат] LT', + lastWeek : '[Өткөн аптанын] dddd [күнү] [саат] LT', + sameElse : 'L' + }, + relativeTime : { + future : '%s ичинде', + past : '%s мурун', + s : 'бирнече секунд', + ss : '%d секунд', + m : 'бир мүнөт', + mm : '%d мүнөт', + h : 'бир саат', + hh : '%d саат', + d : 'бир күн', + dd : '%d күн', + M : 'бир ай', + MM : '%d ай', + y : 'бир жыл', + yy : '%d жыл' + }, + dayOfMonthOrdinalParse: /\d{1,2}-(чи|чы|чү|чу)/, + ordinal : function (number) { + var a = number % 10, + b = number >= 100 ? 100 : null; + return number + (suffixes[number] || suffixes[a] || suffixes[b]); + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 7 // The week that contains Jan 7th is the first week of the year. + } + }); + return ky; -/***/ }), -/* 73 */ -/***/ (function(module, exports, __webpack_require__) { +}))); -module.exports = normalize -var fixer = __webpack_require__(74) -normalize.fixer = fixer +/***/ }), +/* 111 */ +/***/ (function(module, exports, __webpack_require__) { -var makeWarning = __webpack_require__(96) +//! moment.js locale configuration -var fieldsToFix = ['name','version','description','repository','modules','scripts' - ,'files','bin','man','bugs','keywords','readme','homepage','license'] -var otherThingsToFix = ['dependencies','people', 'typos'] +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; -var thingsToFix = fieldsToFix.map(function(fieldName) { - return ucFirst(fieldName) + "Field" -}) -// two ways to do this in CoffeeScript on only one line, sub-70 chars: -// thingsToFix = fieldsToFix.map (name) -> ucFirst(name) + "Field" -// thingsToFix = (ucFirst(name) + "Field" for name in fieldsToFix) -thingsToFix = thingsToFix.concat(otherThingsToFix) -function normalize (data, warn, strict) { - if(warn === true) warn = null, strict = true - if(!strict) strict = false - if(!warn || data.private) warn = function(msg) { /* noop */ } + function processRelativeTime(number, withoutSuffix, key, isFuture) { + var format = { + 'm': ['eng Minutt', 'enger Minutt'], + 'h': ['eng Stonn', 'enger Stonn'], + 'd': ['een Dag', 'engem Dag'], + 'M': ['ee Mount', 'engem Mount'], + 'y': ['ee Joer', 'engem Joer'] + }; + return withoutSuffix ? format[key][0] : format[key][1]; + } + function processFutureTime(string) { + var number = string.substr(0, string.indexOf(' ')); + if (eifelerRegelAppliesToNumber(number)) { + return 'a ' + string; + } + return 'an ' + string; + } + function processPastTime(string) { + var number = string.substr(0, string.indexOf(' ')); + if (eifelerRegelAppliesToNumber(number)) { + return 'viru ' + string; + } + return 'virun ' + string; + } + /** + * Returns true if the word before the given number loses the '-n' ending. + * e.g. 'an 10 Deeg' but 'a 5 Deeg' + * + * @param number {integer} + * @returns {boolean} + */ + function eifelerRegelAppliesToNumber(number) { + number = parseInt(number, 10); + if (isNaN(number)) { + return false; + } + if (number < 0) { + // Negative Number --> always true + return true; + } else if (number < 10) { + // Only 1 digit + if (4 <= number && number <= 7) { + return true; + } + return false; + } else if (number < 100) { + // 2 digits + var lastDigit = number % 10, firstDigit = number / 10; + if (lastDigit === 0) { + return eifelerRegelAppliesToNumber(firstDigit); + } + return eifelerRegelAppliesToNumber(lastDigit); + } else if (number < 10000) { + // 3 or 4 digits --> recursively check first digit + while (number >= 10) { + number = number / 10; + } + return eifelerRegelAppliesToNumber(number); + } else { + // Anything larger than 4 digits: recursively check first n-3 digits + number = number / 1000; + return eifelerRegelAppliesToNumber(number); + } + } + + var lb = moment.defineLocale('lb', { + months: 'Januar_Februar_Mäerz_Abrëll_Mee_Juni_Juli_August_September_Oktober_November_Dezember'.split('_'), + monthsShort: 'Jan._Febr._Mrz._Abr._Mee_Jun._Jul._Aug._Sept._Okt._Nov._Dez.'.split('_'), + monthsParseExact : true, + weekdays: 'Sonndeg_Méindeg_Dënschdeg_Mëttwoch_Donneschdeg_Freideg_Samschdeg'.split('_'), + weekdaysShort: 'So._Mé._Dë._Më._Do._Fr._Sa.'.split('_'), + weekdaysMin: 'So_Mé_Dë_Më_Do_Fr_Sa'.split('_'), + weekdaysParseExact : true, + longDateFormat: { + LT: 'H:mm [Auer]', + LTS: 'H:mm:ss [Auer]', + L: 'DD.MM.YYYY', + LL: 'D. MMMM YYYY', + LLL: 'D. MMMM YYYY H:mm [Auer]', + LLLL: 'dddd, D. MMMM YYYY H:mm [Auer]' + }, + calendar: { + sameDay: '[Haut um] LT', + sameElse: 'L', + nextDay: '[Muer um] LT', + nextWeek: 'dddd [um] LT', + lastDay: '[Gëschter um] LT', + lastWeek: function () { + // Different date string for 'Dënschdeg' (Tuesday) and 'Donneschdeg' (Thursday) due to phonological rule + switch (this.day()) { + case 2: + case 4: + return '[Leschten] dddd [um] LT'; + default: + return '[Leschte] dddd [um] LT'; + } + } + }, + relativeTime : { + future : processFutureTime, + past : processPastTime, + s : 'e puer Sekonnen', + ss : '%d Sekonnen', + m : processRelativeTime, + mm : '%d Minutten', + h : processRelativeTime, + hh : '%d Stonnen', + d : processRelativeTime, + dd : '%d Deeg', + M : processRelativeTime, + MM : '%d Méint', + y : processRelativeTime, + yy : '%d Joer' + }, + dayOfMonthOrdinalParse: /\d{1,2}\./, + ordinal: '%d.', + week: { + dow: 1, // Monday is the first day of the week. + doy: 4 // The week that contains Jan 4th is the first week of the year. + } + }); - if (data.scripts && - data.scripts.install === "node-gyp rebuild" && - !data.scripts.preinstall) { - data.gypfile = true - } - fixer.warn = function() { warn(makeWarning.apply(null, arguments)) } - thingsToFix.forEach(function(thingName) { - fixer["fix" + ucFirst(thingName)](data, strict) - }) - data._id = data.name + "@" + data.version -} + return lb; -function ucFirst (string) { - return string.charAt(0).toUpperCase() + string.slice(1); -} +}))); /***/ }), -/* 74 */ +/* 112 */ /***/ (function(module, exports, __webpack_require__) { -var semver = __webpack_require__(75) -var validateLicense = __webpack_require__(76); -var hostedGitInfo = __webpack_require__(81) -var isBuiltinModule = __webpack_require__(85).isCore -var depTypes = ["dependencies","devDependencies","optionalDependencies"] -var extractDescription = __webpack_require__(94) -var url = __webpack_require__(82) -var typos = __webpack_require__(95) - -var fixer = module.exports = { - // default warning function - warn: function() {}, +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var lo = moment.defineLocale('lo', { + months : 'ມັງກອນ_ກຸມພາ_ມີນາ_ເມສາ_ພຶດສະພາ_ມິຖຸນາ_ກໍລະກົດ_ສິງຫາ_ກັນຍາ_ຕຸລາ_ພະຈິກ_ທັນວາ'.split('_'), + monthsShort : 'ມັງກອນ_ກຸມພາ_ມີນາ_ເມສາ_ພຶດສະພາ_ມິຖຸນາ_ກໍລະກົດ_ສິງຫາ_ກັນຍາ_ຕຸລາ_ພະຈິກ_ທັນວາ'.split('_'), + weekdays : 'ອາທິດ_ຈັນ_ອັງຄານ_ພຸດ_ພະຫັດ_ສຸກ_ເສົາ'.split('_'), + weekdaysShort : 'ທິດ_ຈັນ_ອັງຄານ_ພຸດ_ພະຫັດ_ສຸກ_ເສົາ'.split('_'), + weekdaysMin : 'ທ_ຈ_ອຄ_ພ_ພຫ_ສກ_ສ'.split('_'), + weekdaysParseExact : true, + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY HH:mm', + LLLL : 'ວັນdddd D MMMM YYYY HH:mm' + }, + meridiemParse: /ຕອນເຊົ້າ|ຕອນແລງ/, + isPM: function (input) { + return input === 'ຕອນແລງ'; + }, + meridiem : function (hour, minute, isLower) { + if (hour < 12) { + return 'ຕອນເຊົ້າ'; + } else { + return 'ຕອນແລງ'; + } + }, + calendar : { + sameDay : '[ມື້ນີ້ເວລາ] LT', + nextDay : '[ມື້ອື່ນເວລາ] LT', + nextWeek : '[ວັນ]dddd[ໜ້າເວລາ] LT', + lastDay : '[ມື້ວານນີ້ເວລາ] LT', + lastWeek : '[ວັນ]dddd[ແລ້ວນີ້ເວລາ] LT', + sameElse : 'L' + }, + relativeTime : { + future : 'ອີກ %s', + past : '%sຜ່ານມາ', + s : 'ບໍ່ເທົ່າໃດວິນາທີ', + ss : '%d ວິນາທີ' , + m : '1 ນາທີ', + mm : '%d ນາທີ', + h : '1 ຊົ່ວໂມງ', + hh : '%d ຊົ່ວໂມງ', + d : '1 ມື້', + dd : '%d ມື້', + M : '1 ເດືອນ', + MM : '%d ເດືອນ', + y : '1 ປີ', + yy : '%d ປີ' + }, + dayOfMonthOrdinalParse: /(ທີ່)\d{1,2}/, + ordinal : function (number) { + return 'ທີ່' + number; + } + }); - fixRepositoryField: function(data) { - if (data.repositories) { - this.warn("repositories"); - data.repository = data.repositories[0] - } - if (!data.repository) return this.warn("missingRepository") - if (typeof data.repository === "string") { - data.repository = { - type: "git", - url: data.repository - } - } - var r = data.repository.url || "" - if (r) { - var hosted = hostedGitInfo.fromUrl(r) - if (hosted) { - r = data.repository.url - = hosted.getDefaultRepresentation() == "shortcut" ? hosted.https() : hosted.toString() - } - } + return lo; - if (r.match(/github.com\/[^\/]+\/[^\/]+\.git\.git$/)) { - this.warn("brokenGitUrl", r) - } - } +}))); -, fixTypos: function(data) { - Object.keys(typos.topLevel).forEach(function (d) { - if (data.hasOwnProperty(d)) { - this.warn("typo", d, typos.topLevel[d]) - } - }, this) - } -, fixScriptsField: function(data) { - if (!data.scripts) return - if (typeof data.scripts !== "object") { - this.warn("nonObjectScripts") - delete data.scripts - return - } - Object.keys(data.scripts).forEach(function (k) { - if (typeof data.scripts[k] !== "string") { - this.warn("nonStringScript") - delete data.scripts[k] - } else if (typos.script[k] && !data.scripts[typos.script[k]]) { - this.warn("typo", k, typos.script[k], "scripts") - } - }, this) - } +/***/ }), +/* 113 */ +/***/ (function(module, exports, __webpack_require__) { -, fixFilesField: function(data) { - var files = data.files - if (files && !Array.isArray(files)) { - this.warn("nonArrayFiles") - delete data.files - } else if (data.files) { - data.files = data.files.filter(function(file) { - if (!file || typeof file !== "string") { - this.warn("invalidFilename", file) - return false +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var units = { + 'ss' : 'sekundė_sekundžių_sekundes', + 'm' : 'minutė_minutės_minutę', + 'mm': 'minutės_minučių_minutes', + 'h' : 'valanda_valandos_valandą', + 'hh': 'valandos_valandų_valandas', + 'd' : 'diena_dienos_dieną', + 'dd': 'dienos_dienų_dienas', + 'M' : 'mėnuo_mėnesio_mėnesį', + 'MM': 'mėnesiai_mėnesių_mėnesius', + 'y' : 'metai_metų_metus', + 'yy': 'metai_metų_metus' + }; + function translateSeconds(number, withoutSuffix, key, isFuture) { + if (withoutSuffix) { + return 'kelios sekundės'; } else { - return true + return isFuture ? 'kelių sekundžių' : 'kelias sekundes'; } - }, this) } - } - -, fixBinField: function(data) { - if (!data.bin) return; - if (typeof data.bin === "string") { - var b = {} - var match - if (match = data.name.match(/^@[^/]+[/](.*)$/)) { - b[match[1]] = data.bin - } else { - b[data.name] = data.bin - } - data.bin = b + function translateSingular(number, withoutSuffix, key, isFuture) { + return withoutSuffix ? forms(key)[0] : (isFuture ? forms(key)[1] : forms(key)[2]); } - } - -, fixManField: function(data) { - if (!data.man) return; - if (typeof data.man === "string") { - data.man = [ data.man ] + function special(number) { + return number % 10 === 0 || (number > 10 && number < 20); } - } -, fixBundleDependenciesField: function(data) { - var bdd = "bundledDependencies" - var bd = "bundleDependencies" - if (data[bdd] && !data[bd]) { - data[bd] = data[bdd] - delete data[bdd] + function forms(key) { + return units[key].split('_'); } - if (data[bd] && !Array.isArray(data[bd])) { - this.warn("nonArrayBundleDependencies") - delete data[bd] - } else if (data[bd]) { - data[bd] = data[bd].filter(function(bd) { - if (!bd || typeof bd !== 'string') { - this.warn("nonStringBundleDependency", bd) - return false + function translate(number, withoutSuffix, key, isFuture) { + var result = number + ' '; + if (number === 1) { + return result + translateSingular(number, withoutSuffix, key[0], isFuture); + } else if (withoutSuffix) { + return result + (special(number) ? forms(key)[1] : forms(key)[0]); } else { - if (!data.dependencies) { - data.dependencies = {} - } - if (!data.dependencies.hasOwnProperty(bd)) { - this.warn("nonDependencyBundleDependency", bd) - data.dependencies[bd] = "*" - } - return true + if (isFuture) { + return result + forms(key)[1]; + } else { + return result + (special(number) ? forms(key)[1] : forms(key)[2]); + } } - }, this) } - } + var lt = moment.defineLocale('lt', { + months : { + format: 'sausio_vasario_kovo_balandžio_gegužės_birželio_liepos_rugpjūčio_rugsėjo_spalio_lapkričio_gruodžio'.split('_'), + standalone: 'sausis_vasaris_kovas_balandis_gegužė_birželis_liepa_rugpjūtis_rugsėjis_spalis_lapkritis_gruodis'.split('_'), + isFormat: /D[oD]?(\[[^\[\]]*\]|\s)+MMMM?|MMMM?(\[[^\[\]]*\]|\s)+D[oD]?/ + }, + monthsShort : 'sau_vas_kov_bal_geg_bir_lie_rgp_rgs_spa_lap_grd'.split('_'), + weekdays : { + format: 'sekmadienį_pirmadienį_antradienį_trečiadienį_ketvirtadienį_penktadienį_šeštadienį'.split('_'), + standalone: 'sekmadienis_pirmadienis_antradienis_trečiadienis_ketvirtadienis_penktadienis_šeštadienis'.split('_'), + isFormat: /dddd HH:mm/ + }, + weekdaysShort : 'Sek_Pir_Ant_Tre_Ket_Pen_Šeš'.split('_'), + weekdaysMin : 'S_P_A_T_K_Pn_Š'.split('_'), + weekdaysParseExact : true, + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'YYYY-MM-DD', + LL : 'YYYY [m.] MMMM D [d.]', + LLL : 'YYYY [m.] MMMM D [d.], HH:mm [val.]', + LLLL : 'YYYY [m.] MMMM D [d.], dddd, HH:mm [val.]', + l : 'YYYY-MM-DD', + ll : 'YYYY [m.] MMMM D [d.]', + lll : 'YYYY [m.] MMMM D [d.], HH:mm [val.]', + llll : 'YYYY [m.] MMMM D [d.], ddd, HH:mm [val.]' + }, + calendar : { + sameDay : '[Šiandien] LT', + nextDay : '[Rytoj] LT', + nextWeek : 'dddd LT', + lastDay : '[Vakar] LT', + lastWeek : '[Praėjusį] dddd LT', + sameElse : 'L' + }, + relativeTime : { + future : 'po %s', + past : 'prieš %s', + s : translateSeconds, + ss : translate, + m : translateSingular, + mm : translate, + h : translateSingular, + hh : translate, + d : translateSingular, + dd : translate, + M : translateSingular, + MM : translate, + y : translateSingular, + yy : translate + }, + dayOfMonthOrdinalParse: /\d{1,2}-oji/, + ordinal : function (number) { + return number + '-oji'; + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } + }); -, fixDependencies: function(data, strict) { - var loose = !strict - objectifyDeps(data, this.warn) - addOptionalDepsToDeps(data, this.warn) - this.fixBundleDependenciesField(data) + return lt; - ;['dependencies','devDependencies'].forEach(function(deps) { - if (!(deps in data)) return - if (!data[deps] || typeof data[deps] !== "object") { - this.warn("nonObjectDependencies", deps) - delete data[deps] - return - } - Object.keys(data[deps]).forEach(function (d) { - var r = data[deps][d] - if (typeof r !== 'string') { - this.warn("nonStringDependency", d, JSON.stringify(r)) - delete data[deps][d] - } - var hosted = hostedGitInfo.fromUrl(data[deps][d]) - if (hosted) data[deps][d] = hosted.toString() - }, this) - }, this) - } +}))); -, fixModulesField: function (data) { - if (data.modules) { - this.warn("deprecatedModules") - delete data.modules - } - } -, fixKeywordsField: function (data) { - if (typeof data.keywords === "string") { - data.keywords = data.keywords.split(/,\s+/) - } - if (data.keywords && !Array.isArray(data.keywords)) { - delete data.keywords - this.warn("nonArrayKeywords") - } else if (data.keywords) { - data.keywords = data.keywords.filter(function(kw) { - if (typeof kw !== "string" || !kw) { - this.warn("nonStringKeyword"); - return false +/***/ }), +/* 114 */ +/***/ (function(module, exports, __webpack_require__) { + +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var units = { + 'ss': 'sekundes_sekundēm_sekunde_sekundes'.split('_'), + 'm': 'minūtes_minūtēm_minūte_minūtes'.split('_'), + 'mm': 'minūtes_minūtēm_minūte_minūtes'.split('_'), + 'h': 'stundas_stundām_stunda_stundas'.split('_'), + 'hh': 'stundas_stundām_stunda_stundas'.split('_'), + 'd': 'dienas_dienām_diena_dienas'.split('_'), + 'dd': 'dienas_dienām_diena_dienas'.split('_'), + 'M': 'mēneša_mēnešiem_mēnesis_mēneši'.split('_'), + 'MM': 'mēneša_mēnešiem_mēnesis_mēneši'.split('_'), + 'y': 'gada_gadiem_gads_gadi'.split('_'), + 'yy': 'gada_gadiem_gads_gadi'.split('_') + }; + /** + * @param withoutSuffix boolean true = a length of time; false = before/after a period of time. + */ + function format(forms, number, withoutSuffix) { + if (withoutSuffix) { + // E.g. "21 minūte", "3 minūtes". + return number % 10 === 1 && number % 100 !== 11 ? forms[2] : forms[3]; } else { - return true + // E.g. "21 minūtes" as in "pēc 21 minūtes". + // E.g. "3 minūtēm" as in "pēc 3 minūtēm". + return number % 10 === 1 && number % 100 !== 11 ? forms[0] : forms[1]; + } + } + function relativeTimeWithPlural(number, withoutSuffix, key) { + return number + ' ' + format(units[key], number, withoutSuffix); + } + function relativeTimeWithSingular(number, withoutSuffix, key) { + return format(units[key], number, withoutSuffix); + } + function relativeSeconds(number, withoutSuffix) { + return withoutSuffix ? 'dažas sekundes' : 'dažām sekundēm'; + } + + var lv = moment.defineLocale('lv', { + months : 'janvāris_februāris_marts_aprīlis_maijs_jūnijs_jūlijs_augusts_septembris_oktobris_novembris_decembris'.split('_'), + monthsShort : 'jan_feb_mar_apr_mai_jūn_jūl_aug_sep_okt_nov_dec'.split('_'), + weekdays : 'svētdiena_pirmdiena_otrdiena_trešdiena_ceturtdiena_piektdiena_sestdiena'.split('_'), + weekdaysShort : 'Sv_P_O_T_C_Pk_S'.split('_'), + weekdaysMin : 'Sv_P_O_T_C_Pk_S'.split('_'), + weekdaysParseExact : true, + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD.MM.YYYY.', + LL : 'YYYY. [gada] D. MMMM', + LLL : 'YYYY. [gada] D. MMMM, HH:mm', + LLLL : 'YYYY. [gada] D. MMMM, dddd, HH:mm' + }, + calendar : { + sameDay : '[Šodien pulksten] LT', + nextDay : '[Rīt pulksten] LT', + nextWeek : 'dddd [pulksten] LT', + lastDay : '[Vakar pulksten] LT', + lastWeek : '[Pagājušā] dddd [pulksten] LT', + sameElse : 'L' + }, + relativeTime : { + future : 'pēc %s', + past : 'pirms %s', + s : relativeSeconds, + ss : relativeTimeWithPlural, + m : relativeTimeWithSingular, + mm : relativeTimeWithPlural, + h : relativeTimeWithSingular, + hh : relativeTimeWithPlural, + d : relativeTimeWithSingular, + dd : relativeTimeWithPlural, + M : relativeTimeWithSingular, + MM : relativeTimeWithPlural, + y : relativeTimeWithSingular, + yy : relativeTimeWithPlural + }, + dayOfMonthOrdinalParse: /\d{1,2}\./, + ordinal : '%d.', + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. } - }, this) - } - } + }); -, fixVersionField: function(data, strict) { - // allow "loose" semver 1.0 versions in non-strict mode - // enforce strict semver 2.0 compliance in strict mode - var loose = !strict - if (!data.version) { - data.version = "" - return true - } - if (!semver.valid(data.version, loose)) { - throw new Error('Invalid version: "'+ data.version + '"') - } - data.version = semver.clean(data.version, loose) - return true - } + return lv; -, fixPeople: function(data) { - modifyPeople(data, unParsePerson) - modifyPeople(data, parsePerson) - } +}))); -, fixNameField: function(data, options) { - if (typeof options === "boolean") options = {strict: options} - else if (typeof options === "undefined") options = {} - var strict = options.strict - if (!data.name && !strict) { - data.name = "" - return - } - if (typeof data.name !== "string") { - throw new Error("name field must be a string.") - } - if (!strict) - data.name = data.name.trim() - ensureValidName(data.name, strict, options.allowLegacyCase) - if (isBuiltinModule(data.name)) - this.warn("conflictingName", data.name) - } +/***/ }), +/* 115 */ +/***/ (function(module, exports, __webpack_require__) { -, fixDescriptionField: function (data) { - if (data.description && typeof data.description !== 'string') { - this.warn("nonStringDescription") - delete data.description - } - if (data.readme && !data.description) - data.description = extractDescription(data.readme) - if(data.description === undefined) delete data.description; - if (!data.description) this.warn("missingDescription") - } +//! moment.js locale configuration -, fixReadmeField: function (data) { - if (!data.readme) { - this.warn("missingReadme") - data.readme = "ERROR: No README data found!" - } - } +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; -, fixBugsField: function(data) { - if (!data.bugs && data.repository && data.repository.url) { - var hosted = hostedGitInfo.fromUrl(data.repository.url) - if(hosted && hosted.bugs()) { - data.bugs = {url: hosted.bugs()} - } - } - else if(data.bugs) { - var emailRe = /^.+@.*\..+$/ - if(typeof data.bugs == "string") { - if(emailRe.test(data.bugs)) - data.bugs = {email:data.bugs} - else if(url.parse(data.bugs).protocol) - data.bugs = {url: data.bugs} - else - this.warn("nonEmailUrlBugsString") - } - else { - bugsTypos(data.bugs, this.warn) - var oldBugs = data.bugs - data.bugs = {} - if(oldBugs.url) { - if(typeof(oldBugs.url) == "string" && url.parse(oldBugs.url).protocol) - data.bugs.url = oldBugs.url - else - this.warn("nonUrlBugsUrlField") + + var translator = { + words: { //Different grammatical cases + ss: ['sekund', 'sekunda', 'sekundi'], + m: ['jedan minut', 'jednog minuta'], + mm: ['minut', 'minuta', 'minuta'], + h: ['jedan sat', 'jednog sata'], + hh: ['sat', 'sata', 'sati'], + dd: ['dan', 'dana', 'dana'], + MM: ['mjesec', 'mjeseca', 'mjeseci'], + yy: ['godina', 'godine', 'godina'] + }, + correctGrammaticalCase: function (number, wordKey) { + return number === 1 ? wordKey[0] : (number >= 2 && number <= 4 ? wordKey[1] : wordKey[2]); + }, + translate: function (number, withoutSuffix, key) { + var wordKey = translator.words[key]; + if (key.length === 1) { + return withoutSuffix ? wordKey[0] : wordKey[1]; + } else { + return number + ' ' + translator.correctGrammaticalCase(number, wordKey); + } } - if(oldBugs.email) { - if(typeof(oldBugs.email) == "string" && emailRe.test(oldBugs.email)) - data.bugs.email = oldBugs.email - else - this.warn("nonEmailBugsEmailField") + }; + + var me = moment.defineLocale('me', { + months: 'januar_februar_mart_april_maj_jun_jul_avgust_septembar_oktobar_novembar_decembar'.split('_'), + monthsShort: 'jan._feb._mar._apr._maj_jun_jul_avg._sep._okt._nov._dec.'.split('_'), + monthsParseExact : true, + weekdays: 'nedjelja_ponedjeljak_utorak_srijeda_četvrtak_petak_subota'.split('_'), + weekdaysShort: 'ned._pon._uto._sri._čet._pet._sub.'.split('_'), + weekdaysMin: 'ne_po_ut_sr_če_pe_su'.split('_'), + weekdaysParseExact : true, + longDateFormat: { + LT: 'H:mm', + LTS : 'H:mm:ss', + L: 'DD.MM.YYYY', + LL: 'D. MMMM YYYY', + LLL: 'D. MMMM YYYY H:mm', + LLLL: 'dddd, D. MMMM YYYY H:mm' + }, + calendar: { + sameDay: '[danas u] LT', + nextDay: '[sjutra u] LT', + + nextWeek: function () { + switch (this.day()) { + case 0: + return '[u] [nedjelju] [u] LT'; + case 3: + return '[u] [srijedu] [u] LT'; + case 6: + return '[u] [subotu] [u] LT'; + case 1: + case 2: + case 4: + case 5: + return '[u] dddd [u] LT'; + } + }, + lastDay : '[juče u] LT', + lastWeek : function () { + var lastWeekDays = [ + '[prošle] [nedjelje] [u] LT', + '[prošlog] [ponedjeljka] [u] LT', + '[prošlog] [utorka] [u] LT', + '[prošle] [srijede] [u] LT', + '[prošlog] [četvrtka] [u] LT', + '[prošlog] [petka] [u] LT', + '[prošle] [subote] [u] LT' + ]; + return lastWeekDays[this.day()]; + }, + sameElse : 'L' + }, + relativeTime : { + future : 'za %s', + past : 'prije %s', + s : 'nekoliko sekundi', + ss : translator.translate, + m : translator.translate, + mm : translator.translate, + h : translator.translate, + hh : translator.translate, + d : 'dan', + dd : translator.translate, + M : 'mjesec', + MM : translator.translate, + y : 'godinu', + yy : translator.translate + }, + dayOfMonthOrdinalParse: /\d{1,2}\./, + ordinal : '%d.', + week : { + dow : 1, // Monday is the first day of the week. + doy : 7 // The week that contains Jan 7th is the first week of the year. } - } - if(!data.bugs.email && !data.bugs.url) { - delete data.bugs - this.warn("emptyNormalizedBugs") - } - } - } + }); -, fixHomepageField: function(data) { - if (!data.homepage && data.repository && data.repository.url) { - var hosted = hostedGitInfo.fromUrl(data.repository.url) - if (hosted && hosted.docs()) data.homepage = hosted.docs() - } - if (!data.homepage) return + return me; - if(typeof data.homepage !== "string") { - this.warn("nonUrlHomepage") - return delete data.homepage - } - if(!url.parse(data.homepage).protocol) { - data.homepage = "http://" + data.homepage - } - } +}))); -, fixLicenseField: function(data) { - if (!data.license) { - return this.warn("missingLicense") - } else{ - if ( - typeof(data.license) !== 'string' || - data.license.length < 1 || - data.license.trim() === '' - ) { - this.warn("invalidLicense") - } else { - if (!validateLicense(data.license).validForNewPackages) - this.warn("invalidLicense") - } - } - } -} -function isValidScopedPackageName(spec) { - if (spec.charAt(0) !== '@') return false +/***/ }), +/* 116 */ +/***/ (function(module, exports, __webpack_require__) { - var rest = spec.slice(1).split('/') - if (rest.length !== 2) return false +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var mi = moment.defineLocale('mi', { + months: 'Kohi-tāte_Hui-tanguru_Poutū-te-rangi_Paenga-whāwhā_Haratua_Pipiri_Hōngoingoi_Here-turi-kōkā_Mahuru_Whiringa-ā-nuku_Whiringa-ā-rangi_Hakihea'.split('_'), + monthsShort: 'Kohi_Hui_Pou_Pae_Hara_Pipi_Hōngoi_Here_Mahu_Whi-nu_Whi-ra_Haki'.split('_'), + monthsRegex: /(?:['a-z\u0101\u014D\u016B]+\-?){1,3}/i, + monthsStrictRegex: /(?:['a-z\u0101\u014D\u016B]+\-?){1,3}/i, + monthsShortRegex: /(?:['a-z\u0101\u014D\u016B]+\-?){1,3}/i, + monthsShortStrictRegex: /(?:['a-z\u0101\u014D\u016B]+\-?){1,2}/i, + weekdays: 'Rātapu_Mane_Tūrei_Wenerei_Tāite_Paraire_Hātarei'.split('_'), + weekdaysShort: 'Ta_Ma_Tū_We_Tāi_Pa_Hā'.split('_'), + weekdaysMin: 'Ta_Ma_Tū_We_Tāi_Pa_Hā'.split('_'), + longDateFormat: { + LT: 'HH:mm', + LTS: 'HH:mm:ss', + L: 'DD/MM/YYYY', + LL: 'D MMMM YYYY', + LLL: 'D MMMM YYYY [i] HH:mm', + LLLL: 'dddd, D MMMM YYYY [i] HH:mm' + }, + calendar: { + sameDay: '[i teie mahana, i] LT', + nextDay: '[apopo i] LT', + nextWeek: 'dddd [i] LT', + lastDay: '[inanahi i] LT', + lastWeek: 'dddd [whakamutunga i] LT', + sameElse: 'L' + }, + relativeTime: { + future: 'i roto i %s', + past: '%s i mua', + s: 'te hēkona ruarua', + ss: '%d hēkona', + m: 'he meneti', + mm: '%d meneti', + h: 'te haora', + hh: '%d haora', + d: 'he ra', + dd: '%d ra', + M: 'he marama', + MM: '%d marama', + y: 'he tau', + yy: '%d tau' + }, + dayOfMonthOrdinalParse: /\d{1,2}º/, + ordinal: '%dº', + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } + }); - return rest[0] && rest[1] && - rest[0] === encodeURIComponent(rest[0]) && - rest[1] === encodeURIComponent(rest[1]) -} + return mi; -function isCorrectlyEncodedName(spec) { - return !spec.match(/[\/@\s\+%:]/) && - spec === encodeURIComponent(spec) -} +}))); -function ensureValidName (name, strict, allowLegacyCase) { - if (name.charAt(0) === "." || - !(isValidScopedPackageName(name) || isCorrectlyEncodedName(name)) || - (strict && (!allowLegacyCase) && name !== name.toLowerCase()) || - name.toLowerCase() === "node_modules" || - name.toLowerCase() === "favicon.ico") { - throw new Error("Invalid name: " + JSON.stringify(name)) - } -} -function modifyPeople (data, fn) { - if (data.author) data.author = fn(data.author) - ;["maintainers", "contributors"].forEach(function (set) { - if (!Array.isArray(data[set])) return; - data[set] = data[set].map(fn) - }) - return data -} +/***/ }), +/* 117 */ +/***/ (function(module, exports, __webpack_require__) { -function unParsePerson (person) { - if (typeof person === "string") return person - var name = person.name || "" - var u = person.url || person.web - var url = u ? (" ("+u+")") : "" - var e = person.email || person.mail - var email = e ? (" <"+e+">") : "" - return name+email+url -} +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var mk = moment.defineLocale('mk', { + months : 'јануари_февруари_март_април_мај_јуни_јули_август_септември_октомври_ноември_декември'.split('_'), + monthsShort : 'јан_фев_мар_апр_мај_јун_јул_авг_сеп_окт_ное_дек'.split('_'), + weekdays : 'недела_понеделник_вторник_среда_четврток_петок_сабота'.split('_'), + weekdaysShort : 'нед_пон_вто_сре_чет_пет_саб'.split('_'), + weekdaysMin : 'нe_пo_вт_ср_че_пе_сa'.split('_'), + longDateFormat : { + LT : 'H:mm', + LTS : 'H:mm:ss', + L : 'D.MM.YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY H:mm', + LLLL : 'dddd, D MMMM YYYY H:mm' + }, + calendar : { + sameDay : '[Денес во] LT', + nextDay : '[Утре во] LT', + nextWeek : '[Во] dddd [во] LT', + lastDay : '[Вчера во] LT', + lastWeek : function () { + switch (this.day()) { + case 0: + case 3: + case 6: + return '[Изминатата] dddd [во] LT'; + case 1: + case 2: + case 4: + case 5: + return '[Изминатиот] dddd [во] LT'; + } + }, + sameElse : 'L' + }, + relativeTime : { + future : 'после %s', + past : 'пред %s', + s : 'неколку секунди', + ss : '%d секунди', + m : 'минута', + mm : '%d минути', + h : 'час', + hh : '%d часа', + d : 'ден', + dd : '%d дена', + M : 'месец', + MM : '%d месеци', + y : 'година', + yy : '%d години' + }, + dayOfMonthOrdinalParse: /\d{1,2}-(ев|ен|ти|ви|ри|ми)/, + ordinal : function (number) { + var lastDigit = number % 10, + last2Digits = number % 100; + if (number === 0) { + return number + '-ев'; + } else if (last2Digits === 0) { + return number + '-ен'; + } else if (last2Digits > 10 && last2Digits < 20) { + return number + '-ти'; + } else if (lastDigit === 1) { + return number + '-ви'; + } else if (lastDigit === 2) { + return number + '-ри'; + } else if (lastDigit === 7 || lastDigit === 8) { + return number + '-ми'; + } else { + return number + '-ти'; + } + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 7 // The week that contains Jan 7th is the first week of the year. + } + }); -function parsePerson (person) { - if (typeof person !== "string") return person - var name = person.match(/^([^\(<]+)/) - var url = person.match(/\(([^\)]+)\)/) - var email = person.match(/<([^>]+)>/) - var obj = {} - if (name && name[0].trim()) obj.name = name[0].trim() - if (email) obj.email = email[1]; - if (url) obj.url = url[1]; - return obj -} + return mk; -function addOptionalDepsToDeps (data, warn) { - var o = data.optionalDependencies - if (!o) return; - var d = data.dependencies || {} - Object.keys(o).forEach(function (k) { - d[k] = o[k] - }) - data.dependencies = d -} +}))); -function depObjectify (deps, type, warn) { - if (!deps) return {} - if (typeof deps === "string") { - deps = deps.trim().split(/[\n\r\s\t ,]+/) - } - if (!Array.isArray(deps)) return deps - warn("deprecatedArrayDependencies", type) - var o = {} - deps.filter(function (d) { - return typeof d === "string" - }).forEach(function(d) { - d = d.trim().split(/(:?[@\s><=])/) - var dn = d.shift() - var dv = d.join("") - dv = dv.trim() - dv = dv.replace(/^@/, "") - o[dn] = dv - }) - return o -} -function objectifyDeps (data, warn) { - depTypes.forEach(function (type) { - if (!data[type]) return; - data[type] = depObjectify(data[type], type, warn) - }) -} +/***/ }), +/* 118 */ +/***/ (function(module, exports, __webpack_require__) { -function bugsTypos(bugs, warn) { - if (!bugs) return - Object.keys(bugs).forEach(function (k) { - if (typos.bugs[k]) { - warn("typo", k, typos.bugs[k], "bugs") - bugs[typos.bugs[k]] = bugs[k] - delete bugs[k] - } - }) -} +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var ml = moment.defineLocale('ml', { + months : 'ജനുവരി_ഫെബ്രുവരി_മാർച്ച്_ഏപ്രിൽ_മേയ്_ജൂൺ_ജൂലൈ_ഓഗസ്റ്റ്_സെപ്റ്റംബർ_ഒക്ടോബർ_നവംബർ_ഡിസംബർ'.split('_'), + monthsShort : 'ജനു._ഫെബ്രു._മാർ._ഏപ്രി._മേയ്_ജൂൺ_ജൂലൈ._ഓഗ._സെപ്റ്റ._ഒക്ടോ._നവം._ഡിസം.'.split('_'), + monthsParseExact : true, + weekdays : 'ഞായറാഴ്ച_തിങ്കളാഴ്ച_ചൊവ്വാഴ്ച_ബുധനാഴ്ച_വ്യാഴാഴ്ച_വെള്ളിയാഴ്ച_ശനിയാഴ്ച'.split('_'), + weekdaysShort : 'ഞായർ_തിങ്കൾ_ചൊവ്വ_ബുധൻ_വ്യാഴം_വെള്ളി_ശനി'.split('_'), + weekdaysMin : 'ഞാ_തി_ചൊ_ബു_വ്യാ_വെ_ശ'.split('_'), + longDateFormat : { + LT : 'A h:mm -നു', + LTS : 'A h:mm:ss -നു', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY, A h:mm -നു', + LLLL : 'dddd, D MMMM YYYY, A h:mm -നു' + }, + calendar : { + sameDay : '[ഇന്ന്] LT', + nextDay : '[നാളെ] LT', + nextWeek : 'dddd, LT', + lastDay : '[ഇന്നലെ] LT', + lastWeek : '[കഴിഞ്ഞ] dddd, LT', + sameElse : 'L' + }, + relativeTime : { + future : '%s കഴിഞ്ഞ്', + past : '%s മുൻപ്', + s : 'അൽപ നിമിഷങ്ങൾ', + ss : '%d സെക്കൻഡ്', + m : 'ഒരു മിനിറ്റ്', + mm : '%d മിനിറ്റ്', + h : 'ഒരു മണിക്കൂർ', + hh : '%d മണിക്കൂർ', + d : 'ഒരു ദിവസം', + dd : '%d ദിവസം', + M : 'ഒരു മാസം', + MM : '%d മാസം', + y : 'ഒരു വർഷം', + yy : '%d വർഷം' + }, + meridiemParse: /രാത്രി|രാവിലെ|ഉച്ച കഴിഞ്ഞ്|വൈകുന്നേരം|രാത്രി/i, + meridiemHour : function (hour, meridiem) { + if (hour === 12) { + hour = 0; + } + if ((meridiem === 'രാത്രി' && hour >= 4) || + meridiem === 'ഉച്ച കഴിഞ്ഞ്' || + meridiem === 'വൈകുന്നേരം') { + return hour + 12; + } else { + return hour; + } + }, + meridiem : function (hour, minute, isLower) { + if (hour < 4) { + return 'രാത്രി'; + } else if (hour < 12) { + return 'രാവിലെ'; + } else if (hour < 17) { + return 'ഉച്ച കഴിഞ്ഞ്'; + } else if (hour < 20) { + return 'വൈകുന്നേരം'; + } else { + return 'രാത്രി'; + } + } + }); + return ml; -/***/ }), -/* 75 */ -/***/ (function(module, exports) { +}))); -exports = module.exports = SemVer; -// The debug function is excluded entirely from the minified version. -/* nomin */ var debug; -/* nomin */ if (typeof process === 'object' && - /* nomin */ process.env && - /* nomin */ process.env.NODE_DEBUG && - /* nomin */ /\bsemver\b/i.test(process.env.NODE_DEBUG)) - /* nomin */ debug = function() { - /* nomin */ var args = Array.prototype.slice.call(arguments, 0); - /* nomin */ args.unshift('SEMVER'); - /* nomin */ console.log.apply(console, args); - /* nomin */ }; -/* nomin */ else - /* nomin */ debug = function() {}; +/***/ }), +/* 119 */ +/***/ (function(module, exports, __webpack_require__) { -// Note: this is the semver.org version of the spec that it implements -// Not necessarily the package version of this code. -exports.SEMVER_SPEC_VERSION = '2.0.0'; +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + function translate(number, withoutSuffix, key, isFuture) { + switch (key) { + case 's': + return withoutSuffix ? 'хэдхэн секунд' : 'хэдхэн секундын'; + case 'ss': + return number + (withoutSuffix ? ' секунд' : ' секундын'); + case 'm': + case 'mm': + return number + (withoutSuffix ? ' минут' : ' минутын'); + case 'h': + case 'hh': + return number + (withoutSuffix ? ' цаг' : ' цагийн'); + case 'd': + case 'dd': + return number + (withoutSuffix ? ' өдөр' : ' өдрийн'); + case 'M': + case 'MM': + return number + (withoutSuffix ? ' сар' : ' сарын'); + case 'y': + case 'yy': + return number + (withoutSuffix ? ' жил' : ' жилийн'); + default: + return number; + } + } + + var mn = moment.defineLocale('mn', { + months : 'Нэгдүгээр сар_Хоёрдугаар сар_Гуравдугаар сар_Дөрөвдүгээр сар_Тавдугаар сар_Зургадугаар сар_Долдугаар сар_Наймдугаар сар_Есдүгээр сар_Аравдугаар сар_Арван нэгдүгээр сар_Арван хоёрдугаар сар'.split('_'), + monthsShort : '1 сар_2 сар_3 сар_4 сар_5 сар_6 сар_7 сар_8 сар_9 сар_10 сар_11 сар_12 сар'.split('_'), + monthsParseExact : true, + weekdays : 'Ням_Даваа_Мягмар_Лхагва_Пүрэв_Баасан_Бямба'.split('_'), + weekdaysShort : 'Ням_Дав_Мяг_Лха_Пүр_Баа_Бям'.split('_'), + weekdaysMin : 'Ня_Да_Мя_Лх_Пү_Ба_Бя'.split('_'), + weekdaysParseExact : true, + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'YYYY-MM-DD', + LL : 'YYYY оны MMMMын D', + LLL : 'YYYY оны MMMMын D HH:mm', + LLLL : 'dddd, YYYY оны MMMMын D HH:mm' + }, + meridiemParse: /ҮӨ|ҮХ/i, + isPM : function (input) { + return input === 'ҮХ'; + }, + meridiem : function (hour, minute, isLower) { + if (hour < 12) { + return 'ҮӨ'; + } else { + return 'ҮХ'; + } + }, + calendar : { + sameDay : '[Өнөөдөр] LT', + nextDay : '[Маргааш] LT', + nextWeek : '[Ирэх] dddd LT', + lastDay : '[Өчигдөр] LT', + lastWeek : '[Өнгөрсөн] dddd LT', + sameElse : 'L' + }, + relativeTime : { + future : '%s дараа', + past : '%s өмнө', + s : translate, + ss : translate, + m : translate, + mm : translate, + h : translate, + hh : translate, + d : translate, + dd : translate, + M : translate, + MM : translate, + y : translate, + yy : translate + }, + dayOfMonthOrdinalParse: /\d{1,2} өдөр/, + ordinal : function (number, period) { + switch (period) { + case 'd': + case 'D': + case 'DDD': + return number + ' өдөр'; + default: + return number; + } + } + }); -var MAX_LENGTH = 256; -var MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER || 9007199254740991; + return mn; -// The actual regexps go on exports.re -var re = exports.re = []; -var src = exports.src = []; -var R = 0; +}))); -// The following Regular Expressions can be used for tokenizing, -// validating, and parsing SemVer version strings. -// ## Numeric Identifier -// A single `0`, or a non-zero digit followed by zero or more digits. +/***/ }), +/* 120 */ +/***/ (function(module, exports, __webpack_require__) { -var NUMERICIDENTIFIER = R++; -src[NUMERICIDENTIFIER] = '0|[1-9]\\d*'; -var NUMERICIDENTIFIERLOOSE = R++; -src[NUMERICIDENTIFIERLOOSE] = '[0-9]+'; +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var symbolMap = { + '1': '१', + '2': '२', + '3': '३', + '4': '४', + '5': '५', + '6': '६', + '7': '७', + '8': '८', + '9': '९', + '0': '०' + }, + numberMap = { + '१': '1', + '२': '2', + '३': '3', + '४': '4', + '५': '5', + '६': '6', + '७': '7', + '८': '8', + '९': '9', + '०': '0' + }; + + function relativeTimeMr(number, withoutSuffix, string, isFuture) + { + var output = ''; + if (withoutSuffix) { + switch (string) { + case 's': output = 'काही सेकंद'; break; + case 'ss': output = '%d सेकंद'; break; + case 'm': output = 'एक मिनिट'; break; + case 'mm': output = '%d मिनिटे'; break; + case 'h': output = 'एक तास'; break; + case 'hh': output = '%d तास'; break; + case 'd': output = 'एक दिवस'; break; + case 'dd': output = '%d दिवस'; break; + case 'M': output = 'एक महिना'; break; + case 'MM': output = '%d महिने'; break; + case 'y': output = 'एक वर्ष'; break; + case 'yy': output = '%d वर्षे'; break; + } + } + else { + switch (string) { + case 's': output = 'काही सेकंदां'; break; + case 'ss': output = '%d सेकंदां'; break; + case 'm': output = 'एका मिनिटा'; break; + case 'mm': output = '%d मिनिटां'; break; + case 'h': output = 'एका तासा'; break; + case 'hh': output = '%d तासां'; break; + case 'd': output = 'एका दिवसा'; break; + case 'dd': output = '%d दिवसां'; break; + case 'M': output = 'एका महिन्या'; break; + case 'MM': output = '%d महिन्यां'; break; + case 'y': output = 'एका वर्षा'; break; + case 'yy': output = '%d वर्षां'; break; + } + } + return output.replace(/%d/i, number); + } + + var mr = moment.defineLocale('mr', { + months : 'जानेवारी_फेब्रुवारी_मार्च_एप्रिल_मे_जून_जुलै_ऑगस्ट_सप्टेंबर_ऑक्टोबर_नोव्हेंबर_डिसेंबर'.split('_'), + monthsShort: 'जाने._फेब्रु._मार्च._एप्रि._मे._जून._जुलै._ऑग._सप्टें._ऑक्टो._नोव्हें._डिसें.'.split('_'), + monthsParseExact : true, + weekdays : 'रविवार_सोमवार_मंगळवार_बुधवार_गुरूवार_शुक्रवार_शनिवार'.split('_'), + weekdaysShort : 'रवि_सोम_मंगळ_बुध_गुरू_शुक्र_शनि'.split('_'), + weekdaysMin : 'र_सो_मं_बु_गु_शु_श'.split('_'), + longDateFormat : { + LT : 'A h:mm वाजता', + LTS : 'A h:mm:ss वाजता', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY, A h:mm वाजता', + LLLL : 'dddd, D MMMM YYYY, A h:mm वाजता' + }, + calendar : { + sameDay : '[आज] LT', + nextDay : '[उद्या] LT', + nextWeek : 'dddd, LT', + lastDay : '[काल] LT', + lastWeek: '[मागील] dddd, LT', + sameElse : 'L' + }, + relativeTime : { + future: '%sमध्ये', + past: '%sपूर्वी', + s: relativeTimeMr, + ss: relativeTimeMr, + m: relativeTimeMr, + mm: relativeTimeMr, + h: relativeTimeMr, + hh: relativeTimeMr, + d: relativeTimeMr, + dd: relativeTimeMr, + M: relativeTimeMr, + MM: relativeTimeMr, + y: relativeTimeMr, + yy: relativeTimeMr + }, + preparse: function (string) { + return string.replace(/[१२३४५६७८९०]/g, function (match) { + return numberMap[match]; + }); + }, + postformat: function (string) { + return string.replace(/\d/g, function (match) { + return symbolMap[match]; + }); + }, + meridiemParse: /रात्री|सकाळी|दुपारी|सायंकाळी/, + meridiemHour : function (hour, meridiem) { + if (hour === 12) { + hour = 0; + } + if (meridiem === 'रात्री') { + return hour < 4 ? hour : hour + 12; + } else if (meridiem === 'सकाळी') { + return hour; + } else if (meridiem === 'दुपारी') { + return hour >= 10 ? hour : hour + 12; + } else if (meridiem === 'सायंकाळी') { + return hour + 12; + } + }, + meridiem: function (hour, minute, isLower) { + if (hour < 4) { + return 'रात्री'; + } else if (hour < 10) { + return 'सकाळी'; + } else if (hour < 17) { + return 'दुपारी'; + } else if (hour < 20) { + return 'सायंकाळी'; + } else { + return 'रात्री'; + } + }, + week : { + dow : 0, // Sunday is the first day of the week. + doy : 6 // The week that contains Jan 6th is the first week of the year. + } + }); + return mr; -// ## Non-numeric Identifier -// Zero or more digits, followed by a letter or hyphen, and then zero or -// more letters, digits, or hyphens. +}))); -var NONNUMERICIDENTIFIER = R++; -src[NONNUMERICIDENTIFIER] = '\\d*[a-zA-Z-][a-zA-Z0-9-]*'; +/***/ }), +/* 121 */ +/***/ (function(module, exports, __webpack_require__) { -// ## Main Version -// Three dot-separated numeric identifiers. +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var ms = moment.defineLocale('ms', { + months : 'Januari_Februari_Mac_April_Mei_Jun_Julai_Ogos_September_Oktober_November_Disember'.split('_'), + monthsShort : 'Jan_Feb_Mac_Apr_Mei_Jun_Jul_Ogs_Sep_Okt_Nov_Dis'.split('_'), + weekdays : 'Ahad_Isnin_Selasa_Rabu_Khamis_Jumaat_Sabtu'.split('_'), + weekdaysShort : 'Ahd_Isn_Sel_Rab_Kha_Jum_Sab'.split('_'), + weekdaysMin : 'Ah_Is_Sl_Rb_Km_Jm_Sb'.split('_'), + longDateFormat : { + LT : 'HH.mm', + LTS : 'HH.mm.ss', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY [pukul] HH.mm', + LLLL : 'dddd, D MMMM YYYY [pukul] HH.mm' + }, + meridiemParse: /pagi|tengahari|petang|malam/, + meridiemHour: function (hour, meridiem) { + if (hour === 12) { + hour = 0; + } + if (meridiem === 'pagi') { + return hour; + } else if (meridiem === 'tengahari') { + return hour >= 11 ? hour : hour + 12; + } else if (meridiem === 'petang' || meridiem === 'malam') { + return hour + 12; + } + }, + meridiem : function (hours, minutes, isLower) { + if (hours < 11) { + return 'pagi'; + } else if (hours < 15) { + return 'tengahari'; + } else if (hours < 19) { + return 'petang'; + } else { + return 'malam'; + } + }, + calendar : { + sameDay : '[Hari ini pukul] LT', + nextDay : '[Esok pukul] LT', + nextWeek : 'dddd [pukul] LT', + lastDay : '[Kelmarin pukul] LT', + lastWeek : 'dddd [lepas pukul] LT', + sameElse : 'L' + }, + relativeTime : { + future : 'dalam %s', + past : '%s yang lepas', + s : 'beberapa saat', + ss : '%d saat', + m : 'seminit', + mm : '%d minit', + h : 'sejam', + hh : '%d jam', + d : 'sehari', + dd : '%d hari', + M : 'sebulan', + MM : '%d bulan', + y : 'setahun', + yy : '%d tahun' + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 7 // The week that contains Jan 7th is the first week of the year. + } + }); -var MAINVERSION = R++; -src[MAINVERSION] = '(' + src[NUMERICIDENTIFIER] + ')\\.' + - '(' + src[NUMERICIDENTIFIER] + ')\\.' + - '(' + src[NUMERICIDENTIFIER] + ')'; + return ms; -var MAINVERSIONLOOSE = R++; -src[MAINVERSIONLOOSE] = '(' + src[NUMERICIDENTIFIERLOOSE] + ')\\.' + - '(' + src[NUMERICIDENTIFIERLOOSE] + ')\\.' + - '(' + src[NUMERICIDENTIFIERLOOSE] + ')'; +}))); -// ## Pre-release Version Identifier -// A numeric identifier, or a non-numeric identifier. -var PRERELEASEIDENTIFIER = R++; -src[PRERELEASEIDENTIFIER] = '(?:' + src[NUMERICIDENTIFIER] + - '|' + src[NONNUMERICIDENTIFIER] + ')'; +/***/ }), +/* 122 */ +/***/ (function(module, exports, __webpack_require__) { -var PRERELEASEIDENTIFIERLOOSE = R++; -src[PRERELEASEIDENTIFIERLOOSE] = '(?:' + src[NUMERICIDENTIFIERLOOSE] + - '|' + src[NONNUMERICIDENTIFIER] + ')'; +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var msMy = moment.defineLocale('ms-my', { + months : 'Januari_Februari_Mac_April_Mei_Jun_Julai_Ogos_September_Oktober_November_Disember'.split('_'), + monthsShort : 'Jan_Feb_Mac_Apr_Mei_Jun_Jul_Ogs_Sep_Okt_Nov_Dis'.split('_'), + weekdays : 'Ahad_Isnin_Selasa_Rabu_Khamis_Jumaat_Sabtu'.split('_'), + weekdaysShort : 'Ahd_Isn_Sel_Rab_Kha_Jum_Sab'.split('_'), + weekdaysMin : 'Ah_Is_Sl_Rb_Km_Jm_Sb'.split('_'), + longDateFormat : { + LT : 'HH.mm', + LTS : 'HH.mm.ss', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY [pukul] HH.mm', + LLLL : 'dddd, D MMMM YYYY [pukul] HH.mm' + }, + meridiemParse: /pagi|tengahari|petang|malam/, + meridiemHour: function (hour, meridiem) { + if (hour === 12) { + hour = 0; + } + if (meridiem === 'pagi') { + return hour; + } else if (meridiem === 'tengahari') { + return hour >= 11 ? hour : hour + 12; + } else if (meridiem === 'petang' || meridiem === 'malam') { + return hour + 12; + } + }, + meridiem : function (hours, minutes, isLower) { + if (hours < 11) { + return 'pagi'; + } else if (hours < 15) { + return 'tengahari'; + } else if (hours < 19) { + return 'petang'; + } else { + return 'malam'; + } + }, + calendar : { + sameDay : '[Hari ini pukul] LT', + nextDay : '[Esok pukul] LT', + nextWeek : 'dddd [pukul] LT', + lastDay : '[Kelmarin pukul] LT', + lastWeek : 'dddd [lepas pukul] LT', + sameElse : 'L' + }, + relativeTime : { + future : 'dalam %s', + past : '%s yang lepas', + s : 'beberapa saat', + ss : '%d saat', + m : 'seminit', + mm : '%d minit', + h : 'sejam', + hh : '%d jam', + d : 'sehari', + dd : '%d hari', + M : 'sebulan', + MM : '%d bulan', + y : 'setahun', + yy : '%d tahun' + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 7 // The week that contains Jan 7th is the first week of the year. + } + }); + return msMy; -// ## Pre-release Version -// Hyphen, followed by one or more dot-separated pre-release version -// identifiers. +}))); -var PRERELEASE = R++; -src[PRERELEASE] = '(?:-(' + src[PRERELEASEIDENTIFIER] + - '(?:\\.' + src[PRERELEASEIDENTIFIER] + ')*))'; -var PRERELEASELOOSE = R++; -src[PRERELEASELOOSE] = '(?:-?(' + src[PRERELEASEIDENTIFIERLOOSE] + - '(?:\\.' + src[PRERELEASEIDENTIFIERLOOSE] + ')*))'; +/***/ }), +/* 123 */ +/***/ (function(module, exports, __webpack_require__) { -// ## Build Metadata Identifier -// Any combination of digits, letters, or hyphens. +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var mt = moment.defineLocale('mt', { + months : 'Jannar_Frar_Marzu_April_Mejju_Ġunju_Lulju_Awwissu_Settembru_Ottubru_Novembru_Diċembru'.split('_'), + monthsShort : 'Jan_Fra_Mar_Apr_Mej_Ġun_Lul_Aww_Set_Ott_Nov_Diċ'.split('_'), + weekdays : 'Il-Ħadd_It-Tnejn_It-Tlieta_L-Erbgħa_Il-Ħamis_Il-Ġimgħa_Is-Sibt'.split('_'), + weekdaysShort : 'Ħad_Tne_Tli_Erb_Ħam_Ġim_Sib'.split('_'), + weekdaysMin : 'Ħa_Tn_Tl_Er_Ħa_Ġi_Si'.split('_'), + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY HH:mm', + LLLL : 'dddd, D MMMM YYYY HH:mm' + }, + calendar : { + sameDay : '[Illum fil-]LT', + nextDay : '[Għada fil-]LT', + nextWeek : 'dddd [fil-]LT', + lastDay : '[Il-bieraħ fil-]LT', + lastWeek : 'dddd [li għadda] [fil-]LT', + sameElse : 'L' + }, + relativeTime : { + future : 'f’ %s', + past : '%s ilu', + s : 'ftit sekondi', + ss : '%d sekondi', + m : 'minuta', + mm : '%d minuti', + h : 'siegħa', + hh : '%d siegħat', + d : 'ġurnata', + dd : '%d ġranet', + M : 'xahar', + MM : '%d xhur', + y : 'sena', + yy : '%d sni' + }, + dayOfMonthOrdinalParse : /\d{1,2}º/, + ordinal: '%dº', + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } + }); -var BUILDIDENTIFIER = R++; -src[BUILDIDENTIFIER] = '[0-9A-Za-z-]+'; + return mt; -// ## Build Metadata -// Plus sign, followed by one or more period-separated build metadata -// identifiers. +}))); -var BUILD = R++; -src[BUILD] = '(?:\\+(' + src[BUILDIDENTIFIER] + - '(?:\\.' + src[BUILDIDENTIFIER] + ')*))'; +/***/ }), +/* 124 */ +/***/ (function(module, exports, __webpack_require__) { -// ## Full Version String -// A main version, followed optionally by a pre-release version and -// build metadata. +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var symbolMap = { + '1': '၁', + '2': '၂', + '3': '၃', + '4': '၄', + '5': '၅', + '6': '၆', + '7': '၇', + '8': '၈', + '9': '၉', + '0': '၀' + }, numberMap = { + '၁': '1', + '၂': '2', + '၃': '3', + '၄': '4', + '၅': '5', + '၆': '6', + '၇': '7', + '၈': '8', + '၉': '9', + '၀': '0' + }; + + var my = moment.defineLocale('my', { + months: 'ဇန်နဝါရီ_ဖေဖော်ဝါရီ_မတ်_ဧပြီ_မေ_ဇွန်_ဇူလိုင်_သြဂုတ်_စက်တင်ဘာ_အောက်တိုဘာ_နိုဝင်ဘာ_ဒီဇင်ဘာ'.split('_'), + monthsShort: 'ဇန်_ဖေ_မတ်_ပြီ_မေ_ဇွန်_လိုင်_သြ_စက်_အောက်_နို_ဒီ'.split('_'), + weekdays: 'တနင်္ဂနွေ_တနင်္လာ_အင်္ဂါ_ဗုဒ္ဓဟူး_ကြာသပတေး_သောကြာ_စနေ'.split('_'), + weekdaysShort: 'နွေ_လာ_ဂါ_ဟူး_ကြာ_သော_နေ'.split('_'), + weekdaysMin: 'နွေ_လာ_ဂါ_ဟူး_ကြာ_သော_နေ'.split('_'), + + longDateFormat: { + LT: 'HH:mm', + LTS: 'HH:mm:ss', + L: 'DD/MM/YYYY', + LL: 'D MMMM YYYY', + LLL: 'D MMMM YYYY HH:mm', + LLLL: 'dddd D MMMM YYYY HH:mm' + }, + calendar: { + sameDay: '[ယနေ.] LT [မှာ]', + nextDay: '[မနက်ဖြန်] LT [မှာ]', + nextWeek: 'dddd LT [မှာ]', + lastDay: '[မနေ.က] LT [မှာ]', + lastWeek: '[ပြီးခဲ့သော] dddd LT [မှာ]', + sameElse: 'L' + }, + relativeTime: { + future: 'လာမည့် %s မှာ', + past: 'လွန်ခဲ့သော %s က', + s: 'စက္ကန်.အနည်းငယ်', + ss : '%d စက္ကန့်', + m: 'တစ်မိနစ်', + mm: '%d မိနစ်', + h: 'တစ်နာရီ', + hh: '%d နာရီ', + d: 'တစ်ရက်', + dd: '%d ရက်', + M: 'တစ်လ', + MM: '%d လ', + y: 'တစ်နှစ်', + yy: '%d နှစ်' + }, + preparse: function (string) { + return string.replace(/[၁၂၃၄၅၆၇၈၉၀]/g, function (match) { + return numberMap[match]; + }); + }, + postformat: function (string) { + return string.replace(/\d/g, function (match) { + return symbolMap[match]; + }); + }, + week: { + dow: 1, // Monday is the first day of the week. + doy: 4 // The week that contains Jan 4th is the first week of the year. + } + }); -// Note that the only major, minor, patch, and pre-release sections of -// the version string are capturing groups. The build metadata is not a -// capturing group, because it should not ever be used in version -// comparison. + return my; -var FULL = R++; -var FULLPLAIN = 'v?' + src[MAINVERSION] + - src[PRERELEASE] + '?' + - src[BUILD] + '?'; +}))); -src[FULL] = '^' + FULLPLAIN + '$'; -// like full, but allows v1.2.3 and =1.2.3, which people do sometimes. -// also, 1.0.0alpha1 (prerelease without the hyphen) which is pretty -// common in the npm registry. -var LOOSEPLAIN = '[v=\\s]*' + src[MAINVERSIONLOOSE] + - src[PRERELEASELOOSE] + '?' + - src[BUILD] + '?'; +/***/ }), +/* 125 */ +/***/ (function(module, exports, __webpack_require__) { -var LOOSE = R++; -src[LOOSE] = '^' + LOOSEPLAIN + '$'; +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var nb = moment.defineLocale('nb', { + months : 'januar_februar_mars_april_mai_juni_juli_august_september_oktober_november_desember'.split('_'), + monthsShort : 'jan._feb._mars_april_mai_juni_juli_aug._sep._okt._nov._des.'.split('_'), + monthsParseExact : true, + weekdays : 'søndag_mandag_tirsdag_onsdag_torsdag_fredag_lørdag'.split('_'), + weekdaysShort : 'sø._ma._ti._on._to._fr._lø.'.split('_'), + weekdaysMin : 'sø_ma_ti_on_to_fr_lø'.split('_'), + weekdaysParseExact : true, + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD.MM.YYYY', + LL : 'D. MMMM YYYY', + LLL : 'D. MMMM YYYY [kl.] HH:mm', + LLLL : 'dddd D. MMMM YYYY [kl.] HH:mm' + }, + calendar : { + sameDay: '[i dag kl.] LT', + nextDay: '[i morgen kl.] LT', + nextWeek: 'dddd [kl.] LT', + lastDay: '[i går kl.] LT', + lastWeek: '[forrige] dddd [kl.] LT', + sameElse: 'L' + }, + relativeTime : { + future : 'om %s', + past : '%s siden', + s : 'noen sekunder', + ss : '%d sekunder', + m : 'ett minutt', + mm : '%d minutter', + h : 'en time', + hh : '%d timer', + d : 'en dag', + dd : '%d dager', + M : 'en måned', + MM : '%d måneder', + y : 'ett år', + yy : '%d år' + }, + dayOfMonthOrdinalParse: /\d{1,2}\./, + ordinal : '%d.', + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } + }); -var GTLT = R++; -src[GTLT] = '((?:<|>)?=?)'; + return nb; -// Something like "2.*" or "1.2.x". -// Note that "x.x" is a valid xRange identifer, meaning "any version" -// Only the first item is strictly required. -var XRANGEIDENTIFIERLOOSE = R++; -src[XRANGEIDENTIFIERLOOSE] = src[NUMERICIDENTIFIERLOOSE] + '|x|X|\\*'; -var XRANGEIDENTIFIER = R++; -src[XRANGEIDENTIFIER] = src[NUMERICIDENTIFIER] + '|x|X|\\*'; +}))); -var XRANGEPLAIN = R++; -src[XRANGEPLAIN] = '[v=\\s]*(' + src[XRANGEIDENTIFIER] + ')' + - '(?:\\.(' + src[XRANGEIDENTIFIER] + ')' + - '(?:\\.(' + src[XRANGEIDENTIFIER] + ')' + - '(?:' + src[PRERELEASE] + ')?' + - src[BUILD] + '?' + - ')?)?'; -var XRANGEPLAINLOOSE = R++; -src[XRANGEPLAINLOOSE] = '[v=\\s]*(' + src[XRANGEIDENTIFIERLOOSE] + ')' + - '(?:\\.(' + src[XRANGEIDENTIFIERLOOSE] + ')' + - '(?:\\.(' + src[XRANGEIDENTIFIERLOOSE] + ')' + - '(?:' + src[PRERELEASELOOSE] + ')?' + - src[BUILD] + '?' + - ')?)?'; +/***/ }), +/* 126 */ +/***/ (function(module, exports, __webpack_require__) { -var XRANGE = R++; -src[XRANGE] = '^' + src[GTLT] + '\\s*' + src[XRANGEPLAIN] + '$'; -var XRANGELOOSE = R++; -src[XRANGELOOSE] = '^' + src[GTLT] + '\\s*' + src[XRANGEPLAINLOOSE] + '$'; +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var symbolMap = { + '1': '१', + '2': '२', + '3': '३', + '4': '४', + '5': '५', + '6': '६', + '7': '७', + '8': '८', + '9': '९', + '0': '०' + }, + numberMap = { + '१': '1', + '२': '2', + '३': '3', + '४': '4', + '५': '5', + '६': '6', + '७': '7', + '८': '8', + '९': '9', + '०': '0' + }; + + var ne = moment.defineLocale('ne', { + months : 'जनवरी_फेब्रुवरी_मार्च_अप्रिल_मई_जुन_जुलाई_अगष्ट_सेप्टेम्बर_अक्टोबर_नोभेम्बर_डिसेम्बर'.split('_'), + monthsShort : 'जन._फेब्रु._मार्च_अप्रि._मई_जुन_जुलाई._अग._सेप्ट._अक्टो._नोभे._डिसे.'.split('_'), + monthsParseExact : true, + weekdays : 'आइतबार_सोमबार_मङ्गलबार_बुधबार_बिहिबार_शुक्रबार_शनिबार'.split('_'), + weekdaysShort : 'आइत._सोम._मङ्गल._बुध._बिहि._शुक्र._शनि.'.split('_'), + weekdaysMin : 'आ._सो._मं._बु._बि._शु._श.'.split('_'), + weekdaysParseExact : true, + longDateFormat : { + LT : 'Aको h:mm बजे', + LTS : 'Aको h:mm:ss बजे', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY, Aको h:mm बजे', + LLLL : 'dddd, D MMMM YYYY, Aको h:mm बजे' + }, + preparse: function (string) { + return string.replace(/[१२३४५६७८९०]/g, function (match) { + return numberMap[match]; + }); + }, + postformat: function (string) { + return string.replace(/\d/g, function (match) { + return symbolMap[match]; + }); + }, + meridiemParse: /राति|बिहान|दिउँसो|साँझ/, + meridiemHour : function (hour, meridiem) { + if (hour === 12) { + hour = 0; + } + if (meridiem === 'राति') { + return hour < 4 ? hour : hour + 12; + } else if (meridiem === 'बिहान') { + return hour; + } else if (meridiem === 'दिउँसो') { + return hour >= 10 ? hour : hour + 12; + } else if (meridiem === 'साँझ') { + return hour + 12; + } + }, + meridiem : function (hour, minute, isLower) { + if (hour < 3) { + return 'राति'; + } else if (hour < 12) { + return 'बिहान'; + } else if (hour < 16) { + return 'दिउँसो'; + } else if (hour < 20) { + return 'साँझ'; + } else { + return 'राति'; + } + }, + calendar : { + sameDay : '[आज] LT', + nextDay : '[भोलि] LT', + nextWeek : '[आउँदो] dddd[,] LT', + lastDay : '[हिजो] LT', + lastWeek : '[गएको] dddd[,] LT', + sameElse : 'L' + }, + relativeTime : { + future : '%sमा', + past : '%s अगाडि', + s : 'केही क्षण', + ss : '%d सेकेण्ड', + m : 'एक मिनेट', + mm : '%d मिनेट', + h : 'एक घण्टा', + hh : '%d घण्टा', + d : 'एक दिन', + dd : '%d दिन', + M : 'एक महिना', + MM : '%d महिना', + y : 'एक बर्ष', + yy : '%d बर्ष' + }, + week : { + dow : 0, // Sunday is the first day of the week. + doy : 6 // The week that contains Jan 6th is the first week of the year. + } + }); -// Tilde ranges. -// Meaning is "reasonably at or greater than" -var LONETILDE = R++; -src[LONETILDE] = '(?:~>?)'; + return ne; -var TILDETRIM = R++; -src[TILDETRIM] = '(\\s*)' + src[LONETILDE] + '\\s+'; -re[TILDETRIM] = new RegExp(src[TILDETRIM], 'g'); -var tildeTrimReplace = '$1~'; +}))); -var TILDE = R++; -src[TILDE] = '^' + src[LONETILDE] + src[XRANGEPLAIN] + '$'; -var TILDELOOSE = R++; -src[TILDELOOSE] = '^' + src[LONETILDE] + src[XRANGEPLAINLOOSE] + '$'; -// Caret ranges. -// Meaning is "at least and backwards compatible with" -var LONECARET = R++; -src[LONECARET] = '(?:\\^)'; +/***/ }), +/* 127 */ +/***/ (function(module, exports, __webpack_require__) { -var CARETTRIM = R++; -src[CARETTRIM] = '(\\s*)' + src[LONECARET] + '\\s+'; -re[CARETTRIM] = new RegExp(src[CARETTRIM], 'g'); -var caretTrimReplace = '$1^'; +//! moment.js locale configuration -var CARET = R++; -src[CARET] = '^' + src[LONECARET] + src[XRANGEPLAIN] + '$'; -var CARETLOOSE = R++; -src[CARETLOOSE] = '^' + src[LONECARET] + src[XRANGEPLAINLOOSE] + '$'; +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; -// A simple gt/lt/eq thing, or just "" to indicate "any version" -var COMPARATORLOOSE = R++; -src[COMPARATORLOOSE] = '^' + src[GTLT] + '\\s*(' + LOOSEPLAIN + ')$|^$'; -var COMPARATOR = R++; -src[COMPARATOR] = '^' + src[GTLT] + '\\s*(' + FULLPLAIN + ')$|^$'; + var monthsShortWithDots = 'jan._feb._mrt._apr._mei_jun._jul._aug._sep._okt._nov._dec.'.split('_'), + monthsShortWithoutDots = 'jan_feb_mrt_apr_mei_jun_jul_aug_sep_okt_nov_dec'.split('_'); -// An expression to strip any whitespace between the gtlt and the thing -// it modifies, so that `> 1.2.3` ==> `>1.2.3` -var COMPARATORTRIM = R++; -src[COMPARATORTRIM] = '(\\s*)' + src[GTLT] + - '\\s*(' + LOOSEPLAIN + '|' + src[XRANGEPLAIN] + ')'; + var monthsParse = [/^jan/i, /^feb/i, /^maart|mrt.?$/i, /^apr/i, /^mei$/i, /^jun[i.]?$/i, /^jul[i.]?$/i, /^aug/i, /^sep/i, /^okt/i, /^nov/i, /^dec/i]; + var monthsRegex = /^(januari|februari|maart|april|mei|ju[nl]i|augustus|september|oktober|november|december|jan\.?|feb\.?|mrt\.?|apr\.?|ju[nl]\.?|aug\.?|sep\.?|okt\.?|nov\.?|dec\.?)/i; -// this one has to use the /g flag -re[COMPARATORTRIM] = new RegExp(src[COMPARATORTRIM], 'g'); -var comparatorTrimReplace = '$1$2$3'; + var nl = moment.defineLocale('nl', { + months : 'januari_februari_maart_april_mei_juni_juli_augustus_september_oktober_november_december'.split('_'), + monthsShort : function (m, format) { + if (!m) { + return monthsShortWithDots; + } else if (/-MMM-/.test(format)) { + return monthsShortWithoutDots[m.month()]; + } else { + return monthsShortWithDots[m.month()]; + } + }, + monthsRegex: monthsRegex, + monthsShortRegex: monthsRegex, + monthsStrictRegex: /^(januari|februari|maart|april|mei|ju[nl]i|augustus|september|oktober|november|december)/i, + monthsShortStrictRegex: /^(jan\.?|feb\.?|mrt\.?|apr\.?|mei|ju[nl]\.?|aug\.?|sep\.?|okt\.?|nov\.?|dec\.?)/i, + + monthsParse : monthsParse, + longMonthsParse : monthsParse, + shortMonthsParse : monthsParse, + + weekdays : 'zondag_maandag_dinsdag_woensdag_donderdag_vrijdag_zaterdag'.split('_'), + weekdaysShort : 'zo._ma._di._wo._do._vr._za.'.split('_'), + weekdaysMin : 'zo_ma_di_wo_do_vr_za'.split('_'), + weekdaysParseExact : true, + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD-MM-YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY HH:mm', + LLLL : 'dddd D MMMM YYYY HH:mm' + }, + calendar : { + sameDay: '[vandaag om] LT', + nextDay: '[morgen om] LT', + nextWeek: 'dddd [om] LT', + lastDay: '[gisteren om] LT', + lastWeek: '[afgelopen] dddd [om] LT', + sameElse: 'L' + }, + relativeTime : { + future : 'over %s', + past : '%s geleden', + s : 'een paar seconden', + ss : '%d seconden', + m : 'één minuut', + mm : '%d minuten', + h : 'één uur', + hh : '%d uur', + d : 'één dag', + dd : '%d dagen', + M : 'één maand', + MM : '%d maanden', + y : 'één jaar', + yy : '%d jaar' + }, + dayOfMonthOrdinalParse: /\d{1,2}(ste|de)/, + ordinal : function (number) { + return number + ((number === 1 || number === 8 || number >= 20) ? 'ste' : 'de'); + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } + }); -// Something like `1.2.3 - 1.2.4` -// Note that these all use the loose form, because they'll be -// checked against either the strict or loose comparator form -// later. -var HYPHENRANGE = R++; -src[HYPHENRANGE] = '^\\s*(' + src[XRANGEPLAIN] + ')' + - '\\s+-\\s+' + - '(' + src[XRANGEPLAIN] + ')' + - '\\s*$'; + return nl; -var HYPHENRANGELOOSE = R++; -src[HYPHENRANGELOOSE] = '^\\s*(' + src[XRANGEPLAINLOOSE] + ')' + - '\\s+-\\s+' + - '(' + src[XRANGEPLAINLOOSE] + ')' + - '\\s*$'; +}))); -// Star ranges basically just allow anything at all. -var STAR = R++; -src[STAR] = '(<|>)?=?\\s*\\*'; -// Compile to actual regexp objects. -// All are flag-free, unless they were created above with a flag. -for (var i = 0; i < R; i++) { - debug(i, src[i]); - if (!re[i]) - re[i] = new RegExp(src[i]); -} +/***/ }), +/* 128 */ +/***/ (function(module, exports, __webpack_require__) { -exports.parse = parse; -function parse(version, loose) { - if (version instanceof SemVer) - return version; +//! moment.js locale configuration - if (typeof version !== 'string') - return null; +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; - if (version.length > MAX_LENGTH) - return null; - var r = loose ? re[LOOSE] : re[FULL]; - if (!r.test(version)) - return null; + var monthsShortWithDots = 'jan._feb._mrt._apr._mei_jun._jul._aug._sep._okt._nov._dec.'.split('_'), + monthsShortWithoutDots = 'jan_feb_mrt_apr_mei_jun_jul_aug_sep_okt_nov_dec'.split('_'); - try { - return new SemVer(version, loose); - } catch (er) { - return null; - } -} + var monthsParse = [/^jan/i, /^feb/i, /^maart|mrt.?$/i, /^apr/i, /^mei$/i, /^jun[i.]?$/i, /^jul[i.]?$/i, /^aug/i, /^sep/i, /^okt/i, /^nov/i, /^dec/i]; + var monthsRegex = /^(januari|februari|maart|april|mei|ju[nl]i|augustus|september|oktober|november|december|jan\.?|feb\.?|mrt\.?|apr\.?|ju[nl]\.?|aug\.?|sep\.?|okt\.?|nov\.?|dec\.?)/i; -exports.valid = valid; -function valid(version, loose) { - var v = parse(version, loose); - return v ? v.version : null; -} + var nlBe = moment.defineLocale('nl-be', { + months : 'januari_februari_maart_april_mei_juni_juli_augustus_september_oktober_november_december'.split('_'), + monthsShort : function (m, format) { + if (!m) { + return monthsShortWithDots; + } else if (/-MMM-/.test(format)) { + return monthsShortWithoutDots[m.month()]; + } else { + return monthsShortWithDots[m.month()]; + } + }, + monthsRegex: monthsRegex, + monthsShortRegex: monthsRegex, + monthsStrictRegex: /^(januari|februari|maart|april|mei|ju[nl]i|augustus|september|oktober|november|december)/i, + monthsShortStrictRegex: /^(jan\.?|feb\.?|mrt\.?|apr\.?|mei|ju[nl]\.?|aug\.?|sep\.?|okt\.?|nov\.?|dec\.?)/i, + + monthsParse : monthsParse, + longMonthsParse : monthsParse, + shortMonthsParse : monthsParse, + + weekdays : 'zondag_maandag_dinsdag_woensdag_donderdag_vrijdag_zaterdag'.split('_'), + weekdaysShort : 'zo._ma._di._wo._do._vr._za.'.split('_'), + weekdaysMin : 'zo_ma_di_wo_do_vr_za'.split('_'), + weekdaysParseExact : true, + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY HH:mm', + LLLL : 'dddd D MMMM YYYY HH:mm' + }, + calendar : { + sameDay: '[vandaag om] LT', + nextDay: '[morgen om] LT', + nextWeek: 'dddd [om] LT', + lastDay: '[gisteren om] LT', + lastWeek: '[afgelopen] dddd [om] LT', + sameElse: 'L' + }, + relativeTime : { + future : 'over %s', + past : '%s geleden', + s : 'een paar seconden', + ss : '%d seconden', + m : 'één minuut', + mm : '%d minuten', + h : 'één uur', + hh : '%d uur', + d : 'één dag', + dd : '%d dagen', + M : 'één maand', + MM : '%d maanden', + y : 'één jaar', + yy : '%d jaar' + }, + dayOfMonthOrdinalParse: /\d{1,2}(ste|de)/, + ordinal : function (number) { + return number + ((number === 1 || number === 8 || number >= 20) ? 'ste' : 'de'); + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } + }); -exports.clean = clean; -function clean(version, loose) { - var s = parse(version.trim().replace(/^[=v]+/, ''), loose); - return s ? s.version : null; -} + return nlBe; -exports.SemVer = SemVer; +}))); -function SemVer(version, loose) { - if (version instanceof SemVer) { - if (version.loose === loose) - return version; - else - version = version.version; - } else if (typeof version !== 'string') { - throw new TypeError('Invalid Version: ' + version); - } - if (version.length > MAX_LENGTH) - throw new TypeError('version is longer than ' + MAX_LENGTH + ' characters') +/***/ }), +/* 129 */ +/***/ (function(module, exports, __webpack_require__) { - if (!(this instanceof SemVer)) - return new SemVer(version, loose); +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var nn = moment.defineLocale('nn', { + months : 'januar_februar_mars_april_mai_juni_juli_august_september_oktober_november_desember'.split('_'), + monthsShort : 'jan_feb_mar_apr_mai_jun_jul_aug_sep_okt_nov_des'.split('_'), + weekdays : 'sundag_måndag_tysdag_onsdag_torsdag_fredag_laurdag'.split('_'), + weekdaysShort : 'sun_mån_tys_ons_tor_fre_lau'.split('_'), + weekdaysMin : 'su_må_ty_on_to_fr_lø'.split('_'), + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD.MM.YYYY', + LL : 'D. MMMM YYYY', + LLL : 'D. MMMM YYYY [kl.] H:mm', + LLLL : 'dddd D. MMMM YYYY [kl.] HH:mm' + }, + calendar : { + sameDay: '[I dag klokka] LT', + nextDay: '[I morgon klokka] LT', + nextWeek: 'dddd [klokka] LT', + lastDay: '[I går klokka] LT', + lastWeek: '[Føregåande] dddd [klokka] LT', + sameElse: 'L' + }, + relativeTime : { + future : 'om %s', + past : '%s sidan', + s : 'nokre sekund', + ss : '%d sekund', + m : 'eit minutt', + mm : '%d minutt', + h : 'ein time', + hh : '%d timar', + d : 'ein dag', + dd : '%d dagar', + M : 'ein månad', + MM : '%d månader', + y : 'eit år', + yy : '%d år' + }, + dayOfMonthOrdinalParse: /\d{1,2}\./, + ordinal : '%d.', + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } + }); - debug('SemVer', version, loose); - this.loose = loose; - var m = version.trim().match(loose ? re[LOOSE] : re[FULL]); + return nn; - if (!m) - throw new TypeError('Invalid Version: ' + version); +}))); - this.raw = version; - // these are actually numbers - this.major = +m[1]; - this.minor = +m[2]; - this.patch = +m[3]; - - if (this.major > MAX_SAFE_INTEGER || this.major < 0) - throw new TypeError('Invalid major version') +/***/ }), +/* 130 */ +/***/ (function(module, exports, __webpack_require__) { - if (this.minor > MAX_SAFE_INTEGER || this.minor < 0) - throw new TypeError('Invalid minor version') +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var symbolMap = { + '1': '੧', + '2': '੨', + '3': '੩', + '4': '੪', + '5': '੫', + '6': '੬', + '7': '੭', + '8': '੮', + '9': '੯', + '0': '੦' + }, + numberMap = { + '੧': '1', + '੨': '2', + '੩': '3', + '੪': '4', + '੫': '5', + '੬': '6', + '੭': '7', + '੮': '8', + '੯': '9', + '੦': '0' + }; + + var paIn = moment.defineLocale('pa-in', { + // There are months name as per Nanakshahi Calendar but they are not used as rigidly in modern Punjabi. + months : 'ਜਨਵਰੀ_ਫ਼ਰਵਰੀ_ਮਾਰਚ_ਅਪ੍ਰੈਲ_ਮਈ_ਜੂਨ_ਜੁਲਾਈ_ਅਗਸਤ_ਸਤੰਬਰ_ਅਕਤੂਬਰ_ਨਵੰਬਰ_ਦਸੰਬਰ'.split('_'), + monthsShort : 'ਜਨਵਰੀ_ਫ਼ਰਵਰੀ_ਮਾਰਚ_ਅਪ੍ਰੈਲ_ਮਈ_ਜੂਨ_ਜੁਲਾਈ_ਅਗਸਤ_ਸਤੰਬਰ_ਅਕਤੂਬਰ_ਨਵੰਬਰ_ਦਸੰਬਰ'.split('_'), + weekdays : 'ਐਤਵਾਰ_ਸੋਮਵਾਰ_ਮੰਗਲਵਾਰ_ਬੁਧਵਾਰ_ਵੀਰਵਾਰ_ਸ਼ੁੱਕਰਵਾਰ_ਸ਼ਨੀਚਰਵਾਰ'.split('_'), + weekdaysShort : 'ਐਤ_ਸੋਮ_ਮੰਗਲ_ਬੁਧ_ਵੀਰ_ਸ਼ੁਕਰ_ਸ਼ਨੀ'.split('_'), + weekdaysMin : 'ਐਤ_ਸੋਮ_ਮੰਗਲ_ਬੁਧ_ਵੀਰ_ਸ਼ੁਕਰ_ਸ਼ਨੀ'.split('_'), + longDateFormat : { + LT : 'A h:mm ਵਜੇ', + LTS : 'A h:mm:ss ਵਜੇ', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY, A h:mm ਵਜੇ', + LLLL : 'dddd, D MMMM YYYY, A h:mm ਵਜੇ' + }, + calendar : { + sameDay : '[ਅਜ] LT', + nextDay : '[ਕਲ] LT', + nextWeek : '[ਅਗਲਾ] dddd, LT', + lastDay : '[ਕਲ] LT', + lastWeek : '[ਪਿਛਲੇ] dddd, LT', + sameElse : 'L' + }, + relativeTime : { + future : '%s ਵਿੱਚ', + past : '%s ਪਿਛਲੇ', + s : 'ਕੁਝ ਸਕਿੰਟ', + ss : '%d ਸਕਿੰਟ', + m : 'ਇਕ ਮਿੰਟ', + mm : '%d ਮਿੰਟ', + h : 'ਇੱਕ ਘੰਟਾ', + hh : '%d ਘੰਟੇ', + d : 'ਇੱਕ ਦਿਨ', + dd : '%d ਦਿਨ', + M : 'ਇੱਕ ਮਹੀਨਾ', + MM : '%d ਮਹੀਨੇ', + y : 'ਇੱਕ ਸਾਲ', + yy : '%d ਸਾਲ' + }, + preparse: function (string) { + return string.replace(/[੧੨੩੪੫੬੭੮੯੦]/g, function (match) { + return numberMap[match]; + }); + }, + postformat: function (string) { + return string.replace(/\d/g, function (match) { + return symbolMap[match]; + }); + }, + // Punjabi notation for meridiems are quite fuzzy in practice. While there exists + // a rigid notion of a 'Pahar' it is not used as rigidly in modern Punjabi. + meridiemParse: /ਰਾਤ|ਸਵੇਰ|ਦੁਪਹਿਰ|ਸ਼ਾਮ/, + meridiemHour : function (hour, meridiem) { + if (hour === 12) { + hour = 0; + } + if (meridiem === 'ਰਾਤ') { + return hour < 4 ? hour : hour + 12; + } else if (meridiem === 'ਸਵੇਰ') { + return hour; + } else if (meridiem === 'ਦੁਪਹਿਰ') { + return hour >= 10 ? hour : hour + 12; + } else if (meridiem === 'ਸ਼ਾਮ') { + return hour + 12; + } + }, + meridiem : function (hour, minute, isLower) { + if (hour < 4) { + return 'ਰਾਤ'; + } else if (hour < 10) { + return 'ਸਵੇਰ'; + } else if (hour < 17) { + return 'ਦੁਪਹਿਰ'; + } else if (hour < 20) { + return 'ਸ਼ਾਮ'; + } else { + return 'ਰਾਤ'; + } + }, + week : { + dow : 0, // Sunday is the first day of the week. + doy : 6 // The week that contains Jan 6th is the first week of the year. + } + }); - if (this.patch > MAX_SAFE_INTEGER || this.patch < 0) - throw new TypeError('Invalid patch version') + return paIn; - // numberify any prerelease numeric ids - if (!m[4]) - this.prerelease = []; - else - this.prerelease = m[4].split('.').map(function(id) { - if (/^[0-9]+$/.test(id)) { - var num = +id; - if (num >= 0 && num < MAX_SAFE_INTEGER) - return num; - } - return id; - }); +}))); - this.build = m[5] ? m[5].split('.') : []; - this.format(); -} -SemVer.prototype.format = function() { - this.version = this.major + '.' + this.minor + '.' + this.patch; - if (this.prerelease.length) - this.version += '-' + this.prerelease.join('.'); - return this.version; -}; +/***/ }), +/* 131 */ +/***/ (function(module, exports, __webpack_require__) { -SemVer.prototype.toString = function() { - return this.version; -}; +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var monthsNominative = 'styczeń_luty_marzec_kwiecień_maj_czerwiec_lipiec_sierpień_wrzesień_październik_listopad_grudzień'.split('_'), + monthsSubjective = 'stycznia_lutego_marca_kwietnia_maja_czerwca_lipca_sierpnia_września_października_listopada_grudnia'.split('_'); + function plural(n) { + return (n % 10 < 5) && (n % 10 > 1) && ((~~(n / 10) % 10) !== 1); + } + function translate(number, withoutSuffix, key) { + var result = number + ' '; + switch (key) { + case 'ss': + return result + (plural(number) ? 'sekundy' : 'sekund'); + case 'm': + return withoutSuffix ? 'minuta' : 'minutę'; + case 'mm': + return result + (plural(number) ? 'minuty' : 'minut'); + case 'h': + return withoutSuffix ? 'godzina' : 'godzinę'; + case 'hh': + return result + (plural(number) ? 'godziny' : 'godzin'); + case 'MM': + return result + (plural(number) ? 'miesiące' : 'miesięcy'); + case 'yy': + return result + (plural(number) ? 'lata' : 'lat'); + } + } + + var pl = moment.defineLocale('pl', { + months : function (momentToFormat, format) { + if (!momentToFormat) { + return monthsNominative; + } else if (format === '') { + // Hack: if format empty we know this is used to generate + // RegExp by moment. Give then back both valid forms of months + // in RegExp ready format. + return '(' + monthsSubjective[momentToFormat.month()] + '|' + monthsNominative[momentToFormat.month()] + ')'; + } else if (/D MMMM/.test(format)) { + return monthsSubjective[momentToFormat.month()]; + } else { + return monthsNominative[momentToFormat.month()]; + } + }, + monthsShort : 'sty_lut_mar_kwi_maj_cze_lip_sie_wrz_paź_lis_gru'.split('_'), + weekdays : 'niedziela_poniedziałek_wtorek_środa_czwartek_piątek_sobota'.split('_'), + weekdaysShort : 'ndz_pon_wt_śr_czw_pt_sob'.split('_'), + weekdaysMin : 'Nd_Pn_Wt_Śr_Cz_Pt_So'.split('_'), + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD.MM.YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY HH:mm', + LLLL : 'dddd, D MMMM YYYY HH:mm' + }, + calendar : { + sameDay: '[Dziś o] LT', + nextDay: '[Jutro o] LT', + nextWeek: function () { + switch (this.day()) { + case 0: + return '[W niedzielę o] LT'; -SemVer.prototype.compare = function(other) { - debug('SemVer.compare', this.version, this.loose, other); - if (!(other instanceof SemVer)) - other = new SemVer(other, this.loose); + case 2: + return '[We wtorek o] LT'; - return this.compareMain(other) || this.comparePre(other); -}; + case 3: + return '[W środę o] LT'; -SemVer.prototype.compareMain = function(other) { - if (!(other instanceof SemVer)) - other = new SemVer(other, this.loose); + case 6: + return '[W sobotę o] LT'; - return compareIdentifiers(this.major, other.major) || - compareIdentifiers(this.minor, other.minor) || - compareIdentifiers(this.patch, other.patch); -}; + default: + return '[W] dddd [o] LT'; + } + }, + lastDay: '[Wczoraj o] LT', + lastWeek: function () { + switch (this.day()) { + case 0: + return '[W zeszłą niedzielę o] LT'; + case 3: + return '[W zeszłą środę o] LT'; + case 6: + return '[W zeszłą sobotę o] LT'; + default: + return '[W zeszły] dddd [o] LT'; + } + }, + sameElse: 'L' + }, + relativeTime : { + future : 'za %s', + past : '%s temu', + s : 'kilka sekund', + ss : translate, + m : translate, + mm : translate, + h : translate, + hh : translate, + d : '1 dzień', + dd : '%d dni', + M : 'miesiąc', + MM : translate, + y : 'rok', + yy : translate + }, + dayOfMonthOrdinalParse: /\d{1,2}\./, + ordinal : '%d.', + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } + }); -SemVer.prototype.comparePre = function(other) { - if (!(other instanceof SemVer)) - other = new SemVer(other, this.loose); + return pl; - // NOT having a prerelease is > having one - if (this.prerelease.length && !other.prerelease.length) - return -1; - else if (!this.prerelease.length && other.prerelease.length) - return 1; - else if (!this.prerelease.length && !other.prerelease.length) - return 0; +}))); - var i = 0; - do { - var a = this.prerelease[i]; - var b = other.prerelease[i]; - debug('prerelease compare', i, a, b); - if (a === undefined && b === undefined) - return 0; - else if (b === undefined) - return 1; - else if (a === undefined) - return -1; - else if (a === b) - continue; - else - return compareIdentifiers(a, b); - } while (++i); -}; -// preminor will bump the version up to the next minor release, and immediately -// down to pre-release. premajor and prepatch work the same way. -SemVer.prototype.inc = function(release, identifier) { - switch (release) { - case 'premajor': - this.prerelease.length = 0; - this.patch = 0; - this.minor = 0; - this.major++; - this.inc('pre', identifier); - break; - case 'preminor': - this.prerelease.length = 0; - this.patch = 0; - this.minor++; - this.inc('pre', identifier); - break; - case 'prepatch': - // If this is already a prerelease, it will bump to the next version - // drop any prereleases that might already exist, since they are not - // relevant at this point. - this.prerelease.length = 0; - this.inc('patch', identifier); - this.inc('pre', identifier); - break; - // If the input is a non-prerelease version, this acts the same as - // prepatch. - case 'prerelease': - if (this.prerelease.length === 0) - this.inc('patch', identifier); - this.inc('pre', identifier); - break; +/***/ }), +/* 132 */ +/***/ (function(module, exports, __webpack_require__) { - case 'major': - // If this is a pre-major version, bump up to the same major version. - // Otherwise increment major. - // 1.0.0-5 bumps to 1.0.0 - // 1.1.0 bumps to 2.0.0 - if (this.minor !== 0 || this.patch !== 0 || this.prerelease.length === 0) - this.major++; - this.minor = 0; - this.patch = 0; - this.prerelease = []; - break; - case 'minor': - // If this is a pre-minor version, bump up to the same minor version. - // Otherwise increment minor. - // 1.2.0-5 bumps to 1.2.0 - // 1.2.1 bumps to 1.3.0 - if (this.patch !== 0 || this.prerelease.length === 0) - this.minor++; - this.patch = 0; - this.prerelease = []; - break; - case 'patch': - // If this is not a pre-release version, it will increment the patch. - // If it is a pre-release it will bump up to the same patch version. - // 1.2.0-5 patches to 1.2.0 - // 1.2.0 patches to 1.2.1 - if (this.prerelease.length === 0) - this.patch++; - this.prerelease = []; - break; - // This probably shouldn't be used publicly. - // 1.0.0 "pre" would become 1.0.0-0 which is the wrong direction. - case 'pre': - if (this.prerelease.length === 0) - this.prerelease = [0]; - else { - var i = this.prerelease.length; - while (--i >= 0) { - if (typeof this.prerelease[i] === 'number') { - this.prerelease[i]++; - i = -2; - } +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var pt = moment.defineLocale('pt', { + months : 'Janeiro_Fevereiro_Março_Abril_Maio_Junho_Julho_Agosto_Setembro_Outubro_Novembro_Dezembro'.split('_'), + monthsShort : 'Jan_Fev_Mar_Abr_Mai_Jun_Jul_Ago_Set_Out_Nov_Dez'.split('_'), + weekdays : 'Domingo_Segunda-feira_Terça-feira_Quarta-feira_Quinta-feira_Sexta-feira_Sábado'.split('_'), + weekdaysShort : 'Dom_Seg_Ter_Qua_Qui_Sex_Sáb'.split('_'), + weekdaysMin : 'Do_2ª_3ª_4ª_5ª_6ª_Sá'.split('_'), + weekdaysParseExact : true, + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD/MM/YYYY', + LL : 'D [de] MMMM [de] YYYY', + LLL : 'D [de] MMMM [de] YYYY HH:mm', + LLLL : 'dddd, D [de] MMMM [de] YYYY HH:mm' + }, + calendar : { + sameDay: '[Hoje às] LT', + nextDay: '[Amanhã às] LT', + nextWeek: 'dddd [às] LT', + lastDay: '[Ontem às] LT', + lastWeek: function () { + return (this.day() === 0 || this.day() === 6) ? + '[Último] dddd [às] LT' : // Saturday + Sunday + '[Última] dddd [às] LT'; // Monday - Friday + }, + sameElse: 'L' + }, + relativeTime : { + future : 'em %s', + past : 'há %s', + s : 'segundos', + ss : '%d segundos', + m : 'um minuto', + mm : '%d minutos', + h : 'uma hora', + hh : '%d horas', + d : 'um dia', + dd : '%d dias', + M : 'um mês', + MM : '%d meses', + y : 'um ano', + yy : '%d anos' + }, + dayOfMonthOrdinalParse: /\d{1,2}º/, + ordinal : '%dº', + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. } - if (i === -1) // didn't increment anything - this.prerelease.push(0); - } - if (identifier) { - // 1.2.0-beta.1 bumps to 1.2.0-beta.2, - // 1.2.0-beta.fooblz or 1.2.0-beta bumps to 1.2.0-beta.0 - if (this.prerelease[0] === identifier) { - if (isNaN(this.prerelease[1])) - this.prerelease = [identifier, 0]; - } else - this.prerelease = [identifier, 0]; - } - break; + }); - default: - throw new Error('invalid increment argument: ' + release); - } - this.format(); - this.raw = this.version; - return this; -}; + return pt; -exports.inc = inc; -function inc(version, release, loose, identifier) { - if (typeof(loose) === 'string') { - identifier = loose; - loose = undefined; - } +}))); - try { - return new SemVer(version, loose).inc(release, identifier).version; - } catch (er) { - return null; - } -} -exports.diff = diff; -function diff(version1, version2) { - if (eq(version1, version2)) { - return null; - } else { - var v1 = parse(version1); - var v2 = parse(version2); - if (v1.prerelease.length || v2.prerelease.length) { - for (var key in v1) { - if (key === 'major' || key === 'minor' || key === 'patch') { - if (v1[key] !== v2[key]) { - return 'pre'+key; - } - } - } - return 'prerelease'; - } - for (var key in v1) { - if (key === 'major' || key === 'minor' || key === 'patch') { - if (v1[key] !== v2[key]) { - return key; - } - } - } - } -} +/***/ }), +/* 133 */ +/***/ (function(module, exports, __webpack_require__) { -exports.compareIdentifiers = compareIdentifiers; +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var ptBr = moment.defineLocale('pt-br', { + months : 'Janeiro_Fevereiro_Março_Abril_Maio_Junho_Julho_Agosto_Setembro_Outubro_Novembro_Dezembro'.split('_'), + monthsShort : 'Jan_Fev_Mar_Abr_Mai_Jun_Jul_Ago_Set_Out_Nov_Dez'.split('_'), + weekdays : 'Domingo_Segunda-feira_Terça-feira_Quarta-feira_Quinta-feira_Sexta-feira_Sábado'.split('_'), + weekdaysShort : 'Dom_Seg_Ter_Qua_Qui_Sex_Sáb'.split('_'), + weekdaysMin : 'Do_2ª_3ª_4ª_5ª_6ª_Sá'.split('_'), + weekdaysParseExact : true, + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD/MM/YYYY', + LL : 'D [de] MMMM [de] YYYY', + LLL : 'D [de] MMMM [de] YYYY [às] HH:mm', + LLLL : 'dddd, D [de] MMMM [de] YYYY [às] HH:mm' + }, + calendar : { + sameDay: '[Hoje às] LT', + nextDay: '[Amanhã às] LT', + nextWeek: 'dddd [às] LT', + lastDay: '[Ontem às] LT', + lastWeek: function () { + return (this.day() === 0 || this.day() === 6) ? + '[Último] dddd [às] LT' : // Saturday + Sunday + '[Última] dddd [às] LT'; // Monday - Friday + }, + sameElse: 'L' + }, + relativeTime : { + future : 'em %s', + past : 'há %s', + s : 'poucos segundos', + ss : '%d segundos', + m : 'um minuto', + mm : '%d minutos', + h : 'uma hora', + hh : '%d horas', + d : 'um dia', + dd : '%d dias', + M : 'um mês', + MM : '%d meses', + y : 'um ano', + yy : '%d anos' + }, + dayOfMonthOrdinalParse: /\d{1,2}º/, + ordinal : '%dº' + }); -var numeric = /^[0-9]+$/; -function compareIdentifiers(a, b) { - var anum = numeric.test(a); - var bnum = numeric.test(b); + return ptBr; - if (anum && bnum) { - a = +a; - b = +b; - } +}))); - return (anum && !bnum) ? -1 : - (bnum && !anum) ? 1 : - a < b ? -1 : - a > b ? 1 : - 0; -} -exports.rcompareIdentifiers = rcompareIdentifiers; -function rcompareIdentifiers(a, b) { - return compareIdentifiers(b, a); -} +/***/ }), +/* 134 */ +/***/ (function(module, exports, __webpack_require__) { -exports.major = major; -function major(a, loose) { - return new SemVer(a, loose).major; -} +//! moment.js locale configuration -exports.minor = minor; -function minor(a, loose) { - return new SemVer(a, loose).minor; -} +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; -exports.patch = patch; -function patch(a, loose) { - return new SemVer(a, loose).patch; -} -exports.compare = compare; -function compare(a, b, loose) { - return new SemVer(a, loose).compare(new SemVer(b, loose)); -} + function relativeTimeWithPlural(number, withoutSuffix, key) { + var format = { + 'ss': 'secunde', + 'mm': 'minute', + 'hh': 'ore', + 'dd': 'zile', + 'MM': 'luni', + 'yy': 'ani' + }, + separator = ' '; + if (number % 100 >= 20 || (number >= 100 && number % 100 === 0)) { + separator = ' de '; + } + return number + separator + format[key]; + } + + var ro = moment.defineLocale('ro', { + months : 'ianuarie_februarie_martie_aprilie_mai_iunie_iulie_august_septembrie_octombrie_noiembrie_decembrie'.split('_'), + monthsShort : 'ian._febr._mart._apr._mai_iun._iul._aug._sept._oct._nov._dec.'.split('_'), + monthsParseExact: true, + weekdays : 'duminică_luni_marți_miercuri_joi_vineri_sâmbătă'.split('_'), + weekdaysShort : 'Dum_Lun_Mar_Mie_Joi_Vin_Sâm'.split('_'), + weekdaysMin : 'Du_Lu_Ma_Mi_Jo_Vi_Sâ'.split('_'), + longDateFormat : { + LT : 'H:mm', + LTS : 'H:mm:ss', + L : 'DD.MM.YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY H:mm', + LLLL : 'dddd, D MMMM YYYY H:mm' + }, + calendar : { + sameDay: '[azi la] LT', + nextDay: '[mâine la] LT', + nextWeek: 'dddd [la] LT', + lastDay: '[ieri la] LT', + lastWeek: '[fosta] dddd [la] LT', + sameElse: 'L' + }, + relativeTime : { + future : 'peste %s', + past : '%s în urmă', + s : 'câteva secunde', + ss : relativeTimeWithPlural, + m : 'un minut', + mm : relativeTimeWithPlural, + h : 'o oră', + hh : relativeTimeWithPlural, + d : 'o zi', + dd : relativeTimeWithPlural, + M : 'o lună', + MM : relativeTimeWithPlural, + y : 'un an', + yy : relativeTimeWithPlural + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 7 // The week that contains Jan 7th is the first week of the year. + } + }); -exports.compareLoose = compareLoose; -function compareLoose(a, b) { - return compare(a, b, true); -} + return ro; -exports.rcompare = rcompare; -function rcompare(a, b, loose) { - return compare(b, a, loose); -} +}))); -exports.sort = sort; -function sort(list, loose) { - return list.sort(function(a, b) { - return exports.compare(a, b, loose); - }); -} -exports.rsort = rsort; -function rsort(list, loose) { - return list.sort(function(a, b) { - return exports.rcompare(a, b, loose); - }); -} +/***/ }), +/* 135 */ +/***/ (function(module, exports, __webpack_require__) { -exports.gt = gt; -function gt(a, b, loose) { - return compare(a, b, loose) > 0; -} +//! moment.js locale configuration -exports.lt = lt; -function lt(a, b, loose) { - return compare(a, b, loose) < 0; -} +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; -exports.eq = eq; -function eq(a, b, loose) { - return compare(a, b, loose) === 0; -} -exports.neq = neq; -function neq(a, b, loose) { - return compare(a, b, loose) !== 0; -} + function plural(word, num) { + var forms = word.split('_'); + return num % 10 === 1 && num % 100 !== 11 ? forms[0] : (num % 10 >= 2 && num % 10 <= 4 && (num % 100 < 10 || num % 100 >= 20) ? forms[1] : forms[2]); + } + function relativeTimeWithPlural(number, withoutSuffix, key) { + var format = { + 'ss': withoutSuffix ? 'секунда_секунды_секунд' : 'секунду_секунды_секунд', + 'mm': withoutSuffix ? 'минута_минуты_минут' : 'минуту_минуты_минут', + 'hh': 'час_часа_часов', + 'dd': 'день_дня_дней', + 'MM': 'месяц_месяца_месяцев', + 'yy': 'год_года_лет' + }; + if (key === 'm') { + return withoutSuffix ? 'минута' : 'минуту'; + } + else { + return number + ' ' + plural(format[key], +number); + } + } + var monthsParse = [/^янв/i, /^фев/i, /^мар/i, /^апр/i, /^ма[йя]/i, /^июн/i, /^июл/i, /^авг/i, /^сен/i, /^окт/i, /^ноя/i, /^дек/i]; -exports.gte = gte; -function gte(a, b, loose) { - return compare(a, b, loose) >= 0; -} + // http://new.gramota.ru/spravka/rules/139-prop : § 103 + // Сокращения месяцев: http://new.gramota.ru/spravka/buro/search-answer?s=242637 + // CLDR data: http://www.unicode.org/cldr/charts/28/summary/ru.html#1753 + var ru = moment.defineLocale('ru', { + months : { + format: 'января_февраля_марта_апреля_мая_июня_июля_августа_сентября_октября_ноября_декабря'.split('_'), + standalone: 'январь_февраль_март_апрель_май_июнь_июль_август_сентябрь_октябрь_ноябрь_декабрь'.split('_') + }, + monthsShort : { + // по CLDR именно "июл." и "июн.", но какой смысл менять букву на точку ? + format: 'янв._февр._мар._апр._мая_июня_июля_авг._сент._окт._нояб._дек.'.split('_'), + standalone: 'янв._февр._март_апр._май_июнь_июль_авг._сент._окт._нояб._дек.'.split('_') + }, + weekdays : { + standalone: 'воскресенье_понедельник_вторник_среда_четверг_пятница_суббота'.split('_'), + format: 'воскресенье_понедельник_вторник_среду_четверг_пятницу_субботу'.split('_'), + isFormat: /\[ ?[Вв] ?(?:прошлую|следующую|эту)? ?\] ?dddd/ + }, + weekdaysShort : 'вс_пн_вт_ср_чт_пт_сб'.split('_'), + weekdaysMin : 'вс_пн_вт_ср_чт_пт_сб'.split('_'), + monthsParse : monthsParse, + longMonthsParse : monthsParse, + shortMonthsParse : monthsParse, + + // полные названия с падежами, по три буквы, для некоторых, по 4 буквы, сокращения с точкой и без точки + monthsRegex: /^(январ[ья]|янв\.?|феврал[ья]|февр?\.?|марта?|мар\.?|апрел[ья]|апр\.?|ма[йя]|июн[ья]|июн\.?|июл[ья]|июл\.?|августа?|авг\.?|сентябр[ья]|сент?\.?|октябр[ья]|окт\.?|ноябр[ья]|нояб?\.?|декабр[ья]|дек\.?)/i, + + // копия предыдущего + monthsShortRegex: /^(январ[ья]|янв\.?|феврал[ья]|февр?\.?|марта?|мар\.?|апрел[ья]|апр\.?|ма[йя]|июн[ья]|июн\.?|июл[ья]|июл\.?|августа?|авг\.?|сентябр[ья]|сент?\.?|октябр[ья]|окт\.?|ноябр[ья]|нояб?\.?|декабр[ья]|дек\.?)/i, + + // полные названия с падежами + monthsStrictRegex: /^(январ[яь]|феврал[яь]|марта?|апрел[яь]|ма[яй]|июн[яь]|июл[яь]|августа?|сентябр[яь]|октябр[яь]|ноябр[яь]|декабр[яь])/i, + + // Выражение, которое соотвествует только сокращённым формам + monthsShortStrictRegex: /^(янв\.|февр?\.|мар[т.]|апр\.|ма[яй]|июн[ья.]|июл[ья.]|авг\.|сент?\.|окт\.|нояб?\.|дек\.)/i, + longDateFormat : { + LT : 'H:mm', + LTS : 'H:mm:ss', + L : 'DD.MM.YYYY', + LL : 'D MMMM YYYY г.', + LLL : 'D MMMM YYYY г., H:mm', + LLLL : 'dddd, D MMMM YYYY г., H:mm' + }, + calendar : { + sameDay: '[Сегодня, в] LT', + nextDay: '[Завтра, в] LT', + lastDay: '[Вчера, в] LT', + nextWeek: function (now) { + if (now.week() !== this.week()) { + switch (this.day()) { + case 0: + return '[В следующее] dddd, [в] LT'; + case 1: + case 2: + case 4: + return '[В следующий] dddd, [в] LT'; + case 3: + case 5: + case 6: + return '[В следующую] dddd, [в] LT'; + } + } else { + if (this.day() === 2) { + return '[Во] dddd, [в] LT'; + } else { + return '[В] dddd, [в] LT'; + } + } + }, + lastWeek: function (now) { + if (now.week() !== this.week()) { + switch (this.day()) { + case 0: + return '[В прошлое] dddd, [в] LT'; + case 1: + case 2: + case 4: + return '[В прошлый] dddd, [в] LT'; + case 3: + case 5: + case 6: + return '[В прошлую] dddd, [в] LT'; + } + } else { + if (this.day() === 2) { + return '[Во] dddd, [в] LT'; + } else { + return '[В] dddd, [в] LT'; + } + } + }, + sameElse: 'L' + }, + relativeTime : { + future : 'через %s', + past : '%s назад', + s : 'несколько секунд', + ss : relativeTimeWithPlural, + m : relativeTimeWithPlural, + mm : relativeTimeWithPlural, + h : 'час', + hh : relativeTimeWithPlural, + d : 'день', + dd : relativeTimeWithPlural, + M : 'месяц', + MM : relativeTimeWithPlural, + y : 'год', + yy : relativeTimeWithPlural + }, + meridiemParse: /ночи|утра|дня|вечера/i, + isPM : function (input) { + return /^(дня|вечера)$/.test(input); + }, + meridiem : function (hour, minute, isLower) { + if (hour < 4) { + return 'ночи'; + } else if (hour < 12) { + return 'утра'; + } else if (hour < 17) { + return 'дня'; + } else { + return 'вечера'; + } + }, + dayOfMonthOrdinalParse: /\d{1,2}-(й|го|я)/, + ordinal: function (number, period) { + switch (period) { + case 'M': + case 'd': + case 'DDD': + return number + '-й'; + case 'D': + return number + '-го'; + case 'w': + case 'W': + return number + '-я'; + default: + return number; + } + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } + }); -exports.lte = lte; -function lte(a, b, loose) { - return compare(a, b, loose) <= 0; -} + return ru; -exports.cmp = cmp; -function cmp(a, op, b, loose) { - var ret; - switch (op) { - case '===': - if (typeof a === 'object') a = a.version; - if (typeof b === 'object') b = b.version; - ret = a === b; - break; - case '!==': - if (typeof a === 'object') a = a.version; - if (typeof b === 'object') b = b.version; - ret = a !== b; - break; - case '': case '=': case '==': ret = eq(a, b, loose); break; - case '!=': ret = neq(a, b, loose); break; - case '>': ret = gt(a, b, loose); break; - case '>=': ret = gte(a, b, loose); break; - case '<': ret = lt(a, b, loose); break; - case '<=': ret = lte(a, b, loose); break; - default: throw new TypeError('Invalid operator: ' + op); - } - return ret; -} +}))); -exports.Comparator = Comparator; -function Comparator(comp, loose) { - if (comp instanceof Comparator) { - if (comp.loose === loose) - return comp; - else - comp = comp.value; - } - if (!(this instanceof Comparator)) - return new Comparator(comp, loose); +/***/ }), +/* 136 */ +/***/ (function(module, exports, __webpack_require__) { - debug('comparator', comp, loose); - this.loose = loose; - this.parse(comp); +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var months = [ + 'جنوري', + 'فيبروري', + 'مارچ', + 'اپريل', + 'مئي', + 'جون', + 'جولاءِ', + 'آگسٽ', + 'سيپٽمبر', + 'آڪٽوبر', + 'نومبر', + 'ڊسمبر' + ]; + var days = [ + 'آچر', + 'سومر', + 'اڱارو', + 'اربع', + 'خميس', + 'جمع', + 'ڇنڇر' + ]; - if (this.semver === ANY) - this.value = ''; - else - this.value = this.operator + this.semver.version; + var sd = moment.defineLocale('sd', { + months : months, + monthsShort : months, + weekdays : days, + weekdaysShort : days, + weekdaysMin : days, + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY HH:mm', + LLLL : 'dddd، D MMMM YYYY HH:mm' + }, + meridiemParse: /صبح|شام/, + isPM : function (input) { + return 'شام' === input; + }, + meridiem : function (hour, minute, isLower) { + if (hour < 12) { + return 'صبح'; + } + return 'شام'; + }, + calendar : { + sameDay : '[اڄ] LT', + nextDay : '[سڀاڻي] LT', + nextWeek : 'dddd [اڳين هفتي تي] LT', + lastDay : '[ڪالهه] LT', + lastWeek : '[گزريل هفتي] dddd [تي] LT', + sameElse : 'L' + }, + relativeTime : { + future : '%s پوء', + past : '%s اڳ', + s : 'چند سيڪنڊ', + ss : '%d سيڪنڊ', + m : 'هڪ منٽ', + mm : '%d منٽ', + h : 'هڪ ڪلاڪ', + hh : '%d ڪلاڪ', + d : 'هڪ ڏينهن', + dd : '%d ڏينهن', + M : 'هڪ مهينو', + MM : '%d مهينا', + y : 'هڪ سال', + yy : '%d سال' + }, + preparse: function (string) { + return string.replace(/،/g, ','); + }, + postformat: function (string) { + return string.replace(/,/g, '،'); + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } + }); - debug('comp', this); -} + return sd; -var ANY = {}; -Comparator.prototype.parse = function(comp) { - var r = this.loose ? re[COMPARATORLOOSE] : re[COMPARATOR]; - var m = comp.match(r); +}))); - if (!m) - throw new TypeError('Invalid comparator: ' + comp); - this.operator = m[1]; - if (this.operator === '=') - this.operator = ''; +/***/ }), +/* 137 */ +/***/ (function(module, exports, __webpack_require__) { - // if it literally is just '>' or '' then allow anything. - if (!m[2]) - this.semver = ANY; - else - this.semver = new SemVer(m[2], this.loose); -}; +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var se = moment.defineLocale('se', { + months : 'ođđajagemánnu_guovvamánnu_njukčamánnu_cuoŋománnu_miessemánnu_geassemánnu_suoidnemánnu_borgemánnu_čakčamánnu_golggotmánnu_skábmamánnu_juovlamánnu'.split('_'), + monthsShort : 'ođđj_guov_njuk_cuo_mies_geas_suoi_borg_čakč_golg_skáb_juov'.split('_'), + weekdays : 'sotnabeaivi_vuossárga_maŋŋebárga_gaskavahkku_duorastat_bearjadat_lávvardat'.split('_'), + weekdaysShort : 'sotn_vuos_maŋ_gask_duor_bear_láv'.split('_'), + weekdaysMin : 's_v_m_g_d_b_L'.split('_'), + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD.MM.YYYY', + LL : 'MMMM D. [b.] YYYY', + LLL : 'MMMM D. [b.] YYYY [ti.] HH:mm', + LLLL : 'dddd, MMMM D. [b.] YYYY [ti.] HH:mm' + }, + calendar : { + sameDay: '[otne ti] LT', + nextDay: '[ihttin ti] LT', + nextWeek: 'dddd [ti] LT', + lastDay: '[ikte ti] LT', + lastWeek: '[ovddit] dddd [ti] LT', + sameElse: 'L' + }, + relativeTime : { + future : '%s geažes', + past : 'maŋit %s', + s : 'moadde sekunddat', + ss: '%d sekunddat', + m : 'okta minuhta', + mm : '%d minuhtat', + h : 'okta diimmu', + hh : '%d diimmut', + d : 'okta beaivi', + dd : '%d beaivvit', + M : 'okta mánnu', + MM : '%d mánut', + y : 'okta jahki', + yy : '%d jagit' + }, + dayOfMonthOrdinalParse: /\d{1,2}\./, + ordinal : '%d.', + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } + }); -Comparator.prototype.toString = function() { - return this.value; -}; + return se; -Comparator.prototype.test = function(version) { - debug('Comparator.test', version, this.loose); +}))); - if (this.semver === ANY) - return true; - if (typeof version === 'string') - version = new SemVer(version, this.loose); +/***/ }), +/* 138 */ +/***/ (function(module, exports, __webpack_require__) { - return cmp(version, this.operator, this.semver, this.loose); -}; +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + /*jshint -W100*/ + var si = moment.defineLocale('si', { + months : 'ජනවාරි_පෙබරවාරි_මාර්තු_අප්‍රේල්_මැයි_ජූනි_ජූලි_අගෝස්තු_සැප්තැම්බර්_ඔක්තෝබර්_නොවැම්බර්_දෙසැම්බර්'.split('_'), + monthsShort : 'ජන_පෙබ_මාර්_අප්_මැයි_ජූනි_ජූලි_අගෝ_සැප්_ඔක්_නොවැ_දෙසැ'.split('_'), + weekdays : 'ඉරිදා_සඳුදා_අඟහරුවාදා_බදාදා_බ්‍රහස්පතින්දා_සිකුරාදා_සෙනසුරාදා'.split('_'), + weekdaysShort : 'ඉරි_සඳු_අඟ_බදා_බ්‍රහ_සිකු_සෙන'.split('_'), + weekdaysMin : 'ඉ_ස_අ_බ_බ්‍ර_සි_සෙ'.split('_'), + weekdaysParseExact : true, + longDateFormat : { + LT : 'a h:mm', + LTS : 'a h:mm:ss', + L : 'YYYY/MM/DD', + LL : 'YYYY MMMM D', + LLL : 'YYYY MMMM D, a h:mm', + LLLL : 'YYYY MMMM D [වැනි] dddd, a h:mm:ss' + }, + calendar : { + sameDay : '[අද] LT[ට]', + nextDay : '[හෙට] LT[ට]', + nextWeek : 'dddd LT[ට]', + lastDay : '[ඊයේ] LT[ට]', + lastWeek : '[පසුගිය] dddd LT[ට]', + sameElse : 'L' + }, + relativeTime : { + future : '%sකින්', + past : '%sකට පෙර', + s : 'තත්පර කිහිපය', + ss : 'තත්පර %d', + m : 'මිනිත්තුව', + mm : 'මිනිත්තු %d', + h : 'පැය', + hh : 'පැය %d', + d : 'දිනය', + dd : 'දින %d', + M : 'මාසය', + MM : 'මාස %d', + y : 'වසර', + yy : 'වසර %d' + }, + dayOfMonthOrdinalParse: /\d{1,2} වැනි/, + ordinal : function (number) { + return number + ' වැනි'; + }, + meridiemParse : /පෙර වරු|පස් වරු|පෙ.ව|ප.ව./, + isPM : function (input) { + return input === 'ප.ව.' || input === 'පස් වරු'; + }, + meridiem : function (hours, minutes, isLower) { + if (hours > 11) { + return isLower ? 'ප.ව.' : 'පස් වරු'; + } else { + return isLower ? 'පෙ.ව.' : 'පෙර වරු'; + } + } + }); -Comparator.prototype.intersects = function(comp, loose) { - if (!(comp instanceof Comparator)) { - throw new TypeError('a Comparator is required'); - } + return si; - var rangeTmp; +}))); - if (this.operator === '') { - rangeTmp = new Range(comp.value, loose); - return satisfies(this.value, rangeTmp, loose); - } else if (comp.operator === '') { - rangeTmp = new Range(this.value, loose); - return satisfies(comp.semver, rangeTmp, loose); - } - var sameDirectionIncreasing = - (this.operator === '>=' || this.operator === '>') && - (comp.operator === '>=' || comp.operator === '>'); - var sameDirectionDecreasing = - (this.operator === '<=' || this.operator === '<') && - (comp.operator === '<=' || comp.operator === '<'); - var sameSemVer = this.semver.version === comp.semver.version; - var differentDirectionsInclusive = - (this.operator === '>=' || this.operator === '<=') && - (comp.operator === '>=' || comp.operator === '<='); - var oppositeDirectionsLessThan = - cmp(this.semver, '<', comp.semver, loose) && - ((this.operator === '>=' || this.operator === '>') && - (comp.operator === '<=' || comp.operator === '<')); - var oppositeDirectionsGreaterThan = - cmp(this.semver, '>', comp.semver, loose) && - ((this.operator === '<=' || this.operator === '<') && - (comp.operator === '>=' || comp.operator === '>')); +/***/ }), +/* 139 */ +/***/ (function(module, exports, __webpack_require__) { - return sameDirectionIncreasing || sameDirectionDecreasing || - (sameSemVer && differentDirectionsInclusive) || - oppositeDirectionsLessThan || oppositeDirectionsGreaterThan; -}; +//! moment.js locale configuration +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; -exports.Range = Range; -function Range(range, loose) { - if (range instanceof Range) { - if (range.loose === loose) { - return range; - } else { - return new Range(range.raw, loose); + + var months = 'január_február_marec_apríl_máj_jún_júl_august_september_október_november_december'.split('_'), + monthsShort = 'jan_feb_mar_apr_máj_jún_júl_aug_sep_okt_nov_dec'.split('_'); + function plural(n) { + return (n > 1) && (n < 5); + } + function translate(number, withoutSuffix, key, isFuture) { + var result = number + ' '; + switch (key) { + case 's': // a few seconds / in a few seconds / a few seconds ago + return (withoutSuffix || isFuture) ? 'pár sekúnd' : 'pár sekundami'; + case 'ss': // 9 seconds / in 9 seconds / 9 seconds ago + if (withoutSuffix || isFuture) { + return result + (plural(number) ? 'sekundy' : 'sekúnd'); + } else { + return result + 'sekundami'; + } + break; + case 'm': // a minute / in a minute / a minute ago + return withoutSuffix ? 'minúta' : (isFuture ? 'minútu' : 'minútou'); + case 'mm': // 9 minutes / in 9 minutes / 9 minutes ago + if (withoutSuffix || isFuture) { + return result + (plural(number) ? 'minúty' : 'minút'); + } else { + return result + 'minútami'; + } + break; + case 'h': // an hour / in an hour / an hour ago + return withoutSuffix ? 'hodina' : (isFuture ? 'hodinu' : 'hodinou'); + case 'hh': // 9 hours / in 9 hours / 9 hours ago + if (withoutSuffix || isFuture) { + return result + (plural(number) ? 'hodiny' : 'hodín'); + } else { + return result + 'hodinami'; + } + break; + case 'd': // a day / in a day / a day ago + return (withoutSuffix || isFuture) ? 'deň' : 'dňom'; + case 'dd': // 9 days / in 9 days / 9 days ago + if (withoutSuffix || isFuture) { + return result + (plural(number) ? 'dni' : 'dní'); + } else { + return result + 'dňami'; + } + break; + case 'M': // a month / in a month / a month ago + return (withoutSuffix || isFuture) ? 'mesiac' : 'mesiacom'; + case 'MM': // 9 months / in 9 months / 9 months ago + if (withoutSuffix || isFuture) { + return result + (plural(number) ? 'mesiace' : 'mesiacov'); + } else { + return result + 'mesiacmi'; + } + break; + case 'y': // a year / in a year / a year ago + return (withoutSuffix || isFuture) ? 'rok' : 'rokom'; + case 'yy': // 9 years / in 9 years / 9 years ago + if (withoutSuffix || isFuture) { + return result + (plural(number) ? 'roky' : 'rokov'); + } else { + return result + 'rokmi'; + } + break; + } } - } - if (range instanceof Comparator) { - return new Range(range.value, loose); - } + var sk = moment.defineLocale('sk', { + months : months, + monthsShort : monthsShort, + weekdays : 'nedeľa_pondelok_utorok_streda_štvrtok_piatok_sobota'.split('_'), + weekdaysShort : 'ne_po_ut_st_št_pi_so'.split('_'), + weekdaysMin : 'ne_po_ut_st_št_pi_so'.split('_'), + longDateFormat : { + LT: 'H:mm', + LTS : 'H:mm:ss', + L : 'DD.MM.YYYY', + LL : 'D. MMMM YYYY', + LLL : 'D. MMMM YYYY H:mm', + LLLL : 'dddd D. MMMM YYYY H:mm' + }, + calendar : { + sameDay: '[dnes o] LT', + nextDay: '[zajtra o] LT', + nextWeek: function () { + switch (this.day()) { + case 0: + return '[v nedeľu o] LT'; + case 1: + case 2: + return '[v] dddd [o] LT'; + case 3: + return '[v stredu o] LT'; + case 4: + return '[vo štvrtok o] LT'; + case 5: + return '[v piatok o] LT'; + case 6: + return '[v sobotu o] LT'; + } + }, + lastDay: '[včera o] LT', + lastWeek: function () { + switch (this.day()) { + case 0: + return '[minulú nedeľu o] LT'; + case 1: + case 2: + return '[minulý] dddd [o] LT'; + case 3: + return '[minulú stredu o] LT'; + case 4: + case 5: + return '[minulý] dddd [o] LT'; + case 6: + return '[minulú sobotu o] LT'; + } + }, + sameElse: 'L' + }, + relativeTime : { + future : 'za %s', + past : 'pred %s', + s : translate, + ss : translate, + m : translate, + mm : translate, + h : translate, + hh : translate, + d : translate, + dd : translate, + M : translate, + MM : translate, + y : translate, + yy : translate + }, + dayOfMonthOrdinalParse: /\d{1,2}\./, + ordinal : '%d.', + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } + }); - if (!(this instanceof Range)) - return new Range(range, loose); + return sk; - this.loose = loose; +}))); - // First, split based on boolean or || - this.raw = range; - this.set = range.split(/\s*\|\|\s*/).map(function(range) { - return this.parseRange(range.trim()); - }, this).filter(function(c) { - // throw out any that are not relevant for whatever reason - return c.length; - }); - if (!this.set.length) { - throw new TypeError('Invalid SemVer Range: ' + range); - } +/***/ }), +/* 140 */ +/***/ (function(module, exports, __webpack_require__) { - this.format(); -} +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + function processRelativeTime(number, withoutSuffix, key, isFuture) { + var result = number + ' '; + switch (key) { + case 's': + return withoutSuffix || isFuture ? 'nekaj sekund' : 'nekaj sekundami'; + case 'ss': + if (number === 1) { + result += withoutSuffix ? 'sekundo' : 'sekundi'; + } else if (number === 2) { + result += withoutSuffix || isFuture ? 'sekundi' : 'sekundah'; + } else if (number < 5) { + result += withoutSuffix || isFuture ? 'sekunde' : 'sekundah'; + } else { + result += 'sekund'; + } + return result; + case 'm': + return withoutSuffix ? 'ena minuta' : 'eno minuto'; + case 'mm': + if (number === 1) { + result += withoutSuffix ? 'minuta' : 'minuto'; + } else if (number === 2) { + result += withoutSuffix || isFuture ? 'minuti' : 'minutama'; + } else if (number < 5) { + result += withoutSuffix || isFuture ? 'minute' : 'minutami'; + } else { + result += withoutSuffix || isFuture ? 'minut' : 'minutami'; + } + return result; + case 'h': + return withoutSuffix ? 'ena ura' : 'eno uro'; + case 'hh': + if (number === 1) { + result += withoutSuffix ? 'ura' : 'uro'; + } else if (number === 2) { + result += withoutSuffix || isFuture ? 'uri' : 'urama'; + } else if (number < 5) { + result += withoutSuffix || isFuture ? 'ure' : 'urami'; + } else { + result += withoutSuffix || isFuture ? 'ur' : 'urami'; + } + return result; + case 'd': + return withoutSuffix || isFuture ? 'en dan' : 'enim dnem'; + case 'dd': + if (number === 1) { + result += withoutSuffix || isFuture ? 'dan' : 'dnem'; + } else if (number === 2) { + result += withoutSuffix || isFuture ? 'dni' : 'dnevoma'; + } else { + result += withoutSuffix || isFuture ? 'dni' : 'dnevi'; + } + return result; + case 'M': + return withoutSuffix || isFuture ? 'en mesec' : 'enim mesecem'; + case 'MM': + if (number === 1) { + result += withoutSuffix || isFuture ? 'mesec' : 'mesecem'; + } else if (number === 2) { + result += withoutSuffix || isFuture ? 'meseca' : 'mesecema'; + } else if (number < 5) { + result += withoutSuffix || isFuture ? 'mesece' : 'meseci'; + } else { + result += withoutSuffix || isFuture ? 'mesecev' : 'meseci'; + } + return result; + case 'y': + return withoutSuffix || isFuture ? 'eno leto' : 'enim letom'; + case 'yy': + if (number === 1) { + result += withoutSuffix || isFuture ? 'leto' : 'letom'; + } else if (number === 2) { + result += withoutSuffix || isFuture ? 'leti' : 'letoma'; + } else if (number < 5) { + result += withoutSuffix || isFuture ? 'leta' : 'leti'; + } else { + result += withoutSuffix || isFuture ? 'let' : 'leti'; + } + return result; + } + } + + var sl = moment.defineLocale('sl', { + months : 'januar_februar_marec_april_maj_junij_julij_avgust_september_oktober_november_december'.split('_'), + monthsShort : 'jan._feb._mar._apr._maj._jun._jul._avg._sep._okt._nov._dec.'.split('_'), + monthsParseExact: true, + weekdays : 'nedelja_ponedeljek_torek_sreda_četrtek_petek_sobota'.split('_'), + weekdaysShort : 'ned._pon._tor._sre._čet._pet._sob.'.split('_'), + weekdaysMin : 'ne_po_to_sr_če_pe_so'.split('_'), + weekdaysParseExact : true, + longDateFormat : { + LT : 'H:mm', + LTS : 'H:mm:ss', + L : 'DD.MM.YYYY', + LL : 'D. MMMM YYYY', + LLL : 'D. MMMM YYYY H:mm', + LLLL : 'dddd, D. MMMM YYYY H:mm' + }, + calendar : { + sameDay : '[danes ob] LT', + nextDay : '[jutri ob] LT', + + nextWeek : function () { + switch (this.day()) { + case 0: + return '[v] [nedeljo] [ob] LT'; + case 3: + return '[v] [sredo] [ob] LT'; + case 6: + return '[v] [soboto] [ob] LT'; + case 1: + case 2: + case 4: + case 5: + return '[v] dddd [ob] LT'; + } + }, + lastDay : '[včeraj ob] LT', + lastWeek : function () { + switch (this.day()) { + case 0: + return '[prejšnjo] [nedeljo] [ob] LT'; + case 3: + return '[prejšnjo] [sredo] [ob] LT'; + case 6: + return '[prejšnjo] [soboto] [ob] LT'; + case 1: + case 2: + case 4: + case 5: + return '[prejšnji] dddd [ob] LT'; + } + }, + sameElse : 'L' + }, + relativeTime : { + future : 'čez %s', + past : 'pred %s', + s : processRelativeTime, + ss : processRelativeTime, + m : processRelativeTime, + mm : processRelativeTime, + h : processRelativeTime, + hh : processRelativeTime, + d : processRelativeTime, + dd : processRelativeTime, + M : processRelativeTime, + MM : processRelativeTime, + y : processRelativeTime, + yy : processRelativeTime + }, + dayOfMonthOrdinalParse: /\d{1,2}\./, + ordinal : '%d.', + week : { + dow : 1, // Monday is the first day of the week. + doy : 7 // The week that contains Jan 7th is the first week of the year. + } + }); -Range.prototype.format = function() { - this.range = this.set.map(function(comps) { - return comps.join(' ').trim(); - }).join('||').trim(); - return this.range; -}; + return sl; -Range.prototype.toString = function() { - return this.range; -}; +}))); -Range.prototype.parseRange = function(range) { - var loose = this.loose; - range = range.trim(); - debug('range', range, loose); - // `1.2.3 - 1.2.4` => `>=1.2.3 <=1.2.4` - var hr = loose ? re[HYPHENRANGELOOSE] : re[HYPHENRANGE]; - range = range.replace(hr, hyphenReplace); - debug('hyphen replace', range); - // `> 1.2.3 < 1.2.5` => `>1.2.3 <1.2.5` - range = range.replace(re[COMPARATORTRIM], comparatorTrimReplace); - debug('comparator trim', range, re[COMPARATORTRIM]); - // `~ 1.2.3` => `~1.2.3` - range = range.replace(re[TILDETRIM], tildeTrimReplace); +/***/ }), +/* 141 */ +/***/ (function(module, exports, __webpack_require__) { - // `^ 1.2.3` => `^1.2.3` - range = range.replace(re[CARETTRIM], caretTrimReplace); +//! moment.js locale configuration - // normalize spaces - range = range.split(/\s+/).join(' '); +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; - // At this point, the range is completely trimmed and - // ready to be split into comparators. - var compRe = loose ? re[COMPARATORLOOSE] : re[COMPARATOR]; - var set = range.split(' ').map(function(comp) { - return parseComparator(comp, loose); - }).join(' ').split(/\s+/); - if (this.loose) { - // in loose mode, throw out any that are not valid comparators - set = set.filter(function(comp) { - return !!comp.match(compRe); + var sq = moment.defineLocale('sq', { + months : 'Janar_Shkurt_Mars_Prill_Maj_Qershor_Korrik_Gusht_Shtator_Tetor_Nëntor_Dhjetor'.split('_'), + monthsShort : 'Jan_Shk_Mar_Pri_Maj_Qer_Kor_Gus_Sht_Tet_Nën_Dhj'.split('_'), + weekdays : 'E Diel_E Hënë_E Martë_E Mërkurë_E Enjte_E Premte_E Shtunë'.split('_'), + weekdaysShort : 'Die_Hën_Mar_Mër_Enj_Pre_Sht'.split('_'), + weekdaysMin : 'D_H_Ma_Më_E_P_Sh'.split('_'), + weekdaysParseExact : true, + meridiemParse: /PD|MD/, + isPM: function (input) { + return input.charAt(0) === 'M'; + }, + meridiem : function (hours, minutes, isLower) { + return hours < 12 ? 'PD' : 'MD'; + }, + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY HH:mm', + LLLL : 'dddd, D MMMM YYYY HH:mm' + }, + calendar : { + sameDay : '[Sot në] LT', + nextDay : '[Nesër në] LT', + nextWeek : 'dddd [në] LT', + lastDay : '[Dje në] LT', + lastWeek : 'dddd [e kaluar në] LT', + sameElse : 'L' + }, + relativeTime : { + future : 'në %s', + past : '%s më parë', + s : 'disa sekonda', + ss : '%d sekonda', + m : 'një minutë', + mm : '%d minuta', + h : 'një orë', + hh : '%d orë', + d : 'një ditë', + dd : '%d ditë', + M : 'një muaj', + MM : '%d muaj', + y : 'një vit', + yy : '%d vite' + }, + dayOfMonthOrdinalParse: /\d{1,2}\./, + ordinal : '%d.', + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } }); - } - set = set.map(function(comp) { - return new Comparator(comp, loose); - }); - - return set; -}; -Range.prototype.intersects = function(range, loose) { - if (!(range instanceof Range)) { - throw new TypeError('a Range is required'); - } + return sq; - return this.set.some(function(thisComparators) { - return thisComparators.every(function(thisComparator) { - return range.set.some(function(rangeComparators) { - return rangeComparators.every(function(rangeComparator) { - return thisComparator.intersects(rangeComparator, loose); - }); - }); - }); - }); -}; +}))); -// Mostly just for testing and legacy API reasons -exports.toComparators = toComparators; -function toComparators(range, loose) { - return new Range(range, loose).set.map(function(comp) { - return comp.map(function(c) { - return c.value; - }).join(' ').trim().split(' '); - }); -} -// comprised of xranges, tildes, stars, and gtlt's at this point. -// already replaced the hyphen ranges -// turn into a set of JUST comparators. -function parseComparator(comp, loose) { - debug('comp', comp); - comp = replaceCarets(comp, loose); - debug('caret', comp); - comp = replaceTildes(comp, loose); - debug('tildes', comp); - comp = replaceXRanges(comp, loose); - debug('xrange', comp); - comp = replaceStars(comp, loose); - debug('stars', comp); - return comp; -} +/***/ }), +/* 142 */ +/***/ (function(module, exports, __webpack_require__) { -function isX(id) { - return !id || id.toLowerCase() === 'x' || id === '*'; -} +//! moment.js locale configuration -// ~, ~> --> * (any, kinda silly) -// ~2, ~2.x, ~2.x.x, ~>2, ~>2.x ~>2.x.x --> >=2.0.0 <3.0.0 -// ~2.0, ~2.0.x, ~>2.0, ~>2.0.x --> >=2.0.0 <2.1.0 -// ~1.2, ~1.2.x, ~>1.2, ~>1.2.x --> >=1.2.0 <1.3.0 -// ~1.2.3, ~>1.2.3 --> >=1.2.3 <1.3.0 -// ~1.2.0, ~>1.2.0 --> >=1.2.0 <1.3.0 -function replaceTildes(comp, loose) { - return comp.trim().split(/\s+/).map(function(comp) { - return replaceTilde(comp, loose); - }).join(' '); -} +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; -function replaceTilde(comp, loose) { - var r = loose ? re[TILDELOOSE] : re[TILDE]; - return comp.replace(r, function(_, M, m, p, pr) { - debug('tilde', comp, _, M, m, p, pr); - var ret; - if (isX(M)) - ret = ''; - else if (isX(m)) - ret = '>=' + M + '.0.0 <' + (+M + 1) + '.0.0'; - else if (isX(p)) - // ~1.2 == >=1.2.0 <1.3.0 - ret = '>=' + M + '.' + m + '.0 <' + M + '.' + (+m + 1) + '.0'; - else if (pr) { - debug('replaceTilde pr', pr); - if (pr.charAt(0) !== '-') - pr = '-' + pr; - ret = '>=' + M + '.' + m + '.' + p + pr + - ' <' + M + '.' + (+m + 1) + '.0'; - } else - // ~1.2.3 == >=1.2.3 <1.3.0 - ret = '>=' + M + '.' + m + '.' + p + - ' <' + M + '.' + (+m + 1) + '.0'; + var translator = { + words: { //Different grammatical cases + ss: ['sekunda', 'sekunde', 'sekundi'], + m: ['jedan minut', 'jedne minute'], + mm: ['minut', 'minute', 'minuta'], + h: ['jedan sat', 'jednog sata'], + hh: ['sat', 'sata', 'sati'], + dd: ['dan', 'dana', 'dana'], + MM: ['mesec', 'meseca', 'meseci'], + yy: ['godina', 'godine', 'godina'] + }, + correctGrammaticalCase: function (number, wordKey) { + return number === 1 ? wordKey[0] : (number >= 2 && number <= 4 ? wordKey[1] : wordKey[2]); + }, + translate: function (number, withoutSuffix, key) { + var wordKey = translator.words[key]; + if (key.length === 1) { + return withoutSuffix ? wordKey[0] : wordKey[1]; + } else { + return number + ' ' + translator.correctGrammaticalCase(number, wordKey); + } + } + }; - debug('tilde return', ret); - return ret; - }); -} + var sr = moment.defineLocale('sr', { + months: 'januar_februar_mart_april_maj_jun_jul_avgust_septembar_oktobar_novembar_decembar'.split('_'), + monthsShort: 'jan._feb._mar._apr._maj_jun_jul_avg._sep._okt._nov._dec.'.split('_'), + monthsParseExact: true, + weekdays: 'nedelja_ponedeljak_utorak_sreda_četvrtak_petak_subota'.split('_'), + weekdaysShort: 'ned._pon._uto._sre._čet._pet._sub.'.split('_'), + weekdaysMin: 'ne_po_ut_sr_če_pe_su'.split('_'), + weekdaysParseExact : true, + longDateFormat: { + LT: 'H:mm', + LTS : 'H:mm:ss', + L: 'DD.MM.YYYY', + LL: 'D. MMMM YYYY', + LLL: 'D. MMMM YYYY H:mm', + LLLL: 'dddd, D. MMMM YYYY H:mm' + }, + calendar: { + sameDay: '[danas u] LT', + nextDay: '[sutra u] LT', + nextWeek: function () { + switch (this.day()) { + case 0: + return '[u] [nedelju] [u] LT'; + case 3: + return '[u] [sredu] [u] LT'; + case 6: + return '[u] [subotu] [u] LT'; + case 1: + case 2: + case 4: + case 5: + return '[u] dddd [u] LT'; + } + }, + lastDay : '[juče u] LT', + lastWeek : function () { + var lastWeekDays = [ + '[prošle] [nedelje] [u] LT', + '[prošlog] [ponedeljka] [u] LT', + '[prošlog] [utorka] [u] LT', + '[prošle] [srede] [u] LT', + '[prošlog] [četvrtka] [u] LT', + '[prošlog] [petka] [u] LT', + '[prošle] [subote] [u] LT' + ]; + return lastWeekDays[this.day()]; + }, + sameElse : 'L' + }, + relativeTime : { + future : 'za %s', + past : 'pre %s', + s : 'nekoliko sekundi', + ss : translator.translate, + m : translator.translate, + mm : translator.translate, + h : translator.translate, + hh : translator.translate, + d : 'dan', + dd : translator.translate, + M : 'mesec', + MM : translator.translate, + y : 'godinu', + yy : translator.translate + }, + dayOfMonthOrdinalParse: /\d{1,2}\./, + ordinal : '%d.', + week : { + dow : 1, // Monday is the first day of the week. + doy : 7 // The week that contains Jan 7th is the first week of the year. + } + }); -// ^ --> * (any, kinda silly) -// ^2, ^2.x, ^2.x.x --> >=2.0.0 <3.0.0 -// ^2.0, ^2.0.x --> >=2.0.0 <3.0.0 -// ^1.2, ^1.2.x --> >=1.2.0 <2.0.0 -// ^1.2.3 --> >=1.2.3 <2.0.0 -// ^1.2.0 --> >=1.2.0 <2.0.0 -function replaceCarets(comp, loose) { - return comp.trim().split(/\s+/).map(function(comp) { - return replaceCaret(comp, loose); - }).join(' '); -} + return sr; -function replaceCaret(comp, loose) { - debug('caret', comp, loose); - var r = loose ? re[CARETLOOSE] : re[CARET]; - return comp.replace(r, function(_, M, m, p, pr) { - debug('caret', comp, _, M, m, p, pr); - var ret; +}))); - if (isX(M)) - ret = ''; - else if (isX(m)) - ret = '>=' + M + '.0.0 <' + (+M + 1) + '.0.0'; - else if (isX(p)) { - if (M === '0') - ret = '>=' + M + '.' + m + '.0 <' + M + '.' + (+m + 1) + '.0'; - else - ret = '>=' + M + '.' + m + '.0 <' + (+M + 1) + '.0.0'; - } else if (pr) { - debug('replaceCaret pr', pr); - if (pr.charAt(0) !== '-') - pr = '-' + pr; - if (M === '0') { - if (m === '0') - ret = '>=' + M + '.' + m + '.' + p + pr + - ' <' + M + '.' + m + '.' + (+p + 1); - else - ret = '>=' + M + '.' + m + '.' + p + pr + - ' <' + M + '.' + (+m + 1) + '.0'; - } else - ret = '>=' + M + '.' + m + '.' + p + pr + - ' <' + (+M + 1) + '.0.0'; - } else { - debug('no pr'); - if (M === '0') { - if (m === '0') - ret = '>=' + M + '.' + m + '.' + p + - ' <' + M + '.' + m + '.' + (+p + 1); - else - ret = '>=' + M + '.' + m + '.' + p + - ' <' + M + '.' + (+m + 1) + '.0'; - } else - ret = '>=' + M + '.' + m + '.' + p + - ' <' + (+M + 1) + '.0.0'; - } - debug('caret return', ret); - return ret; - }); -} +/***/ }), +/* 143 */ +/***/ (function(module, exports, __webpack_require__) { -function replaceXRanges(comp, loose) { - debug('replaceXRanges', comp, loose); - return comp.split(/\s+/).map(function(comp) { - return replaceXRange(comp, loose); - }).join(' '); -} +//! moment.js locale configuration -function replaceXRange(comp, loose) { - comp = comp.trim(); - var r = loose ? re[XRANGELOOSE] : re[XRANGE]; - return comp.replace(r, function(ret, gtlt, M, m, p, pr) { - debug('xRange', comp, ret, gtlt, M, m, p, pr); - var xM = isX(M); - var xm = xM || isX(m); - var xp = xm || isX(p); - var anyX = xp; +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; - if (gtlt === '=' && anyX) - gtlt = ''; - if (xM) { - if (gtlt === '>' || gtlt === '<') { - // nothing is allowed - ret = '<0.0.0'; - } else { - // nothing is forbidden - ret = '*'; - } - } else if (gtlt && anyX) { - // replace X with 0 - if (xm) - m = 0; - if (xp) - p = 0; + var translator = { + words: { //Different grammatical cases + ss: ['секунда', 'секунде', 'секунди'], + m: ['један минут', 'једне минуте'], + mm: ['минут', 'минуте', 'минута'], + h: ['један сат', 'једног сата'], + hh: ['сат', 'сата', 'сати'], + dd: ['дан', 'дана', 'дана'], + MM: ['месец', 'месеца', 'месеци'], + yy: ['година', 'године', 'година'] + }, + correctGrammaticalCase: function (number, wordKey) { + return number === 1 ? wordKey[0] : (number >= 2 && number <= 4 ? wordKey[1] : wordKey[2]); + }, + translate: function (number, withoutSuffix, key) { + var wordKey = translator.words[key]; + if (key.length === 1) { + return withoutSuffix ? wordKey[0] : wordKey[1]; + } else { + return number + ' ' + translator.correctGrammaticalCase(number, wordKey); + } + } + }; - if (gtlt === '>') { - // >1 => >=2.0.0 - // >1.2 => >=1.3.0 - // >1.2.3 => >= 1.2.4 - gtlt = '>='; - if (xm) { - M = +M + 1; - m = 0; - p = 0; - } else if (xp) { - m = +m + 1; - p = 0; + var srCyrl = moment.defineLocale('sr-cyrl', { + months: 'јануар_фебруар_март_април_мај_јун_јул_август_септембар_октобар_новембар_децембар'.split('_'), + monthsShort: 'јан._феб._мар._апр._мај_јун_јул_авг._сеп._окт._нов._дец.'.split('_'), + monthsParseExact: true, + weekdays: 'недеља_понедељак_уторак_среда_четвртак_петак_субота'.split('_'), + weekdaysShort: 'нед._пон._уто._сре._чет._пет._суб.'.split('_'), + weekdaysMin: 'не_по_ут_ср_че_пе_су'.split('_'), + weekdaysParseExact : true, + longDateFormat: { + LT: 'H:mm', + LTS : 'H:mm:ss', + L: 'DD.MM.YYYY', + LL: 'D. MMMM YYYY', + LLL: 'D. MMMM YYYY H:mm', + LLLL: 'dddd, D. MMMM YYYY H:mm' + }, + calendar: { + sameDay: '[данас у] LT', + nextDay: '[сутра у] LT', + nextWeek: function () { + switch (this.day()) { + case 0: + return '[у] [недељу] [у] LT'; + case 3: + return '[у] [среду] [у] LT'; + case 6: + return '[у] [суботу] [у] LT'; + case 1: + case 2: + case 4: + case 5: + return '[у] dddd [у] LT'; + } + }, + lastDay : '[јуче у] LT', + lastWeek : function () { + var lastWeekDays = [ + '[прошле] [недеље] [у] LT', + '[прошлог] [понедељка] [у] LT', + '[прошлог] [уторка] [у] LT', + '[прошле] [среде] [у] LT', + '[прошлог] [четвртка] [у] LT', + '[прошлог] [петка] [у] LT', + '[прошле] [суботе] [у] LT' + ]; + return lastWeekDays[this.day()]; + }, + sameElse : 'L' + }, + relativeTime : { + future : 'за %s', + past : 'пре %s', + s : 'неколико секунди', + ss : translator.translate, + m : translator.translate, + mm : translator.translate, + h : translator.translate, + hh : translator.translate, + d : 'дан', + dd : translator.translate, + M : 'месец', + MM : translator.translate, + y : 'годину', + yy : translator.translate + }, + dayOfMonthOrdinalParse: /\d{1,2}\./, + ordinal : '%d.', + week : { + dow : 1, // Monday is the first day of the week. + doy : 7 // The week that contains Jan 7th is the first week of the year. } - } else if (gtlt === '<=') { - // <=0.7.x is actually <0.8.0, since any 0.7.x should - // pass. Similarly, <=7.x is actually <8.0.0, etc. - gtlt = '<'; - if (xm) - M = +M + 1; - else - m = +m + 1; - } + }); - ret = gtlt + M + '.' + m + '.' + p; - } else if (xm) { - ret = '>=' + M + '.0.0 <' + (+M + 1) + '.0.0'; - } else if (xp) { - ret = '>=' + M + '.' + m + '.0 <' + M + '.' + (+m + 1) + '.0'; - } + return srCyrl; - debug('xRange return', ret); +}))); - return ret; - }); -} -// Because * is AND-ed with everything else in the comparator, -// and '' means "any version", just remove the *s entirely. -function replaceStars(comp, loose) { - debug('replaceStars', comp, loose); - // Looseness is ignored here. star is always as loose as it gets! - return comp.trim().replace(re[STAR], ''); -} +/***/ }), +/* 144 */ +/***/ (function(module, exports, __webpack_require__) { -// This function is passed to string.replace(re[HYPHENRANGE]) -// M, m, patch, prerelease, build -// 1.2 - 3.4.5 => >=1.2.0 <=3.4.5 -// 1.2.3 - 3.4 => >=1.2.0 <3.5.0 Any 3.4.x will do -// 1.2 - 3.4 => >=1.2.0 <3.5.0 -function hyphenReplace($0, - from, fM, fm, fp, fpr, fb, - to, tM, tm, tp, tpr, tb) { +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var ss = moment.defineLocale('ss', { + months : "Bhimbidvwane_Indlovana_Indlov'lenkhulu_Mabasa_Inkhwekhweti_Inhlaba_Kholwane_Ingci_Inyoni_Imphala_Lweti_Ingongoni".split('_'), + monthsShort : 'Bhi_Ina_Inu_Mab_Ink_Inh_Kho_Igc_Iny_Imp_Lwe_Igo'.split('_'), + weekdays : 'Lisontfo_Umsombuluko_Lesibili_Lesitsatfu_Lesine_Lesihlanu_Umgcibelo'.split('_'), + weekdaysShort : 'Lis_Umb_Lsb_Les_Lsi_Lsh_Umg'.split('_'), + weekdaysMin : 'Li_Us_Lb_Lt_Ls_Lh_Ug'.split('_'), + weekdaysParseExact : true, + longDateFormat : { + LT : 'h:mm A', + LTS : 'h:mm:ss A', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY h:mm A', + LLLL : 'dddd, D MMMM YYYY h:mm A' + }, + calendar : { + sameDay : '[Namuhla nga] LT', + nextDay : '[Kusasa nga] LT', + nextWeek : 'dddd [nga] LT', + lastDay : '[Itolo nga] LT', + lastWeek : 'dddd [leliphelile] [nga] LT', + sameElse : 'L' + }, + relativeTime : { + future : 'nga %s', + past : 'wenteka nga %s', + s : 'emizuzwana lomcane', + ss : '%d mzuzwana', + m : 'umzuzu', + mm : '%d emizuzu', + h : 'lihora', + hh : '%d emahora', + d : 'lilanga', + dd : '%d emalanga', + M : 'inyanga', + MM : '%d tinyanga', + y : 'umnyaka', + yy : '%d iminyaka' + }, + meridiemParse: /ekuseni|emini|entsambama|ebusuku/, + meridiem : function (hours, minutes, isLower) { + if (hours < 11) { + return 'ekuseni'; + } else if (hours < 15) { + return 'emini'; + } else if (hours < 19) { + return 'entsambama'; + } else { + return 'ebusuku'; + } + }, + meridiemHour : function (hour, meridiem) { + if (hour === 12) { + hour = 0; + } + if (meridiem === 'ekuseni') { + return hour; + } else if (meridiem === 'emini') { + return hour >= 11 ? hour : hour + 12; + } else if (meridiem === 'entsambama' || meridiem === 'ebusuku') { + if (hour === 0) { + return 0; + } + return hour + 12; + } + }, + dayOfMonthOrdinalParse: /\d{1,2}/, + ordinal : '%d', + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } + }); - if (isX(fM)) - from = ''; - else if (isX(fm)) - from = '>=' + fM + '.0.0'; - else if (isX(fp)) - from = '>=' + fM + '.' + fm + '.0'; - else - from = '>=' + from; + return ss; - if (isX(tM)) - to = ''; - else if (isX(tm)) - to = '<' + (+tM + 1) + '.0.0'; - else if (isX(tp)) - to = '<' + tM + '.' + (+tm + 1) + '.0'; - else if (tpr) - to = '<=' + tM + '.' + tm + '.' + tp + '-' + tpr; - else - to = '<=' + to; +}))); - return (from + ' ' + to).trim(); -} +/***/ }), +/* 145 */ +/***/ (function(module, exports, __webpack_require__) { -// if ANY of the sets match ALL of its comparators, then pass -Range.prototype.test = function(version) { - if (!version) - return false; +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var sv = moment.defineLocale('sv', { + months : 'januari_februari_mars_april_maj_juni_juli_augusti_september_oktober_november_december'.split('_'), + monthsShort : 'jan_feb_mar_apr_maj_jun_jul_aug_sep_okt_nov_dec'.split('_'), + weekdays : 'söndag_måndag_tisdag_onsdag_torsdag_fredag_lördag'.split('_'), + weekdaysShort : 'sön_mån_tis_ons_tor_fre_lör'.split('_'), + weekdaysMin : 'sö_må_ti_on_to_fr_lö'.split('_'), + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'YYYY-MM-DD', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY [kl.] HH:mm', + LLLL : 'dddd D MMMM YYYY [kl.] HH:mm', + lll : 'D MMM YYYY HH:mm', + llll : 'ddd D MMM YYYY HH:mm' + }, + calendar : { + sameDay: '[Idag] LT', + nextDay: '[Imorgon] LT', + lastDay: '[Igår] LT', + nextWeek: '[På] dddd LT', + lastWeek: '[I] dddd[s] LT', + sameElse: 'L' + }, + relativeTime : { + future : 'om %s', + past : 'för %s sedan', + s : 'några sekunder', + ss : '%d sekunder', + m : 'en minut', + mm : '%d minuter', + h : 'en timme', + hh : '%d timmar', + d : 'en dag', + dd : '%d dagar', + M : 'en månad', + MM : '%d månader', + y : 'ett år', + yy : '%d år' + }, + dayOfMonthOrdinalParse: /\d{1,2}(e|a)/, + ordinal : function (number) { + var b = number % 10, + output = (~~(number % 100 / 10) === 1) ? 'e' : + (b === 1) ? 'a' : + (b === 2) ? 'a' : + (b === 3) ? 'e' : 'e'; + return number + output; + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } + }); - if (typeof version === 'string') - version = new SemVer(version, this.loose); + return sv; - for (var i = 0; i < this.set.length; i++) { - if (testSet(this.set[i], version)) - return true; - } - return false; -}; +}))); -function testSet(set, version) { - for (var i = 0; i < set.length; i++) { - if (!set[i].test(version)) - return false; - } - if (version.prerelease.length) { - // Find the set of versions that are allowed to have prereleases - // For example, ^1.2.3-pr.1 desugars to >=1.2.3-pr.1 <2.0.0 - // That should allow `1.2.3-pr.2` to pass. - // However, `1.2.4-alpha.notready` should NOT be allowed, - // even though it's within the range set by the comparators. - for (var i = 0; i < set.length; i++) { - debug(set[i].semver); - if (set[i].semver === ANY) - continue; +/***/ }), +/* 146 */ +/***/ (function(module, exports, __webpack_require__) { - if (set[i].semver.prerelease.length > 0) { - var allowed = set[i].semver; - if (allowed.major === version.major && - allowed.minor === version.minor && - allowed.patch === version.patch) - return true; - } - } +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var sw = moment.defineLocale('sw', { + months : 'Januari_Februari_Machi_Aprili_Mei_Juni_Julai_Agosti_Septemba_Oktoba_Novemba_Desemba'.split('_'), + monthsShort : 'Jan_Feb_Mac_Apr_Mei_Jun_Jul_Ago_Sep_Okt_Nov_Des'.split('_'), + weekdays : 'Jumapili_Jumatatu_Jumanne_Jumatano_Alhamisi_Ijumaa_Jumamosi'.split('_'), + weekdaysShort : 'Jpl_Jtat_Jnne_Jtan_Alh_Ijm_Jmos'.split('_'), + weekdaysMin : 'J2_J3_J4_J5_Al_Ij_J1'.split('_'), + weekdaysParseExact : true, + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD.MM.YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY HH:mm', + LLLL : 'dddd, D MMMM YYYY HH:mm' + }, + calendar : { + sameDay : '[leo saa] LT', + nextDay : '[kesho saa] LT', + nextWeek : '[wiki ijayo] dddd [saat] LT', + lastDay : '[jana] LT', + lastWeek : '[wiki iliyopita] dddd [saat] LT', + sameElse : 'L' + }, + relativeTime : { + future : '%s baadaye', + past : 'tokea %s', + s : 'hivi punde', + ss : 'sekunde %d', + m : 'dakika moja', + mm : 'dakika %d', + h : 'saa limoja', + hh : 'masaa %d', + d : 'siku moja', + dd : 'masiku %d', + M : 'mwezi mmoja', + MM : 'miezi %d', + y : 'mwaka mmoja', + yy : 'miaka %d' + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 7 // The week that contains Jan 7th is the first week of the year. + } + }); - // Version has a -pre, but it's not one of the ones we like. - return false; - } + return sw; - return true; -} +}))); -exports.satisfies = satisfies; -function satisfies(version, range, loose) { - try { - range = new Range(range, loose); - } catch (er) { - return false; - } - return range.test(version); -} -exports.maxSatisfying = maxSatisfying; -function maxSatisfying(versions, range, loose) { - var max = null; - var maxSV = null; - try { - var rangeObj = new Range(range, loose); - } catch (er) { - return null; - } - versions.forEach(function (v) { - if (rangeObj.test(v)) { // satisfies(v, range, loose) - if (!max || maxSV.compare(v) === -1) { // compare(max, v, true) - max = v; - maxSV = new SemVer(max, loose); - } - } - }) - return max; -} +/***/ }), +/* 147 */ +/***/ (function(module, exports, __webpack_require__) { -exports.minSatisfying = minSatisfying; -function minSatisfying(versions, range, loose) { - var min = null; - var minSV = null; - try { - var rangeObj = new Range(range, loose); - } catch (er) { - return null; - } - versions.forEach(function (v) { - if (rangeObj.test(v)) { // satisfies(v, range, loose) - if (!min || minSV.compare(v) === 1) { // compare(min, v, true) - min = v; - minSV = new SemVer(min, loose); - } - } - }) - return min; -} +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var symbolMap = { + '1': '௧', + '2': '௨', + '3': '௩', + '4': '௪', + '5': '௫', + '6': '௬', + '7': '௭', + '8': '௮', + '9': '௯', + '0': '௦' + }, numberMap = { + '௧': '1', + '௨': '2', + '௩': '3', + '௪': '4', + '௫': '5', + '௬': '6', + '௭': '7', + '௮': '8', + '௯': '9', + '௦': '0' + }; + + var ta = moment.defineLocale('ta', { + months : 'ஜனவரி_பிப்ரவரி_மார்ச்_ஏப்ரல்_மே_ஜூன்_ஜூலை_ஆகஸ்ட்_செப்டெம்பர்_அக்டோபர்_நவம்பர்_டிசம்பர்'.split('_'), + monthsShort : 'ஜனவரி_பிப்ரவரி_மார்ச்_ஏப்ரல்_மே_ஜூன்_ஜூலை_ஆகஸ்ட்_செப்டெம்பர்_அக்டோபர்_நவம்பர்_டிசம்பர்'.split('_'), + weekdays : 'ஞாயிற்றுக்கிழமை_திங்கட்கிழமை_செவ்வாய்கிழமை_புதன்கிழமை_வியாழக்கிழமை_வெள்ளிக்கிழமை_சனிக்கிழமை'.split('_'), + weekdaysShort : 'ஞாயிறு_திங்கள்_செவ்வாய்_புதன்_வியாழன்_வெள்ளி_சனி'.split('_'), + weekdaysMin : 'ஞா_தி_செ_பு_வி_வெ_ச'.split('_'), + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY, HH:mm', + LLLL : 'dddd, D MMMM YYYY, HH:mm' + }, + calendar : { + sameDay : '[இன்று] LT', + nextDay : '[நாளை] LT', + nextWeek : 'dddd, LT', + lastDay : '[நேற்று] LT', + lastWeek : '[கடந்த வாரம்] dddd, LT', + sameElse : 'L' + }, + relativeTime : { + future : '%s இல்', + past : '%s முன்', + s : 'ஒரு சில விநாடிகள்', + ss : '%d விநாடிகள்', + m : 'ஒரு நிமிடம்', + mm : '%d நிமிடங்கள்', + h : 'ஒரு மணி நேரம்', + hh : '%d மணி நேரம்', + d : 'ஒரு நாள்', + dd : '%d நாட்கள்', + M : 'ஒரு மாதம்', + MM : '%d மாதங்கள்', + y : 'ஒரு வருடம்', + yy : '%d ஆண்டுகள்' + }, + dayOfMonthOrdinalParse: /\d{1,2}வது/, + ordinal : function (number) { + return number + 'வது'; + }, + preparse: function (string) { + return string.replace(/[௧௨௩௪௫௬௭௮௯௦]/g, function (match) { + return numberMap[match]; + }); + }, + postformat: function (string) { + return string.replace(/\d/g, function (match) { + return symbolMap[match]; + }); + }, + // refer http://ta.wikipedia.org/s/1er1 + meridiemParse: /யாமம்|வைகறை|காலை|நண்பகல்|எற்பாடு|மாலை/, + meridiem : function (hour, minute, isLower) { + if (hour < 2) { + return ' யாமம்'; + } else if (hour < 6) { + return ' வைகறை'; // வைகறை + } else if (hour < 10) { + return ' காலை'; // காலை + } else if (hour < 14) { + return ' நண்பகல்'; // நண்பகல் + } else if (hour < 18) { + return ' எற்பாடு'; // எற்பாடு + } else if (hour < 22) { + return ' மாலை'; // மாலை + } else { + return ' யாமம்'; + } + }, + meridiemHour : function (hour, meridiem) { + if (hour === 12) { + hour = 0; + } + if (meridiem === 'யாமம்') { + return hour < 2 ? hour : hour + 12; + } else if (meridiem === 'வைகறை' || meridiem === 'காலை') { + return hour; + } else if (meridiem === 'நண்பகல்') { + return hour >= 10 ? hour : hour + 12; + } else { + return hour + 12; + } + }, + week : { + dow : 0, // Sunday is the first day of the week. + doy : 6 // The week that contains Jan 6th is the first week of the year. + } + }); -exports.validRange = validRange; -function validRange(range, loose) { - try { - // Return '*' instead of '' so that truthiness works. - // This will throw if it's invalid anyway - return new Range(range, loose).range || '*'; - } catch (er) { - return null; - } -} + return ta; -// Determine if version is less than all the versions possible in the range -exports.ltr = ltr; -function ltr(version, range, loose) { - return outside(version, range, '<', loose); -} +}))); -// Determine if version is greater than all the versions possible in the range. -exports.gtr = gtr; -function gtr(version, range, loose) { - return outside(version, range, '>', loose); -} -exports.outside = outside; -function outside(version, range, hilo, loose) { - version = new SemVer(version, loose); - range = new Range(range, loose); +/***/ }), +/* 148 */ +/***/ (function(module, exports, __webpack_require__) { - var gtfn, ltefn, ltfn, comp, ecomp; - switch (hilo) { - case '>': - gtfn = gt; - ltefn = lte; - ltfn = lt; - comp = '>'; - ecomp = '>='; - break; - case '<': - gtfn = lt; - ltefn = gte; - ltfn = gt; - comp = '<'; - ecomp = '<='; - break; - default: - throw new TypeError('Must provide a hilo val of "<" or ">"'); - } +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var te = moment.defineLocale('te', { + months : 'జనవరి_ఫిబ్రవరి_మార్చి_ఏప్రిల్_మే_జూన్_జులై_ఆగస్టు_సెప్టెంబర్_అక్టోబర్_నవంబర్_డిసెంబర్'.split('_'), + monthsShort : 'జన._ఫిబ్ర._మార్చి_ఏప్రి._మే_జూన్_జులై_ఆగ._సెప్._అక్టో._నవ._డిసె.'.split('_'), + monthsParseExact : true, + weekdays : 'ఆదివారం_సోమవారం_మంగళవారం_బుధవారం_గురువారం_శుక్రవారం_శనివారం'.split('_'), + weekdaysShort : 'ఆది_సోమ_మంగళ_బుధ_గురు_శుక్ర_శని'.split('_'), + weekdaysMin : 'ఆ_సో_మం_బు_గు_శు_శ'.split('_'), + longDateFormat : { + LT : 'A h:mm', + LTS : 'A h:mm:ss', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY, A h:mm', + LLLL : 'dddd, D MMMM YYYY, A h:mm' + }, + calendar : { + sameDay : '[నేడు] LT', + nextDay : '[రేపు] LT', + nextWeek : 'dddd, LT', + lastDay : '[నిన్న] LT', + lastWeek : '[గత] dddd, LT', + sameElse : 'L' + }, + relativeTime : { + future : '%s లో', + past : '%s క్రితం', + s : 'కొన్ని క్షణాలు', + ss : '%d సెకన్లు', + m : 'ఒక నిమిషం', + mm : '%d నిమిషాలు', + h : 'ఒక గంట', + hh : '%d గంటలు', + d : 'ఒక రోజు', + dd : '%d రోజులు', + M : 'ఒక నెల', + MM : '%d నెలలు', + y : 'ఒక సంవత్సరం', + yy : '%d సంవత్సరాలు' + }, + dayOfMonthOrdinalParse : /\d{1,2}వ/, + ordinal : '%dవ', + meridiemParse: /రాత్రి|ఉదయం|మధ్యాహ్నం|సాయంత్రం/, + meridiemHour : function (hour, meridiem) { + if (hour === 12) { + hour = 0; + } + if (meridiem === 'రాత్రి') { + return hour < 4 ? hour : hour + 12; + } else if (meridiem === 'ఉదయం') { + return hour; + } else if (meridiem === 'మధ్యాహ్నం') { + return hour >= 10 ? hour : hour + 12; + } else if (meridiem === 'సాయంత్రం') { + return hour + 12; + } + }, + meridiem : function (hour, minute, isLower) { + if (hour < 4) { + return 'రాత్రి'; + } else if (hour < 10) { + return 'ఉదయం'; + } else if (hour < 17) { + return 'మధ్యాహ్నం'; + } else if (hour < 20) { + return 'సాయంత్రం'; + } else { + return 'రాత్రి'; + } + }, + week : { + dow : 0, // Sunday is the first day of the week. + doy : 6 // The week that contains Jan 6th is the first week of the year. + } + }); - // If it satisifes the range it is not outside - if (satisfies(version, range, loose)) { - return false; - } + return te; - // From now on, variable terms are as if we're in "gtr" mode. - // but note that everything is flipped for the "ltr" function. +}))); - for (var i = 0; i < range.set.length; ++i) { - var comparators = range.set[i]; - var high = null; - var low = null; +/***/ }), +/* 149 */ +/***/ (function(module, exports, __webpack_require__) { - comparators.forEach(function(comparator) { - if (comparator.semver === ANY) { - comparator = new Comparator('>=0.0.0') - } - high = high || comparator; - low = low || comparator; - if (gtfn(comparator.semver, high.semver, loose)) { - high = comparator; - } else if (ltfn(comparator.semver, low.semver, loose)) { - low = comparator; - } +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var tet = moment.defineLocale('tet', { + months : 'Janeiru_Fevereiru_Marsu_Abril_Maiu_Juñu_Jullu_Agustu_Setembru_Outubru_Novembru_Dezembru'.split('_'), + monthsShort : 'Jan_Fev_Mar_Abr_Mai_Jun_Jul_Ago_Set_Out_Nov_Dez'.split('_'), + weekdays : 'Domingu_Segunda_Tersa_Kuarta_Kinta_Sesta_Sabadu'.split('_'), + weekdaysShort : 'Dom_Seg_Ters_Kua_Kint_Sest_Sab'.split('_'), + weekdaysMin : 'Do_Seg_Te_Ku_Ki_Ses_Sa'.split('_'), + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY HH:mm', + LLLL : 'dddd, D MMMM YYYY HH:mm' + }, + calendar : { + sameDay: '[Ohin iha] LT', + nextDay: '[Aban iha] LT', + nextWeek: 'dddd [iha] LT', + lastDay: '[Horiseik iha] LT', + lastWeek: 'dddd [semana kotuk] [iha] LT', + sameElse: 'L' + }, + relativeTime : { + future : 'iha %s', + past : '%s liuba', + s : 'minutu balun', + ss : 'minutu %d', + m : 'minutu ida', + mm : 'minutu %d', + h : 'oras ida', + hh : 'oras %d', + d : 'loron ida', + dd : 'loron %d', + M : 'fulan ida', + MM : 'fulan %d', + y : 'tinan ida', + yy : 'tinan %d' + }, + dayOfMonthOrdinalParse: /\d{1,2}(st|nd|rd|th)/, + ordinal : function (number) { + var b = number % 10, + output = (~~(number % 100 / 10) === 1) ? 'th' : + (b === 1) ? 'st' : + (b === 2) ? 'nd' : + (b === 3) ? 'rd' : 'th'; + return number + output; + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } }); - // If the edge version comparator has a operator then our version - // isn't outside it - if (high.operator === comp || high.operator === ecomp) { - return false; - } + return tet; - // If the lowest version comparator has an operator and our version - // is less than it then it isn't higher than the range - if ((!low.operator || low.operator === comp) && - ltefn(version, low.semver)) { - return false; - } else if (low.operator === ecomp && ltfn(version, low.semver)) { - return false; - } - } - return true; -} +}))); -exports.prerelease = prerelease; -function prerelease(version, loose) { - var parsed = parse(version, loose); - return (parsed && parsed.prerelease.length) ? parsed.prerelease : null; -} -exports.intersects = intersects; -function intersects(r1, r2, loose) { - r1 = new Range(r1, loose) - r2 = new Range(r2, loose) - return r1.intersects(r2) -} +/***/ }), +/* 150 */ +/***/ (function(module, exports, __webpack_require__) { + +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var suffixes = { + 0: '-ум', + 1: '-ум', + 2: '-юм', + 3: '-юм', + 4: '-ум', + 5: '-ум', + 6: '-ум', + 7: '-ум', + 8: '-ум', + 9: '-ум', + 10: '-ум', + 12: '-ум', + 13: '-ум', + 20: '-ум', + 30: '-юм', + 40: '-ум', + 50: '-ум', + 60: '-ум', + 70: '-ум', + 80: '-ум', + 90: '-ум', + 100: '-ум' + }; + + var tg = moment.defineLocale('tg', { + months : 'январ_феврал_март_апрел_май_июн_июл_август_сентябр_октябр_ноябр_декабр'.split('_'), + monthsShort : 'янв_фев_мар_апр_май_июн_июл_авг_сен_окт_ноя_дек'.split('_'), + weekdays : 'якшанбе_душанбе_сешанбе_чоршанбе_панҷшанбе_ҷумъа_шанбе'.split('_'), + weekdaysShort : 'яшб_дшб_сшб_чшб_пшб_ҷум_шнб'.split('_'), + weekdaysMin : 'яш_дш_сш_чш_пш_ҷм_шб'.split('_'), + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY HH:mm', + LLLL : 'dddd, D MMMM YYYY HH:mm' + }, + calendar : { + sameDay : '[Имрӯз соати] LT', + nextDay : '[Пагоҳ соати] LT', + lastDay : '[Дирӯз соати] LT', + nextWeek : 'dddd[и] [ҳафтаи оянда соати] LT', + lastWeek : 'dddd[и] [ҳафтаи гузашта соати] LT', + sameElse : 'L' + }, + relativeTime : { + future : 'баъди %s', + past : '%s пеш', + s : 'якчанд сония', + m : 'як дақиқа', + mm : '%d дақиқа', + h : 'як соат', + hh : '%d соат', + d : 'як рӯз', + dd : '%d рӯз', + M : 'як моҳ', + MM : '%d моҳ', + y : 'як сол', + yy : '%d сол' + }, + meridiemParse: /шаб|субҳ|рӯз|бегоҳ/, + meridiemHour: function (hour, meridiem) { + if (hour === 12) { + hour = 0; + } + if (meridiem === 'шаб') { + return hour < 4 ? hour : hour + 12; + } else if (meridiem === 'субҳ') { + return hour; + } else if (meridiem === 'рӯз') { + return hour >= 11 ? hour : hour + 12; + } else if (meridiem === 'бегоҳ') { + return hour + 12; + } + }, + meridiem: function (hour, minute, isLower) { + if (hour < 4) { + return 'шаб'; + } else if (hour < 11) { + return 'субҳ'; + } else if (hour < 16) { + return 'рӯз'; + } else if (hour < 19) { + return 'бегоҳ'; + } else { + return 'шаб'; + } + }, + dayOfMonthOrdinalParse: /\d{1,2}-(ум|юм)/, + ordinal: function (number) { + var a = number % 10, + b = number >= 100 ? 100 : null; + return number + (suffixes[number] || suffixes[a] || suffixes[b]); + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 7 // The week that contains Jan 1th is the first week of the year. + } + }); + + return tg; + +}))); /***/ }), -/* 76 */ +/* 151 */ /***/ (function(module, exports, __webpack_require__) { -var parse = __webpack_require__(77); -var correct = __webpack_require__(79); +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var th = moment.defineLocale('th', { + months : 'มกราคม_กุมภาพันธ์_มีนาคม_เมษายน_พฤษภาคม_มิถุนายน_กรกฎาคม_สิงหาคม_กันยายน_ตุลาคม_พฤศจิกายน_ธันวาคม'.split('_'), + monthsShort : 'ม.ค._ก.พ._มี.ค._เม.ย._พ.ค._มิ.ย._ก.ค._ส.ค._ก.ย._ต.ค._พ.ย._ธ.ค.'.split('_'), + monthsParseExact: true, + weekdays : 'อาทิตย์_จันทร์_อังคาร_พุธ_พฤหัสบดี_ศุกร์_เสาร์'.split('_'), + weekdaysShort : 'อาทิตย์_จันทร์_อังคาร_พุธ_พฤหัส_ศุกร์_เสาร์'.split('_'), // yes, three characters difference + weekdaysMin : 'อา._จ._อ._พ._พฤ._ศ._ส.'.split('_'), + weekdaysParseExact : true, + longDateFormat : { + LT : 'H:mm', + LTS : 'H:mm:ss', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY เวลา H:mm', + LLLL : 'วันddddที่ D MMMM YYYY เวลา H:mm' + }, + meridiemParse: /ก่อนเที่ยง|หลังเที่ยง/, + isPM: function (input) { + return input === 'หลังเที่ยง'; + }, + meridiem : function (hour, minute, isLower) { + if (hour < 12) { + return 'ก่อนเที่ยง'; + } else { + return 'หลังเที่ยง'; + } + }, + calendar : { + sameDay : '[วันนี้ เวลา] LT', + nextDay : '[พรุ่งนี้ เวลา] LT', + nextWeek : 'dddd[หน้า เวลา] LT', + lastDay : '[เมื่อวานนี้ เวลา] LT', + lastWeek : '[วัน]dddd[ที่แล้ว เวลา] LT', + sameElse : 'L' + }, + relativeTime : { + future : 'อีก %s', + past : '%sที่แล้ว', + s : 'ไม่กี่วินาที', + ss : '%d วินาที', + m : '1 นาที', + mm : '%d นาที', + h : '1 ชั่วโมง', + hh : '%d ชั่วโมง', + d : '1 วัน', + dd : '%d วัน', + M : '1 เดือน', + MM : '%d เดือน', + y : '1 ปี', + yy : '%d ปี' + } + }); -var genericWarning = ( - 'license should be ' + - 'a valid SPDX license expression (without "LicenseRef"), ' + - '"UNLICENSED", or ' + - '"SEE LICENSE IN "' -); + return th; -var fileReferenceRE = /^SEE LICEN[CS]E IN (.+)$/; +}))); -function startsWith(prefix, string) { - return string.slice(0, prefix.length) === prefix; -} -function usesLicenseRef(ast) { - if (ast.hasOwnProperty('license')) { - var license = ast.license; - return ( - startsWith('LicenseRef', license) || - startsWith('DocumentRef', license) - ); - } else { - return ( - usesLicenseRef(ast.left) || - usesLicenseRef(ast.right) - ); - } -} +/***/ }), +/* 152 */ +/***/ (function(module, exports, __webpack_require__) { -module.exports = function(argument) { - var ast; +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var tlPh = moment.defineLocale('tl-ph', { + months : 'Enero_Pebrero_Marso_Abril_Mayo_Hunyo_Hulyo_Agosto_Setyembre_Oktubre_Nobyembre_Disyembre'.split('_'), + monthsShort : 'Ene_Peb_Mar_Abr_May_Hun_Hul_Ago_Set_Okt_Nob_Dis'.split('_'), + weekdays : 'Linggo_Lunes_Martes_Miyerkules_Huwebes_Biyernes_Sabado'.split('_'), + weekdaysShort : 'Lin_Lun_Mar_Miy_Huw_Biy_Sab'.split('_'), + weekdaysMin : 'Li_Lu_Ma_Mi_Hu_Bi_Sab'.split('_'), + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'MM/D/YYYY', + LL : 'MMMM D, YYYY', + LLL : 'MMMM D, YYYY HH:mm', + LLLL : 'dddd, MMMM DD, YYYY HH:mm' + }, + calendar : { + sameDay: 'LT [ngayong araw]', + nextDay: '[Bukas ng] LT', + nextWeek: 'LT [sa susunod na] dddd', + lastDay: 'LT [kahapon]', + lastWeek: 'LT [noong nakaraang] dddd', + sameElse: 'L' + }, + relativeTime : { + future : 'sa loob ng %s', + past : '%s ang nakalipas', + s : 'ilang segundo', + ss : '%d segundo', + m : 'isang minuto', + mm : '%d minuto', + h : 'isang oras', + hh : '%d oras', + d : 'isang araw', + dd : '%d araw', + M : 'isang buwan', + MM : '%d buwan', + y : 'isang taon', + yy : '%d taon' + }, + dayOfMonthOrdinalParse: /\d{1,2}/, + ordinal : function (number) { + return number; + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } + }); - try { - ast = parse(argument); - } catch (e) { - var match - if ( - argument === 'UNLICENSED' || - argument === 'UNLICENCED' - ) { - return { - validForOldPackages: true, - validForNewPackages: true, - unlicensed: true - }; - } else if (match = fileReferenceRE.exec(argument)) { - return { - validForOldPackages: true, - validForNewPackages: true, - inFile: match[1] - }; - } else { - var result = { - validForOldPackages: false, - validForNewPackages: false, - warnings: [genericWarning] - }; - var corrected = correct(argument); - if (corrected) { - result.warnings.push( - 'license is similar to the valid expression "' + corrected + '"' - ); - } - return result; - } - } + return tlPh; - if (usesLicenseRef(ast)) { - return { - validForNewPackages: false, - validForOldPackages: false, - spdx: true, - warnings: [genericWarning] - }; - } else { - return { - validForNewPackages: true, - validForOldPackages: true, - spdx: true - }; - } -}; +}))); /***/ }), -/* 77 */ +/* 153 */ /***/ (function(module, exports, __webpack_require__) { -var parser = __webpack_require__(78).parser +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var numbersNouns = 'pagh_wa’_cha’_wej_loS_vagh_jav_Soch_chorgh_Hut'.split('_'); + + function translateFuture(output) { + var time = output; + time = (output.indexOf('jaj') !== -1) ? + time.slice(0, -3) + 'leS' : + (output.indexOf('jar') !== -1) ? + time.slice(0, -3) + 'waQ' : + (output.indexOf('DIS') !== -1) ? + time.slice(0, -3) + 'nem' : + time + ' pIq'; + return time; + } + + function translatePast(output) { + var time = output; + time = (output.indexOf('jaj') !== -1) ? + time.slice(0, -3) + 'Hu’' : + (output.indexOf('jar') !== -1) ? + time.slice(0, -3) + 'wen' : + (output.indexOf('DIS') !== -1) ? + time.slice(0, -3) + 'ben' : + time + ' ret'; + return time; + } + + function translate(number, withoutSuffix, string, isFuture) { + var numberNoun = numberAsNoun(number); + switch (string) { + case 'ss': + return numberNoun + ' lup'; + case 'mm': + return numberNoun + ' tup'; + case 'hh': + return numberNoun + ' rep'; + case 'dd': + return numberNoun + ' jaj'; + case 'MM': + return numberNoun + ' jar'; + case 'yy': + return numberNoun + ' DIS'; + } + } + + function numberAsNoun(number) { + var hundred = Math.floor((number % 1000) / 100), + ten = Math.floor((number % 100) / 10), + one = number % 10, + word = ''; + if (hundred > 0) { + word += numbersNouns[hundred] + 'vatlh'; + } + if (ten > 0) { + word += ((word !== '') ? ' ' : '') + numbersNouns[ten] + 'maH'; + } + if (one > 0) { + word += ((word !== '') ? ' ' : '') + numbersNouns[one]; + } + return (word === '') ? 'pagh' : word; + } + + var tlh = moment.defineLocale('tlh', { + months : 'tera’ jar wa’_tera’ jar cha’_tera’ jar wej_tera’ jar loS_tera’ jar vagh_tera’ jar jav_tera’ jar Soch_tera’ jar chorgh_tera’ jar Hut_tera’ jar wa’maH_tera’ jar wa’maH wa’_tera’ jar wa’maH cha’'.split('_'), + monthsShort : 'jar wa’_jar cha’_jar wej_jar loS_jar vagh_jar jav_jar Soch_jar chorgh_jar Hut_jar wa’maH_jar wa’maH wa’_jar wa’maH cha’'.split('_'), + monthsParseExact : true, + weekdays : 'lojmItjaj_DaSjaj_povjaj_ghItlhjaj_loghjaj_buqjaj_ghInjaj'.split('_'), + weekdaysShort : 'lojmItjaj_DaSjaj_povjaj_ghItlhjaj_loghjaj_buqjaj_ghInjaj'.split('_'), + weekdaysMin : 'lojmItjaj_DaSjaj_povjaj_ghItlhjaj_loghjaj_buqjaj_ghInjaj'.split('_'), + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD.MM.YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY HH:mm', + LLLL : 'dddd, D MMMM YYYY HH:mm' + }, + calendar : { + sameDay: '[DaHjaj] LT', + nextDay: '[wa’leS] LT', + nextWeek: 'LLL', + lastDay: '[wa’Hu’] LT', + lastWeek: 'LLL', + sameElse: 'L' + }, + relativeTime : { + future : translateFuture, + past : translatePast, + s : 'puS lup', + ss : translate, + m : 'wa’ tup', + mm : translate, + h : 'wa’ rep', + hh : translate, + d : 'wa’ jaj', + dd : translate, + M : 'wa’ jar', + MM : translate, + y : 'wa’ DIS', + yy : translate + }, + dayOfMonthOrdinalParse: /\d{1,2}\./, + ordinal : '%d.', + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } + }); + + return tlh; -module.exports = function (argument) { - return parser.parse(argument) -} +}))); /***/ }), -/* 78 */ +/* 154 */ /***/ (function(module, exports, __webpack_require__) { -/* WEBPACK VAR INJECTION */(function(module) {/* parser generated by jison 0.4.17 */ -/* - Returns a Parser object of the following structure: - Parser: { - yy: {} - } +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + var suffixes = { + 1: '\'inci', + 5: '\'inci', + 8: '\'inci', + 70: '\'inci', + 80: '\'inci', + 2: '\'nci', + 7: '\'nci', + 20: '\'nci', + 50: '\'nci', + 3: '\'üncü', + 4: '\'üncü', + 100: '\'üncü', + 6: '\'ncı', + 9: '\'uncu', + 10: '\'uncu', + 30: '\'uncu', + 60: '\'ıncı', + 90: '\'ıncı' + }; + + var tr = moment.defineLocale('tr', { + months : 'Ocak_Şubat_Mart_Nisan_Mayıs_Haziran_Temmuz_Ağustos_Eylül_Ekim_Kasım_Aralık'.split('_'), + monthsShort : 'Oca_Şub_Mar_Nis_May_Haz_Tem_Ağu_Eyl_Eki_Kas_Ara'.split('_'), + weekdays : 'Pazar_Pazartesi_Salı_Çarşamba_Perşembe_Cuma_Cumartesi'.split('_'), + weekdaysShort : 'Paz_Pts_Sal_Çar_Per_Cum_Cts'.split('_'), + weekdaysMin : 'Pz_Pt_Sa_Ça_Pe_Cu_Ct'.split('_'), + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD.MM.YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY HH:mm', + LLLL : 'dddd, D MMMM YYYY HH:mm' + }, + calendar : { + sameDay : '[bugün saat] LT', + nextDay : '[yarın saat] LT', + nextWeek : '[gelecek] dddd [saat] LT', + lastDay : '[dün] LT', + lastWeek : '[geçen] dddd [saat] LT', + sameElse : 'L' + }, + relativeTime : { + future : '%s sonra', + past : '%s önce', + s : 'birkaç saniye', + ss : '%d saniye', + m : 'bir dakika', + mm : '%d dakika', + h : 'bir saat', + hh : '%d saat', + d : 'bir gün', + dd : '%d gün', + M : 'bir ay', + MM : '%d ay', + y : 'bir yıl', + yy : '%d yıl' + }, + ordinal: function (number, period) { + switch (period) { + case 'd': + case 'D': + case 'Do': + case 'DD': + return number; + default: + if (number === 0) { // special case for zero + return number + '\'ıncı'; + } + var a = number % 10, + b = number % 100 - a, + c = number >= 100 ? 100 : null; + return number + (suffixes[a] || suffixes[b] || suffixes[c]); + } + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 7 // The week that contains Jan 7th is the first week of the year. + } + }); - Parser.prototype: { - yy: {}, - trace: function(), - symbols_: {associative list: name ==> number}, - terminals_: {associative list: number ==> name}, - productions_: [...], - performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate, $$, _$), - table: [...], - defaultActions: {...}, - parseError: function(str, hash), - parse: function(input), + return tr; - lexer: { - EOF: 1, - parseError: function(str, hash), - setInput: function(input), - input: function(), - unput: function(str), - more: function(), - less: function(n), - pastInput: function(), - upcomingInput: function(), - showPosition: function(), - test_match: function(regex_match_array, rule_index), - next: function(), - lex: function(), - begin: function(condition), - popState: function(), - _currentRules: function(), - topState: function(), - pushState: function(condition), +}))); - options: { - ranges: boolean (optional: true ==> token location info will include a .range[] member) - flex: boolean (optional: true ==> flex-like lexing behaviour where the rules are tested exhaustively to find the longest match) - backtrack_lexer: boolean (optional: true ==> lexer regexes are tested in order and for each matching regex the action code is invoked; the lexer terminates the scan when a token is returned by the action code) + +/***/ }), +/* 155 */ +/***/ (function(module, exports, __webpack_require__) { + +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + // After the year there should be a slash and the amount of years since December 26, 1979 in Roman numerals. + // This is currently too difficult (maybe even impossible) to add. + var tzl = moment.defineLocale('tzl', { + months : 'Januar_Fevraglh_Març_Avrïu_Mai_Gün_Julia_Guscht_Setemvar_Listopäts_Noemvar_Zecemvar'.split('_'), + monthsShort : 'Jan_Fev_Mar_Avr_Mai_Gün_Jul_Gus_Set_Lis_Noe_Zec'.split('_'), + weekdays : 'Súladi_Lúneçi_Maitzi_Márcuri_Xhúadi_Viénerçi_Sáturi'.split('_'), + weekdaysShort : 'Súl_Lún_Mai_Már_Xhú_Vié_Sát'.split('_'), + weekdaysMin : 'Sú_Lú_Ma_Má_Xh_Vi_Sá'.split('_'), + longDateFormat : { + LT : 'HH.mm', + LTS : 'HH.mm.ss', + L : 'DD.MM.YYYY', + LL : 'D. MMMM [dallas] YYYY', + LLL : 'D. MMMM [dallas] YYYY HH.mm', + LLLL : 'dddd, [li] D. MMMM [dallas] YYYY HH.mm' + }, + meridiemParse: /d\'o|d\'a/i, + isPM : function (input) { + return 'd\'o' === input.toLowerCase(); }, + meridiem : function (hours, minutes, isLower) { + if (hours > 11) { + return isLower ? 'd\'o' : 'D\'O'; + } else { + return isLower ? 'd\'a' : 'D\'A'; + } + }, + calendar : { + sameDay : '[oxhi à] LT', + nextDay : '[demà à] LT', + nextWeek : 'dddd [à] LT', + lastDay : '[ieiri à] LT', + lastWeek : '[sür el] dddd [lasteu à] LT', + sameElse : 'L' + }, + relativeTime : { + future : 'osprei %s', + past : 'ja%s', + s : processRelativeTime, + ss : processRelativeTime, + m : processRelativeTime, + mm : processRelativeTime, + h : processRelativeTime, + hh : processRelativeTime, + d : processRelativeTime, + dd : processRelativeTime, + M : processRelativeTime, + MM : processRelativeTime, + y : processRelativeTime, + yy : processRelativeTime + }, + dayOfMonthOrdinalParse: /\d{1,2}\./, + ordinal : '%d.', + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. + } + }); - performAction: function(yy, yy_, $avoiding_name_collisions, YY_START), - rules: [...], - conditions: {associative list: name ==> set}, + function processRelativeTime(number, withoutSuffix, key, isFuture) { + var format = { + 's': ['viensas secunds', '\'iensas secunds'], + 'ss': [number + ' secunds', '' + number + ' secunds'], + 'm': ['\'n míut', '\'iens míut'], + 'mm': [number + ' míuts', '' + number + ' míuts'], + 'h': ['\'n þora', '\'iensa þora'], + 'hh': [number + ' þoras', '' + number + ' þoras'], + 'd': ['\'n ziua', '\'iensa ziua'], + 'dd': [number + ' ziuas', '' + number + ' ziuas'], + 'M': ['\'n mes', '\'iens mes'], + 'MM': [number + ' mesen', '' + number + ' mesen'], + 'y': ['\'n ar', '\'iens ar'], + 'yy': [number + ' ars', '' + number + ' ars'] + }; + return isFuture ? format[key][0] : (withoutSuffix ? format[key][0] : format[key][1]); } - } + return tzl; - token location info (@$, _$, etc.): { - first_line: n, - last_line: n, - first_column: n, - last_column: n, - range: [start_number, end_number] (where the numbers are indexes into the input string, regular zero-based) - } +}))); - the parseError function receives a 'hash' object with these members for lexer and parser errors: { - text: (matched text) - token: (the produced terminal token, if any) - line: (yylineno) - } - while parser (grammar) errors will also provide these members, i.e. parser errors deliver a superset of attributes: { - loc: (yylloc) - expected: (string describing the set of expected tokens) - recoverable: (boolean: TRUE when the parser has a error recovery rule available for this particular error) - } -*/ -var spdxparse = (function(){ -var o=function(k,v,o,l){for(o=o||{},l=k.length;l--;o[k[l]]=v);return o},$V0=[1,5],$V1=[1,6],$V2=[1,7],$V3=[1,4],$V4=[1,9],$V5=[1,10],$V6=[5,14,15,17],$V7=[5,12,14,15,17]; -var parser = {trace: function trace() { }, -yy: {}, -symbols_: {"error":2,"start":3,"expression":4,"EOS":5,"simpleExpression":6,"LICENSE":7,"PLUS":8,"LICENSEREF":9,"DOCUMENTREF":10,"COLON":11,"WITH":12,"EXCEPTION":13,"AND":14,"OR":15,"OPEN":16,"CLOSE":17,"$accept":0,"$end":1}, -terminals_: {2:"error",5:"EOS",7:"LICENSE",8:"PLUS",9:"LICENSEREF",10:"DOCUMENTREF",11:"COLON",12:"WITH",13:"EXCEPTION",14:"AND",15:"OR",16:"OPEN",17:"CLOSE"}, -productions_: [0,[3,2],[6,1],[6,2],[6,1],[6,3],[4,1],[4,3],[4,3],[4,3],[4,3]], -performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate /* action[1] */, $$ /* vstack */, _$ /* lstack */) { -/* this == yyval */ +/***/ }), +/* 156 */ +/***/ (function(module, exports, __webpack_require__) { -var $0 = $$.length - 1; -switch (yystate) { -case 1: -return this.$ = $$[$0-1] -break; -case 2: case 4: case 5: -this.$ = {license: yytext} -break; -case 3: -this.$ = {license: $$[$0-1], plus: true} -break; -case 6: -this.$ = $$[$0] -break; -case 7: -this.$ = {exception: $$[$0]} -this.$.license = $$[$0-2].license -if ($$[$0-2].hasOwnProperty('plus')) { - this.$.plus = $$[$0-2].plus -} -break; -case 8: -this.$ = {conjunction: 'and', left: $$[$0-2], right: $$[$0]} -break; -case 9: -this.$ = {conjunction: 'or', left: $$[$0-2], right: $$[$0]} -break; -case 10: -this.$ = $$[$0-1] -break; -} -}, -table: [{3:1,4:2,6:3,7:$V0,9:$V1,10:$V2,16:$V3},{1:[3]},{5:[1,8],14:$V4,15:$V5},o($V6,[2,6],{12:[1,11]}),{4:12,6:3,7:$V0,9:$V1,10:$V2,16:$V3},o($V7,[2,2],{8:[1,13]}),o($V7,[2,4]),{11:[1,14]},{1:[2,1]},{4:15,6:3,7:$V0,9:$V1,10:$V2,16:$V3},{4:16,6:3,7:$V0,9:$V1,10:$V2,16:$V3},{13:[1,17]},{14:$V4,15:$V5,17:[1,18]},o($V7,[2,3]),{9:[1,19]},o($V6,[2,8]),o([5,15,17],[2,9],{14:$V4}),o($V6,[2,7]),o($V6,[2,10]),o($V7,[2,5])], -defaultActions: {8:[2,1]}, -parseError: function parseError(str, hash) { - if (hash.recoverable) { - this.trace(str); - } else { - function _parseError (msg, hash) { - this.message = msg; - this.hash = hash; +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var tzm = moment.defineLocale('tzm', { + months : 'ⵉⵏⵏⴰⵢⵔ_ⴱⵕⴰⵢⵕ_ⵎⴰⵕⵚ_ⵉⴱⵔⵉⵔ_ⵎⴰⵢⵢⵓ_ⵢⵓⵏⵢⵓ_ⵢⵓⵍⵢⵓⵣ_ⵖⵓⵛⵜ_ⵛⵓⵜⴰⵏⴱⵉⵔ_ⴽⵟⵓⴱⵕ_ⵏⵓⵡⴰⵏⴱⵉⵔ_ⴷⵓⵊⵏⴱⵉⵔ'.split('_'), + monthsShort : 'ⵉⵏⵏⴰⵢⵔ_ⴱⵕⴰⵢⵕ_ⵎⴰⵕⵚ_ⵉⴱⵔⵉⵔ_ⵎⴰⵢⵢⵓ_ⵢⵓⵏⵢⵓ_ⵢⵓⵍⵢⵓⵣ_ⵖⵓⵛⵜ_ⵛⵓⵜⴰⵏⴱⵉⵔ_ⴽⵟⵓⴱⵕ_ⵏⵓⵡⴰⵏⴱⵉⵔ_ⴷⵓⵊⵏⴱⵉⵔ'.split('_'), + weekdays : 'ⴰⵙⴰⵎⴰⵙ_ⴰⵢⵏⴰⵙ_ⴰⵙⵉⵏⴰⵙ_ⴰⴽⵔⴰⵙ_ⴰⴽⵡⴰⵙ_ⴰⵙⵉⵎⵡⴰⵙ_ⴰⵙⵉⴹⵢⴰⵙ'.split('_'), + weekdaysShort : 'ⴰⵙⴰⵎⴰⵙ_ⴰⵢⵏⴰⵙ_ⴰⵙⵉⵏⴰⵙ_ⴰⴽⵔⴰⵙ_ⴰⴽⵡⴰⵙ_ⴰⵙⵉⵎⵡⴰⵙ_ⴰⵙⵉⴹⵢⴰⵙ'.split('_'), + weekdaysMin : 'ⴰⵙⴰⵎⴰⵙ_ⴰⵢⵏⴰⵙ_ⴰⵙⵉⵏⴰⵙ_ⴰⴽⵔⴰⵙ_ⴰⴽⵡⴰⵙ_ⴰⵙⵉⵎⵡⴰⵙ_ⴰⵙⵉⴹⵢⴰⵙ'.split('_'), + longDateFormat : { + LT : 'HH:mm', + LTS: 'HH:mm:ss', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY HH:mm', + LLLL : 'dddd D MMMM YYYY HH:mm' + }, + calendar : { + sameDay: '[ⴰⵙⴷⵅ ⴴ] LT', + nextDay: '[ⴰⵙⴽⴰ ⴴ] LT', + nextWeek: 'dddd [ⴴ] LT', + lastDay: '[ⴰⵚⴰⵏⵜ ⴴ] LT', + lastWeek: 'dddd [ⴴ] LT', + sameElse: 'L' + }, + relativeTime : { + future : 'ⴷⴰⴷⵅ ⵙ ⵢⴰⵏ %s', + past : 'ⵢⴰⵏ %s', + s : 'ⵉⵎⵉⴽ', + ss : '%d ⵉⵎⵉⴽ', + m : 'ⵎⵉⵏⵓⴺ', + mm : '%d ⵎⵉⵏⵓⴺ', + h : 'ⵙⴰⵄⴰ', + hh : '%d ⵜⴰⵙⵙⴰⵄⵉⵏ', + d : 'ⴰⵙⵙ', + dd : '%d oⵙⵙⴰⵏ', + M : 'ⴰⵢoⵓⵔ', + MM : '%d ⵉⵢⵢⵉⵔⵏ', + y : 'ⴰⵙⴳⴰⵙ', + yy : '%d ⵉⵙⴳⴰⵙⵏ' + }, + week : { + dow : 6, // Saturday is the first day of the week. + doy : 12 // The week that contains Jan 12th is the first week of the year. } - _parseError.prototype = Error; + }); - throw new _parseError(str, hash); - } -}, -parse: function parse(input) { - var self = this, stack = [0], tstack = [], vstack = [null], lstack = [], table = this.table, yytext = '', yylineno = 0, yyleng = 0, recovering = 0, TERROR = 2, EOF = 1; - var args = lstack.slice.call(arguments, 1); - var lexer = Object.create(this.lexer); - var sharedState = { yy: {} }; - for (var k in this.yy) { - if (Object.prototype.hasOwnProperty.call(this.yy, k)) { - sharedState.yy[k] = this.yy[k]; + return tzm; + +}))); + + +/***/ }), +/* 157 */ +/***/ (function(module, exports, __webpack_require__) { + +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var tzmLatn = moment.defineLocale('tzm-latn', { + months : 'innayr_brˤayrˤ_marˤsˤ_ibrir_mayyw_ywnyw_ywlywz_ɣwšt_šwtanbir_ktˤwbrˤ_nwwanbir_dwjnbir'.split('_'), + monthsShort : 'innayr_brˤayrˤ_marˤsˤ_ibrir_mayyw_ywnyw_ywlywz_ɣwšt_šwtanbir_ktˤwbrˤ_nwwanbir_dwjnbir'.split('_'), + weekdays : 'asamas_aynas_asinas_akras_akwas_asimwas_asiḍyas'.split('_'), + weekdaysShort : 'asamas_aynas_asinas_akras_akwas_asimwas_asiḍyas'.split('_'), + weekdaysMin : 'asamas_aynas_asinas_akras_akwas_asimwas_asiḍyas'.split('_'), + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY HH:mm', + LLLL : 'dddd D MMMM YYYY HH:mm' + }, + calendar : { + sameDay: '[asdkh g] LT', + nextDay: '[aska g] LT', + nextWeek: 'dddd [g] LT', + lastDay: '[assant g] LT', + lastWeek: 'dddd [g] LT', + sameElse: 'L' + }, + relativeTime : { + future : 'dadkh s yan %s', + past : 'yan %s', + s : 'imik', + ss : '%d imik', + m : 'minuḍ', + mm : '%d minuḍ', + h : 'saɛa', + hh : '%d tassaɛin', + d : 'ass', + dd : '%d ossan', + M : 'ayowr', + MM : '%d iyyirn', + y : 'asgas', + yy : '%d isgasn' + }, + week : { + dow : 6, // Saturday is the first day of the week. + doy : 12 // The week that contains Jan 12th is the first week of the year. } + }); + + return tzmLatn; + +}))); + + +/***/ }), +/* 158 */ +/***/ (function(module, exports, __webpack_require__) { + +//! moment.js language configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var ugCn = moment.defineLocale('ug-cn', { + months: 'يانۋار_فېۋرال_مارت_ئاپرېل_ماي_ئىيۇن_ئىيۇل_ئاۋغۇست_سېنتەبىر_ئۆكتەبىر_نويابىر_دېكابىر'.split( + '_' + ), + monthsShort: 'يانۋار_فېۋرال_مارت_ئاپرېل_ماي_ئىيۇن_ئىيۇل_ئاۋغۇست_سېنتەبىر_ئۆكتەبىر_نويابىر_دېكابىر'.split( + '_' + ), + weekdays: 'يەكشەنبە_دۈشەنبە_سەيشەنبە_چارشەنبە_پەيشەنبە_جۈمە_شەنبە'.split( + '_' + ), + weekdaysShort: 'يە_دۈ_سە_چا_پە_جۈ_شە'.split('_'), + weekdaysMin: 'يە_دۈ_سە_چا_پە_جۈ_شە'.split('_'), + longDateFormat: { + LT: 'HH:mm', + LTS: 'HH:mm:ss', + L: 'YYYY-MM-DD', + LL: 'YYYY-يىلىM-ئاينىڭD-كۈنى', + LLL: 'YYYY-يىلىM-ئاينىڭD-كۈنى، HH:mm', + LLLL: 'dddd، YYYY-يىلىM-ئاينىڭD-كۈنى، HH:mm' + }, + meridiemParse: /يېرىم كېچە|سەھەر|چۈشتىن بۇرۇن|چۈش|چۈشتىن كېيىن|كەچ/, + meridiemHour: function (hour, meridiem) { + if (hour === 12) { + hour = 0; + } + if ( + meridiem === 'يېرىم كېچە' || + meridiem === 'سەھەر' || + meridiem === 'چۈشتىن بۇرۇن' + ) { + return hour; + } else if (meridiem === 'چۈشتىن كېيىن' || meridiem === 'كەچ') { + return hour + 12; + } else { + return hour >= 11 ? hour : hour + 12; + } + }, + meridiem: function (hour, minute, isLower) { + var hm = hour * 100 + minute; + if (hm < 600) { + return 'يېرىم كېچە'; + } else if (hm < 900) { + return 'سەھەر'; + } else if (hm < 1130) { + return 'چۈشتىن بۇرۇن'; + } else if (hm < 1230) { + return 'چۈش'; + } else if (hm < 1800) { + return 'چۈشتىن كېيىن'; + } else { + return 'كەچ'; + } + }, + calendar: { + sameDay: '[بۈگۈن سائەت] LT', + nextDay: '[ئەتە سائەت] LT', + nextWeek: '[كېلەركى] dddd [سائەت] LT', + lastDay: '[تۆنۈگۈن] LT', + lastWeek: '[ئالدىنقى] dddd [سائەت] LT', + sameElse: 'L' + }, + relativeTime: { + future: '%s كېيىن', + past: '%s بۇرۇن', + s: 'نەچچە سېكونت', + ss: '%d سېكونت', + m: 'بىر مىنۇت', + mm: '%d مىنۇت', + h: 'بىر سائەت', + hh: '%d سائەت', + d: 'بىر كۈن', + dd: '%d كۈن', + M: 'بىر ئاي', + MM: '%d ئاي', + y: 'بىر يىل', + yy: '%d يىل' + }, + + dayOfMonthOrdinalParse: /\d{1,2}(-كۈنى|-ئاي|-ھەپتە)/, + ordinal: function (number, period) { + switch (period) { + case 'd': + case 'D': + case 'DDD': + return number + '-كۈنى'; + case 'w': + case 'W': + return number + '-ھەپتە'; + default: + return number; + } + }, + preparse: function (string) { + return string.replace(/،/g, ','); + }, + postformat: function (string) { + return string.replace(/,/g, '،'); + }, + week: { + // GB/T 7408-1994《数据元和交换格式·信息交换·日期和时间表示法》与ISO 8601:1988等效 + dow: 1, // Monday is the first day of the week. + doy: 7 // The week that contains Jan 1st is the first week of the year. + } + }); + + return ugCn; + +}))); + + +/***/ }), +/* 159 */ +/***/ (function(module, exports, __webpack_require__) { + +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + function plural(word, num) { + var forms = word.split('_'); + return num % 10 === 1 && num % 100 !== 11 ? forms[0] : (num % 10 >= 2 && num % 10 <= 4 && (num % 100 < 10 || num % 100 >= 20) ? forms[1] : forms[2]); } - lexer.setInput(input, sharedState.yy); - sharedState.yy.lexer = lexer; - sharedState.yy.parser = this; - if (typeof lexer.yylloc == 'undefined') { - lexer.yylloc = {}; - } - var yyloc = lexer.yylloc; - lstack.push(yyloc); - var ranges = lexer.options && lexer.options.ranges; - if (typeof sharedState.yy.parseError === 'function') { - this.parseError = sharedState.yy.parseError; - } else { - this.parseError = Object.getPrototypeOf(this).parseError; + function relativeTimeWithPlural(number, withoutSuffix, key) { + var format = { + 'ss': withoutSuffix ? 'секунда_секунди_секунд' : 'секунду_секунди_секунд', + 'mm': withoutSuffix ? 'хвилина_хвилини_хвилин' : 'хвилину_хвилини_хвилин', + 'hh': withoutSuffix ? 'година_години_годин' : 'годину_години_годин', + 'dd': 'день_дні_днів', + 'MM': 'місяць_місяці_місяців', + 'yy': 'рік_роки_років' + }; + if (key === 'm') { + return withoutSuffix ? 'хвилина' : 'хвилину'; + } + else if (key === 'h') { + return withoutSuffix ? 'година' : 'годину'; + } + else { + return number + ' ' + plural(format[key], +number); + } } - function popStack(n) { - stack.length = stack.length - 2 * n; - vstack.length = vstack.length - n; - lstack.length = lstack.length - n; + function weekdaysCaseReplace(m, format) { + var weekdays = { + 'nominative': 'неділя_понеділок_вівторок_середа_четвер_п’ятниця_субота'.split('_'), + 'accusative': 'неділю_понеділок_вівторок_середу_четвер_п’ятницю_суботу'.split('_'), + 'genitive': 'неділі_понеділка_вівторка_середи_четверга_п’ятниці_суботи'.split('_') + }; + + if (m === true) { + return weekdays['nominative'].slice(1, 7).concat(weekdays['nominative'].slice(0, 1)); + } + if (!m) { + return weekdays['nominative']; + } + + var nounCase = (/(\[[ВвУу]\]) ?dddd/).test(format) ? + 'accusative' : + ((/\[?(?:минулої|наступної)? ?\] ?dddd/).test(format) ? + 'genitive' : + 'nominative'); + return weekdays[nounCase][m.day()]; } - _token_stack: - var lex = function () { - var token; - token = lexer.lex() || EOF; - if (typeof token !== 'number') { - token = self.symbols_[token] || token; - } - return token; + function processHoursFunction(str) { + return function () { + return str + 'о' + (this.hours() === 11 ? 'б' : '') + '] LT'; }; - var symbol, preErrorSymbol, state, action, a, r, yyval = {}, p, len, newState, expected; - while (true) { - state = stack[stack.length - 1]; - if (this.defaultActions[state]) { - action = this.defaultActions[state]; - } else { - if (symbol === null || typeof symbol == 'undefined') { - symbol = lex(); - } - action = table[state] && table[state][symbol]; - } - if (typeof action === 'undefined' || !action.length || !action[0]) { - var errStr = ''; - expected = []; - for (p in table[state]) { - if (this.terminals_[p] && p > TERROR) { - expected.push('\'' + this.terminals_[p] + '\''); - } - } - if (lexer.showPosition) { - errStr = 'Parse error on line ' + (yylineno + 1) + ':\n' + lexer.showPosition() + '\nExpecting ' + expected.join(', ') + ', got \'' + (this.terminals_[symbol] || symbol) + '\''; - } else { - errStr = 'Parse error on line ' + (yylineno + 1) + ': Unexpected ' + (symbol == EOF ? 'end of input' : '\'' + (this.terminals_[symbol] || symbol) + '\''); - } - this.parseError(errStr, { - text: lexer.match, - token: this.terminals_[symbol] || symbol, - line: lexer.yylineno, - loc: yyloc, - expected: expected - }); - } - if (action[0] instanceof Array && action.length > 1) { - throw new Error('Parse Error: multiple actions possible at state: ' + state + ', token: ' + symbol); - } - switch (action[0]) { - case 1: - stack.push(symbol); - vstack.push(lexer.yytext); - lstack.push(lexer.yylloc); - stack.push(action[1]); - symbol = null; - if (!preErrorSymbol) { - yyleng = lexer.yyleng; - yytext = lexer.yytext; - yylineno = lexer.yylineno; - yyloc = lexer.yylloc; - if (recovering > 0) { - recovering--; + } + + var uk = moment.defineLocale('uk', { + months : { + 'format': 'січня_лютого_березня_квітня_травня_червня_липня_серпня_вересня_жовтня_листопада_грудня'.split('_'), + 'standalone': 'січень_лютий_березень_квітень_травень_червень_липень_серпень_вересень_жовтень_листопад_грудень'.split('_') + }, + monthsShort : 'січ_лют_бер_квіт_трав_черв_лип_серп_вер_жовт_лист_груд'.split('_'), + weekdays : weekdaysCaseReplace, + weekdaysShort : 'нд_пн_вт_ср_чт_пт_сб'.split('_'), + weekdaysMin : 'нд_пн_вт_ср_чт_пт_сб'.split('_'), + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD.MM.YYYY', + LL : 'D MMMM YYYY р.', + LLL : 'D MMMM YYYY р., HH:mm', + LLLL : 'dddd, D MMMM YYYY р., HH:mm' + }, + calendar : { + sameDay: processHoursFunction('[Сьогодні '), + nextDay: processHoursFunction('[Завтра '), + lastDay: processHoursFunction('[Вчора '), + nextWeek: processHoursFunction('[У] dddd ['), + lastWeek: function () { + switch (this.day()) { + case 0: + case 3: + case 5: + case 6: + return processHoursFunction('[Минулої] dddd [').call(this); + case 1: + case 2: + case 4: + return processHoursFunction('[Минулого] dddd [').call(this); } + }, + sameElse: 'L' + }, + relativeTime : { + future : 'за %s', + past : '%s тому', + s : 'декілька секунд', + ss : relativeTimeWithPlural, + m : relativeTimeWithPlural, + mm : relativeTimeWithPlural, + h : 'годину', + hh : relativeTimeWithPlural, + d : 'день', + dd : relativeTimeWithPlural, + M : 'місяць', + MM : relativeTimeWithPlural, + y : 'рік', + yy : relativeTimeWithPlural + }, + // M. E.: those two are virtually unused but a user might want to implement them for his/her website for some reason + meridiemParse: /ночі|ранку|дня|вечора/, + isPM: function (input) { + return /^(дня|вечора)$/.test(input); + }, + meridiem : function (hour, minute, isLower) { + if (hour < 4) { + return 'ночі'; + } else if (hour < 12) { + return 'ранку'; + } else if (hour < 17) { + return 'дня'; } else { - symbol = preErrorSymbol; - preErrorSymbol = null; - } - break; - case 2: - len = this.productions_[action[1]][1]; - yyval.$ = vstack[vstack.length - len]; - yyval._$ = { - first_line: lstack[lstack.length - (len || 1)].first_line, - last_line: lstack[lstack.length - 1].last_line, - first_column: lstack[lstack.length - (len || 1)].first_column, - last_column: lstack[lstack.length - 1].last_column - }; - if (ranges) { - yyval._$.range = [ - lstack[lstack.length - (len || 1)].range[0], - lstack[lstack.length - 1].range[1] - ]; - } - r = this.performAction.apply(yyval, [ - yytext, - yyleng, - yylineno, - sharedState.yy, - action[1], - vstack, - lstack - ].concat(args)); - if (typeof r !== 'undefined') { - return r; + return 'вечора'; } - if (len) { - stack = stack.slice(0, -1 * len * 2); - vstack = vstack.slice(0, -1 * len); - lstack = lstack.slice(0, -1 * len); + }, + dayOfMonthOrdinalParse: /\d{1,2}-(й|го)/, + ordinal: function (number, period) { + switch (period) { + case 'M': + case 'd': + case 'DDD': + case 'w': + case 'W': + return number + '-й'; + case 'D': + return number + '-го'; + default: + return number; } - stack.push(this.productions_[action[1]][0]); - vstack.push(yyval.$); - lstack.push(yyval._$); - newState = table[stack[stack.length - 2]][stack[stack.length - 1]]; - stack.push(newState); - break; - case 3: - return true; + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 7 // The week that contains Jan 7th is the first week of the year. } - } - return true; -}}; -/* generated by jison-lex 0.3.4 */ -var lexer = (function(){ -var lexer = ({ + }); -EOF:1, + return uk; -parseError:function parseError(str, hash) { - if (this.yy.parser) { - this.yy.parser.parseError(str, hash); - } else { - throw new Error(str); - } - }, +}))); -// resets the lexer, sets new input -setInput:function (input, yy) { - this.yy = yy || this.yy || {}; - this._input = input; - this._more = this._backtrack = this.done = false; - this.yylineno = this.yyleng = 0; - this.yytext = this.matched = this.match = ''; - this.conditionStack = ['INITIAL']; - this.yylloc = { - first_line: 1, - first_column: 0, - last_line: 1, - last_column: 0 - }; - if (this.options.ranges) { - this.yylloc.range = [0,0]; - } - this.offset = 0; - return this; - }, -// consumes and returns one char from the input -input:function () { - var ch = this._input[0]; - this.yytext += ch; - this.yyleng++; - this.offset++; - this.match += ch; - this.matched += ch; - var lines = ch.match(/(?:\r\n?|\n).*/g); - if (lines) { - this.yylineno++; - this.yylloc.last_line++; - } else { - this.yylloc.last_column++; +/***/ }), +/* 160 */ +/***/ (function(module, exports, __webpack_require__) { + +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var months = [ + 'جنوری', + 'فروری', + 'مارچ', + 'اپریل', + 'مئی', + 'جون', + 'جولائی', + 'اگست', + 'ستمبر', + 'اکتوبر', + 'نومبر', + 'دسمبر' + ]; + var days = [ + 'اتوار', + 'پیر', + 'منگل', + 'بدھ', + 'جمعرات', + 'جمعہ', + 'ہفتہ' + ]; + + var ur = moment.defineLocale('ur', { + months : months, + monthsShort : months, + weekdays : days, + weekdaysShort : days, + weekdaysMin : days, + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY HH:mm', + LLLL : 'dddd، D MMMM YYYY HH:mm' + }, + meridiemParse: /صبح|شام/, + isPM : function (input) { + return 'شام' === input; + }, + meridiem : function (hour, minute, isLower) { + if (hour < 12) { + return 'صبح'; + } + return 'شام'; + }, + calendar : { + sameDay : '[آج بوقت] LT', + nextDay : '[کل بوقت] LT', + nextWeek : 'dddd [بوقت] LT', + lastDay : '[گذشتہ روز بوقت] LT', + lastWeek : '[گذشتہ] dddd [بوقت] LT', + sameElse : 'L' + }, + relativeTime : { + future : '%s بعد', + past : '%s قبل', + s : 'چند سیکنڈ', + ss : '%d سیکنڈ', + m : 'ایک منٹ', + mm : '%d منٹ', + h : 'ایک گھنٹہ', + hh : '%d گھنٹے', + d : 'ایک دن', + dd : '%d دن', + M : 'ایک ماہ', + MM : '%d ماہ', + y : 'ایک سال', + yy : '%d سال' + }, + preparse: function (string) { + return string.replace(/،/g, ','); + }, + postformat: function (string) { + return string.replace(/,/g, '،'); + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. } - if (this.options.ranges) { - this.yylloc.range[1]++; + }); + + return ur; + +}))); + + +/***/ }), +/* 161 */ +/***/ (function(module, exports, __webpack_require__) { + +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var uz = moment.defineLocale('uz', { + months : 'январ_феврал_март_апрел_май_июн_июл_август_сентябр_октябр_ноябр_декабр'.split('_'), + monthsShort : 'янв_фев_мар_апр_май_июн_июл_авг_сен_окт_ноя_дек'.split('_'), + weekdays : 'Якшанба_Душанба_Сешанба_Чоршанба_Пайшанба_Жума_Шанба'.split('_'), + weekdaysShort : 'Якш_Душ_Сеш_Чор_Пай_Жум_Шан'.split('_'), + weekdaysMin : 'Як_Ду_Се_Чо_Па_Жу_Ша'.split('_'), + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY HH:mm', + LLLL : 'D MMMM YYYY, dddd HH:mm' + }, + calendar : { + sameDay : '[Бугун соат] LT [да]', + nextDay : '[Эртага] LT [да]', + nextWeek : 'dddd [куни соат] LT [да]', + lastDay : '[Кеча соат] LT [да]', + lastWeek : '[Утган] dddd [куни соат] LT [да]', + sameElse : 'L' + }, + relativeTime : { + future : 'Якин %s ичида', + past : 'Бир неча %s олдин', + s : 'фурсат', + ss : '%d фурсат', + m : 'бир дакика', + mm : '%d дакика', + h : 'бир соат', + hh : '%d соат', + d : 'бир кун', + dd : '%d кун', + M : 'бир ой', + MM : '%d ой', + y : 'бир йил', + yy : '%d йил' + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 7 // The week that contains Jan 4th is the first week of the year. } + }); - this._input = this._input.slice(1); - return ch; - }, + return uz; -// unshifts one char (or a string) into the input -unput:function (ch) { - var len = ch.length; - var lines = ch.split(/(?:\r\n?|\n)/g); +}))); - this._input = ch + this._input; - this.yytext = this.yytext.substr(0, this.yytext.length - len); - //this.yyleng -= len; - this.offset -= len; - var oldLines = this.match.split(/(?:\r\n?|\n)/g); - this.match = this.match.substr(0, this.match.length - 1); - this.matched = this.matched.substr(0, this.matched.length - 1); - if (lines.length - 1) { - this.yylineno -= lines.length - 1; +/***/ }), +/* 162 */ +/***/ (function(module, exports, __webpack_require__) { + +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var uzLatn = moment.defineLocale('uz-latn', { + months : 'Yanvar_Fevral_Mart_Aprel_May_Iyun_Iyul_Avgust_Sentabr_Oktabr_Noyabr_Dekabr'.split('_'), + monthsShort : 'Yan_Fev_Mar_Apr_May_Iyun_Iyul_Avg_Sen_Okt_Noy_Dek'.split('_'), + weekdays : 'Yakshanba_Dushanba_Seshanba_Chorshanba_Payshanba_Juma_Shanba'.split('_'), + weekdaysShort : 'Yak_Dush_Sesh_Chor_Pay_Jum_Shan'.split('_'), + weekdaysMin : 'Ya_Du_Se_Cho_Pa_Ju_Sha'.split('_'), + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY HH:mm', + LLLL : 'D MMMM YYYY, dddd HH:mm' + }, + calendar : { + sameDay : '[Bugun soat] LT [da]', + nextDay : '[Ertaga] LT [da]', + nextWeek : 'dddd [kuni soat] LT [da]', + lastDay : '[Kecha soat] LT [da]', + lastWeek : '[O\'tgan] dddd [kuni soat] LT [da]', + sameElse : 'L' + }, + relativeTime : { + future : 'Yaqin %s ichida', + past : 'Bir necha %s oldin', + s : 'soniya', + ss : '%d soniya', + m : 'bir daqiqa', + mm : '%d daqiqa', + h : 'bir soat', + hh : '%d soat', + d : 'bir kun', + dd : '%d kun', + M : 'bir oy', + MM : '%d oy', + y : 'bir yil', + yy : '%d yil' + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 7 // The week that contains Jan 7th is the first week of the year. } - var r = this.yylloc.range; + }); - this.yylloc = { - first_line: this.yylloc.first_line, - last_line: this.yylineno + 1, - first_column: this.yylloc.first_column, - last_column: lines ? - (lines.length === oldLines.length ? this.yylloc.first_column : 0) - + oldLines[oldLines.length - lines.length].length - lines[0].length : - this.yylloc.first_column - len - }; + return uzLatn; - if (this.options.ranges) { - this.yylloc.range = [r[0], r[0] + this.yyleng - len]; +}))); + + +/***/ }), +/* 163 */ +/***/ (function(module, exports, __webpack_require__) { + +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var vi = moment.defineLocale('vi', { + months : 'tháng 1_tháng 2_tháng 3_tháng 4_tháng 5_tháng 6_tháng 7_tháng 8_tháng 9_tháng 10_tháng 11_tháng 12'.split('_'), + monthsShort : 'Th01_Th02_Th03_Th04_Th05_Th06_Th07_Th08_Th09_Th10_Th11_Th12'.split('_'), + monthsParseExact : true, + weekdays : 'chủ nhật_thứ hai_thứ ba_thứ tư_thứ năm_thứ sáu_thứ bảy'.split('_'), + weekdaysShort : 'CN_T2_T3_T4_T5_T6_T7'.split('_'), + weekdaysMin : 'CN_T2_T3_T4_T5_T6_T7'.split('_'), + weekdaysParseExact : true, + meridiemParse: /sa|ch/i, + isPM : function (input) { + return /^ch$/i.test(input); + }, + meridiem : function (hours, minutes, isLower) { + if (hours < 12) { + return isLower ? 'sa' : 'SA'; + } else { + return isLower ? 'ch' : 'CH'; + } + }, + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'DD/MM/YYYY', + LL : 'D MMMM [năm] YYYY', + LLL : 'D MMMM [năm] YYYY HH:mm', + LLLL : 'dddd, D MMMM [năm] YYYY HH:mm', + l : 'DD/M/YYYY', + ll : 'D MMM YYYY', + lll : 'D MMM YYYY HH:mm', + llll : 'ddd, D MMM YYYY HH:mm' + }, + calendar : { + sameDay: '[Hôm nay lúc] LT', + nextDay: '[Ngày mai lúc] LT', + nextWeek: 'dddd [tuần tới lúc] LT', + lastDay: '[Hôm qua lúc] LT', + lastWeek: 'dddd [tuần rồi lúc] LT', + sameElse: 'L' + }, + relativeTime : { + future : '%s tới', + past : '%s trước', + s : 'vài giây', + ss : '%d giây' , + m : 'một phút', + mm : '%d phút', + h : 'một giờ', + hh : '%d giờ', + d : 'một ngày', + dd : '%d ngày', + M : 'một tháng', + MM : '%d tháng', + y : 'một năm', + yy : '%d năm' + }, + dayOfMonthOrdinalParse: /\d{1,2}/, + ordinal : function (number) { + return number; + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. } - this.yyleng = this.yytext.length; - return this; - }, + }); -// When called from action, caches matched text and appends it on next action -more:function () { - this._more = true; - return this; - }, + return vi; -// When called from action, signals the lexer that this rule fails to match the input, so the next matching rule (regex) should be tested instead. -reject:function () { - if (this.options.backtrack_lexer) { - this._backtrack = true; - } else { - return this.parseError('Lexical error on line ' + (this.yylineno + 1) + '. You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n' + this.showPosition(), { - text: "", - token: null, - line: this.yylineno - }); +}))); + + +/***/ }), +/* 164 */ +/***/ (function(module, exports, __webpack_require__) { +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var xPseudo = moment.defineLocale('x-pseudo', { + months : 'J~áñúá~rý_F~ébrú~árý_~Márc~h_Áp~ríl_~Máý_~Júñé~_Júl~ý_Áú~gúst~_Sép~témb~ér_Ó~ctób~ér_Ñ~óvém~bér_~Décé~mbér'.split('_'), + monthsShort : 'J~áñ_~Féb_~Már_~Ápr_~Máý_~Júñ_~Júl_~Áúg_~Sép_~Óct_~Ñóv_~Déc'.split('_'), + monthsParseExact : true, + weekdays : 'S~úñdá~ý_Mó~ñdáý~_Túé~sdáý~_Wéd~ñésd~áý_T~húrs~dáý_~Fríd~áý_S~átúr~dáý'.split('_'), + weekdaysShort : 'S~úñ_~Móñ_~Túé_~Wéd_~Thú_~Frí_~Sát'.split('_'), + weekdaysMin : 'S~ú_Mó~_Tú_~Wé_T~h_Fr~_Sá'.split('_'), + weekdaysParseExact : true, + longDateFormat : { + LT : 'HH:mm', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY HH:mm', + LLLL : 'dddd, D MMMM YYYY HH:mm' + }, + calendar : { + sameDay : '[T~ódá~ý át] LT', + nextDay : '[T~ómó~rró~w át] LT', + nextWeek : 'dddd [át] LT', + lastDay : '[Ý~ést~érdá~ý át] LT', + lastWeek : '[L~ást] dddd [át] LT', + sameElse : 'L' + }, + relativeTime : { + future : 'í~ñ %s', + past : '%s á~gó', + s : 'á ~féw ~sécó~ñds', + ss : '%d s~écóñ~ds', + m : 'á ~míñ~úté', + mm : '%d m~íñú~tés', + h : 'á~ñ hó~úr', + hh : '%d h~óúrs', + d : 'á ~dáý', + dd : '%d d~áýs', + M : 'á ~móñ~th', + MM : '%d m~óñt~hs', + y : 'á ~ýéár', + yy : '%d ý~éárs' + }, + dayOfMonthOrdinalParse: /\d{1,2}(th|st|nd|rd)/, + ordinal : function (number) { + var b = number % 10, + output = (~~(number % 100 / 10) === 1) ? 'th' : + (b === 1) ? 'st' : + (b === 2) ? 'nd' : + (b === 3) ? 'rd' : 'th'; + return number + output; + }, + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. } - return this; - }, + }); -// retain first n characters of the match -less:function (n) { - this.unput(this.match.slice(n)); - }, + return xPseudo; -// displays already matched input, i.e. for error messages -pastInput:function () { - var past = this.matched.substr(0, this.matched.length - this.match.length); - return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, ""); - }, +}))); -// displays upcoming input, i.e. for error messages -upcomingInput:function () { - var next = this.match; - if (next.length < 20) { - next += this._input.substr(0, 20-next.length); + +/***/ }), +/* 165 */ +/***/ (function(module, exports, __webpack_require__) { + +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var yo = moment.defineLocale('yo', { + months : 'Sẹ́rẹ́_Èrèlè_Ẹrẹ̀nà_Ìgbé_Èbibi_Òkùdu_Agẹmo_Ògún_Owewe_Ọ̀wàrà_Bélú_Ọ̀pẹ̀̀'.split('_'), + monthsShort : 'Sẹ́r_Èrl_Ẹrn_Ìgb_Èbi_Òkù_Agẹ_Ògú_Owe_Ọ̀wà_Bél_Ọ̀pẹ̀̀'.split('_'), + weekdays : 'Àìkú_Ajé_Ìsẹ́gun_Ọjọ́rú_Ọjọ́bọ_Ẹtì_Àbámẹ́ta'.split('_'), + weekdaysShort : 'Àìk_Ajé_Ìsẹ́_Ọjr_Ọjb_Ẹtì_Àbá'.split('_'), + weekdaysMin : 'Àì_Aj_Ìs_Ọr_Ọb_Ẹt_Àb'.split('_'), + longDateFormat : { + LT : 'h:mm A', + LTS : 'h:mm:ss A', + L : 'DD/MM/YYYY', + LL : 'D MMMM YYYY', + LLL : 'D MMMM YYYY h:mm A', + LLLL : 'dddd, D MMMM YYYY h:mm A' + }, + calendar : { + sameDay : '[Ònì ni] LT', + nextDay : '[Ọ̀la ni] LT', + nextWeek : 'dddd [Ọsẹ̀ tón\'bọ] [ni] LT', + lastDay : '[Àna ni] LT', + lastWeek : 'dddd [Ọsẹ̀ tólọ́] [ni] LT', + sameElse : 'L' + }, + relativeTime : { + future : 'ní %s', + past : '%s kọjá', + s : 'ìsẹjú aayá die', + ss :'aayá %d', + m : 'ìsẹjú kan', + mm : 'ìsẹjú %d', + h : 'wákati kan', + hh : 'wákati %d', + d : 'ọjọ́ kan', + dd : 'ọjọ́ %d', + M : 'osù kan', + MM : 'osù %d', + y : 'ọdún kan', + yy : 'ọdún %d' + }, + dayOfMonthOrdinalParse : /ọjọ́\s\d{1,2}/, + ordinal : 'ọjọ́ %d', + week : { + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. } - return (next.substr(0,20) + (next.length > 20 ? '...' : '')).replace(/\n/g, ""); - }, + }); -// displays the character position where the lexing error occurred, i.e. for error messages -showPosition:function () { - var pre = this.pastInput(); - var c = new Array(pre.length + 1).join("-"); - return pre + this.upcomingInput() + "\n" + c + "^"; - }, + return yo; -// test the lexed token: return FALSE when not a match, otherwise return token -test_match:function (match, indexed_rule) { - var token, - lines, - backup; +}))); - if (this.options.backtrack_lexer) { - // save context - backup = { - yylineno: this.yylineno, - yylloc: { - first_line: this.yylloc.first_line, - last_line: this.last_line, - first_column: this.yylloc.first_column, - last_column: this.yylloc.last_column - }, - yytext: this.yytext, - match: this.match, - matches: this.matches, - matched: this.matched, - yyleng: this.yyleng, - offset: this.offset, - _more: this._more, - _input: this._input, - yy: this.yy, - conditionStack: this.conditionStack.slice(0), - done: this.done - }; - if (this.options.ranges) { - backup.yylloc.range = this.yylloc.range.slice(0); + +/***/ }), +/* 166 */ +/***/ (function(module, exports, __webpack_require__) { + +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var zhCn = moment.defineLocale('zh-cn', { + months : '一月_二月_三月_四月_五月_六月_七月_八月_九月_十月_十一月_十二月'.split('_'), + monthsShort : '1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月'.split('_'), + weekdays : '星期日_星期一_星期二_星期三_星期四_星期五_星期六'.split('_'), + weekdaysShort : '周日_周一_周二_周三_周四_周五_周六'.split('_'), + weekdaysMin : '日_一_二_三_四_五_六'.split('_'), + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'YYYY/MM/DD', + LL : 'YYYY年M月D日', + LLL : 'YYYY年M月D日Ah点mm分', + LLLL : 'YYYY年M月D日ddddAh点mm分', + l : 'YYYY/M/D', + ll : 'YYYY年M月D日', + lll : 'YYYY年M月D日 HH:mm', + llll : 'YYYY年M月D日dddd HH:mm' + }, + meridiemParse: /凌晨|早上|上午|中午|下午|晚上/, + meridiemHour: function (hour, meridiem) { + if (hour === 12) { + hour = 0; + } + if (meridiem === '凌晨' || meridiem === '早上' || + meridiem === '上午') { + return hour; + } else if (meridiem === '下午' || meridiem === '晚上') { + return hour + 12; + } else { + // '中午' + return hour >= 11 ? hour : hour + 12; + } + }, + meridiem : function (hour, minute, isLower) { + var hm = hour * 100 + minute; + if (hm < 600) { + return '凌晨'; + } else if (hm < 900) { + return '早上'; + } else if (hm < 1130) { + return '上午'; + } else if (hm < 1230) { + return '中午'; + } else if (hm < 1800) { + return '下午'; + } else { + return '晚上'; + } + }, + calendar : { + sameDay : '[今天]LT', + nextDay : '[明天]LT', + nextWeek : '[下]ddddLT', + lastDay : '[昨天]LT', + lastWeek : '[上]ddddLT', + sameElse : 'L' + }, + dayOfMonthOrdinalParse: /\d{1,2}(日|月|周)/, + ordinal : function (number, period) { + switch (period) { + case 'd': + case 'D': + case 'DDD': + return number + '日'; + case 'M': + return number + '月'; + case 'w': + case 'W': + return number + '周'; + default: + return number; } + }, + relativeTime : { + future : '%s内', + past : '%s前', + s : '几秒', + ss : '%d 秒', + m : '1 分钟', + mm : '%d 分钟', + h : '1 小时', + hh : '%d 小时', + d : '1 天', + dd : '%d 天', + M : '1 个月', + MM : '%d 个月', + y : '1 年', + yy : '%d 年' + }, + week : { + // GB/T 7408-1994《数据元和交换格式·信息交换·日期和时间表示法》与ISO 8601:1988等效 + dow : 1, // Monday is the first day of the week. + doy : 4 // The week that contains Jan 4th is the first week of the year. } + }); - lines = match[0].match(/(?:\r\n?|\n).*/g); - if (lines) { - this.yylineno += lines.length; - } - this.yylloc = { - first_line: this.yylloc.last_line, - last_line: this.yylineno + 1, - first_column: this.yylloc.last_column, - last_column: lines ? - lines[lines.length - 1].length - lines[lines.length - 1].match(/\r?\n?/)[0].length : - this.yylloc.last_column + match[0].length - }; - this.yytext += match[0]; - this.match += match[0]; - this.matches = match; - this.yyleng = this.yytext.length; - if (this.options.ranges) { - this.yylloc.range = [this.offset, this.offset += this.yyleng]; - } - this._more = false; - this._backtrack = false; - this._input = this._input.slice(match[0].length); - this.matched += match[0]; - token = this.performAction.call(this, this.yy, this, indexed_rule, this.conditionStack[this.conditionStack.length - 1]); - if (this.done && this._input) { - this.done = false; - } - if (token) { - return token; - } else if (this._backtrack) { - // recover context - for (var k in backup) { - this[k] = backup[k]; + return zhCn; + +}))); + + +/***/ }), +/* 167 */ +/***/ (function(module, exports, __webpack_require__) { + +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var zhHk = moment.defineLocale('zh-hk', { + months : '一月_二月_三月_四月_五月_六月_七月_八月_九月_十月_十一月_十二月'.split('_'), + monthsShort : '1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月'.split('_'), + weekdays : '星期日_星期一_星期二_星期三_星期四_星期五_星期六'.split('_'), + weekdaysShort : '週日_週一_週二_週三_週四_週五_週六'.split('_'), + weekdaysMin : '日_一_二_三_四_五_六'.split('_'), + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'YYYY/MM/DD', + LL : 'YYYY年M月D日', + LLL : 'YYYY年M月D日 HH:mm', + LLLL : 'YYYY年M月D日dddd HH:mm', + l : 'YYYY/M/D', + ll : 'YYYY年M月D日', + lll : 'YYYY年M月D日 HH:mm', + llll : 'YYYY年M月D日dddd HH:mm' + }, + meridiemParse: /凌晨|早上|上午|中午|下午|晚上/, + meridiemHour : function (hour, meridiem) { + if (hour === 12) { + hour = 0; } - return false; // rule action called reject() implying the next rule should be tested instead. + if (meridiem === '凌晨' || meridiem === '早上' || meridiem === '上午') { + return hour; + } else if (meridiem === '中午') { + return hour >= 11 ? hour : hour + 12; + } else if (meridiem === '下午' || meridiem === '晚上') { + return hour + 12; + } + }, + meridiem : function (hour, minute, isLower) { + var hm = hour * 100 + minute; + if (hm < 600) { + return '凌晨'; + } else if (hm < 900) { + return '早上'; + } else if (hm < 1130) { + return '上午'; + } else if (hm < 1230) { + return '中午'; + } else if (hm < 1800) { + return '下午'; + } else { + return '晚上'; + } + }, + calendar : { + sameDay : '[今天]LT', + nextDay : '[明天]LT', + nextWeek : '[下]ddddLT', + lastDay : '[昨天]LT', + lastWeek : '[上]ddddLT', + sameElse : 'L' + }, + dayOfMonthOrdinalParse: /\d{1,2}(日|月|週)/, + ordinal : function (number, period) { + switch (period) { + case 'd' : + case 'D' : + case 'DDD' : + return number + '日'; + case 'M' : + return number + '月'; + case 'w' : + case 'W' : + return number + '週'; + default : + return number; + } + }, + relativeTime : { + future : '%s內', + past : '%s前', + s : '幾秒', + ss : '%d 秒', + m : '1 分鐘', + mm : '%d 分鐘', + h : '1 小時', + hh : '%d 小時', + d : '1 天', + dd : '%d 天', + M : '1 個月', + MM : '%d 個月', + y : '1 年', + yy : '%d 年' } - return false; - }, + }); -// return next match in input -next:function () { - if (this.done) { - return this.EOF; - } - if (!this._input) { - this.done = true; - } + return zhHk; - var token, - match, - tempMatch, - index; - if (!this._more) { - this.yytext = ''; - this.match = ''; - } - var rules = this._currentRules(); - for (var i = 0; i < rules.length; i++) { - tempMatch = this._input.match(this.rules[rules[i]]); - if (tempMatch && (!match || tempMatch[0].length > match[0].length)) { - match = tempMatch; - index = i; - if (this.options.backtrack_lexer) { - token = this.test_match(tempMatch, rules[i]); - if (token !== false) { - return token; - } else if (this._backtrack) { - match = false; - continue; // rule action called reject() implying a rule MISmatch. - } else { - // else: this is a lexer rule which consumes input without producing a token (e.g. whitespace) - return false; - } - } else if (!this.options.flex) { - break; - } +}))); + + +/***/ }), +/* 168 */ +/***/ (function(module, exports, __webpack_require__) { + +//! moment.js locale configuration + +;(function (global, factory) { + true ? factory(__webpack_require__(40)) : + undefined +}(this, (function (moment) { 'use strict'; + + + var zhTw = moment.defineLocale('zh-tw', { + months : '一月_二月_三月_四月_五月_六月_七月_八月_九月_十月_十一月_十二月'.split('_'), + monthsShort : '1月_2月_3月_4月_5月_6月_7月_8月_9月_10月_11月_12月'.split('_'), + weekdays : '星期日_星期一_星期二_星期三_星期四_星期五_星期六'.split('_'), + weekdaysShort : '週日_週一_週二_週三_週四_週五_週六'.split('_'), + weekdaysMin : '日_一_二_三_四_五_六'.split('_'), + longDateFormat : { + LT : 'HH:mm', + LTS : 'HH:mm:ss', + L : 'YYYY/MM/DD', + LL : 'YYYY年M月D日', + LLL : 'YYYY年M月D日 HH:mm', + LLLL : 'YYYY年M月D日dddd HH:mm', + l : 'YYYY/M/D', + ll : 'YYYY年M月D日', + lll : 'YYYY年M月D日 HH:mm', + llll : 'YYYY年M月D日dddd HH:mm' + }, + meridiemParse: /凌晨|早上|上午|中午|下午|晚上/, + meridiemHour : function (hour, meridiem) { + if (hour === 12) { + hour = 0; } - } - if (match) { - token = this.test_match(match, rules[index]); - if (token !== false) { - return token; + if (meridiem === '凌晨' || meridiem === '早上' || meridiem === '上午') { + return hour; + } else if (meridiem === '中午') { + return hour >= 11 ? hour : hour + 12; + } else if (meridiem === '下午' || meridiem === '晚上') { + return hour + 12; } - // else: this is a lexer rule which consumes input without producing a token (e.g. whitespace) - return false; - } - if (this._input === "") { - return this.EOF; - } else { - return this.parseError('Lexical error on line ' + (this.yylineno + 1) + '. Unrecognized text.\n' + this.showPosition(), { - text: "", - token: null, - line: this.yylineno - }); + }, + meridiem : function (hour, minute, isLower) { + var hm = hour * 100 + minute; + if (hm < 600) { + return '凌晨'; + } else if (hm < 900) { + return '早上'; + } else if (hm < 1130) { + return '上午'; + } else if (hm < 1230) { + return '中午'; + } else if (hm < 1800) { + return '下午'; + } else { + return '晚上'; + } + }, + calendar : { + sameDay : '[今天] LT', + nextDay : '[明天] LT', + nextWeek : '[下]dddd LT', + lastDay : '[昨天] LT', + lastWeek : '[上]dddd LT', + sameElse : 'L' + }, + dayOfMonthOrdinalParse: /\d{1,2}(日|月|週)/, + ordinal : function (number, period) { + switch (period) { + case 'd' : + case 'D' : + case 'DDD' : + return number + '日'; + case 'M' : + return number + '月'; + case 'w' : + case 'W' : + return number + '週'; + default : + return number; + } + }, + relativeTime : { + future : '%s內', + past : '%s前', + s : '幾秒', + ss : '%d 秒', + m : '1 分鐘', + mm : '%d 分鐘', + h : '1 小時', + hh : '%d 小時', + d : '1 天', + dd : '%d 天', + M : '1 個月', + MM : '%d 個月', + y : '1 年', + yy : '%d 年' } - }, + }); -// return next match that has a token -lex:function lex() { - var r = this.next(); - if (r) { - return r; - } else { - return this.lex(); - } - }, + return zhTw; -// activates a new lexer condition state (pushes the new lexer condition state onto the condition stack) -begin:function begin(condition) { - this.conditionStack.push(condition); - }, +}))); -// pop the previously active lexer condition state off the condition stack -popState:function popState() { - var n = this.conditionStack.length - 1; - if (n > 0) { - return this.conditionStack.pop(); - } else { - return this.conditionStack[0]; - } - }, -// produce the lexer rule set which is active for the currently active lexer condition state -_currentRules:function _currentRules() { - if (this.conditionStack.length && this.conditionStack[this.conditionStack.length - 1]) { - return this.conditions[this.conditionStack[this.conditionStack.length - 1]].rules; - } else { - return this.conditions["INITIAL"].rules; - } - }, +/***/ }), +/* 169 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { -// return the currently active lexer condition state; when an index argument is provided it produces the N-th previous condition state, if available -topState:function topState(n) { - n = this.conditionStack.length - 1 - Math.abs(n || 0); - if (n >= 0) { - return this.conditionStack[n]; - } else { - return "INITIAL"; - } - }, +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony import */ var _internal_operators_audit__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(170); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "audit", function() { return _internal_operators_audit__WEBPACK_IMPORTED_MODULE_0__["audit"]; }); -// alias for begin(condition) -pushState:function pushState(condition) { - this.begin(condition); - }, +/* harmony import */ var _internal_operators_auditTime__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(198); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "auditTime", function() { return _internal_operators_auditTime__WEBPACK_IMPORTED_MODULE_1__["auditTime"]; }); -// return the number of states currently on the stack -stateStackSize:function stateStackSize() { - return this.conditionStack.length; - }, -options: {}, -performAction: function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) { -var YYSTATE=YY_START; -switch($avoiding_name_collisions) { -case 0:return 5 -break; -case 1:/* skip whitespace */ -break; -case 2:return 8 -break; -case 3:return 16 -break; -case 4:return 17 -break; -case 5:return 11 -break; -case 6:return 10 -break; -case 7:return 9 -break; -case 8:return 14 -break; -case 9:return 15 -break; -case 10:return 12 -break; -case 11:return 7 -break; -case 12:return 7 -break; -case 13:return 7 -break; -case 14:return 7 -break; -case 15:return 7 -break; -case 16:return 7 -break; -case 17:return 7 -break; -case 18:return 7 -break; -case 19:return 7 -break; -case 20:return 7 -break; -case 21:return 7 -break; -case 22:return 7 -break; -case 23:return 7 -break; -case 24:return 13 -break; -case 25:return 13 -break; -case 26:return 13 -break; -case 27:return 13 -break; -case 28:return 13 -break; -case 29:return 13 -break; -case 30:return 13 -break; -case 31:return 13 -break; -case 32:return 7 -break; -case 33:return 13 -break; -case 34:return 7 -break; -case 35:return 13 -break; -case 36:return 7 -break; -case 37:return 13 -break; -case 38:return 13 -break; -case 39:return 7 -break; -case 40:return 13 -break; -case 41:return 13 -break; -case 42:return 13 -break; -case 43:return 13 -break; -case 44:return 13 -break; -case 45:return 7 -break; -case 46:return 13 -break; -case 47:return 7 -break; -case 48:return 7 -break; -case 49:return 7 -break; -case 50:return 7 -break; -case 51:return 7 -break; -case 52:return 7 -break; -case 53:return 7 -break; -case 54:return 7 -break; -case 55:return 7 -break; -case 56:return 7 -break; -case 57:return 7 -break; -case 58:return 7 -break; -case 59:return 7 -break; -case 60:return 7 -break; -case 61:return 7 -break; -case 62:return 7 -break; -case 63:return 13 -break; -case 64:return 7 -break; -case 65:return 7 -break; -case 66:return 13 -break; -case 67:return 7 -break; -case 68:return 7 -break; -case 69:return 7 -break; -case 70:return 7 -break; -case 71:return 7 -break; -case 72:return 7 -break; -case 73:return 13 -break; -case 74:return 7 -break; -case 75:return 13 -break; -case 76:return 7 -break; -case 77:return 7 -break; -case 78:return 7 -break; -case 79:return 7 -break; -case 80:return 7 -break; -case 81:return 7 -break; -case 82:return 7 -break; -case 83:return 7 -break; -case 84:return 7 -break; -case 85:return 7 -break; -case 86:return 7 -break; -case 87:return 7 -break; -case 88:return 7 -break; -case 89:return 7 -break; -case 90:return 7 -break; -case 91:return 7 -break; -case 92:return 7 -break; -case 93:return 7 -break; -case 94:return 7 -break; -case 95:return 7 -break; -case 96:return 7 -break; -case 97:return 7 -break; -case 98:return 7 -break; -case 99:return 7 -break; -case 100:return 7 -break; -case 101:return 7 -break; -case 102:return 7 -break; -case 103:return 7 -break; -case 104:return 7 -break; -case 105:return 7 -break; -case 106:return 7 -break; -case 107:return 7 -break; -case 108:return 7 -break; -case 109:return 7 -break; -case 110:return 7 -break; -case 111:return 7 -break; -case 112:return 7 -break; -case 113:return 7 -break; -case 114:return 7 -break; -case 115:return 7 -break; -case 116:return 7 -break; -case 117:return 7 -break; -case 118:return 7 -break; -case 119:return 7 -break; -case 120:return 7 -break; -case 121:return 7 -break; -case 122:return 7 -break; -case 123:return 7 -break; -case 124:return 7 -break; -case 125:return 7 -break; -case 126:return 7 -break; -case 127:return 7 -break; -case 128:return 7 -break; -case 129:return 7 -break; -case 130:return 7 -break; -case 131:return 7 -break; -case 132:return 7 -break; -case 133:return 7 -break; -case 134:return 7 -break; -case 135:return 7 -break; -case 136:return 7 -break; -case 137:return 7 -break; -case 138:return 7 -break; -case 139:return 7 -break; -case 140:return 7 -break; -case 141:return 7 -break; -case 142:return 7 -break; -case 143:return 7 -break; -case 144:return 7 -break; -case 145:return 7 -break; -case 146:return 7 -break; -case 147:return 7 -break; -case 148:return 7 -break; -case 149:return 7 -break; -case 150:return 7 -break; -case 151:return 7 -break; -case 152:return 7 -break; -case 153:return 7 -break; -case 154:return 7 -break; -case 155:return 7 -break; -case 156:return 7 -break; -case 157:return 7 -break; -case 158:return 7 -break; -case 159:return 7 -break; -case 160:return 7 -break; -case 161:return 7 -break; -case 162:return 7 -break; -case 163:return 7 -break; -case 164:return 7 -break; -case 165:return 7 -break; -case 166:return 7 -break; -case 167:return 7 -break; -case 168:return 7 -break; -case 169:return 7 -break; -case 170:return 7 -break; -case 171:return 7 -break; -case 172:return 7 -break; -case 173:return 7 -break; -case 174:return 7 -break; -case 175:return 7 -break; -case 176:return 7 -break; -case 177:return 7 -break; -case 178:return 7 -break; -case 179:return 7 -break; -case 180:return 7 -break; -case 181:return 7 -break; -case 182:return 7 -break; -case 183:return 7 -break; -case 184:return 7 -break; -case 185:return 7 -break; -case 186:return 7 -break; -case 187:return 7 -break; -case 188:return 7 -break; -case 189:return 7 -break; -case 190:return 7 -break; -case 191:return 7 -break; -case 192:return 7 -break; -case 193:return 7 -break; -case 194:return 7 -break; -case 195:return 7 -break; -case 196:return 7 -break; -case 197:return 7 -break; -case 198:return 7 -break; -case 199:return 7 -break; -case 200:return 7 -break; -case 201:return 7 -break; -case 202:return 7 -break; -case 203:return 7 -break; -case 204:return 7 -break; -case 205:return 7 -break; -case 206:return 7 -break; -case 207:return 7 -break; -case 208:return 7 -break; -case 209:return 7 -break; -case 210:return 7 -break; -case 211:return 7 -break; -case 212:return 7 -break; -case 213:return 7 -break; -case 214:return 7 -break; -case 215:return 7 -break; -case 216:return 7 -break; -case 217:return 7 -break; -case 218:return 7 -break; -case 219:return 7 -break; -case 220:return 7 -break; -case 221:return 7 -break; -case 222:return 7 -break; -case 223:return 7 -break; -case 224:return 7 -break; -case 225:return 7 -break; -case 226:return 7 -break; -case 227:return 7 -break; -case 228:return 7 -break; -case 229:return 7 -break; -case 230:return 7 -break; -case 231:return 7 -break; -case 232:return 7 -break; -case 233:return 7 -break; -case 234:return 7 -break; -case 235:return 7 -break; -case 236:return 7 -break; -case 237:return 7 -break; -case 238:return 7 -break; -case 239:return 7 -break; -case 240:return 7 -break; -case 241:return 7 -break; -case 242:return 7 -break; -case 243:return 7 -break; -case 244:return 7 -break; -case 245:return 7 -break; -case 246:return 7 -break; -case 247:return 7 -break; -case 248:return 7 -break; -case 249:return 7 -break; -case 250:return 7 -break; -case 251:return 7 -break; -case 252:return 7 -break; -case 253:return 7 -break; -case 254:return 7 -break; -case 255:return 7 -break; -case 256:return 7 -break; -case 257:return 7 -break; -case 258:return 7 -break; -case 259:return 7 -break; -case 260:return 7 -break; -case 261:return 7 -break; -case 262:return 7 -break; -case 263:return 7 -break; -case 264:return 7 -break; -case 265:return 7 -break; -case 266:return 7 -break; -case 267:return 7 -break; -case 268:return 7 -break; -case 269:return 7 -break; -case 270:return 7 -break; -case 271:return 7 -break; -case 272:return 7 -break; -case 273:return 7 -break; -case 274:return 7 -break; -case 275:return 7 -break; -case 276:return 7 -break; -case 277:return 7 -break; -case 278:return 7 -break; -case 279:return 7 -break; -case 280:return 7 -break; -case 281:return 7 -break; -case 282:return 7 -break; -case 283:return 7 -break; -case 284:return 7 -break; -case 285:return 7 -break; -case 286:return 7 -break; -case 287:return 7 -break; -case 288:return 7 -break; -case 289:return 7 -break; -case 290:return 7 -break; -case 291:return 7 -break; -case 292:return 7 -break; -case 293:return 7 -break; -case 294:return 7 -break; -case 295:return 7 -break; -case 296:return 7 -break; -case 297:return 7 -break; -case 298:return 7 -break; -case 299:return 7 -break; -case 300:return 7 -break; -case 301:return 7 -break; -case 302:return 7 -break; -case 303:return 7 -break; -case 304:return 7 -break; -case 305:return 7 -break; -case 306:return 7 -break; -case 307:return 7 -break; -case 308:return 7 -break; -case 309:return 7 -break; -case 310:return 7 -break; -case 311:return 7 -break; -case 312:return 7 -break; -case 313:return 7 -break; -case 314:return 7 -break; -case 315:return 7 -break; -case 316:return 7 -break; -case 317:return 7 -break; -case 318:return 7 -break; -case 319:return 7 -break; -case 320:return 7 -break; -case 321:return 7 -break; -case 322:return 7 -break; -case 323:return 7 -break; -case 324:return 7 -break; -case 325:return 7 -break; -case 326:return 7 -break; -case 327:return 7 -break; -case 328:return 7 -break; -case 329:return 7 -break; -case 330:return 7 -break; -case 331:return 7 -break; -case 332:return 7 -break; -case 333:return 7 -break; -case 334:return 7 -break; -case 335:return 7 -break; -case 336:return 7 -break; -case 337:return 7 -break; -case 338:return 7 -break; -case 339:return 7 -break; -case 340:return 7 -break; -case 341:return 7 -break; -case 342:return 7 -break; -case 343:return 7 -break; -case 344:return 7 -break; -case 345:return 7 -break; -case 346:return 7 -break; -case 347:return 7 -break; -case 348:return 7 -break; -case 349:return 7 -break; -case 350:return 7 -break; -case 351:return 7 -break; -case 352:return 7 -break; -case 353:return 7 -break; -case 354:return 7 -break; -case 355:return 7 -break; -case 356:return 7 -break; -case 357:return 7 -break; -case 358:return 7 -break; -case 359:return 7 -break; -case 360:return 7 -break; -case 361:return 7 -break; -case 362:return 7 -break; -case 363:return 7 -break; -case 364:return 7 -break; +/* harmony import */ var _internal_operators_buffer__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(207); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "buffer", function() { return _internal_operators_buffer__WEBPACK_IMPORTED_MODULE_2__["buffer"]; }); + +/* harmony import */ var _internal_operators_bufferCount__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(208); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "bufferCount", function() { return _internal_operators_bufferCount__WEBPACK_IMPORTED_MODULE_3__["bufferCount"]; }); + +/* harmony import */ var _internal_operators_bufferTime__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(209); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "bufferTime", function() { return _internal_operators_bufferTime__WEBPACK_IMPORTED_MODULE_4__["bufferTime"]; }); + +/* harmony import */ var _internal_operators_bufferToggle__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(210); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "bufferToggle", function() { return _internal_operators_bufferToggle__WEBPACK_IMPORTED_MODULE_5__["bufferToggle"]; }); + +/* harmony import */ var _internal_operators_bufferWhen__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(211); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "bufferWhen", function() { return _internal_operators_bufferWhen__WEBPACK_IMPORTED_MODULE_6__["bufferWhen"]; }); + +/* harmony import */ var _internal_operators_catchError__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(212); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "catchError", function() { return _internal_operators_catchError__WEBPACK_IMPORTED_MODULE_7__["catchError"]; }); + +/* harmony import */ var _internal_operators_combineAll__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(213); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "combineAll", function() { return _internal_operators_combineAll__WEBPACK_IMPORTED_MODULE_8__["combineAll"]; }); + +/* harmony import */ var _internal_operators_combineLatest__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(217); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "combineLatest", function() { return _internal_operators_combineLatest__WEBPACK_IMPORTED_MODULE_9__["combineLatest"]; }); + +/* harmony import */ var _internal_operators_concat__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(225); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "concat", function() { return _internal_operators_concat__WEBPACK_IMPORTED_MODULE_10__["concat"]; }); + +/* harmony import */ var _internal_operators_concatAll__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(228); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "concatAll", function() { return _internal_operators_concatAll__WEBPACK_IMPORTED_MODULE_11__["concatAll"]; }); + +/* harmony import */ var _internal_operators_concatMap__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(233); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "concatMap", function() { return _internal_operators_concatMap__WEBPACK_IMPORTED_MODULE_12__["concatMap"]; }); + +/* harmony import */ var _internal_operators_concatMapTo__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(234); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "concatMapTo", function() { return _internal_operators_concatMapTo__WEBPACK_IMPORTED_MODULE_13__["concatMapTo"]; }); + +/* harmony import */ var _internal_operators_count__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(235); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "count", function() { return _internal_operators_count__WEBPACK_IMPORTED_MODULE_14__["count"]; }); + +/* harmony import */ var _internal_operators_debounce__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(236); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "debounce", function() { return _internal_operators_debounce__WEBPACK_IMPORTED_MODULE_15__["debounce"]; }); + +/* harmony import */ var _internal_operators_debounceTime__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(237); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "debounceTime", function() { return _internal_operators_debounceTime__WEBPACK_IMPORTED_MODULE_16__["debounceTime"]; }); + +/* harmony import */ var _internal_operators_defaultIfEmpty__WEBPACK_IMPORTED_MODULE_17__ = __webpack_require__(238); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "defaultIfEmpty", function() { return _internal_operators_defaultIfEmpty__WEBPACK_IMPORTED_MODULE_17__["defaultIfEmpty"]; }); + +/* harmony import */ var _internal_operators_delay__WEBPACK_IMPORTED_MODULE_18__ = __webpack_require__(239); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "delay", function() { return _internal_operators_delay__WEBPACK_IMPORTED_MODULE_18__["delay"]; }); + +/* harmony import */ var _internal_operators_delayWhen__WEBPACK_IMPORTED_MODULE_19__ = __webpack_require__(244); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "delayWhen", function() { return _internal_operators_delayWhen__WEBPACK_IMPORTED_MODULE_19__["delayWhen"]; }); + +/* harmony import */ var _internal_operators_dematerialize__WEBPACK_IMPORTED_MODULE_20__ = __webpack_require__(245); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "dematerialize", function() { return _internal_operators_dematerialize__WEBPACK_IMPORTED_MODULE_20__["dematerialize"]; }); + +/* harmony import */ var _internal_operators_distinct__WEBPACK_IMPORTED_MODULE_21__ = __webpack_require__(246); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "distinct", function() { return _internal_operators_distinct__WEBPACK_IMPORTED_MODULE_21__["distinct"]; }); + +/* harmony import */ var _internal_operators_distinctUntilChanged__WEBPACK_IMPORTED_MODULE_22__ = __webpack_require__(247); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "distinctUntilChanged", function() { return _internal_operators_distinctUntilChanged__WEBPACK_IMPORTED_MODULE_22__["distinctUntilChanged"]; }); + +/* harmony import */ var _internal_operators_distinctUntilKeyChanged__WEBPACK_IMPORTED_MODULE_23__ = __webpack_require__(248); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "distinctUntilKeyChanged", function() { return _internal_operators_distinctUntilKeyChanged__WEBPACK_IMPORTED_MODULE_23__["distinctUntilKeyChanged"]; }); + +/* harmony import */ var _internal_operators_elementAt__WEBPACK_IMPORTED_MODULE_24__ = __webpack_require__(249); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "elementAt", function() { return _internal_operators_elementAt__WEBPACK_IMPORTED_MODULE_24__["elementAt"]; }); + +/* harmony import */ var _internal_operators_endWith__WEBPACK_IMPORTED_MODULE_25__ = __webpack_require__(255); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "endWith", function() { return _internal_operators_endWith__WEBPACK_IMPORTED_MODULE_25__["endWith"]; }); + +/* harmony import */ var _internal_operators_every__WEBPACK_IMPORTED_MODULE_26__ = __webpack_require__(256); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "every", function() { return _internal_operators_every__WEBPACK_IMPORTED_MODULE_26__["every"]; }); + +/* harmony import */ var _internal_operators_exhaust__WEBPACK_IMPORTED_MODULE_27__ = __webpack_require__(257); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "exhaust", function() { return _internal_operators_exhaust__WEBPACK_IMPORTED_MODULE_27__["exhaust"]; }); + +/* harmony import */ var _internal_operators_exhaustMap__WEBPACK_IMPORTED_MODULE_28__ = __webpack_require__(258); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "exhaustMap", function() { return _internal_operators_exhaustMap__WEBPACK_IMPORTED_MODULE_28__["exhaustMap"]; }); + +/* harmony import */ var _internal_operators_expand__WEBPACK_IMPORTED_MODULE_29__ = __webpack_require__(259); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "expand", function() { return _internal_operators_expand__WEBPACK_IMPORTED_MODULE_29__["expand"]; }); + +/* harmony import */ var _internal_operators_filter__WEBPACK_IMPORTED_MODULE_30__ = __webpack_require__(251); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "filter", function() { return _internal_operators_filter__WEBPACK_IMPORTED_MODULE_30__["filter"]; }); + +/* harmony import */ var _internal_operators_finalize__WEBPACK_IMPORTED_MODULE_31__ = __webpack_require__(260); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "finalize", function() { return _internal_operators_finalize__WEBPACK_IMPORTED_MODULE_31__["finalize"]; }); + +/* harmony import */ var _internal_operators_find__WEBPACK_IMPORTED_MODULE_32__ = __webpack_require__(261); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "find", function() { return _internal_operators_find__WEBPACK_IMPORTED_MODULE_32__["find"]; }); + +/* harmony import */ var _internal_operators_findIndex__WEBPACK_IMPORTED_MODULE_33__ = __webpack_require__(262); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "findIndex", function() { return _internal_operators_findIndex__WEBPACK_IMPORTED_MODULE_33__["findIndex"]; }); + +/* harmony import */ var _internal_operators_first__WEBPACK_IMPORTED_MODULE_34__ = __webpack_require__(263); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "first", function() { return _internal_operators_first__WEBPACK_IMPORTED_MODULE_34__["first"]; }); + +/* harmony import */ var _internal_operators_groupBy__WEBPACK_IMPORTED_MODULE_35__ = __webpack_require__(264); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "groupBy", function() { return _internal_operators_groupBy__WEBPACK_IMPORTED_MODULE_35__["groupBy"]; }); + +/* harmony import */ var _internal_operators_ignoreElements__WEBPACK_IMPORTED_MODULE_36__ = __webpack_require__(268); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "ignoreElements", function() { return _internal_operators_ignoreElements__WEBPACK_IMPORTED_MODULE_36__["ignoreElements"]; }); + +/* harmony import */ var _internal_operators_isEmpty__WEBPACK_IMPORTED_MODULE_37__ = __webpack_require__(269); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "isEmpty", function() { return _internal_operators_isEmpty__WEBPACK_IMPORTED_MODULE_37__["isEmpty"]; }); + +/* harmony import */ var _internal_operators_last__WEBPACK_IMPORTED_MODULE_38__ = __webpack_require__(270); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "last", function() { return _internal_operators_last__WEBPACK_IMPORTED_MODULE_38__["last"]; }); + +/* harmony import */ var _internal_operators_map__WEBPACK_IMPORTED_MODULE_39__ = __webpack_require__(231); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "map", function() { return _internal_operators_map__WEBPACK_IMPORTED_MODULE_39__["map"]; }); + +/* harmony import */ var _internal_operators_mapTo__WEBPACK_IMPORTED_MODULE_40__ = __webpack_require__(272); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "mapTo", function() { return _internal_operators_mapTo__WEBPACK_IMPORTED_MODULE_40__["mapTo"]; }); + +/* harmony import */ var _internal_operators_materialize__WEBPACK_IMPORTED_MODULE_41__ = __webpack_require__(273); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "materialize", function() { return _internal_operators_materialize__WEBPACK_IMPORTED_MODULE_41__["materialize"]; }); + +/* harmony import */ var _internal_operators_max__WEBPACK_IMPORTED_MODULE_42__ = __webpack_require__(274); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "max", function() { return _internal_operators_max__WEBPACK_IMPORTED_MODULE_42__["max"]; }); + +/* harmony import */ var _internal_operators_merge__WEBPACK_IMPORTED_MODULE_43__ = __webpack_require__(277); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "merge", function() { return _internal_operators_merge__WEBPACK_IMPORTED_MODULE_43__["merge"]; }); + +/* harmony import */ var _internal_operators_mergeAll__WEBPACK_IMPORTED_MODULE_44__ = __webpack_require__(229); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "mergeAll", function() { return _internal_operators_mergeAll__WEBPACK_IMPORTED_MODULE_44__["mergeAll"]; }); + +/* harmony import */ var _internal_operators_mergeMap__WEBPACK_IMPORTED_MODULE_45__ = __webpack_require__(230); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "mergeMap", function() { return _internal_operators_mergeMap__WEBPACK_IMPORTED_MODULE_45__["mergeMap"]; }); + +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "flatMap", function() { return _internal_operators_mergeMap__WEBPACK_IMPORTED_MODULE_45__["mergeMap"]; }); + +/* harmony import */ var _internal_operators_mergeMapTo__WEBPACK_IMPORTED_MODULE_46__ = __webpack_require__(279); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "mergeMapTo", function() { return _internal_operators_mergeMapTo__WEBPACK_IMPORTED_MODULE_46__["mergeMapTo"]; }); + +/* harmony import */ var _internal_operators_mergeScan__WEBPACK_IMPORTED_MODULE_47__ = __webpack_require__(280); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "mergeScan", function() { return _internal_operators_mergeScan__WEBPACK_IMPORTED_MODULE_47__["mergeScan"]; }); + +/* harmony import */ var _internal_operators_min__WEBPACK_IMPORTED_MODULE_48__ = __webpack_require__(281); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "min", function() { return _internal_operators_min__WEBPACK_IMPORTED_MODULE_48__["min"]; }); + +/* harmony import */ var _internal_operators_multicast__WEBPACK_IMPORTED_MODULE_49__ = __webpack_require__(282); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "multicast", function() { return _internal_operators_multicast__WEBPACK_IMPORTED_MODULE_49__["multicast"]; }); + +/* harmony import */ var _internal_operators_observeOn__WEBPACK_IMPORTED_MODULE_50__ = __webpack_require__(285); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "observeOn", function() { return _internal_operators_observeOn__WEBPACK_IMPORTED_MODULE_50__["observeOn"]; }); + +/* harmony import */ var _internal_operators_onErrorResumeNext__WEBPACK_IMPORTED_MODULE_51__ = __webpack_require__(286); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "onErrorResumeNext", function() { return _internal_operators_onErrorResumeNext__WEBPACK_IMPORTED_MODULE_51__["onErrorResumeNext"]; }); + +/* harmony import */ var _internal_operators_pairwise__WEBPACK_IMPORTED_MODULE_52__ = __webpack_require__(287); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "pairwise", function() { return _internal_operators_pairwise__WEBPACK_IMPORTED_MODULE_52__["pairwise"]; }); + +/* harmony import */ var _internal_operators_partition__WEBPACK_IMPORTED_MODULE_53__ = __webpack_require__(288); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "partition", function() { return _internal_operators_partition__WEBPACK_IMPORTED_MODULE_53__["partition"]; }); + +/* harmony import */ var _internal_operators_pluck__WEBPACK_IMPORTED_MODULE_54__ = __webpack_require__(290); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "pluck", function() { return _internal_operators_pluck__WEBPACK_IMPORTED_MODULE_54__["pluck"]; }); + +/* harmony import */ var _internal_operators_publish__WEBPACK_IMPORTED_MODULE_55__ = __webpack_require__(291); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "publish", function() { return _internal_operators_publish__WEBPACK_IMPORTED_MODULE_55__["publish"]; }); + +/* harmony import */ var _internal_operators_publishBehavior__WEBPACK_IMPORTED_MODULE_56__ = __webpack_require__(292); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "publishBehavior", function() { return _internal_operators_publishBehavior__WEBPACK_IMPORTED_MODULE_56__["publishBehavior"]; }); + +/* harmony import */ var _internal_operators_publishLast__WEBPACK_IMPORTED_MODULE_57__ = __webpack_require__(294); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "publishLast", function() { return _internal_operators_publishLast__WEBPACK_IMPORTED_MODULE_57__["publishLast"]; }); + +/* harmony import */ var _internal_operators_publishReplay__WEBPACK_IMPORTED_MODULE_58__ = __webpack_require__(296); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "publishReplay", function() { return _internal_operators_publishReplay__WEBPACK_IMPORTED_MODULE_58__["publishReplay"]; }); + +/* harmony import */ var _internal_operators_race__WEBPACK_IMPORTED_MODULE_59__ = __webpack_require__(301); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "race", function() { return _internal_operators_race__WEBPACK_IMPORTED_MODULE_59__["race"]; }); + +/* harmony import */ var _internal_operators_reduce__WEBPACK_IMPORTED_MODULE_60__ = __webpack_require__(275); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "reduce", function() { return _internal_operators_reduce__WEBPACK_IMPORTED_MODULE_60__["reduce"]; }); + +/* harmony import */ var _internal_operators_repeat__WEBPACK_IMPORTED_MODULE_61__ = __webpack_require__(303); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "repeat", function() { return _internal_operators_repeat__WEBPACK_IMPORTED_MODULE_61__["repeat"]; }); + +/* harmony import */ var _internal_operators_repeatWhen__WEBPACK_IMPORTED_MODULE_62__ = __webpack_require__(304); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "repeatWhen", function() { return _internal_operators_repeatWhen__WEBPACK_IMPORTED_MODULE_62__["repeatWhen"]; }); + +/* harmony import */ var _internal_operators_retry__WEBPACK_IMPORTED_MODULE_63__ = __webpack_require__(305); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "retry", function() { return _internal_operators_retry__WEBPACK_IMPORTED_MODULE_63__["retry"]; }); + +/* harmony import */ var _internal_operators_retryWhen__WEBPACK_IMPORTED_MODULE_64__ = __webpack_require__(306); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "retryWhen", function() { return _internal_operators_retryWhen__WEBPACK_IMPORTED_MODULE_64__["retryWhen"]; }); + +/* harmony import */ var _internal_operators_refCount__WEBPACK_IMPORTED_MODULE_65__ = __webpack_require__(284); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "refCount", function() { return _internal_operators_refCount__WEBPACK_IMPORTED_MODULE_65__["refCount"]; }); + +/* harmony import */ var _internal_operators_sample__WEBPACK_IMPORTED_MODULE_66__ = __webpack_require__(307); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "sample", function() { return _internal_operators_sample__WEBPACK_IMPORTED_MODULE_66__["sample"]; }); + +/* harmony import */ var _internal_operators_sampleTime__WEBPACK_IMPORTED_MODULE_67__ = __webpack_require__(308); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "sampleTime", function() { return _internal_operators_sampleTime__WEBPACK_IMPORTED_MODULE_67__["sampleTime"]; }); + +/* harmony import */ var _internal_operators_scan__WEBPACK_IMPORTED_MODULE_68__ = __webpack_require__(276); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "scan", function() { return _internal_operators_scan__WEBPACK_IMPORTED_MODULE_68__["scan"]; }); + +/* harmony import */ var _internal_operators_sequenceEqual__WEBPACK_IMPORTED_MODULE_69__ = __webpack_require__(309); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "sequenceEqual", function() { return _internal_operators_sequenceEqual__WEBPACK_IMPORTED_MODULE_69__["sequenceEqual"]; }); + +/* harmony import */ var _internal_operators_share__WEBPACK_IMPORTED_MODULE_70__ = __webpack_require__(310); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "share", function() { return _internal_operators_share__WEBPACK_IMPORTED_MODULE_70__["share"]; }); + +/* harmony import */ var _internal_operators_shareReplay__WEBPACK_IMPORTED_MODULE_71__ = __webpack_require__(311); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "shareReplay", function() { return _internal_operators_shareReplay__WEBPACK_IMPORTED_MODULE_71__["shareReplay"]; }); + +/* harmony import */ var _internal_operators_single__WEBPACK_IMPORTED_MODULE_72__ = __webpack_require__(312); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "single", function() { return _internal_operators_single__WEBPACK_IMPORTED_MODULE_72__["single"]; }); + +/* harmony import */ var _internal_operators_skip__WEBPACK_IMPORTED_MODULE_73__ = __webpack_require__(313); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "skip", function() { return _internal_operators_skip__WEBPACK_IMPORTED_MODULE_73__["skip"]; }); + +/* harmony import */ var _internal_operators_skipLast__WEBPACK_IMPORTED_MODULE_74__ = __webpack_require__(314); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "skipLast", function() { return _internal_operators_skipLast__WEBPACK_IMPORTED_MODULE_74__["skipLast"]; }); + +/* harmony import */ var _internal_operators_skipUntil__WEBPACK_IMPORTED_MODULE_75__ = __webpack_require__(315); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "skipUntil", function() { return _internal_operators_skipUntil__WEBPACK_IMPORTED_MODULE_75__["skipUntil"]; }); + +/* harmony import */ var _internal_operators_skipWhile__WEBPACK_IMPORTED_MODULE_76__ = __webpack_require__(316); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "skipWhile", function() { return _internal_operators_skipWhile__WEBPACK_IMPORTED_MODULE_76__["skipWhile"]; }); + +/* harmony import */ var _internal_operators_startWith__WEBPACK_IMPORTED_MODULE_77__ = __webpack_require__(317); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "startWith", function() { return _internal_operators_startWith__WEBPACK_IMPORTED_MODULE_77__["startWith"]; }); + +/* harmony import */ var _internal_operators_subscribeOn__WEBPACK_IMPORTED_MODULE_78__ = __webpack_require__(318); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "subscribeOn", function() { return _internal_operators_subscribeOn__WEBPACK_IMPORTED_MODULE_78__["subscribeOn"]; }); + +/* harmony import */ var _internal_operators_switchAll__WEBPACK_IMPORTED_MODULE_79__ = __webpack_require__(324); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "switchAll", function() { return _internal_operators_switchAll__WEBPACK_IMPORTED_MODULE_79__["switchAll"]; }); + +/* harmony import */ var _internal_operators_switchMap__WEBPACK_IMPORTED_MODULE_80__ = __webpack_require__(325); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "switchMap", function() { return _internal_operators_switchMap__WEBPACK_IMPORTED_MODULE_80__["switchMap"]; }); + +/* harmony import */ var _internal_operators_switchMapTo__WEBPACK_IMPORTED_MODULE_81__ = __webpack_require__(326); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "switchMapTo", function() { return _internal_operators_switchMapTo__WEBPACK_IMPORTED_MODULE_81__["switchMapTo"]; }); + +/* harmony import */ var _internal_operators_take__WEBPACK_IMPORTED_MODULE_82__ = __webpack_require__(254); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "take", function() { return _internal_operators_take__WEBPACK_IMPORTED_MODULE_82__["take"]; }); + +/* harmony import */ var _internal_operators_takeLast__WEBPACK_IMPORTED_MODULE_83__ = __webpack_require__(271); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "takeLast", function() { return _internal_operators_takeLast__WEBPACK_IMPORTED_MODULE_83__["takeLast"]; }); + +/* harmony import */ var _internal_operators_takeUntil__WEBPACK_IMPORTED_MODULE_84__ = __webpack_require__(327); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "takeUntil", function() { return _internal_operators_takeUntil__WEBPACK_IMPORTED_MODULE_84__["takeUntil"]; }); + +/* harmony import */ var _internal_operators_takeWhile__WEBPACK_IMPORTED_MODULE_85__ = __webpack_require__(328); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "takeWhile", function() { return _internal_operators_takeWhile__WEBPACK_IMPORTED_MODULE_85__["takeWhile"]; }); + +/* harmony import */ var _internal_operators_tap__WEBPACK_IMPORTED_MODULE_86__ = __webpack_require__(329); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "tap", function() { return _internal_operators_tap__WEBPACK_IMPORTED_MODULE_86__["tap"]; }); + +/* harmony import */ var _internal_operators_throttle__WEBPACK_IMPORTED_MODULE_87__ = __webpack_require__(330); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "throttle", function() { return _internal_operators_throttle__WEBPACK_IMPORTED_MODULE_87__["throttle"]; }); + +/* harmony import */ var _internal_operators_throttleTime__WEBPACK_IMPORTED_MODULE_88__ = __webpack_require__(331); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "throttleTime", function() { return _internal_operators_throttleTime__WEBPACK_IMPORTED_MODULE_88__["throttleTime"]; }); + +/* harmony import */ var _internal_operators_throwIfEmpty__WEBPACK_IMPORTED_MODULE_89__ = __webpack_require__(252); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "throwIfEmpty", function() { return _internal_operators_throwIfEmpty__WEBPACK_IMPORTED_MODULE_89__["throwIfEmpty"]; }); + +/* harmony import */ var _internal_operators_timeInterval__WEBPACK_IMPORTED_MODULE_90__ = __webpack_require__(332); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "timeInterval", function() { return _internal_operators_timeInterval__WEBPACK_IMPORTED_MODULE_90__["timeInterval"]; }); + +/* harmony import */ var _internal_operators_timeout__WEBPACK_IMPORTED_MODULE_91__ = __webpack_require__(334); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "timeout", function() { return _internal_operators_timeout__WEBPACK_IMPORTED_MODULE_91__["timeout"]; }); + +/* harmony import */ var _internal_operators_timeoutWith__WEBPACK_IMPORTED_MODULE_92__ = __webpack_require__(336); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "timeoutWith", function() { return _internal_operators_timeoutWith__WEBPACK_IMPORTED_MODULE_92__["timeoutWith"]; }); + +/* harmony import */ var _internal_operators_timestamp__WEBPACK_IMPORTED_MODULE_93__ = __webpack_require__(337); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "timestamp", function() { return _internal_operators_timestamp__WEBPACK_IMPORTED_MODULE_93__["timestamp"]; }); + +/* harmony import */ var _internal_operators_toArray__WEBPACK_IMPORTED_MODULE_94__ = __webpack_require__(338); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "toArray", function() { return _internal_operators_toArray__WEBPACK_IMPORTED_MODULE_94__["toArray"]; }); + +/* harmony import */ var _internal_operators_window__WEBPACK_IMPORTED_MODULE_95__ = __webpack_require__(339); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "window", function() { return _internal_operators_window__WEBPACK_IMPORTED_MODULE_95__["window"]; }); + +/* harmony import */ var _internal_operators_windowCount__WEBPACK_IMPORTED_MODULE_96__ = __webpack_require__(340); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "windowCount", function() { return _internal_operators_windowCount__WEBPACK_IMPORTED_MODULE_96__["windowCount"]; }); + +/* harmony import */ var _internal_operators_windowTime__WEBPACK_IMPORTED_MODULE_97__ = __webpack_require__(341); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "windowTime", function() { return _internal_operators_windowTime__WEBPACK_IMPORTED_MODULE_97__["windowTime"]; }); + +/* harmony import */ var _internal_operators_windowToggle__WEBPACK_IMPORTED_MODULE_98__ = __webpack_require__(342); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "windowToggle", function() { return _internal_operators_windowToggle__WEBPACK_IMPORTED_MODULE_98__["windowToggle"]; }); + +/* harmony import */ var _internal_operators_windowWhen__WEBPACK_IMPORTED_MODULE_99__ = __webpack_require__(343); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "windowWhen", function() { return _internal_operators_windowWhen__WEBPACK_IMPORTED_MODULE_99__["windowWhen"]; }); + +/* harmony import */ var _internal_operators_withLatestFrom__WEBPACK_IMPORTED_MODULE_100__ = __webpack_require__(344); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "withLatestFrom", function() { return _internal_operators_withLatestFrom__WEBPACK_IMPORTED_MODULE_100__["withLatestFrom"]; }); + +/* harmony import */ var _internal_operators_zip__WEBPACK_IMPORTED_MODULE_101__ = __webpack_require__(345); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "zip", function() { return _internal_operators_zip__WEBPACK_IMPORTED_MODULE_101__["zip"]; }); + +/* harmony import */ var _internal_operators_zipAll__WEBPACK_IMPORTED_MODULE_102__ = __webpack_require__(347); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "zipAll", function() { return _internal_operators_zipAll__WEBPACK_IMPORTED_MODULE_102__["zipAll"]; }); + +/** PURE_IMPORTS_START PURE_IMPORTS_END */ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +//# sourceMappingURL=index.js.map + + +/***/ }), +/* 170 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "audit", function() { return audit; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(171); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(182); +/** PURE_IMPORTS_START tslib,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ + + + +function audit(durationSelector) { + return function auditOperatorFunction(source) { + return source.lift(new AuditOperator(durationSelector)); + }; +} +var AuditOperator = /*@__PURE__*/ (function () { + function AuditOperator(durationSelector) { + this.durationSelector = durationSelector; + } + AuditOperator.prototype.call = function (subscriber, source) { + return source.subscribe(new AuditSubscriber(subscriber, this.durationSelector)); + }; + return AuditOperator; +}()); +var AuditSubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](AuditSubscriber, _super); + function AuditSubscriber(destination, durationSelector) { + var _this = _super.call(this, destination) || this; + _this.durationSelector = durationSelector; + _this.hasValue = false; + return _this; + } + AuditSubscriber.prototype._next = function (value) { + this.value = value; + this.hasValue = true; + if (!this.throttled) { + var duration = void 0; + try { + var durationSelector = this.durationSelector; + duration = durationSelector(value); + } + catch (err) { + return this.destination.error(err); + } + var innerSubscription = Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__["subscribeToResult"])(this, duration); + if (!innerSubscription || innerSubscription.closed) { + this.clearThrottle(); + } + else { + this.add(this.throttled = innerSubscription); + } + } + }; + AuditSubscriber.prototype.clearThrottle = function () { + var _a = this, value = _a.value, hasValue = _a.hasValue, throttled = _a.throttled; + if (throttled) { + this.remove(throttled); + this.throttled = null; + throttled.unsubscribe(); + } + if (hasValue) { + this.value = null; + this.hasValue = false; + this.destination.next(value); + } + }; + AuditSubscriber.prototype.notifyNext = function (outerValue, innerValue, outerIndex, innerIndex) { + this.clearThrottle(); + }; + AuditSubscriber.prototype.notifyComplete = function () { + this.clearThrottle(); + }; + return AuditSubscriber; +}(_OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__["OuterSubscriber"])); +//# sourceMappingURL=audit.js.map + + +/***/ }), +/* 171 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "OuterSubscriber", function() { return OuterSubscriber; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(172); +/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ + + +var OuterSubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](OuterSubscriber, _super); + function OuterSubscriber() { + return _super !== null && _super.apply(this, arguments) || this; + } + OuterSubscriber.prototype.notifyNext = function (outerValue, innerValue, outerIndex, innerIndex, innerSub) { + this.destination.next(innerValue); + }; + OuterSubscriber.prototype.notifyError = function (error, innerSub) { + this.destination.error(error); + }; + OuterSubscriber.prototype.notifyComplete = function (innerSub) { + this.destination.complete(); + }; + return OuterSubscriber; +}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); + +//# sourceMappingURL=OuterSubscriber.js.map + + +/***/ }), +/* 172 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Subscriber", function() { return Subscriber; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "SafeSubscriber", function() { return SafeSubscriber; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _util_isFunction__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(173); +/* harmony import */ var _Observer__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(174); +/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(177); +/* harmony import */ var _internal_symbol_rxSubscriber__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(181); +/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(175); +/* harmony import */ var _util_hostReportError__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(176); +/** PURE_IMPORTS_START tslib,_util_isFunction,_Observer,_Subscription,_internal_symbol_rxSubscriber,_config,_util_hostReportError PURE_IMPORTS_END */ + + + + + + + +var Subscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](Subscriber, _super); + function Subscriber(destinationOrNext, error, complete) { + var _this = _super.call(this) || this; + _this.syncErrorValue = null; + _this.syncErrorThrown = false; + _this.syncErrorThrowable = false; + _this.isStopped = false; + switch (arguments.length) { + case 0: + _this.destination = _Observer__WEBPACK_IMPORTED_MODULE_2__["empty"]; + break; + case 1: + if (!destinationOrNext) { + _this.destination = _Observer__WEBPACK_IMPORTED_MODULE_2__["empty"]; + break; + } + if (typeof destinationOrNext === 'object') { + if (destinationOrNext instanceof Subscriber) { + _this.syncErrorThrowable = destinationOrNext.syncErrorThrowable; + _this.destination = destinationOrNext; + destinationOrNext.add(_this); + } + else { + _this.syncErrorThrowable = true; + _this.destination = new SafeSubscriber(_this, destinationOrNext); + } + break; + } + default: + _this.syncErrorThrowable = true; + _this.destination = new SafeSubscriber(_this, destinationOrNext, error, complete); + break; + } + return _this; + } + Subscriber.prototype[_internal_symbol_rxSubscriber__WEBPACK_IMPORTED_MODULE_4__["rxSubscriber"]] = function () { return this; }; + Subscriber.create = function (next, error, complete) { + var subscriber = new Subscriber(next, error, complete); + subscriber.syncErrorThrowable = false; + return subscriber; + }; + Subscriber.prototype.next = function (value) { + if (!this.isStopped) { + this._next(value); + } + }; + Subscriber.prototype.error = function (err) { + if (!this.isStopped) { + this.isStopped = true; + this._error(err); + } + }; + Subscriber.prototype.complete = function () { + if (!this.isStopped) { + this.isStopped = true; + this._complete(); + } + }; + Subscriber.prototype.unsubscribe = function () { + if (this.closed) { + return; + } + this.isStopped = true; + _super.prototype.unsubscribe.call(this); + }; + Subscriber.prototype._next = function (value) { + this.destination.next(value); + }; + Subscriber.prototype._error = function (err) { + this.destination.error(err); + this.unsubscribe(); + }; + Subscriber.prototype._complete = function () { + this.destination.complete(); + this.unsubscribe(); + }; + Subscriber.prototype._unsubscribeAndRecycle = function () { + var _parentOrParents = this._parentOrParents; + this._parentOrParents = null; + this.unsubscribe(); + this.closed = false; + this.isStopped = false; + this._parentOrParents = _parentOrParents; + return this; + }; + return Subscriber; +}(_Subscription__WEBPACK_IMPORTED_MODULE_3__["Subscription"])); + +var SafeSubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](SafeSubscriber, _super); + function SafeSubscriber(_parentSubscriber, observerOrNext, error, complete) { + var _this = _super.call(this) || this; + _this._parentSubscriber = _parentSubscriber; + var next; + var context = _this; + if (Object(_util_isFunction__WEBPACK_IMPORTED_MODULE_1__["isFunction"])(observerOrNext)) { + next = observerOrNext; + } + else if (observerOrNext) { + next = observerOrNext.next; + error = observerOrNext.error; + complete = observerOrNext.complete; + if (observerOrNext !== _Observer__WEBPACK_IMPORTED_MODULE_2__["empty"]) { + context = Object.create(observerOrNext); + if (Object(_util_isFunction__WEBPACK_IMPORTED_MODULE_1__["isFunction"])(context.unsubscribe)) { + _this.add(context.unsubscribe.bind(context)); + } + context.unsubscribe = _this.unsubscribe.bind(_this); + } + } + _this._context = context; + _this._next = next; + _this._error = error; + _this._complete = complete; + return _this; + } + SafeSubscriber.prototype.next = function (value) { + if (!this.isStopped && this._next) { + var _parentSubscriber = this._parentSubscriber; + if (!_config__WEBPACK_IMPORTED_MODULE_5__["config"].useDeprecatedSynchronousErrorHandling || !_parentSubscriber.syncErrorThrowable) { + this.__tryOrUnsub(this._next, value); + } + else if (this.__tryOrSetError(_parentSubscriber, this._next, value)) { + this.unsubscribe(); + } + } + }; + SafeSubscriber.prototype.error = function (err) { + if (!this.isStopped) { + var _parentSubscriber = this._parentSubscriber; + var useDeprecatedSynchronousErrorHandling = _config__WEBPACK_IMPORTED_MODULE_5__["config"].useDeprecatedSynchronousErrorHandling; + if (this._error) { + if (!useDeprecatedSynchronousErrorHandling || !_parentSubscriber.syncErrorThrowable) { + this.__tryOrUnsub(this._error, err); + this.unsubscribe(); + } + else { + this.__tryOrSetError(_parentSubscriber, this._error, err); + this.unsubscribe(); + } + } + else if (!_parentSubscriber.syncErrorThrowable) { + this.unsubscribe(); + if (useDeprecatedSynchronousErrorHandling) { + throw err; + } + Object(_util_hostReportError__WEBPACK_IMPORTED_MODULE_6__["hostReportError"])(err); + } + else { + if (useDeprecatedSynchronousErrorHandling) { + _parentSubscriber.syncErrorValue = err; + _parentSubscriber.syncErrorThrown = true; + } + else { + Object(_util_hostReportError__WEBPACK_IMPORTED_MODULE_6__["hostReportError"])(err); + } + this.unsubscribe(); + } + } + }; + SafeSubscriber.prototype.complete = function () { + var _this = this; + if (!this.isStopped) { + var _parentSubscriber = this._parentSubscriber; + if (this._complete) { + var wrappedComplete = function () { return _this._complete.call(_this._context); }; + if (!_config__WEBPACK_IMPORTED_MODULE_5__["config"].useDeprecatedSynchronousErrorHandling || !_parentSubscriber.syncErrorThrowable) { + this.__tryOrUnsub(wrappedComplete); + this.unsubscribe(); + } + else { + this.__tryOrSetError(_parentSubscriber, wrappedComplete); + this.unsubscribe(); + } + } + else { + this.unsubscribe(); + } + } + }; + SafeSubscriber.prototype.__tryOrUnsub = function (fn, value) { + try { + fn.call(this._context, value); + } + catch (err) { + this.unsubscribe(); + if (_config__WEBPACK_IMPORTED_MODULE_5__["config"].useDeprecatedSynchronousErrorHandling) { + throw err; + } + else { + Object(_util_hostReportError__WEBPACK_IMPORTED_MODULE_6__["hostReportError"])(err); + } + } + }; + SafeSubscriber.prototype.__tryOrSetError = function (parent, fn, value) { + if (!_config__WEBPACK_IMPORTED_MODULE_5__["config"].useDeprecatedSynchronousErrorHandling) { + throw new Error('bad call'); + } + try { + fn.call(this._context, value); + } + catch (err) { + if (_config__WEBPACK_IMPORTED_MODULE_5__["config"].useDeprecatedSynchronousErrorHandling) { + parent.syncErrorValue = err; + parent.syncErrorThrown = true; + return true; + } + else { + Object(_util_hostReportError__WEBPACK_IMPORTED_MODULE_6__["hostReportError"])(err); + return true; + } + } + return false; + }; + SafeSubscriber.prototype._unsubscribe = function () { + var _parentSubscriber = this._parentSubscriber; + this._context = null; + this._parentSubscriber = null; + _parentSubscriber.unsubscribe(); + }; + return SafeSubscriber; +}(Subscriber)); + +//# sourceMappingURL=Subscriber.js.map + + +/***/ }), +/* 173 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "isFunction", function() { return isFunction; }); +/** PURE_IMPORTS_START PURE_IMPORTS_END */ +function isFunction(x) { + return typeof x === 'function'; +} +//# sourceMappingURL=isFunction.js.map + + +/***/ }), +/* 174 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "empty", function() { return empty; }); +/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(175); +/* harmony import */ var _util_hostReportError__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(176); +/** PURE_IMPORTS_START _config,_util_hostReportError PURE_IMPORTS_END */ + + +var empty = { + closed: true, + next: function (value) { }, + error: function (err) { + if (_config__WEBPACK_IMPORTED_MODULE_0__["config"].useDeprecatedSynchronousErrorHandling) { + throw err; + } + else { + Object(_util_hostReportError__WEBPACK_IMPORTED_MODULE_1__["hostReportError"])(err); + } + }, + complete: function () { } +}; +//# sourceMappingURL=Observer.js.map + + +/***/ }), +/* 175 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "config", function() { return config; }); +/** PURE_IMPORTS_START PURE_IMPORTS_END */ +var _enable_super_gross_mode_that_will_cause_bad_things = false; +var config = { + Promise: undefined, + set useDeprecatedSynchronousErrorHandling(value) { + if (value) { + var error = /*@__PURE__*/ new Error(); + /*@__PURE__*/ console.warn('DEPRECATED! RxJS was set to use deprecated synchronous error handling behavior by code at: \n' + error.stack); + } + else if (_enable_super_gross_mode_that_will_cause_bad_things) { + /*@__PURE__*/ console.log('RxJS: Back to a better error behavior. Thank you. <3'); + } + _enable_super_gross_mode_that_will_cause_bad_things = value; + }, + get useDeprecatedSynchronousErrorHandling() { + return _enable_super_gross_mode_that_will_cause_bad_things; + }, +}; +//# sourceMappingURL=config.js.map + + +/***/ }), +/* 176 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "hostReportError", function() { return hostReportError; }); +/** PURE_IMPORTS_START PURE_IMPORTS_END */ +function hostReportError(err) { + setTimeout(function () { throw err; }, 0); +} +//# sourceMappingURL=hostReportError.js.map + + +/***/ }), +/* 177 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Subscription", function() { return Subscription; }); +/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(178); +/* harmony import */ var _util_isObject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(179); +/* harmony import */ var _util_isFunction__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(173); +/* harmony import */ var _util_UnsubscriptionError__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(180); +/** PURE_IMPORTS_START _util_isArray,_util_isObject,_util_isFunction,_util_UnsubscriptionError PURE_IMPORTS_END */ + + + + +var Subscription = /*@__PURE__*/ (function () { + function Subscription(unsubscribe) { + this.closed = false; + this._parentOrParents = null; + this._subscriptions = null; + if (unsubscribe) { + this._unsubscribe = unsubscribe; + } + } + Subscription.prototype.unsubscribe = function () { + var errors; + if (this.closed) { + return; + } + var _a = this, _parentOrParents = _a._parentOrParents, _unsubscribe = _a._unsubscribe, _subscriptions = _a._subscriptions; + this.closed = true; + this._parentOrParents = null; + this._subscriptions = null; + if (_parentOrParents instanceof Subscription) { + _parentOrParents.remove(this); + } + else if (_parentOrParents !== null) { + for (var index = 0; index < _parentOrParents.length; ++index) { + var parent_1 = _parentOrParents[index]; + parent_1.remove(this); + } + } + if (Object(_util_isFunction__WEBPACK_IMPORTED_MODULE_2__["isFunction"])(_unsubscribe)) { + try { + _unsubscribe.call(this); + } + catch (e) { + errors = e instanceof _util_UnsubscriptionError__WEBPACK_IMPORTED_MODULE_3__["UnsubscriptionError"] ? flattenUnsubscriptionErrors(e.errors) : [e]; + } + } + if (Object(_util_isArray__WEBPACK_IMPORTED_MODULE_0__["isArray"])(_subscriptions)) { + var index = -1; + var len = _subscriptions.length; + while (++index < len) { + var sub = _subscriptions[index]; + if (Object(_util_isObject__WEBPACK_IMPORTED_MODULE_1__["isObject"])(sub)) { + try { + sub.unsubscribe(); + } + catch (e) { + errors = errors || []; + if (e instanceof _util_UnsubscriptionError__WEBPACK_IMPORTED_MODULE_3__["UnsubscriptionError"]) { + errors = errors.concat(flattenUnsubscriptionErrors(e.errors)); + } + else { + errors.push(e); + } + } + } + } + } + if (errors) { + throw new _util_UnsubscriptionError__WEBPACK_IMPORTED_MODULE_3__["UnsubscriptionError"](errors); + } + }; + Subscription.prototype.add = function (teardown) { + var subscription = teardown; + if (!teardown) { + return Subscription.EMPTY; + } + switch (typeof teardown) { + case 'function': + subscription = new Subscription(teardown); + case 'object': + if (subscription === this || subscription.closed || typeof subscription.unsubscribe !== 'function') { + return subscription; + } + else if (this.closed) { + subscription.unsubscribe(); + return subscription; + } + else if (!(subscription instanceof Subscription)) { + var tmp = subscription; + subscription = new Subscription(); + subscription._subscriptions = [tmp]; + } + break; + default: { + throw new Error('unrecognized teardown ' + teardown + ' added to Subscription.'); + } + } + var _parentOrParents = subscription._parentOrParents; + if (_parentOrParents === null) { + subscription._parentOrParents = this; + } + else if (_parentOrParents instanceof Subscription) { + if (_parentOrParents === this) { + return subscription; + } + subscription._parentOrParents = [_parentOrParents, this]; + } + else if (_parentOrParents.indexOf(this) === -1) { + _parentOrParents.push(this); + } + else { + return subscription; + } + var subscriptions = this._subscriptions; + if (subscriptions === null) { + this._subscriptions = [subscription]; + } + else { + subscriptions.push(subscription); + } + return subscription; + }; + Subscription.prototype.remove = function (subscription) { + var subscriptions = this._subscriptions; + if (subscriptions) { + var subscriptionIndex = subscriptions.indexOf(subscription); + if (subscriptionIndex !== -1) { + subscriptions.splice(subscriptionIndex, 1); + } + } + }; + Subscription.EMPTY = (function (empty) { + empty.closed = true; + return empty; + }(new Subscription())); + return Subscription; +}()); + +function flattenUnsubscriptionErrors(errors) { + return errors.reduce(function (errs, err) { return errs.concat((err instanceof _util_UnsubscriptionError__WEBPACK_IMPORTED_MODULE_3__["UnsubscriptionError"]) ? err.errors : err); }, []); +} +//# sourceMappingURL=Subscription.js.map + + +/***/ }), +/* 178 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "isArray", function() { return isArray; }); +/** PURE_IMPORTS_START PURE_IMPORTS_END */ +var isArray = /*@__PURE__*/ (function () { return Array.isArray || (function (x) { return x && typeof x.length === 'number'; }); })(); +//# sourceMappingURL=isArray.js.map + + +/***/ }), +/* 179 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "isObject", function() { return isObject; }); +/** PURE_IMPORTS_START PURE_IMPORTS_END */ +function isObject(x) { + return x !== null && typeof x === 'object'; +} +//# sourceMappingURL=isObject.js.map + + +/***/ }), +/* 180 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "UnsubscriptionError", function() { return UnsubscriptionError; }); +/** PURE_IMPORTS_START PURE_IMPORTS_END */ +var UnsubscriptionErrorImpl = /*@__PURE__*/ (function () { + function UnsubscriptionErrorImpl(errors) { + Error.call(this); + this.message = errors ? + errors.length + " errors occurred during unsubscription:\n" + errors.map(function (err, i) { return i + 1 + ") " + err.toString(); }).join('\n ') : ''; + this.name = 'UnsubscriptionError'; + this.errors = errors; + return this; + } + UnsubscriptionErrorImpl.prototype = /*@__PURE__*/ Object.create(Error.prototype); + return UnsubscriptionErrorImpl; +})(); +var UnsubscriptionError = UnsubscriptionErrorImpl; +//# sourceMappingURL=UnsubscriptionError.js.map + + +/***/ }), +/* 181 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "rxSubscriber", function() { return rxSubscriber; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "$$rxSubscriber", function() { return $$rxSubscriber; }); +/** PURE_IMPORTS_START PURE_IMPORTS_END */ +var rxSubscriber = /*@__PURE__*/ (function () { + return typeof Symbol === 'function' + ? /*@__PURE__*/ Symbol('rxSubscriber') + : '@@rxSubscriber_' + /*@__PURE__*/ Math.random(); +})(); +var $$rxSubscriber = rxSubscriber; +//# sourceMappingURL=rxSubscriber.js.map + + +/***/ }), +/* 182 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "subscribeToResult", function() { return subscribeToResult; }); +/* harmony import */ var _InnerSubscriber__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(183); +/* harmony import */ var _subscribeTo__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(184); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(193); +/** PURE_IMPORTS_START _InnerSubscriber,_subscribeTo,_Observable PURE_IMPORTS_END */ + + + +function subscribeToResult(outerSubscriber, result, outerValue, outerIndex, destination) { + if (destination === void 0) { + destination = new _InnerSubscriber__WEBPACK_IMPORTED_MODULE_0__["InnerSubscriber"](outerSubscriber, outerValue, outerIndex); + } + if (destination.closed) { + return undefined; + } + if (result instanceof _Observable__WEBPACK_IMPORTED_MODULE_2__["Observable"]) { + return result.subscribe(destination); + } + return Object(_subscribeTo__WEBPACK_IMPORTED_MODULE_1__["subscribeTo"])(result)(destination); +} +//# sourceMappingURL=subscribeToResult.js.map + + +/***/ }), +/* 183 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "InnerSubscriber", function() { return InnerSubscriber; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(172); +/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ + + +var InnerSubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](InnerSubscriber, _super); + function InnerSubscriber(parent, outerValue, outerIndex) { + var _this = _super.call(this) || this; + _this.parent = parent; + _this.outerValue = outerValue; + _this.outerIndex = outerIndex; + _this.index = 0; + return _this; + } + InnerSubscriber.prototype._next = function (value) { + this.parent.notifyNext(this.outerValue, value, this.outerIndex, this.index++, this); + }; + InnerSubscriber.prototype._error = function (error) { + this.parent.notifyError(error, this); + this.unsubscribe(); + }; + InnerSubscriber.prototype._complete = function () { + this.parent.notifyComplete(this); + this.unsubscribe(); + }; + return InnerSubscriber; +}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); + +//# sourceMappingURL=InnerSubscriber.js.map + + +/***/ }), +/* 184 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "subscribeTo", function() { return subscribeTo; }); +/* harmony import */ var _subscribeToArray__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(185); +/* harmony import */ var _subscribeToPromise__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(186); +/* harmony import */ var _subscribeToIterable__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(187); +/* harmony import */ var _subscribeToObservable__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(189); +/* harmony import */ var _isArrayLike__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(191); +/* harmony import */ var _isPromise__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(192); +/* harmony import */ var _isObject__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(179); +/* harmony import */ var _symbol_iterator__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(188); +/* harmony import */ var _symbol_observable__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(190); +/** PURE_IMPORTS_START _subscribeToArray,_subscribeToPromise,_subscribeToIterable,_subscribeToObservable,_isArrayLike,_isPromise,_isObject,_symbol_iterator,_symbol_observable PURE_IMPORTS_END */ + + + + + + + + + +var subscribeTo = function (result) { + if (!!result && typeof result[_symbol_observable__WEBPACK_IMPORTED_MODULE_8__["observable"]] === 'function') { + return Object(_subscribeToObservable__WEBPACK_IMPORTED_MODULE_3__["subscribeToObservable"])(result); + } + else if (Object(_isArrayLike__WEBPACK_IMPORTED_MODULE_4__["isArrayLike"])(result)) { + return Object(_subscribeToArray__WEBPACK_IMPORTED_MODULE_0__["subscribeToArray"])(result); + } + else if (Object(_isPromise__WEBPACK_IMPORTED_MODULE_5__["isPromise"])(result)) { + return Object(_subscribeToPromise__WEBPACK_IMPORTED_MODULE_1__["subscribeToPromise"])(result); + } + else if (!!result && typeof result[_symbol_iterator__WEBPACK_IMPORTED_MODULE_7__["iterator"]] === 'function') { + return Object(_subscribeToIterable__WEBPACK_IMPORTED_MODULE_2__["subscribeToIterable"])(result); + } + else { + var value = Object(_isObject__WEBPACK_IMPORTED_MODULE_6__["isObject"])(result) ? 'an invalid object' : "'" + result + "'"; + var msg = "You provided " + value + " where a stream was expected." + + ' You can provide an Observable, Promise, Array, or Iterable.'; + throw new TypeError(msg); + } +}; +//# sourceMappingURL=subscribeTo.js.map + + +/***/ }), +/* 185 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "subscribeToArray", function() { return subscribeToArray; }); +/** PURE_IMPORTS_START PURE_IMPORTS_END */ +var subscribeToArray = function (array) { + return function (subscriber) { + for (var i = 0, len = array.length; i < len && !subscriber.closed; i++) { + subscriber.next(array[i]); + } + subscriber.complete(); + }; +}; +//# sourceMappingURL=subscribeToArray.js.map + + +/***/ }), +/* 186 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "subscribeToPromise", function() { return subscribeToPromise; }); +/* harmony import */ var _hostReportError__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(176); +/** PURE_IMPORTS_START _hostReportError PURE_IMPORTS_END */ + +var subscribeToPromise = function (promise) { + return function (subscriber) { + promise.then(function (value) { + if (!subscriber.closed) { + subscriber.next(value); + subscriber.complete(); + } + }, function (err) { return subscriber.error(err); }) + .then(null, _hostReportError__WEBPACK_IMPORTED_MODULE_0__["hostReportError"]); + return subscriber; + }; +}; +//# sourceMappingURL=subscribeToPromise.js.map + + +/***/ }), +/* 187 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "subscribeToIterable", function() { return subscribeToIterable; }); +/* harmony import */ var _symbol_iterator__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(188); +/** PURE_IMPORTS_START _symbol_iterator PURE_IMPORTS_END */ + +var subscribeToIterable = function (iterable) { + return function (subscriber) { + var iterator = iterable[_symbol_iterator__WEBPACK_IMPORTED_MODULE_0__["iterator"]](); + do { + var item = iterator.next(); + if (item.done) { + subscriber.complete(); + break; + } + subscriber.next(item.value); + if (subscriber.closed) { + break; + } + } while (true); + if (typeof iterator.return === 'function') { + subscriber.add(function () { + if (iterator.return) { + iterator.return(); + } + }); + } + return subscriber; + }; +}; +//# sourceMappingURL=subscribeToIterable.js.map + + +/***/ }), +/* 188 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "getSymbolIterator", function() { return getSymbolIterator; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "iterator", function() { return iterator; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "$$iterator", function() { return $$iterator; }); +/** PURE_IMPORTS_START PURE_IMPORTS_END */ +function getSymbolIterator() { + if (typeof Symbol !== 'function' || !Symbol.iterator) { + return '@@iterator'; + } + return Symbol.iterator; +} +var iterator = /*@__PURE__*/ getSymbolIterator(); +var $$iterator = iterator; +//# sourceMappingURL=iterator.js.map + + +/***/ }), +/* 189 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "subscribeToObservable", function() { return subscribeToObservable; }); +/* harmony import */ var _symbol_observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(190); +/** PURE_IMPORTS_START _symbol_observable PURE_IMPORTS_END */ + +var subscribeToObservable = function (obj) { + return function (subscriber) { + var obs = obj[_symbol_observable__WEBPACK_IMPORTED_MODULE_0__["observable"]](); + if (typeof obs.subscribe !== 'function') { + throw new TypeError('Provided object does not correctly implement Symbol.observable'); + } + else { + return obs.subscribe(subscriber); + } + }; +}; +//# sourceMappingURL=subscribeToObservable.js.map + + +/***/ }), +/* 190 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "observable", function() { return observable; }); +/** PURE_IMPORTS_START PURE_IMPORTS_END */ +var observable = /*@__PURE__*/ (function () { return typeof Symbol === 'function' && Symbol.observable || '@@observable'; })(); +//# sourceMappingURL=observable.js.map + + +/***/ }), +/* 191 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "isArrayLike", function() { return isArrayLike; }); +/** PURE_IMPORTS_START PURE_IMPORTS_END */ +var isArrayLike = (function (x) { return x && typeof x.length === 'number' && typeof x !== 'function'; }); +//# sourceMappingURL=isArrayLike.js.map + + +/***/ }), +/* 192 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "isPromise", function() { return isPromise; }); +/** PURE_IMPORTS_START PURE_IMPORTS_END */ +function isPromise(value) { + return !!value && typeof value.subscribe !== 'function' && typeof value.then === 'function'; +} +//# sourceMappingURL=isPromise.js.map + + +/***/ }), +/* 193 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Observable", function() { return Observable; }); +/* harmony import */ var _util_canReportError__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(194); +/* harmony import */ var _util_toSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(195); +/* harmony import */ var _symbol_observable__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(190); +/* harmony import */ var _util_pipe__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(196); +/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(175); +/** PURE_IMPORTS_START _util_canReportError,_util_toSubscriber,_symbol_observable,_util_pipe,_config PURE_IMPORTS_END */ + + + + + +var Observable = /*@__PURE__*/ (function () { + function Observable(subscribe) { + this._isScalar = false; + if (subscribe) { + this._subscribe = subscribe; + } + } + Observable.prototype.lift = function (operator) { + var observable = new Observable(); + observable.source = this; + observable.operator = operator; + return observable; + }; + Observable.prototype.subscribe = function (observerOrNext, error, complete) { + var operator = this.operator; + var sink = Object(_util_toSubscriber__WEBPACK_IMPORTED_MODULE_1__["toSubscriber"])(observerOrNext, error, complete); + if (operator) { + sink.add(operator.call(sink, this.source)); + } + else { + sink.add(this.source || (_config__WEBPACK_IMPORTED_MODULE_4__["config"].useDeprecatedSynchronousErrorHandling && !sink.syncErrorThrowable) ? + this._subscribe(sink) : + this._trySubscribe(sink)); + } + if (_config__WEBPACK_IMPORTED_MODULE_4__["config"].useDeprecatedSynchronousErrorHandling) { + if (sink.syncErrorThrowable) { + sink.syncErrorThrowable = false; + if (sink.syncErrorThrown) { + throw sink.syncErrorValue; + } + } + } + return sink; + }; + Observable.prototype._trySubscribe = function (sink) { + try { + return this._subscribe(sink); + } + catch (err) { + if (_config__WEBPACK_IMPORTED_MODULE_4__["config"].useDeprecatedSynchronousErrorHandling) { + sink.syncErrorThrown = true; + sink.syncErrorValue = err; + } + if (Object(_util_canReportError__WEBPACK_IMPORTED_MODULE_0__["canReportError"])(sink)) { + sink.error(err); + } + else { + console.warn(err); + } + } + }; + Observable.prototype.forEach = function (next, promiseCtor) { + var _this = this; + promiseCtor = getPromiseCtor(promiseCtor); + return new promiseCtor(function (resolve, reject) { + var subscription; + subscription = _this.subscribe(function (value) { + try { + next(value); + } + catch (err) { + reject(err); + if (subscription) { + subscription.unsubscribe(); + } + } + }, reject, resolve); + }); + }; + Observable.prototype._subscribe = function (subscriber) { + var source = this.source; + return source && source.subscribe(subscriber); + }; + Observable.prototype[_symbol_observable__WEBPACK_IMPORTED_MODULE_2__["observable"]] = function () { + return this; + }; + Observable.prototype.pipe = function () { + var operations = []; + for (var _i = 0; _i < arguments.length; _i++) { + operations[_i] = arguments[_i]; + } + if (operations.length === 0) { + return this; + } + return Object(_util_pipe__WEBPACK_IMPORTED_MODULE_3__["pipeFromArray"])(operations)(this); + }; + Observable.prototype.toPromise = function (promiseCtor) { + var _this = this; + promiseCtor = getPromiseCtor(promiseCtor); + return new promiseCtor(function (resolve, reject) { + var value; + _this.subscribe(function (x) { return value = x; }, function (err) { return reject(err); }, function () { return resolve(value); }); + }); + }; + Observable.create = function (subscribe) { + return new Observable(subscribe); + }; + return Observable; +}()); + +function getPromiseCtor(promiseCtor) { + if (!promiseCtor) { + promiseCtor = _config__WEBPACK_IMPORTED_MODULE_4__["config"].Promise || Promise; + } + if (!promiseCtor) { + throw new Error('no Promise impl found'); + } + return promiseCtor; +} +//# sourceMappingURL=Observable.js.map + + +/***/ }), +/* 194 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "canReportError", function() { return canReportError; }); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(172); +/** PURE_IMPORTS_START _Subscriber PURE_IMPORTS_END */ + +function canReportError(observer) { + while (observer) { + var _a = observer, closed_1 = _a.closed, destination = _a.destination, isStopped = _a.isStopped; + if (closed_1 || isStopped) { + return false; + } + else if (destination && destination instanceof _Subscriber__WEBPACK_IMPORTED_MODULE_0__["Subscriber"]) { + observer = destination; + } + else { + observer = null; + } + } + return true; +} +//# sourceMappingURL=canReportError.js.map + + +/***/ }), +/* 195 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "toSubscriber", function() { return toSubscriber; }); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(172); +/* harmony import */ var _symbol_rxSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(181); +/* harmony import */ var _Observer__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(174); +/** PURE_IMPORTS_START _Subscriber,_symbol_rxSubscriber,_Observer PURE_IMPORTS_END */ + + + +function toSubscriber(nextOrObserver, error, complete) { + if (nextOrObserver) { + if (nextOrObserver instanceof _Subscriber__WEBPACK_IMPORTED_MODULE_0__["Subscriber"]) { + return nextOrObserver; + } + if (nextOrObserver[_symbol_rxSubscriber__WEBPACK_IMPORTED_MODULE_1__["rxSubscriber"]]) { + return nextOrObserver[_symbol_rxSubscriber__WEBPACK_IMPORTED_MODULE_1__["rxSubscriber"]](); + } + } + if (!nextOrObserver && !error && !complete) { + return new _Subscriber__WEBPACK_IMPORTED_MODULE_0__["Subscriber"](_Observer__WEBPACK_IMPORTED_MODULE_2__["empty"]); + } + return new _Subscriber__WEBPACK_IMPORTED_MODULE_0__["Subscriber"](nextOrObserver, error, complete); +} +//# sourceMappingURL=toSubscriber.js.map + + +/***/ }), +/* 196 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "pipe", function() { return pipe; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "pipeFromArray", function() { return pipeFromArray; }); +/* harmony import */ var _noop__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(197); +/** PURE_IMPORTS_START _noop PURE_IMPORTS_END */ + +function pipe() { + var fns = []; + for (var _i = 0; _i < arguments.length; _i++) { + fns[_i] = arguments[_i]; + } + return pipeFromArray(fns); +} +function pipeFromArray(fns) { + if (!fns) { + return _noop__WEBPACK_IMPORTED_MODULE_0__["noop"]; + } + if (fns.length === 1) { + return fns[0]; + } + return function piped(input) { + return fns.reduce(function (prev, fn) { return fn(prev); }, input); + }; +} +//# sourceMappingURL=pipe.js.map + + +/***/ }), +/* 197 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "noop", function() { return noop; }); +/** PURE_IMPORTS_START PURE_IMPORTS_END */ +function noop() { } +//# sourceMappingURL=noop.js.map + + +/***/ }), +/* 198 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "auditTime", function() { return auditTime; }); +/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); +/* harmony import */ var _audit__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(170); +/* harmony import */ var _observable_timer__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(204); +/** PURE_IMPORTS_START _scheduler_async,_audit,_observable_timer PURE_IMPORTS_END */ + + + +function auditTime(duration, scheduler) { + if (scheduler === void 0) { + scheduler = _scheduler_async__WEBPACK_IMPORTED_MODULE_0__["async"]; + } + return Object(_audit__WEBPACK_IMPORTED_MODULE_1__["audit"])(function () { return Object(_observable_timer__WEBPACK_IMPORTED_MODULE_2__["timer"])(duration, scheduler); }); +} +//# sourceMappingURL=auditTime.js.map + + +/***/ }), +/* 199 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "async", function() { return async; }); +/* harmony import */ var _AsyncAction__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(200); +/* harmony import */ var _AsyncScheduler__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(202); +/** PURE_IMPORTS_START _AsyncAction,_AsyncScheduler PURE_IMPORTS_END */ + + +var async = /*@__PURE__*/ new _AsyncScheduler__WEBPACK_IMPORTED_MODULE_1__["AsyncScheduler"](_AsyncAction__WEBPACK_IMPORTED_MODULE_0__["AsyncAction"]); +//# sourceMappingURL=async.js.map + + +/***/ }), +/* 200 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "AsyncAction", function() { return AsyncAction; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _Action__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(201); +/** PURE_IMPORTS_START tslib,_Action PURE_IMPORTS_END */ + + +var AsyncAction = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](AsyncAction, _super); + function AsyncAction(scheduler, work) { + var _this = _super.call(this, scheduler, work) || this; + _this.scheduler = scheduler; + _this.work = work; + _this.pending = false; + return _this; + } + AsyncAction.prototype.schedule = function (state, delay) { + if (delay === void 0) { + delay = 0; + } + if (this.closed) { + return this; + } + this.state = state; + var id = this.id; + var scheduler = this.scheduler; + if (id != null) { + this.id = this.recycleAsyncId(scheduler, id, delay); + } + this.pending = true; + this.delay = delay; + this.id = this.id || this.requestAsyncId(scheduler, this.id, delay); + return this; + }; + AsyncAction.prototype.requestAsyncId = function (scheduler, id, delay) { + if (delay === void 0) { + delay = 0; + } + return setInterval(scheduler.flush.bind(scheduler, this), delay); + }; + AsyncAction.prototype.recycleAsyncId = function (scheduler, id, delay) { + if (delay === void 0) { + delay = 0; + } + if (delay !== null && this.delay === delay && this.pending === false) { + return id; + } + clearInterval(id); + return undefined; + }; + AsyncAction.prototype.execute = function (state, delay) { + if (this.closed) { + return new Error('executing a cancelled action'); + } + this.pending = false; + var error = this._execute(state, delay); + if (error) { + return error; + } + else if (this.pending === false && this.id != null) { + this.id = this.recycleAsyncId(this.scheduler, this.id, null); + } + }; + AsyncAction.prototype._execute = function (state, delay) { + var errored = false; + var errorValue = undefined; + try { + this.work(state); + } + catch (e) { + errored = true; + errorValue = !!e && e || new Error(e); + } + if (errored) { + this.unsubscribe(); + return errorValue; + } + }; + AsyncAction.prototype._unsubscribe = function () { + var id = this.id; + var scheduler = this.scheduler; + var actions = scheduler.actions; + var index = actions.indexOf(this); + this.work = null; + this.state = null; + this.pending = false; + this.scheduler = null; + if (index !== -1) { + actions.splice(index, 1); + } + if (id != null) { + this.id = this.recycleAsyncId(scheduler, id, null); + } + this.delay = null; + }; + return AsyncAction; +}(_Action__WEBPACK_IMPORTED_MODULE_1__["Action"])); + +//# sourceMappingURL=AsyncAction.js.map + + +/***/ }), +/* 201 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Action", function() { return Action; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(177); +/** PURE_IMPORTS_START tslib,_Subscription PURE_IMPORTS_END */ + + +var Action = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](Action, _super); + function Action(scheduler, work) { + return _super.call(this) || this; + } + Action.prototype.schedule = function (state, delay) { + if (delay === void 0) { + delay = 0; + } + return this; + }; + return Action; +}(_Subscription__WEBPACK_IMPORTED_MODULE_1__["Subscription"])); + +//# sourceMappingURL=Action.js.map + + +/***/ }), +/* 202 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "AsyncScheduler", function() { return AsyncScheduler; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _Scheduler__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(203); +/** PURE_IMPORTS_START tslib,_Scheduler PURE_IMPORTS_END */ + + +var AsyncScheduler = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](AsyncScheduler, _super); + function AsyncScheduler(SchedulerAction, now) { + if (now === void 0) { + now = _Scheduler__WEBPACK_IMPORTED_MODULE_1__["Scheduler"].now; + } + var _this = _super.call(this, SchedulerAction, function () { + if (AsyncScheduler.delegate && AsyncScheduler.delegate !== _this) { + return AsyncScheduler.delegate.now(); + } + else { + return now(); + } + }) || this; + _this.actions = []; + _this.active = false; + _this.scheduled = undefined; + return _this; + } + AsyncScheduler.prototype.schedule = function (work, delay, state) { + if (delay === void 0) { + delay = 0; + } + if (AsyncScheduler.delegate && AsyncScheduler.delegate !== this) { + return AsyncScheduler.delegate.schedule(work, delay, state); + } + else { + return _super.prototype.schedule.call(this, work, delay, state); + } + }; + AsyncScheduler.prototype.flush = function (action) { + var actions = this.actions; + if (this.active) { + actions.push(action); + return; + } + var error; + this.active = true; + do { + if (error = action.execute(action.state, action.delay)) { + break; + } + } while (action = actions.shift()); + this.active = false; + if (error) { + while (action = actions.shift()) { + action.unsubscribe(); + } + throw error; + } + }; + return AsyncScheduler; +}(_Scheduler__WEBPACK_IMPORTED_MODULE_1__["Scheduler"])); + +//# sourceMappingURL=AsyncScheduler.js.map + + +/***/ }), +/* 203 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Scheduler", function() { return Scheduler; }); +var Scheduler = /*@__PURE__*/ (function () { + function Scheduler(SchedulerAction, now) { + if (now === void 0) { + now = Scheduler.now; + } + this.SchedulerAction = SchedulerAction; + this.now = now; + } + Scheduler.prototype.schedule = function (work, delay, state) { + if (delay === void 0) { + delay = 0; + } + return new this.SchedulerAction(this, work).schedule(state, delay); + }; + Scheduler.now = function () { return Date.now(); }; + return Scheduler; +}()); + +//# sourceMappingURL=Scheduler.js.map + + +/***/ }), +/* 204 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "timer", function() { return timer; }); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(193); +/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(199); +/* harmony import */ var _util_isNumeric__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(205); +/* harmony import */ var _util_isScheduler__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(206); +/** PURE_IMPORTS_START _Observable,_scheduler_async,_util_isNumeric,_util_isScheduler PURE_IMPORTS_END */ + + + + +function timer(dueTime, periodOrScheduler, scheduler) { + if (dueTime === void 0) { + dueTime = 0; + } + var period = -1; + if (Object(_util_isNumeric__WEBPACK_IMPORTED_MODULE_2__["isNumeric"])(periodOrScheduler)) { + period = Number(periodOrScheduler) < 1 && 1 || Number(periodOrScheduler); + } + else if (Object(_util_isScheduler__WEBPACK_IMPORTED_MODULE_3__["isScheduler"])(periodOrScheduler)) { + scheduler = periodOrScheduler; + } + if (!Object(_util_isScheduler__WEBPACK_IMPORTED_MODULE_3__["isScheduler"])(scheduler)) { + scheduler = _scheduler_async__WEBPACK_IMPORTED_MODULE_1__["async"]; + } + return new _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"](function (subscriber) { + var due = Object(_util_isNumeric__WEBPACK_IMPORTED_MODULE_2__["isNumeric"])(dueTime) + ? dueTime + : (+dueTime - scheduler.now()); + return scheduler.schedule(dispatch, due, { + index: 0, period: period, subscriber: subscriber + }); + }); +} +function dispatch(state) { + var index = state.index, period = state.period, subscriber = state.subscriber; + subscriber.next(index); + if (subscriber.closed) { + return; + } + else if (period === -1) { + return subscriber.complete(); + } + state.index = index + 1; + this.schedule(state, period); +} +//# sourceMappingURL=timer.js.map + + +/***/ }), +/* 205 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "isNumeric", function() { return isNumeric; }); +/* harmony import */ var _isArray__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(178); +/** PURE_IMPORTS_START _isArray PURE_IMPORTS_END */ + +function isNumeric(val) { + return !Object(_isArray__WEBPACK_IMPORTED_MODULE_0__["isArray"])(val) && (val - parseFloat(val) + 1) >= 0; +} +//# sourceMappingURL=isNumeric.js.map + + +/***/ }), +/* 206 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "isScheduler", function() { return isScheduler; }); +/** PURE_IMPORTS_START PURE_IMPORTS_END */ +function isScheduler(value) { + return value && typeof value.schedule === 'function'; +} +//# sourceMappingURL=isScheduler.js.map + + +/***/ }), +/* 207 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "buffer", function() { return buffer; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(171); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(182); +/** PURE_IMPORTS_START tslib,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ + + + +function buffer(closingNotifier) { + return function bufferOperatorFunction(source) { + return source.lift(new BufferOperator(closingNotifier)); + }; +} +var BufferOperator = /*@__PURE__*/ (function () { + function BufferOperator(closingNotifier) { + this.closingNotifier = closingNotifier; + } + BufferOperator.prototype.call = function (subscriber, source) { + return source.subscribe(new BufferSubscriber(subscriber, this.closingNotifier)); + }; + return BufferOperator; +}()); +var BufferSubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](BufferSubscriber, _super); + function BufferSubscriber(destination, closingNotifier) { + var _this = _super.call(this, destination) || this; + _this.buffer = []; + _this.add(Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__["subscribeToResult"])(_this, closingNotifier)); + return _this; + } + BufferSubscriber.prototype._next = function (value) { + this.buffer.push(value); + }; + BufferSubscriber.prototype.notifyNext = function (outerValue, innerValue, outerIndex, innerIndex, innerSub) { + var buffer = this.buffer; + this.buffer = []; + this.destination.next(buffer); + }; + return BufferSubscriber; +}(_OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__["OuterSubscriber"])); +//# sourceMappingURL=buffer.js.map + + +/***/ }), +/* 208 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "bufferCount", function() { return bufferCount; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(172); +/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ + + +function bufferCount(bufferSize, startBufferEvery) { + if (startBufferEvery === void 0) { + startBufferEvery = null; + } + return function bufferCountOperatorFunction(source) { + return source.lift(new BufferCountOperator(bufferSize, startBufferEvery)); + }; +} +var BufferCountOperator = /*@__PURE__*/ (function () { + function BufferCountOperator(bufferSize, startBufferEvery) { + this.bufferSize = bufferSize; + this.startBufferEvery = startBufferEvery; + if (!startBufferEvery || bufferSize === startBufferEvery) { + this.subscriberClass = BufferCountSubscriber; + } + else { + this.subscriberClass = BufferSkipCountSubscriber; + } + } + BufferCountOperator.prototype.call = function (subscriber, source) { + return source.subscribe(new this.subscriberClass(subscriber, this.bufferSize, this.startBufferEvery)); + }; + return BufferCountOperator; +}()); +var BufferCountSubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](BufferCountSubscriber, _super); + function BufferCountSubscriber(destination, bufferSize) { + var _this = _super.call(this, destination) || this; + _this.bufferSize = bufferSize; + _this.buffer = []; + return _this; + } + BufferCountSubscriber.prototype._next = function (value) { + var buffer = this.buffer; + buffer.push(value); + if (buffer.length == this.bufferSize) { + this.destination.next(buffer); + this.buffer = []; + } + }; + BufferCountSubscriber.prototype._complete = function () { + var buffer = this.buffer; + if (buffer.length > 0) { + this.destination.next(buffer); + } + _super.prototype._complete.call(this); + }; + return BufferCountSubscriber; +}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); +var BufferSkipCountSubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](BufferSkipCountSubscriber, _super); + function BufferSkipCountSubscriber(destination, bufferSize, startBufferEvery) { + var _this = _super.call(this, destination) || this; + _this.bufferSize = bufferSize; + _this.startBufferEvery = startBufferEvery; + _this.buffers = []; + _this.count = 0; + return _this; + } + BufferSkipCountSubscriber.prototype._next = function (value) { + var _a = this, bufferSize = _a.bufferSize, startBufferEvery = _a.startBufferEvery, buffers = _a.buffers, count = _a.count; + this.count++; + if (count % startBufferEvery === 0) { + buffers.push([]); + } + for (var i = buffers.length; i--;) { + var buffer = buffers[i]; + buffer.push(value); + if (buffer.length === bufferSize) { + buffers.splice(i, 1); + this.destination.next(buffer); + } + } + }; + BufferSkipCountSubscriber.prototype._complete = function () { + var _a = this, buffers = _a.buffers, destination = _a.destination; + while (buffers.length > 0) { + var buffer = buffers.shift(); + if (buffer.length > 0) { + destination.next(buffer); + } + } + _super.prototype._complete.call(this); + }; + return BufferSkipCountSubscriber; +}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); +//# sourceMappingURL=bufferCount.js.map + + +/***/ }), +/* 209 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "bufferTime", function() { return bufferTime; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(199); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(172); +/* harmony import */ var _util_isScheduler__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(206); +/** PURE_IMPORTS_START tslib,_scheduler_async,_Subscriber,_util_isScheduler PURE_IMPORTS_END */ + + + + +function bufferTime(bufferTimeSpan) { + var length = arguments.length; + var scheduler = _scheduler_async__WEBPACK_IMPORTED_MODULE_1__["async"]; + if (Object(_util_isScheduler__WEBPACK_IMPORTED_MODULE_3__["isScheduler"])(arguments[arguments.length - 1])) { + scheduler = arguments[arguments.length - 1]; + length--; + } + var bufferCreationInterval = null; + if (length >= 2) { + bufferCreationInterval = arguments[1]; + } + var maxBufferSize = Number.POSITIVE_INFINITY; + if (length >= 3) { + maxBufferSize = arguments[2]; + } + return function bufferTimeOperatorFunction(source) { + return source.lift(new BufferTimeOperator(bufferTimeSpan, bufferCreationInterval, maxBufferSize, scheduler)); + }; +} +var BufferTimeOperator = /*@__PURE__*/ (function () { + function BufferTimeOperator(bufferTimeSpan, bufferCreationInterval, maxBufferSize, scheduler) { + this.bufferTimeSpan = bufferTimeSpan; + this.bufferCreationInterval = bufferCreationInterval; + this.maxBufferSize = maxBufferSize; + this.scheduler = scheduler; + } + BufferTimeOperator.prototype.call = function (subscriber, source) { + return source.subscribe(new BufferTimeSubscriber(subscriber, this.bufferTimeSpan, this.bufferCreationInterval, this.maxBufferSize, this.scheduler)); + }; + return BufferTimeOperator; +}()); +var Context = /*@__PURE__*/ (function () { + function Context() { + this.buffer = []; + } + return Context; +}()); +var BufferTimeSubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](BufferTimeSubscriber, _super); + function BufferTimeSubscriber(destination, bufferTimeSpan, bufferCreationInterval, maxBufferSize, scheduler) { + var _this = _super.call(this, destination) || this; + _this.bufferTimeSpan = bufferTimeSpan; + _this.bufferCreationInterval = bufferCreationInterval; + _this.maxBufferSize = maxBufferSize; + _this.scheduler = scheduler; + _this.contexts = []; + var context = _this.openContext(); + _this.timespanOnly = bufferCreationInterval == null || bufferCreationInterval < 0; + if (_this.timespanOnly) { + var timeSpanOnlyState = { subscriber: _this, context: context, bufferTimeSpan: bufferTimeSpan }; + _this.add(context.closeAction = scheduler.schedule(dispatchBufferTimeSpanOnly, bufferTimeSpan, timeSpanOnlyState)); + } + else { + var closeState = { subscriber: _this, context: context }; + var creationState = { bufferTimeSpan: bufferTimeSpan, bufferCreationInterval: bufferCreationInterval, subscriber: _this, scheduler: scheduler }; + _this.add(context.closeAction = scheduler.schedule(dispatchBufferClose, bufferTimeSpan, closeState)); + _this.add(scheduler.schedule(dispatchBufferCreation, bufferCreationInterval, creationState)); + } + return _this; + } + BufferTimeSubscriber.prototype._next = function (value) { + var contexts = this.contexts; + var len = contexts.length; + var filledBufferContext; + for (var i = 0; i < len; i++) { + var context_1 = contexts[i]; + var buffer = context_1.buffer; + buffer.push(value); + if (buffer.length == this.maxBufferSize) { + filledBufferContext = context_1; + } + } + if (filledBufferContext) { + this.onBufferFull(filledBufferContext); + } + }; + BufferTimeSubscriber.prototype._error = function (err) { + this.contexts.length = 0; + _super.prototype._error.call(this, err); + }; + BufferTimeSubscriber.prototype._complete = function () { + var _a = this, contexts = _a.contexts, destination = _a.destination; + while (contexts.length > 0) { + var context_2 = contexts.shift(); + destination.next(context_2.buffer); + } + _super.prototype._complete.call(this); + }; + BufferTimeSubscriber.prototype._unsubscribe = function () { + this.contexts = null; + }; + BufferTimeSubscriber.prototype.onBufferFull = function (context) { + this.closeContext(context); + var closeAction = context.closeAction; + closeAction.unsubscribe(); + this.remove(closeAction); + if (!this.closed && this.timespanOnly) { + context = this.openContext(); + var bufferTimeSpan = this.bufferTimeSpan; + var timeSpanOnlyState = { subscriber: this, context: context, bufferTimeSpan: bufferTimeSpan }; + this.add(context.closeAction = this.scheduler.schedule(dispatchBufferTimeSpanOnly, bufferTimeSpan, timeSpanOnlyState)); + } + }; + BufferTimeSubscriber.prototype.openContext = function () { + var context = new Context(); + this.contexts.push(context); + return context; + }; + BufferTimeSubscriber.prototype.closeContext = function (context) { + this.destination.next(context.buffer); + var contexts = this.contexts; + var spliceIndex = contexts ? contexts.indexOf(context) : -1; + if (spliceIndex >= 0) { + contexts.splice(contexts.indexOf(context), 1); + } + }; + return BufferTimeSubscriber; +}(_Subscriber__WEBPACK_IMPORTED_MODULE_2__["Subscriber"])); +function dispatchBufferTimeSpanOnly(state) { + var subscriber = state.subscriber; + var prevContext = state.context; + if (prevContext) { + subscriber.closeContext(prevContext); + } + if (!subscriber.closed) { + state.context = subscriber.openContext(); + state.context.closeAction = this.schedule(state, state.bufferTimeSpan); + } +} +function dispatchBufferCreation(state) { + var bufferCreationInterval = state.bufferCreationInterval, bufferTimeSpan = state.bufferTimeSpan, subscriber = state.subscriber, scheduler = state.scheduler; + var context = subscriber.openContext(); + var action = this; + if (!subscriber.closed) { + subscriber.add(context.closeAction = scheduler.schedule(dispatchBufferClose, bufferTimeSpan, { subscriber: subscriber, context: context })); + action.schedule(state, bufferCreationInterval); + } +} +function dispatchBufferClose(arg) { + var subscriber = arg.subscriber, context = arg.context; + subscriber.closeContext(context); +} +//# sourceMappingURL=bufferTime.js.map + + +/***/ }), +/* 210 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "bufferToggle", function() { return bufferToggle; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(177); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(182); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(171); +/** PURE_IMPORTS_START tslib,_Subscription,_util_subscribeToResult,_OuterSubscriber PURE_IMPORTS_END */ + + + + +function bufferToggle(openings, closingSelector) { + return function bufferToggleOperatorFunction(source) { + return source.lift(new BufferToggleOperator(openings, closingSelector)); + }; +} +var BufferToggleOperator = /*@__PURE__*/ (function () { + function BufferToggleOperator(openings, closingSelector) { + this.openings = openings; + this.closingSelector = closingSelector; + } + BufferToggleOperator.prototype.call = function (subscriber, source) { + return source.subscribe(new BufferToggleSubscriber(subscriber, this.openings, this.closingSelector)); + }; + return BufferToggleOperator; +}()); +var BufferToggleSubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](BufferToggleSubscriber, _super); + function BufferToggleSubscriber(destination, openings, closingSelector) { + var _this = _super.call(this, destination) || this; + _this.openings = openings; + _this.closingSelector = closingSelector; + _this.contexts = []; + _this.add(Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__["subscribeToResult"])(_this, openings)); + return _this; + } + BufferToggleSubscriber.prototype._next = function (value) { + var contexts = this.contexts; + var len = contexts.length; + for (var i = 0; i < len; i++) { + contexts[i].buffer.push(value); + } + }; + BufferToggleSubscriber.prototype._error = function (err) { + var contexts = this.contexts; + while (contexts.length > 0) { + var context_1 = contexts.shift(); + context_1.subscription.unsubscribe(); + context_1.buffer = null; + context_1.subscription = null; + } + this.contexts = null; + _super.prototype._error.call(this, err); + }; + BufferToggleSubscriber.prototype._complete = function () { + var contexts = this.contexts; + while (contexts.length > 0) { + var context_2 = contexts.shift(); + this.destination.next(context_2.buffer); + context_2.subscription.unsubscribe(); + context_2.buffer = null; + context_2.subscription = null; + } + this.contexts = null; + _super.prototype._complete.call(this); + }; + BufferToggleSubscriber.prototype.notifyNext = function (outerValue, innerValue, outerIndex, innerIndex, innerSub) { + outerValue ? this.closeBuffer(outerValue) : this.openBuffer(innerValue); + }; + BufferToggleSubscriber.prototype.notifyComplete = function (innerSub) { + this.closeBuffer(innerSub.context); + }; + BufferToggleSubscriber.prototype.openBuffer = function (value) { + try { + var closingSelector = this.closingSelector; + var closingNotifier = closingSelector.call(this, value); + if (closingNotifier) { + this.trySubscribe(closingNotifier); + } + } + catch (err) { + this._error(err); + } + }; + BufferToggleSubscriber.prototype.closeBuffer = function (context) { + var contexts = this.contexts; + if (contexts && context) { + var buffer = context.buffer, subscription = context.subscription; + this.destination.next(buffer); + contexts.splice(contexts.indexOf(context), 1); + this.remove(subscription); + subscription.unsubscribe(); + } + }; + BufferToggleSubscriber.prototype.trySubscribe = function (closingNotifier) { + var contexts = this.contexts; + var buffer = []; + var subscription = new _Subscription__WEBPACK_IMPORTED_MODULE_1__["Subscription"](); + var context = { buffer: buffer, subscription: subscription }; + contexts.push(context); + var innerSubscription = Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__["subscribeToResult"])(this, closingNotifier, context); + if (!innerSubscription || innerSubscription.closed) { + this.closeBuffer(context); + } + else { + innerSubscription.context = context; + this.add(innerSubscription); + subscription.add(innerSubscription); + } + }; + return BufferToggleSubscriber; +}(_OuterSubscriber__WEBPACK_IMPORTED_MODULE_3__["OuterSubscriber"])); +//# sourceMappingURL=bufferToggle.js.map + + +/***/ }), +/* 211 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "bufferWhen", function() { return bufferWhen; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(177); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(171); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(182); +/** PURE_IMPORTS_START tslib,_Subscription,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ + + + + +function bufferWhen(closingSelector) { + return function (source) { + return source.lift(new BufferWhenOperator(closingSelector)); + }; +} +var BufferWhenOperator = /*@__PURE__*/ (function () { + function BufferWhenOperator(closingSelector) { + this.closingSelector = closingSelector; + } + BufferWhenOperator.prototype.call = function (subscriber, source) { + return source.subscribe(new BufferWhenSubscriber(subscriber, this.closingSelector)); + }; + return BufferWhenOperator; +}()); +var BufferWhenSubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](BufferWhenSubscriber, _super); + function BufferWhenSubscriber(destination, closingSelector) { + var _this = _super.call(this, destination) || this; + _this.closingSelector = closingSelector; + _this.subscribing = false; + _this.openBuffer(); + return _this; + } + BufferWhenSubscriber.prototype._next = function (value) { + this.buffer.push(value); + }; + BufferWhenSubscriber.prototype._complete = function () { + var buffer = this.buffer; + if (buffer) { + this.destination.next(buffer); + } + _super.prototype._complete.call(this); + }; + BufferWhenSubscriber.prototype._unsubscribe = function () { + this.buffer = null; + this.subscribing = false; + }; + BufferWhenSubscriber.prototype.notifyNext = function (outerValue, innerValue, outerIndex, innerIndex, innerSub) { + this.openBuffer(); + }; + BufferWhenSubscriber.prototype.notifyComplete = function () { + if (this.subscribing) { + this.complete(); + } + else { + this.openBuffer(); + } + }; + BufferWhenSubscriber.prototype.openBuffer = function () { + var closingSubscription = this.closingSubscription; + if (closingSubscription) { + this.remove(closingSubscription); + closingSubscription.unsubscribe(); + } + var buffer = this.buffer; + if (this.buffer) { + this.destination.next(buffer); + } + this.buffer = []; + var closingNotifier; + try { + var closingSelector = this.closingSelector; + closingNotifier = closingSelector(); + } + catch (err) { + return this.error(err); + } + closingSubscription = new _Subscription__WEBPACK_IMPORTED_MODULE_1__["Subscription"](); + this.closingSubscription = closingSubscription; + this.add(closingSubscription); + this.subscribing = true; + closingSubscription.add(Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_3__["subscribeToResult"])(this, closingNotifier)); + this.subscribing = false; + }; + return BufferWhenSubscriber; +}(_OuterSubscriber__WEBPACK_IMPORTED_MODULE_2__["OuterSubscriber"])); +//# sourceMappingURL=bufferWhen.js.map + + +/***/ }), +/* 212 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "catchError", function() { return catchError; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(171); +/* harmony import */ var _InnerSubscriber__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(183); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(182); +/** PURE_IMPORTS_START tslib,_OuterSubscriber,_InnerSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ + + + + +function catchError(selector) { + return function catchErrorOperatorFunction(source) { + var operator = new CatchOperator(selector); + var caught = source.lift(operator); + return (operator.caught = caught); + }; +} +var CatchOperator = /*@__PURE__*/ (function () { + function CatchOperator(selector) { + this.selector = selector; + } + CatchOperator.prototype.call = function (subscriber, source) { + return source.subscribe(new CatchSubscriber(subscriber, this.selector, this.caught)); + }; + return CatchOperator; +}()); +var CatchSubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](CatchSubscriber, _super); + function CatchSubscriber(destination, selector, caught) { + var _this = _super.call(this, destination) || this; + _this.selector = selector; + _this.caught = caught; + return _this; + } + CatchSubscriber.prototype.error = function (err) { + if (!this.isStopped) { + var result = void 0; + try { + result = this.selector(err, this.caught); + } + catch (err2) { + _super.prototype.error.call(this, err2); + return; + } + this._unsubscribeAndRecycle(); + var innerSubscriber = new _InnerSubscriber__WEBPACK_IMPORTED_MODULE_2__["InnerSubscriber"](this, undefined, undefined); + this.add(innerSubscriber); + Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_3__["subscribeToResult"])(this, result, undefined, undefined, innerSubscriber); + } + }; + return CatchSubscriber; +}(_OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__["OuterSubscriber"])); +//# sourceMappingURL=catchError.js.map + + +/***/ }), +/* 213 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "combineAll", function() { return combineAll; }); +/* harmony import */ var _observable_combineLatest__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(214); +/** PURE_IMPORTS_START _observable_combineLatest PURE_IMPORTS_END */ + +function combineAll(project) { + return function (source) { return source.lift(new _observable_combineLatest__WEBPACK_IMPORTED_MODULE_0__["CombineLatestOperator"](project)); }; +} +//# sourceMappingURL=combineAll.js.map + + +/***/ }), +/* 214 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "combineLatest", function() { return combineLatest; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "CombineLatestOperator", function() { return CombineLatestOperator; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "CombineLatestSubscriber", function() { return CombineLatestSubscriber; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _util_isScheduler__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(206); +/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(178); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(171); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(182); +/* harmony import */ var _fromArray__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(215); +/** PURE_IMPORTS_START tslib,_util_isScheduler,_util_isArray,_OuterSubscriber,_util_subscribeToResult,_fromArray PURE_IMPORTS_END */ + + + + + + +var NONE = {}; +function combineLatest() { + var observables = []; + for (var _i = 0; _i < arguments.length; _i++) { + observables[_i] = arguments[_i]; + } + var resultSelector = null; + var scheduler = null; + if (Object(_util_isScheduler__WEBPACK_IMPORTED_MODULE_1__["isScheduler"])(observables[observables.length - 1])) { + scheduler = observables.pop(); + } + if (typeof observables[observables.length - 1] === 'function') { + resultSelector = observables.pop(); + } + if (observables.length === 1 && Object(_util_isArray__WEBPACK_IMPORTED_MODULE_2__["isArray"])(observables[0])) { + observables = observables[0]; + } + return Object(_fromArray__WEBPACK_IMPORTED_MODULE_5__["fromArray"])(observables, scheduler).lift(new CombineLatestOperator(resultSelector)); +} +var CombineLatestOperator = /*@__PURE__*/ (function () { + function CombineLatestOperator(resultSelector) { + this.resultSelector = resultSelector; + } + CombineLatestOperator.prototype.call = function (subscriber, source) { + return source.subscribe(new CombineLatestSubscriber(subscriber, this.resultSelector)); + }; + return CombineLatestOperator; +}()); + +var CombineLatestSubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](CombineLatestSubscriber, _super); + function CombineLatestSubscriber(destination, resultSelector) { + var _this = _super.call(this, destination) || this; + _this.resultSelector = resultSelector; + _this.active = 0; + _this.values = []; + _this.observables = []; + return _this; + } + CombineLatestSubscriber.prototype._next = function (observable) { + this.values.push(NONE); + this.observables.push(observable); + }; + CombineLatestSubscriber.prototype._complete = function () { + var observables = this.observables; + var len = observables.length; + if (len === 0) { + this.destination.complete(); + } + else { + this.active = len; + this.toRespond = len; + for (var i = 0; i < len; i++) { + var observable = observables[i]; + this.add(Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_4__["subscribeToResult"])(this, observable, observable, i)); + } + } + }; + CombineLatestSubscriber.prototype.notifyComplete = function (unused) { + if ((this.active -= 1) === 0) { + this.destination.complete(); + } + }; + CombineLatestSubscriber.prototype.notifyNext = function (outerValue, innerValue, outerIndex, innerIndex, innerSub) { + var values = this.values; + var oldVal = values[outerIndex]; + var toRespond = !this.toRespond + ? 0 + : oldVal === NONE ? --this.toRespond : this.toRespond; + values[outerIndex] = innerValue; + if (toRespond === 0) { + if (this.resultSelector) { + this._tryResultSelector(values); + } + else { + this.destination.next(values.slice()); + } + } + }; + CombineLatestSubscriber.prototype._tryResultSelector = function (values) { + var result; + try { + result = this.resultSelector.apply(this, values); + } + catch (err) { + this.destination.error(err); + return; + } + this.destination.next(result); + }; + return CombineLatestSubscriber; +}(_OuterSubscriber__WEBPACK_IMPORTED_MODULE_3__["OuterSubscriber"])); + +//# sourceMappingURL=combineLatest.js.map + + +/***/ }), +/* 215 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "fromArray", function() { return fromArray; }); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(193); +/* harmony import */ var _util_subscribeToArray__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(185); +/* harmony import */ var _scheduled_scheduleArray__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(216); +/** PURE_IMPORTS_START _Observable,_util_subscribeToArray,_scheduled_scheduleArray PURE_IMPORTS_END */ + + + +function fromArray(input, scheduler) { + if (!scheduler) { + return new _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"](Object(_util_subscribeToArray__WEBPACK_IMPORTED_MODULE_1__["subscribeToArray"])(input)); + } + else { + return Object(_scheduled_scheduleArray__WEBPACK_IMPORTED_MODULE_2__["scheduleArray"])(input, scheduler); + } +} +//# sourceMappingURL=fromArray.js.map + + +/***/ }), +/* 216 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "scheduleArray", function() { return scheduleArray; }); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(193); +/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(177); +/** PURE_IMPORTS_START _Observable,_Subscription PURE_IMPORTS_END */ + + +function scheduleArray(input, scheduler) { + return new _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"](function (subscriber) { + var sub = new _Subscription__WEBPACK_IMPORTED_MODULE_1__["Subscription"](); + var i = 0; + sub.add(scheduler.schedule(function () { + if (i === input.length) { + subscriber.complete(); + return; + } + subscriber.next(input[i++]); + if (!subscriber.closed) { + sub.add(this.schedule()); + } + })); + return sub; + }); +} +//# sourceMappingURL=scheduleArray.js.map + + +/***/ }), +/* 217 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "combineLatest", function() { return combineLatest; }); +/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(178); +/* harmony import */ var _observable_combineLatest__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(214); +/* harmony import */ var _observable_from__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(218); +/** PURE_IMPORTS_START _util_isArray,_observable_combineLatest,_observable_from PURE_IMPORTS_END */ + + + +var none = {}; +function combineLatest() { + var observables = []; + for (var _i = 0; _i < arguments.length; _i++) { + observables[_i] = arguments[_i]; + } + var project = null; + if (typeof observables[observables.length - 1] === 'function') { + project = observables.pop(); + } + if (observables.length === 1 && Object(_util_isArray__WEBPACK_IMPORTED_MODULE_0__["isArray"])(observables[0])) { + observables = observables[0].slice(); + } + return function (source) { return source.lift.call(Object(_observable_from__WEBPACK_IMPORTED_MODULE_2__["from"])([source].concat(observables)), new _observable_combineLatest__WEBPACK_IMPORTED_MODULE_1__["CombineLatestOperator"](project)); }; +} +//# sourceMappingURL=combineLatest.js.map + + +/***/ }), +/* 218 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "from", function() { return from; }); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(193); +/* harmony import */ var _util_subscribeTo__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(184); +/* harmony import */ var _scheduled_scheduled__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(219); +/** PURE_IMPORTS_START _Observable,_util_subscribeTo,_scheduled_scheduled PURE_IMPORTS_END */ + + + +function from(input, scheduler) { + if (!scheduler) { + if (input instanceof _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"]) { + return input; + } + return new _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"](Object(_util_subscribeTo__WEBPACK_IMPORTED_MODULE_1__["subscribeTo"])(input)); + } + else { + return Object(_scheduled_scheduled__WEBPACK_IMPORTED_MODULE_2__["scheduled"])(input, scheduler); + } +} +//# sourceMappingURL=from.js.map + + +/***/ }), +/* 219 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "scheduled", function() { return scheduled; }); +/* harmony import */ var _scheduleObservable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(220); +/* harmony import */ var _schedulePromise__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(221); +/* harmony import */ var _scheduleArray__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(216); +/* harmony import */ var _scheduleIterable__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(222); +/* harmony import */ var _util_isInteropObservable__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(223); +/* harmony import */ var _util_isPromise__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(192); +/* harmony import */ var _util_isArrayLike__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(191); +/* harmony import */ var _util_isIterable__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(224); +/** PURE_IMPORTS_START _scheduleObservable,_schedulePromise,_scheduleArray,_scheduleIterable,_util_isInteropObservable,_util_isPromise,_util_isArrayLike,_util_isIterable PURE_IMPORTS_END */ + + + + + + + + +function scheduled(input, scheduler) { + if (input != null) { + if (Object(_util_isInteropObservable__WEBPACK_IMPORTED_MODULE_4__["isInteropObservable"])(input)) { + return Object(_scheduleObservable__WEBPACK_IMPORTED_MODULE_0__["scheduleObservable"])(input, scheduler); + } + else if (Object(_util_isPromise__WEBPACK_IMPORTED_MODULE_5__["isPromise"])(input)) { + return Object(_schedulePromise__WEBPACK_IMPORTED_MODULE_1__["schedulePromise"])(input, scheduler); + } + else if (Object(_util_isArrayLike__WEBPACK_IMPORTED_MODULE_6__["isArrayLike"])(input)) { + return Object(_scheduleArray__WEBPACK_IMPORTED_MODULE_2__["scheduleArray"])(input, scheduler); + } + else if (Object(_util_isIterable__WEBPACK_IMPORTED_MODULE_7__["isIterable"])(input) || typeof input === 'string') { + return Object(_scheduleIterable__WEBPACK_IMPORTED_MODULE_3__["scheduleIterable"])(input, scheduler); + } + } + throw new TypeError((input !== null && typeof input || input) + ' is not observable'); +} +//# sourceMappingURL=scheduled.js.map + + +/***/ }), +/* 220 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "scheduleObservable", function() { return scheduleObservable; }); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(193); +/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(177); +/* harmony import */ var _symbol_observable__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(190); +/** PURE_IMPORTS_START _Observable,_Subscription,_symbol_observable PURE_IMPORTS_END */ + + + +function scheduleObservable(input, scheduler) { + return new _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"](function (subscriber) { + var sub = new _Subscription__WEBPACK_IMPORTED_MODULE_1__["Subscription"](); + sub.add(scheduler.schedule(function () { + var observable = input[_symbol_observable__WEBPACK_IMPORTED_MODULE_2__["observable"]](); + sub.add(observable.subscribe({ + next: function (value) { sub.add(scheduler.schedule(function () { return subscriber.next(value); })); }, + error: function (err) { sub.add(scheduler.schedule(function () { return subscriber.error(err); })); }, + complete: function () { sub.add(scheduler.schedule(function () { return subscriber.complete(); })); }, + })); + })); + return sub; + }); +} +//# sourceMappingURL=scheduleObservable.js.map + + +/***/ }), +/* 221 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "schedulePromise", function() { return schedulePromise; }); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(193); +/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(177); +/** PURE_IMPORTS_START _Observable,_Subscription PURE_IMPORTS_END */ + + +function schedulePromise(input, scheduler) { + return new _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"](function (subscriber) { + var sub = new _Subscription__WEBPACK_IMPORTED_MODULE_1__["Subscription"](); + sub.add(scheduler.schedule(function () { + return input.then(function (value) { + sub.add(scheduler.schedule(function () { + subscriber.next(value); + sub.add(scheduler.schedule(function () { return subscriber.complete(); })); + })); + }, function (err) { + sub.add(scheduler.schedule(function () { return subscriber.error(err); })); + }); + })); + return sub; + }); +} +//# sourceMappingURL=schedulePromise.js.map + + +/***/ }), +/* 222 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "scheduleIterable", function() { return scheduleIterable; }); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(193); +/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(177); +/* harmony import */ var _symbol_iterator__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(188); +/** PURE_IMPORTS_START _Observable,_Subscription,_symbol_iterator PURE_IMPORTS_END */ + + + +function scheduleIterable(input, scheduler) { + if (!input) { + throw new Error('Iterable cannot be null'); + } + return new _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"](function (subscriber) { + var sub = new _Subscription__WEBPACK_IMPORTED_MODULE_1__["Subscription"](); + var iterator; + sub.add(function () { + if (iterator && typeof iterator.return === 'function') { + iterator.return(); + } + }); + sub.add(scheduler.schedule(function () { + iterator = input[_symbol_iterator__WEBPACK_IMPORTED_MODULE_2__["iterator"]](); + sub.add(scheduler.schedule(function () { + if (subscriber.closed) { + return; + } + var value; + var done; + try { + var result = iterator.next(); + value = result.value; + done = result.done; + } + catch (err) { + subscriber.error(err); + return; + } + if (done) { + subscriber.complete(); + } + else { + subscriber.next(value); + this.schedule(); + } + })); + })); + return sub; + }); +} +//# sourceMappingURL=scheduleIterable.js.map + + +/***/ }), +/* 223 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "isInteropObservable", function() { return isInteropObservable; }); +/* harmony import */ var _symbol_observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(190); +/** PURE_IMPORTS_START _symbol_observable PURE_IMPORTS_END */ + +function isInteropObservable(input) { + return input && typeof input[_symbol_observable__WEBPACK_IMPORTED_MODULE_0__["observable"]] === 'function'; +} +//# sourceMappingURL=isInteropObservable.js.map + + +/***/ }), +/* 224 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "isIterable", function() { return isIterable; }); +/* harmony import */ var _symbol_iterator__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(188); +/** PURE_IMPORTS_START _symbol_iterator PURE_IMPORTS_END */ + +function isIterable(input) { + return input && typeof input[_symbol_iterator__WEBPACK_IMPORTED_MODULE_0__["iterator"]] === 'function'; +} +//# sourceMappingURL=isIterable.js.map + + +/***/ }), +/* 225 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "concat", function() { return concat; }); +/* harmony import */ var _observable_concat__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(226); +/** PURE_IMPORTS_START _observable_concat PURE_IMPORTS_END */ + +function concat() { + var observables = []; + for (var _i = 0; _i < arguments.length; _i++) { + observables[_i] = arguments[_i]; + } + return function (source) { return source.lift.call(_observable_concat__WEBPACK_IMPORTED_MODULE_0__["concat"].apply(void 0, [source].concat(observables))); }; +} +//# sourceMappingURL=concat.js.map + + +/***/ }), +/* 226 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "concat", function() { return concat; }); +/* harmony import */ var _of__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(227); +/* harmony import */ var _operators_concatAll__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(228); +/** PURE_IMPORTS_START _of,_operators_concatAll PURE_IMPORTS_END */ + + +function concat() { + var observables = []; + for (var _i = 0; _i < arguments.length; _i++) { + observables[_i] = arguments[_i]; + } + return Object(_operators_concatAll__WEBPACK_IMPORTED_MODULE_1__["concatAll"])()(_of__WEBPACK_IMPORTED_MODULE_0__["of"].apply(void 0, observables)); +} +//# sourceMappingURL=concat.js.map + + +/***/ }), +/* 227 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "of", function() { return of; }); +/* harmony import */ var _util_isScheduler__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(206); +/* harmony import */ var _fromArray__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(215); +/* harmony import */ var _scheduled_scheduleArray__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(216); +/** PURE_IMPORTS_START _util_isScheduler,_fromArray,_scheduled_scheduleArray PURE_IMPORTS_END */ + + + +function of() { + var args = []; + for (var _i = 0; _i < arguments.length; _i++) { + args[_i] = arguments[_i]; + } + var scheduler = args[args.length - 1]; + if (Object(_util_isScheduler__WEBPACK_IMPORTED_MODULE_0__["isScheduler"])(scheduler)) { + args.pop(); + return Object(_scheduled_scheduleArray__WEBPACK_IMPORTED_MODULE_2__["scheduleArray"])(args, scheduler); + } + else { + return Object(_fromArray__WEBPACK_IMPORTED_MODULE_1__["fromArray"])(args); + } +} +//# sourceMappingURL=of.js.map + + +/***/ }), +/* 228 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "concatAll", function() { return concatAll; }); +/* harmony import */ var _mergeAll__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(229); +/** PURE_IMPORTS_START _mergeAll PURE_IMPORTS_END */ + +function concatAll() { + return Object(_mergeAll__WEBPACK_IMPORTED_MODULE_0__["mergeAll"])(1); +} +//# sourceMappingURL=concatAll.js.map + + +/***/ }), +/* 229 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "mergeAll", function() { return mergeAll; }); +/* harmony import */ var _mergeMap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(230); +/* harmony import */ var _util_identity__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(232); +/** PURE_IMPORTS_START _mergeMap,_util_identity PURE_IMPORTS_END */ + + +function mergeAll(concurrent) { + if (concurrent === void 0) { + concurrent = Number.POSITIVE_INFINITY; + } + return Object(_mergeMap__WEBPACK_IMPORTED_MODULE_0__["mergeMap"])(_util_identity__WEBPACK_IMPORTED_MODULE_1__["identity"], concurrent); +} +//# sourceMappingURL=mergeAll.js.map + + +/***/ }), +/* 230 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "mergeMap", function() { return mergeMap; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "MergeMapOperator", function() { return MergeMapOperator; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "MergeMapSubscriber", function() { return MergeMapSubscriber; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(182); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(171); +/* harmony import */ var _InnerSubscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(183); +/* harmony import */ var _map__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(231); +/* harmony import */ var _observable_from__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(218); +/** PURE_IMPORTS_START tslib,_util_subscribeToResult,_OuterSubscriber,_InnerSubscriber,_map,_observable_from PURE_IMPORTS_END */ + + + + + + +function mergeMap(project, resultSelector, concurrent) { + if (concurrent === void 0) { + concurrent = Number.POSITIVE_INFINITY; + } + if (typeof resultSelector === 'function') { + return function (source) { return source.pipe(mergeMap(function (a, i) { return Object(_observable_from__WEBPACK_IMPORTED_MODULE_5__["from"])(project(a, i)).pipe(Object(_map__WEBPACK_IMPORTED_MODULE_4__["map"])(function (b, ii) { return resultSelector(a, b, i, ii); })); }, concurrent)); }; + } + else if (typeof resultSelector === 'number') { + concurrent = resultSelector; + } + return function (source) { return source.lift(new MergeMapOperator(project, concurrent)); }; +} +var MergeMapOperator = /*@__PURE__*/ (function () { + function MergeMapOperator(project, concurrent) { + if (concurrent === void 0) { + concurrent = Number.POSITIVE_INFINITY; + } + this.project = project; + this.concurrent = concurrent; + } + MergeMapOperator.prototype.call = function (observer, source) { + return source.subscribe(new MergeMapSubscriber(observer, this.project, this.concurrent)); + }; + return MergeMapOperator; +}()); + +var MergeMapSubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](MergeMapSubscriber, _super); + function MergeMapSubscriber(destination, project, concurrent) { + if (concurrent === void 0) { + concurrent = Number.POSITIVE_INFINITY; + } + var _this = _super.call(this, destination) || this; + _this.project = project; + _this.concurrent = concurrent; + _this.hasCompleted = false; + _this.buffer = []; + _this.active = 0; + _this.index = 0; + return _this; + } + MergeMapSubscriber.prototype._next = function (value) { + if (this.active < this.concurrent) { + this._tryNext(value); + } + else { + this.buffer.push(value); + } + }; + MergeMapSubscriber.prototype._tryNext = function (value) { + var result; + var index = this.index++; + try { + result = this.project(value, index); + } + catch (err) { + this.destination.error(err); + return; + } + this.active++; + this._innerSub(result, value, index); + }; + MergeMapSubscriber.prototype._innerSub = function (ish, value, index) { + var innerSubscriber = new _InnerSubscriber__WEBPACK_IMPORTED_MODULE_3__["InnerSubscriber"](this, undefined, undefined); + var destination = this.destination; + destination.add(innerSubscriber); + Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_1__["subscribeToResult"])(this, ish, value, index, innerSubscriber); + }; + MergeMapSubscriber.prototype._complete = function () { + this.hasCompleted = true; + if (this.active === 0 && this.buffer.length === 0) { + this.destination.complete(); + } + this.unsubscribe(); + }; + MergeMapSubscriber.prototype.notifyNext = function (outerValue, innerValue, outerIndex, innerIndex, innerSub) { + this.destination.next(innerValue); + }; + MergeMapSubscriber.prototype.notifyComplete = function (innerSub) { + var buffer = this.buffer; + this.remove(innerSub); + this.active--; + if (buffer.length > 0) { + this._next(buffer.shift()); + } + else if (this.active === 0 && this.hasCompleted) { + this.destination.complete(); + } + }; + return MergeMapSubscriber; +}(_OuterSubscriber__WEBPACK_IMPORTED_MODULE_2__["OuterSubscriber"])); + +//# sourceMappingURL=mergeMap.js.map + + +/***/ }), +/* 231 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "map", function() { return map; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "MapOperator", function() { return MapOperator; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(172); +/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ + + +function map(project, thisArg) { + return function mapOperation(source) { + if (typeof project !== 'function') { + throw new TypeError('argument is not a function. Are you looking for `mapTo()`?'); + } + return source.lift(new MapOperator(project, thisArg)); + }; +} +var MapOperator = /*@__PURE__*/ (function () { + function MapOperator(project, thisArg) { + this.project = project; + this.thisArg = thisArg; + } + MapOperator.prototype.call = function (subscriber, source) { + return source.subscribe(new MapSubscriber(subscriber, this.project, this.thisArg)); + }; + return MapOperator; +}()); + +var MapSubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](MapSubscriber, _super); + function MapSubscriber(destination, project, thisArg) { + var _this = _super.call(this, destination) || this; + _this.project = project; + _this.count = 0; + _this.thisArg = thisArg || _this; + return _this; + } + MapSubscriber.prototype._next = function (value) { + var result; + try { + result = this.project.call(this.thisArg, value, this.count++); + } + catch (err) { + this.destination.error(err); + return; + } + this.destination.next(result); + }; + return MapSubscriber; +}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); +//# sourceMappingURL=map.js.map + + +/***/ }), +/* 232 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "identity", function() { return identity; }); +/** PURE_IMPORTS_START PURE_IMPORTS_END */ +function identity(x) { + return x; +} +//# sourceMappingURL=identity.js.map + + +/***/ }), +/* 233 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "concatMap", function() { return concatMap; }); +/* harmony import */ var _mergeMap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(230); +/** PURE_IMPORTS_START _mergeMap PURE_IMPORTS_END */ + +function concatMap(project, resultSelector) { + return Object(_mergeMap__WEBPACK_IMPORTED_MODULE_0__["mergeMap"])(project, resultSelector, 1); +} +//# sourceMappingURL=concatMap.js.map + + +/***/ }), +/* 234 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "concatMapTo", function() { return concatMapTo; }); +/* harmony import */ var _concatMap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(233); +/** PURE_IMPORTS_START _concatMap PURE_IMPORTS_END */ + +function concatMapTo(innerObservable, resultSelector) { + return Object(_concatMap__WEBPACK_IMPORTED_MODULE_0__["concatMap"])(function () { return innerObservable; }, resultSelector); +} +//# sourceMappingURL=concatMapTo.js.map + + +/***/ }), +/* 235 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "count", function() { return count; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(172); +/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ + + +function count(predicate) { + return function (source) { return source.lift(new CountOperator(predicate, source)); }; +} +var CountOperator = /*@__PURE__*/ (function () { + function CountOperator(predicate, source) { + this.predicate = predicate; + this.source = source; + } + CountOperator.prototype.call = function (subscriber, source) { + return source.subscribe(new CountSubscriber(subscriber, this.predicate, this.source)); + }; + return CountOperator; +}()); +var CountSubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](CountSubscriber, _super); + function CountSubscriber(destination, predicate, source) { + var _this = _super.call(this, destination) || this; + _this.predicate = predicate; + _this.source = source; + _this.count = 0; + _this.index = 0; + return _this; + } + CountSubscriber.prototype._next = function (value) { + if (this.predicate) { + this._tryPredicate(value); + } + else { + this.count++; + } + }; + CountSubscriber.prototype._tryPredicate = function (value) { + var result; + try { + result = this.predicate(value, this.index++, this.source); + } + catch (err) { + this.destination.error(err); + return; + } + if (result) { + this.count++; + } + }; + CountSubscriber.prototype._complete = function () { + this.destination.next(this.count); + this.destination.complete(); + }; + return CountSubscriber; +}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); +//# sourceMappingURL=count.js.map + + +/***/ }), +/* 236 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "debounce", function() { return debounce; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(171); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(182); +/** PURE_IMPORTS_START tslib,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ + + + +function debounce(durationSelector) { + return function (source) { return source.lift(new DebounceOperator(durationSelector)); }; +} +var DebounceOperator = /*@__PURE__*/ (function () { + function DebounceOperator(durationSelector) { + this.durationSelector = durationSelector; + } + DebounceOperator.prototype.call = function (subscriber, source) { + return source.subscribe(new DebounceSubscriber(subscriber, this.durationSelector)); + }; + return DebounceOperator; +}()); +var DebounceSubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](DebounceSubscriber, _super); + function DebounceSubscriber(destination, durationSelector) { + var _this = _super.call(this, destination) || this; + _this.durationSelector = durationSelector; + _this.hasValue = false; + _this.durationSubscription = null; + return _this; + } + DebounceSubscriber.prototype._next = function (value) { + try { + var result = this.durationSelector.call(this, value); + if (result) { + this._tryNext(value, result); + } + } + catch (err) { + this.destination.error(err); + } + }; + DebounceSubscriber.prototype._complete = function () { + this.emitValue(); + this.destination.complete(); + }; + DebounceSubscriber.prototype._tryNext = function (value, duration) { + var subscription = this.durationSubscription; + this.value = value; + this.hasValue = true; + if (subscription) { + subscription.unsubscribe(); + this.remove(subscription); + } + subscription = Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__["subscribeToResult"])(this, duration); + if (subscription && !subscription.closed) { + this.add(this.durationSubscription = subscription); + } + }; + DebounceSubscriber.prototype.notifyNext = function (outerValue, innerValue, outerIndex, innerIndex, innerSub) { + this.emitValue(); + }; + DebounceSubscriber.prototype.notifyComplete = function () { + this.emitValue(); + }; + DebounceSubscriber.prototype.emitValue = function () { + if (this.hasValue) { + var value = this.value; + var subscription = this.durationSubscription; + if (subscription) { + this.durationSubscription = null; + subscription.unsubscribe(); + this.remove(subscription); + } + this.value = null; + this.hasValue = false; + _super.prototype._next.call(this, value); + } + }; + return DebounceSubscriber; +}(_OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__["OuterSubscriber"])); +//# sourceMappingURL=debounce.js.map + + +/***/ }), +/* 237 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "debounceTime", function() { return debounceTime; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(172); +/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(199); +/** PURE_IMPORTS_START tslib,_Subscriber,_scheduler_async PURE_IMPORTS_END */ + + + +function debounceTime(dueTime, scheduler) { + if (scheduler === void 0) { + scheduler = _scheduler_async__WEBPACK_IMPORTED_MODULE_2__["async"]; + } + return function (source) { return source.lift(new DebounceTimeOperator(dueTime, scheduler)); }; +} +var DebounceTimeOperator = /*@__PURE__*/ (function () { + function DebounceTimeOperator(dueTime, scheduler) { + this.dueTime = dueTime; + this.scheduler = scheduler; + } + DebounceTimeOperator.prototype.call = function (subscriber, source) { + return source.subscribe(new DebounceTimeSubscriber(subscriber, this.dueTime, this.scheduler)); + }; + return DebounceTimeOperator; +}()); +var DebounceTimeSubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](DebounceTimeSubscriber, _super); + function DebounceTimeSubscriber(destination, dueTime, scheduler) { + var _this = _super.call(this, destination) || this; + _this.dueTime = dueTime; + _this.scheduler = scheduler; + _this.debouncedSubscription = null; + _this.lastValue = null; + _this.hasValue = false; + return _this; + } + DebounceTimeSubscriber.prototype._next = function (value) { + this.clearDebounce(); + this.lastValue = value; + this.hasValue = true; + this.add(this.debouncedSubscription = this.scheduler.schedule(dispatchNext, this.dueTime, this)); + }; + DebounceTimeSubscriber.prototype._complete = function () { + this.debouncedNext(); + this.destination.complete(); + }; + DebounceTimeSubscriber.prototype.debouncedNext = function () { + this.clearDebounce(); + if (this.hasValue) { + var lastValue = this.lastValue; + this.lastValue = null; + this.hasValue = false; + this.destination.next(lastValue); + } + }; + DebounceTimeSubscriber.prototype.clearDebounce = function () { + var debouncedSubscription = this.debouncedSubscription; + if (debouncedSubscription !== null) { + this.remove(debouncedSubscription); + debouncedSubscription.unsubscribe(); + this.debouncedSubscription = null; + } + }; + return DebounceTimeSubscriber; +}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); +function dispatchNext(subscriber) { + subscriber.debouncedNext(); +} +//# sourceMappingURL=debounceTime.js.map + + +/***/ }), +/* 238 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "defaultIfEmpty", function() { return defaultIfEmpty; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(172); +/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ + + +function defaultIfEmpty(defaultValue) { + if (defaultValue === void 0) { + defaultValue = null; + } + return function (source) { return source.lift(new DefaultIfEmptyOperator(defaultValue)); }; +} +var DefaultIfEmptyOperator = /*@__PURE__*/ (function () { + function DefaultIfEmptyOperator(defaultValue) { + this.defaultValue = defaultValue; + } + DefaultIfEmptyOperator.prototype.call = function (subscriber, source) { + return source.subscribe(new DefaultIfEmptySubscriber(subscriber, this.defaultValue)); + }; + return DefaultIfEmptyOperator; +}()); +var DefaultIfEmptySubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](DefaultIfEmptySubscriber, _super); + function DefaultIfEmptySubscriber(destination, defaultValue) { + var _this = _super.call(this, destination) || this; + _this.defaultValue = defaultValue; + _this.isEmpty = true; + return _this; + } + DefaultIfEmptySubscriber.prototype._next = function (value) { + this.isEmpty = false; + this.destination.next(value); + }; + DefaultIfEmptySubscriber.prototype._complete = function () { + if (this.isEmpty) { + this.destination.next(this.defaultValue); + } + this.destination.complete(); + }; + return DefaultIfEmptySubscriber; +}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); +//# sourceMappingURL=defaultIfEmpty.js.map + + +/***/ }), +/* 239 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "delay", function() { return delay; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(199); +/* harmony import */ var _util_isDate__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(240); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(172); +/* harmony import */ var _Notification__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(241); +/** PURE_IMPORTS_START tslib,_scheduler_async,_util_isDate,_Subscriber,_Notification PURE_IMPORTS_END */ + + + + + +function delay(delay, scheduler) { + if (scheduler === void 0) { + scheduler = _scheduler_async__WEBPACK_IMPORTED_MODULE_1__["async"]; + } + var absoluteDelay = Object(_util_isDate__WEBPACK_IMPORTED_MODULE_2__["isDate"])(delay); + var delayFor = absoluteDelay ? (+delay - scheduler.now()) : Math.abs(delay); + return function (source) { return source.lift(new DelayOperator(delayFor, scheduler)); }; +} +var DelayOperator = /*@__PURE__*/ (function () { + function DelayOperator(delay, scheduler) { + this.delay = delay; + this.scheduler = scheduler; + } + DelayOperator.prototype.call = function (subscriber, source) { + return source.subscribe(new DelaySubscriber(subscriber, this.delay, this.scheduler)); + }; + return DelayOperator; +}()); +var DelaySubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](DelaySubscriber, _super); + function DelaySubscriber(destination, delay, scheduler) { + var _this = _super.call(this, destination) || this; + _this.delay = delay; + _this.scheduler = scheduler; + _this.queue = []; + _this.active = false; + _this.errored = false; + return _this; + } + DelaySubscriber.dispatch = function (state) { + var source = state.source; + var queue = source.queue; + var scheduler = state.scheduler; + var destination = state.destination; + while (queue.length > 0 && (queue[0].time - scheduler.now()) <= 0) { + queue.shift().notification.observe(destination); + } + if (queue.length > 0) { + var delay_1 = Math.max(0, queue[0].time - scheduler.now()); + this.schedule(state, delay_1); + } + else { + this.unsubscribe(); + source.active = false; + } + }; + DelaySubscriber.prototype._schedule = function (scheduler) { + this.active = true; + var destination = this.destination; + destination.add(scheduler.schedule(DelaySubscriber.dispatch, this.delay, { + source: this, destination: this.destination, scheduler: scheduler + })); + }; + DelaySubscriber.prototype.scheduleNotification = function (notification) { + if (this.errored === true) { + return; + } + var scheduler = this.scheduler; + var message = new DelayMessage(scheduler.now() + this.delay, notification); + this.queue.push(message); + if (this.active === false) { + this._schedule(scheduler); + } + }; + DelaySubscriber.prototype._next = function (value) { + this.scheduleNotification(_Notification__WEBPACK_IMPORTED_MODULE_4__["Notification"].createNext(value)); + }; + DelaySubscriber.prototype._error = function (err) { + this.errored = true; + this.queue = []; + this.destination.error(err); + this.unsubscribe(); + }; + DelaySubscriber.prototype._complete = function () { + this.scheduleNotification(_Notification__WEBPACK_IMPORTED_MODULE_4__["Notification"].createComplete()); + this.unsubscribe(); + }; + return DelaySubscriber; +}(_Subscriber__WEBPACK_IMPORTED_MODULE_3__["Subscriber"])); +var DelayMessage = /*@__PURE__*/ (function () { + function DelayMessage(time, notification) { + this.time = time; + this.notification = notification; + } + return DelayMessage; +}()); +//# sourceMappingURL=delay.js.map + + +/***/ }), +/* 240 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "isDate", function() { return isDate; }); +/** PURE_IMPORTS_START PURE_IMPORTS_END */ +function isDate(value) { + return value instanceof Date && !isNaN(+value); +} +//# sourceMappingURL=isDate.js.map + + +/***/ }), +/* 241 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "NotificationKind", function() { return NotificationKind; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Notification", function() { return Notification; }); +/* harmony import */ var _observable_empty__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(242); +/* harmony import */ var _observable_of__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(227); +/* harmony import */ var _observable_throwError__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(243); +/** PURE_IMPORTS_START _observable_empty,_observable_of,_observable_throwError PURE_IMPORTS_END */ + + + +var NotificationKind; +/*@__PURE__*/ (function (NotificationKind) { + NotificationKind["NEXT"] = "N"; + NotificationKind["ERROR"] = "E"; + NotificationKind["COMPLETE"] = "C"; +})(NotificationKind || (NotificationKind = {})); +var Notification = /*@__PURE__*/ (function () { + function Notification(kind, value, error) { + this.kind = kind; + this.value = value; + this.error = error; + this.hasValue = kind === 'N'; + } + Notification.prototype.observe = function (observer) { + switch (this.kind) { + case 'N': + return observer.next && observer.next(this.value); + case 'E': + return observer.error && observer.error(this.error); + case 'C': + return observer.complete && observer.complete(); + } + }; + Notification.prototype.do = function (next, error, complete) { + var kind = this.kind; + switch (kind) { + case 'N': + return next && next(this.value); + case 'E': + return error && error(this.error); + case 'C': + return complete && complete(); + } + }; + Notification.prototype.accept = function (nextOrObserver, error, complete) { + if (nextOrObserver && typeof nextOrObserver.next === 'function') { + return this.observe(nextOrObserver); + } + else { + return this.do(nextOrObserver, error, complete); + } + }; + Notification.prototype.toObservable = function () { + var kind = this.kind; + switch (kind) { + case 'N': + return Object(_observable_of__WEBPACK_IMPORTED_MODULE_1__["of"])(this.value); + case 'E': + return Object(_observable_throwError__WEBPACK_IMPORTED_MODULE_2__["throwError"])(this.error); + case 'C': + return Object(_observable_empty__WEBPACK_IMPORTED_MODULE_0__["empty"])(); + } + throw new Error('unexpected notification kind value'); + }; + Notification.createNext = function (value) { + if (typeof value !== 'undefined') { + return new Notification('N', value); + } + return Notification.undefinedValueNotification; + }; + Notification.createError = function (err) { + return new Notification('E', undefined, err); + }; + Notification.createComplete = function () { + return Notification.completeNotification; + }; + Notification.completeNotification = new Notification('C'); + Notification.undefinedValueNotification = new Notification('N', undefined); + return Notification; +}()); + +//# sourceMappingURL=Notification.js.map + + +/***/ }), +/* 242 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "EMPTY", function() { return EMPTY; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "empty", function() { return empty; }); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(193); +/** PURE_IMPORTS_START _Observable PURE_IMPORTS_END */ + +var EMPTY = /*@__PURE__*/ new _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"](function (subscriber) { return subscriber.complete(); }); +function empty(scheduler) { + return scheduler ? emptyScheduled(scheduler) : EMPTY; +} +function emptyScheduled(scheduler) { + return new _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"](function (subscriber) { return scheduler.schedule(function () { return subscriber.complete(); }); }); +} +//# sourceMappingURL=empty.js.map + + +/***/ }), +/* 243 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "throwError", function() { return throwError; }); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(193); +/** PURE_IMPORTS_START _Observable PURE_IMPORTS_END */ + +function throwError(error, scheduler) { + if (!scheduler) { + return new _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"](function (subscriber) { return subscriber.error(error); }); + } + else { + return new _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"](function (subscriber) { return scheduler.schedule(dispatch, 0, { error: error, subscriber: subscriber }); }); + } +} +function dispatch(_a) { + var error = _a.error, subscriber = _a.subscriber; + subscriber.error(error); +} +//# sourceMappingURL=throwError.js.map + + +/***/ }), +/* 244 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "delayWhen", function() { return delayWhen; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(172); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(193); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(171); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(182); +/** PURE_IMPORTS_START tslib,_Subscriber,_Observable,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ + + + + + +function delayWhen(delayDurationSelector, subscriptionDelay) { + if (subscriptionDelay) { + return function (source) { + return new SubscriptionDelayObservable(source, subscriptionDelay) + .lift(new DelayWhenOperator(delayDurationSelector)); + }; + } + return function (source) { return source.lift(new DelayWhenOperator(delayDurationSelector)); }; +} +var DelayWhenOperator = /*@__PURE__*/ (function () { + function DelayWhenOperator(delayDurationSelector) { + this.delayDurationSelector = delayDurationSelector; + } + DelayWhenOperator.prototype.call = function (subscriber, source) { + return source.subscribe(new DelayWhenSubscriber(subscriber, this.delayDurationSelector)); + }; + return DelayWhenOperator; +}()); +var DelayWhenSubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](DelayWhenSubscriber, _super); + function DelayWhenSubscriber(destination, delayDurationSelector) { + var _this = _super.call(this, destination) || this; + _this.delayDurationSelector = delayDurationSelector; + _this.completed = false; + _this.delayNotifierSubscriptions = []; + _this.index = 0; + return _this; + } + DelayWhenSubscriber.prototype.notifyNext = function (outerValue, innerValue, outerIndex, innerIndex, innerSub) { + this.destination.next(outerValue); + this.removeSubscription(innerSub); + this.tryComplete(); + }; + DelayWhenSubscriber.prototype.notifyError = function (error, innerSub) { + this._error(error); + }; + DelayWhenSubscriber.prototype.notifyComplete = function (innerSub) { + var value = this.removeSubscription(innerSub); + if (value) { + this.destination.next(value); + } + this.tryComplete(); + }; + DelayWhenSubscriber.prototype._next = function (value) { + var index = this.index++; + try { + var delayNotifier = this.delayDurationSelector(value, index); + if (delayNotifier) { + this.tryDelay(delayNotifier, value); + } + } + catch (err) { + this.destination.error(err); + } + }; + DelayWhenSubscriber.prototype._complete = function () { + this.completed = true; + this.tryComplete(); + this.unsubscribe(); + }; + DelayWhenSubscriber.prototype.removeSubscription = function (subscription) { + subscription.unsubscribe(); + var subscriptionIdx = this.delayNotifierSubscriptions.indexOf(subscription); + if (subscriptionIdx !== -1) { + this.delayNotifierSubscriptions.splice(subscriptionIdx, 1); + } + return subscription.outerValue; + }; + DelayWhenSubscriber.prototype.tryDelay = function (delayNotifier, value) { + var notifierSubscription = Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_4__["subscribeToResult"])(this, delayNotifier, value); + if (notifierSubscription && !notifierSubscription.closed) { + var destination = this.destination; + destination.add(notifierSubscription); + this.delayNotifierSubscriptions.push(notifierSubscription); + } + }; + DelayWhenSubscriber.prototype.tryComplete = function () { + if (this.completed && this.delayNotifierSubscriptions.length === 0) { + this.destination.complete(); + } + }; + return DelayWhenSubscriber; +}(_OuterSubscriber__WEBPACK_IMPORTED_MODULE_3__["OuterSubscriber"])); +var SubscriptionDelayObservable = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](SubscriptionDelayObservable, _super); + function SubscriptionDelayObservable(source, subscriptionDelay) { + var _this = _super.call(this) || this; + _this.source = source; + _this.subscriptionDelay = subscriptionDelay; + return _this; + } + SubscriptionDelayObservable.prototype._subscribe = function (subscriber) { + this.subscriptionDelay.subscribe(new SubscriptionDelaySubscriber(subscriber, this.source)); + }; + return SubscriptionDelayObservable; +}(_Observable__WEBPACK_IMPORTED_MODULE_2__["Observable"])); +var SubscriptionDelaySubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](SubscriptionDelaySubscriber, _super); + function SubscriptionDelaySubscriber(parent, source) { + var _this = _super.call(this) || this; + _this.parent = parent; + _this.source = source; + _this.sourceSubscribed = false; + return _this; + } + SubscriptionDelaySubscriber.prototype._next = function (unused) { + this.subscribeToSource(); + }; + SubscriptionDelaySubscriber.prototype._error = function (err) { + this.unsubscribe(); + this.parent.error(err); + }; + SubscriptionDelaySubscriber.prototype._complete = function () { + this.unsubscribe(); + this.subscribeToSource(); + }; + SubscriptionDelaySubscriber.prototype.subscribeToSource = function () { + if (!this.sourceSubscribed) { + this.sourceSubscribed = true; + this.unsubscribe(); + this.source.subscribe(this.parent); + } + }; + return SubscriptionDelaySubscriber; +}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); +//# sourceMappingURL=delayWhen.js.map + + +/***/ }), +/* 245 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "dematerialize", function() { return dematerialize; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(172); +/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ + + +function dematerialize() { + return function dematerializeOperatorFunction(source) { + return source.lift(new DeMaterializeOperator()); + }; +} +var DeMaterializeOperator = /*@__PURE__*/ (function () { + function DeMaterializeOperator() { + } + DeMaterializeOperator.prototype.call = function (subscriber, source) { + return source.subscribe(new DeMaterializeSubscriber(subscriber)); + }; + return DeMaterializeOperator; +}()); +var DeMaterializeSubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](DeMaterializeSubscriber, _super); + function DeMaterializeSubscriber(destination) { + return _super.call(this, destination) || this; + } + DeMaterializeSubscriber.prototype._next = function (value) { + value.observe(this.destination); + }; + return DeMaterializeSubscriber; +}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); +//# sourceMappingURL=dematerialize.js.map + + +/***/ }), +/* 246 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "distinct", function() { return distinct; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "DistinctSubscriber", function() { return DistinctSubscriber; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(171); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(182); +/** PURE_IMPORTS_START tslib,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ + + + +function distinct(keySelector, flushes) { + return function (source) { return source.lift(new DistinctOperator(keySelector, flushes)); }; +} +var DistinctOperator = /*@__PURE__*/ (function () { + function DistinctOperator(keySelector, flushes) { + this.keySelector = keySelector; + this.flushes = flushes; + } + DistinctOperator.prototype.call = function (subscriber, source) { + return source.subscribe(new DistinctSubscriber(subscriber, this.keySelector, this.flushes)); + }; + return DistinctOperator; +}()); +var DistinctSubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](DistinctSubscriber, _super); + function DistinctSubscriber(destination, keySelector, flushes) { + var _this = _super.call(this, destination) || this; + _this.keySelector = keySelector; + _this.values = new Set(); + if (flushes) { + _this.add(Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__["subscribeToResult"])(_this, flushes)); + } + return _this; + } + DistinctSubscriber.prototype.notifyNext = function (outerValue, innerValue, outerIndex, innerIndex, innerSub) { + this.values.clear(); + }; + DistinctSubscriber.prototype.notifyError = function (error, innerSub) { + this._error(error); + }; + DistinctSubscriber.prototype._next = function (value) { + if (this.keySelector) { + this._useKeySelector(value); + } + else { + this._finalizeNext(value, value); + } + }; + DistinctSubscriber.prototype._useKeySelector = function (value) { + var key; + var destination = this.destination; + try { + key = this.keySelector(value); + } + catch (err) { + destination.error(err); + return; + } + this._finalizeNext(key, value); + }; + DistinctSubscriber.prototype._finalizeNext = function (key, value) { + var values = this.values; + if (!values.has(key)) { + values.add(key); + this.destination.next(value); + } + }; + return DistinctSubscriber; +}(_OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__["OuterSubscriber"])); + +//# sourceMappingURL=distinct.js.map + + +/***/ }), +/* 247 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "distinctUntilChanged", function() { return distinctUntilChanged; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(172); +/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ + + +function distinctUntilChanged(compare, keySelector) { + return function (source) { return source.lift(new DistinctUntilChangedOperator(compare, keySelector)); }; +} +var DistinctUntilChangedOperator = /*@__PURE__*/ (function () { + function DistinctUntilChangedOperator(compare, keySelector) { + this.compare = compare; + this.keySelector = keySelector; + } + DistinctUntilChangedOperator.prototype.call = function (subscriber, source) { + return source.subscribe(new DistinctUntilChangedSubscriber(subscriber, this.compare, this.keySelector)); + }; + return DistinctUntilChangedOperator; +}()); +var DistinctUntilChangedSubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](DistinctUntilChangedSubscriber, _super); + function DistinctUntilChangedSubscriber(destination, compare, keySelector) { + var _this = _super.call(this, destination) || this; + _this.keySelector = keySelector; + _this.hasKey = false; + if (typeof compare === 'function') { + _this.compare = compare; + } + return _this; + } + DistinctUntilChangedSubscriber.prototype.compare = function (x, y) { + return x === y; + }; + DistinctUntilChangedSubscriber.prototype._next = function (value) { + var key; + try { + var keySelector = this.keySelector; + key = keySelector ? keySelector(value) : value; + } + catch (err) { + return this.destination.error(err); + } + var result = false; + if (this.hasKey) { + try { + var compare = this.compare; + result = compare(this.key, key); + } + catch (err) { + return this.destination.error(err); + } + } + else { + this.hasKey = true; + } + if (!result) { + this.key = key; + this.destination.next(value); + } + }; + return DistinctUntilChangedSubscriber; +}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); +//# sourceMappingURL=distinctUntilChanged.js.map + + +/***/ }), +/* 248 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "distinctUntilKeyChanged", function() { return distinctUntilKeyChanged; }); +/* harmony import */ var _distinctUntilChanged__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(247); +/** PURE_IMPORTS_START _distinctUntilChanged PURE_IMPORTS_END */ + +function distinctUntilKeyChanged(key, compare) { + return Object(_distinctUntilChanged__WEBPACK_IMPORTED_MODULE_0__["distinctUntilChanged"])(function (x, y) { return compare ? compare(x[key], y[key]) : x[key] === y[key]; }); +} +//# sourceMappingURL=distinctUntilKeyChanged.js.map + + +/***/ }), +/* 249 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "elementAt", function() { return elementAt; }); +/* harmony import */ var _util_ArgumentOutOfRangeError__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(250); +/* harmony import */ var _filter__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(251); +/* harmony import */ var _throwIfEmpty__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(252); +/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(238); +/* harmony import */ var _take__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(254); +/** PURE_IMPORTS_START _util_ArgumentOutOfRangeError,_filter,_throwIfEmpty,_defaultIfEmpty,_take PURE_IMPORTS_END */ + + + + + +function elementAt(index, defaultValue) { + if (index < 0) { + throw new _util_ArgumentOutOfRangeError__WEBPACK_IMPORTED_MODULE_0__["ArgumentOutOfRangeError"](); + } + var hasDefaultValue = arguments.length >= 2; + return function (source) { + return source.pipe(Object(_filter__WEBPACK_IMPORTED_MODULE_1__["filter"])(function (v, i) { return i === index; }), Object(_take__WEBPACK_IMPORTED_MODULE_4__["take"])(1), hasDefaultValue + ? Object(_defaultIfEmpty__WEBPACK_IMPORTED_MODULE_3__["defaultIfEmpty"])(defaultValue) + : Object(_throwIfEmpty__WEBPACK_IMPORTED_MODULE_2__["throwIfEmpty"])(function () { return new _util_ArgumentOutOfRangeError__WEBPACK_IMPORTED_MODULE_0__["ArgumentOutOfRangeError"](); })); + }; +} +//# sourceMappingURL=elementAt.js.map + + +/***/ }), +/* 250 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ArgumentOutOfRangeError", function() { return ArgumentOutOfRangeError; }); +/** PURE_IMPORTS_START PURE_IMPORTS_END */ +var ArgumentOutOfRangeErrorImpl = /*@__PURE__*/ (function () { + function ArgumentOutOfRangeErrorImpl() { + Error.call(this); + this.message = 'argument out of range'; + this.name = 'ArgumentOutOfRangeError'; + return this; + } + ArgumentOutOfRangeErrorImpl.prototype = /*@__PURE__*/ Object.create(Error.prototype); + return ArgumentOutOfRangeErrorImpl; +})(); +var ArgumentOutOfRangeError = ArgumentOutOfRangeErrorImpl; +//# sourceMappingURL=ArgumentOutOfRangeError.js.map + + +/***/ }), +/* 251 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "filter", function() { return filter; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(172); +/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ + + +function filter(predicate, thisArg) { + return function filterOperatorFunction(source) { + return source.lift(new FilterOperator(predicate, thisArg)); + }; +} +var FilterOperator = /*@__PURE__*/ (function () { + function FilterOperator(predicate, thisArg) { + this.predicate = predicate; + this.thisArg = thisArg; + } + FilterOperator.prototype.call = function (subscriber, source) { + return source.subscribe(new FilterSubscriber(subscriber, this.predicate, this.thisArg)); + }; + return FilterOperator; +}()); +var FilterSubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](FilterSubscriber, _super); + function FilterSubscriber(destination, predicate, thisArg) { + var _this = _super.call(this, destination) || this; + _this.predicate = predicate; + _this.thisArg = thisArg; + _this.count = 0; + return _this; + } + FilterSubscriber.prototype._next = function (value) { + var result; + try { + result = this.predicate.call(this.thisArg, value, this.count++); + } + catch (err) { + this.destination.error(err); + return; + } + if (result) { + this.destination.next(value); + } + }; + return FilterSubscriber; +}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); +//# sourceMappingURL=filter.js.map + + +/***/ }), +/* 252 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "throwIfEmpty", function() { return throwIfEmpty; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _util_EmptyError__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(253); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(172); +/** PURE_IMPORTS_START tslib,_util_EmptyError,_Subscriber PURE_IMPORTS_END */ + + + +function throwIfEmpty(errorFactory) { + if (errorFactory === void 0) { + errorFactory = defaultErrorFactory; + } + return function (source) { + return source.lift(new ThrowIfEmptyOperator(errorFactory)); + }; +} +var ThrowIfEmptyOperator = /*@__PURE__*/ (function () { + function ThrowIfEmptyOperator(errorFactory) { + this.errorFactory = errorFactory; + } + ThrowIfEmptyOperator.prototype.call = function (subscriber, source) { + return source.subscribe(new ThrowIfEmptySubscriber(subscriber, this.errorFactory)); + }; + return ThrowIfEmptyOperator; +}()); +var ThrowIfEmptySubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](ThrowIfEmptySubscriber, _super); + function ThrowIfEmptySubscriber(destination, errorFactory) { + var _this = _super.call(this, destination) || this; + _this.errorFactory = errorFactory; + _this.hasValue = false; + return _this; + } + ThrowIfEmptySubscriber.prototype._next = function (value) { + this.hasValue = true; + this.destination.next(value); + }; + ThrowIfEmptySubscriber.prototype._complete = function () { + if (!this.hasValue) { + var err = void 0; + try { + err = this.errorFactory(); + } + catch (e) { + err = e; + } + this.destination.error(err); + } + else { + return this.destination.complete(); + } + }; + return ThrowIfEmptySubscriber; +}(_Subscriber__WEBPACK_IMPORTED_MODULE_2__["Subscriber"])); +function defaultErrorFactory() { + return new _util_EmptyError__WEBPACK_IMPORTED_MODULE_1__["EmptyError"](); +} +//# sourceMappingURL=throwIfEmpty.js.map + + +/***/ }), +/* 253 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "EmptyError", function() { return EmptyError; }); +/** PURE_IMPORTS_START PURE_IMPORTS_END */ +var EmptyErrorImpl = /*@__PURE__*/ (function () { + function EmptyErrorImpl() { + Error.call(this); + this.message = 'no elements in sequence'; + this.name = 'EmptyError'; + return this; + } + EmptyErrorImpl.prototype = /*@__PURE__*/ Object.create(Error.prototype); + return EmptyErrorImpl; +})(); +var EmptyError = EmptyErrorImpl; +//# sourceMappingURL=EmptyError.js.map + + +/***/ }), +/* 254 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "take", function() { return take; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(172); +/* harmony import */ var _util_ArgumentOutOfRangeError__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(250); +/* harmony import */ var _observable_empty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(242); +/** PURE_IMPORTS_START tslib,_Subscriber,_util_ArgumentOutOfRangeError,_observable_empty PURE_IMPORTS_END */ + + + + +function take(count) { + return function (source) { + if (count === 0) { + return Object(_observable_empty__WEBPACK_IMPORTED_MODULE_3__["empty"])(); + } + else { + return source.lift(new TakeOperator(count)); + } + }; +} +var TakeOperator = /*@__PURE__*/ (function () { + function TakeOperator(total) { + this.total = total; + if (this.total < 0) { + throw new _util_ArgumentOutOfRangeError__WEBPACK_IMPORTED_MODULE_2__["ArgumentOutOfRangeError"]; + } + } + TakeOperator.prototype.call = function (subscriber, source) { + return source.subscribe(new TakeSubscriber(subscriber, this.total)); + }; + return TakeOperator; +}()); +var TakeSubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](TakeSubscriber, _super); + function TakeSubscriber(destination, total) { + var _this = _super.call(this, destination) || this; + _this.total = total; + _this.count = 0; + return _this; + } + TakeSubscriber.prototype._next = function (value) { + var total = this.total; + var count = ++this.count; + if (count <= total) { + this.destination.next(value); + if (count === total) { + this.destination.complete(); + this.unsubscribe(); + } + } + }; + return TakeSubscriber; +}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); +//# sourceMappingURL=take.js.map + + +/***/ }), +/* 255 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "endWith", function() { return endWith; }); +/* harmony import */ var _observable_concat__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(226); +/* harmony import */ var _observable_of__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(227); +/** PURE_IMPORTS_START _observable_concat,_observable_of PURE_IMPORTS_END */ + + +function endWith() { + var array = []; + for (var _i = 0; _i < arguments.length; _i++) { + array[_i] = arguments[_i]; + } + return function (source) { return Object(_observable_concat__WEBPACK_IMPORTED_MODULE_0__["concat"])(source, _observable_of__WEBPACK_IMPORTED_MODULE_1__["of"].apply(void 0, array)); }; +} +//# sourceMappingURL=endWith.js.map + + +/***/ }), +/* 256 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "every", function() { return every; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(172); +/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ + + +function every(predicate, thisArg) { + return function (source) { return source.lift(new EveryOperator(predicate, thisArg, source)); }; +} +var EveryOperator = /*@__PURE__*/ (function () { + function EveryOperator(predicate, thisArg, source) { + this.predicate = predicate; + this.thisArg = thisArg; + this.source = source; + } + EveryOperator.prototype.call = function (observer, source) { + return source.subscribe(new EverySubscriber(observer, this.predicate, this.thisArg, this.source)); + }; + return EveryOperator; +}()); +var EverySubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](EverySubscriber, _super); + function EverySubscriber(destination, predicate, thisArg, source) { + var _this = _super.call(this, destination) || this; + _this.predicate = predicate; + _this.thisArg = thisArg; + _this.source = source; + _this.index = 0; + _this.thisArg = thisArg || _this; + return _this; + } + EverySubscriber.prototype.notifyComplete = function (everyValueMatch) { + this.destination.next(everyValueMatch); + this.destination.complete(); + }; + EverySubscriber.prototype._next = function (value) { + var result = false; + try { + result = this.predicate.call(this.thisArg, value, this.index++, this.source); + } + catch (err) { + this.destination.error(err); + return; + } + if (!result) { + this.notifyComplete(false); + } + }; + EverySubscriber.prototype._complete = function () { + this.notifyComplete(true); + }; + return EverySubscriber; +}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); +//# sourceMappingURL=every.js.map + + +/***/ }), +/* 257 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "exhaust", function() { return exhaust; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(171); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(182); +/** PURE_IMPORTS_START tslib,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ + + + +function exhaust() { + return function (source) { return source.lift(new SwitchFirstOperator()); }; +} +var SwitchFirstOperator = /*@__PURE__*/ (function () { + function SwitchFirstOperator() { + } + SwitchFirstOperator.prototype.call = function (subscriber, source) { + return source.subscribe(new SwitchFirstSubscriber(subscriber)); + }; + return SwitchFirstOperator; +}()); +var SwitchFirstSubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](SwitchFirstSubscriber, _super); + function SwitchFirstSubscriber(destination) { + var _this = _super.call(this, destination) || this; + _this.hasCompleted = false; + _this.hasSubscription = false; + return _this; + } + SwitchFirstSubscriber.prototype._next = function (value) { + if (!this.hasSubscription) { + this.hasSubscription = true; + this.add(Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__["subscribeToResult"])(this, value)); + } + }; + SwitchFirstSubscriber.prototype._complete = function () { + this.hasCompleted = true; + if (!this.hasSubscription) { + this.destination.complete(); + } + }; + SwitchFirstSubscriber.prototype.notifyComplete = function (innerSub) { + this.remove(innerSub); + this.hasSubscription = false; + if (this.hasCompleted) { + this.destination.complete(); + } + }; + return SwitchFirstSubscriber; +}(_OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__["OuterSubscriber"])); +//# sourceMappingURL=exhaust.js.map + + +/***/ }), +/* 258 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "exhaustMap", function() { return exhaustMap; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(171); +/* harmony import */ var _InnerSubscriber__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(183); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(182); +/* harmony import */ var _map__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(231); +/* harmony import */ var _observable_from__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(218); +/** PURE_IMPORTS_START tslib,_OuterSubscriber,_InnerSubscriber,_util_subscribeToResult,_map,_observable_from PURE_IMPORTS_END */ + + + + + + +function exhaustMap(project, resultSelector) { + if (resultSelector) { + return function (source) { return source.pipe(exhaustMap(function (a, i) { return Object(_observable_from__WEBPACK_IMPORTED_MODULE_5__["from"])(project(a, i)).pipe(Object(_map__WEBPACK_IMPORTED_MODULE_4__["map"])(function (b, ii) { return resultSelector(a, b, i, ii); })); })); }; + } + return function (source) { + return source.lift(new ExhaustMapOperator(project)); + }; +} +var ExhaustMapOperator = /*@__PURE__*/ (function () { + function ExhaustMapOperator(project) { + this.project = project; + } + ExhaustMapOperator.prototype.call = function (subscriber, source) { + return source.subscribe(new ExhaustMapSubscriber(subscriber, this.project)); + }; + return ExhaustMapOperator; +}()); +var ExhaustMapSubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](ExhaustMapSubscriber, _super); + function ExhaustMapSubscriber(destination, project) { + var _this = _super.call(this, destination) || this; + _this.project = project; + _this.hasSubscription = false; + _this.hasCompleted = false; + _this.index = 0; + return _this; + } + ExhaustMapSubscriber.prototype._next = function (value) { + if (!this.hasSubscription) { + this.tryNext(value); + } + }; + ExhaustMapSubscriber.prototype.tryNext = function (value) { + var result; + var index = this.index++; + try { + result = this.project(value, index); + } + catch (err) { + this.destination.error(err); + return; + } + this.hasSubscription = true; + this._innerSub(result, value, index); + }; + ExhaustMapSubscriber.prototype._innerSub = function (result, value, index) { + var innerSubscriber = new _InnerSubscriber__WEBPACK_IMPORTED_MODULE_2__["InnerSubscriber"](this, undefined, undefined); + var destination = this.destination; + destination.add(innerSubscriber); + Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_3__["subscribeToResult"])(this, result, value, index, innerSubscriber); + }; + ExhaustMapSubscriber.prototype._complete = function () { + this.hasCompleted = true; + if (!this.hasSubscription) { + this.destination.complete(); + } + this.unsubscribe(); + }; + ExhaustMapSubscriber.prototype.notifyNext = function (outerValue, innerValue, outerIndex, innerIndex, innerSub) { + this.destination.next(innerValue); + }; + ExhaustMapSubscriber.prototype.notifyError = function (err) { + this.destination.error(err); + }; + ExhaustMapSubscriber.prototype.notifyComplete = function (innerSub) { + var destination = this.destination; + destination.remove(innerSub); + this.hasSubscription = false; + if (this.hasCompleted) { + this.destination.complete(); + } + }; + return ExhaustMapSubscriber; +}(_OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__["OuterSubscriber"])); +//# sourceMappingURL=exhaustMap.js.map + + +/***/ }), +/* 259 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "expand", function() { return expand; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ExpandOperator", function() { return ExpandOperator; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ExpandSubscriber", function() { return ExpandSubscriber; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(171); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(182); +/** PURE_IMPORTS_START tslib,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ + + + +function expand(project, concurrent, scheduler) { + if (concurrent === void 0) { + concurrent = Number.POSITIVE_INFINITY; + } + if (scheduler === void 0) { + scheduler = undefined; + } + concurrent = (concurrent || 0) < 1 ? Number.POSITIVE_INFINITY : concurrent; + return function (source) { return source.lift(new ExpandOperator(project, concurrent, scheduler)); }; +} +var ExpandOperator = /*@__PURE__*/ (function () { + function ExpandOperator(project, concurrent, scheduler) { + this.project = project; + this.concurrent = concurrent; + this.scheduler = scheduler; + } + ExpandOperator.prototype.call = function (subscriber, source) { + return source.subscribe(new ExpandSubscriber(subscriber, this.project, this.concurrent, this.scheduler)); + }; + return ExpandOperator; +}()); + +var ExpandSubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](ExpandSubscriber, _super); + function ExpandSubscriber(destination, project, concurrent, scheduler) { + var _this = _super.call(this, destination) || this; + _this.project = project; + _this.concurrent = concurrent; + _this.scheduler = scheduler; + _this.index = 0; + _this.active = 0; + _this.hasCompleted = false; + if (concurrent < Number.POSITIVE_INFINITY) { + _this.buffer = []; + } + return _this; + } + ExpandSubscriber.dispatch = function (arg) { + var subscriber = arg.subscriber, result = arg.result, value = arg.value, index = arg.index; + subscriber.subscribeToProjection(result, value, index); + }; + ExpandSubscriber.prototype._next = function (value) { + var destination = this.destination; + if (destination.closed) { + this._complete(); + return; + } + var index = this.index++; + if (this.active < this.concurrent) { + destination.next(value); + try { + var project = this.project; + var result = project(value, index); + if (!this.scheduler) { + this.subscribeToProjection(result, value, index); + } + else { + var state = { subscriber: this, result: result, value: value, index: index }; + var destination_1 = this.destination; + destination_1.add(this.scheduler.schedule(ExpandSubscriber.dispatch, 0, state)); + } + } + catch (e) { + destination.error(e); + } + } + else { + this.buffer.push(value); + } + }; + ExpandSubscriber.prototype.subscribeToProjection = function (result, value, index) { + this.active++; + var destination = this.destination; + destination.add(Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__["subscribeToResult"])(this, result, value, index)); + }; + ExpandSubscriber.prototype._complete = function () { + this.hasCompleted = true; + if (this.hasCompleted && this.active === 0) { + this.destination.complete(); + } + this.unsubscribe(); + }; + ExpandSubscriber.prototype.notifyNext = function (outerValue, innerValue, outerIndex, innerIndex, innerSub) { + this._next(innerValue); + }; + ExpandSubscriber.prototype.notifyComplete = function (innerSub) { + var buffer = this.buffer; + var destination = this.destination; + destination.remove(innerSub); + this.active--; + if (buffer && buffer.length > 0) { + this._next(buffer.shift()); + } + if (this.hasCompleted && this.active === 0) { + this.destination.complete(); + } + }; + return ExpandSubscriber; +}(_OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__["OuterSubscriber"])); + +//# sourceMappingURL=expand.js.map + + +/***/ }), +/* 260 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "finalize", function() { return finalize; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(172); +/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(177); +/** PURE_IMPORTS_START tslib,_Subscriber,_Subscription PURE_IMPORTS_END */ + + + +function finalize(callback) { + return function (source) { return source.lift(new FinallyOperator(callback)); }; +} +var FinallyOperator = /*@__PURE__*/ (function () { + function FinallyOperator(callback) { + this.callback = callback; + } + FinallyOperator.prototype.call = function (subscriber, source) { + return source.subscribe(new FinallySubscriber(subscriber, this.callback)); + }; + return FinallyOperator; +}()); +var FinallySubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](FinallySubscriber, _super); + function FinallySubscriber(destination, callback) { + var _this = _super.call(this, destination) || this; + _this.add(new _Subscription__WEBPACK_IMPORTED_MODULE_2__["Subscription"](callback)); + return _this; + } + return FinallySubscriber; +}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); +//# sourceMappingURL=finalize.js.map + + +/***/ }), +/* 261 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "find", function() { return find; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "FindValueOperator", function() { return FindValueOperator; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "FindValueSubscriber", function() { return FindValueSubscriber; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(172); +/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ + + +function find(predicate, thisArg) { + if (typeof predicate !== 'function') { + throw new TypeError('predicate is not a function'); + } + return function (source) { return source.lift(new FindValueOperator(predicate, source, false, thisArg)); }; +} +var FindValueOperator = /*@__PURE__*/ (function () { + function FindValueOperator(predicate, source, yieldIndex, thisArg) { + this.predicate = predicate; + this.source = source; + this.yieldIndex = yieldIndex; + this.thisArg = thisArg; + } + FindValueOperator.prototype.call = function (observer, source) { + return source.subscribe(new FindValueSubscriber(observer, this.predicate, this.source, this.yieldIndex, this.thisArg)); + }; + return FindValueOperator; +}()); + +var FindValueSubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](FindValueSubscriber, _super); + function FindValueSubscriber(destination, predicate, source, yieldIndex, thisArg) { + var _this = _super.call(this, destination) || this; + _this.predicate = predicate; + _this.source = source; + _this.yieldIndex = yieldIndex; + _this.thisArg = thisArg; + _this.index = 0; + return _this; + } + FindValueSubscriber.prototype.notifyComplete = function (value) { + var destination = this.destination; + destination.next(value); + destination.complete(); + this.unsubscribe(); + }; + FindValueSubscriber.prototype._next = function (value) { + var _a = this, predicate = _a.predicate, thisArg = _a.thisArg; + var index = this.index++; + try { + var result = predicate.call(thisArg || this, value, index, this.source); + if (result) { + this.notifyComplete(this.yieldIndex ? index : value); + } + } + catch (err) { + this.destination.error(err); + } + }; + FindValueSubscriber.prototype._complete = function () { + this.notifyComplete(this.yieldIndex ? -1 : undefined); + }; + return FindValueSubscriber; +}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); + +//# sourceMappingURL=find.js.map + + +/***/ }), +/* 262 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "findIndex", function() { return findIndex; }); +/* harmony import */ var _operators_find__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(261); +/** PURE_IMPORTS_START _operators_find PURE_IMPORTS_END */ + +function findIndex(predicate, thisArg) { + return function (source) { return source.lift(new _operators_find__WEBPACK_IMPORTED_MODULE_0__["FindValueOperator"](predicate, source, true, thisArg)); }; +} +//# sourceMappingURL=findIndex.js.map + + +/***/ }), +/* 263 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "first", function() { return first; }); +/* harmony import */ var _util_EmptyError__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(253); +/* harmony import */ var _filter__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(251); +/* harmony import */ var _take__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(254); +/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(238); +/* harmony import */ var _throwIfEmpty__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(252); +/* harmony import */ var _util_identity__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(232); +/** PURE_IMPORTS_START _util_EmptyError,_filter,_take,_defaultIfEmpty,_throwIfEmpty,_util_identity PURE_IMPORTS_END */ + + + + + + +function first(predicate, defaultValue) { + var hasDefaultValue = arguments.length >= 2; + return function (source) { return source.pipe(predicate ? Object(_filter__WEBPACK_IMPORTED_MODULE_1__["filter"])(function (v, i) { return predicate(v, i, source); }) : _util_identity__WEBPACK_IMPORTED_MODULE_5__["identity"], Object(_take__WEBPACK_IMPORTED_MODULE_2__["take"])(1), hasDefaultValue ? Object(_defaultIfEmpty__WEBPACK_IMPORTED_MODULE_3__["defaultIfEmpty"])(defaultValue) : Object(_throwIfEmpty__WEBPACK_IMPORTED_MODULE_4__["throwIfEmpty"])(function () { return new _util_EmptyError__WEBPACK_IMPORTED_MODULE_0__["EmptyError"](); })); }; +} +//# sourceMappingURL=first.js.map + + +/***/ }), +/* 264 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "groupBy", function() { return groupBy; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "GroupedObservable", function() { return GroupedObservable; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(172); +/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(177); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(193); +/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(265); +/** PURE_IMPORTS_START tslib,_Subscriber,_Subscription,_Observable,_Subject PURE_IMPORTS_END */ + + + + + +function groupBy(keySelector, elementSelector, durationSelector, subjectSelector) { + return function (source) { + return source.lift(new GroupByOperator(keySelector, elementSelector, durationSelector, subjectSelector)); + }; +} +var GroupByOperator = /*@__PURE__*/ (function () { + function GroupByOperator(keySelector, elementSelector, durationSelector, subjectSelector) { + this.keySelector = keySelector; + this.elementSelector = elementSelector; + this.durationSelector = durationSelector; + this.subjectSelector = subjectSelector; + } + GroupByOperator.prototype.call = function (subscriber, source) { + return source.subscribe(new GroupBySubscriber(subscriber, this.keySelector, this.elementSelector, this.durationSelector, this.subjectSelector)); + }; + return GroupByOperator; +}()); +var GroupBySubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](GroupBySubscriber, _super); + function GroupBySubscriber(destination, keySelector, elementSelector, durationSelector, subjectSelector) { + var _this = _super.call(this, destination) || this; + _this.keySelector = keySelector; + _this.elementSelector = elementSelector; + _this.durationSelector = durationSelector; + _this.subjectSelector = subjectSelector; + _this.groups = null; + _this.attemptedToUnsubscribe = false; + _this.count = 0; + return _this; + } + GroupBySubscriber.prototype._next = function (value) { + var key; + try { + key = this.keySelector(value); + } + catch (err) { + this.error(err); + return; + } + this._group(value, key); + }; + GroupBySubscriber.prototype._group = function (value, key) { + var groups = this.groups; + if (!groups) { + groups = this.groups = new Map(); + } + var group = groups.get(key); + var element; + if (this.elementSelector) { + try { + element = this.elementSelector(value); + } + catch (err) { + this.error(err); + } + } + else { + element = value; + } + if (!group) { + group = (this.subjectSelector ? this.subjectSelector() : new _Subject__WEBPACK_IMPORTED_MODULE_4__["Subject"]()); + groups.set(key, group); + var groupedObservable = new GroupedObservable(key, group, this); + this.destination.next(groupedObservable); + if (this.durationSelector) { + var duration = void 0; + try { + duration = this.durationSelector(new GroupedObservable(key, group)); + } + catch (err) { + this.error(err); + return; + } + this.add(duration.subscribe(new GroupDurationSubscriber(key, group, this))); + } + } + if (!group.closed) { + group.next(element); + } + }; + GroupBySubscriber.prototype._error = function (err) { + var groups = this.groups; + if (groups) { + groups.forEach(function (group, key) { + group.error(err); + }); + groups.clear(); + } + this.destination.error(err); + }; + GroupBySubscriber.prototype._complete = function () { + var groups = this.groups; + if (groups) { + groups.forEach(function (group, key) { + group.complete(); + }); + groups.clear(); + } + this.destination.complete(); + }; + GroupBySubscriber.prototype.removeGroup = function (key) { + this.groups.delete(key); + }; + GroupBySubscriber.prototype.unsubscribe = function () { + if (!this.closed) { + this.attemptedToUnsubscribe = true; + if (this.count === 0) { + _super.prototype.unsubscribe.call(this); + } + } + }; + return GroupBySubscriber; +}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); +var GroupDurationSubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](GroupDurationSubscriber, _super); + function GroupDurationSubscriber(key, group, parent) { + var _this = _super.call(this, group) || this; + _this.key = key; + _this.group = group; + _this.parent = parent; + return _this; + } + GroupDurationSubscriber.prototype._next = function (value) { + this.complete(); + }; + GroupDurationSubscriber.prototype._unsubscribe = function () { + var _a = this, parent = _a.parent, key = _a.key; + this.key = this.parent = null; + if (parent) { + parent.removeGroup(key); + } + }; + return GroupDurationSubscriber; +}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); +var GroupedObservable = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](GroupedObservable, _super); + function GroupedObservable(key, groupSubject, refCountSubscription) { + var _this = _super.call(this) || this; + _this.key = key; + _this.groupSubject = groupSubject; + _this.refCountSubscription = refCountSubscription; + return _this; + } + GroupedObservable.prototype._subscribe = function (subscriber) { + var subscription = new _Subscription__WEBPACK_IMPORTED_MODULE_2__["Subscription"](); + var _a = this, refCountSubscription = _a.refCountSubscription, groupSubject = _a.groupSubject; + if (refCountSubscription && !refCountSubscription.closed) { + subscription.add(new InnerRefCountSubscription(refCountSubscription)); + } + subscription.add(groupSubject.subscribe(subscriber)); + return subscription; + }; + return GroupedObservable; +}(_Observable__WEBPACK_IMPORTED_MODULE_3__["Observable"])); + +var InnerRefCountSubscription = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](InnerRefCountSubscription, _super); + function InnerRefCountSubscription(parent) { + var _this = _super.call(this) || this; + _this.parent = parent; + parent.count++; + return _this; + } + InnerRefCountSubscription.prototype.unsubscribe = function () { + var parent = this.parent; + if (!parent.closed && !this.closed) { + _super.prototype.unsubscribe.call(this); + parent.count -= 1; + if (parent.count === 0 && parent.attemptedToUnsubscribe) { + parent.unsubscribe(); + } + } + }; + return InnerRefCountSubscription; +}(_Subscription__WEBPACK_IMPORTED_MODULE_2__["Subscription"])); +//# sourceMappingURL=groupBy.js.map + + +/***/ }), +/* 265 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "SubjectSubscriber", function() { return SubjectSubscriber; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Subject", function() { return Subject; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "AnonymousSubject", function() { return AnonymousSubject; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(193); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(172); +/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(177); +/* harmony import */ var _util_ObjectUnsubscribedError__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(266); +/* harmony import */ var _SubjectSubscription__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(267); +/* harmony import */ var _internal_symbol_rxSubscriber__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(181); +/** PURE_IMPORTS_START tslib,_Observable,_Subscriber,_Subscription,_util_ObjectUnsubscribedError,_SubjectSubscription,_internal_symbol_rxSubscriber PURE_IMPORTS_END */ + + + + + + + +var SubjectSubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](SubjectSubscriber, _super); + function SubjectSubscriber(destination) { + var _this = _super.call(this, destination) || this; + _this.destination = destination; + return _this; + } + return SubjectSubscriber; +}(_Subscriber__WEBPACK_IMPORTED_MODULE_2__["Subscriber"])); + +var Subject = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](Subject, _super); + function Subject() { + var _this = _super.call(this) || this; + _this.observers = []; + _this.closed = false; + _this.isStopped = false; + _this.hasError = false; + _this.thrownError = null; + return _this; + } + Subject.prototype[_internal_symbol_rxSubscriber__WEBPACK_IMPORTED_MODULE_6__["rxSubscriber"]] = function () { + return new SubjectSubscriber(this); + }; + Subject.prototype.lift = function (operator) { + var subject = new AnonymousSubject(this, this); + subject.operator = operator; + return subject; + }; + Subject.prototype.next = function (value) { + if (this.closed) { + throw new _util_ObjectUnsubscribedError__WEBPACK_IMPORTED_MODULE_4__["ObjectUnsubscribedError"](); + } + if (!this.isStopped) { + var observers = this.observers; + var len = observers.length; + var copy = observers.slice(); + for (var i = 0; i < len; i++) { + copy[i].next(value); + } + } + }; + Subject.prototype.error = function (err) { + if (this.closed) { + throw new _util_ObjectUnsubscribedError__WEBPACK_IMPORTED_MODULE_4__["ObjectUnsubscribedError"](); + } + this.hasError = true; + this.thrownError = err; + this.isStopped = true; + var observers = this.observers; + var len = observers.length; + var copy = observers.slice(); + for (var i = 0; i < len; i++) { + copy[i].error(err); + } + this.observers.length = 0; + }; + Subject.prototype.complete = function () { + if (this.closed) { + throw new _util_ObjectUnsubscribedError__WEBPACK_IMPORTED_MODULE_4__["ObjectUnsubscribedError"](); + } + this.isStopped = true; + var observers = this.observers; + var len = observers.length; + var copy = observers.slice(); + for (var i = 0; i < len; i++) { + copy[i].complete(); + } + this.observers.length = 0; + }; + Subject.prototype.unsubscribe = function () { + this.isStopped = true; + this.closed = true; + this.observers = null; + }; + Subject.prototype._trySubscribe = function (subscriber) { + if (this.closed) { + throw new _util_ObjectUnsubscribedError__WEBPACK_IMPORTED_MODULE_4__["ObjectUnsubscribedError"](); + } + else { + return _super.prototype._trySubscribe.call(this, subscriber); + } + }; + Subject.prototype._subscribe = function (subscriber) { + if (this.closed) { + throw new _util_ObjectUnsubscribedError__WEBPACK_IMPORTED_MODULE_4__["ObjectUnsubscribedError"](); + } + else if (this.hasError) { + subscriber.error(this.thrownError); + return _Subscription__WEBPACK_IMPORTED_MODULE_3__["Subscription"].EMPTY; + } + else if (this.isStopped) { + subscriber.complete(); + return _Subscription__WEBPACK_IMPORTED_MODULE_3__["Subscription"].EMPTY; + } + else { + this.observers.push(subscriber); + return new _SubjectSubscription__WEBPACK_IMPORTED_MODULE_5__["SubjectSubscription"](this, subscriber); + } + }; + Subject.prototype.asObservable = function () { + var observable = new _Observable__WEBPACK_IMPORTED_MODULE_1__["Observable"](); + observable.source = this; + return observable; + }; + Subject.create = function (destination, source) { + return new AnonymousSubject(destination, source); + }; + return Subject; +}(_Observable__WEBPACK_IMPORTED_MODULE_1__["Observable"])); + +var AnonymousSubject = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](AnonymousSubject, _super); + function AnonymousSubject(destination, source) { + var _this = _super.call(this) || this; + _this.destination = destination; + _this.source = source; + return _this; + } + AnonymousSubject.prototype.next = function (value) { + var destination = this.destination; + if (destination && destination.next) { + destination.next(value); + } + }; + AnonymousSubject.prototype.error = function (err) { + var destination = this.destination; + if (destination && destination.error) { + this.destination.error(err); + } + }; + AnonymousSubject.prototype.complete = function () { + var destination = this.destination; + if (destination && destination.complete) { + this.destination.complete(); + } + }; + AnonymousSubject.prototype._subscribe = function (subscriber) { + var source = this.source; + if (source) { + return this.source.subscribe(subscriber); + } + else { + return _Subscription__WEBPACK_IMPORTED_MODULE_3__["Subscription"].EMPTY; + } + }; + return AnonymousSubject; +}(Subject)); + +//# sourceMappingURL=Subject.js.map + + +/***/ }), +/* 266 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ObjectUnsubscribedError", function() { return ObjectUnsubscribedError; }); +/** PURE_IMPORTS_START PURE_IMPORTS_END */ +var ObjectUnsubscribedErrorImpl = /*@__PURE__*/ (function () { + function ObjectUnsubscribedErrorImpl() { + Error.call(this); + this.message = 'object unsubscribed'; + this.name = 'ObjectUnsubscribedError'; + return this; + } + ObjectUnsubscribedErrorImpl.prototype = /*@__PURE__*/ Object.create(Error.prototype); + return ObjectUnsubscribedErrorImpl; +})(); +var ObjectUnsubscribedError = ObjectUnsubscribedErrorImpl; +//# sourceMappingURL=ObjectUnsubscribedError.js.map + + +/***/ }), +/* 267 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "SubjectSubscription", function() { return SubjectSubscription; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(177); +/** PURE_IMPORTS_START tslib,_Subscription PURE_IMPORTS_END */ + + +var SubjectSubscription = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](SubjectSubscription, _super); + function SubjectSubscription(subject, subscriber) { + var _this = _super.call(this) || this; + _this.subject = subject; + _this.subscriber = subscriber; + _this.closed = false; + return _this; + } + SubjectSubscription.prototype.unsubscribe = function () { + if (this.closed) { + return; + } + this.closed = true; + var subject = this.subject; + var observers = subject.observers; + this.subject = null; + if (!observers || observers.length === 0 || subject.isStopped || subject.closed) { + return; + } + var subscriberIndex = observers.indexOf(this.subscriber); + if (subscriberIndex !== -1) { + observers.splice(subscriberIndex, 1); + } + }; + return SubjectSubscription; +}(_Subscription__WEBPACK_IMPORTED_MODULE_1__["Subscription"])); + +//# sourceMappingURL=SubjectSubscription.js.map + + +/***/ }), +/* 268 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ignoreElements", function() { return ignoreElements; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(172); +/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ + + +function ignoreElements() { + return function ignoreElementsOperatorFunction(source) { + return source.lift(new IgnoreElementsOperator()); + }; +} +var IgnoreElementsOperator = /*@__PURE__*/ (function () { + function IgnoreElementsOperator() { + } + IgnoreElementsOperator.prototype.call = function (subscriber, source) { + return source.subscribe(new IgnoreElementsSubscriber(subscriber)); + }; + return IgnoreElementsOperator; +}()); +var IgnoreElementsSubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](IgnoreElementsSubscriber, _super); + function IgnoreElementsSubscriber() { + return _super !== null && _super.apply(this, arguments) || this; + } + IgnoreElementsSubscriber.prototype._next = function (unused) { + }; + return IgnoreElementsSubscriber; +}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); +//# sourceMappingURL=ignoreElements.js.map + + +/***/ }), +/* 269 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "isEmpty", function() { return isEmpty; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(172); +/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ + + +function isEmpty() { + return function (source) { return source.lift(new IsEmptyOperator()); }; +} +var IsEmptyOperator = /*@__PURE__*/ (function () { + function IsEmptyOperator() { + } + IsEmptyOperator.prototype.call = function (observer, source) { + return source.subscribe(new IsEmptySubscriber(observer)); + }; + return IsEmptyOperator; +}()); +var IsEmptySubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](IsEmptySubscriber, _super); + function IsEmptySubscriber(destination) { + return _super.call(this, destination) || this; + } + IsEmptySubscriber.prototype.notifyComplete = function (isEmpty) { + var destination = this.destination; + destination.next(isEmpty); + destination.complete(); + }; + IsEmptySubscriber.prototype._next = function (value) { + this.notifyComplete(false); + }; + IsEmptySubscriber.prototype._complete = function () { + this.notifyComplete(true); + }; + return IsEmptySubscriber; +}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); +//# sourceMappingURL=isEmpty.js.map + + +/***/ }), +/* 270 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "last", function() { return last; }); +/* harmony import */ var _util_EmptyError__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(253); +/* harmony import */ var _filter__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(251); +/* harmony import */ var _takeLast__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(271); +/* harmony import */ var _throwIfEmpty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(252); +/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(238); +/* harmony import */ var _util_identity__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(232); +/** PURE_IMPORTS_START _util_EmptyError,_filter,_takeLast,_throwIfEmpty,_defaultIfEmpty,_util_identity PURE_IMPORTS_END */ + + + + + + +function last(predicate, defaultValue) { + var hasDefaultValue = arguments.length >= 2; + return function (source) { return source.pipe(predicate ? Object(_filter__WEBPACK_IMPORTED_MODULE_1__["filter"])(function (v, i) { return predicate(v, i, source); }) : _util_identity__WEBPACK_IMPORTED_MODULE_5__["identity"], Object(_takeLast__WEBPACK_IMPORTED_MODULE_2__["takeLast"])(1), hasDefaultValue ? Object(_defaultIfEmpty__WEBPACK_IMPORTED_MODULE_4__["defaultIfEmpty"])(defaultValue) : Object(_throwIfEmpty__WEBPACK_IMPORTED_MODULE_3__["throwIfEmpty"])(function () { return new _util_EmptyError__WEBPACK_IMPORTED_MODULE_0__["EmptyError"](); })); }; +} +//# sourceMappingURL=last.js.map + + +/***/ }), +/* 271 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "takeLast", function() { return takeLast; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(172); +/* harmony import */ var _util_ArgumentOutOfRangeError__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(250); +/* harmony import */ var _observable_empty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(242); +/** PURE_IMPORTS_START tslib,_Subscriber,_util_ArgumentOutOfRangeError,_observable_empty PURE_IMPORTS_END */ + + + + +function takeLast(count) { + return function takeLastOperatorFunction(source) { + if (count === 0) { + return Object(_observable_empty__WEBPACK_IMPORTED_MODULE_3__["empty"])(); + } + else { + return source.lift(new TakeLastOperator(count)); + } + }; +} +var TakeLastOperator = /*@__PURE__*/ (function () { + function TakeLastOperator(total) { + this.total = total; + if (this.total < 0) { + throw new _util_ArgumentOutOfRangeError__WEBPACK_IMPORTED_MODULE_2__["ArgumentOutOfRangeError"]; + } + } + TakeLastOperator.prototype.call = function (subscriber, source) { + return source.subscribe(new TakeLastSubscriber(subscriber, this.total)); + }; + return TakeLastOperator; +}()); +var TakeLastSubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](TakeLastSubscriber, _super); + function TakeLastSubscriber(destination, total) { + var _this = _super.call(this, destination) || this; + _this.total = total; + _this.ring = new Array(); + _this.count = 0; + return _this; + } + TakeLastSubscriber.prototype._next = function (value) { + var ring = this.ring; + var total = this.total; + var count = this.count++; + if (ring.length < total) { + ring.push(value); + } + else { + var index = count % total; + ring[index] = value; + } + }; + TakeLastSubscriber.prototype._complete = function () { + var destination = this.destination; + var count = this.count; + if (count > 0) { + var total = this.count >= this.total ? this.total : this.count; + var ring = this.ring; + for (var i = 0; i < total; i++) { + var idx = (count++) % total; + destination.next(ring[idx]); + } + } + destination.complete(); + }; + return TakeLastSubscriber; +}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); +//# sourceMappingURL=takeLast.js.map + + +/***/ }), +/* 272 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "mapTo", function() { return mapTo; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(172); +/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ + + +function mapTo(value) { + return function (source) { return source.lift(new MapToOperator(value)); }; +} +var MapToOperator = /*@__PURE__*/ (function () { + function MapToOperator(value) { + this.value = value; + } + MapToOperator.prototype.call = function (subscriber, source) { + return source.subscribe(new MapToSubscriber(subscriber, this.value)); + }; + return MapToOperator; +}()); +var MapToSubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](MapToSubscriber, _super); + function MapToSubscriber(destination, value) { + var _this = _super.call(this, destination) || this; + _this.value = value; + return _this; + } + MapToSubscriber.prototype._next = function (x) { + this.destination.next(this.value); + }; + return MapToSubscriber; +}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); +//# sourceMappingURL=mapTo.js.map + + +/***/ }), +/* 273 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "materialize", function() { return materialize; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(172); +/* harmony import */ var _Notification__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(241); +/** PURE_IMPORTS_START tslib,_Subscriber,_Notification PURE_IMPORTS_END */ + + + +function materialize() { + return function materializeOperatorFunction(source) { + return source.lift(new MaterializeOperator()); + }; +} +var MaterializeOperator = /*@__PURE__*/ (function () { + function MaterializeOperator() { + } + MaterializeOperator.prototype.call = function (subscriber, source) { + return source.subscribe(new MaterializeSubscriber(subscriber)); + }; + return MaterializeOperator; +}()); +var MaterializeSubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](MaterializeSubscriber, _super); + function MaterializeSubscriber(destination) { + return _super.call(this, destination) || this; + } + MaterializeSubscriber.prototype._next = function (value) { + this.destination.next(_Notification__WEBPACK_IMPORTED_MODULE_2__["Notification"].createNext(value)); + }; + MaterializeSubscriber.prototype._error = function (err) { + var destination = this.destination; + destination.next(_Notification__WEBPACK_IMPORTED_MODULE_2__["Notification"].createError(err)); + destination.complete(); + }; + MaterializeSubscriber.prototype._complete = function () { + var destination = this.destination; + destination.next(_Notification__WEBPACK_IMPORTED_MODULE_2__["Notification"].createComplete()); + destination.complete(); + }; + return MaterializeSubscriber; +}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); +//# sourceMappingURL=materialize.js.map + + +/***/ }), +/* 274 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "max", function() { return max; }); +/* harmony import */ var _reduce__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(275); +/** PURE_IMPORTS_START _reduce PURE_IMPORTS_END */ + +function max(comparer) { + var max = (typeof comparer === 'function') + ? function (x, y) { return comparer(x, y) > 0 ? x : y; } + : function (x, y) { return x > y ? x : y; }; + return Object(_reduce__WEBPACK_IMPORTED_MODULE_0__["reduce"])(max); +} +//# sourceMappingURL=max.js.map + + +/***/ }), +/* 275 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "reduce", function() { return reduce; }); +/* harmony import */ var _scan__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); +/* harmony import */ var _takeLast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(271); +/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(238); +/* harmony import */ var _util_pipe__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(196); +/** PURE_IMPORTS_START _scan,_takeLast,_defaultIfEmpty,_util_pipe PURE_IMPORTS_END */ + + + + +function reduce(accumulator, seed) { + if (arguments.length >= 2) { + return function reduceOperatorFunctionWithSeed(source) { + return Object(_util_pipe__WEBPACK_IMPORTED_MODULE_3__["pipe"])(Object(_scan__WEBPACK_IMPORTED_MODULE_0__["scan"])(accumulator, seed), Object(_takeLast__WEBPACK_IMPORTED_MODULE_1__["takeLast"])(1), Object(_defaultIfEmpty__WEBPACK_IMPORTED_MODULE_2__["defaultIfEmpty"])(seed))(source); + }; + } + return function reduceOperatorFunction(source) { + return Object(_util_pipe__WEBPACK_IMPORTED_MODULE_3__["pipe"])(Object(_scan__WEBPACK_IMPORTED_MODULE_0__["scan"])(function (acc, value, index) { return accumulator(acc, value, index + 1); }), Object(_takeLast__WEBPACK_IMPORTED_MODULE_1__["takeLast"])(1))(source); + }; +} +//# sourceMappingURL=reduce.js.map + + +/***/ }), +/* 276 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "scan", function() { return scan; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(172); +/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ + + +function scan(accumulator, seed) { + var hasSeed = false; + if (arguments.length >= 2) { + hasSeed = true; + } + return function scanOperatorFunction(source) { + return source.lift(new ScanOperator(accumulator, seed, hasSeed)); + }; +} +var ScanOperator = /*@__PURE__*/ (function () { + function ScanOperator(accumulator, seed, hasSeed) { + if (hasSeed === void 0) { + hasSeed = false; + } + this.accumulator = accumulator; + this.seed = seed; + this.hasSeed = hasSeed; + } + ScanOperator.prototype.call = function (subscriber, source) { + return source.subscribe(new ScanSubscriber(subscriber, this.accumulator, this.seed, this.hasSeed)); + }; + return ScanOperator; +}()); +var ScanSubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](ScanSubscriber, _super); + function ScanSubscriber(destination, accumulator, _seed, hasSeed) { + var _this = _super.call(this, destination) || this; + _this.accumulator = accumulator; + _this._seed = _seed; + _this.hasSeed = hasSeed; + _this.index = 0; + return _this; + } + Object.defineProperty(ScanSubscriber.prototype, "seed", { + get: function () { + return this._seed; + }, + set: function (value) { + this.hasSeed = true; + this._seed = value; + }, + enumerable: true, + configurable: true + }); + ScanSubscriber.prototype._next = function (value) { + if (!this.hasSeed) { + this.seed = value; + this.destination.next(value); + } + else { + return this._tryNext(value); + } + }; + ScanSubscriber.prototype._tryNext = function (value) { + var index = this.index++; + var result; + try { + result = this.accumulator(this.seed, value, index); + } + catch (err) { + this.destination.error(err); + } + this.seed = result; + this.destination.next(result); + }; + return ScanSubscriber; +}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); +//# sourceMappingURL=scan.js.map + + +/***/ }), +/* 277 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "merge", function() { return merge; }); +/* harmony import */ var _observable_merge__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(278); +/** PURE_IMPORTS_START _observable_merge PURE_IMPORTS_END */ + +function merge() { + var observables = []; + for (var _i = 0; _i < arguments.length; _i++) { + observables[_i] = arguments[_i]; + } + return function (source) { return source.lift.call(_observable_merge__WEBPACK_IMPORTED_MODULE_0__["merge"].apply(void 0, [source].concat(observables))); }; +} +//# sourceMappingURL=merge.js.map + + +/***/ }), +/* 278 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "merge", function() { return merge; }); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(193); +/* harmony import */ var _util_isScheduler__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(206); +/* harmony import */ var _operators_mergeAll__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(229); +/* harmony import */ var _fromArray__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(215); +/** PURE_IMPORTS_START _Observable,_util_isScheduler,_operators_mergeAll,_fromArray PURE_IMPORTS_END */ + + + + +function merge() { + var observables = []; + for (var _i = 0; _i < arguments.length; _i++) { + observables[_i] = arguments[_i]; + } + var concurrent = Number.POSITIVE_INFINITY; + var scheduler = null; + var last = observables[observables.length - 1]; + if (Object(_util_isScheduler__WEBPACK_IMPORTED_MODULE_1__["isScheduler"])(last)) { + scheduler = observables.pop(); + if (observables.length > 1 && typeof observables[observables.length - 1] === 'number') { + concurrent = observables.pop(); + } + } + else if (typeof last === 'number') { + concurrent = observables.pop(); + } + if (scheduler === null && observables.length === 1 && observables[0] instanceof _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"]) { + return observables[0]; + } + return Object(_operators_mergeAll__WEBPACK_IMPORTED_MODULE_2__["mergeAll"])(concurrent)(Object(_fromArray__WEBPACK_IMPORTED_MODULE_3__["fromArray"])(observables, scheduler)); +} +//# sourceMappingURL=merge.js.map + + +/***/ }), +/* 279 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "mergeMapTo", function() { return mergeMapTo; }); +/* harmony import */ var _mergeMap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(230); +/** PURE_IMPORTS_START _mergeMap PURE_IMPORTS_END */ + +function mergeMapTo(innerObservable, resultSelector, concurrent) { + if (concurrent === void 0) { + concurrent = Number.POSITIVE_INFINITY; + } + if (typeof resultSelector === 'function') { + return Object(_mergeMap__WEBPACK_IMPORTED_MODULE_0__["mergeMap"])(function () { return innerObservable; }, resultSelector, concurrent); + } + if (typeof resultSelector === 'number') { + concurrent = resultSelector; + } + return Object(_mergeMap__WEBPACK_IMPORTED_MODULE_0__["mergeMap"])(function () { return innerObservable; }, concurrent); +} +//# sourceMappingURL=mergeMapTo.js.map + + +/***/ }), +/* 280 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "mergeScan", function() { return mergeScan; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "MergeScanOperator", function() { return MergeScanOperator; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "MergeScanSubscriber", function() { return MergeScanSubscriber; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(182); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(171); +/* harmony import */ var _InnerSubscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(183); +/** PURE_IMPORTS_START tslib,_util_subscribeToResult,_OuterSubscriber,_InnerSubscriber PURE_IMPORTS_END */ + + + + +function mergeScan(accumulator, seed, concurrent) { + if (concurrent === void 0) { + concurrent = Number.POSITIVE_INFINITY; + } + return function (source) { return source.lift(new MergeScanOperator(accumulator, seed, concurrent)); }; +} +var MergeScanOperator = /*@__PURE__*/ (function () { + function MergeScanOperator(accumulator, seed, concurrent) { + this.accumulator = accumulator; + this.seed = seed; + this.concurrent = concurrent; + } + MergeScanOperator.prototype.call = function (subscriber, source) { + return source.subscribe(new MergeScanSubscriber(subscriber, this.accumulator, this.seed, this.concurrent)); + }; + return MergeScanOperator; +}()); + +var MergeScanSubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](MergeScanSubscriber, _super); + function MergeScanSubscriber(destination, accumulator, acc, concurrent) { + var _this = _super.call(this, destination) || this; + _this.accumulator = accumulator; + _this.acc = acc; + _this.concurrent = concurrent; + _this.hasValue = false; + _this.hasCompleted = false; + _this.buffer = []; + _this.active = 0; + _this.index = 0; + return _this; + } + MergeScanSubscriber.prototype._next = function (value) { + if (this.active < this.concurrent) { + var index = this.index++; + var destination = this.destination; + var ish = void 0; + try { + var accumulator = this.accumulator; + ish = accumulator(this.acc, value, index); + } + catch (e) { + return destination.error(e); + } + this.active++; + this._innerSub(ish, value, index); + } + else { + this.buffer.push(value); + } + }; + MergeScanSubscriber.prototype._innerSub = function (ish, value, index) { + var innerSubscriber = new _InnerSubscriber__WEBPACK_IMPORTED_MODULE_3__["InnerSubscriber"](this, undefined, undefined); + var destination = this.destination; + destination.add(innerSubscriber); + Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_1__["subscribeToResult"])(this, ish, value, index, innerSubscriber); + }; + MergeScanSubscriber.prototype._complete = function () { + this.hasCompleted = true; + if (this.active === 0 && this.buffer.length === 0) { + if (this.hasValue === false) { + this.destination.next(this.acc); + } + this.destination.complete(); + } + this.unsubscribe(); + }; + MergeScanSubscriber.prototype.notifyNext = function (outerValue, innerValue, outerIndex, innerIndex, innerSub) { + var destination = this.destination; + this.acc = innerValue; + this.hasValue = true; + destination.next(innerValue); + }; + MergeScanSubscriber.prototype.notifyComplete = function (innerSub) { + var buffer = this.buffer; + var destination = this.destination; + destination.remove(innerSub); + this.active--; + if (buffer.length > 0) { + this._next(buffer.shift()); + } + else if (this.active === 0 && this.hasCompleted) { + if (this.hasValue === false) { + this.destination.next(this.acc); + } + this.destination.complete(); + } + }; + return MergeScanSubscriber; +}(_OuterSubscriber__WEBPACK_IMPORTED_MODULE_2__["OuterSubscriber"])); + +//# sourceMappingURL=mergeScan.js.map + + +/***/ }), +/* 281 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "min", function() { return min; }); +/* harmony import */ var _reduce__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(275); +/** PURE_IMPORTS_START _reduce PURE_IMPORTS_END */ + +function min(comparer) { + var min = (typeof comparer === 'function') + ? function (x, y) { return comparer(x, y) < 0 ? x : y; } + : function (x, y) { return x < y ? x : y; }; + return Object(_reduce__WEBPACK_IMPORTED_MODULE_0__["reduce"])(min); +} +//# sourceMappingURL=min.js.map + + +/***/ }), +/* 282 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "multicast", function() { return multicast; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "MulticastOperator", function() { return MulticastOperator; }); +/* harmony import */ var _observable_ConnectableObservable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(283); +/** PURE_IMPORTS_START _observable_ConnectableObservable PURE_IMPORTS_END */ + +function multicast(subjectOrSubjectFactory, selector) { + return function multicastOperatorFunction(source) { + var subjectFactory; + if (typeof subjectOrSubjectFactory === 'function') { + subjectFactory = subjectOrSubjectFactory; + } + else { + subjectFactory = function subjectFactory() { + return subjectOrSubjectFactory; + }; + } + if (typeof selector === 'function') { + return source.lift(new MulticastOperator(subjectFactory, selector)); + } + var connectable = Object.create(source, _observable_ConnectableObservable__WEBPACK_IMPORTED_MODULE_0__["connectableObservableDescriptor"]); + connectable.source = source; + connectable.subjectFactory = subjectFactory; + return connectable; + }; +} +var MulticastOperator = /*@__PURE__*/ (function () { + function MulticastOperator(subjectFactory, selector) { + this.subjectFactory = subjectFactory; + this.selector = selector; + } + MulticastOperator.prototype.call = function (subscriber, source) { + var selector = this.selector; + var subject = this.subjectFactory(); + var subscription = selector(subject).subscribe(subscriber); + subscription.add(source.subscribe(subject)); + return subscription; + }; + return MulticastOperator; +}()); + +//# sourceMappingURL=multicast.js.map + + +/***/ }), +/* 283 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ConnectableObservable", function() { return ConnectableObservable; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "connectableObservableDescriptor", function() { return connectableObservableDescriptor; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(265); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(193); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(172); +/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(177); +/* harmony import */ var _operators_refCount__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(284); +/** PURE_IMPORTS_START tslib,_Subject,_Observable,_Subscriber,_Subscription,_operators_refCount PURE_IMPORTS_END */ + + + + + + +var ConnectableObservable = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](ConnectableObservable, _super); + function ConnectableObservable(source, subjectFactory) { + var _this = _super.call(this) || this; + _this.source = source; + _this.subjectFactory = subjectFactory; + _this._refCount = 0; + _this._isComplete = false; + return _this; + } + ConnectableObservable.prototype._subscribe = function (subscriber) { + return this.getSubject().subscribe(subscriber); + }; + ConnectableObservable.prototype.getSubject = function () { + var subject = this._subject; + if (!subject || subject.isStopped) { + this._subject = this.subjectFactory(); + } + return this._subject; + }; + ConnectableObservable.prototype.connect = function () { + var connection = this._connection; + if (!connection) { + this._isComplete = false; + connection = this._connection = new _Subscription__WEBPACK_IMPORTED_MODULE_4__["Subscription"](); + connection.add(this.source + .subscribe(new ConnectableSubscriber(this.getSubject(), this))); + if (connection.closed) { + this._connection = null; + connection = _Subscription__WEBPACK_IMPORTED_MODULE_4__["Subscription"].EMPTY; + } + } + return connection; + }; + ConnectableObservable.prototype.refCount = function () { + return Object(_operators_refCount__WEBPACK_IMPORTED_MODULE_5__["refCount"])()(this); + }; + return ConnectableObservable; +}(_Observable__WEBPACK_IMPORTED_MODULE_2__["Observable"])); + +var connectableObservableDescriptor = /*@__PURE__*/ (function () { + var connectableProto = ConnectableObservable.prototype; + return { + operator: { value: null }, + _refCount: { value: 0, writable: true }, + _subject: { value: null, writable: true }, + _connection: { value: null, writable: true }, + _subscribe: { value: connectableProto._subscribe }, + _isComplete: { value: connectableProto._isComplete, writable: true }, + getSubject: { value: connectableProto.getSubject }, + connect: { value: connectableProto.connect }, + refCount: { value: connectableProto.refCount } + }; +})(); +var ConnectableSubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](ConnectableSubscriber, _super); + function ConnectableSubscriber(destination, connectable) { + var _this = _super.call(this, destination) || this; + _this.connectable = connectable; + return _this; + } + ConnectableSubscriber.prototype._error = function (err) { + this._unsubscribe(); + _super.prototype._error.call(this, err); + }; + ConnectableSubscriber.prototype._complete = function () { + this.connectable._isComplete = true; + this._unsubscribe(); + _super.prototype._complete.call(this); + }; + ConnectableSubscriber.prototype._unsubscribe = function () { + var connectable = this.connectable; + if (connectable) { + this.connectable = null; + var connection = connectable._connection; + connectable._refCount = 0; + connectable._subject = null; + connectable._connection = null; + if (connection) { + connection.unsubscribe(); + } + } + }; + return ConnectableSubscriber; +}(_Subject__WEBPACK_IMPORTED_MODULE_1__["SubjectSubscriber"])); +var RefCountOperator = /*@__PURE__*/ (function () { + function RefCountOperator(connectable) { + this.connectable = connectable; + } + RefCountOperator.prototype.call = function (subscriber, source) { + var connectable = this.connectable; + connectable._refCount++; + var refCounter = new RefCountSubscriber(subscriber, connectable); + var subscription = source.subscribe(refCounter); + if (!refCounter.closed) { + refCounter.connection = connectable.connect(); + } + return subscription; + }; + return RefCountOperator; +}()); +var RefCountSubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](RefCountSubscriber, _super); + function RefCountSubscriber(destination, connectable) { + var _this = _super.call(this, destination) || this; + _this.connectable = connectable; + return _this; + } + RefCountSubscriber.prototype._unsubscribe = function () { + var connectable = this.connectable; + if (!connectable) { + this.connection = null; + return; + } + this.connectable = null; + var refCount = connectable._refCount; + if (refCount <= 0) { + this.connection = null; + return; + } + connectable._refCount = refCount - 1; + if (refCount > 1) { + this.connection = null; + return; + } + var connection = this.connection; + var sharedConnection = connectable._connection; + this.connection = null; + if (sharedConnection && (!connection || sharedConnection === connection)) { + sharedConnection.unsubscribe(); + } + }; + return RefCountSubscriber; +}(_Subscriber__WEBPACK_IMPORTED_MODULE_3__["Subscriber"])); +//# sourceMappingURL=ConnectableObservable.js.map + + +/***/ }), +/* 284 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "refCount", function() { return refCount; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(172); +/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ + + +function refCount() { + return function refCountOperatorFunction(source) { + return source.lift(new RefCountOperator(source)); + }; +} +var RefCountOperator = /*@__PURE__*/ (function () { + function RefCountOperator(connectable) { + this.connectable = connectable; + } + RefCountOperator.prototype.call = function (subscriber, source) { + var connectable = this.connectable; + connectable._refCount++; + var refCounter = new RefCountSubscriber(subscriber, connectable); + var subscription = source.subscribe(refCounter); + if (!refCounter.closed) { + refCounter.connection = connectable.connect(); + } + return subscription; + }; + return RefCountOperator; +}()); +var RefCountSubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](RefCountSubscriber, _super); + function RefCountSubscriber(destination, connectable) { + var _this = _super.call(this, destination) || this; + _this.connectable = connectable; + return _this; + } + RefCountSubscriber.prototype._unsubscribe = function () { + var connectable = this.connectable; + if (!connectable) { + this.connection = null; + return; + } + this.connectable = null; + var refCount = connectable._refCount; + if (refCount <= 0) { + this.connection = null; + return; + } + connectable._refCount = refCount - 1; + if (refCount > 1) { + this.connection = null; + return; + } + var connection = this.connection; + var sharedConnection = connectable._connection; + this.connection = null; + if (sharedConnection && (!connection || sharedConnection === connection)) { + sharedConnection.unsubscribe(); + } + }; + return RefCountSubscriber; +}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); +//# sourceMappingURL=refCount.js.map + + +/***/ }), +/* 285 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "observeOn", function() { return observeOn; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ObserveOnOperator", function() { return ObserveOnOperator; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ObserveOnSubscriber", function() { return ObserveOnSubscriber; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ObserveOnMessage", function() { return ObserveOnMessage; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(172); +/* harmony import */ var _Notification__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(241); +/** PURE_IMPORTS_START tslib,_Subscriber,_Notification PURE_IMPORTS_END */ + + + +function observeOn(scheduler, delay) { + if (delay === void 0) { + delay = 0; + } + return function observeOnOperatorFunction(source) { + return source.lift(new ObserveOnOperator(scheduler, delay)); + }; +} +var ObserveOnOperator = /*@__PURE__*/ (function () { + function ObserveOnOperator(scheduler, delay) { + if (delay === void 0) { + delay = 0; + } + this.scheduler = scheduler; + this.delay = delay; + } + ObserveOnOperator.prototype.call = function (subscriber, source) { + return source.subscribe(new ObserveOnSubscriber(subscriber, this.scheduler, this.delay)); + }; + return ObserveOnOperator; +}()); + +var ObserveOnSubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](ObserveOnSubscriber, _super); + function ObserveOnSubscriber(destination, scheduler, delay) { + if (delay === void 0) { + delay = 0; + } + var _this = _super.call(this, destination) || this; + _this.scheduler = scheduler; + _this.delay = delay; + return _this; + } + ObserveOnSubscriber.dispatch = function (arg) { + var notification = arg.notification, destination = arg.destination; + notification.observe(destination); + this.unsubscribe(); + }; + ObserveOnSubscriber.prototype.scheduleMessage = function (notification) { + var destination = this.destination; + destination.add(this.scheduler.schedule(ObserveOnSubscriber.dispatch, this.delay, new ObserveOnMessage(notification, this.destination))); + }; + ObserveOnSubscriber.prototype._next = function (value) { + this.scheduleMessage(_Notification__WEBPACK_IMPORTED_MODULE_2__["Notification"].createNext(value)); + }; + ObserveOnSubscriber.prototype._error = function (err) { + this.scheduleMessage(_Notification__WEBPACK_IMPORTED_MODULE_2__["Notification"].createError(err)); + this.unsubscribe(); + }; + ObserveOnSubscriber.prototype._complete = function () { + this.scheduleMessage(_Notification__WEBPACK_IMPORTED_MODULE_2__["Notification"].createComplete()); + this.unsubscribe(); + }; + return ObserveOnSubscriber; +}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); + +var ObserveOnMessage = /*@__PURE__*/ (function () { + function ObserveOnMessage(notification, destination) { + this.notification = notification; + this.destination = destination; + } + return ObserveOnMessage; +}()); + +//# sourceMappingURL=observeOn.js.map + + +/***/ }), +/* 286 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "onErrorResumeNext", function() { return onErrorResumeNext; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "onErrorResumeNextStatic", function() { return onErrorResumeNextStatic; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _observable_from__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(218); +/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(178); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(171); +/* harmony import */ var _InnerSubscriber__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(183); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(182); +/** PURE_IMPORTS_START tslib,_observable_from,_util_isArray,_OuterSubscriber,_InnerSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ + + + + + + +function onErrorResumeNext() { + var nextSources = []; + for (var _i = 0; _i < arguments.length; _i++) { + nextSources[_i] = arguments[_i]; + } + if (nextSources.length === 1 && Object(_util_isArray__WEBPACK_IMPORTED_MODULE_2__["isArray"])(nextSources[0])) { + nextSources = nextSources[0]; + } + return function (source) { return source.lift(new OnErrorResumeNextOperator(nextSources)); }; +} +function onErrorResumeNextStatic() { + var nextSources = []; + for (var _i = 0; _i < arguments.length; _i++) { + nextSources[_i] = arguments[_i]; + } + var source = null; + if (nextSources.length === 1 && Object(_util_isArray__WEBPACK_IMPORTED_MODULE_2__["isArray"])(nextSources[0])) { + nextSources = nextSources[0]; + } + source = nextSources.shift(); + return Object(_observable_from__WEBPACK_IMPORTED_MODULE_1__["from"])(source, null).lift(new OnErrorResumeNextOperator(nextSources)); +} +var OnErrorResumeNextOperator = /*@__PURE__*/ (function () { + function OnErrorResumeNextOperator(nextSources) { + this.nextSources = nextSources; + } + OnErrorResumeNextOperator.prototype.call = function (subscriber, source) { + return source.subscribe(new OnErrorResumeNextSubscriber(subscriber, this.nextSources)); + }; + return OnErrorResumeNextOperator; +}()); +var OnErrorResumeNextSubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](OnErrorResumeNextSubscriber, _super); + function OnErrorResumeNextSubscriber(destination, nextSources) { + var _this = _super.call(this, destination) || this; + _this.destination = destination; + _this.nextSources = nextSources; + return _this; + } + OnErrorResumeNextSubscriber.prototype.notifyError = function (error, innerSub) { + this.subscribeToNextSource(); + }; + OnErrorResumeNextSubscriber.prototype.notifyComplete = function (innerSub) { + this.subscribeToNextSource(); + }; + OnErrorResumeNextSubscriber.prototype._error = function (err) { + this.subscribeToNextSource(); + this.unsubscribe(); + }; + OnErrorResumeNextSubscriber.prototype._complete = function () { + this.subscribeToNextSource(); + this.unsubscribe(); + }; + OnErrorResumeNextSubscriber.prototype.subscribeToNextSource = function () { + var next = this.nextSources.shift(); + if (!!next) { + var innerSubscriber = new _InnerSubscriber__WEBPACK_IMPORTED_MODULE_4__["InnerSubscriber"](this, undefined, undefined); + var destination = this.destination; + destination.add(innerSubscriber); + Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_5__["subscribeToResult"])(this, next, undefined, undefined, innerSubscriber); + } + else { + this.destination.complete(); + } + }; + return OnErrorResumeNextSubscriber; +}(_OuterSubscriber__WEBPACK_IMPORTED_MODULE_3__["OuterSubscriber"])); +//# sourceMappingURL=onErrorResumeNext.js.map + + +/***/ }), +/* 287 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "pairwise", function() { return pairwise; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(172); +/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ + + +function pairwise() { + return function (source) { return source.lift(new PairwiseOperator()); }; +} +var PairwiseOperator = /*@__PURE__*/ (function () { + function PairwiseOperator() { + } + PairwiseOperator.prototype.call = function (subscriber, source) { + return source.subscribe(new PairwiseSubscriber(subscriber)); + }; + return PairwiseOperator; +}()); +var PairwiseSubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](PairwiseSubscriber, _super); + function PairwiseSubscriber(destination) { + var _this = _super.call(this, destination) || this; + _this.hasPrev = false; + return _this; + } + PairwiseSubscriber.prototype._next = function (value) { + var pair; + if (this.hasPrev) { + pair = [this.prev, value]; + } + else { + this.hasPrev = true; + } + this.prev = value; + if (pair) { + this.destination.next(pair); + } + }; + return PairwiseSubscriber; +}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); +//# sourceMappingURL=pairwise.js.map + + +/***/ }), +/* 288 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "partition", function() { return partition; }); +/* harmony import */ var _util_not__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(289); +/* harmony import */ var _filter__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(251); +/** PURE_IMPORTS_START _util_not,_filter PURE_IMPORTS_END */ + + +function partition(predicate, thisArg) { + return function (source) { + return [ + Object(_filter__WEBPACK_IMPORTED_MODULE_1__["filter"])(predicate, thisArg)(source), + Object(_filter__WEBPACK_IMPORTED_MODULE_1__["filter"])(Object(_util_not__WEBPACK_IMPORTED_MODULE_0__["not"])(predicate, thisArg))(source) + ]; + }; +} +//# sourceMappingURL=partition.js.map + + +/***/ }), +/* 289 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "not", function() { return not; }); +/** PURE_IMPORTS_START PURE_IMPORTS_END */ +function not(pred, thisArg) { + function notPred() { + return !(notPred.pred.apply(notPred.thisArg, arguments)); + } + notPred.pred = pred; + notPred.thisArg = thisArg; + return notPred; +} +//# sourceMappingURL=not.js.map + + +/***/ }), +/* 290 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "pluck", function() { return pluck; }); +/* harmony import */ var _map__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(231); +/** PURE_IMPORTS_START _map PURE_IMPORTS_END */ + +function pluck() { + var properties = []; + for (var _i = 0; _i < arguments.length; _i++) { + properties[_i] = arguments[_i]; + } + var length = properties.length; + if (length === 0) { + throw new Error('list of properties cannot be empty.'); + } + return function (source) { return Object(_map__WEBPACK_IMPORTED_MODULE_0__["map"])(plucker(properties, length))(source); }; +} +function plucker(props, length) { + var mapper = function (x) { + var currentProp = x; + for (var i = 0; i < length; i++) { + var p = currentProp[props[i]]; + if (typeof p !== 'undefined') { + currentProp = p; + } + else { + return undefined; + } + } + return currentProp; + }; + return mapper; +} +//# sourceMappingURL=pluck.js.map + + +/***/ }), +/* 291 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "publish", function() { return publish; }); +/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(265); +/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(282); +/** PURE_IMPORTS_START _Subject,_multicast PURE_IMPORTS_END */ + + +function publish(selector) { + return selector ? + Object(_multicast__WEBPACK_IMPORTED_MODULE_1__["multicast"])(function () { return new _Subject__WEBPACK_IMPORTED_MODULE_0__["Subject"](); }, selector) : + Object(_multicast__WEBPACK_IMPORTED_MODULE_1__["multicast"])(new _Subject__WEBPACK_IMPORTED_MODULE_0__["Subject"]()); +} +//# sourceMappingURL=publish.js.map + + +/***/ }), +/* 292 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "publishBehavior", function() { return publishBehavior; }); +/* harmony import */ var _BehaviorSubject__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(293); +/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(282); +/** PURE_IMPORTS_START _BehaviorSubject,_multicast PURE_IMPORTS_END */ + + +function publishBehavior(value) { + return function (source) { return Object(_multicast__WEBPACK_IMPORTED_MODULE_1__["multicast"])(new _BehaviorSubject__WEBPACK_IMPORTED_MODULE_0__["BehaviorSubject"](value))(source); }; +} +//# sourceMappingURL=publishBehavior.js.map + + +/***/ }), +/* 293 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "BehaviorSubject", function() { return BehaviorSubject; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(265); +/* harmony import */ var _util_ObjectUnsubscribedError__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(266); +/** PURE_IMPORTS_START tslib,_Subject,_util_ObjectUnsubscribedError PURE_IMPORTS_END */ + + + +var BehaviorSubject = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](BehaviorSubject, _super); + function BehaviorSubject(_value) { + var _this = _super.call(this) || this; + _this._value = _value; + return _this; + } + Object.defineProperty(BehaviorSubject.prototype, "value", { + get: function () { + return this.getValue(); + }, + enumerable: true, + configurable: true + }); + BehaviorSubject.prototype._subscribe = function (subscriber) { + var subscription = _super.prototype._subscribe.call(this, subscriber); + if (subscription && !subscription.closed) { + subscriber.next(this._value); + } + return subscription; + }; + BehaviorSubject.prototype.getValue = function () { + if (this.hasError) { + throw this.thrownError; + } + else if (this.closed) { + throw new _util_ObjectUnsubscribedError__WEBPACK_IMPORTED_MODULE_2__["ObjectUnsubscribedError"](); + } + else { + return this._value; + } + }; + BehaviorSubject.prototype.next = function (value) { + _super.prototype.next.call(this, this._value = value); + }; + return BehaviorSubject; +}(_Subject__WEBPACK_IMPORTED_MODULE_1__["Subject"])); + +//# sourceMappingURL=BehaviorSubject.js.map + + +/***/ }), +/* 294 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "publishLast", function() { return publishLast; }); +/* harmony import */ var _AsyncSubject__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(295); +/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(282); +/** PURE_IMPORTS_START _AsyncSubject,_multicast PURE_IMPORTS_END */ + + +function publishLast() { + return function (source) { return Object(_multicast__WEBPACK_IMPORTED_MODULE_1__["multicast"])(new _AsyncSubject__WEBPACK_IMPORTED_MODULE_0__["AsyncSubject"]())(source); }; +} +//# sourceMappingURL=publishLast.js.map + + +/***/ }), +/* 295 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "AsyncSubject", function() { return AsyncSubject; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(265); +/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(177); +/** PURE_IMPORTS_START tslib,_Subject,_Subscription PURE_IMPORTS_END */ + + + +var AsyncSubject = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](AsyncSubject, _super); + function AsyncSubject() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.value = null; + _this.hasNext = false; + _this.hasCompleted = false; + return _this; + } + AsyncSubject.prototype._subscribe = function (subscriber) { + if (this.hasError) { + subscriber.error(this.thrownError); + return _Subscription__WEBPACK_IMPORTED_MODULE_2__["Subscription"].EMPTY; + } + else if (this.hasCompleted && this.hasNext) { + subscriber.next(this.value); + subscriber.complete(); + return _Subscription__WEBPACK_IMPORTED_MODULE_2__["Subscription"].EMPTY; + } + return _super.prototype._subscribe.call(this, subscriber); + }; + AsyncSubject.prototype.next = function (value) { + if (!this.hasCompleted) { + this.value = value; + this.hasNext = true; + } + }; + AsyncSubject.prototype.error = function (error) { + if (!this.hasCompleted) { + _super.prototype.error.call(this, error); + } + }; + AsyncSubject.prototype.complete = function () { + this.hasCompleted = true; + if (this.hasNext) { + _super.prototype.next.call(this, this.value); + } + _super.prototype.complete.call(this); + }; + return AsyncSubject; +}(_Subject__WEBPACK_IMPORTED_MODULE_1__["Subject"])); + +//# sourceMappingURL=AsyncSubject.js.map + + +/***/ }), +/* 296 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "publishReplay", function() { return publishReplay; }); +/* harmony import */ var _ReplaySubject__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(297); +/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(282); +/** PURE_IMPORTS_START _ReplaySubject,_multicast PURE_IMPORTS_END */ + + +function publishReplay(bufferSize, windowTime, selectorOrScheduler, scheduler) { + if (selectorOrScheduler && typeof selectorOrScheduler !== 'function') { + scheduler = selectorOrScheduler; + } + var selector = typeof selectorOrScheduler === 'function' ? selectorOrScheduler : undefined; + var subject = new _ReplaySubject__WEBPACK_IMPORTED_MODULE_0__["ReplaySubject"](bufferSize, windowTime, scheduler); + return function (source) { return Object(_multicast__WEBPACK_IMPORTED_MODULE_1__["multicast"])(function () { return subject; }, selector)(source); }; +} +//# sourceMappingURL=publishReplay.js.map + + +/***/ }), +/* 297 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ReplaySubject", function() { return ReplaySubject; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(265); +/* harmony import */ var _scheduler_queue__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(298); +/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(177); +/* harmony import */ var _operators_observeOn__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(285); +/* harmony import */ var _util_ObjectUnsubscribedError__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(266); +/* harmony import */ var _SubjectSubscription__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(267); +/** PURE_IMPORTS_START tslib,_Subject,_scheduler_queue,_Subscription,_operators_observeOn,_util_ObjectUnsubscribedError,_SubjectSubscription PURE_IMPORTS_END */ + + + + + + + +var ReplaySubject = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](ReplaySubject, _super); + function ReplaySubject(bufferSize, windowTime, scheduler) { + if (bufferSize === void 0) { + bufferSize = Number.POSITIVE_INFINITY; + } + if (windowTime === void 0) { + windowTime = Number.POSITIVE_INFINITY; + } + var _this = _super.call(this) || this; + _this.scheduler = scheduler; + _this._events = []; + _this._infiniteTimeWindow = false; + _this._bufferSize = bufferSize < 1 ? 1 : bufferSize; + _this._windowTime = windowTime < 1 ? 1 : windowTime; + if (windowTime === Number.POSITIVE_INFINITY) { + _this._infiniteTimeWindow = true; + _this.next = _this.nextInfiniteTimeWindow; + } + else { + _this.next = _this.nextTimeWindow; + } + return _this; + } + ReplaySubject.prototype.nextInfiniteTimeWindow = function (value) { + var _events = this._events; + _events.push(value); + if (_events.length > this._bufferSize) { + _events.shift(); + } + _super.prototype.next.call(this, value); + }; + ReplaySubject.prototype.nextTimeWindow = function (value) { + this._events.push(new ReplayEvent(this._getNow(), value)); + this._trimBufferThenGetEvents(); + _super.prototype.next.call(this, value); + }; + ReplaySubject.prototype._subscribe = function (subscriber) { + var _infiniteTimeWindow = this._infiniteTimeWindow; + var _events = _infiniteTimeWindow ? this._events : this._trimBufferThenGetEvents(); + var scheduler = this.scheduler; + var len = _events.length; + var subscription; + if (this.closed) { + throw new _util_ObjectUnsubscribedError__WEBPACK_IMPORTED_MODULE_5__["ObjectUnsubscribedError"](); + } + else if (this.isStopped || this.hasError) { + subscription = _Subscription__WEBPACK_IMPORTED_MODULE_3__["Subscription"].EMPTY; + } + else { + this.observers.push(subscriber); + subscription = new _SubjectSubscription__WEBPACK_IMPORTED_MODULE_6__["SubjectSubscription"](this, subscriber); + } + if (scheduler) { + subscriber.add(subscriber = new _operators_observeOn__WEBPACK_IMPORTED_MODULE_4__["ObserveOnSubscriber"](subscriber, scheduler)); + } + if (_infiniteTimeWindow) { + for (var i = 0; i < len && !subscriber.closed; i++) { + subscriber.next(_events[i]); + } + } + else { + for (var i = 0; i < len && !subscriber.closed; i++) { + subscriber.next(_events[i].value); + } + } + if (this.hasError) { + subscriber.error(this.thrownError); + } + else if (this.isStopped) { + subscriber.complete(); + } + return subscription; + }; + ReplaySubject.prototype._getNow = function () { + return (this.scheduler || _scheduler_queue__WEBPACK_IMPORTED_MODULE_2__["queue"]).now(); + }; + ReplaySubject.prototype._trimBufferThenGetEvents = function () { + var now = this._getNow(); + var _bufferSize = this._bufferSize; + var _windowTime = this._windowTime; + var _events = this._events; + var eventsCount = _events.length; + var spliceCount = 0; + while (spliceCount < eventsCount) { + if ((now - _events[spliceCount].time) < _windowTime) { + break; + } + spliceCount++; + } + if (eventsCount > _bufferSize) { + spliceCount = Math.max(spliceCount, eventsCount - _bufferSize); + } + if (spliceCount > 0) { + _events.splice(0, spliceCount); + } + return _events; + }; + return ReplaySubject; +}(_Subject__WEBPACK_IMPORTED_MODULE_1__["Subject"])); + +var ReplayEvent = /*@__PURE__*/ (function () { + function ReplayEvent(time, value) { + this.time = time; + this.value = value; + } + return ReplayEvent; +}()); +//# sourceMappingURL=ReplaySubject.js.map + + +/***/ }), +/* 298 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "queue", function() { return queue; }); +/* harmony import */ var _QueueAction__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(299); +/* harmony import */ var _QueueScheduler__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(300); +/** PURE_IMPORTS_START _QueueAction,_QueueScheduler PURE_IMPORTS_END */ + + +var queue = /*@__PURE__*/ new _QueueScheduler__WEBPACK_IMPORTED_MODULE_1__["QueueScheduler"](_QueueAction__WEBPACK_IMPORTED_MODULE_0__["QueueAction"]); +//# sourceMappingURL=queue.js.map + + +/***/ }), +/* 299 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "QueueAction", function() { return QueueAction; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _AsyncAction__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(200); +/** PURE_IMPORTS_START tslib,_AsyncAction PURE_IMPORTS_END */ + + +var QueueAction = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](QueueAction, _super); + function QueueAction(scheduler, work) { + var _this = _super.call(this, scheduler, work) || this; + _this.scheduler = scheduler; + _this.work = work; + return _this; + } + QueueAction.prototype.schedule = function (state, delay) { + if (delay === void 0) { + delay = 0; + } + if (delay > 0) { + return _super.prototype.schedule.call(this, state, delay); + } + this.delay = delay; + this.state = state; + this.scheduler.flush(this); + return this; + }; + QueueAction.prototype.execute = function (state, delay) { + return (delay > 0 || this.closed) ? + _super.prototype.execute.call(this, state, delay) : + this._execute(state, delay); + }; + QueueAction.prototype.requestAsyncId = function (scheduler, id, delay) { + if (delay === void 0) { + delay = 0; + } + if ((delay !== null && delay > 0) || (delay === null && this.delay > 0)) { + return _super.prototype.requestAsyncId.call(this, scheduler, id, delay); + } + return scheduler.flush(this); + }; + return QueueAction; +}(_AsyncAction__WEBPACK_IMPORTED_MODULE_1__["AsyncAction"])); + +//# sourceMappingURL=QueueAction.js.map + + +/***/ }), +/* 300 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "QueueScheduler", function() { return QueueScheduler; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _AsyncScheduler__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(202); +/** PURE_IMPORTS_START tslib,_AsyncScheduler PURE_IMPORTS_END */ + + +var QueueScheduler = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](QueueScheduler, _super); + function QueueScheduler() { + return _super !== null && _super.apply(this, arguments) || this; + } + return QueueScheduler; +}(_AsyncScheduler__WEBPACK_IMPORTED_MODULE_1__["AsyncScheduler"])); + +//# sourceMappingURL=QueueScheduler.js.map + + +/***/ }), +/* 301 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "race", function() { return race; }); +/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(178); +/* harmony import */ var _observable_race__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(302); +/** PURE_IMPORTS_START _util_isArray,_observable_race PURE_IMPORTS_END */ + + +function race() { + var observables = []; + for (var _i = 0; _i < arguments.length; _i++) { + observables[_i] = arguments[_i]; + } + return function raceOperatorFunction(source) { + if (observables.length === 1 && Object(_util_isArray__WEBPACK_IMPORTED_MODULE_0__["isArray"])(observables[0])) { + observables = observables[0]; + } + return source.lift.call(_observable_race__WEBPACK_IMPORTED_MODULE_1__["race"].apply(void 0, [source].concat(observables))); + }; +} +//# sourceMappingURL=race.js.map + + +/***/ }), +/* 302 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "race", function() { return race; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "RaceOperator", function() { return RaceOperator; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "RaceSubscriber", function() { return RaceSubscriber; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(178); +/* harmony import */ var _fromArray__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(215); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(171); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(182); +/** PURE_IMPORTS_START tslib,_util_isArray,_fromArray,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ + + + + + +function race() { + var observables = []; + for (var _i = 0; _i < arguments.length; _i++) { + observables[_i] = arguments[_i]; + } + if (observables.length === 1) { + if (Object(_util_isArray__WEBPACK_IMPORTED_MODULE_1__["isArray"])(observables[0])) { + observables = observables[0]; + } + else { + return observables[0]; + } + } + return Object(_fromArray__WEBPACK_IMPORTED_MODULE_2__["fromArray"])(observables, undefined).lift(new RaceOperator()); +} +var RaceOperator = /*@__PURE__*/ (function () { + function RaceOperator() { + } + RaceOperator.prototype.call = function (subscriber, source) { + return source.subscribe(new RaceSubscriber(subscriber)); + }; + return RaceOperator; +}()); + +var RaceSubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](RaceSubscriber, _super); + function RaceSubscriber(destination) { + var _this = _super.call(this, destination) || this; + _this.hasFirst = false; + _this.observables = []; + _this.subscriptions = []; + return _this; + } + RaceSubscriber.prototype._next = function (observable) { + this.observables.push(observable); + }; + RaceSubscriber.prototype._complete = function () { + var observables = this.observables; + var len = observables.length; + if (len === 0) { + this.destination.complete(); + } + else { + for (var i = 0; i < len && !this.hasFirst; i++) { + var observable = observables[i]; + var subscription = Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_4__["subscribeToResult"])(this, observable, observable, i); + if (this.subscriptions) { + this.subscriptions.push(subscription); + } + this.add(subscription); + } + this.observables = null; + } + }; + RaceSubscriber.prototype.notifyNext = function (outerValue, innerValue, outerIndex, innerIndex, innerSub) { + if (!this.hasFirst) { + this.hasFirst = true; + for (var i = 0; i < this.subscriptions.length; i++) { + if (i !== outerIndex) { + var subscription = this.subscriptions[i]; + subscription.unsubscribe(); + this.remove(subscription); + } + } + this.subscriptions = null; + } + this.destination.next(innerValue); + }; + return RaceSubscriber; +}(_OuterSubscriber__WEBPACK_IMPORTED_MODULE_3__["OuterSubscriber"])); + +//# sourceMappingURL=race.js.map + + +/***/ }), +/* 303 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "repeat", function() { return repeat; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(172); +/* harmony import */ var _observable_empty__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(242); +/** PURE_IMPORTS_START tslib,_Subscriber,_observable_empty PURE_IMPORTS_END */ + + + +function repeat(count) { + if (count === void 0) { + count = -1; + } + return function (source) { + if (count === 0) { + return Object(_observable_empty__WEBPACK_IMPORTED_MODULE_2__["empty"])(); + } + else if (count < 0) { + return source.lift(new RepeatOperator(-1, source)); + } + else { + return source.lift(new RepeatOperator(count - 1, source)); + } + }; +} +var RepeatOperator = /*@__PURE__*/ (function () { + function RepeatOperator(count, source) { + this.count = count; + this.source = source; + } + RepeatOperator.prototype.call = function (subscriber, source) { + return source.subscribe(new RepeatSubscriber(subscriber, this.count, this.source)); + }; + return RepeatOperator; +}()); +var RepeatSubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](RepeatSubscriber, _super); + function RepeatSubscriber(destination, count, source) { + var _this = _super.call(this, destination) || this; + _this.count = count; + _this.source = source; + return _this; + } + RepeatSubscriber.prototype.complete = function () { + if (!this.isStopped) { + var _a = this, source = _a.source, count = _a.count; + if (count === 0) { + return _super.prototype.complete.call(this); + } + else if (count > -1) { + this.count = count - 1; + } + source.subscribe(this._unsubscribeAndRecycle()); + } + }; + return RepeatSubscriber; +}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); +//# sourceMappingURL=repeat.js.map + + +/***/ }), +/* 304 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "repeatWhen", function() { return repeatWhen; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(265); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(171); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(182); +/** PURE_IMPORTS_START tslib,_Subject,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ + + + + +function repeatWhen(notifier) { + return function (source) { return source.lift(new RepeatWhenOperator(notifier)); }; +} +var RepeatWhenOperator = /*@__PURE__*/ (function () { + function RepeatWhenOperator(notifier) { + this.notifier = notifier; + } + RepeatWhenOperator.prototype.call = function (subscriber, source) { + return source.subscribe(new RepeatWhenSubscriber(subscriber, this.notifier, source)); + }; + return RepeatWhenOperator; +}()); +var RepeatWhenSubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](RepeatWhenSubscriber, _super); + function RepeatWhenSubscriber(destination, notifier, source) { + var _this = _super.call(this, destination) || this; + _this.notifier = notifier; + _this.source = source; + _this.sourceIsBeingSubscribedTo = true; + return _this; + } + RepeatWhenSubscriber.prototype.notifyNext = function (outerValue, innerValue, outerIndex, innerIndex, innerSub) { + this.sourceIsBeingSubscribedTo = true; + this.source.subscribe(this); + }; + RepeatWhenSubscriber.prototype.notifyComplete = function (innerSub) { + if (this.sourceIsBeingSubscribedTo === false) { + return _super.prototype.complete.call(this); + } + }; + RepeatWhenSubscriber.prototype.complete = function () { + this.sourceIsBeingSubscribedTo = false; + if (!this.isStopped) { + if (!this.retries) { + this.subscribeToRetries(); + } + if (!this.retriesSubscription || this.retriesSubscription.closed) { + return _super.prototype.complete.call(this); + } + this._unsubscribeAndRecycle(); + this.notifications.next(); + } + }; + RepeatWhenSubscriber.prototype._unsubscribe = function () { + var _a = this, notifications = _a.notifications, retriesSubscription = _a.retriesSubscription; + if (notifications) { + notifications.unsubscribe(); + this.notifications = null; + } + if (retriesSubscription) { + retriesSubscription.unsubscribe(); + this.retriesSubscription = null; + } + this.retries = null; + }; + RepeatWhenSubscriber.prototype._unsubscribeAndRecycle = function () { + var _unsubscribe = this._unsubscribe; + this._unsubscribe = null; + _super.prototype._unsubscribeAndRecycle.call(this); + this._unsubscribe = _unsubscribe; + return this; + }; + RepeatWhenSubscriber.prototype.subscribeToRetries = function () { + this.notifications = new _Subject__WEBPACK_IMPORTED_MODULE_1__["Subject"](); + var retries; + try { + var notifier = this.notifier; + retries = notifier(this.notifications); + } + catch (e) { + return _super.prototype.complete.call(this); + } + this.retries = retries; + this.retriesSubscription = Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_3__["subscribeToResult"])(this, retries); + }; + return RepeatWhenSubscriber; +}(_OuterSubscriber__WEBPACK_IMPORTED_MODULE_2__["OuterSubscriber"])); +//# sourceMappingURL=repeatWhen.js.map + + +/***/ }), +/* 305 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "retry", function() { return retry; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(172); +/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ + + +function retry(count) { + if (count === void 0) { + count = -1; + } + return function (source) { return source.lift(new RetryOperator(count, source)); }; +} +var RetryOperator = /*@__PURE__*/ (function () { + function RetryOperator(count, source) { + this.count = count; + this.source = source; + } + RetryOperator.prototype.call = function (subscriber, source) { + return source.subscribe(new RetrySubscriber(subscriber, this.count, this.source)); + }; + return RetryOperator; +}()); +var RetrySubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](RetrySubscriber, _super); + function RetrySubscriber(destination, count, source) { + var _this = _super.call(this, destination) || this; + _this.count = count; + _this.source = source; + return _this; + } + RetrySubscriber.prototype.error = function (err) { + if (!this.isStopped) { + var _a = this, source = _a.source, count = _a.count; + if (count === 0) { + return _super.prototype.error.call(this, err); + } + else if (count > -1) { + this.count = count - 1; + } + source.subscribe(this._unsubscribeAndRecycle()); + } + }; + return RetrySubscriber; +}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); +//# sourceMappingURL=retry.js.map + + +/***/ }), +/* 306 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "retryWhen", function() { return retryWhen; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(265); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(171); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(182); +/** PURE_IMPORTS_START tslib,_Subject,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ + + + + +function retryWhen(notifier) { + return function (source) { return source.lift(new RetryWhenOperator(notifier, source)); }; +} +var RetryWhenOperator = /*@__PURE__*/ (function () { + function RetryWhenOperator(notifier, source) { + this.notifier = notifier; + this.source = source; + } + RetryWhenOperator.prototype.call = function (subscriber, source) { + return source.subscribe(new RetryWhenSubscriber(subscriber, this.notifier, this.source)); + }; + return RetryWhenOperator; +}()); +var RetryWhenSubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](RetryWhenSubscriber, _super); + function RetryWhenSubscriber(destination, notifier, source) { + var _this = _super.call(this, destination) || this; + _this.notifier = notifier; + _this.source = source; + return _this; + } + RetryWhenSubscriber.prototype.error = function (err) { + if (!this.isStopped) { + var errors = this.errors; + var retries = this.retries; + var retriesSubscription = this.retriesSubscription; + if (!retries) { + errors = new _Subject__WEBPACK_IMPORTED_MODULE_1__["Subject"](); + try { + var notifier = this.notifier; + retries = notifier(errors); + } + catch (e) { + return _super.prototype.error.call(this, e); + } + retriesSubscription = Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_3__["subscribeToResult"])(this, retries); + } + else { + this.errors = null; + this.retriesSubscription = null; + } + this._unsubscribeAndRecycle(); + this.errors = errors; + this.retries = retries; + this.retriesSubscription = retriesSubscription; + errors.next(err); + } + }; + RetryWhenSubscriber.prototype._unsubscribe = function () { + var _a = this, errors = _a.errors, retriesSubscription = _a.retriesSubscription; + if (errors) { + errors.unsubscribe(); + this.errors = null; + } + if (retriesSubscription) { + retriesSubscription.unsubscribe(); + this.retriesSubscription = null; + } + this.retries = null; + }; + RetryWhenSubscriber.prototype.notifyNext = function (outerValue, innerValue, outerIndex, innerIndex, innerSub) { + var _unsubscribe = this._unsubscribe; + this._unsubscribe = null; + this._unsubscribeAndRecycle(); + this._unsubscribe = _unsubscribe; + this.source.subscribe(this); + }; + return RetryWhenSubscriber; +}(_OuterSubscriber__WEBPACK_IMPORTED_MODULE_2__["OuterSubscriber"])); +//# sourceMappingURL=retryWhen.js.map + + +/***/ }), +/* 307 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "sample", function() { return sample; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(171); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(182); +/** PURE_IMPORTS_START tslib,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ + + + +function sample(notifier) { + return function (source) { return source.lift(new SampleOperator(notifier)); }; +} +var SampleOperator = /*@__PURE__*/ (function () { + function SampleOperator(notifier) { + this.notifier = notifier; + } + SampleOperator.prototype.call = function (subscriber, source) { + var sampleSubscriber = new SampleSubscriber(subscriber); + var subscription = source.subscribe(sampleSubscriber); + subscription.add(Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__["subscribeToResult"])(sampleSubscriber, this.notifier)); + return subscription; + }; + return SampleOperator; +}()); +var SampleSubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](SampleSubscriber, _super); + function SampleSubscriber() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this.hasValue = false; + return _this; + } + SampleSubscriber.prototype._next = function (value) { + this.value = value; + this.hasValue = true; + }; + SampleSubscriber.prototype.notifyNext = function (outerValue, innerValue, outerIndex, innerIndex, innerSub) { + this.emitValue(); + }; + SampleSubscriber.prototype.notifyComplete = function () { + this.emitValue(); + }; + SampleSubscriber.prototype.emitValue = function () { + if (this.hasValue) { + this.hasValue = false; + this.destination.next(this.value); + } + }; + return SampleSubscriber; +}(_OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__["OuterSubscriber"])); +//# sourceMappingURL=sample.js.map + + +/***/ }), +/* 308 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "sampleTime", function() { return sampleTime; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(172); +/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(199); +/** PURE_IMPORTS_START tslib,_Subscriber,_scheduler_async PURE_IMPORTS_END */ + + + +function sampleTime(period, scheduler) { + if (scheduler === void 0) { + scheduler = _scheduler_async__WEBPACK_IMPORTED_MODULE_2__["async"]; + } + return function (source) { return source.lift(new SampleTimeOperator(period, scheduler)); }; +} +var SampleTimeOperator = /*@__PURE__*/ (function () { + function SampleTimeOperator(period, scheduler) { + this.period = period; + this.scheduler = scheduler; + } + SampleTimeOperator.prototype.call = function (subscriber, source) { + return source.subscribe(new SampleTimeSubscriber(subscriber, this.period, this.scheduler)); + }; + return SampleTimeOperator; +}()); +var SampleTimeSubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](SampleTimeSubscriber, _super); + function SampleTimeSubscriber(destination, period, scheduler) { + var _this = _super.call(this, destination) || this; + _this.period = period; + _this.scheduler = scheduler; + _this.hasValue = false; + _this.add(scheduler.schedule(dispatchNotification, period, { subscriber: _this, period: period })); + return _this; + } + SampleTimeSubscriber.prototype._next = function (value) { + this.lastValue = value; + this.hasValue = true; + }; + SampleTimeSubscriber.prototype.notifyNext = function () { + if (this.hasValue) { + this.hasValue = false; + this.destination.next(this.lastValue); + } + }; + return SampleTimeSubscriber; +}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); +function dispatchNotification(state) { + var subscriber = state.subscriber, period = state.period; + subscriber.notifyNext(); + this.schedule(state, period); +} +//# sourceMappingURL=sampleTime.js.map + + +/***/ }), +/* 309 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "sequenceEqual", function() { return sequenceEqual; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "SequenceEqualOperator", function() { return SequenceEqualOperator; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "SequenceEqualSubscriber", function() { return SequenceEqualSubscriber; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(172); +/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ + + +function sequenceEqual(compareTo, comparator) { + return function (source) { return source.lift(new SequenceEqualOperator(compareTo, comparator)); }; +} +var SequenceEqualOperator = /*@__PURE__*/ (function () { + function SequenceEqualOperator(compareTo, comparator) { + this.compareTo = compareTo; + this.comparator = comparator; + } + SequenceEqualOperator.prototype.call = function (subscriber, source) { + return source.subscribe(new SequenceEqualSubscriber(subscriber, this.compareTo, this.comparator)); + }; + return SequenceEqualOperator; +}()); + +var SequenceEqualSubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](SequenceEqualSubscriber, _super); + function SequenceEqualSubscriber(destination, compareTo, comparator) { + var _this = _super.call(this, destination) || this; + _this.compareTo = compareTo; + _this.comparator = comparator; + _this._a = []; + _this._b = []; + _this._oneComplete = false; + _this.destination.add(compareTo.subscribe(new SequenceEqualCompareToSubscriber(destination, _this))); + return _this; + } + SequenceEqualSubscriber.prototype._next = function (value) { + if (this._oneComplete && this._b.length === 0) { + this.emit(false); + } + else { + this._a.push(value); + this.checkValues(); + } + }; + SequenceEqualSubscriber.prototype._complete = function () { + if (this._oneComplete) { + this.emit(this._a.length === 0 && this._b.length === 0); + } + else { + this._oneComplete = true; + } + this.unsubscribe(); + }; + SequenceEqualSubscriber.prototype.checkValues = function () { + var _c = this, _a = _c._a, _b = _c._b, comparator = _c.comparator; + while (_a.length > 0 && _b.length > 0) { + var a = _a.shift(); + var b = _b.shift(); + var areEqual = false; + try { + areEqual = comparator ? comparator(a, b) : a === b; + } + catch (e) { + this.destination.error(e); + } + if (!areEqual) { + this.emit(false); + } + } + }; + SequenceEqualSubscriber.prototype.emit = function (value) { + var destination = this.destination; + destination.next(value); + destination.complete(); + }; + SequenceEqualSubscriber.prototype.nextB = function (value) { + if (this._oneComplete && this._a.length === 0) { + this.emit(false); + } + else { + this._b.push(value); + this.checkValues(); + } + }; + SequenceEqualSubscriber.prototype.completeB = function () { + if (this._oneComplete) { + this.emit(this._a.length === 0 && this._b.length === 0); + } + else { + this._oneComplete = true; + } + }; + return SequenceEqualSubscriber; +}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); + +var SequenceEqualCompareToSubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](SequenceEqualCompareToSubscriber, _super); + function SequenceEqualCompareToSubscriber(destination, parent) { + var _this = _super.call(this, destination) || this; + _this.parent = parent; + return _this; + } + SequenceEqualCompareToSubscriber.prototype._next = function (value) { + this.parent.nextB(value); + }; + SequenceEqualCompareToSubscriber.prototype._error = function (err) { + this.parent.error(err); + this.unsubscribe(); + }; + SequenceEqualCompareToSubscriber.prototype._complete = function () { + this.parent.completeB(); + this.unsubscribe(); + }; + return SequenceEqualCompareToSubscriber; +}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); +//# sourceMappingURL=sequenceEqual.js.map + + +/***/ }), +/* 310 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "share", function() { return share; }); +/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(282); +/* harmony import */ var _refCount__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(284); +/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(265); +/** PURE_IMPORTS_START _multicast,_refCount,_Subject PURE_IMPORTS_END */ + + + +function shareSubjectFactory() { + return new _Subject__WEBPACK_IMPORTED_MODULE_2__["Subject"](); +} +function share() { + return function (source) { return Object(_refCount__WEBPACK_IMPORTED_MODULE_1__["refCount"])()(Object(_multicast__WEBPACK_IMPORTED_MODULE_0__["multicast"])(shareSubjectFactory)(source)); }; +} +//# sourceMappingURL=share.js.map + + +/***/ }), +/* 311 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "shareReplay", function() { return shareReplay; }); +/* harmony import */ var _ReplaySubject__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(297); +/** PURE_IMPORTS_START _ReplaySubject PURE_IMPORTS_END */ + +function shareReplay(configOrBufferSize, windowTime, scheduler) { + var config; + if (configOrBufferSize && typeof configOrBufferSize === 'object') { + config = configOrBufferSize; + } + else { + config = { + bufferSize: configOrBufferSize, + windowTime: windowTime, + refCount: false, + scheduler: scheduler + }; + } + return function (source) { return source.lift(shareReplayOperator(config)); }; +} +function shareReplayOperator(_a) { + var _b = _a.bufferSize, bufferSize = _b === void 0 ? Number.POSITIVE_INFINITY : _b, _c = _a.windowTime, windowTime = _c === void 0 ? Number.POSITIVE_INFINITY : _c, useRefCount = _a.refCount, scheduler = _a.scheduler; + var subject; + var refCount = 0; + var subscription; + var hasError = false; + var isComplete = false; + return function shareReplayOperation(source) { + refCount++; + if (!subject || hasError) { + hasError = false; + subject = new _ReplaySubject__WEBPACK_IMPORTED_MODULE_0__["ReplaySubject"](bufferSize, windowTime, scheduler); + subscription = source.subscribe({ + next: function (value) { subject.next(value); }, + error: function (err) { + hasError = true; + subject.error(err); + }, + complete: function () { + isComplete = true; + subject.complete(); + }, + }); + } + var innerSub = subject.subscribe(this); + this.add(function () { + refCount--; + innerSub.unsubscribe(); + if (subscription && !isComplete && useRefCount && refCount === 0) { + subscription.unsubscribe(); + subscription = undefined; + subject = undefined; + } + }); + }; +} +//# sourceMappingURL=shareReplay.js.map + + +/***/ }), +/* 312 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "single", function() { return single; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(172); +/* harmony import */ var _util_EmptyError__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(253); +/** PURE_IMPORTS_START tslib,_Subscriber,_util_EmptyError PURE_IMPORTS_END */ + + + +function single(predicate) { + return function (source) { return source.lift(new SingleOperator(predicate, source)); }; +} +var SingleOperator = /*@__PURE__*/ (function () { + function SingleOperator(predicate, source) { + this.predicate = predicate; + this.source = source; + } + SingleOperator.prototype.call = function (subscriber, source) { + return source.subscribe(new SingleSubscriber(subscriber, this.predicate, this.source)); + }; + return SingleOperator; +}()); +var SingleSubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](SingleSubscriber, _super); + function SingleSubscriber(destination, predicate, source) { + var _this = _super.call(this, destination) || this; + _this.predicate = predicate; + _this.source = source; + _this.seenValue = false; + _this.index = 0; + return _this; + } + SingleSubscriber.prototype.applySingleValue = function (value) { + if (this.seenValue) { + this.destination.error('Sequence contains more than one element'); + } + else { + this.seenValue = true; + this.singleValue = value; + } + }; + SingleSubscriber.prototype._next = function (value) { + var index = this.index++; + if (this.predicate) { + this.tryNext(value, index); + } + else { + this.applySingleValue(value); + } + }; + SingleSubscriber.prototype.tryNext = function (value, index) { + try { + if (this.predicate(value, index, this.source)) { + this.applySingleValue(value); + } + } + catch (err) { + this.destination.error(err); + } + }; + SingleSubscriber.prototype._complete = function () { + var destination = this.destination; + if (this.index > 0) { + destination.next(this.seenValue ? this.singleValue : undefined); + destination.complete(); + } + else { + destination.error(new _util_EmptyError__WEBPACK_IMPORTED_MODULE_2__["EmptyError"]); + } + }; + return SingleSubscriber; +}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); +//# sourceMappingURL=single.js.map + + +/***/ }), +/* 313 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "skip", function() { return skip; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(172); +/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ + + +function skip(count) { + return function (source) { return source.lift(new SkipOperator(count)); }; +} +var SkipOperator = /*@__PURE__*/ (function () { + function SkipOperator(total) { + this.total = total; + } + SkipOperator.prototype.call = function (subscriber, source) { + return source.subscribe(new SkipSubscriber(subscriber, this.total)); + }; + return SkipOperator; +}()); +var SkipSubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](SkipSubscriber, _super); + function SkipSubscriber(destination, total) { + var _this = _super.call(this, destination) || this; + _this.total = total; + _this.count = 0; + return _this; + } + SkipSubscriber.prototype._next = function (x) { + if (++this.count > this.total) { + this.destination.next(x); + } + }; + return SkipSubscriber; +}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); +//# sourceMappingURL=skip.js.map + + +/***/ }), +/* 314 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "skipLast", function() { return skipLast; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(172); +/* harmony import */ var _util_ArgumentOutOfRangeError__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(250); +/** PURE_IMPORTS_START tslib,_Subscriber,_util_ArgumentOutOfRangeError PURE_IMPORTS_END */ + + + +function skipLast(count) { + return function (source) { return source.lift(new SkipLastOperator(count)); }; +} +var SkipLastOperator = /*@__PURE__*/ (function () { + function SkipLastOperator(_skipCount) { + this._skipCount = _skipCount; + if (this._skipCount < 0) { + throw new _util_ArgumentOutOfRangeError__WEBPACK_IMPORTED_MODULE_2__["ArgumentOutOfRangeError"]; + } + } + SkipLastOperator.prototype.call = function (subscriber, source) { + if (this._skipCount === 0) { + return source.subscribe(new _Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"](subscriber)); + } + else { + return source.subscribe(new SkipLastSubscriber(subscriber, this._skipCount)); + } + }; + return SkipLastOperator; +}()); +var SkipLastSubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](SkipLastSubscriber, _super); + function SkipLastSubscriber(destination, _skipCount) { + var _this = _super.call(this, destination) || this; + _this._skipCount = _skipCount; + _this._count = 0; + _this._ring = new Array(_skipCount); + return _this; + } + SkipLastSubscriber.prototype._next = function (value) { + var skipCount = this._skipCount; + var count = this._count++; + if (count < skipCount) { + this._ring[count] = value; + } + else { + var currentIndex = count % skipCount; + var ring = this._ring; + var oldValue = ring[currentIndex]; + ring[currentIndex] = value; + this.destination.next(oldValue); + } + }; + return SkipLastSubscriber; +}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); +//# sourceMappingURL=skipLast.js.map + + +/***/ }), +/* 315 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "skipUntil", function() { return skipUntil; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(171); +/* harmony import */ var _InnerSubscriber__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(183); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(182); +/** PURE_IMPORTS_START tslib,_OuterSubscriber,_InnerSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ + + + + +function skipUntil(notifier) { + return function (source) { return source.lift(new SkipUntilOperator(notifier)); }; +} +var SkipUntilOperator = /*@__PURE__*/ (function () { + function SkipUntilOperator(notifier) { + this.notifier = notifier; + } + SkipUntilOperator.prototype.call = function (destination, source) { + return source.subscribe(new SkipUntilSubscriber(destination, this.notifier)); + }; + return SkipUntilOperator; +}()); +var SkipUntilSubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](SkipUntilSubscriber, _super); + function SkipUntilSubscriber(destination, notifier) { + var _this = _super.call(this, destination) || this; + _this.hasValue = false; + var innerSubscriber = new _InnerSubscriber__WEBPACK_IMPORTED_MODULE_2__["InnerSubscriber"](_this, undefined, undefined); + _this.add(innerSubscriber); + _this.innerSubscription = innerSubscriber; + Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_3__["subscribeToResult"])(_this, notifier, undefined, undefined, innerSubscriber); + return _this; + } + SkipUntilSubscriber.prototype._next = function (value) { + if (this.hasValue) { + _super.prototype._next.call(this, value); + } + }; + SkipUntilSubscriber.prototype.notifyNext = function (outerValue, innerValue, outerIndex, innerIndex, innerSub) { + this.hasValue = true; + if (this.innerSubscription) { + this.innerSubscription.unsubscribe(); + } + }; + SkipUntilSubscriber.prototype.notifyComplete = function () { + }; + return SkipUntilSubscriber; +}(_OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__["OuterSubscriber"])); +//# sourceMappingURL=skipUntil.js.map + + +/***/ }), +/* 316 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "skipWhile", function() { return skipWhile; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(172); +/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ + + +function skipWhile(predicate) { + return function (source) { return source.lift(new SkipWhileOperator(predicate)); }; +} +var SkipWhileOperator = /*@__PURE__*/ (function () { + function SkipWhileOperator(predicate) { + this.predicate = predicate; + } + SkipWhileOperator.prototype.call = function (subscriber, source) { + return source.subscribe(new SkipWhileSubscriber(subscriber, this.predicate)); + }; + return SkipWhileOperator; +}()); +var SkipWhileSubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](SkipWhileSubscriber, _super); + function SkipWhileSubscriber(destination, predicate) { + var _this = _super.call(this, destination) || this; + _this.predicate = predicate; + _this.skipping = true; + _this.index = 0; + return _this; + } + SkipWhileSubscriber.prototype._next = function (value) { + var destination = this.destination; + if (this.skipping) { + this.tryCallPredicate(value); + } + if (!this.skipping) { + destination.next(value); + } + }; + SkipWhileSubscriber.prototype.tryCallPredicate = function (value) { + try { + var result = this.predicate(value, this.index++); + this.skipping = Boolean(result); + } + catch (err) { + this.destination.error(err); + } + }; + return SkipWhileSubscriber; +}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); +//# sourceMappingURL=skipWhile.js.map + + +/***/ }), +/* 317 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "startWith", function() { return startWith; }); +/* harmony import */ var _observable_concat__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(226); +/* harmony import */ var _util_isScheduler__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(206); +/** PURE_IMPORTS_START _observable_concat,_util_isScheduler PURE_IMPORTS_END */ + + +function startWith() { + var array = []; + for (var _i = 0; _i < arguments.length; _i++) { + array[_i] = arguments[_i]; + } + var scheduler = array[array.length - 1]; + if (Object(_util_isScheduler__WEBPACK_IMPORTED_MODULE_1__["isScheduler"])(scheduler)) { + array.pop(); + return function (source) { return Object(_observable_concat__WEBPACK_IMPORTED_MODULE_0__["concat"])(array, source, scheduler); }; + } + else { + return function (source) { return Object(_observable_concat__WEBPACK_IMPORTED_MODULE_0__["concat"])(array, source); }; + } +} +//# sourceMappingURL=startWith.js.map + + +/***/ }), +/* 318 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "subscribeOn", function() { return subscribeOn; }); +/* harmony import */ var _observable_SubscribeOnObservable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(319); +/** PURE_IMPORTS_START _observable_SubscribeOnObservable PURE_IMPORTS_END */ + +function subscribeOn(scheduler, delay) { + if (delay === void 0) { + delay = 0; + } + return function subscribeOnOperatorFunction(source) { + return source.lift(new SubscribeOnOperator(scheduler, delay)); + }; +} +var SubscribeOnOperator = /*@__PURE__*/ (function () { + function SubscribeOnOperator(scheduler, delay) { + this.scheduler = scheduler; + this.delay = delay; + } + SubscribeOnOperator.prototype.call = function (subscriber, source) { + return new _observable_SubscribeOnObservable__WEBPACK_IMPORTED_MODULE_0__["SubscribeOnObservable"](source, this.delay, this.scheduler).subscribe(subscriber); + }; + return SubscribeOnOperator; +}()); +//# sourceMappingURL=subscribeOn.js.map + + +/***/ }), +/* 319 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "SubscribeOnObservable", function() { return SubscribeOnObservable; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(193); +/* harmony import */ var _scheduler_asap__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(320); +/* harmony import */ var _util_isNumeric__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(205); +/** PURE_IMPORTS_START tslib,_Observable,_scheduler_asap,_util_isNumeric PURE_IMPORTS_END */ + + + + +var SubscribeOnObservable = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](SubscribeOnObservable, _super); + function SubscribeOnObservable(source, delayTime, scheduler) { + if (delayTime === void 0) { + delayTime = 0; + } + if (scheduler === void 0) { + scheduler = _scheduler_asap__WEBPACK_IMPORTED_MODULE_2__["asap"]; + } + var _this = _super.call(this) || this; + _this.source = source; + _this.delayTime = delayTime; + _this.scheduler = scheduler; + if (!Object(_util_isNumeric__WEBPACK_IMPORTED_MODULE_3__["isNumeric"])(delayTime) || delayTime < 0) { + _this.delayTime = 0; + } + if (!scheduler || typeof scheduler.schedule !== 'function') { + _this.scheduler = _scheduler_asap__WEBPACK_IMPORTED_MODULE_2__["asap"]; + } + return _this; + } + SubscribeOnObservable.create = function (source, delay, scheduler) { + if (delay === void 0) { + delay = 0; + } + if (scheduler === void 0) { + scheduler = _scheduler_asap__WEBPACK_IMPORTED_MODULE_2__["asap"]; + } + return new SubscribeOnObservable(source, delay, scheduler); + }; + SubscribeOnObservable.dispatch = function (arg) { + var source = arg.source, subscriber = arg.subscriber; + return this.add(source.subscribe(subscriber)); + }; + SubscribeOnObservable.prototype._subscribe = function (subscriber) { + var delay = this.delayTime; + var source = this.source; + var scheduler = this.scheduler; + return scheduler.schedule(SubscribeOnObservable.dispatch, delay, { + source: source, subscriber: subscriber + }); + }; + return SubscribeOnObservable; +}(_Observable__WEBPACK_IMPORTED_MODULE_1__["Observable"])); + +//# sourceMappingURL=SubscribeOnObservable.js.map + + +/***/ }), +/* 320 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "asap", function() { return asap; }); +/* harmony import */ var _AsapAction__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(321); +/* harmony import */ var _AsapScheduler__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(323); +/** PURE_IMPORTS_START _AsapAction,_AsapScheduler PURE_IMPORTS_END */ + + +var asap = /*@__PURE__*/ new _AsapScheduler__WEBPACK_IMPORTED_MODULE_1__["AsapScheduler"](_AsapAction__WEBPACK_IMPORTED_MODULE_0__["AsapAction"]); +//# sourceMappingURL=asap.js.map + + +/***/ }), +/* 321 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "AsapAction", function() { return AsapAction; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _util_Immediate__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(322); +/* harmony import */ var _AsyncAction__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(200); +/** PURE_IMPORTS_START tslib,_util_Immediate,_AsyncAction PURE_IMPORTS_END */ + + + +var AsapAction = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](AsapAction, _super); + function AsapAction(scheduler, work) { + var _this = _super.call(this, scheduler, work) || this; + _this.scheduler = scheduler; + _this.work = work; + return _this; + } + AsapAction.prototype.requestAsyncId = function (scheduler, id, delay) { + if (delay === void 0) { + delay = 0; + } + if (delay !== null && delay > 0) { + return _super.prototype.requestAsyncId.call(this, scheduler, id, delay); + } + scheduler.actions.push(this); + return scheduler.scheduled || (scheduler.scheduled = _util_Immediate__WEBPACK_IMPORTED_MODULE_1__["Immediate"].setImmediate(scheduler.flush.bind(scheduler, null))); + }; + AsapAction.prototype.recycleAsyncId = function (scheduler, id, delay) { + if (delay === void 0) { + delay = 0; + } + if ((delay !== null && delay > 0) || (delay === null && this.delay > 0)) { + return _super.prototype.recycleAsyncId.call(this, scheduler, id, delay); + } + if (scheduler.actions.length === 0) { + _util_Immediate__WEBPACK_IMPORTED_MODULE_1__["Immediate"].clearImmediate(id); + scheduler.scheduled = undefined; + } + return undefined; + }; + return AsapAction; +}(_AsyncAction__WEBPACK_IMPORTED_MODULE_2__["AsyncAction"])); + +//# sourceMappingURL=AsapAction.js.map + + +/***/ }), +/* 322 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Immediate", function() { return Immediate; }); +/** PURE_IMPORTS_START PURE_IMPORTS_END */ +var nextHandle = 1; +var tasksByHandle = {}; +function runIfPresent(handle) { + var cb = tasksByHandle[handle]; + if (cb) { + cb(); + } +} +var Immediate = { + setImmediate: function (cb) { + var handle = nextHandle++; + tasksByHandle[handle] = cb; + Promise.resolve().then(function () { return runIfPresent(handle); }); + return handle; + }, + clearImmediate: function (handle) { + delete tasksByHandle[handle]; + }, +}; +//# sourceMappingURL=Immediate.js.map + + +/***/ }), +/* 323 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "AsapScheduler", function() { return AsapScheduler; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _AsyncScheduler__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(202); +/** PURE_IMPORTS_START tslib,_AsyncScheduler PURE_IMPORTS_END */ + + +var AsapScheduler = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](AsapScheduler, _super); + function AsapScheduler() { + return _super !== null && _super.apply(this, arguments) || this; + } + AsapScheduler.prototype.flush = function (action) { + this.active = true; + this.scheduled = undefined; + var actions = this.actions; + var error; + var index = -1; + var count = actions.length; + action = action || actions.shift(); + do { + if (error = action.execute(action.state, action.delay)) { + break; + } + } while (++index < count && (action = actions.shift())); + this.active = false; + if (error) { + while (++index < count && (action = actions.shift())) { + action.unsubscribe(); + } + throw error; + } + }; + return AsapScheduler; +}(_AsyncScheduler__WEBPACK_IMPORTED_MODULE_1__["AsyncScheduler"])); + +//# sourceMappingURL=AsapScheduler.js.map + + +/***/ }), +/* 324 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "switchAll", function() { return switchAll; }); +/* harmony import */ var _switchMap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(325); +/* harmony import */ var _util_identity__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(232); +/** PURE_IMPORTS_START _switchMap,_util_identity PURE_IMPORTS_END */ + + +function switchAll() { + return Object(_switchMap__WEBPACK_IMPORTED_MODULE_0__["switchMap"])(_util_identity__WEBPACK_IMPORTED_MODULE_1__["identity"]); +} +//# sourceMappingURL=switchAll.js.map + + +/***/ }), +/* 325 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "switchMap", function() { return switchMap; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(171); +/* harmony import */ var _InnerSubscriber__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(183); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(182); +/* harmony import */ var _map__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(231); +/* harmony import */ var _observable_from__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(218); +/** PURE_IMPORTS_START tslib,_OuterSubscriber,_InnerSubscriber,_util_subscribeToResult,_map,_observable_from PURE_IMPORTS_END */ + + + + + + +function switchMap(project, resultSelector) { + if (typeof resultSelector === 'function') { + return function (source) { return source.pipe(switchMap(function (a, i) { return Object(_observable_from__WEBPACK_IMPORTED_MODULE_5__["from"])(project(a, i)).pipe(Object(_map__WEBPACK_IMPORTED_MODULE_4__["map"])(function (b, ii) { return resultSelector(a, b, i, ii); })); })); }; + } + return function (source) { return source.lift(new SwitchMapOperator(project)); }; +} +var SwitchMapOperator = /*@__PURE__*/ (function () { + function SwitchMapOperator(project) { + this.project = project; + } + SwitchMapOperator.prototype.call = function (subscriber, source) { + return source.subscribe(new SwitchMapSubscriber(subscriber, this.project)); + }; + return SwitchMapOperator; +}()); +var SwitchMapSubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](SwitchMapSubscriber, _super); + function SwitchMapSubscriber(destination, project) { + var _this = _super.call(this, destination) || this; + _this.project = project; + _this.index = 0; + return _this; + } + SwitchMapSubscriber.prototype._next = function (value) { + var result; + var index = this.index++; + try { + result = this.project(value, index); + } + catch (error) { + this.destination.error(error); + return; + } + this._innerSub(result, value, index); + }; + SwitchMapSubscriber.prototype._innerSub = function (result, value, index) { + var innerSubscription = this.innerSubscription; + if (innerSubscription) { + innerSubscription.unsubscribe(); + } + var innerSubscriber = new _InnerSubscriber__WEBPACK_IMPORTED_MODULE_2__["InnerSubscriber"](this, undefined, undefined); + var destination = this.destination; + destination.add(innerSubscriber); + this.innerSubscription = Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_3__["subscribeToResult"])(this, result, value, index, innerSubscriber); + }; + SwitchMapSubscriber.prototype._complete = function () { + var innerSubscription = this.innerSubscription; + if (!innerSubscription || innerSubscription.closed) { + _super.prototype._complete.call(this); + } + this.unsubscribe(); + }; + SwitchMapSubscriber.prototype._unsubscribe = function () { + this.innerSubscription = null; + }; + SwitchMapSubscriber.prototype.notifyComplete = function (innerSub) { + var destination = this.destination; + destination.remove(innerSub); + this.innerSubscription = null; + if (this.isStopped) { + _super.prototype._complete.call(this); + } + }; + SwitchMapSubscriber.prototype.notifyNext = function (outerValue, innerValue, outerIndex, innerIndex, innerSub) { + this.destination.next(innerValue); + }; + return SwitchMapSubscriber; +}(_OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__["OuterSubscriber"])); +//# sourceMappingURL=switchMap.js.map + + +/***/ }), +/* 326 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "switchMapTo", function() { return switchMapTo; }); +/* harmony import */ var _switchMap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(325); +/** PURE_IMPORTS_START _switchMap PURE_IMPORTS_END */ + +function switchMapTo(innerObservable, resultSelector) { + return resultSelector ? Object(_switchMap__WEBPACK_IMPORTED_MODULE_0__["switchMap"])(function () { return innerObservable; }, resultSelector) : Object(_switchMap__WEBPACK_IMPORTED_MODULE_0__["switchMap"])(function () { return innerObservable; }); +} +//# sourceMappingURL=switchMapTo.js.map + + +/***/ }), +/* 327 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "takeUntil", function() { return takeUntil; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(171); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(182); +/** PURE_IMPORTS_START tslib,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ + + + +function takeUntil(notifier) { + return function (source) { return source.lift(new TakeUntilOperator(notifier)); }; +} +var TakeUntilOperator = /*@__PURE__*/ (function () { + function TakeUntilOperator(notifier) { + this.notifier = notifier; + } + TakeUntilOperator.prototype.call = function (subscriber, source) { + var takeUntilSubscriber = new TakeUntilSubscriber(subscriber); + var notifierSubscription = Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__["subscribeToResult"])(takeUntilSubscriber, this.notifier); + if (notifierSubscription && !takeUntilSubscriber.seenValue) { + takeUntilSubscriber.add(notifierSubscription); + return source.subscribe(takeUntilSubscriber); + } + return takeUntilSubscriber; + }; + return TakeUntilOperator; +}()); +var TakeUntilSubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](TakeUntilSubscriber, _super); + function TakeUntilSubscriber(destination) { + var _this = _super.call(this, destination) || this; + _this.seenValue = false; + return _this; + } + TakeUntilSubscriber.prototype.notifyNext = function (outerValue, innerValue, outerIndex, innerIndex, innerSub) { + this.seenValue = true; + this.complete(); + }; + TakeUntilSubscriber.prototype.notifyComplete = function () { + }; + return TakeUntilSubscriber; +}(_OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__["OuterSubscriber"])); +//# sourceMappingURL=takeUntil.js.map + + +/***/ }), +/* 328 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "takeWhile", function() { return takeWhile; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(172); +/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ + + +function takeWhile(predicate, inclusive) { + if (inclusive === void 0) { + inclusive = false; + } + return function (source) { + return source.lift(new TakeWhileOperator(predicate, inclusive)); + }; +} +var TakeWhileOperator = /*@__PURE__*/ (function () { + function TakeWhileOperator(predicate, inclusive) { + this.predicate = predicate; + this.inclusive = inclusive; + } + TakeWhileOperator.prototype.call = function (subscriber, source) { + return source.subscribe(new TakeWhileSubscriber(subscriber, this.predicate, this.inclusive)); + }; + return TakeWhileOperator; +}()); +var TakeWhileSubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](TakeWhileSubscriber, _super); + function TakeWhileSubscriber(destination, predicate, inclusive) { + var _this = _super.call(this, destination) || this; + _this.predicate = predicate; + _this.inclusive = inclusive; + _this.index = 0; + return _this; + } + TakeWhileSubscriber.prototype._next = function (value) { + var destination = this.destination; + var result; + try { + result = this.predicate(value, this.index++); + } + catch (err) { + destination.error(err); + return; + } + this.nextOrComplete(value, result); + }; + TakeWhileSubscriber.prototype.nextOrComplete = function (value, predicateResult) { + var destination = this.destination; + if (Boolean(predicateResult)) { + destination.next(value); + } + else { + if (this.inclusive) { + destination.next(value); + } + destination.complete(); + } + }; + return TakeWhileSubscriber; +}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); +//# sourceMappingURL=takeWhile.js.map + + +/***/ }), +/* 329 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "tap", function() { return tap; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(172); +/* harmony import */ var _util_noop__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(197); +/* harmony import */ var _util_isFunction__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(173); +/** PURE_IMPORTS_START tslib,_Subscriber,_util_noop,_util_isFunction PURE_IMPORTS_END */ + + + + +function tap(nextOrObserver, error, complete) { + return function tapOperatorFunction(source) { + return source.lift(new DoOperator(nextOrObserver, error, complete)); + }; +} +var DoOperator = /*@__PURE__*/ (function () { + function DoOperator(nextOrObserver, error, complete) { + this.nextOrObserver = nextOrObserver; + this.error = error; + this.complete = complete; + } + DoOperator.prototype.call = function (subscriber, source) { + return source.subscribe(new TapSubscriber(subscriber, this.nextOrObserver, this.error, this.complete)); + }; + return DoOperator; +}()); +var TapSubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](TapSubscriber, _super); + function TapSubscriber(destination, observerOrNext, error, complete) { + var _this = _super.call(this, destination) || this; + _this._tapNext = _util_noop__WEBPACK_IMPORTED_MODULE_2__["noop"]; + _this._tapError = _util_noop__WEBPACK_IMPORTED_MODULE_2__["noop"]; + _this._tapComplete = _util_noop__WEBPACK_IMPORTED_MODULE_2__["noop"]; + _this._tapError = error || _util_noop__WEBPACK_IMPORTED_MODULE_2__["noop"]; + _this._tapComplete = complete || _util_noop__WEBPACK_IMPORTED_MODULE_2__["noop"]; + if (Object(_util_isFunction__WEBPACK_IMPORTED_MODULE_3__["isFunction"])(observerOrNext)) { + _this._context = _this; + _this._tapNext = observerOrNext; + } + else if (observerOrNext) { + _this._context = observerOrNext; + _this._tapNext = observerOrNext.next || _util_noop__WEBPACK_IMPORTED_MODULE_2__["noop"]; + _this._tapError = observerOrNext.error || _util_noop__WEBPACK_IMPORTED_MODULE_2__["noop"]; + _this._tapComplete = observerOrNext.complete || _util_noop__WEBPACK_IMPORTED_MODULE_2__["noop"]; + } + return _this; + } + TapSubscriber.prototype._next = function (value) { + try { + this._tapNext.call(this._context, value); + } + catch (err) { + this.destination.error(err); + return; + } + this.destination.next(value); + }; + TapSubscriber.prototype._error = function (err) { + try { + this._tapError.call(this._context, err); + } + catch (err) { + this.destination.error(err); + return; + } + this.destination.error(err); + }; + TapSubscriber.prototype._complete = function () { + try { + this._tapComplete.call(this._context); + } + catch (err) { + this.destination.error(err); + return; + } + return this.destination.complete(); + }; + return TapSubscriber; +}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); +//# sourceMappingURL=tap.js.map + + +/***/ }), +/* 330 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "defaultThrottleConfig", function() { return defaultThrottleConfig; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "throttle", function() { return throttle; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(171); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(182); +/** PURE_IMPORTS_START tslib,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ + + + +var defaultThrottleConfig = { + leading: true, + trailing: false +}; +function throttle(durationSelector, config) { + if (config === void 0) { + config = defaultThrottleConfig; + } + return function (source) { return source.lift(new ThrottleOperator(durationSelector, config.leading, config.trailing)); }; +} +var ThrottleOperator = /*@__PURE__*/ (function () { + function ThrottleOperator(durationSelector, leading, trailing) { + this.durationSelector = durationSelector; + this.leading = leading; + this.trailing = trailing; + } + ThrottleOperator.prototype.call = function (subscriber, source) { + return source.subscribe(new ThrottleSubscriber(subscriber, this.durationSelector, this.leading, this.trailing)); + }; + return ThrottleOperator; +}()); +var ThrottleSubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](ThrottleSubscriber, _super); + function ThrottleSubscriber(destination, durationSelector, _leading, _trailing) { + var _this = _super.call(this, destination) || this; + _this.destination = destination; + _this.durationSelector = durationSelector; + _this._leading = _leading; + _this._trailing = _trailing; + _this._hasValue = false; + return _this; + } + ThrottleSubscriber.prototype._next = function (value) { + this._hasValue = true; + this._sendValue = value; + if (!this._throttled) { + if (this._leading) { + this.send(); + } + else { + this.throttle(value); + } + } + }; + ThrottleSubscriber.prototype.send = function () { + var _a = this, _hasValue = _a._hasValue, _sendValue = _a._sendValue; + if (_hasValue) { + this.destination.next(_sendValue); + this.throttle(_sendValue); + } + this._hasValue = false; + this._sendValue = null; + }; + ThrottleSubscriber.prototype.throttle = function (value) { + var duration = this.tryDurationSelector(value); + if (!!duration) { + this.add(this._throttled = Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__["subscribeToResult"])(this, duration)); + } + }; + ThrottleSubscriber.prototype.tryDurationSelector = function (value) { + try { + return this.durationSelector(value); + } + catch (err) { + this.destination.error(err); + return null; + } + }; + ThrottleSubscriber.prototype.throttlingDone = function () { + var _a = this, _throttled = _a._throttled, _trailing = _a._trailing; + if (_throttled) { + _throttled.unsubscribe(); + } + this._throttled = null; + if (_trailing) { + this.send(); + } + }; + ThrottleSubscriber.prototype.notifyNext = function (outerValue, innerValue, outerIndex, innerIndex, innerSub) { + this.throttlingDone(); + }; + ThrottleSubscriber.prototype.notifyComplete = function () { + this.throttlingDone(); + }; + return ThrottleSubscriber; +}(_OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__["OuterSubscriber"])); +//# sourceMappingURL=throttle.js.map + + +/***/ }), +/* 331 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "throttleTime", function() { return throttleTime; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(172); +/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(199); +/* harmony import */ var _throttle__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(330); +/** PURE_IMPORTS_START tslib,_Subscriber,_scheduler_async,_throttle PURE_IMPORTS_END */ + + + + +function throttleTime(duration, scheduler, config) { + if (scheduler === void 0) { + scheduler = _scheduler_async__WEBPACK_IMPORTED_MODULE_2__["async"]; + } + if (config === void 0) { + config = _throttle__WEBPACK_IMPORTED_MODULE_3__["defaultThrottleConfig"]; + } + return function (source) { return source.lift(new ThrottleTimeOperator(duration, scheduler, config.leading, config.trailing)); }; +} +var ThrottleTimeOperator = /*@__PURE__*/ (function () { + function ThrottleTimeOperator(duration, scheduler, leading, trailing) { + this.duration = duration; + this.scheduler = scheduler; + this.leading = leading; + this.trailing = trailing; + } + ThrottleTimeOperator.prototype.call = function (subscriber, source) { + return source.subscribe(new ThrottleTimeSubscriber(subscriber, this.duration, this.scheduler, this.leading, this.trailing)); + }; + return ThrottleTimeOperator; +}()); +var ThrottleTimeSubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](ThrottleTimeSubscriber, _super); + function ThrottleTimeSubscriber(destination, duration, scheduler, leading, trailing) { + var _this = _super.call(this, destination) || this; + _this.duration = duration; + _this.scheduler = scheduler; + _this.leading = leading; + _this.trailing = trailing; + _this._hasTrailingValue = false; + _this._trailingValue = null; + return _this; + } + ThrottleTimeSubscriber.prototype._next = function (value) { + if (this.throttled) { + if (this.trailing) { + this._trailingValue = value; + this._hasTrailingValue = true; + } + } + else { + this.add(this.throttled = this.scheduler.schedule(dispatchNext, this.duration, { subscriber: this })); + if (this.leading) { + this.destination.next(value); + } + else if (this.trailing) { + this._trailingValue = value; + this._hasTrailingValue = true; + } + } + }; + ThrottleTimeSubscriber.prototype._complete = function () { + if (this._hasTrailingValue) { + this.destination.next(this._trailingValue); + this.destination.complete(); + } + else { + this.destination.complete(); + } + }; + ThrottleTimeSubscriber.prototype.clearThrottle = function () { + var throttled = this.throttled; + if (throttled) { + if (this.trailing && this._hasTrailingValue) { + this.destination.next(this._trailingValue); + this._trailingValue = null; + this._hasTrailingValue = false; + } + throttled.unsubscribe(); + this.remove(throttled); + this.throttled = null; + } + }; + return ThrottleTimeSubscriber; +}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); +function dispatchNext(arg) { + var subscriber = arg.subscriber; + subscriber.clearThrottle(); +} +//# sourceMappingURL=throttleTime.js.map + + +/***/ }), +/* 332 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "timeInterval", function() { return timeInterval; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "TimeInterval", function() { return TimeInterval; }); +/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); +/* harmony import */ var _scan__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(276); +/* harmony import */ var _observable_defer__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(333); +/* harmony import */ var _map__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(231); +/** PURE_IMPORTS_START _scheduler_async,_scan,_observable_defer,_map PURE_IMPORTS_END */ + + + + +function timeInterval(scheduler) { + if (scheduler === void 0) { + scheduler = _scheduler_async__WEBPACK_IMPORTED_MODULE_0__["async"]; + } + return function (source) { + return Object(_observable_defer__WEBPACK_IMPORTED_MODULE_2__["defer"])(function () { + return source.pipe(Object(_scan__WEBPACK_IMPORTED_MODULE_1__["scan"])(function (_a, value) { + var current = _a.current; + return ({ value: value, current: scheduler.now(), last: current }); + }, { current: scheduler.now(), value: undefined, last: undefined }), Object(_map__WEBPACK_IMPORTED_MODULE_3__["map"])(function (_a) { + var current = _a.current, last = _a.last, value = _a.value; + return new TimeInterval(value, current - last); + })); + }); + }; +} +var TimeInterval = /*@__PURE__*/ (function () { + function TimeInterval(value, interval) { + this.value = value; + this.interval = interval; + } + return TimeInterval; +}()); + +//# sourceMappingURL=timeInterval.js.map + + +/***/ }), +/* 333 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "defer", function() { return defer; }); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(193); +/* harmony import */ var _from__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(218); +/* harmony import */ var _empty__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(242); +/** PURE_IMPORTS_START _Observable,_from,_empty PURE_IMPORTS_END */ + + + +function defer(observableFactory) { + return new _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"](function (subscriber) { + var input; + try { + input = observableFactory(); + } + catch (err) { + subscriber.error(err); + return undefined; + } + var source = input ? Object(_from__WEBPACK_IMPORTED_MODULE_1__["from"])(input) : Object(_empty__WEBPACK_IMPORTED_MODULE_2__["empty"])(); + return source.subscribe(subscriber); + }); +} +//# sourceMappingURL=defer.js.map + + +/***/ }), +/* 334 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "timeout", function() { return timeout; }); +/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); +/* harmony import */ var _util_TimeoutError__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(335); +/* harmony import */ var _timeoutWith__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(336); +/* harmony import */ var _observable_throwError__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(243); +/** PURE_IMPORTS_START _scheduler_async,_util_TimeoutError,_timeoutWith,_observable_throwError PURE_IMPORTS_END */ + + + + +function timeout(due, scheduler) { + if (scheduler === void 0) { + scheduler = _scheduler_async__WEBPACK_IMPORTED_MODULE_0__["async"]; + } + return Object(_timeoutWith__WEBPACK_IMPORTED_MODULE_2__["timeoutWith"])(due, Object(_observable_throwError__WEBPACK_IMPORTED_MODULE_3__["throwError"])(new _util_TimeoutError__WEBPACK_IMPORTED_MODULE_1__["TimeoutError"]()), scheduler); +} +//# sourceMappingURL=timeout.js.map + + +/***/ }), +/* 335 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "TimeoutError", function() { return TimeoutError; }); +/** PURE_IMPORTS_START PURE_IMPORTS_END */ +var TimeoutErrorImpl = /*@__PURE__*/ (function () { + function TimeoutErrorImpl() { + Error.call(this); + this.message = 'Timeout has occurred'; + this.name = 'TimeoutError'; + return this; + } + TimeoutErrorImpl.prototype = /*@__PURE__*/ Object.create(Error.prototype); + return TimeoutErrorImpl; +})(); +var TimeoutError = TimeoutErrorImpl; +//# sourceMappingURL=TimeoutError.js.map + + +/***/ }), +/* 336 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "timeoutWith", function() { return timeoutWith; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(199); +/* harmony import */ var _util_isDate__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(240); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(171); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(182); +/** PURE_IMPORTS_START tslib,_scheduler_async,_util_isDate,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ + + + + + +function timeoutWith(due, withObservable, scheduler) { + if (scheduler === void 0) { + scheduler = _scheduler_async__WEBPACK_IMPORTED_MODULE_1__["async"]; + } + return function (source) { + var absoluteTimeout = Object(_util_isDate__WEBPACK_IMPORTED_MODULE_2__["isDate"])(due); + var waitFor = absoluteTimeout ? (+due - scheduler.now()) : Math.abs(due); + return source.lift(new TimeoutWithOperator(waitFor, absoluteTimeout, withObservable, scheduler)); + }; +} +var TimeoutWithOperator = /*@__PURE__*/ (function () { + function TimeoutWithOperator(waitFor, absoluteTimeout, withObservable, scheduler) { + this.waitFor = waitFor; + this.absoluteTimeout = absoluteTimeout; + this.withObservable = withObservable; + this.scheduler = scheduler; + } + TimeoutWithOperator.prototype.call = function (subscriber, source) { + return source.subscribe(new TimeoutWithSubscriber(subscriber, this.absoluteTimeout, this.waitFor, this.withObservable, this.scheduler)); + }; + return TimeoutWithOperator; +}()); +var TimeoutWithSubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](TimeoutWithSubscriber, _super); + function TimeoutWithSubscriber(destination, absoluteTimeout, waitFor, withObservable, scheduler) { + var _this = _super.call(this, destination) || this; + _this.absoluteTimeout = absoluteTimeout; + _this.waitFor = waitFor; + _this.withObservable = withObservable; + _this.scheduler = scheduler; + _this.action = null; + _this.scheduleTimeout(); + return _this; + } + TimeoutWithSubscriber.dispatchTimeout = function (subscriber) { + var withObservable = subscriber.withObservable; + subscriber._unsubscribeAndRecycle(); + subscriber.add(Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_4__["subscribeToResult"])(subscriber, withObservable)); + }; + TimeoutWithSubscriber.prototype.scheduleTimeout = function () { + var action = this.action; + if (action) { + this.action = action.schedule(this, this.waitFor); + } + else { + this.add(this.action = this.scheduler.schedule(TimeoutWithSubscriber.dispatchTimeout, this.waitFor, this)); + } + }; + TimeoutWithSubscriber.prototype._next = function (value) { + if (!this.absoluteTimeout) { + this.scheduleTimeout(); + } + _super.prototype._next.call(this, value); + }; + TimeoutWithSubscriber.prototype._unsubscribe = function () { + this.action = null; + this.scheduler = null; + this.withObservable = null; + }; + return TimeoutWithSubscriber; +}(_OuterSubscriber__WEBPACK_IMPORTED_MODULE_3__["OuterSubscriber"])); +//# sourceMappingURL=timeoutWith.js.map + + +/***/ }), +/* 337 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "timestamp", function() { return timestamp; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Timestamp", function() { return Timestamp; }); +/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(199); +/* harmony import */ var _map__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(231); +/** PURE_IMPORTS_START _scheduler_async,_map PURE_IMPORTS_END */ + + +function timestamp(scheduler) { + if (scheduler === void 0) { + scheduler = _scheduler_async__WEBPACK_IMPORTED_MODULE_0__["async"]; + } + return Object(_map__WEBPACK_IMPORTED_MODULE_1__["map"])(function (value) { return new Timestamp(value, scheduler.now()); }); +} +var Timestamp = /*@__PURE__*/ (function () { + function Timestamp(value, timestamp) { + this.value = value; + this.timestamp = timestamp; + } + return Timestamp; +}()); + +//# sourceMappingURL=timestamp.js.map + + +/***/ }), +/* 338 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "toArray", function() { return toArray; }); +/* harmony import */ var _reduce__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(275); +/** PURE_IMPORTS_START _reduce PURE_IMPORTS_END */ + +function toArrayReducer(arr, item, index) { + if (index === 0) { + return [item]; + } + arr.push(item); + return arr; +} +function toArray() { + return Object(_reduce__WEBPACK_IMPORTED_MODULE_0__["reduce"])(toArrayReducer, []); +} +//# sourceMappingURL=toArray.js.map + + +/***/ }), +/* 339 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "window", function() { return window; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(265); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(171); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(182); +/** PURE_IMPORTS_START tslib,_Subject,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ + + + + +function window(windowBoundaries) { + return function windowOperatorFunction(source) { + return source.lift(new WindowOperator(windowBoundaries)); + }; +} +var WindowOperator = /*@__PURE__*/ (function () { + function WindowOperator(windowBoundaries) { + this.windowBoundaries = windowBoundaries; + } + WindowOperator.prototype.call = function (subscriber, source) { + var windowSubscriber = new WindowSubscriber(subscriber); + var sourceSubscription = source.subscribe(windowSubscriber); + if (!sourceSubscription.closed) { + windowSubscriber.add(Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_3__["subscribeToResult"])(windowSubscriber, this.windowBoundaries)); + } + return sourceSubscription; + }; + return WindowOperator; +}()); +var WindowSubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](WindowSubscriber, _super); + function WindowSubscriber(destination) { + var _this = _super.call(this, destination) || this; + _this.window = new _Subject__WEBPACK_IMPORTED_MODULE_1__["Subject"](); + destination.next(_this.window); + return _this; + } + WindowSubscriber.prototype.notifyNext = function (outerValue, innerValue, outerIndex, innerIndex, innerSub) { + this.openWindow(); + }; + WindowSubscriber.prototype.notifyError = function (error, innerSub) { + this._error(error); + }; + WindowSubscriber.prototype.notifyComplete = function (innerSub) { + this._complete(); + }; + WindowSubscriber.prototype._next = function (value) { + this.window.next(value); + }; + WindowSubscriber.prototype._error = function (err) { + this.window.error(err); + this.destination.error(err); + }; + WindowSubscriber.prototype._complete = function () { + this.window.complete(); + this.destination.complete(); + }; + WindowSubscriber.prototype._unsubscribe = function () { + this.window = null; + }; + WindowSubscriber.prototype.openWindow = function () { + var prevWindow = this.window; + if (prevWindow) { + prevWindow.complete(); + } + var destination = this.destination; + var newWindow = this.window = new _Subject__WEBPACK_IMPORTED_MODULE_1__["Subject"](); + destination.next(newWindow); + }; + return WindowSubscriber; +}(_OuterSubscriber__WEBPACK_IMPORTED_MODULE_2__["OuterSubscriber"])); +//# sourceMappingURL=window.js.map + + +/***/ }), +/* 340 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "windowCount", function() { return windowCount; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(172); +/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(265); +/** PURE_IMPORTS_START tslib,_Subscriber,_Subject PURE_IMPORTS_END */ + + + +function windowCount(windowSize, startWindowEvery) { + if (startWindowEvery === void 0) { + startWindowEvery = 0; + } + return function windowCountOperatorFunction(source) { + return source.lift(new WindowCountOperator(windowSize, startWindowEvery)); + }; +} +var WindowCountOperator = /*@__PURE__*/ (function () { + function WindowCountOperator(windowSize, startWindowEvery) { + this.windowSize = windowSize; + this.startWindowEvery = startWindowEvery; + } + WindowCountOperator.prototype.call = function (subscriber, source) { + return source.subscribe(new WindowCountSubscriber(subscriber, this.windowSize, this.startWindowEvery)); + }; + return WindowCountOperator; +}()); +var WindowCountSubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](WindowCountSubscriber, _super); + function WindowCountSubscriber(destination, windowSize, startWindowEvery) { + var _this = _super.call(this, destination) || this; + _this.destination = destination; + _this.windowSize = windowSize; + _this.startWindowEvery = startWindowEvery; + _this.windows = [new _Subject__WEBPACK_IMPORTED_MODULE_2__["Subject"]()]; + _this.count = 0; + destination.next(_this.windows[0]); + return _this; + } + WindowCountSubscriber.prototype._next = function (value) { + var startWindowEvery = (this.startWindowEvery > 0) ? this.startWindowEvery : this.windowSize; + var destination = this.destination; + var windowSize = this.windowSize; + var windows = this.windows; + var len = windows.length; + for (var i = 0; i < len && !this.closed; i++) { + windows[i].next(value); + } + var c = this.count - windowSize + 1; + if (c >= 0 && c % startWindowEvery === 0 && !this.closed) { + windows.shift().complete(); + } + if (++this.count % startWindowEvery === 0 && !this.closed) { + var window_1 = new _Subject__WEBPACK_IMPORTED_MODULE_2__["Subject"](); + windows.push(window_1); + destination.next(window_1); + } + }; + WindowCountSubscriber.prototype._error = function (err) { + var windows = this.windows; + if (windows) { + while (windows.length > 0 && !this.closed) { + windows.shift().error(err); + } + } + this.destination.error(err); + }; + WindowCountSubscriber.prototype._complete = function () { + var windows = this.windows; + if (windows) { + while (windows.length > 0 && !this.closed) { + windows.shift().complete(); + } + } + this.destination.complete(); + }; + WindowCountSubscriber.prototype._unsubscribe = function () { + this.count = 0; + this.windows = null; + }; + return WindowCountSubscriber; +}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); +//# sourceMappingURL=windowCount.js.map + + +/***/ }), +/* 341 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "windowTime", function() { return windowTime; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(265); +/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(199); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(172); +/* harmony import */ var _util_isNumeric__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(205); +/* harmony import */ var _util_isScheduler__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(206); +/** PURE_IMPORTS_START tslib,_Subject,_scheduler_async,_Subscriber,_util_isNumeric,_util_isScheduler PURE_IMPORTS_END */ + + + + + + +function windowTime(windowTimeSpan) { + var scheduler = _scheduler_async__WEBPACK_IMPORTED_MODULE_2__["async"]; + var windowCreationInterval = null; + var maxWindowSize = Number.POSITIVE_INFINITY; + if (Object(_util_isScheduler__WEBPACK_IMPORTED_MODULE_5__["isScheduler"])(arguments[3])) { + scheduler = arguments[3]; + } + if (Object(_util_isScheduler__WEBPACK_IMPORTED_MODULE_5__["isScheduler"])(arguments[2])) { + scheduler = arguments[2]; + } + else if (Object(_util_isNumeric__WEBPACK_IMPORTED_MODULE_4__["isNumeric"])(arguments[2])) { + maxWindowSize = arguments[2]; + } + if (Object(_util_isScheduler__WEBPACK_IMPORTED_MODULE_5__["isScheduler"])(arguments[1])) { + scheduler = arguments[1]; + } + else if (Object(_util_isNumeric__WEBPACK_IMPORTED_MODULE_4__["isNumeric"])(arguments[1])) { + windowCreationInterval = arguments[1]; + } + return function windowTimeOperatorFunction(source) { + return source.lift(new WindowTimeOperator(windowTimeSpan, windowCreationInterval, maxWindowSize, scheduler)); + }; +} +var WindowTimeOperator = /*@__PURE__*/ (function () { + function WindowTimeOperator(windowTimeSpan, windowCreationInterval, maxWindowSize, scheduler) { + this.windowTimeSpan = windowTimeSpan; + this.windowCreationInterval = windowCreationInterval; + this.maxWindowSize = maxWindowSize; + this.scheduler = scheduler; + } + WindowTimeOperator.prototype.call = function (subscriber, source) { + return source.subscribe(new WindowTimeSubscriber(subscriber, this.windowTimeSpan, this.windowCreationInterval, this.maxWindowSize, this.scheduler)); + }; + return WindowTimeOperator; +}()); +var CountedSubject = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](CountedSubject, _super); + function CountedSubject() { + var _this = _super !== null && _super.apply(this, arguments) || this; + _this._numberOfNextedValues = 0; + return _this; + } + CountedSubject.prototype.next = function (value) { + this._numberOfNextedValues++; + _super.prototype.next.call(this, value); + }; + Object.defineProperty(CountedSubject.prototype, "numberOfNextedValues", { + get: function () { + return this._numberOfNextedValues; + }, + enumerable: true, + configurable: true + }); + return CountedSubject; +}(_Subject__WEBPACK_IMPORTED_MODULE_1__["Subject"])); +var WindowTimeSubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](WindowTimeSubscriber, _super); + function WindowTimeSubscriber(destination, windowTimeSpan, windowCreationInterval, maxWindowSize, scheduler) { + var _this = _super.call(this, destination) || this; + _this.destination = destination; + _this.windowTimeSpan = windowTimeSpan; + _this.windowCreationInterval = windowCreationInterval; + _this.maxWindowSize = maxWindowSize; + _this.scheduler = scheduler; + _this.windows = []; + var window = _this.openWindow(); + if (windowCreationInterval !== null && windowCreationInterval >= 0) { + var closeState = { subscriber: _this, window: window, context: null }; + var creationState = { windowTimeSpan: windowTimeSpan, windowCreationInterval: windowCreationInterval, subscriber: _this, scheduler: scheduler }; + _this.add(scheduler.schedule(dispatchWindowClose, windowTimeSpan, closeState)); + _this.add(scheduler.schedule(dispatchWindowCreation, windowCreationInterval, creationState)); + } + else { + var timeSpanOnlyState = { subscriber: _this, window: window, windowTimeSpan: windowTimeSpan }; + _this.add(scheduler.schedule(dispatchWindowTimeSpanOnly, windowTimeSpan, timeSpanOnlyState)); + } + return _this; + } + WindowTimeSubscriber.prototype._next = function (value) { + var windows = this.windows; + var len = windows.length; + for (var i = 0; i < len; i++) { + var window_1 = windows[i]; + if (!window_1.closed) { + window_1.next(value); + if (window_1.numberOfNextedValues >= this.maxWindowSize) { + this.closeWindow(window_1); + } + } + } + }; + WindowTimeSubscriber.prototype._error = function (err) { + var windows = this.windows; + while (windows.length > 0) { + windows.shift().error(err); + } + this.destination.error(err); + }; + WindowTimeSubscriber.prototype._complete = function () { + var windows = this.windows; + while (windows.length > 0) { + var window_2 = windows.shift(); + if (!window_2.closed) { + window_2.complete(); + } + } + this.destination.complete(); + }; + WindowTimeSubscriber.prototype.openWindow = function () { + var window = new CountedSubject(); + this.windows.push(window); + var destination = this.destination; + destination.next(window); + return window; + }; + WindowTimeSubscriber.prototype.closeWindow = function (window) { + window.complete(); + var windows = this.windows; + windows.splice(windows.indexOf(window), 1); + }; + return WindowTimeSubscriber; +}(_Subscriber__WEBPACK_IMPORTED_MODULE_3__["Subscriber"])); +function dispatchWindowTimeSpanOnly(state) { + var subscriber = state.subscriber, windowTimeSpan = state.windowTimeSpan, window = state.window; + if (window) { + subscriber.closeWindow(window); + } + state.window = subscriber.openWindow(); + this.schedule(state, windowTimeSpan); +} +function dispatchWindowCreation(state) { + var windowTimeSpan = state.windowTimeSpan, subscriber = state.subscriber, scheduler = state.scheduler, windowCreationInterval = state.windowCreationInterval; + var window = subscriber.openWindow(); + var action = this; + var context = { action: action, subscription: null }; + var timeSpanState = { subscriber: subscriber, window: window, context: context }; + context.subscription = scheduler.schedule(dispatchWindowClose, windowTimeSpan, timeSpanState); + action.add(context.subscription); + action.schedule(state, windowCreationInterval); +} +function dispatchWindowClose(state) { + var subscriber = state.subscriber, window = state.window, context = state.context; + if (context && context.action && context.subscription) { + context.action.remove(context.subscription); + } + subscriber.closeWindow(window); +} +//# sourceMappingURL=windowTime.js.map + + +/***/ }), +/* 342 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "windowToggle", function() { return windowToggle; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(265); +/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(177); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(171); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(182); +/** PURE_IMPORTS_START tslib,_Subject,_Subscription,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ + + + + + +function windowToggle(openings, closingSelector) { + return function (source) { return source.lift(new WindowToggleOperator(openings, closingSelector)); }; +} +var WindowToggleOperator = /*@__PURE__*/ (function () { + function WindowToggleOperator(openings, closingSelector) { + this.openings = openings; + this.closingSelector = closingSelector; + } + WindowToggleOperator.prototype.call = function (subscriber, source) { + return source.subscribe(new WindowToggleSubscriber(subscriber, this.openings, this.closingSelector)); + }; + return WindowToggleOperator; +}()); +var WindowToggleSubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](WindowToggleSubscriber, _super); + function WindowToggleSubscriber(destination, openings, closingSelector) { + var _this = _super.call(this, destination) || this; + _this.openings = openings; + _this.closingSelector = closingSelector; + _this.contexts = []; + _this.add(_this.openSubscription = Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_4__["subscribeToResult"])(_this, openings, openings)); + return _this; + } + WindowToggleSubscriber.prototype._next = function (value) { + var contexts = this.contexts; + if (contexts) { + var len = contexts.length; + for (var i = 0; i < len; i++) { + contexts[i].window.next(value); + } + } + }; + WindowToggleSubscriber.prototype._error = function (err) { + var contexts = this.contexts; + this.contexts = null; + if (contexts) { + var len = contexts.length; + var index = -1; + while (++index < len) { + var context_1 = contexts[index]; + context_1.window.error(err); + context_1.subscription.unsubscribe(); + } + } + _super.prototype._error.call(this, err); + }; + WindowToggleSubscriber.prototype._complete = function () { + var contexts = this.contexts; + this.contexts = null; + if (contexts) { + var len = contexts.length; + var index = -1; + while (++index < len) { + var context_2 = contexts[index]; + context_2.window.complete(); + context_2.subscription.unsubscribe(); + } + } + _super.prototype._complete.call(this); + }; + WindowToggleSubscriber.prototype._unsubscribe = function () { + var contexts = this.contexts; + this.contexts = null; + if (contexts) { + var len = contexts.length; + var index = -1; + while (++index < len) { + var context_3 = contexts[index]; + context_3.window.unsubscribe(); + context_3.subscription.unsubscribe(); + } + } + }; + WindowToggleSubscriber.prototype.notifyNext = function (outerValue, innerValue, outerIndex, innerIndex, innerSub) { + if (outerValue === this.openings) { + var closingNotifier = void 0; + try { + var closingSelector = this.closingSelector; + closingNotifier = closingSelector(innerValue); + } + catch (e) { + return this.error(e); + } + var window_1 = new _Subject__WEBPACK_IMPORTED_MODULE_1__["Subject"](); + var subscription = new _Subscription__WEBPACK_IMPORTED_MODULE_2__["Subscription"](); + var context_4 = { window: window_1, subscription: subscription }; + this.contexts.push(context_4); + var innerSubscription = Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_4__["subscribeToResult"])(this, closingNotifier, context_4); + if (innerSubscription.closed) { + this.closeWindow(this.contexts.length - 1); + } + else { + innerSubscription.context = context_4; + subscription.add(innerSubscription); + } + this.destination.next(window_1); + } + else { + this.closeWindow(this.contexts.indexOf(outerValue)); + } + }; + WindowToggleSubscriber.prototype.notifyError = function (err) { + this.error(err); + }; + WindowToggleSubscriber.prototype.notifyComplete = function (inner) { + if (inner !== this.openSubscription) { + this.closeWindow(this.contexts.indexOf(inner.context)); + } + }; + WindowToggleSubscriber.prototype.closeWindow = function (index) { + if (index === -1) { + return; + } + var contexts = this.contexts; + var context = contexts[index]; + var window = context.window, subscription = context.subscription; + contexts.splice(index, 1); + window.complete(); + subscription.unsubscribe(); + }; + return WindowToggleSubscriber; +}(_OuterSubscriber__WEBPACK_IMPORTED_MODULE_3__["OuterSubscriber"])); +//# sourceMappingURL=windowToggle.js.map + + +/***/ }), +/* 343 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "windowWhen", function() { return windowWhen; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(265); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(171); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(182); +/** PURE_IMPORTS_START tslib,_Subject,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ + + + + +function windowWhen(closingSelector) { + return function windowWhenOperatorFunction(source) { + return source.lift(new WindowOperator(closingSelector)); + }; +} +var WindowOperator = /*@__PURE__*/ (function () { + function WindowOperator(closingSelector) { + this.closingSelector = closingSelector; + } + WindowOperator.prototype.call = function (subscriber, source) { + return source.subscribe(new WindowSubscriber(subscriber, this.closingSelector)); + }; + return WindowOperator; +}()); +var WindowSubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](WindowSubscriber, _super); + function WindowSubscriber(destination, closingSelector) { + var _this = _super.call(this, destination) || this; + _this.destination = destination; + _this.closingSelector = closingSelector; + _this.openWindow(); + return _this; + } + WindowSubscriber.prototype.notifyNext = function (outerValue, innerValue, outerIndex, innerIndex, innerSub) { + this.openWindow(innerSub); + }; + WindowSubscriber.prototype.notifyError = function (error, innerSub) { + this._error(error); + }; + WindowSubscriber.prototype.notifyComplete = function (innerSub) { + this.openWindow(innerSub); + }; + WindowSubscriber.prototype._next = function (value) { + this.window.next(value); + }; + WindowSubscriber.prototype._error = function (err) { + this.window.error(err); + this.destination.error(err); + this.unsubscribeClosingNotification(); + }; + WindowSubscriber.prototype._complete = function () { + this.window.complete(); + this.destination.complete(); + this.unsubscribeClosingNotification(); + }; + WindowSubscriber.prototype.unsubscribeClosingNotification = function () { + if (this.closingNotification) { + this.closingNotification.unsubscribe(); + } + }; + WindowSubscriber.prototype.openWindow = function (innerSub) { + if (innerSub === void 0) { + innerSub = null; + } + if (innerSub) { + this.remove(innerSub); + innerSub.unsubscribe(); + } + var prevWindow = this.window; + if (prevWindow) { + prevWindow.complete(); + } + var window = this.window = new _Subject__WEBPACK_IMPORTED_MODULE_1__["Subject"](); + this.destination.next(window); + var closingNotifier; + try { + var closingSelector = this.closingSelector; + closingNotifier = closingSelector(); + } + catch (e) { + this.destination.error(e); + this.window.error(e); + return; + } + this.add(this.closingNotification = Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_3__["subscribeToResult"])(this, closingNotifier)); + }; + return WindowSubscriber; +}(_OuterSubscriber__WEBPACK_IMPORTED_MODULE_2__["OuterSubscriber"])); +//# sourceMappingURL=windowWhen.js.map + + +/***/ }), +/* 344 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "withLatestFrom", function() { return withLatestFrom; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(171); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(182); +/** PURE_IMPORTS_START tslib,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ + + + +function withLatestFrom() { + var args = []; + for (var _i = 0; _i < arguments.length; _i++) { + args[_i] = arguments[_i]; + } + return function (source) { + var project; + if (typeof args[args.length - 1] === 'function') { + project = args.pop(); + } + var observables = args; + return source.lift(new WithLatestFromOperator(observables, project)); + }; +} +var WithLatestFromOperator = /*@__PURE__*/ (function () { + function WithLatestFromOperator(observables, project) { + this.observables = observables; + this.project = project; + } + WithLatestFromOperator.prototype.call = function (subscriber, source) { + return source.subscribe(new WithLatestFromSubscriber(subscriber, this.observables, this.project)); + }; + return WithLatestFromOperator; +}()); +var WithLatestFromSubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](WithLatestFromSubscriber, _super); + function WithLatestFromSubscriber(destination, observables, project) { + var _this = _super.call(this, destination) || this; + _this.observables = observables; + _this.project = project; + _this.toRespond = []; + var len = observables.length; + _this.values = new Array(len); + for (var i = 0; i < len; i++) { + _this.toRespond.push(i); + } + for (var i = 0; i < len; i++) { + var observable = observables[i]; + _this.add(Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__["subscribeToResult"])(_this, observable, observable, i)); + } + return _this; + } + WithLatestFromSubscriber.prototype.notifyNext = function (outerValue, innerValue, outerIndex, innerIndex, innerSub) { + this.values[outerIndex] = innerValue; + var toRespond = this.toRespond; + if (toRespond.length > 0) { + var found = toRespond.indexOf(outerIndex); + if (found !== -1) { + toRespond.splice(found, 1); + } + } + }; + WithLatestFromSubscriber.prototype.notifyComplete = function () { + }; + WithLatestFromSubscriber.prototype._next = function (value) { + if (this.toRespond.length === 0) { + var args = [value].concat(this.values); + if (this.project) { + this._tryProject(args); + } + else { + this.destination.next(args); + } + } + }; + WithLatestFromSubscriber.prototype._tryProject = function (args) { + var result; + try { + result = this.project.apply(this, args); + } + catch (err) { + this.destination.error(err); + return; + } + this.destination.next(result); + }; + return WithLatestFromSubscriber; +}(_OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__["OuterSubscriber"])); +//# sourceMappingURL=withLatestFrom.js.map + + +/***/ }), +/* 345 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "zip", function() { return zip; }); +/* harmony import */ var _observable_zip__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(346); +/** PURE_IMPORTS_START _observable_zip PURE_IMPORTS_END */ + +function zip() { + var observables = []; + for (var _i = 0; _i < arguments.length; _i++) { + observables[_i] = arguments[_i]; + } + return function zipOperatorFunction(source) { + return source.lift.call(_observable_zip__WEBPACK_IMPORTED_MODULE_0__["zip"].apply(void 0, [source].concat(observables))); + }; +} +//# sourceMappingURL=zip.js.map + + +/***/ }), +/* 346 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "zip", function() { return zip; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ZipOperator", function() { return ZipOperator; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ZipSubscriber", function() { return ZipSubscriber; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _fromArray__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(215); +/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(178); +/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(172); +/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(171); +/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(182); +/* harmony import */ var _internal_symbol_iterator__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(188); +/** PURE_IMPORTS_START tslib,_fromArray,_util_isArray,_Subscriber,_OuterSubscriber,_util_subscribeToResult,_.._internal_symbol_iterator PURE_IMPORTS_END */ + + + + + + + +function zip() { + var observables = []; + for (var _i = 0; _i < arguments.length; _i++) { + observables[_i] = arguments[_i]; + } + var resultSelector = observables[observables.length - 1]; + if (typeof resultSelector === 'function') { + observables.pop(); + } + return Object(_fromArray__WEBPACK_IMPORTED_MODULE_1__["fromArray"])(observables, undefined).lift(new ZipOperator(resultSelector)); +} +var ZipOperator = /*@__PURE__*/ (function () { + function ZipOperator(resultSelector) { + this.resultSelector = resultSelector; + } + ZipOperator.prototype.call = function (subscriber, source) { + return source.subscribe(new ZipSubscriber(subscriber, this.resultSelector)); + }; + return ZipOperator; +}()); + +var ZipSubscriber = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](ZipSubscriber, _super); + function ZipSubscriber(destination, resultSelector, values) { + if (values === void 0) { + values = Object.create(null); + } + var _this = _super.call(this, destination) || this; + _this.iterators = []; + _this.active = 0; + _this.resultSelector = (typeof resultSelector === 'function') ? resultSelector : null; + _this.values = values; + return _this; + } + ZipSubscriber.prototype._next = function (value) { + var iterators = this.iterators; + if (Object(_util_isArray__WEBPACK_IMPORTED_MODULE_2__["isArray"])(value)) { + iterators.push(new StaticArrayIterator(value)); + } + else if (typeof value[_internal_symbol_iterator__WEBPACK_IMPORTED_MODULE_6__["iterator"]] === 'function') { + iterators.push(new StaticIterator(value[_internal_symbol_iterator__WEBPACK_IMPORTED_MODULE_6__["iterator"]]())); + } + else { + iterators.push(new ZipBufferIterator(this.destination, this, value)); + } + }; + ZipSubscriber.prototype._complete = function () { + var iterators = this.iterators; + var len = iterators.length; + this.unsubscribe(); + if (len === 0) { + this.destination.complete(); + return; + } + this.active = len; + for (var i = 0; i < len; i++) { + var iterator = iterators[i]; + if (iterator.stillUnsubscribed) { + var destination = this.destination; + destination.add(iterator.subscribe(iterator, i)); + } + else { + this.active--; + } + } + }; + ZipSubscriber.prototype.notifyInactive = function () { + this.active--; + if (this.active === 0) { + this.destination.complete(); + } + }; + ZipSubscriber.prototype.checkIterators = function () { + var iterators = this.iterators; + var len = iterators.length; + var destination = this.destination; + for (var i = 0; i < len; i++) { + var iterator = iterators[i]; + if (typeof iterator.hasValue === 'function' && !iterator.hasValue()) { + return; + } + } + var shouldComplete = false; + var args = []; + for (var i = 0; i < len; i++) { + var iterator = iterators[i]; + var result = iterator.next(); + if (iterator.hasCompleted()) { + shouldComplete = true; + } + if (result.done) { + destination.complete(); + return; + } + args.push(result.value); + } + if (this.resultSelector) { + this._tryresultSelector(args); + } + else { + destination.next(args); + } + if (shouldComplete) { + destination.complete(); + } + }; + ZipSubscriber.prototype._tryresultSelector = function (args) { + var result; + try { + result = this.resultSelector.apply(this, args); + } + catch (err) { + this.destination.error(err); + return; + } + this.destination.next(result); + }; + return ZipSubscriber; +}(_Subscriber__WEBPACK_IMPORTED_MODULE_3__["Subscriber"])); + +var StaticIterator = /*@__PURE__*/ (function () { + function StaticIterator(iterator) { + this.iterator = iterator; + this.nextResult = iterator.next(); + } + StaticIterator.prototype.hasValue = function () { + return true; + }; + StaticIterator.prototype.next = function () { + var result = this.nextResult; + this.nextResult = this.iterator.next(); + return result; + }; + StaticIterator.prototype.hasCompleted = function () { + var nextResult = this.nextResult; + return nextResult && nextResult.done; + }; + return StaticIterator; +}()); +var StaticArrayIterator = /*@__PURE__*/ (function () { + function StaticArrayIterator(array) { + this.array = array; + this.index = 0; + this.length = 0; + this.length = array.length; + } + StaticArrayIterator.prototype[_internal_symbol_iterator__WEBPACK_IMPORTED_MODULE_6__["iterator"]] = function () { + return this; + }; + StaticArrayIterator.prototype.next = function (value) { + var i = this.index++; + var array = this.array; + return i < this.length ? { value: array[i], done: false } : { value: null, done: true }; + }; + StaticArrayIterator.prototype.hasValue = function () { + return this.array.length > this.index; + }; + StaticArrayIterator.prototype.hasCompleted = function () { + return this.array.length === this.index; + }; + return StaticArrayIterator; +}()); +var ZipBufferIterator = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](ZipBufferIterator, _super); + function ZipBufferIterator(destination, parent, observable) { + var _this = _super.call(this, destination) || this; + _this.parent = parent; + _this.observable = observable; + _this.stillUnsubscribed = true; + _this.buffer = []; + _this.isComplete = false; + return _this; + } + ZipBufferIterator.prototype[_internal_symbol_iterator__WEBPACK_IMPORTED_MODULE_6__["iterator"]] = function () { + return this; + }; + ZipBufferIterator.prototype.next = function () { + var buffer = this.buffer; + if (buffer.length === 0 && this.isComplete) { + return { value: null, done: true }; + } + else { + return { value: buffer.shift(), done: false }; + } + }; + ZipBufferIterator.prototype.hasValue = function () { + return this.buffer.length > 0; + }; + ZipBufferIterator.prototype.hasCompleted = function () { + return this.buffer.length === 0 && this.isComplete; + }; + ZipBufferIterator.prototype.notifyComplete = function () { + if (this.buffer.length > 0) { + this.isComplete = true; + this.parent.notifyInactive(); + } + else { + this.destination.complete(); + } + }; + ZipBufferIterator.prototype.notifyNext = function (outerValue, innerValue, outerIndex, innerIndex, innerSub) { + this.buffer.push(innerValue); + this.parent.checkIterators(); + }; + ZipBufferIterator.prototype.subscribe = function (value, index) { + return Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_5__["subscribeToResult"])(this, this.observable, this, index); + }; + return ZipBufferIterator; +}(_OuterSubscriber__WEBPACK_IMPORTED_MODULE_4__["OuterSubscriber"])); +//# sourceMappingURL=zip.js.map + + +/***/ }), +/* 347 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "zipAll", function() { return zipAll; }); +/* harmony import */ var _observable_zip__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(346); +/** PURE_IMPORTS_START _observable_zip PURE_IMPORTS_END */ + +function zipAll(project) { + return function (source) { return source.lift(new _observable_zip__WEBPACK_IMPORTED_MODULE_0__["ZipOperator"](project)); }; +} +//# sourceMappingURL=zipAll.js.map + + +/***/ }), +/* 348 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +const callbacks = new Set(); +let called = false; + +function exit(exit, signal) { + if (called) { + return; + } + + called = true; + + for (const callback of callbacks) { + callback(); + } + + if (exit === true) { + process.exit(128 + signal); // eslint-disable-line unicorn/no-process-exit + } +} + +module.exports = callback => { + callbacks.add(callback); + + if (callbacks.size === 1) { + process.once('exit', exit); + process.once('SIGINT', exit.bind(null, true, 2)); + process.once('SIGTERM', exit.bind(null, true, 15)); + + // PM2 Cluster shutdown message. Caught to support async handlers with pm2, needed because + // explicitly calling process.exit() doesn't trigger the beforeExit event, and the exit + // event cannot support async handlers, since the event loop is never called after it. + process.on('message', message => { + if (message === 'shutdown') { + exit(true, -128); + } + }); + } + + return () => { + callbacks.delete(callback); + }; +}; + + +/***/ }), +/* 349 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +Object.defineProperty(exports, "__esModule", { value: true }); +const $isCliError = Symbol('isCliError'); +function createCliError(message) { + const error = new Error(message); + error[$isCliError] = true; + return error; +} +exports.createCliError = createCliError; +function isCliError(error) { + return error && !!error[$isCliError]; +} +exports.isCliError = isCliError; + + +/***/ }), +/* 350 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +Object.defineProperty(exports, "__esModule", { value: true }); +const tslib_1 = __webpack_require__(36); +const execa_1 = tslib_1.__importDefault(__webpack_require__(351)); +const fs_1 = __webpack_require__(23); +const Rx = tslib_1.__importStar(__webpack_require__(392)); +const operators_1 = __webpack_require__(169); +const chalk_1 = tslib_1.__importDefault(__webpack_require__(2)); +const tree_kill_1 = tslib_1.__importDefault(__webpack_require__(412)); +const util_1 = __webpack_require__(29); +const treeKillAsync = util_1.promisify((...args) => tree_kill_1.default(...args)); +const observe_lines_1 = __webpack_require__(413); +const errors_1 = __webpack_require__(349); +const SECOND = 1000; +const STOP_TIMEOUT = 30 * SECOND; +async function withTimeout(attempt, ms, onTimeout) { + const TIMEOUT = Symbol('timeout'); + try { + await Promise.race([ + attempt(), + new Promise((_, reject) => setTimeout(() => reject(TIMEOUT), ms)), + ]); + } + catch (error) { + if (error === TIMEOUT) { + await onTimeout(); + } + else { + throw error; + } + } +} +function startProc(name, options, log) { + const { cmd, args, cwd, env, stdin } = options; + log.info('[%s] > %s', name, cmd, args.join(' ')); + // spawn fails with ENOENT when either the + // cmd or cwd don't exist, so we check for the cwd + // ahead of time so that the error is less ambiguous + try { + if (!fs_1.statSync(cwd).isDirectory()) { + throw new Error(`cwd "${cwd}" exists but is not a directory`); + } + } + catch (err) { + if (err.code === 'ENOENT') { + throw new Error(`cwd "${cwd}" does not exist`); + } + } + const childProcess = execa_1.default(cmd, args, { + cwd, + env, + stdio: ['pipe', 'pipe', 'pipe'], + preferLocal: true, + }); + if (stdin) { + childProcess.stdin.end(stdin, 'utf8'); + } + else { + childProcess.stdin.end(); + } + let stopCalled = false; + const outcome$ = Rx.race( + // observe first exit event + Rx.fromEvent(childProcess, 'exit').pipe(operators_1.take(1), operators_1.map(([code]) => { + if (stopCalled) { + return null; + } + // JVM exits with 143 on SIGTERM and 130 on SIGINT, dont' treat then as errors + if (code > 0 && !(code === 143 || code === 130)) { + throw errors_1.createCliError(`[${name}] exited with code ${code}`); + } + return code; + })), + // observe first error event + Rx.fromEvent(childProcess, 'error').pipe(operators_1.take(1), operators_1.mergeMap(err => Rx.throwError(err)))).pipe(operators_1.share()); + const lines$ = Rx.merge(observe_lines_1.observeLines(childProcess.stdout), observe_lines_1.observeLines(childProcess.stderr)).pipe(operators_1.tap(line => log.write(` ${chalk_1.default.gray('proc')} [${chalk_1.default.gray(name)}] ${line}`)), operators_1.share()); + const outcomePromise = Rx.merge(lines$.pipe(operators_1.ignoreElements()), outcome$).toPromise(); + async function stop(signal) { + if (stopCalled) { + return; + } + stopCalled = true; + await withTimeout(async () => { + log.debug(`Sending "${signal}" to proc "${name}"`); + await treeKillAsync(childProcess.pid, signal); + await outcomePromise; + }, STOP_TIMEOUT, async () => { + log.warning(`Proc "${name}" was sent "${signal}" didn't emit the "exit" or "error" events after ${STOP_TIMEOUT} ms, sending SIGKILL`); + await treeKillAsync(childProcess.pid, 'SIGKILL'); + }); + await withTimeout(async () => { + try { + await outcomePromise; + } + catch (error) { + // ignore + } + }, STOP_TIMEOUT, async () => { + throw new Error(`Proc "${name}" was stopped but never emitted either the "exit" or "error" event after ${STOP_TIMEOUT} ms`); + }); + } + return { + name, + lines$, + outcome$, + outcomePromise, + stop, + }; +} +exports.startProc = startProc; + + +/***/ }), +/* 351 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +const path = __webpack_require__(16); +const childProcess = __webpack_require__(352); +const crossSpawn = __webpack_require__(353); +const stripFinalNewline = __webpack_require__(366); +const npmRunPath = __webpack_require__(367); +const onetime = __webpack_require__(368); +const makeError = __webpack_require__(370); +const normalizeStdio = __webpack_require__(375); +const {spawnedKill, spawnedCancel, setupTimeout, setExitHandler} = __webpack_require__(376); +const {handleInput, getSpawnedResult, makeAllStream, validateInputSync} = __webpack_require__(381); +const {mergePromise, getSpawnedPromise} = __webpack_require__(390); +const {joinCommand, parseCommand} = __webpack_require__(391); + +const DEFAULT_MAX_BUFFER = 1000 * 1000 * 100; + +const getEnv = ({env: envOption, extendEnv, preferLocal, localDir, execPath}) => { + const env = extendEnv ? {...process.env, ...envOption} : envOption; + + if (preferLocal) { + return npmRunPath.env({env, cwd: localDir, execPath}); + } + + return env; +}; + +const handleArgs = (file, args, options = {}) => { + const parsed = crossSpawn._parse(file, args, options); + file = parsed.command; + args = parsed.args; + options = parsed.options; + + options = { + maxBuffer: DEFAULT_MAX_BUFFER, + buffer: true, + stripFinalNewline: true, + extendEnv: true, + preferLocal: false, + localDir: options.cwd || process.cwd(), + execPath: process.execPath, + encoding: 'utf8', + reject: true, + cleanup: true, + all: false, + ...options, + windowsHide: true + }; + + options.env = getEnv(options); + + options.stdio = normalizeStdio(options); + + if (process.platform === 'win32' && path.basename(file, '.exe') === 'cmd') { + // #116 + args.unshift('/q'); + } + + return {file, args, options, parsed}; +}; + +const handleOutput = (options, value, error) => { + if (typeof value !== 'string' && !Buffer.isBuffer(value)) { + // When `execa.sync()` errors, we normalize it to '' to mimic `execa()` + return error === undefined ? undefined : ''; + } + + if (options.stripFinalNewline) { + return stripFinalNewline(value); + } + + return value; +}; + +const execa = (file, args, options) => { + const parsed = handleArgs(file, args, options); + const command = joinCommand(file, args); + + let spawned; + try { + spawned = childProcess.spawn(parsed.file, parsed.args, parsed.options); + } catch (error) { + // Ensure the returned error is always both a promise and a child process + const dummySpawned = new childProcess.ChildProcess(); + const errorPromise = Promise.reject(makeError({ + error, + stdout: '', + stderr: '', + all: '', + command, + parsed, + timedOut: false, + isCanceled: false, + killed: false + })); + return mergePromise(dummySpawned, errorPromise); + } + + const spawnedPromise = getSpawnedPromise(spawned); + const timedPromise = setupTimeout(spawned, parsed.options, spawnedPromise); + const processDone = setExitHandler(spawned, parsed.options, timedPromise); + + const context = {isCanceled: false}; + + spawned.kill = spawnedKill.bind(null, spawned.kill.bind(spawned)); + spawned.cancel = spawnedCancel.bind(null, spawned, context); + + const handlePromise = async () => { + const [{error, exitCode, signal, timedOut}, stdoutResult, stderrResult, allResult] = await getSpawnedResult(spawned, parsed.options, processDone); + const stdout = handleOutput(parsed.options, stdoutResult); + const stderr = handleOutput(parsed.options, stderrResult); + const all = handleOutput(parsed.options, allResult); + + if (error || exitCode !== 0 || signal !== null) { + const returnedError = makeError({ + error, + exitCode, + signal, + stdout, + stderr, + all, + command, + parsed, + timedOut, + isCanceled: context.isCanceled, + killed: spawned.killed + }); + + if (!parsed.options.reject) { + return returnedError; + } + + throw returnedError; + } + + return { + command, + exitCode: 0, + stdout, + stderr, + all, + failed: false, + timedOut: false, + isCanceled: false, + killed: false + }; + }; + + const handlePromiseOnce = onetime(handlePromise); + + crossSpawn._enoent.hookChildProcess(spawned, parsed.parsed); + + handleInput(spawned, parsed.options.input); + + spawned.all = makeAllStream(spawned, parsed.options); + + return mergePromise(spawned, handlePromiseOnce); +}; + +module.exports = execa; + +module.exports.sync = (file, args, options) => { + const parsed = handleArgs(file, args, options); + const command = joinCommand(file, args); + + validateInputSync(parsed.options); + + let result; + try { + result = childProcess.spawnSync(parsed.file, parsed.args, parsed.options); + } catch (error) { + throw makeError({ + error, + stdout: '', + stderr: '', + all: '', + command, + parsed, + timedOut: false, + isCanceled: false, + killed: false + }); + } + + const stdout = handleOutput(parsed.options, result.stdout, result.error); + const stderr = handleOutput(parsed.options, result.stderr, result.error); + + if (result.error || result.status !== 0 || result.signal !== null) { + const error = makeError({ + stdout, + stderr, + error: result.error, + signal: result.signal, + exitCode: result.status, + command, + parsed, + timedOut: result.error && result.error.code === 'ETIMEDOUT', + isCanceled: false, + killed: result.signal !== null + }); + + if (!parsed.options.reject) { + return error; + } + + throw error; + } + + return { + command, + exitCode: 0, + stdout, + stderr, + failed: false, + timedOut: false, + isCanceled: false, + killed: false + }; +}; + +module.exports.command = (command, options) => { + const [file, ...args] = parseCommand(command); + return execa(file, args, options); +}; + +module.exports.commandSync = (command, options) => { + const [file, ...args] = parseCommand(command); + return execa.sync(file, args, options); +}; + +module.exports.node = (scriptPath, args, options = {}) => { + if (args && !Array.isArray(args) && typeof args === 'object') { + options = args; + args = []; + } + + const stdio = normalizeStdio.node(options); + + const {nodePath = process.execPath, nodeOptions = process.execArgv} = options; + + return execa( + nodePath, + [ + ...nodeOptions, + scriptPath, + ...(Array.isArray(args) ? args : []) + ], + { + ...options, + stdin: undefined, + stdout: undefined, + stderr: undefined, + stdio, + shell: false + } + ); +}; + + +/***/ }), +/* 352 */ +/***/ (function(module, exports) { + +module.exports = require("child_process"); + +/***/ }), +/* 353 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +const cp = __webpack_require__(352); +const parse = __webpack_require__(354); +const enoent = __webpack_require__(365); + +function spawn(command, args, options) { + // Parse the arguments + const parsed = parse(command, args, options); + + // Spawn the child process + const spawned = cp.spawn(parsed.command, parsed.args, parsed.options); + + // Hook into child process "exit" event to emit an error if the command + // does not exists, see: https://github.com/IndigoUnited/node-cross-spawn/issues/16 + enoent.hookChildProcess(spawned, parsed); + + return spawned; +} + +function spawnSync(command, args, options) { + // Parse the arguments + const parsed = parse(command, args, options); + + // Spawn the child process + const result = cp.spawnSync(parsed.command, parsed.args, parsed.options); + + // Analyze if the command does not exist, see: https://github.com/IndigoUnited/node-cross-spawn/issues/16 + result.error = result.error || enoent.verifyENOENTSync(result.status, parsed); + + return result; +} + +module.exports = spawn; +module.exports.spawn = spawn; +module.exports.sync = spawnSync; + +module.exports._parse = parse; +module.exports._enoent = enoent; + + +/***/ }), +/* 354 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +const path = __webpack_require__(16); +const resolveCommand = __webpack_require__(355); +const escape = __webpack_require__(361); +const readShebang = __webpack_require__(362); + +const isWin = process.platform === 'win32'; +const isExecutableRegExp = /\.(?:com|exe)$/i; +const isCmdShimRegExp = /node_modules[\\/].bin[\\/][^\\/]+\.cmd$/i; + +function detectShebang(parsed) { + parsed.file = resolveCommand(parsed); + + const shebang = parsed.file && readShebang(parsed.file); + + if (shebang) { + parsed.args.unshift(parsed.file); + parsed.command = shebang; + + return resolveCommand(parsed); + } + + return parsed.file; +} + +function parseNonShell(parsed) { + if (!isWin) { + return parsed; + } + + // Detect & add support for shebangs + const commandFile = detectShebang(parsed); + + // We don't need a shell if the command filename is an executable + const needsShell = !isExecutableRegExp.test(commandFile); + + // If a shell is required, use cmd.exe and take care of escaping everything correctly + // Note that `forceShell` is an hidden option used only in tests + if (parsed.options.forceShell || needsShell) { + // Need to double escape meta chars if the command is a cmd-shim located in `node_modules/.bin/` + // The cmd-shim simply calls execute the package bin file with NodeJS, proxying any argument + // Because the escape of metachars with ^ gets interpreted when the cmd.exe is first called, + // we need to double escape them + const needsDoubleEscapeMetaChars = isCmdShimRegExp.test(commandFile); + + // Normalize posix paths into OS compatible paths (e.g.: foo/bar -> foo\bar) + // This is necessary otherwise it will always fail with ENOENT in those cases + parsed.command = path.normalize(parsed.command); + + // Escape command & arguments + parsed.command = escape.command(parsed.command); + parsed.args = parsed.args.map((arg) => escape.argument(arg, needsDoubleEscapeMetaChars)); + + const shellCommand = [parsed.command].concat(parsed.args).join(' '); + + parsed.args = ['/d', '/s', '/c', `"${shellCommand}"`]; + parsed.command = process.env.comspec || 'cmd.exe'; + parsed.options.windowsVerbatimArguments = true; // Tell node's spawn that the arguments are already escaped + } + + return parsed; +} + +function parse(command, args, options) { + // Normalize arguments, similar to nodejs + if (args && !Array.isArray(args)) { + options = args; + args = null; + } + + args = args ? args.slice(0) : []; // Clone array to avoid changing the original + options = Object.assign({}, options); // Clone object to avoid changing the original + + // Build our parsed object + const parsed = { + command, + args, + options, + file: undefined, + original: { + command, + args, + }, + }; + + // Delegate further parsing to shell or non-shell + return options.shell ? parsed : parseNonShell(parsed); +} + +module.exports = parse; + + +/***/ }), +/* 355 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +const path = __webpack_require__(16); +const which = __webpack_require__(356); +const pathKey = __webpack_require__(360)(); + +function resolveCommandAttempt(parsed, withoutPathExt) { + const cwd = process.cwd(); + const hasCustomCwd = parsed.options.cwd != null; + // Worker threads do not have process.chdir() + const shouldSwitchCwd = hasCustomCwd && process.chdir !== undefined; + + // If a custom `cwd` was specified, we need to change the process cwd + // because `which` will do stat calls but does not support a custom cwd + if (shouldSwitchCwd) { + try { + process.chdir(parsed.options.cwd); + } catch (err) { + /* Empty */ + } + } + + let resolved; + + try { + resolved = which.sync(parsed.command, { + path: (parsed.options.env || process.env)[pathKey], + pathExt: withoutPathExt ? path.delimiter : undefined, + }); + } catch (e) { + /* Empty */ + } finally { + if (shouldSwitchCwd) { + process.chdir(cwd); + } + } + + // If we successfully resolved, ensure that an absolute path is returned + // Note that when a custom `cwd` was used, we need to resolve to an absolute path based on it + if (resolved) { + resolved = path.resolve(hasCustomCwd ? parsed.options.cwd : '', resolved); + } + + return resolved; +} + +function resolveCommand(parsed) { + return resolveCommandAttempt(parsed) || resolveCommandAttempt(parsed, true); +} + +module.exports = resolveCommand; + + +/***/ }), +/* 356 */ +/***/ (function(module, exports, __webpack_require__) { + +const isWindows = process.platform === 'win32' || + process.env.OSTYPE === 'cygwin' || + process.env.OSTYPE === 'msys' + +const path = __webpack_require__(16) +const COLON = isWindows ? ';' : ':' +const isexe = __webpack_require__(357) + +const getNotFoundError = (cmd) => + Object.assign(new Error(`not found: ${cmd}`), { code: 'ENOENT' }) + +const getPathInfo = (cmd, opt) => { + const colon = opt.colon || COLON + + // If it has a slash, then we don't bother searching the pathenv. + // just check the file itself, and that's it. + const pathEnv = cmd.match(/\//) || isWindows && cmd.match(/\\/) ? [''] + : ( + [ + // windows always checks the cwd first + ...(isWindows ? [process.cwd()] : []), + ...(opt.path || process.env.PATH || + /* istanbul ignore next: very unusual */ '').split(colon), + ] + ) + const pathExtExe = isWindows + ? opt.pathExt || process.env.PATHEXT || '.EXE;.CMD;.BAT;.COM' + : '' + const pathExt = isWindows ? pathExtExe.split(colon) : [''] + + if (isWindows) { + if (cmd.indexOf('.') !== -1 && pathExt[0] !== '') + pathExt.unshift('') + } + + return { + pathEnv, + pathExt, + pathExtExe, + } +} + +const which = (cmd, opt, cb) => { + if (typeof opt === 'function') { + cb = opt + opt = {} + } + if (!opt) + opt = {} + + const { pathEnv, pathExt, pathExtExe } = getPathInfo(cmd, opt) + const found = [] + + const step = i => new Promise((resolve, reject) => { + if (i === pathEnv.length) + return opt.all && found.length ? resolve(found) + : reject(getNotFoundError(cmd)) + + const ppRaw = pathEnv[i] + const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw + + const pCmd = path.join(pathPart, cmd) + const p = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd + : pCmd + + resolve(subStep(p, i, 0)) + }) + + const subStep = (p, i, ii) => new Promise((resolve, reject) => { + if (ii === pathExt.length) + return resolve(step(i + 1)) + const ext = pathExt[ii] + isexe(p + ext, { pathExt: pathExtExe }, (er, is) => { + if (!er && is) { + if (opt.all) + found.push(p + ext) + else + return resolve(p + ext) + } + return resolve(subStep(p, i, ii + 1)) + }) + }) + + return cb ? step(0).then(res => cb(null, res), cb) : step(0) +} + +const whichSync = (cmd, opt) => { + opt = opt || {} + + const { pathEnv, pathExt, pathExtExe } = getPathInfo(cmd, opt) + const found = [] + + for (let i = 0; i < pathEnv.length; i ++) { + const ppRaw = pathEnv[i] + const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw + + const pCmd = path.join(pathPart, cmd) + const p = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd + : pCmd + + for (let j = 0; j < pathExt.length; j ++) { + const cur = p + pathExt[j] + try { + const is = isexe.sync(cur, { pathExt: pathExtExe }) + if (is) { + if (opt.all) + found.push(cur) + else + return cur + } + } catch (ex) {} + } + } + + if (opt.all && found.length) + return found + + if (opt.nothrow) + return null + + throw getNotFoundError(cmd) +} + +module.exports = which +which.sync = whichSync + + +/***/ }), +/* 357 */ +/***/ (function(module, exports, __webpack_require__) { + +var fs = __webpack_require__(23) +var core +if (process.platform === 'win32' || global.TESTING_WINDOWS) { + core = __webpack_require__(358) +} else { + core = __webpack_require__(359) +} + +module.exports = isexe +isexe.sync = sync + +function isexe (path, options, cb) { + if (typeof options === 'function') { + cb = options + options = {} + } + + if (!cb) { + if (typeof Promise !== 'function') { + throw new TypeError('callback not provided') + } + + return new Promise(function (resolve, reject) { + isexe(path, options || {}, function (er, is) { + if (er) { + reject(er) + } else { + resolve(is) + } + }) + }) + } + + core(path, options || {}, function (er, is) { + // ignore EACCES because that just means we aren't allowed to run it + if (er) { + if (er.code === 'EACCES' || options && options.ignoreErrors) { + er = null + is = false + } + } + cb(er, is) + }) +} + +function sync (path, options) { + // my kingdom for a filtered catch + try { + return core.sync(path, options || {}) + } catch (er) { + if (options && options.ignoreErrors || er.code === 'EACCES') { + return false + } else { + throw er + } + } +} + + +/***/ }), +/* 358 */ +/***/ (function(module, exports, __webpack_require__) { + +module.exports = isexe +isexe.sync = sync + +var fs = __webpack_require__(23) + +function checkPathExt (path, options) { + var pathext = options.pathExt !== undefined ? + options.pathExt : process.env.PATHEXT + + if (!pathext) { + return true + } + + pathext = pathext.split(';') + if (pathext.indexOf('') !== -1) { + return true + } + for (var i = 0; i < pathext.length; i++) { + var p = pathext[i].toLowerCase() + if (p && path.substr(-p.length).toLowerCase() === p) { + return true + } + } + return false +} + +function checkStat (stat, path, options) { + if (!stat.isSymbolicLink() && !stat.isFile()) { + return false + } + return checkPathExt(path, options) +} + +function isexe (path, options, cb) { + fs.stat(path, function (er, stat) { + cb(er, er ? false : checkStat(stat, path, options)) + }) +} + +function sync (path, options) { + return checkStat(fs.statSync(path), path, options) +} + + +/***/ }), +/* 359 */ +/***/ (function(module, exports, __webpack_require__) { + +module.exports = isexe +isexe.sync = sync + +var fs = __webpack_require__(23) + +function isexe (path, options, cb) { + fs.stat(path, function (er, stat) { + cb(er, er ? false : checkStat(stat, options)) + }) +} + +function sync (path, options) { + return checkStat(fs.statSync(path), options) +} + +function checkStat (stat, options) { + return stat.isFile() && checkMode(stat, options) +} + +function checkMode (stat, options) { + var mod = stat.mode + var uid = stat.uid + var gid = stat.gid + + var myUid = options.uid !== undefined ? + options.uid : process.getuid && process.getuid() + var myGid = options.gid !== undefined ? + options.gid : process.getgid && process.getgid() + + var u = parseInt('100', 8) + var g = parseInt('010', 8) + var o = parseInt('001', 8) + var ug = u | g + + var ret = (mod & o) || + (mod & g) && gid === myGid || + (mod & u) && uid === myUid || + (mod & ug) && myUid === 0 + + return ret +} + + +/***/ }), +/* 360 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +const pathKey = (options = {}) => { + const environment = options.env || process.env; + const platform = options.platform || process.platform; + + if (platform !== 'win32') { + return 'PATH'; + } + + return Object.keys(environment).find(key => key.toUpperCase() === 'PATH') || 'Path'; +}; + +module.exports = pathKey; +// TODO: Remove this for the next major release +module.exports.default = pathKey; + + +/***/ }), +/* 361 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +// See http://www.robvanderwoude.com/escapechars.php +const metaCharsRegExp = /([()\][%!^"`<>&|;, *?])/g; + +function escapeCommand(arg) { + // Escape meta chars + arg = arg.replace(metaCharsRegExp, '^$1'); + + return arg; +} + +function escapeArgument(arg, doubleEscapeMetaChars) { + // Convert to string + arg = `${arg}`; + + // Algorithm below is based on https://qntm.org/cmd + + // Sequence of backslashes followed by a double quote: + // double up all the backslashes and escape the double quote + arg = arg.replace(/(\\*)"/g, '$1$1\\"'); + + // Sequence of backslashes followed by the end of the string + // (which will become a double quote later): + // double up all the backslashes + arg = arg.replace(/(\\*)$/, '$1$1'); + + // All other backslashes occur literally + + // Quote the whole thing: + arg = `"${arg}"`; + + // Escape meta chars + arg = arg.replace(metaCharsRegExp, '^$1'); + + // Double escape meta chars if necessary + if (doubleEscapeMetaChars) { + arg = arg.replace(metaCharsRegExp, '^$1'); + } + + return arg; +} + +module.exports.command = escapeCommand; +module.exports.argument = escapeArgument; + + +/***/ }), +/* 362 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +const fs = __webpack_require__(23); +const shebangCommand = __webpack_require__(363); + +function readShebang(command) { + // Read the first 150 bytes from the file + const size = 150; + const buffer = Buffer.alloc(size); + + let fd; + + try { + fd = fs.openSync(command, 'r'); + fs.readSync(fd, buffer, 0, size, 0); + fs.closeSync(fd); + } catch (e) { /* Empty */ } + + // Attempt to extract shebang (null is returned if not a shebang) + return shebangCommand(buffer.toString()); +} + +module.exports = readShebang; + + +/***/ }), +/* 363 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +const shebangRegex = __webpack_require__(364); + +module.exports = (string = '') => { + const match = string.match(shebangRegex); + + if (!match) { + return null; + } + + const [path, argument] = match[0].replace(/#! ?/, '').split(' '); + const binary = path.split('/').pop(); + + if (binary === 'env') { + return argument; + } + + return argument ? `${binary} ${argument}` : binary; +}; + + +/***/ }), +/* 364 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +module.exports = /^#!(.*)/; + + +/***/ }), +/* 365 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +const isWin = process.platform === 'win32'; + +function notFoundError(original, syscall) { + return Object.assign(new Error(`${syscall} ${original.command} ENOENT`), { + code: 'ENOENT', + errno: 'ENOENT', + syscall: `${syscall} ${original.command}`, + path: original.command, + spawnargs: original.args, + }); +} + +function hookChildProcess(cp, parsed) { + if (!isWin) { + return; + } + + const originalEmit = cp.emit; + + cp.emit = function (name, arg1) { + // If emitting "exit" event and exit code is 1, we need to check if + // the command exists and emit an "error" instead + // See https://github.com/IndigoUnited/node-cross-spawn/issues/16 + if (name === 'exit') { + const err = verifyENOENT(arg1, parsed, 'spawn'); + + if (err) { + return originalEmit.call(cp, 'error', err); + } + } + + return originalEmit.apply(cp, arguments); // eslint-disable-line prefer-rest-params + }; +} + +function verifyENOENT(status, parsed) { + if (isWin && status === 1 && !parsed.file) { + return notFoundError(parsed.original, 'spawn'); + } + + return null; +} + +function verifyENOENTSync(status, parsed) { + if (isWin && status === 1 && !parsed.file) { + return notFoundError(parsed.original, 'spawnSync'); + } + + return null; +} + +module.exports = { + hookChildProcess, + verifyENOENT, + verifyENOENTSync, + notFoundError, +}; + + +/***/ }), +/* 366 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +module.exports = input => { + const LF = typeof input === 'string' ? '\n' : '\n'.charCodeAt(); + const CR = typeof input === 'string' ? '\r' : '\r'.charCodeAt(); + + if (input[input.length - 1] === LF) { + input = input.slice(0, input.length - 1); + } + + if (input[input.length - 1] === CR) { + input = input.slice(0, input.length - 1); + } + + return input; +}; + + +/***/ }), +/* 367 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +const path = __webpack_require__(16); +const pathKey = __webpack_require__(360); + +const npmRunPath = options => { + options = { + cwd: process.cwd(), + path: process.env[pathKey()], + execPath: process.execPath, + ...options + }; + + let previous; + let cwdPath = path.resolve(options.cwd); + const result = []; + + while (previous !== cwdPath) { + result.push(path.join(cwdPath, 'node_modules/.bin')); + previous = cwdPath; + cwdPath = path.resolve(cwdPath, '..'); + } + + // Ensure the running `node` binary is used + const execPathDir = path.resolve(options.cwd, options.execPath, '..'); + result.unshift(execPathDir); + + return result.concat(options.path).join(path.delimiter); +}; + +module.exports = npmRunPath; +// TODO: Remove this for the next major release +module.exports.default = npmRunPath; + +module.exports.env = options => { + options = { + env: process.env, + ...options + }; + + const env = {...options.env}; + const path = pathKey({env}); + + options.path = env[path]; + env[path] = module.exports(options); + + return env; +}; + + +/***/ }), +/* 368 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +const mimicFn = __webpack_require__(369); + +const calledFunctions = new WeakMap(); + +const oneTime = (fn, options = {}) => { + if (typeof fn !== 'function') { + throw new TypeError('Expected a function'); + } + + let ret; + let isCalled = false; + let callCount = 0; + const functionName = fn.displayName || fn.name || ''; + + const onetime = function (...args) { + calledFunctions.set(onetime, ++callCount); + + if (isCalled) { + if (options.throw === true) { + throw new Error(`Function \`${functionName}\` can only be called once`); + } + + return ret; + } + + isCalled = true; + ret = fn.apply(this, args); + fn = null; + + return ret; + }; + + mimicFn(onetime, fn); + calledFunctions.set(onetime, callCount); + + return onetime; +}; + +module.exports = oneTime; +// TODO: Remove this for the next major release +module.exports.default = oneTime; + +module.exports.callCount = fn => { + if (!calledFunctions.has(fn)) { + throw new Error(`The given function \`${fn.name}\` is not wrapped by the \`onetime\` package`); + } + + return calledFunctions.get(fn); +}; + + +/***/ }), +/* 369 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +const mimicFn = (to, from) => { + for (const prop of Reflect.ownKeys(from)) { + Object.defineProperty(to, prop, Object.getOwnPropertyDescriptor(from, prop)); + } + + return to; +}; + +module.exports = mimicFn; +// TODO: Remove this for the next major release +module.exports.default = mimicFn; + + +/***/ }), +/* 370 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +const {signalsByName} = __webpack_require__(371); + +const getErrorPrefix = ({timedOut, timeout, errorCode, signal, signalDescription, exitCode, isCanceled}) => { + if (timedOut) { + return `timed out after ${timeout} milliseconds`; + } + + if (isCanceled) { + return 'was canceled'; + } + + if (errorCode !== undefined) { + return `failed with ${errorCode}`; + } + + if (signal !== undefined) { + return `was killed with ${signal} (${signalDescription})`; + } + + if (exitCode !== undefined) { + return `failed with exit code ${exitCode}`; + } + + return 'failed'; +}; + +const makeError = ({ + stdout, + stderr, + all, + error, + signal, + exitCode, + command, + timedOut, + isCanceled, + killed, + parsed: {options: {timeout}} +}) => { + // `signal` and `exitCode` emitted on `spawned.on('exit')` event can be `null`. + // We normalize them to `undefined` + exitCode = exitCode === null ? undefined : exitCode; + signal = signal === null ? undefined : signal; + const signalDescription = signal === undefined ? undefined : signalsByName[signal].description; + + const errorCode = error && error.code; + + const prefix = getErrorPrefix({timedOut, timeout, errorCode, signal, signalDescription, exitCode, isCanceled}); + const message = `Command ${prefix}: ${command}`; + + if (error instanceof Error) { + error.originalMessage = error.message; + error.message = `${message}\n${error.message}`; + } else { + error = new Error(message); + } + + error.command = command; + error.exitCode = exitCode; + error.signal = signal; + error.signalDescription = signalDescription; + error.stdout = stdout; + error.stderr = stderr; + + if (all !== undefined) { + error.all = all; + } + + if ('bufferedData' in error) { + delete error.bufferedData; + } + + error.failed = true; + error.timedOut = Boolean(timedOut); + error.isCanceled = isCanceled; + error.killed = killed && !timedOut; + + return error; +}; + +module.exports = makeError; + + +/***/ }), +/* 371 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +Object.defineProperty(exports,"__esModule",{value:true});exports.signalsByNumber=exports.signalsByName=void 0;var _os=__webpack_require__(11); + +var _signals=__webpack_require__(372); +var _realtime=__webpack_require__(374); + + + +const getSignalsByName=function(){ +const signals=(0,_signals.getSignals)(); +return signals.reduce(getSignalByName,{}); +}; + +const getSignalByName=function( +signalByNameMemo, +{name,number,description,supported,action,forced,standard}) +{ +return{ +...signalByNameMemo, +[name]:{name,number,description,supported,action,forced,standard}}; + +}; + +const signalsByName=getSignalsByName();exports.signalsByName=signalsByName; + + + + +const getSignalsByNumber=function(){ +const signals=(0,_signals.getSignals)(); +const length=_realtime.SIGRTMAX+1; +const signalsA=Array.from({length},(value,number)=> +getSignalByNumber(number,signals)); + +return Object.assign({},...signalsA); +}; + +const getSignalByNumber=function(number,signals){ +const signal=findSignalByNumber(number,signals); + +if(signal===undefined){ +return{}; +} + +const{name,description,supported,action,forced,standard}=signal; +return{ +[number]:{ +name, +number, +description, +supported, +action, +forced, +standard}}; + + +}; + + + +const findSignalByNumber=function(number,signals){ +const signal=signals.find(({name})=>_os.constants.signals[name]===number); + +if(signal!==undefined){ +return signal; +} + +return signals.find(signalA=>signalA.number===number); +}; + +const signalsByNumber=getSignalsByNumber();exports.signalsByNumber=signalsByNumber; +//# sourceMappingURL=main.js.map + +/***/ }), +/* 372 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +Object.defineProperty(exports,"__esModule",{value:true});exports.getSignals=void 0;var _os=__webpack_require__(11); + +var _core=__webpack_require__(373); +var _realtime=__webpack_require__(374); + + + +const getSignals=function(){ +const realtimeSignals=(0,_realtime.getRealtimeSignals)(); +const signals=[..._core.SIGNALS,...realtimeSignals].map(normalizeSignal); +return signals; +};exports.getSignals=getSignals; + + + + + + + +const normalizeSignal=function({ +name, +number:defaultNumber, +description, +action, +forced=false, +standard}) +{ +const{ +signals:{[name]:constantSignal}}= +_os.constants; +const supported=constantSignal!==undefined; +const number=supported?constantSignal:defaultNumber; +return{name,number,description,supported,action,forced,standard}; +}; +//# sourceMappingURL=signals.js.map + +/***/ }), +/* 373 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +Object.defineProperty(exports,"__esModule",{value:true});exports.SIGNALS=void 0; + +const SIGNALS=[ +{ +name:"SIGHUP", +number:1, +action:"terminate", +description:"Terminal closed", +standard:"posix"}, + +{ +name:"SIGINT", +number:2, +action:"terminate", +description:"User interruption with CTRL-C", +standard:"ansi"}, + +{ +name:"SIGQUIT", +number:3, +action:"core", +description:"User interruption with CTRL-\\", +standard:"posix"}, + +{ +name:"SIGILL", +number:4, +action:"core", +description:"Invalid machine instruction", +standard:"ansi"}, + +{ +name:"SIGTRAP", +number:5, +action:"core", +description:"Debugger breakpoint", +standard:"posix"}, + +{ +name:"SIGABRT", +number:6, +action:"core", +description:"Aborted", +standard:"ansi"}, + +{ +name:"SIGIOT", +number:6, +action:"core", +description:"Aborted", +standard:"bsd"}, + +{ +name:"SIGBUS", +number:7, +action:"core", +description: +"Bus error due to misaligned, non-existing address or paging error", +standard:"bsd"}, + +{ +name:"SIGEMT", +number:7, +action:"terminate", +description:"Command should be emulated but is not implemented", +standard:"other"}, + +{ +name:"SIGFPE", +number:8, +action:"core", +description:"Floating point arithmetic error", +standard:"ansi"}, + +{ +name:"SIGKILL", +number:9, +action:"terminate", +description:"Forced termination", +standard:"posix", +forced:true}, + +{ +name:"SIGUSR1", +number:10, +action:"terminate", +description:"Application-specific signal", +standard:"posix"}, + +{ +name:"SIGSEGV", +number:11, +action:"core", +description:"Segmentation fault", +standard:"ansi"}, + +{ +name:"SIGUSR2", +number:12, +action:"terminate", +description:"Application-specific signal", +standard:"posix"}, + +{ +name:"SIGPIPE", +number:13, +action:"terminate", +description:"Broken pipe or socket", +standard:"posix"}, + +{ +name:"SIGALRM", +number:14, +action:"terminate", +description:"Timeout or timer", +standard:"posix"}, + +{ +name:"SIGTERM", +number:15, +action:"terminate", +description:"Termination", +standard:"ansi"}, + +{ +name:"SIGSTKFLT", +number:16, +action:"terminate", +description:"Stack is empty or overflowed", +standard:"other"}, + +{ +name:"SIGCHLD", +number:17, +action:"ignore", +description:"Child process terminated, paused or unpaused", +standard:"posix"}, + +{ +name:"SIGCLD", +number:17, +action:"ignore", +description:"Child process terminated, paused or unpaused", +standard:"other"}, + +{ +name:"SIGCONT", +number:18, +action:"unpause", +description:"Unpaused", +standard:"posix", +forced:true}, + +{ +name:"SIGSTOP", +number:19, +action:"pause", +description:"Paused", +standard:"posix", +forced:true}, + +{ +name:"SIGTSTP", +number:20, +action:"pause", +description:"Paused using CTRL-Z or \"suspend\"", +standard:"posix"}, + +{ +name:"SIGTTIN", +number:21, +action:"pause", +description:"Background process cannot read terminal input", +standard:"posix"}, + +{ +name:"SIGBREAK", +number:21, +action:"terminate", +description:"User interruption with CTRL-BREAK", +standard:"other"}, + +{ +name:"SIGTTOU", +number:22, +action:"pause", +description:"Background process cannot write to terminal output", +standard:"posix"}, + +{ +name:"SIGURG", +number:23, +action:"ignore", +description:"Socket received out-of-band data", +standard:"bsd"}, + +{ +name:"SIGXCPU", +number:24, +action:"core", +description:"Process timed out", +standard:"bsd"}, + +{ +name:"SIGXFSZ", +number:25, +action:"core", +description:"File too big", +standard:"bsd"}, + +{ +name:"SIGVTALRM", +number:26, +action:"terminate", +description:"Timeout or timer", +standard:"bsd"}, + +{ +name:"SIGPROF", +number:27, +action:"terminate", +description:"Timeout or timer", +standard:"bsd"}, + +{ +name:"SIGWINCH", +number:28, +action:"ignore", +description:"Terminal window size changed", +standard:"bsd"}, + +{ +name:"SIGIO", +number:29, +action:"terminate", +description:"I/O is available", +standard:"other"}, + +{ +name:"SIGPOLL", +number:29, +action:"terminate", +description:"Watched event", +standard:"other"}, + +{ +name:"SIGINFO", +number:29, +action:"ignore", +description:"Request for process information", +standard:"other"}, + +{ +name:"SIGPWR", +number:30, +action:"terminate", +description:"Device running out of power", +standard:"systemv"}, + +{ +name:"SIGSYS", +number:31, +action:"core", +description:"Invalid system call", +standard:"other"}, + +{ +name:"SIGUNUSED", +number:31, +action:"terminate", +description:"Invalid system call", +standard:"other"}];exports.SIGNALS=SIGNALS; +//# sourceMappingURL=core.js.map + +/***/ }), +/* 374 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +Object.defineProperty(exports,"__esModule",{value:true});exports.SIGRTMAX=exports.getRealtimeSignals=void 0; +const getRealtimeSignals=function(){ +const length=SIGRTMAX-SIGRTMIN+1; +return Array.from({length},getRealtimeSignal); +};exports.getRealtimeSignals=getRealtimeSignals; + +const getRealtimeSignal=function(value,index){ +return{ +name:`SIGRT${index+1}`, +number:SIGRTMIN+index, +action:"terminate", +description:"Application-specific signal (realtime)", +standard:"posix"}; + +}; + +const SIGRTMIN=34; +const SIGRTMAX=64;exports.SIGRTMAX=SIGRTMAX; +//# sourceMappingURL=realtime.js.map + +/***/ }), +/* 375 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +const aliases = ['stdin', 'stdout', 'stderr']; + +const hasAlias = opts => aliases.some(alias => opts[alias] !== undefined); + +const normalizeStdio = opts => { + if (!opts) { + return; + } + + const {stdio} = opts; + + if (stdio === undefined) { + return aliases.map(alias => opts[alias]); + } + + if (hasAlias(opts)) { + throw new Error(`It's not possible to provide \`stdio\` in combination with one of ${aliases.map(alias => `\`${alias}\``).join(', ')}`); + } + + if (typeof stdio === 'string') { + return stdio; + } + + if (!Array.isArray(stdio)) { + throw new TypeError(`Expected \`stdio\` to be of type \`string\` or \`Array\`, got \`${typeof stdio}\``); + } + + const length = Math.max(stdio.length, aliases.length); + return Array.from({length}, (value, index) => stdio[index]); +}; + +module.exports = normalizeStdio; + +// `ipc` is pushed unless it is already present +module.exports.node = opts => { + const stdio = normalizeStdio(opts); + + if (stdio === 'ipc') { + return 'ipc'; + } + + if (stdio === undefined || typeof stdio === 'string') { + return [stdio, stdio, stdio, 'ipc']; + } + + if (stdio.includes('ipc')) { + return stdio; + } + + return [...stdio, 'ipc']; +}; + + +/***/ }), +/* 376 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +const os = __webpack_require__(11); +const onExit = __webpack_require__(377); +const pFinally = __webpack_require__(380); + +const DEFAULT_FORCE_KILL_TIMEOUT = 1000 * 5; + +// Monkey-patches `childProcess.kill()` to add `forceKillAfterTimeout` behavior +const spawnedKill = (kill, signal = 'SIGTERM', options = {}) => { + const killResult = kill(signal); + setKillTimeout(kill, signal, options, killResult); + return killResult; +}; + +const setKillTimeout = (kill, signal, options, killResult) => { + if (!shouldForceKill(signal, options, killResult)) { + return; + } + + const timeout = getForceKillAfterTimeout(options); + setTimeout(() => { + kill('SIGKILL'); + }, timeout).unref(); +}; + +const shouldForceKill = (signal, {forceKillAfterTimeout}, killResult) => { + return isSigterm(signal) && forceKillAfterTimeout !== false && killResult; +}; + +const isSigterm = signal => { + return signal === os.constants.signals.SIGTERM || + (typeof signal === 'string' && signal.toUpperCase() === 'SIGTERM'); +}; + +const getForceKillAfterTimeout = ({forceKillAfterTimeout = true}) => { + if (forceKillAfterTimeout === true) { + return DEFAULT_FORCE_KILL_TIMEOUT; + } + + if (!Number.isInteger(forceKillAfterTimeout) || forceKillAfterTimeout < 0) { + throw new TypeError(`Expected the \`forceKillAfterTimeout\` option to be a non-negative integer, got \`${forceKillAfterTimeout}\` (${typeof forceKillAfterTimeout})`); + } + + return forceKillAfterTimeout; +}; + +// `childProcess.cancel()` +const spawnedCancel = (spawned, context) => { + const killResult = spawned.kill(); + + if (killResult) { + context.isCanceled = true; + } +}; + +const timeoutKill = (spawned, signal, reject) => { + spawned.kill(signal); + reject(Object.assign(new Error('Timed out'), {timedOut: true, signal})); +}; + +// `timeout` option handling +const setupTimeout = (spawned, {timeout, killSignal = 'SIGTERM'}, spawnedPromise) => { + if (timeout === 0 || timeout === undefined) { + return spawnedPromise; + } + + if (!Number.isInteger(timeout) || timeout < 0) { + throw new TypeError(`Expected the \`timeout\` option to be a non-negative integer, got \`${timeout}\` (${typeof timeout})`); + } + + let timeoutId; + const timeoutPromise = new Promise((resolve, reject) => { + timeoutId = setTimeout(() => { + timeoutKill(spawned, killSignal, reject); + }, timeout); + }); + + const safeSpawnedPromise = pFinally(spawnedPromise, () => { + clearTimeout(timeoutId); + }); + + return Promise.race([timeoutPromise, safeSpawnedPromise]); +}; + +// `cleanup` option handling +const setExitHandler = (spawned, {cleanup, detached}, timedPromise) => { + if (!cleanup || detached) { + return timedPromise; + } + + const removeExitHandler = onExit(() => { + spawned.kill(); + }); + + // TODO: Use native "finally" syntax when targeting Node.js 10 + return pFinally(timedPromise, removeExitHandler); +}; + +module.exports = { + spawnedKill, + spawnedCancel, + setupTimeout, + setExitHandler +}; + + +/***/ }), +/* 377 */ +/***/ (function(module, exports, __webpack_require__) { + +// Note: since nyc uses this module to output coverage, any lines +// that are in the direct sync flow of nyc's outputCoverage are +// ignored, since we can never get coverage for them. +var assert = __webpack_require__(30) +var signals = __webpack_require__(378) + +var EE = __webpack_require__(379) +/* istanbul ignore if */ +if (typeof EE !== 'function') { + EE = EE.EventEmitter +} + +var emitter +if (process.__signal_exit_emitter__) { + emitter = process.__signal_exit_emitter__ +} else { + emitter = process.__signal_exit_emitter__ = new EE() + emitter.count = 0 + emitter.emitted = {} +} + +// Because this emitter is a global, we have to check to see if a +// previous version of this library failed to enable infinite listeners. +// I know what you're about to say. But literally everything about +// signal-exit is a compromise with evil. Get used to it. +if (!emitter.infinite) { + emitter.setMaxListeners(Infinity) + emitter.infinite = true +} + +module.exports = function (cb, opts) { + assert.equal(typeof cb, 'function', 'a callback must be provided for exit handler') + + if (loaded === false) { + load() + } + + var ev = 'exit' + if (opts && opts.alwaysLast) { + ev = 'afterexit' + } + + var remove = function () { + emitter.removeListener(ev, cb) + if (emitter.listeners('exit').length === 0 && + emitter.listeners('afterexit').length === 0) { + unload() + } + } + emitter.on(ev, cb) + + return remove +} + +module.exports.unload = unload +function unload () { + if (!loaded) { + return + } + loaded = false + + signals.forEach(function (sig) { + try { + process.removeListener(sig, sigListeners[sig]) + } catch (er) {} + }) + process.emit = originalProcessEmit + process.reallyExit = originalProcessReallyExit + emitter.count -= 1 +} + +function emit (event, code, signal) { + if (emitter.emitted[event]) { + return + } + emitter.emitted[event] = true + emitter.emit(event, code, signal) +} + +// { : , ... } +var sigListeners = {} +signals.forEach(function (sig) { + sigListeners[sig] = function listener () { + // If there are no other listeners, an exit is coming! + // Simplest way: remove us and then re-send the signal. + // We know that this will kill the process, so we can + // safely emit now. + var listeners = process.listeners(sig) + if (listeners.length === emitter.count) { + unload() + emit('exit', null, sig) + /* istanbul ignore next */ + emit('afterexit', null, sig) + /* istanbul ignore next */ + process.kill(process.pid, sig) + } + } +}) + +module.exports.signals = function () { + return signals +} + +module.exports.load = load + +var loaded = false + +function load () { + if (loaded) { + return + } + loaded = true + + // This is the number of onSignalExit's that are in play. + // It's important so that we can count the correct number of + // listeners on signals, and don't wait for the other one to + // handle it instead of us. + emitter.count += 1 + + signals = signals.filter(function (sig) { + try { + process.on(sig, sigListeners[sig]) + return true + } catch (er) { + return false + } + }) + + process.emit = processEmit + process.reallyExit = processReallyExit +} + +var originalProcessReallyExit = process.reallyExit +function processReallyExit (code) { + process.exitCode = code || 0 + emit('exit', process.exitCode, null) + /* istanbul ignore next */ + emit('afterexit', process.exitCode, null) + /* istanbul ignore next */ + originalProcessReallyExit.call(process, process.exitCode) +} + +var originalProcessEmit = process.emit +function processEmit (ev, arg) { + if (ev === 'exit') { + if (arg !== undefined) { + process.exitCode = arg + } + var ret = originalProcessEmit.apply(this, arguments) + emit('exit', process.exitCode, null) + /* istanbul ignore next */ + emit('afterexit', process.exitCode, null) + return ret + } else { + return originalProcessEmit.apply(this, arguments) + } +} + + +/***/ }), +/* 378 */ +/***/ (function(module, exports) { + +// This is not the set of all possible signals. +// +// It IS, however, the set of all signals that trigger +// an exit on either Linux or BSD systems. Linux is a +// superset of the signal names supported on BSD, and +// the unknown signals just fail to register, so we can +// catch that easily enough. +// +// Don't bother with SIGKILL. It's uncatchable, which +// means that we can't fire any callbacks anyway. +// +// If a user does happen to register a handler on a non- +// fatal signal like SIGWINCH or something, and then +// exit, it'll end up firing `process.emit('exit')`, so +// the handler will be fired anyway. +// +// SIGBUS, SIGFPE, SIGSEGV and SIGILL, when not raised +// artificially, inherently leave the process in a +// state from which it is not safe to try and enter JS +// listeners. +module.exports = [ + 'SIGABRT', + 'SIGALRM', + 'SIGHUP', + 'SIGINT', + 'SIGTERM' +] + +if (process.platform !== 'win32') { + module.exports.push( + 'SIGVTALRM', + 'SIGXCPU', + 'SIGXFSZ', + 'SIGUSR2', + 'SIGTRAP', + 'SIGSYS', + 'SIGQUIT', + 'SIGIOT' + // should detect profiler and enable/disable accordingly. + // see #21 + // 'SIGPROF' + ) +} + +if (process.platform === 'linux') { + module.exports.push( + 'SIGIO', + 'SIGPOLL', + 'SIGPWR', + 'SIGSTKFLT', + 'SIGUNUSED' + ) +} + + +/***/ }), +/* 379 */ +/***/ (function(module, exports) { + +module.exports = require("events"); + +/***/ }), +/* 380 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +module.exports = async ( + promise, + onFinally = (() => {}) +) => { + let value; + try { + value = await promise; + } catch (error) { + await onFinally(); + throw error; + } + + await onFinally(); + return value; +}; + + +/***/ }), +/* 381 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +const isStream = __webpack_require__(382); +const getStream = __webpack_require__(383); +const mergeStream = __webpack_require__(389); + +// `input` option +const handleInput = (spawned, input) => { + // Checking for stdin is workaround for https://github.com/nodejs/node/issues/26852 + // TODO: Remove `|| spawned.stdin === undefined` once we drop support for Node.js <=12.2.0 + if (input === undefined || spawned.stdin === undefined) { + return; + } + + if (isStream(input)) { + input.pipe(spawned.stdin); + } else { + spawned.stdin.end(input); + } +}; + +// `all` interleaves `stdout` and `stderr` +const makeAllStream = (spawned, {all}) => { + if (!all || (!spawned.stdout && !spawned.stderr)) { + return; + } + + const mixed = mergeStream(); + + if (spawned.stdout) { + mixed.add(spawned.stdout); + } + + if (spawned.stderr) { + mixed.add(spawned.stderr); + } + + return mixed; +}; + +// On failure, `result.stdout|stderr|all` should contain the currently buffered stream +const getBufferedData = async (stream, streamPromise) => { + if (!stream) { + return; + } + + stream.destroy(); + + try { + return await streamPromise; + } catch (error) { + return error.bufferedData; + } +}; + +const getStreamPromise = (stream, {encoding, buffer, maxBuffer}) => { + if (!stream || !buffer) { + return; + } + + if (encoding) { + return getStream(stream, {encoding, maxBuffer}); + } + + return getStream.buffer(stream, {maxBuffer}); +}; + +// Retrieve result of child process: exit code, signal, error, streams (stdout/stderr/all) +const getSpawnedResult = async ({stdout, stderr, all}, {encoding, buffer, maxBuffer}, processDone) => { + const stdoutPromise = getStreamPromise(stdout, {encoding, buffer, maxBuffer}); + const stderrPromise = getStreamPromise(stderr, {encoding, buffer, maxBuffer}); + const allPromise = getStreamPromise(all, {encoding, buffer, maxBuffer: maxBuffer * 2}); + + try { + return await Promise.all([processDone, stdoutPromise, stderrPromise, allPromise]); + } catch (error) { + return Promise.all([ + {error, signal: error.signal, timedOut: error.timedOut}, + getBufferedData(stdout, stdoutPromise), + getBufferedData(stderr, stderrPromise), + getBufferedData(all, allPromise) + ]); + } +}; + +const validateInputSync = ({input}) => { + if (isStream(input)) { + throw new TypeError('The `input` option cannot be a stream in sync mode'); + } +}; + +module.exports = { + handleInput, + makeAllStream, + getSpawnedResult, + validateInputSync +}; + + + +/***/ }), +/* 382 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +const isStream = stream => + stream !== null && + typeof stream === 'object' && + typeof stream.pipe === 'function'; + +isStream.writable = stream => + isStream(stream) && + stream.writable !== false && + typeof stream._write === 'function' && + typeof stream._writableState === 'object'; + +isStream.readable = stream => + isStream(stream) && + stream.readable !== false && + typeof stream._read === 'function' && + typeof stream._readableState === 'object'; + +isStream.duplex = stream => + isStream.writable(stream) && + isStream.readable(stream); + +isStream.transform = stream => + isStream.duplex(stream) && + typeof stream._transform === 'function' && + typeof stream._transformState === 'object'; + +module.exports = isStream; + + +/***/ }), +/* 383 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +const pump = __webpack_require__(384); +const bufferStream = __webpack_require__(388); + +class MaxBufferError extends Error { + constructor() { + super('maxBuffer exceeded'); + this.name = 'MaxBufferError'; + } +} + +async function getStream(inputStream, options) { + if (!inputStream) { + return Promise.reject(new Error('Expected a stream')); + } + + options = { + maxBuffer: Infinity, + ...options + }; + + const {maxBuffer} = options; + + let stream; + await new Promise((resolve, reject) => { + const rejectPromise = error => { + if (error) { // A null check + error.bufferedData = stream.getBufferedValue(); + } + + reject(error); + }; + + stream = pump(inputStream, bufferStream(options), error => { + if (error) { + rejectPromise(error); + return; + } + + resolve(); + }); + + stream.on('data', () => { + if (stream.getBufferedLength() > maxBuffer) { + rejectPromise(new MaxBufferError()); + } + }); + }); + + return stream.getBufferedValue(); +} + +module.exports = getStream; +// TODO: Remove this for the next major release +module.exports.default = getStream; +module.exports.buffer = (stream, options) => getStream(stream, {...options, encoding: 'buffer'}); +module.exports.array = (stream, options) => getStream(stream, {...options, array: true}); +module.exports.MaxBufferError = MaxBufferError; + + +/***/ }), +/* 384 */ +/***/ (function(module, exports, __webpack_require__) { + +var once = __webpack_require__(385) +var eos = __webpack_require__(387) +var fs = __webpack_require__(23) // we only need fs to get the ReadStream and WriteStream prototypes + +var noop = function () {} +var ancient = /^v?\.0/.test(process.version) + +var isFn = function (fn) { + return typeof fn === 'function' +} + +var isFS = function (stream) { + if (!ancient) return false // newer node version do not need to care about fs is a special way + if (!fs) return false // browser + return (stream instanceof (fs.ReadStream || noop) || stream instanceof (fs.WriteStream || noop)) && isFn(stream.close) +} + +var isRequest = function (stream) { + return stream.setHeader && isFn(stream.abort) +} + +var destroyer = function (stream, reading, writing, callback) { + callback = once(callback) + + var closed = false + stream.on('close', function () { + closed = true + }) + + eos(stream, {readable: reading, writable: writing}, function (err) { + if (err) return callback(err) + closed = true + callback() + }) + + var destroyed = false + return function (err) { + if (closed) return + if (destroyed) return + destroyed = true + + if (isFS(stream)) return stream.close(noop) // use close for fs streams to avoid fd leaks + if (isRequest(stream)) return stream.abort() // request.destroy just do .end - .abort is what we want + + if (isFn(stream.destroy)) return stream.destroy() + + callback(err || new Error('stream was destroyed')) + } +} + +var call = function (fn) { + fn() +} + +var pipe = function (from, to) { + return from.pipe(to) +} + +var pump = function () { + var streams = Array.prototype.slice.call(arguments) + var callback = isFn(streams[streams.length - 1] || noop) && streams.pop() || noop + + if (Array.isArray(streams[0])) streams = streams[0] + if (streams.length < 2) throw new Error('pump requires two streams per minimum') + + var error + var destroys = streams.map(function (stream, i) { + var reading = i < streams.length - 1 + var writing = i > 0 + return destroyer(stream, reading, writing, function (err) { + if (!error) error = err + if (err) destroys.forEach(call) + if (reading) return + destroys.forEach(call) + callback(error) + }) + }) + + return streams.reduce(pipe) +} + +module.exports = pump + + +/***/ }), +/* 385 */ +/***/ (function(module, exports, __webpack_require__) { + +var wrappy = __webpack_require__(386) +module.exports = wrappy(once) +module.exports.strict = wrappy(onceStrict) + +once.proto = once(function () { + Object.defineProperty(Function.prototype, 'once', { + value: function () { + return once(this) + }, + configurable: true + }) + + Object.defineProperty(Function.prototype, 'onceStrict', { + value: function () { + return onceStrict(this) + }, + configurable: true + }) +}) + +function once (fn) { + var f = function () { + if (f.called) return f.value + f.called = true + return f.value = fn.apply(this, arguments) + } + f.called = false + return f +} + +function onceStrict (fn) { + var f = function () { + if (f.called) + throw new Error(f.onceError) + f.called = true + return f.value = fn.apply(this, arguments) + } + var name = fn.name || 'Function wrapped with `once`' + f.onceError = name + " shouldn't be called more than once" + f.called = false + return f +} + + +/***/ }), +/* 386 */ +/***/ (function(module, exports) { + +// Returns a wrapper function that returns a wrapped callback +// The wrapper function should do some stuff, and return a +// presumably different callback function. +// This makes sure that own properties are retained, so that +// decorations and such are not lost along the way. +module.exports = wrappy +function wrappy (fn, cb) { + if (fn && cb) return wrappy(fn)(cb) + + if (typeof fn !== 'function') + throw new TypeError('need wrapper function') + + Object.keys(fn).forEach(function (k) { + wrapper[k] = fn[k] + }) + + return wrapper + + function wrapper() { + var args = new Array(arguments.length) + for (var i = 0; i < args.length; i++) { + args[i] = arguments[i] + } + var ret = fn.apply(this, args) + var cb = args[args.length-1] + if (typeof ret === 'function' && ret !== cb) { + Object.keys(cb).forEach(function (k) { + ret[k] = cb[k] + }) + } + return ret + } +} + + +/***/ }), +/* 387 */ +/***/ (function(module, exports, __webpack_require__) { + +var once = __webpack_require__(385); + +var noop = function() {}; + +var isRequest = function(stream) { + return stream.setHeader && typeof stream.abort === 'function'; +}; + +var isChildProcess = function(stream) { + return stream.stdio && Array.isArray(stream.stdio) && stream.stdio.length === 3 +}; + +var eos = function(stream, opts, callback) { + if (typeof opts === 'function') return eos(stream, null, opts); + if (!opts) opts = {}; + + callback = once(callback || noop); + + var ws = stream._writableState; + var rs = stream._readableState; + var readable = opts.readable || (opts.readable !== false && stream.readable); + var writable = opts.writable || (opts.writable !== false && stream.writable); + + var onlegacyfinish = function() { + if (!stream.writable) onfinish(); + }; + + var onfinish = function() { + writable = false; + if (!readable) callback.call(stream); + }; + + var onend = function() { + readable = false; + if (!writable) callback.call(stream); + }; + + var onexit = function(exitCode) { + callback.call(stream, exitCode ? new Error('exited with error code: ' + exitCode) : null); + }; + + var onerror = function(err) { + callback.call(stream, err); + }; + + var onclose = function() { + if (readable && !(rs && rs.ended)) return callback.call(stream, new Error('premature close')); + if (writable && !(ws && ws.ended)) return callback.call(stream, new Error('premature close')); + }; + + var onrequest = function() { + stream.req.on('finish', onfinish); + }; + + if (isRequest(stream)) { + stream.on('complete', onfinish); + stream.on('abort', onclose); + if (stream.req) onrequest(); + else stream.on('request', onrequest); + } else if (writable && !ws) { // legacy streams + stream.on('end', onlegacyfinish); + stream.on('close', onlegacyfinish); + } + + if (isChildProcess(stream)) stream.on('exit', onexit); + + stream.on('end', onend); + stream.on('finish', onfinish); + if (opts.error !== false) stream.on('error', onerror); + stream.on('close', onclose); + + return function() { + stream.removeListener('complete', onfinish); + stream.removeListener('abort', onclose); + stream.removeListener('request', onrequest); + if (stream.req) stream.req.removeListener('finish', onfinish); + stream.removeListener('end', onlegacyfinish); + stream.removeListener('close', onlegacyfinish); + stream.removeListener('finish', onfinish); + stream.removeListener('exit', onexit); + stream.removeListener('end', onend); + stream.removeListener('error', onerror); + stream.removeListener('close', onclose); + }; +}; + +module.exports = eos; + + +/***/ }), +/* 388 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +const {PassThrough: PassThroughStream} = __webpack_require__(27); + +module.exports = options => { + options = {...options}; + + const {array} = options; + let {encoding} = options; + const isBuffer = encoding === 'buffer'; + let objectMode = false; + + if (array) { + objectMode = !(encoding || isBuffer); + } else { + encoding = encoding || 'utf8'; + } + + if (isBuffer) { + encoding = null; + } + + const stream = new PassThroughStream({objectMode}); + + if (encoding) { + stream.setEncoding(encoding); + } + + let length = 0; + const chunks = []; + + stream.on('data', chunk => { + chunks.push(chunk); + + if (objectMode) { + length = chunks.length; + } else { + length += chunk.length; + } + }); + + stream.getBufferedValue = () => { + if (array) { + return chunks; + } + + return isBuffer ? Buffer.concat(chunks, length) : chunks.join(''); + }; + + stream.getBufferedLength = () => length; + + return stream; +}; + + +/***/ }), +/* 389 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +const { PassThrough } = __webpack_require__(27); + +module.exports = function (/*streams...*/) { + var sources = [] + var output = new PassThrough({objectMode: true}) + + output.setMaxListeners(0) + + output.add = add + output.isEmpty = isEmpty + + output.on('unpipe', remove) + + Array.prototype.slice.call(arguments).forEach(add) + + return output + + function add (source) { + if (Array.isArray(source)) { + source.forEach(add) + return this + } + + sources.push(source); + source.once('end', remove.bind(null, source)) + source.once('error', output.emit.bind(output, 'error')) + source.pipe(output, {end: false}) + return this + } + + function isEmpty () { + return sources.length == 0; + } + + function remove (source) { + sources = sources.filter(function (it) { return it !== source }) + if (!sources.length && output.readable) { output.end() } + } +} + + +/***/ }), +/* 390 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +const mergePromiseProperty = (spawned, promise, property) => { + // Starting the main `promise` is deferred to avoid consuming streams + const value = typeof promise === 'function' ? + (...args) => promise()[property](...args) : + promise[property].bind(promise); + + Object.defineProperty(spawned, property, { + value, + writable: true, + enumerable: false, + configurable: true + }); +}; + +// The return value is a mixin of `childProcess` and `Promise` +const mergePromise = (spawned, promise) => { + mergePromiseProperty(spawned, promise, 'then'); + mergePromiseProperty(spawned, promise, 'catch'); + + // TODO: Remove the `if`-guard when targeting Node.js 10 + if (Promise.prototype.finally) { + mergePromiseProperty(spawned, promise, 'finally'); + } + + return spawned; +}; + +// Use promises instead of `child_process` events +const getSpawnedPromise = spawned => { + return new Promise((resolve, reject) => { + spawned.on('exit', (exitCode, signal) => { + resolve({exitCode, signal}); + }); + + spawned.on('error', error => { + reject(error); + }); + + if (spawned.stdin) { + spawned.stdin.on('error', error => { + reject(error); + }); + } + }); +}; + +module.exports = { + mergePromise, + getSpawnedPromise +}; + + + +/***/ }), +/* 391 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +const SPACES_REGEXP = / +/g; + +const joinCommand = (file, args = []) => { + if (!Array.isArray(args)) { + return file; + } + + return [file, ...args].join(' '); +}; + +// Allow spaces to be escaped by a backslash if not meant as a delimiter +const handleEscaping = (tokens, token, index) => { + if (index === 0) { + return [token]; + } + + const previousToken = tokens[tokens.length - 1]; + + if (previousToken.endsWith('\\')) { + return [...tokens.slice(0, -1), `${previousToken.slice(0, -1)} ${token}`]; + } + + return [...tokens, token]; +}; + +// Handle `execa.command()` +const parseCommand = command => { + return command + .trim() + .split(SPACES_REGEXP) + .reduce(handleEscaping, []); +}; + +module.exports = { + joinCommand, + parseCommand +}; + + +/***/ }), +/* 392 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony import */ var _internal_Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(193); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "Observable", function() { return _internal_Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"]; }); + +/* harmony import */ var _internal_observable_ConnectableObservable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(283); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "ConnectableObservable", function() { return _internal_observable_ConnectableObservable__WEBPACK_IMPORTED_MODULE_1__["ConnectableObservable"]; }); + +/* harmony import */ var _internal_operators_groupBy__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(264); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "GroupedObservable", function() { return _internal_operators_groupBy__WEBPACK_IMPORTED_MODULE_2__["GroupedObservable"]; }); + +/* harmony import */ var _internal_symbol_observable__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(190); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "observable", function() { return _internal_symbol_observable__WEBPACK_IMPORTED_MODULE_3__["observable"]; }); + +/* harmony import */ var _internal_Subject__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(265); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "Subject", function() { return _internal_Subject__WEBPACK_IMPORTED_MODULE_4__["Subject"]; }); + +/* harmony import */ var _internal_BehaviorSubject__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(293); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "BehaviorSubject", function() { return _internal_BehaviorSubject__WEBPACK_IMPORTED_MODULE_5__["BehaviorSubject"]; }); + +/* harmony import */ var _internal_ReplaySubject__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(297); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "ReplaySubject", function() { return _internal_ReplaySubject__WEBPACK_IMPORTED_MODULE_6__["ReplaySubject"]; }); + +/* harmony import */ var _internal_AsyncSubject__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(295); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "AsyncSubject", function() { return _internal_AsyncSubject__WEBPACK_IMPORTED_MODULE_7__["AsyncSubject"]; }); + +/* harmony import */ var _internal_scheduler_asap__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(320); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "asapScheduler", function() { return _internal_scheduler_asap__WEBPACK_IMPORTED_MODULE_8__["asap"]; }); + +/* harmony import */ var _internal_scheduler_async__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(199); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "asyncScheduler", function() { return _internal_scheduler_async__WEBPACK_IMPORTED_MODULE_9__["async"]; }); + +/* harmony import */ var _internal_scheduler_queue__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(298); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "queueScheduler", function() { return _internal_scheduler_queue__WEBPACK_IMPORTED_MODULE_10__["queue"]; }); + +/* harmony import */ var _internal_scheduler_animationFrame__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(393); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "animationFrameScheduler", function() { return _internal_scheduler_animationFrame__WEBPACK_IMPORTED_MODULE_11__["animationFrame"]; }); + +/* harmony import */ var _internal_scheduler_VirtualTimeScheduler__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(396); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "VirtualTimeScheduler", function() { return _internal_scheduler_VirtualTimeScheduler__WEBPACK_IMPORTED_MODULE_12__["VirtualTimeScheduler"]; }); + +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "VirtualAction", function() { return _internal_scheduler_VirtualTimeScheduler__WEBPACK_IMPORTED_MODULE_12__["VirtualAction"]; }); + +/* harmony import */ var _internal_Scheduler__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(203); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "Scheduler", function() { return _internal_Scheduler__WEBPACK_IMPORTED_MODULE_13__["Scheduler"]; }); + +/* harmony import */ var _internal_Subscription__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(177); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "Subscription", function() { return _internal_Subscription__WEBPACK_IMPORTED_MODULE_14__["Subscription"]; }); + +/* harmony import */ var _internal_Subscriber__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(172); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "Subscriber", function() { return _internal_Subscriber__WEBPACK_IMPORTED_MODULE_15__["Subscriber"]; }); + +/* harmony import */ var _internal_Notification__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(241); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "Notification", function() { return _internal_Notification__WEBPACK_IMPORTED_MODULE_16__["Notification"]; }); + +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "NotificationKind", function() { return _internal_Notification__WEBPACK_IMPORTED_MODULE_16__["NotificationKind"]; }); + +/* harmony import */ var _internal_util_pipe__WEBPACK_IMPORTED_MODULE_17__ = __webpack_require__(196); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "pipe", function() { return _internal_util_pipe__WEBPACK_IMPORTED_MODULE_17__["pipe"]; }); + +/* harmony import */ var _internal_util_noop__WEBPACK_IMPORTED_MODULE_18__ = __webpack_require__(197); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "noop", function() { return _internal_util_noop__WEBPACK_IMPORTED_MODULE_18__["noop"]; }); + +/* harmony import */ var _internal_util_identity__WEBPACK_IMPORTED_MODULE_19__ = __webpack_require__(232); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "identity", function() { return _internal_util_identity__WEBPACK_IMPORTED_MODULE_19__["identity"]; }); + +/* harmony import */ var _internal_util_isObservable__WEBPACK_IMPORTED_MODULE_20__ = __webpack_require__(397); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "isObservable", function() { return _internal_util_isObservable__WEBPACK_IMPORTED_MODULE_20__["isObservable"]; }); + +/* harmony import */ var _internal_util_ArgumentOutOfRangeError__WEBPACK_IMPORTED_MODULE_21__ = __webpack_require__(250); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "ArgumentOutOfRangeError", function() { return _internal_util_ArgumentOutOfRangeError__WEBPACK_IMPORTED_MODULE_21__["ArgumentOutOfRangeError"]; }); + +/* harmony import */ var _internal_util_EmptyError__WEBPACK_IMPORTED_MODULE_22__ = __webpack_require__(253); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "EmptyError", function() { return _internal_util_EmptyError__WEBPACK_IMPORTED_MODULE_22__["EmptyError"]; }); + +/* harmony import */ var _internal_util_ObjectUnsubscribedError__WEBPACK_IMPORTED_MODULE_23__ = __webpack_require__(266); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "ObjectUnsubscribedError", function() { return _internal_util_ObjectUnsubscribedError__WEBPACK_IMPORTED_MODULE_23__["ObjectUnsubscribedError"]; }); + +/* harmony import */ var _internal_util_UnsubscriptionError__WEBPACK_IMPORTED_MODULE_24__ = __webpack_require__(180); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "UnsubscriptionError", function() { return _internal_util_UnsubscriptionError__WEBPACK_IMPORTED_MODULE_24__["UnsubscriptionError"]; }); + +/* harmony import */ var _internal_util_TimeoutError__WEBPACK_IMPORTED_MODULE_25__ = __webpack_require__(335); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "TimeoutError", function() { return _internal_util_TimeoutError__WEBPACK_IMPORTED_MODULE_25__["TimeoutError"]; }); + +/* harmony import */ var _internal_observable_bindCallback__WEBPACK_IMPORTED_MODULE_26__ = __webpack_require__(398); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "bindCallback", function() { return _internal_observable_bindCallback__WEBPACK_IMPORTED_MODULE_26__["bindCallback"]; }); + +/* harmony import */ var _internal_observable_bindNodeCallback__WEBPACK_IMPORTED_MODULE_27__ = __webpack_require__(399); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "bindNodeCallback", function() { return _internal_observable_bindNodeCallback__WEBPACK_IMPORTED_MODULE_27__["bindNodeCallback"]; }); + +/* harmony import */ var _internal_observable_combineLatest__WEBPACK_IMPORTED_MODULE_28__ = __webpack_require__(214); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "combineLatest", function() { return _internal_observable_combineLatest__WEBPACK_IMPORTED_MODULE_28__["combineLatest"]; }); + +/* harmony import */ var _internal_observable_concat__WEBPACK_IMPORTED_MODULE_29__ = __webpack_require__(226); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "concat", function() { return _internal_observable_concat__WEBPACK_IMPORTED_MODULE_29__["concat"]; }); + +/* harmony import */ var _internal_observable_defer__WEBPACK_IMPORTED_MODULE_30__ = __webpack_require__(333); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "defer", function() { return _internal_observable_defer__WEBPACK_IMPORTED_MODULE_30__["defer"]; }); + +/* harmony import */ var _internal_observable_empty__WEBPACK_IMPORTED_MODULE_31__ = __webpack_require__(242); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "empty", function() { return _internal_observable_empty__WEBPACK_IMPORTED_MODULE_31__["empty"]; }); + +/* harmony import */ var _internal_observable_forkJoin__WEBPACK_IMPORTED_MODULE_32__ = __webpack_require__(400); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "forkJoin", function() { return _internal_observable_forkJoin__WEBPACK_IMPORTED_MODULE_32__["forkJoin"]; }); + +/* harmony import */ var _internal_observable_from__WEBPACK_IMPORTED_MODULE_33__ = __webpack_require__(218); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "from", function() { return _internal_observable_from__WEBPACK_IMPORTED_MODULE_33__["from"]; }); + +/* harmony import */ var _internal_observable_fromEvent__WEBPACK_IMPORTED_MODULE_34__ = __webpack_require__(401); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "fromEvent", function() { return _internal_observable_fromEvent__WEBPACK_IMPORTED_MODULE_34__["fromEvent"]; }); + +/* harmony import */ var _internal_observable_fromEventPattern__WEBPACK_IMPORTED_MODULE_35__ = __webpack_require__(402); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "fromEventPattern", function() { return _internal_observable_fromEventPattern__WEBPACK_IMPORTED_MODULE_35__["fromEventPattern"]; }); + +/* harmony import */ var _internal_observable_generate__WEBPACK_IMPORTED_MODULE_36__ = __webpack_require__(403); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "generate", function() { return _internal_observable_generate__WEBPACK_IMPORTED_MODULE_36__["generate"]; }); + +/* harmony import */ var _internal_observable_iif__WEBPACK_IMPORTED_MODULE_37__ = __webpack_require__(404); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "iif", function() { return _internal_observable_iif__WEBPACK_IMPORTED_MODULE_37__["iif"]; }); + +/* harmony import */ var _internal_observable_interval__WEBPACK_IMPORTED_MODULE_38__ = __webpack_require__(405); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "interval", function() { return _internal_observable_interval__WEBPACK_IMPORTED_MODULE_38__["interval"]; }); + +/* harmony import */ var _internal_observable_merge__WEBPACK_IMPORTED_MODULE_39__ = __webpack_require__(278); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "merge", function() { return _internal_observable_merge__WEBPACK_IMPORTED_MODULE_39__["merge"]; }); + +/* harmony import */ var _internal_observable_never__WEBPACK_IMPORTED_MODULE_40__ = __webpack_require__(406); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "never", function() { return _internal_observable_never__WEBPACK_IMPORTED_MODULE_40__["never"]; }); + +/* harmony import */ var _internal_observable_of__WEBPACK_IMPORTED_MODULE_41__ = __webpack_require__(227); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "of", function() { return _internal_observable_of__WEBPACK_IMPORTED_MODULE_41__["of"]; }); + +/* harmony import */ var _internal_observable_onErrorResumeNext__WEBPACK_IMPORTED_MODULE_42__ = __webpack_require__(407); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "onErrorResumeNext", function() { return _internal_observable_onErrorResumeNext__WEBPACK_IMPORTED_MODULE_42__["onErrorResumeNext"]; }); + +/* harmony import */ var _internal_observable_pairs__WEBPACK_IMPORTED_MODULE_43__ = __webpack_require__(408); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "pairs", function() { return _internal_observable_pairs__WEBPACK_IMPORTED_MODULE_43__["pairs"]; }); + +/* harmony import */ var _internal_observable_partition__WEBPACK_IMPORTED_MODULE_44__ = __webpack_require__(409); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "partition", function() { return _internal_observable_partition__WEBPACK_IMPORTED_MODULE_44__["partition"]; }); + +/* harmony import */ var _internal_observable_race__WEBPACK_IMPORTED_MODULE_45__ = __webpack_require__(302); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "race", function() { return _internal_observable_race__WEBPACK_IMPORTED_MODULE_45__["race"]; }); + +/* harmony import */ var _internal_observable_range__WEBPACK_IMPORTED_MODULE_46__ = __webpack_require__(410); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "range", function() { return _internal_observable_range__WEBPACK_IMPORTED_MODULE_46__["range"]; }); + +/* harmony import */ var _internal_observable_throwError__WEBPACK_IMPORTED_MODULE_47__ = __webpack_require__(243); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "throwError", function() { return _internal_observable_throwError__WEBPACK_IMPORTED_MODULE_47__["throwError"]; }); + +/* harmony import */ var _internal_observable_timer__WEBPACK_IMPORTED_MODULE_48__ = __webpack_require__(204); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "timer", function() { return _internal_observable_timer__WEBPACK_IMPORTED_MODULE_48__["timer"]; }); + +/* harmony import */ var _internal_observable_using__WEBPACK_IMPORTED_MODULE_49__ = __webpack_require__(411); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "using", function() { return _internal_observable_using__WEBPACK_IMPORTED_MODULE_49__["using"]; }); + +/* harmony import */ var _internal_observable_zip__WEBPACK_IMPORTED_MODULE_50__ = __webpack_require__(346); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "zip", function() { return _internal_observable_zip__WEBPACK_IMPORTED_MODULE_50__["zip"]; }); + +/* harmony import */ var _internal_scheduled_scheduled__WEBPACK_IMPORTED_MODULE_51__ = __webpack_require__(219); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "scheduled", function() { return _internal_scheduled_scheduled__WEBPACK_IMPORTED_MODULE_51__["scheduled"]; }); + +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "EMPTY", function() { return _internal_observable_empty__WEBPACK_IMPORTED_MODULE_31__["EMPTY"]; }); + +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "NEVER", function() { return _internal_observable_never__WEBPACK_IMPORTED_MODULE_40__["NEVER"]; }); + +/* harmony import */ var _internal_config__WEBPACK_IMPORTED_MODULE_52__ = __webpack_require__(175); +/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "config", function() { return _internal_config__WEBPACK_IMPORTED_MODULE_52__["config"]; }); + +/** PURE_IMPORTS_START PURE_IMPORTS_END */ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +//# sourceMappingURL=index.js.map + + +/***/ }), +/* 393 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "animationFrame", function() { return animationFrame; }); +/* harmony import */ var _AnimationFrameAction__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(394); +/* harmony import */ var _AnimationFrameScheduler__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(395); +/** PURE_IMPORTS_START _AnimationFrameAction,_AnimationFrameScheduler PURE_IMPORTS_END */ + + +var animationFrame = /*@__PURE__*/ new _AnimationFrameScheduler__WEBPACK_IMPORTED_MODULE_1__["AnimationFrameScheduler"](_AnimationFrameAction__WEBPACK_IMPORTED_MODULE_0__["AnimationFrameAction"]); +//# sourceMappingURL=animationFrame.js.map + + +/***/ }), +/* 394 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "AnimationFrameAction", function() { return AnimationFrameAction; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _AsyncAction__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(200); +/** PURE_IMPORTS_START tslib,_AsyncAction PURE_IMPORTS_END */ + + +var AnimationFrameAction = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](AnimationFrameAction, _super); + function AnimationFrameAction(scheduler, work) { + var _this = _super.call(this, scheduler, work) || this; + _this.scheduler = scheduler; + _this.work = work; + return _this; + } + AnimationFrameAction.prototype.requestAsyncId = function (scheduler, id, delay) { + if (delay === void 0) { + delay = 0; + } + if (delay !== null && delay > 0) { + return _super.prototype.requestAsyncId.call(this, scheduler, id, delay); + } + scheduler.actions.push(this); + return scheduler.scheduled || (scheduler.scheduled = requestAnimationFrame(function () { return scheduler.flush(null); })); + }; + AnimationFrameAction.prototype.recycleAsyncId = function (scheduler, id, delay) { + if (delay === void 0) { + delay = 0; + } + if ((delay !== null && delay > 0) || (delay === null && this.delay > 0)) { + return _super.prototype.recycleAsyncId.call(this, scheduler, id, delay); + } + if (scheduler.actions.length === 0) { + cancelAnimationFrame(id); + scheduler.scheduled = undefined; + } + return undefined; + }; + return AnimationFrameAction; +}(_AsyncAction__WEBPACK_IMPORTED_MODULE_1__["AsyncAction"])); + +//# sourceMappingURL=AnimationFrameAction.js.map + + +/***/ }), +/* 395 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "AnimationFrameScheduler", function() { return AnimationFrameScheduler; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _AsyncScheduler__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(202); +/** PURE_IMPORTS_START tslib,_AsyncScheduler PURE_IMPORTS_END */ + + +var AnimationFrameScheduler = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](AnimationFrameScheduler, _super); + function AnimationFrameScheduler() { + return _super !== null && _super.apply(this, arguments) || this; + } + AnimationFrameScheduler.prototype.flush = function (action) { + this.active = true; + this.scheduled = undefined; + var actions = this.actions; + var error; + var index = -1; + var count = actions.length; + action = action || actions.shift(); + do { + if (error = action.execute(action.state, action.delay)) { + break; + } + } while (++index < count && (action = actions.shift())); + this.active = false; + if (error) { + while (++index < count && (action = actions.shift())) { + action.unsubscribe(); + } + throw error; + } + }; + return AnimationFrameScheduler; +}(_AsyncScheduler__WEBPACK_IMPORTED_MODULE_1__["AsyncScheduler"])); + +//# sourceMappingURL=AnimationFrameScheduler.js.map + + +/***/ }), +/* 396 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "VirtualTimeScheduler", function() { return VirtualTimeScheduler; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "VirtualAction", function() { return VirtualAction; }); +/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(36); +/* harmony import */ var _AsyncAction__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(200); +/* harmony import */ var _AsyncScheduler__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(202); +/** PURE_IMPORTS_START tslib,_AsyncAction,_AsyncScheduler PURE_IMPORTS_END */ + + + +var VirtualTimeScheduler = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](VirtualTimeScheduler, _super); + function VirtualTimeScheduler(SchedulerAction, maxFrames) { + if (SchedulerAction === void 0) { + SchedulerAction = VirtualAction; + } + if (maxFrames === void 0) { + maxFrames = Number.POSITIVE_INFINITY; + } + var _this = _super.call(this, SchedulerAction, function () { return _this.frame; }) || this; + _this.maxFrames = maxFrames; + _this.frame = 0; + _this.index = -1; + return _this; + } + VirtualTimeScheduler.prototype.flush = function () { + var _a = this, actions = _a.actions, maxFrames = _a.maxFrames; + var error, action; + while ((action = actions[0]) && action.delay <= maxFrames) { + actions.shift(); + this.frame = action.delay; + if (error = action.execute(action.state, action.delay)) { + break; + } + } + if (error) { + while (action = actions.shift()) { + action.unsubscribe(); + } + throw error; + } + }; + VirtualTimeScheduler.frameTimeFactor = 10; + return VirtualTimeScheduler; +}(_AsyncScheduler__WEBPACK_IMPORTED_MODULE_2__["AsyncScheduler"])); + +var VirtualAction = /*@__PURE__*/ (function (_super) { + tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](VirtualAction, _super); + function VirtualAction(scheduler, work, index) { + if (index === void 0) { + index = scheduler.index += 1; + } + var _this = _super.call(this, scheduler, work) || this; + _this.scheduler = scheduler; + _this.work = work; + _this.index = index; + _this.active = true; + _this.index = scheduler.index = index; + return _this; + } + VirtualAction.prototype.schedule = function (state, delay) { + if (delay === void 0) { + delay = 0; + } + if (!this.id) { + return _super.prototype.schedule.call(this, state, delay); + } + this.active = false; + var action = new VirtualAction(this.scheduler, this.work); + this.add(action); + return action.schedule(state, delay); + }; + VirtualAction.prototype.requestAsyncId = function (scheduler, id, delay) { + if (delay === void 0) { + delay = 0; + } + this.delay = scheduler.frame + delay; + var actions = scheduler.actions; + actions.push(this); + actions.sort(VirtualAction.sortActions); + return true; + }; + VirtualAction.prototype.recycleAsyncId = function (scheduler, id, delay) { + if (delay === void 0) { + delay = 0; + } + return undefined; + }; + VirtualAction.prototype._execute = function (state, delay) { + if (this.active === true) { + return _super.prototype._execute.call(this, state, delay); + } + }; + VirtualAction.sortActions = function (a, b) { + if (a.delay === b.delay) { + if (a.index === b.index) { + return 0; + } + else if (a.index > b.index) { + return 1; + } + else { + return -1; + } + } + else if (a.delay > b.delay) { + return 1; + } + else { + return -1; + } + }; + return VirtualAction; +}(_AsyncAction__WEBPACK_IMPORTED_MODULE_1__["AsyncAction"])); + +//# sourceMappingURL=VirtualTimeScheduler.js.map + + +/***/ }), +/* 397 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "isObservable", function() { return isObservable; }); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(193); +/** PURE_IMPORTS_START _Observable PURE_IMPORTS_END */ + +function isObservable(obj) { + return !!obj && (obj instanceof _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"] || (typeof obj.lift === 'function' && typeof obj.subscribe === 'function')); +} +//# sourceMappingURL=isObservable.js.map + + +/***/ }), +/* 398 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "bindCallback", function() { return bindCallback; }); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(193); +/* harmony import */ var _AsyncSubject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(295); +/* harmony import */ var _operators_map__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(231); +/* harmony import */ var _util_canReportError__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(194); +/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(178); +/* harmony import */ var _util_isScheduler__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(206); +/** PURE_IMPORTS_START _Observable,_AsyncSubject,_operators_map,_util_canReportError,_util_isArray,_util_isScheduler PURE_IMPORTS_END */ + + + + + + +function bindCallback(callbackFunc, resultSelector, scheduler) { + if (resultSelector) { + if (Object(_util_isScheduler__WEBPACK_IMPORTED_MODULE_5__["isScheduler"])(resultSelector)) { + scheduler = resultSelector; + } + else { + return function () { + var args = []; + for (var _i = 0; _i < arguments.length; _i++) { + args[_i] = arguments[_i]; + } + return bindCallback(callbackFunc, scheduler).apply(void 0, args).pipe(Object(_operators_map__WEBPACK_IMPORTED_MODULE_2__["map"])(function (args) { return Object(_util_isArray__WEBPACK_IMPORTED_MODULE_4__["isArray"])(args) ? resultSelector.apply(void 0, args) : resultSelector(args); })); + }; + } + } + return function () { + var args = []; + for (var _i = 0; _i < arguments.length; _i++) { + args[_i] = arguments[_i]; + } + var context = this; + var subject; + var params = { + context: context, + subject: subject, + callbackFunc: callbackFunc, + scheduler: scheduler, + }; + return new _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"](function (subscriber) { + if (!scheduler) { + if (!subject) { + subject = new _AsyncSubject__WEBPACK_IMPORTED_MODULE_1__["AsyncSubject"](); + var handler = function () { + var innerArgs = []; + for (var _i = 0; _i < arguments.length; _i++) { + innerArgs[_i] = arguments[_i]; + } + subject.next(innerArgs.length <= 1 ? innerArgs[0] : innerArgs); + subject.complete(); + }; + try { + callbackFunc.apply(context, args.concat([handler])); + } + catch (err) { + if (Object(_util_canReportError__WEBPACK_IMPORTED_MODULE_3__["canReportError"])(subject)) { + subject.error(err); + } + else { + console.warn(err); + } + } + } + return subject.subscribe(subscriber); + } + else { + var state = { + args: args, subscriber: subscriber, params: params, + }; + return scheduler.schedule(dispatch, 0, state); + } + }); + }; +} +function dispatch(state) { + var _this = this; + var self = this; + var args = state.args, subscriber = state.subscriber, params = state.params; + var callbackFunc = params.callbackFunc, context = params.context, scheduler = params.scheduler; + var subject = params.subject; + if (!subject) { + subject = params.subject = new _AsyncSubject__WEBPACK_IMPORTED_MODULE_1__["AsyncSubject"](); + var handler = function () { + var innerArgs = []; + for (var _i = 0; _i < arguments.length; _i++) { + innerArgs[_i] = arguments[_i]; + } + var value = innerArgs.length <= 1 ? innerArgs[0] : innerArgs; + _this.add(scheduler.schedule(dispatchNext, 0, { value: value, subject: subject })); + }; + try { + callbackFunc.apply(context, args.concat([handler])); + } + catch (err) { + subject.error(err); + } + } + this.add(subject.subscribe(subscriber)); +} +function dispatchNext(state) { + var value = state.value, subject = state.subject; + subject.next(value); + subject.complete(); +} +function dispatchError(state) { + var err = state.err, subject = state.subject; + subject.error(err); +} +//# sourceMappingURL=bindCallback.js.map + + +/***/ }), +/* 399 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "bindNodeCallback", function() { return bindNodeCallback; }); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(193); +/* harmony import */ var _AsyncSubject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(295); +/* harmony import */ var _operators_map__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(231); +/* harmony import */ var _util_canReportError__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(194); +/* harmony import */ var _util_isScheduler__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(206); +/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(178); +/** PURE_IMPORTS_START _Observable,_AsyncSubject,_operators_map,_util_canReportError,_util_isScheduler,_util_isArray PURE_IMPORTS_END */ + + + + + + +function bindNodeCallback(callbackFunc, resultSelector, scheduler) { + if (resultSelector) { + if (Object(_util_isScheduler__WEBPACK_IMPORTED_MODULE_4__["isScheduler"])(resultSelector)) { + scheduler = resultSelector; + } + else { + return function () { + var args = []; + for (var _i = 0; _i < arguments.length; _i++) { + args[_i] = arguments[_i]; + } + return bindNodeCallback(callbackFunc, scheduler).apply(void 0, args).pipe(Object(_operators_map__WEBPACK_IMPORTED_MODULE_2__["map"])(function (args) { return Object(_util_isArray__WEBPACK_IMPORTED_MODULE_5__["isArray"])(args) ? resultSelector.apply(void 0, args) : resultSelector(args); })); + }; + } + } + return function () { + var args = []; + for (var _i = 0; _i < arguments.length; _i++) { + args[_i] = arguments[_i]; + } + var params = { + subject: undefined, + args: args, + callbackFunc: callbackFunc, + scheduler: scheduler, + context: this, + }; + return new _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"](function (subscriber) { + var context = params.context; + var subject = params.subject; + if (!scheduler) { + if (!subject) { + subject = params.subject = new _AsyncSubject__WEBPACK_IMPORTED_MODULE_1__["AsyncSubject"](); + var handler = function () { + var innerArgs = []; + for (var _i = 0; _i < arguments.length; _i++) { + innerArgs[_i] = arguments[_i]; + } + var err = innerArgs.shift(); + if (err) { + subject.error(err); + return; + } + subject.next(innerArgs.length <= 1 ? innerArgs[0] : innerArgs); + subject.complete(); + }; + try { + callbackFunc.apply(context, args.concat([handler])); + } + catch (err) { + if (Object(_util_canReportError__WEBPACK_IMPORTED_MODULE_3__["canReportError"])(subject)) { + subject.error(err); + } + else { + console.warn(err); + } + } + } + return subject.subscribe(subscriber); + } + else { + return scheduler.schedule(dispatch, 0, { params: params, subscriber: subscriber, context: context }); + } + }); + }; +} +function dispatch(state) { + var _this = this; + var params = state.params, subscriber = state.subscriber, context = state.context; + var callbackFunc = params.callbackFunc, args = params.args, scheduler = params.scheduler; + var subject = params.subject; + if (!subject) { + subject = params.subject = new _AsyncSubject__WEBPACK_IMPORTED_MODULE_1__["AsyncSubject"](); + var handler = function () { + var innerArgs = []; + for (var _i = 0; _i < arguments.length; _i++) { + innerArgs[_i] = arguments[_i]; + } + var err = innerArgs.shift(); + if (err) { + _this.add(scheduler.schedule(dispatchError, 0, { err: err, subject: subject })); + } + else { + var value = innerArgs.length <= 1 ? innerArgs[0] : innerArgs; + _this.add(scheduler.schedule(dispatchNext, 0, { value: value, subject: subject })); + } + }; + try { + callbackFunc.apply(context, args.concat([handler])); + } + catch (err) { + this.add(scheduler.schedule(dispatchError, 0, { err: err, subject: subject })); + } + } + this.add(subject.subscribe(subscriber)); +} +function dispatchNext(arg) { + var value = arg.value, subject = arg.subject; + subject.next(value); + subject.complete(); +} +function dispatchError(arg) { + var err = arg.err, subject = arg.subject; + subject.error(err); +} +//# sourceMappingURL=bindNodeCallback.js.map + + +/***/ }), +/* 400 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "forkJoin", function() { return forkJoin; }); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(193); +/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(178); +/* harmony import */ var _operators_map__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(231); +/* harmony import */ var _util_isObject__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(179); +/* harmony import */ var _from__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(218); +/** PURE_IMPORTS_START _Observable,_util_isArray,_operators_map,_util_isObject,_from PURE_IMPORTS_END */ + + + + + +function forkJoin() { + var sources = []; + for (var _i = 0; _i < arguments.length; _i++) { + sources[_i] = arguments[_i]; + } + if (sources.length === 1) { + var first_1 = sources[0]; + if (Object(_util_isArray__WEBPACK_IMPORTED_MODULE_1__["isArray"])(first_1)) { + return forkJoinInternal(first_1, null); + } + if (Object(_util_isObject__WEBPACK_IMPORTED_MODULE_3__["isObject"])(first_1) && Object.getPrototypeOf(first_1) === Object.prototype) { + var keys = Object.keys(first_1); + return forkJoinInternal(keys.map(function (key) { return first_1[key]; }), keys); + } + } + if (typeof sources[sources.length - 1] === 'function') { + var resultSelector_1 = sources.pop(); + sources = (sources.length === 1 && Object(_util_isArray__WEBPACK_IMPORTED_MODULE_1__["isArray"])(sources[0])) ? sources[0] : sources; + return forkJoinInternal(sources, null).pipe(Object(_operators_map__WEBPACK_IMPORTED_MODULE_2__["map"])(function (args) { return resultSelector_1.apply(void 0, args); })); + } + return forkJoinInternal(sources, null); +} +function forkJoinInternal(sources, keys) { + return new _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"](function (subscriber) { + var len = sources.length; + if (len === 0) { + subscriber.complete(); + return; + } + var values = new Array(len); + var completed = 0; + var emitted = 0; + var _loop_1 = function (i) { + var source = Object(_from__WEBPACK_IMPORTED_MODULE_4__["from"])(sources[i]); + var hasValue = false; + subscriber.add(source.subscribe({ + next: function (value) { + if (!hasValue) { + hasValue = true; + emitted++; + } + values[i] = value; + }, + error: function (err) { return subscriber.error(err); }, + complete: function () { + completed++; + if (completed === len || !hasValue) { + if (emitted === len) { + subscriber.next(keys ? + keys.reduce(function (result, key, i) { return (result[key] = values[i], result); }, {}) : + values); + } + subscriber.complete(); + } + } + })); + }; + for (var i = 0; i < len; i++) { + _loop_1(i); + } + }); +} +//# sourceMappingURL=forkJoin.js.map + + +/***/ }), +/* 401 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "fromEvent", function() { return fromEvent; }); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(193); +/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(178); +/* harmony import */ var _util_isFunction__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(173); +/* harmony import */ var _operators_map__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(231); +/** PURE_IMPORTS_START _Observable,_util_isArray,_util_isFunction,_operators_map PURE_IMPORTS_END */ + + + + +var toString = /*@__PURE__*/ (function () { return Object.prototype.toString; })(); +function fromEvent(target, eventName, options, resultSelector) { + if (Object(_util_isFunction__WEBPACK_IMPORTED_MODULE_2__["isFunction"])(options)) { + resultSelector = options; + options = undefined; + } + if (resultSelector) { + return fromEvent(target, eventName, options).pipe(Object(_operators_map__WEBPACK_IMPORTED_MODULE_3__["map"])(function (args) { return Object(_util_isArray__WEBPACK_IMPORTED_MODULE_1__["isArray"])(args) ? resultSelector.apply(void 0, args) : resultSelector(args); })); + } + return new _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"](function (subscriber) { + function handler(e) { + if (arguments.length > 1) { + subscriber.next(Array.prototype.slice.call(arguments)); + } + else { + subscriber.next(e); + } + } + setupSubscription(target, eventName, handler, subscriber, options); + }); +} +function setupSubscription(sourceObj, eventName, handler, subscriber, options) { + var unsubscribe; + if (isEventTarget(sourceObj)) { + var source_1 = sourceObj; + sourceObj.addEventListener(eventName, handler, options); + unsubscribe = function () { return source_1.removeEventListener(eventName, handler, options); }; + } + else if (isJQueryStyleEventEmitter(sourceObj)) { + var source_2 = sourceObj; + sourceObj.on(eventName, handler); + unsubscribe = function () { return source_2.off(eventName, handler); }; + } + else if (isNodeStyleEventEmitter(sourceObj)) { + var source_3 = sourceObj; + sourceObj.addListener(eventName, handler); + unsubscribe = function () { return source_3.removeListener(eventName, handler); }; + } + else if (sourceObj && sourceObj.length) { + for (var i = 0, len = sourceObj.length; i < len; i++) { + setupSubscription(sourceObj[i], eventName, handler, subscriber, options); + } + } + else { + throw new TypeError('Invalid event target'); + } + subscriber.add(unsubscribe); +} +function isNodeStyleEventEmitter(sourceObj) { + return sourceObj && typeof sourceObj.addListener === 'function' && typeof sourceObj.removeListener === 'function'; +} +function isJQueryStyleEventEmitter(sourceObj) { + return sourceObj && typeof sourceObj.on === 'function' && typeof sourceObj.off === 'function'; +} +function isEventTarget(sourceObj) { + return sourceObj && typeof sourceObj.addEventListener === 'function' && typeof sourceObj.removeEventListener === 'function'; +} +//# sourceMappingURL=fromEvent.js.map + + +/***/ }), +/* 402 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "fromEventPattern", function() { return fromEventPattern; }); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(193); +/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(178); +/* harmony import */ var _util_isFunction__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(173); +/* harmony import */ var _operators_map__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(231); +/** PURE_IMPORTS_START _Observable,_util_isArray,_util_isFunction,_operators_map PURE_IMPORTS_END */ + + + + +function fromEventPattern(addHandler, removeHandler, resultSelector) { + if (resultSelector) { + return fromEventPattern(addHandler, removeHandler).pipe(Object(_operators_map__WEBPACK_IMPORTED_MODULE_3__["map"])(function (args) { return Object(_util_isArray__WEBPACK_IMPORTED_MODULE_1__["isArray"])(args) ? resultSelector.apply(void 0, args) : resultSelector(args); })); + } + return new _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"](function (subscriber) { + var handler = function () { + var e = []; + for (var _i = 0; _i < arguments.length; _i++) { + e[_i] = arguments[_i]; + } + return subscriber.next(e.length === 1 ? e[0] : e); + }; + var retValue; + try { + retValue = addHandler(handler); + } + catch (err) { + subscriber.error(err); + return undefined; + } + if (!Object(_util_isFunction__WEBPACK_IMPORTED_MODULE_2__["isFunction"])(removeHandler)) { + return undefined; + } + return function () { return removeHandler(handler, retValue); }; + }); +} +//# sourceMappingURL=fromEventPattern.js.map + + +/***/ }), +/* 403 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "generate", function() { return generate; }); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(193); +/* harmony import */ var _util_identity__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(232); +/* harmony import */ var _util_isScheduler__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(206); +/** PURE_IMPORTS_START _Observable,_util_identity,_util_isScheduler PURE_IMPORTS_END */ + + + +function generate(initialStateOrOptions, condition, iterate, resultSelectorOrObservable, scheduler) { + var resultSelector; + var initialState; + if (arguments.length == 1) { + var options = initialStateOrOptions; + initialState = options.initialState; + condition = options.condition; + iterate = options.iterate; + resultSelector = options.resultSelector || _util_identity__WEBPACK_IMPORTED_MODULE_1__["identity"]; + scheduler = options.scheduler; + } + else if (resultSelectorOrObservable === undefined || Object(_util_isScheduler__WEBPACK_IMPORTED_MODULE_2__["isScheduler"])(resultSelectorOrObservable)) { + initialState = initialStateOrOptions; + resultSelector = _util_identity__WEBPACK_IMPORTED_MODULE_1__["identity"]; + scheduler = resultSelectorOrObservable; + } + else { + initialState = initialStateOrOptions; + resultSelector = resultSelectorOrObservable; + } + return new _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"](function (subscriber) { + var state = initialState; + if (scheduler) { + return scheduler.schedule(dispatch, 0, { + subscriber: subscriber, + iterate: iterate, + condition: condition, + resultSelector: resultSelector, + state: state + }); + } + do { + if (condition) { + var conditionResult = void 0; + try { + conditionResult = condition(state); + } + catch (err) { + subscriber.error(err); + return undefined; + } + if (!conditionResult) { + subscriber.complete(); + break; + } + } + var value = void 0; + try { + value = resultSelector(state); + } + catch (err) { + subscriber.error(err); + return undefined; + } + subscriber.next(value); + if (subscriber.closed) { + break; + } + try { + state = iterate(state); + } + catch (err) { + subscriber.error(err); + return undefined; + } + } while (true); + return undefined; + }); +} +function dispatch(state) { + var subscriber = state.subscriber, condition = state.condition; + if (subscriber.closed) { + return undefined; + } + if (state.needIterate) { + try { + state.state = state.iterate(state.state); + } + catch (err) { + subscriber.error(err); + return undefined; + } + } + else { + state.needIterate = true; + } + if (condition) { + var conditionResult = void 0; + try { + conditionResult = condition(state.state); + } + catch (err) { + subscriber.error(err); + return undefined; + } + if (!conditionResult) { + subscriber.complete(); + return undefined; + } + if (subscriber.closed) { + return undefined; + } + } + var value; + try { + value = state.resultSelector(state.state); + } + catch (err) { + subscriber.error(err); + return undefined; + } + if (subscriber.closed) { + return undefined; + } + subscriber.next(value); + if (subscriber.closed) { + return undefined; + } + return this.schedule(state); +} +//# sourceMappingURL=generate.js.map + + +/***/ }), +/* 404 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "iif", function() { return iif; }); +/* harmony import */ var _defer__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(333); +/* harmony import */ var _empty__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(242); +/** PURE_IMPORTS_START _defer,_empty PURE_IMPORTS_END */ + + +function iif(condition, trueResult, falseResult) { + if (trueResult === void 0) { + trueResult = _empty__WEBPACK_IMPORTED_MODULE_1__["EMPTY"]; + } + if (falseResult === void 0) { + falseResult = _empty__WEBPACK_IMPORTED_MODULE_1__["EMPTY"]; + } + return Object(_defer__WEBPACK_IMPORTED_MODULE_0__["defer"])(function () { return condition() ? trueResult : falseResult; }); +} +//# sourceMappingURL=iif.js.map + + +/***/ }), +/* 405 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "interval", function() { return interval; }); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(193); +/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(199); +/* harmony import */ var _util_isNumeric__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(205); +/** PURE_IMPORTS_START _Observable,_scheduler_async,_util_isNumeric PURE_IMPORTS_END */ + + + +function interval(period, scheduler) { + if (period === void 0) { + period = 0; + } + if (scheduler === void 0) { + scheduler = _scheduler_async__WEBPACK_IMPORTED_MODULE_1__["async"]; + } + if (!Object(_util_isNumeric__WEBPACK_IMPORTED_MODULE_2__["isNumeric"])(period) || period < 0) { + period = 0; + } + if (!scheduler || typeof scheduler.schedule !== 'function') { + scheduler = _scheduler_async__WEBPACK_IMPORTED_MODULE_1__["async"]; + } + return new _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"](function (subscriber) { + subscriber.add(scheduler.schedule(dispatch, period, { subscriber: subscriber, counter: 0, period: period })); + return subscriber; + }); +} +function dispatch(state) { + var subscriber = state.subscriber, counter = state.counter, period = state.period; + subscriber.next(counter); + this.schedule({ subscriber: subscriber, counter: counter + 1, period: period }, period); +} +//# sourceMappingURL=interval.js.map + + +/***/ }), +/* 406 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "NEVER", function() { return NEVER; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "never", function() { return never; }); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(193); +/* harmony import */ var _util_noop__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(197); +/** PURE_IMPORTS_START _Observable,_util_noop PURE_IMPORTS_END */ + + +var NEVER = /*@__PURE__*/ new _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"](_util_noop__WEBPACK_IMPORTED_MODULE_1__["noop"]); +function never() { + return NEVER; +} +//# sourceMappingURL=never.js.map + + +/***/ }), +/* 407 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "onErrorResumeNext", function() { return onErrorResumeNext; }); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(193); +/* harmony import */ var _from__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(218); +/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(178); +/* harmony import */ var _empty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(242); +/** PURE_IMPORTS_START _Observable,_from,_util_isArray,_empty PURE_IMPORTS_END */ + + + + +function onErrorResumeNext() { + var sources = []; + for (var _i = 0; _i < arguments.length; _i++) { + sources[_i] = arguments[_i]; + } + if (sources.length === 0) { + return _empty__WEBPACK_IMPORTED_MODULE_3__["EMPTY"]; + } + var first = sources[0], remainder = sources.slice(1); + if (sources.length === 1 && Object(_util_isArray__WEBPACK_IMPORTED_MODULE_2__["isArray"])(first)) { + return onErrorResumeNext.apply(void 0, first); + } + return new _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"](function (subscriber) { + var subNext = function () { return subscriber.add(onErrorResumeNext.apply(void 0, remainder).subscribe(subscriber)); }; + return Object(_from__WEBPACK_IMPORTED_MODULE_1__["from"])(first).subscribe({ + next: function (value) { subscriber.next(value); }, + error: subNext, + complete: subNext, + }); + }); +} +//# sourceMappingURL=onErrorResumeNext.js.map + + +/***/ }), +/* 408 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "pairs", function() { return pairs; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "dispatch", function() { return dispatch; }); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(193); +/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(177); +/** PURE_IMPORTS_START _Observable,_Subscription PURE_IMPORTS_END */ + + +function pairs(obj, scheduler) { + if (!scheduler) { + return new _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"](function (subscriber) { + var keys = Object.keys(obj); + for (var i = 0; i < keys.length && !subscriber.closed; i++) { + var key = keys[i]; + if (obj.hasOwnProperty(key)) { + subscriber.next([key, obj[key]]); + } + } + subscriber.complete(); + }); + } + else { + return new _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"](function (subscriber) { + var keys = Object.keys(obj); + var subscription = new _Subscription__WEBPACK_IMPORTED_MODULE_1__["Subscription"](); + subscription.add(scheduler.schedule(dispatch, 0, { keys: keys, index: 0, subscriber: subscriber, subscription: subscription, obj: obj })); + return subscription; + }); + } +} +function dispatch(state) { + var keys = state.keys, index = state.index, subscriber = state.subscriber, subscription = state.subscription, obj = state.obj; + if (!subscriber.closed) { + if (index < keys.length) { + var key = keys[index]; + subscriber.next([key, obj[key]]); + subscription.add(this.schedule({ keys: keys, index: index + 1, subscriber: subscriber, subscription: subscription, obj: obj })); + } + else { + subscriber.complete(); + } + } +} +//# sourceMappingURL=pairs.js.map + + +/***/ }), +/* 409 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "partition", function() { return partition; }); +/* harmony import */ var _util_not__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(289); +/* harmony import */ var _util_subscribeTo__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(184); +/* harmony import */ var _operators_filter__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(251); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(193); +/** PURE_IMPORTS_START _util_not,_util_subscribeTo,_operators_filter,_Observable PURE_IMPORTS_END */ + + + + +function partition(source, predicate, thisArg) { + return [ + Object(_operators_filter__WEBPACK_IMPORTED_MODULE_2__["filter"])(predicate, thisArg)(new _Observable__WEBPACK_IMPORTED_MODULE_3__["Observable"](Object(_util_subscribeTo__WEBPACK_IMPORTED_MODULE_1__["subscribeTo"])(source))), + Object(_operators_filter__WEBPACK_IMPORTED_MODULE_2__["filter"])(Object(_util_not__WEBPACK_IMPORTED_MODULE_0__["not"])(predicate, thisArg))(new _Observable__WEBPACK_IMPORTED_MODULE_3__["Observable"](Object(_util_subscribeTo__WEBPACK_IMPORTED_MODULE_1__["subscribeTo"])(source))) + ]; +} +//# sourceMappingURL=partition.js.map + + +/***/ }), +/* 410 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "range", function() { return range; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "dispatch", function() { return dispatch; }); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(193); +/** PURE_IMPORTS_START _Observable PURE_IMPORTS_END */ + +function range(start, count, scheduler) { + if (start === void 0) { + start = 0; + } + return new _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"](function (subscriber) { + if (count === undefined) { + count = start; + start = 0; + } + var index = 0; + var current = start; + if (scheduler) { + return scheduler.schedule(dispatch, 0, { + index: index, count: count, start: start, subscriber: subscriber + }); + } + else { + do { + if (index++ >= count) { + subscriber.complete(); + break; + } + subscriber.next(current++); + if (subscriber.closed) { + break; + } + } while (true); + } + return undefined; + }); +} +function dispatch(state) { + var start = state.start, index = state.index, count = state.count, subscriber = state.subscriber; + if (index >= count) { + subscriber.complete(); + return; + } + subscriber.next(start); + if (subscriber.closed) { + return; + } + state.index = index + 1; + state.start = start + 1; + this.schedule(state); +} +//# sourceMappingURL=range.js.map + + +/***/ }), +/* 411 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "using", function() { return using; }); +/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(193); +/* harmony import */ var _from__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(218); +/* harmony import */ var _empty__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(242); +/** PURE_IMPORTS_START _Observable,_from,_empty PURE_IMPORTS_END */ + + + +function using(resourceFactory, observableFactory) { + return new _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"](function (subscriber) { + var resource; + try { + resource = resourceFactory(); + } + catch (err) { + subscriber.error(err); + return undefined; + } + var result; + try { + result = observableFactory(resource); + } + catch (err) { + subscriber.error(err); + return undefined; + } + var source = result ? Object(_from__WEBPACK_IMPORTED_MODULE_1__["from"])(result) : _empty__WEBPACK_IMPORTED_MODULE_2__["EMPTY"]; + var subscription = source.subscribe(subscriber); + return function () { + subscription.unsubscribe(); + if (resource) { + resource.unsubscribe(); + } + }; + }); +} +//# sourceMappingURL=using.js.map + + +/***/ }), +/* 412 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var childProcess = __webpack_require__(352); +var spawn = childProcess.spawn; +var exec = childProcess.exec; + +module.exports = function (pid, signal, callback) { + var tree = {}; + var pidsToProcess = {}; + tree[pid] = []; + pidsToProcess[pid] = 1; + + if (typeof signal === 'function' && callback === undefined) { + callback = signal; + signal = undefined; + } + + switch (process.platform) { + case 'win32': + exec('taskkill /pid ' + pid + ' /T /F', callback); + break; + case 'darwin': + buildProcessTree(pid, tree, pidsToProcess, function (parentPid) { + return spawn('pgrep', ['-P', parentPid]); + }, function () { + killAll(tree, signal, callback); + }); + break; + // case 'sunos': + // buildProcessTreeSunOS(pid, tree, pidsToProcess, function () { + // killAll(tree, signal, callback); + // }); + // break; + default: // Linux + buildProcessTree(pid, tree, pidsToProcess, function (parentPid) { + return spawn('ps', ['-o', 'pid', '--no-headers', '--ppid', parentPid]); + }, function () { + killAll(tree, signal, callback); + }); + break; + } +}; + +function killAll (tree, signal, callback) { + var killed = {}; + try { + Object.keys(tree).forEach(function (pid) { + tree[pid].forEach(function (pidpid) { + if (!killed[pidpid]) { + killPid(pidpid, signal); + killed[pidpid] = 1; + } + }); + if (!killed[pid]) { + killPid(pid, signal); + killed[pid] = 1; + } + }); + } catch (err) { + if (callback) { + return callback(err); + } else { + throw err; + } + } + if (callback) { + return callback(); + } +} + +function killPid(pid, signal) { + try { + process.kill(parseInt(pid, 10), signal); + } + catch (err) { + if (err.code !== 'ESRCH') throw err; + } +} + +function buildProcessTree (parentPid, tree, pidsToProcess, spawnChildProcessesList, cb) { + var ps = spawnChildProcessesList(parentPid); + var allData = ''; + ps.stdout.on('data', function (data) { + var data = data.toString('ascii'); + allData += data; + }); + + var onClose = function (code) { + delete pidsToProcess[parentPid]; + + if (code != 0) { + // no more parent processes + if (Object.keys(pidsToProcess).length == 0) { + cb(); + } + return; + } + + allData.match(/\d+/g).forEach(function (pid) { + pid = parseInt(pid, 10); + tree[parentPid].push(pid); + tree[pid] = []; + pidsToProcess[pid] = 1; + buildProcessTree(pid, tree, pidsToProcess, spawnChildProcessesList, cb); + }); + }; + + ps.on('close', onClose); +} + + +/***/ }), +/* 413 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +Object.defineProperty(exports, "__esModule", { value: true }); +const tslib_1 = __webpack_require__(36); +const Rx = tslib_1.__importStar(__webpack_require__(392)); +const operators_1 = __webpack_require__(169); +const SEP = /\r?\n/; +const observe_readable_1 = __webpack_require__(414); +/** + * Creates an Observable from a Readable Stream that: + * - splits data from `readable` into lines + * - completes when `readable` emits "end" + * - fails if `readable` emits "errors" + * + * @param {ReadableStream} readable + * @return {Rx.Observable} + */ +function observeLines(readable) { + const done$ = observe_readable_1.observeReadable(readable).pipe(operators_1.share()); + const scan$ = Rx.fromEvent(readable, 'data').pipe(operators_1.scan(({ buffer }, chunk) => { + buffer += chunk; + const lines = []; + while (true) { + const match = buffer.match(SEP); + if (!match || match.index === undefined) { + break; + } + lines.push(buffer.slice(0, match.index)); + buffer = buffer.slice(match.index + match[0].length); + } + return { buffer, lines }; + }, { buffer: '' }), + // stop if done completes or errors + operators_1.takeUntil(done$.pipe(operators_1.materialize())), operators_1.share()); + return Rx.merge( + // use done$ to provide completion/errors + done$, + // merge in the "lines" from each step + scan$.pipe(operators_1.mergeMap(({ lines }) => lines || [])), + // inject the "unsplit" data at the end + scan$.pipe(operators_1.last(), operators_1.mergeMap(({ buffer }) => (buffer ? [buffer] : [])), + // if there were no lines, last() will error, so catch and complete + operators_1.catchError(() => Rx.empty()))); +} +exports.observeLines = observeLines; + + +/***/ }), +/* 414 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +Object.defineProperty(exports, "__esModule", { value: true }); +const tslib_1 = __webpack_require__(36); +const Rx = tslib_1.__importStar(__webpack_require__(392)); +const operators_1 = __webpack_require__(169); +/** + * Produces an Observable from a ReadableSteam that: + * - completes on the first "end" event + * - fails on the first "error" event + */ +function observeReadable(readable) { + return Rx.race(Rx.fromEvent(readable, 'end').pipe(operators_1.first(), operators_1.ignoreElements()), Rx.fromEvent(readable, 'error').pipe(operators_1.first(), operators_1.mergeMap(err => Rx.throwError(err)))); +} +exports.observeReadable = observeReadable; + + +/***/ }), +/* 415 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +Object.defineProperty(exports, "__esModule", { value: true }); +var tooling_log_1 = __webpack_require__(416); +exports.ToolingLog = tooling_log_1.ToolingLog; +var tooling_log_text_writer_1 = __webpack_require__(417); +exports.ToolingLogTextWriter = tooling_log_text_writer_1.ToolingLogTextWriter; +var log_levels_1 = __webpack_require__(418); +exports.pickLevelFromFlags = log_levels_1.pickLevelFromFlags; +var tooling_log_collecting_writer_1 = __webpack_require__(419); +exports.ToolingLogCollectingWriter = tooling_log_collecting_writer_1.ToolingLogCollectingWriter; + + +/***/ }), +/* 416 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +Object.defineProperty(exports, "__esModule", { value: true }); +const tslib_1 = __webpack_require__(36); +const Rx = tslib_1.__importStar(__webpack_require__(392)); +const tooling_log_text_writer_1 = __webpack_require__(417); +class ToolingLog { + constructor(writerConfig) { + this.identWidth = 0; + this.writers = writerConfig ? [new tooling_log_text_writer_1.ToolingLogTextWriter(writerConfig)] : []; + this.written$ = new Rx.Subject(); + } + indent(delta = 0) { + this.identWidth = Math.max(this.identWidth + delta, 0); + return this.identWidth; + } + verbose(...args) { + this.sendToWriters('verbose', args); + } + debug(...args) { + this.sendToWriters('debug', args); + } + info(...args) { + this.sendToWriters('info', args); + } + success(...args) { + this.sendToWriters('success', args); + } + warning(...args) { + this.sendToWriters('warning', args); + } + error(error) { + this.sendToWriters('error', [error]); + } + write(...args) { + this.sendToWriters('write', args); + } + getWriters() { + return this.writers.slice(0); + } + setWriters(writers) { + this.writers = [...writers]; + } + getWritten$() { + return this.written$.asObservable(); + } + sendToWriters(type, args) { + const msg = { + type, + indent: this.identWidth, + args, + }; + let written = false; + for (const writer of this.writers) { + if (writer.write(msg)) { + written = true; + } + } + if (written) { + this.written$.next(msg); + } + } +} +exports.ToolingLog = ToolingLog; + + +/***/ }), +/* 417 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +Object.defineProperty(exports, "__esModule", { value: true }); +const tslib_1 = __webpack_require__(36); +const util_1 = __webpack_require__(29); +const chalk_1 = tslib_1.__importDefault(__webpack_require__(2)); +const log_levels_1 = __webpack_require__(418); +const { magentaBright, yellow, red, blue, green, dim } = chalk_1.default; +const PREFIX_INDENT = ' '.repeat(6); +const MSG_PREFIXES = { + verbose: ` ${magentaBright('sill')} `, + debug: ` ${dim('debg')} `, + info: ` ${blue('info')} `, + success: ` ${green('succ')} `, + warning: ` ${yellow('warn')} `, + error: `${red('ERROR')} `, +}; +const has = (obj, key) => obj.hasOwnProperty(key); +function shouldWriteType(level, type) { + if (type === 'write') { + return true; + } + return Boolean(level.flags[type === 'success' ? 'info' : type]); +} +function stringifyError(error) { + if (typeof error !== 'string' && !(error instanceof Error)) { + error = new Error(`"${error}" thrown`); + } + if (typeof error === 'string') { + return error; + } + return error.stack || error.message || error; +} +class ToolingLogTextWriter { + constructor(config) { + this.level = log_levels_1.parseLogLevel(config.level); + this.writeTo = config.writeTo; + if (!this.writeTo || typeof this.writeTo.write !== 'function') { + throw new Error('ToolingLogTextWriter requires the `writeTo` option be set to a stream (like process.stdout)'); + } + } + write({ type, indent, args }) { + if (!shouldWriteType(this.level, type)) { + return false; + } + const txt = type === 'error' ? stringifyError(args[0]) : util_1.format(args[0], ...args.slice(1)); + const prefix = has(MSG_PREFIXES, type) ? MSG_PREFIXES[type] : ''; + (prefix + txt).split('\n').forEach((line, i) => { + let lineIndent = ''; + if (indent > 0) { + // if we are indenting write some spaces followed by a symbol + lineIndent += ' '.repeat(indent - 1); + lineIndent += line.startsWith('-') ? '└' : '│'; + } + if (line && prefix && i > 0) { + // apply additional indentation to lines after + // the first if this message gets a prefix + lineIndent += PREFIX_INDENT; + } + this.writeTo.write(`${lineIndent}${line}\n`); + }); + return true; + } +} +exports.ToolingLogTextWriter = ToolingLogTextWriter; + + +/***/ }), +/* 418 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +Object.defineProperty(exports, "__esModule", { value: true }); +const LEVELS = ['silent', 'error', 'warning', 'info', 'debug', 'verbose']; +function pickLevelFromFlags(flags, options = {}) { + if (flags.verbose) + return 'verbose'; + if (flags.debug) + return 'debug'; + if (flags.quiet) + return 'error'; + if (flags.silent) + return 'silent'; + return options.default || 'info'; +} +exports.pickLevelFromFlags = pickLevelFromFlags; +function parseLogLevel(name) { + const i = LEVELS.indexOf(name); + if (i === -1) { + const msg = `Invalid log level "${name}" ` + `(expected one of ${LEVELS.join(',')})`; + throw new Error(msg); + } + const flags = {}; + LEVELS.forEach((level, levelI) => { + flags[level] = levelI <= i; + }); + return { + name, + flags: flags, + }; +} +exports.parseLogLevel = parseLogLevel; + + +/***/ }), +/* 419 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +Object.defineProperty(exports, "__esModule", { value: true }); +const tooling_log_text_writer_1 = __webpack_require__(417); +class ToolingLogCollectingWriter extends tooling_log_text_writer_1.ToolingLogTextWriter { + constructor() { + super({ + level: 'verbose', + writeTo: { + write: msg => { + // trim trailing new line + this.messages.push(msg.slice(0, -1)); + }, + }, + }); + this.messages = []; + } +} +exports.ToolingLogCollectingWriter = ToolingLogCollectingWriter; + + +/***/ }), +/* 420 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +Object.defineProperty(exports, "__esModule", { value: true }); +var absolute_path_serializer_1 = __webpack_require__(421); +exports.createAbsolutePathSerializer = absolute_path_serializer_1.createAbsolutePathSerializer; + + +/***/ }), +/* 421 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +Object.defineProperty(exports, "__esModule", { value: true }); +function createAbsolutePathSerializer(rootPath) { + return { + print: (value) => value.replace(rootPath, '').replace(/\\/g, '/'), + test: (value) => typeof value === 'string' && value.startsWith(rootPath), + }; +} +exports.createAbsolutePathSerializer = createAbsolutePathSerializer; + + +/***/ }), +/* 422 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +Object.defineProperty(exports, "__esModule", { value: true }); +const path_1 = __webpack_require__(16); +exports.CA_CERT_PATH = path_1.resolve(__dirname, '../certs/ca.crt'); +exports.ES_KEY_PATH = path_1.resolve(__dirname, '../certs/elasticsearch.key'); +exports.ES_CERT_PATH = path_1.resolve(__dirname, '../certs/elasticsearch.crt'); +exports.ES_P12_PATH = path_1.resolve(__dirname, '../certs/elasticsearch.p12'); +exports.ES_P12_PASSWORD = 'storepass'; +exports.ES_EMPTYPASSWORD_P12_PATH = path_1.resolve(__dirname, '../certs/elasticsearch_emptypassword.p12'); +exports.ES_NOPASSWORD_P12_PATH = path_1.resolve(__dirname, '../certs/elasticsearch_nopassword.p12'); +exports.KBN_KEY_PATH = path_1.resolve(__dirname, '../certs/kibana.key'); +exports.KBN_CERT_PATH = path_1.resolve(__dirname, '../certs/kibana.crt'); +exports.KBN_P12_PATH = path_1.resolve(__dirname, '../certs/kibana.p12'); +exports.KBN_P12_PASSWORD = 'storepass'; + + +/***/ }), +/* 423 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +Object.defineProperty(exports, "__esModule", { value: true }); +var run_1 = __webpack_require__(424); +exports.run = run_1.run; +var fail_1 = __webpack_require__(425); +exports.createFailError = fail_1.createFailError; +exports.createFlagError = fail_1.createFlagError; +exports.combineErrors = fail_1.combineErrors; +exports.isFailError = fail_1.isFailError; + + +/***/ }), +/* 424 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +Object.defineProperty(exports, "__esModule", { value: true }); +const tslib_1 = __webpack_require__(36); +// @ts-ignore @types are outdated and module is super simple +const exit_hook_1 = tslib_1.__importDefault(__webpack_require__(348)); +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); + if (flags.help) { + process.stderr.write(flags_1.getHelp(options)); + process.exit(1); + } + const log = new tooling_log_1.ToolingLog({ + level: tooling_log_1.pickLevelFromFlags(flags), + writeTo: process.stdout, + }); + process.on('unhandledRejection', error => { + log.error('UNHANDLED PROMISE REJECTION'); + log.error(error); + process.exit(1); + }); + const handleErrorWithoutExit = (error) => { + if (fail_1.isFailError(error)) { + log.error(error.message); + if (error.showHelp) { + log.write(flags_1.getHelp(options)); + } + process.exitCode = error.exitCode; + } + else { + log.error('UNHANDLED ERROR'); + log.error(error); + process.exitCode = 1; + } + }; + const doCleanup = () => { + const tasks = cleanupTasks.slice(0); + cleanupTasks.length = 0; + for (const task of tasks) { + try { + task(); + } + catch (error) { + handleErrorWithoutExit(error); + } + } + }; + const unhookExit = exit_hook_1.default(doCleanup); + const cleanupTasks = [unhookExit]; + try { + 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 { + await fn({ + log, + flags, + addCleanupTask: (task) => cleanupTasks.push(task), + }); + } + finally { + doCleanup(); + } + } + catch (error) { + handleErrorWithoutExit(error); + process.exit(); + } +} +exports.run = run; + + +/***/ }), +/* 425 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +Object.defineProperty(exports, "__esModule", { value: true }); +const util_1 = __webpack_require__(29); +const FAIL_TAG = Symbol('fail error'); +function createFailError(reason, options = {}) { + const { exitCode = 1, showHelp = false } = options; + return Object.assign(new Error(reason), { + exitCode, + showHelp, + [FAIL_TAG]: true, + }); +} +exports.createFailError = createFailError; +function createFlagError(reason) { + return createFailError(reason, { + showHelp: true, + }); +} +exports.createFlagError = createFlagError; +function isFailError(error) { + return Boolean(error && error[FAIL_TAG]); +} +exports.isFailError = isFailError; +function combineErrors(errors) { + if (errors.length === 1) { + return errors[0]; + } + const exitCode = errors + .filter(isFailError) + .reduce((acc, error) => Math.max(acc, error.exitCode), 1); + const showHelp = errors.some(error => isFailError(error) && error.showHelp); + const message = errors.reduce((acc, error) => { + if (isFailError(error)) { + return acc + '\n' + error.message; + } + return acc + `\nUNHANDLED ERROR\n${util_1.inspect(error)}`; + }, ''); + return createFailError(`${errors.length} errors:\n${message}`, { + exitCode, + showHelp, + }); +} +exports.combineErrors = combineErrors; + + +/***/ }), +/* 426 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +Object.defineProperty(exports, "__esModule", { value: true }); +const tslib_1 = __webpack_require__(36); +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 unexpectedNames = new Set(); + const flagOpts = options.flags || {}; + const { verbose, quiet, silent, debug, help, _, ...others } = getopts_1.default(argv, { + string: flagOpts.string, + boolean: [...(flagOpts.boolean || []), 'verbose', 'quiet', 'silent', 'debug', 'help'], + alias: { + ...(flagOpts.alias || {}), + v: 'verbose', + }, + default: flagOpts.default, + unknown: (name) => { + 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, + silent, + debug, + help, + _, + unexpected, + ...others, + }; +} +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(((_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 + --debug Log debug messages (less than verbose) + --quiet Only log errors + --silent Don't log anything + --help Show this message + `) + .split('\n') + .filter(Boolean) + .join('\n '); + return ` + ${usage} + + ${dedent_1.default(options.description || 'Runs a dev task') + .split('\n') + .join('\n ')} + + Options: + ${optionHelp + '\n\n'}`; +} +exports.getHelp = getHelp; + + +/***/ }), +/* 427 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +const EMPTYARR = [] +const SHORTSPLIT = /$|[!-@[-`{-~][\s\S]*/g +const isArray = Array.isArray + +const parseValue = function(any) { + if (any === "") return "" + if (any === "false") return false + const maybe = Number(any) + return maybe * 0 === 0 ? maybe : any +} + +const parseAlias = function(aliases) { + let out = {}, + key, + alias, + prev, + len, + any, + i, + k + + for (key in aliases) { + any = aliases[key] + alias = out[key] = isArray(any) ? any : [any] + + for (i = 0, len = alias.length; i < len; i++) { + prev = out[alias[i]] = [key] + + for (k = 0; k < len; k++) { + if (i !== k) prev.push(alias[k]) + } + } + } + + return out +} + +const parseDefault = function(aliases, defaults) { + let out = {}, + key, + alias, + value, + len, + i + + for (key in defaults) { + value = defaults[key] + alias = aliases[key] + + out[key] = value + + if (alias === undefined) { + aliases[key] = EMPTYARR + } else { + for (i = 0, len = alias.length; i < len; i++) { + out[alias[i]] = value + } + } + } + + return out +} + +const parseOptions = function(aliases, options, value) { + let out = {}, + key, + alias, + len, + end, + i, + k + + if (options !== undefined) { + for (i = 0, len = options.length; i < len; i++) { + key = options[i] + alias = aliases[key] + + out[key] = value + + if (alias === undefined) { + aliases[key] = EMPTYARR + } else { + for (k = 0, end = alias.length; k < end; k++) { + out[alias[k]] = value + } + } + } + } + + return out +} + +const write = function(out, key, value, aliases, unknown) { + let i, + prev, + alias = aliases[key], + len = alias === undefined ? -1 : alias.length + + if (len >= 0 || unknown === undefined || unknown(key)) { + prev = out[key] + + if (prev === undefined) { + out[key] = value + } else { + if (isArray(prev)) { + prev.push(value) + } else { + out[key] = [prev, value] + } + } + + for (i = 0; i < len; i++) { + out[alias[i]] = out[key] + } + } +} + +const getopts = function(argv, opts) { + let unknown = (opts = opts || {}).unknown, + aliases = parseAlias(opts.alias), + strings = parseOptions(aliases, opts.string, ""), + values = parseDefault(aliases, opts.default), + bools = parseOptions(aliases, opts.boolean, false), + stopEarly = opts.stopEarly, + _ = [], + out = { _ }, + i = 0, + k = 0, + len = argv.length, + key, + arg, + end, + match, + value + + for (; i < len; i++) { + arg = argv[i] + + if (arg[0] !== "-" || arg === "-") { + if (stopEarly) while (i < len) _.push(argv[i++]) + else _.push(arg) + } else if (arg === "--") { + while (++i < len) _.push(argv[i]) + } else if (arg[1] === "-") { + end = arg.indexOf("=", 2) + if (arg[2] === "n" && arg[3] === "o" && arg[4] === "-") { + key = arg.slice(5, end >= 0 ? end : undefined) + value = false + } else if (end >= 0) { + key = arg.slice(2, end) + value = + bools[key] !== undefined || + (strings[key] === undefined + ? parseValue(arg.slice(end + 1)) + : arg.slice(end + 1)) + } else { + key = arg.slice(2) + value = + bools[key] !== undefined || + (len === i + 1 || argv[i + 1][0] === "-" + ? strings[key] === undefined + ? true + : "" + : strings[key] === undefined + ? parseValue(argv[++i]) + : argv[++i]) + } + write(out, key, value, aliases, unknown) + } else { + SHORTSPLIT.lastIndex = 2 + match = SHORTSPLIT.exec(arg) + end = match.index + value = match[0] + + for (k = 1; k < end; k++) { + write( + out, + (key = arg[k]), + k + 1 < end + ? strings[key] === undefined || + arg.substring(k + 1, (k = end)) + value + : value === "" + ? len === i + 1 || argv[i + 1][0] === "-" + ? strings[key] === undefined || "" + : bools[key] !== undefined || + (strings[key] === undefined ? parseValue(argv[++i]) : argv[++i]) + : bools[key] !== undefined || + (strings[key] === undefined ? parseValue(value) : value), + aliases, + unknown + ) + } + } + } + + for (key in values) if (out[key] === undefined) out[key] = values[key] + for (key in bools) if (out[key] === undefined) out[key] = false + for (key in strings) if (out[key] === undefined) out[key] = "" + + return out +} + +module.exports = getopts + + +/***/ }), +/* 428 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +Object.defineProperty(exports, "__esModule", { value: true }); +const tslib_1 = __webpack_require__(36); +const path_1 = tslib_1.__importDefault(__webpack_require__(16)); +const fs_1 = tslib_1.__importDefault(__webpack_require__(23)); +const load_json_file_1 = tslib_1.__importDefault(__webpack_require__(429)); +const isKibanaDir = (dir) => { + try { + const path = path_1.default.resolve(dir, 'package.json'); + const json = load_json_file_1.default.sync(path); + if (json && typeof json === 'object' && 'name' in json && json.name === 'kibana') { + return true; + } + } + catch (error) { + if (error && error.code === 'ENOENT') { + return false; + } + throw error; + } +}; +// search for the kibana directory, since this file is moved around it might +// not be where we think but should always be a relatively close parent +// of this directory +const startDir = fs_1.default.realpathSync(__dirname); +const { root: rootDir } = path_1.default.parse(startDir); +let cursor = startDir; +while (true) { + if (isKibanaDir(cursor)) { + break; + } + const parent = path_1.default.dirname(cursor); + if (parent === rootDir) { + throw new Error(`unable to find kibana directory from ${startDir}`); + } + cursor = parent; +} +exports.REPO_ROOT = cursor; + + +/***/ }), +/* 429 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +const path = __webpack_require__(16); +const {promisify} = __webpack_require__(29); +const fs = __webpack_require__(430); +const stripBom = __webpack_require__(434); +const parseJson = __webpack_require__(435); + +const parse = (data, filePath, options = {}) => { + data = stripBom(data); + + if (typeof options.beforeParse === 'function') { + data = options.beforeParse(data); + } + + return parseJson(data, options.reviver, path.relative(process.cwd(), filePath)); +}; + +module.exports = async (filePath, options) => parse(await promisify(fs.readFile)(filePath, 'utf8'), filePath, options); +module.exports.sync = (filePath, options) => parse(fs.readFileSync(filePath, 'utf8'), filePath, options); + + +/***/ }), +/* 430 */ +/***/ (function(module, exports, __webpack_require__) { + +var fs = __webpack_require__(23) +var polyfills = __webpack_require__(431) +var legacy = __webpack_require__(432) +var clone = __webpack_require__(433) + +var queue = [] + +var util = __webpack_require__(29) + +function noop () {} + +var debug = noop +if (util.debuglog) + debug = util.debuglog('gfs4') +else if (/\bgfs4\b/i.test(process.env.NODE_DEBUG || '')) + debug = function() { + var m = util.format.apply(util, arguments) + m = 'GFS4: ' + m.split(/\n/).join('\nGFS4: ') + console.error(m) + } + +if (/\bgfs4\b/i.test(process.env.NODE_DEBUG || '')) { + process.on('exit', function() { + debug(queue) + __webpack_require__(30).equal(queue.length, 0) + }) +} + +module.exports = patch(clone(fs)) +if (process.env.TEST_GRACEFUL_FS_GLOBAL_PATCH && !fs.__patched) { + module.exports = patch(fs) + fs.__patched = true; +} + +// Always patch fs.close/closeSync, because we want to +// retry() whenever a close happens *anywhere* in the program. +// This is essential when multiple graceful-fs instances are +// in play at the same time. +module.exports.close = (function (fs$close) { return function (fd, cb) { + return fs$close.call(fs, fd, function (err) { + if (!err) + retry() + + if (typeof cb === 'function') + cb.apply(this, arguments) + }) +}})(fs.close) + +module.exports.closeSync = (function (fs$closeSync) { return function (fd) { + // Note that graceful-fs also retries when fs.closeSync() fails. + // Looks like a bug to me, although it's probably a harmless one. + var rval = fs$closeSync.apply(fs, arguments) + retry() + return rval +}})(fs.closeSync) + +// Only patch fs once, otherwise we'll run into a memory leak if +// graceful-fs is loaded multiple times, such as in test environments that +// reset the loaded modules between tests. +// We look for the string `graceful-fs` from the comment above. This +// way we are not adding any extra properties and it will detect if older +// versions of graceful-fs are installed. +if (!/\bgraceful-fs\b/.test(fs.closeSync.toString())) { + fs.closeSync = module.exports.closeSync; + fs.close = module.exports.close; +} + +function patch (fs) { + // Everything that references the open() function needs to be in here + polyfills(fs) + fs.gracefulify = patch + fs.FileReadStream = ReadStream; // Legacy name. + fs.FileWriteStream = WriteStream; // Legacy name. + fs.createReadStream = createReadStream + fs.createWriteStream = createWriteStream + var fs$readFile = fs.readFile + fs.readFile = readFile + function readFile (path, options, cb) { + if (typeof options === 'function') + cb = options, options = null + + return go$readFile(path, options, cb) + + function go$readFile (path, options, cb) { + return fs$readFile(path, options, function (err) { + if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) + enqueue([go$readFile, [path, options, cb]]) + else { + if (typeof cb === 'function') + cb.apply(this, arguments) + retry() + } + }) + } + } + + var fs$writeFile = fs.writeFile + fs.writeFile = writeFile + function writeFile (path, data, options, cb) { + if (typeof options === 'function') + cb = options, options = null + + return go$writeFile(path, data, options, cb) + + function go$writeFile (path, data, options, cb) { + return fs$writeFile(path, data, options, function (err) { + if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) + enqueue([go$writeFile, [path, data, options, cb]]) + else { + if (typeof cb === 'function') + cb.apply(this, arguments) + retry() + } + }) + } + } + + var fs$appendFile = fs.appendFile + if (fs$appendFile) + fs.appendFile = appendFile + function appendFile (path, data, options, cb) { + if (typeof options === 'function') + cb = options, options = null + + return go$appendFile(path, data, options, cb) + + function go$appendFile (path, data, options, cb) { + return fs$appendFile(path, data, options, function (err) { + if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) + enqueue([go$appendFile, [path, data, options, cb]]) + else { + if (typeof cb === 'function') + cb.apply(this, arguments) + retry() + } + }) + } + } + + var fs$readdir = fs.readdir + fs.readdir = readdir + function readdir (path, options, cb) { + var args = [path] + if (typeof options !== 'function') { + args.push(options) + } else { + cb = options + } + args.push(go$readdir$cb) + + return go$readdir(args) + + function go$readdir$cb (err, files) { + if (files && files.sort) + files.sort() + + if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) + enqueue([go$readdir, [args]]) + + else { + if (typeof cb === 'function') + cb.apply(this, arguments) + retry() + } + } + } + + function go$readdir (args) { + return fs$readdir.apply(fs, args) + } + + if (process.version.substr(0, 4) === 'v0.8') { + var legStreams = legacy(fs) + ReadStream = legStreams.ReadStream + WriteStream = legStreams.WriteStream + } + + var fs$ReadStream = fs.ReadStream + if (fs$ReadStream) { + ReadStream.prototype = Object.create(fs$ReadStream.prototype) + ReadStream.prototype.open = ReadStream$open + } + + var fs$WriteStream = fs.WriteStream + if (fs$WriteStream) { + WriteStream.prototype = Object.create(fs$WriteStream.prototype) + WriteStream.prototype.open = WriteStream$open + } + + fs.ReadStream = ReadStream + fs.WriteStream = WriteStream + + function ReadStream (path, options) { + if (this instanceof ReadStream) + return fs$ReadStream.apply(this, arguments), this + else + return ReadStream.apply(Object.create(ReadStream.prototype), arguments) + } + + function ReadStream$open () { + var that = this + open(that.path, that.flags, that.mode, function (err, fd) { + if (err) { + if (that.autoClose) + that.destroy() + + that.emit('error', err) + } else { + that.fd = fd + that.emit('open', fd) + that.read() + } + }) + } + + function WriteStream (path, options) { + if (this instanceof WriteStream) + return fs$WriteStream.apply(this, arguments), this + else + return WriteStream.apply(Object.create(WriteStream.prototype), arguments) + } + + function WriteStream$open () { + var that = this + open(that.path, that.flags, that.mode, function (err, fd) { + if (err) { + that.destroy() + that.emit('error', err) + } else { + that.fd = fd + that.emit('open', fd) + } + }) + } + + function createReadStream (path, options) { + return new ReadStream(path, options) + } + + function createWriteStream (path, options) { + return new WriteStream(path, options) + } + + var fs$open = fs.open + fs.open = open + function open (path, flags, mode, cb) { + if (typeof mode === 'function') + cb = mode, mode = null + + return go$open(path, flags, mode, cb) + + function go$open (path, flags, mode, cb) { + return fs$open(path, flags, mode, function (err, fd) { + if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) + enqueue([go$open, [path, flags, mode, cb]]) + else { + if (typeof cb === 'function') + cb.apply(this, arguments) + retry() + } + }) + } + } + + return fs +} + +function enqueue (elem) { + debug('ENQUEUE', elem[0].name, elem[1]) + queue.push(elem) +} + +function retry () { + var elem = queue.shift() + if (elem) { + debug('RETRY', elem[0].name, elem[1]) + elem[0].apply(null, elem[1]) + } +} + + +/***/ }), +/* 431 */ +/***/ (function(module, exports, __webpack_require__) { + +var constants = __webpack_require__(25) + +var origCwd = process.cwd +var cwd = null + +var platform = process.env.GRACEFUL_FS_PLATFORM || process.platform + +process.cwd = function() { + if (!cwd) + cwd = origCwd.call(process) + return cwd +} +try { + process.cwd() +} catch (er) {} + +var chdir = process.chdir +process.chdir = function(d) { + cwd = null + chdir.call(process, d) +} + +module.exports = patch + +function patch (fs) { + // (re-)implement some things that are known busted or missing. + + // lchmod, broken prior to 0.6.2 + // back-port the fix here. + if (constants.hasOwnProperty('O_SYMLINK') && + process.version.match(/^v0\.6\.[0-2]|^v0\.5\./)) { + patchLchmod(fs) + } + + // lutimes implementation, or no-op + if (!fs.lutimes) { + patchLutimes(fs) + } + + // https://github.com/isaacs/node-graceful-fs/issues/4 + // Chown should not fail on einval or eperm if non-root. + // It should not fail on enosys ever, as this just indicates + // that a fs doesn't support the intended operation. + + fs.chown = chownFix(fs.chown) + fs.fchown = chownFix(fs.fchown) + fs.lchown = chownFix(fs.lchown) + + fs.chmod = chmodFix(fs.chmod) + fs.fchmod = chmodFix(fs.fchmod) + fs.lchmod = chmodFix(fs.lchmod) + + fs.chownSync = chownFixSync(fs.chownSync) + fs.fchownSync = chownFixSync(fs.fchownSync) + fs.lchownSync = chownFixSync(fs.lchownSync) + + fs.chmodSync = chmodFixSync(fs.chmodSync) + fs.fchmodSync = chmodFixSync(fs.fchmodSync) + fs.lchmodSync = chmodFixSync(fs.lchmodSync) + + fs.stat = statFix(fs.stat) + fs.fstat = statFix(fs.fstat) + fs.lstat = statFix(fs.lstat) + + fs.statSync = statFixSync(fs.statSync) + fs.fstatSync = statFixSync(fs.fstatSync) + fs.lstatSync = statFixSync(fs.lstatSync) + + // if lchmod/lchown do not exist, then make them no-ops + if (!fs.lchmod) { + fs.lchmod = function (path, mode, cb) { + if (cb) process.nextTick(cb) + } + fs.lchmodSync = function () {} + } + if (!fs.lchown) { + fs.lchown = function (path, uid, gid, cb) { + if (cb) process.nextTick(cb) + } + fs.lchownSync = function () {} + } + + // on Windows, A/V software can lock the directory, causing this + // to fail with an EACCES or EPERM if the directory contains newly + // created files. Try again on failure, for up to 60 seconds. + + // Set the timeout this long because some Windows Anti-Virus, such as Parity + // bit9, may lock files for up to a minute, causing npm package install + // failures. Also, take care to yield the scheduler. Windows scheduling gives + // CPU to a busy looping process, which can cause the program causing the lock + // contention to be starved of CPU by node, so the contention doesn't resolve. + if (platform === "win32") { + fs.rename = (function (fs$rename) { return function (from, to, cb) { + var start = Date.now() + var backoff = 0; + fs$rename(from, to, function CB (er) { + if (er + && (er.code === "EACCES" || er.code === "EPERM") + && Date.now() - start < 60000) { + setTimeout(function() { + fs.stat(to, function (stater, st) { + if (stater && stater.code === "ENOENT") + fs$rename(from, to, CB); + else + cb(er) + }) + }, backoff) + if (backoff < 100) + backoff += 10; + return; + } + if (cb) cb(er) + }) + }})(fs.rename) + } + + // if read() returns EAGAIN, then just try it again. + fs.read = (function (fs$read) { return function (fd, buffer, offset, length, position, callback_) { + var callback + if (callback_ && typeof callback_ === 'function') { + var eagCounter = 0 + callback = function (er, _, __) { + if (er && er.code === 'EAGAIN' && eagCounter < 10) { + eagCounter ++ + return fs$read.call(fs, fd, buffer, offset, length, position, callback) + } + callback_.apply(this, arguments) + } + } + return fs$read.call(fs, fd, buffer, offset, length, position, callback) + }})(fs.read) + + fs.readSync = (function (fs$readSync) { return function (fd, buffer, offset, length, position) { + var eagCounter = 0 + while (true) { + try { + return fs$readSync.call(fs, fd, buffer, offset, length, position) + } catch (er) { + if (er.code === 'EAGAIN' && eagCounter < 10) { + eagCounter ++ + continue + } + throw er + } + } + }})(fs.readSync) + + function patchLchmod (fs) { + fs.lchmod = function (path, mode, callback) { + fs.open( path + , constants.O_WRONLY | constants.O_SYMLINK + , mode + , function (err, fd) { + if (err) { + if (callback) callback(err) + return + } + // prefer to return the chmod error, if one occurs, + // but still try to close, and report closing errors if they occur. + fs.fchmod(fd, mode, function (err) { + fs.close(fd, function(err2) { + if (callback) callback(err || err2) + }) + }) + }) + } + + fs.lchmodSync = function (path, mode) { + var fd = fs.openSync(path, constants.O_WRONLY | constants.O_SYMLINK, mode) + + // prefer to return the chmod error, if one occurs, + // but still try to close, and report closing errors if they occur. + var threw = true + var ret + try { + ret = fs.fchmodSync(fd, mode) + threw = false + } finally { + if (threw) { + try { + fs.closeSync(fd) + } catch (er) {} + } else { + fs.closeSync(fd) + } + } + return ret + } + } + + function patchLutimes (fs) { + if (constants.hasOwnProperty("O_SYMLINK")) { + fs.lutimes = function (path, at, mt, cb) { + fs.open(path, constants.O_SYMLINK, function (er, fd) { + if (er) { + if (cb) cb(er) + return + } + fs.futimes(fd, at, mt, function (er) { + fs.close(fd, function (er2) { + if (cb) cb(er || er2) + }) + }) + }) + } + + fs.lutimesSync = function (path, at, mt) { + var fd = fs.openSync(path, constants.O_SYMLINK) + var ret + var threw = true + try { + ret = fs.futimesSync(fd, at, mt) + threw = false + } finally { + if (threw) { + try { + fs.closeSync(fd) + } catch (er) {} + } else { + fs.closeSync(fd) + } + } + return ret + } + + } else { + fs.lutimes = function (_a, _b, _c, cb) { if (cb) process.nextTick(cb) } + fs.lutimesSync = function () {} + } + } + + function chmodFix (orig) { + if (!orig) return orig + return function (target, mode, cb) { + return orig.call(fs, target, mode, function (er) { + if (chownErOk(er)) er = null + if (cb) cb.apply(this, arguments) + }) + } + } + + function chmodFixSync (orig) { + if (!orig) return orig + return function (target, mode) { + try { + return orig.call(fs, target, mode) + } catch (er) { + if (!chownErOk(er)) throw er + } + } + } + + + function chownFix (orig) { + if (!orig) return orig + return function (target, uid, gid, cb) { + return orig.call(fs, target, uid, gid, function (er) { + if (chownErOk(er)) er = null + if (cb) cb.apply(this, arguments) + }) + } + } + + function chownFixSync (orig) { + if (!orig) return orig + return function (target, uid, gid) { + try { + return orig.call(fs, target, uid, gid) + } catch (er) { + if (!chownErOk(er)) throw er + } + } + } + + + function statFix (orig) { + if (!orig) return orig + // Older versions of Node erroneously returned signed integers for + // uid + gid. + return function (target, cb) { + return orig.call(fs, target, function (er, stats) { + if (!stats) return cb.apply(this, arguments) + if (stats.uid < 0) stats.uid += 0x100000000 + if (stats.gid < 0) stats.gid += 0x100000000 + if (cb) cb.apply(this, arguments) + }) + } + } + + function statFixSync (orig) { + if (!orig) return orig + // Older versions of Node erroneously returned signed integers for + // uid + gid. + return function (target) { + var stats = orig.call(fs, target) + if (stats.uid < 0) stats.uid += 0x100000000 + if (stats.gid < 0) stats.gid += 0x100000000 + return stats; + } + } + + // ENOSYS means that the fs doesn't support the op. Just ignore + // that, because it doesn't matter. + // + // if there's no getuid, or if getuid() is something other + // than 0, and the error is EINVAL or EPERM, then just ignore + // it. + // + // This specific case is a silent failure in cp, install, tar, + // and most other unix tools that manage permissions. + // + // When running as root, or if other types of errors are + // encountered, then it's strict. + function chownErOk (er) { + if (!er) + return true + + if (er.code === "ENOSYS") + return true + + var nonroot = !process.getuid || process.getuid() !== 0 + if (nonroot) { + if (er.code === "EINVAL" || er.code === "EPERM") + return true + } + + return false + } +} + + +/***/ }), +/* 432 */ +/***/ (function(module, exports, __webpack_require__) { + +var Stream = __webpack_require__(27).Stream + +module.exports = legacy + +function legacy (fs) { + return { + ReadStream: ReadStream, + WriteStream: WriteStream + } + + function ReadStream (path, options) { + if (!(this instanceof ReadStream)) return new ReadStream(path, options); + + Stream.call(this); + + var self = this; + + this.path = path; + this.fd = null; + this.readable = true; + this.paused = false; + + this.flags = 'r'; + this.mode = 438; /*=0666*/ + this.bufferSize = 64 * 1024; + + options = options || {}; + + // Mixin options into this + var keys = Object.keys(options); + for (var index = 0, length = keys.length; index < length; index++) { + var key = keys[index]; + this[key] = options[key]; + } + + if (this.encoding) this.setEncoding(this.encoding); + + if (this.start !== undefined) { + if ('number' !== typeof this.start) { + throw TypeError('start must be a Number'); + } + if (this.end === undefined) { + this.end = Infinity; + } else if ('number' !== typeof this.end) { + throw TypeError('end must be a Number'); + } + + if (this.start > this.end) { + throw new Error('start must be <= end'); + } + + this.pos = this.start; + } + + if (this.fd !== null) { + process.nextTick(function() { + self._read(); + }); + return; + } + + fs.open(this.path, this.flags, this.mode, function (err, fd) { + if (err) { + self.emit('error', err); + self.readable = false; + return; + } + + self.fd = fd; + self.emit('open', fd); + self._read(); + }) + } + + function WriteStream (path, options) { + if (!(this instanceof WriteStream)) return new WriteStream(path, options); + + Stream.call(this); + + this.path = path; + this.fd = null; + this.writable = true; + + this.flags = 'w'; + this.encoding = 'binary'; + this.mode = 438; /*=0666*/ + this.bytesWritten = 0; + + options = options || {}; + + // Mixin options into this + var keys = Object.keys(options); + for (var index = 0, length = keys.length; index < length; index++) { + var key = keys[index]; + this[key] = options[key]; + } + + if (this.start !== undefined) { + if ('number' !== typeof this.start) { + throw TypeError('start must be a Number'); + } + if (this.start < 0) { + throw new Error('start must be >= zero'); + } + + this.pos = this.start; + } + + this.busy = false; + this._queue = []; + + if (this.fd === null) { + this._open = fs.open; + this._queue.push([this._open, this.path, this.flags, this.mode, undefined]); + this.flush(); + } + } +} + + +/***/ }), +/* 433 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +module.exports = clone + +function clone (obj) { + if (obj === null || typeof obj !== 'object') + return obj + + if (obj instanceof Object) + var copy = { __proto__: obj.__proto__ } + else + var copy = Object.create(null) + + Object.getOwnPropertyNames(obj).forEach(function (key) { + Object.defineProperty(copy, key, Object.getOwnPropertyDescriptor(obj, key)) + }) + + return copy +} + + +/***/ }), +/* 434 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +module.exports = string => { + if (typeof string !== 'string') { + throw new TypeError(`Expected a string, got ${typeof string}`); + } + + // Catches EFBBBF (UTF-8 BOM) because the buffer-to-string + // conversion translates it to FEFF (UTF-16 BOM) + if (string.charCodeAt(0) === 0xFEFF) { + return string.slice(1); + } + + return string; +}; + + +/***/ }), +/* 435 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +const errorEx = __webpack_require__(436); +const fallback = __webpack_require__(438); +const {default: LinesAndColumns} = __webpack_require__(439); +const {codeFrameColumns} = __webpack_require__(440); + +const JSONError = errorEx('JSONError', { + fileName: errorEx.append('in %s'), + codeFrame: errorEx.append('\n\n%s\n') +}); + +module.exports = (string, reviver, filename) => { + if (typeof reviver === 'string') { + filename = reviver; + reviver = null; + } + + try { + try { + return JSON.parse(string, reviver); + } catch (error) { + fallback(string, reviver); + throw error; + } + } catch (error) { + error.message = error.message.replace(/\n/g, ''); + const indexMatch = error.message.match(/in JSON at position (\d+) while parsing near/); + + const jsonError = new JSONError(error); + if (filename) { + jsonError.fileName = filename; + } + + if (indexMatch && indexMatch.length > 0) { + const lines = new LinesAndColumns(string); + const index = Number(indexMatch[1]); + const location = lines.locationForIndex(index); + + const codeFrame = codeFrameColumns( + string, + {start: {line: location.line + 1, column: location.column + 1}}, + {highlightCode: true} + ); + + jsonError.codeFrame = codeFrame; + } + + throw jsonError; + } +}; + + +/***/ }), +/* 436 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var util = __webpack_require__(29); +var isArrayish = __webpack_require__(437); + +var errorEx = function errorEx(name, properties) { + if (!name || name.constructor !== String) { + properties = name || {}; + name = Error.name; + } + + var errorExError = function ErrorEXError(message) { + if (!this) { + return new ErrorEXError(message); + } + + message = message instanceof Error + ? message.message + : (message || this.message); + + Error.call(this, message); + Error.captureStackTrace(this, errorExError); + + this.name = name; + + Object.defineProperty(this, 'message', { + configurable: true, + enumerable: false, + get: function () { + var newMessage = message.split(/\r?\n/g); + + for (var key in properties) { + if (!properties.hasOwnProperty(key)) { + continue; + } + + var modifier = properties[key]; + + if ('message' in modifier) { + newMessage = modifier.message(this[key], newMessage) || newMessage; + if (!isArrayish(newMessage)) { + newMessage = [newMessage]; + } + } + } + + return newMessage.join('\n'); + }, + set: function (v) { + message = v; + } + }); + + var stackDescriptor = Object.getOwnPropertyDescriptor(this, 'stack'); + var stackGetter = stackDescriptor.get; + var stackValue = stackDescriptor.value; + delete stackDescriptor.value; + delete stackDescriptor.writable; + + stackDescriptor.get = function () { + var stack = (stackGetter) + ? stackGetter.call(this).split(/\r?\n+/g) + : stackValue.split(/\r?\n+/g); + + // starting in Node 7, the stack builder caches the message. + // just replace it. + stack[0] = this.name + ': ' + this.message; + + var lineCount = 1; + for (var key in properties) { + if (!properties.hasOwnProperty(key)) { + continue; + } + + var modifier = properties[key]; + + if ('line' in modifier) { + var line = modifier.line(this[key]); + if (line) { + stack.splice(lineCount++, 0, ' ' + line); + } + } + + if ('stack' in modifier) { + modifier.stack(this[key], stack); + } + } + + return stack.join('\n'); + }; + + Object.defineProperty(this, 'stack', stackDescriptor); + }; + + if (Object.setPrototypeOf) { + Object.setPrototypeOf(errorExError.prototype, Error.prototype); + Object.setPrototypeOf(errorExError, Error); + } else { + util.inherits(errorExError, Error); + } + + return errorExError; +}; + +errorEx.append = function (str, def) { + return { + message: function (v, message) { + v = v || def; + + if (v) { + message[0] += ' ' + str.replace('%s', v.toString()); + } + + return message; + } + }; +}; + +errorEx.line = function (str, def) { + return { + line: function (v) { + v = v || def; + + if (v) { + return str.replace('%s', v.toString()); + } + + return null; + } + }; +}; + +module.exports = errorEx; + + +/***/ }), +/* 437 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +module.exports = function isArrayish(obj) { + if (!obj) { + return false; + } + + return obj instanceof Array || Array.isArray(obj) || + (obj.length >= 0 && obj.splice instanceof Function); +}; + + +/***/ }), +/* 438 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +module.exports = parseJson +function parseJson (txt, reviver, context) { + context = context || 20 + try { + return JSON.parse(txt, reviver) + } catch (e) { + const syntaxErr = e.message.match(/^Unexpected token.*position\s+(\d+)/i) + const errIdx = syntaxErr + ? +syntaxErr[1] + : e.message.match(/^Unexpected end of JSON.*/i) + ? txt.length - 1 + : null + if (errIdx != null) { + const start = errIdx <= context + ? 0 + : errIdx - context + const end = errIdx + context >= txt.length + ? txt.length + : errIdx + context + e.message += ` while parsing near '${ + start === 0 ? '' : '...' + }${txt.slice(start, end)}${ + end === txt.length ? '' : '...' + }'` + } else { + e.message += ` while parsing '${txt.slice(0, context * 2)}'` + } + throw e + } +} + + +/***/ }), +/* 439 */ +/***/ (function(__webpack_module__, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +var LF = '\n'; +var CR = '\r'; +var LinesAndColumns = (function () { + function LinesAndColumns(string) { + this.string = string; + var offsets = [0]; + for (var offset = 0; offset < string.length;) { + switch (string[offset]) { + case LF: + offset += LF.length; + offsets.push(offset); + break; + case CR: + offset += CR.length; + if (string[offset] === LF) { + offset += LF.length; + } + offsets.push(offset); + break; + default: + offset++; + break; + } + } + this.offsets = offsets; + } + LinesAndColumns.prototype.locationForIndex = function (index) { + if (index < 0 || index > this.string.length) { + return null; + } + var line = 0; + var offsets = this.offsets; + while (offsets[line + 1] <= index) { + line++; + } + var column = index - offsets[line]; + return { line: line, column: column }; + }; + LinesAndColumns.prototype.indexForLocation = function (location) { + var line = location.line, column = location.column; + if (line < 0 || line >= this.offsets.length) { + return null; + } + if (column < 0 || column > this.lengthOfLine(line)) { + return null; + } + return this.offsets[line] + column; + }; + LinesAndColumns.prototype.lengthOfLine = function (line) { + var offset = this.offsets[line]; + var nextOffset = line === this.offsets.length - 1 ? this.string.length : this.offsets[line + 1]; + return nextOffset - offset; + }; + return LinesAndColumns; +}()); +/* harmony default export */ __webpack_exports__["default"] = (LinesAndColumns); + + +/***/ }), +/* 440 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.codeFrameColumns = codeFrameColumns; +exports.default = _default; + +function _highlight() { + const data = _interopRequireWildcard(__webpack_require__(441)); + + _highlight = function () { + return data; + }; + + return data; +} + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; return newObj; } } + +let deprecationWarningShown = false; + +function getDefs(chalk) { + return { + gutter: chalk.grey, + marker: chalk.red.bold, + message: chalk.red.bold + }; +} + +const NEWLINE = /\r\n|[\n\r\u2028\u2029]/; + +function getMarkerLines(loc, source, opts) { + const startLoc = Object.assign({ + column: 0, + line: -1 + }, loc.start); + const endLoc = Object.assign({}, startLoc, loc.end); + const { + linesAbove = 2, + linesBelow = 3 + } = opts || {}; + const startLine = startLoc.line; + const startColumn = startLoc.column; + const endLine = endLoc.line; + const endColumn = endLoc.column; + let start = Math.max(startLine - (linesAbove + 1), 0); + let end = Math.min(source.length, endLine + linesBelow); + + if (startLine === -1) { + start = 0; + } + + if (endLine === -1) { + end = source.length; + } + + const lineDiff = endLine - startLine; + const markerLines = {}; + + if (lineDiff) { + for (let i = 0; i <= lineDiff; i++) { + const lineNumber = i + startLine; + + if (!startColumn) { + markerLines[lineNumber] = true; + } else if (i === 0) { + const sourceLength = source[lineNumber - 1].length; + markerLines[lineNumber] = [startColumn, sourceLength - startColumn + 1]; + } else if (i === lineDiff) { + markerLines[lineNumber] = [0, endColumn]; + } else { + const sourceLength = source[lineNumber - i].length; + markerLines[lineNumber] = [0, sourceLength]; + } + } + } else { + if (startColumn === endColumn) { + if (startColumn) { + markerLines[startLine] = [startColumn, 0]; + } else { + markerLines[startLine] = true; + } + } else { + markerLines[startLine] = [startColumn, endColumn - startColumn]; + } + } + + return { + start, + end, + markerLines + }; +} + +function codeFrameColumns(rawLines, loc, opts = {}) { + const highlighted = (opts.highlightCode || opts.forceColor) && (0, _highlight().shouldHighlight)(opts); + const chalk = (0, _highlight().getChalk)(opts); + const defs = getDefs(chalk); + + const maybeHighlight = (chalkFn, string) => { + return highlighted ? chalkFn(string) : string; + }; + + const lines = rawLines.split(NEWLINE); + const { + start, + end, + markerLines + } = getMarkerLines(loc, lines, opts); + const hasColumns = loc.start && typeof loc.start.column === "number"; + const numberMaxWidth = String(end).length; + const highlightedLines = highlighted ? (0, _highlight().default)(rawLines, opts) : rawLines; + let frame = highlightedLines.split(NEWLINE).slice(start, end).map((line, index) => { + const number = start + 1 + index; + const paddedNumber = ` ${number}`.slice(-numberMaxWidth); + const gutter = ` ${paddedNumber} | `; + const hasMarker = markerLines[number]; + const lastMarkerLine = !markerLines[number + 1]; + + if (hasMarker) { + let markerLine = ""; + + if (Array.isArray(hasMarker)) { + const markerSpacing = line.slice(0, Math.max(hasMarker[0] - 1, 0)).replace(/[^\t]/g, " "); + const numberOfMarkers = hasMarker[1] || 1; + markerLine = ["\n ", maybeHighlight(defs.gutter, gutter.replace(/\d/g, " ")), markerSpacing, maybeHighlight(defs.marker, "^").repeat(numberOfMarkers)].join(""); + + if (lastMarkerLine && opts.message) { + markerLine += " " + maybeHighlight(defs.message, opts.message); + } + } + + return [maybeHighlight(defs.marker, ">"), maybeHighlight(defs.gutter, gutter), line, markerLine].join(""); + } else { + return ` ${maybeHighlight(defs.gutter, gutter)}${line}`; + } + }).join("\n"); + + if (opts.message && !hasColumns) { + frame = `${" ".repeat(numberMaxWidth + 1)}${opts.message}\n${frame}`; + } + + if (highlighted) { + return chalk.reset(frame); + } else { + return frame; + } +} + +function _default(rawLines, lineNumber, colNumber, opts = {}) { + if (!deprecationWarningShown) { + deprecationWarningShown = true; + const message = "Passing lineNumber and colNumber is deprecated to @babel/code-frame. Please use `codeFrameColumns`."; + + if (process.emitWarning) { + process.emitWarning(message, "DeprecationWarning"); + } else { + const deprecationError = new Error(message); + deprecationError.name = "DeprecationWarning"; + console.warn(new Error(message)); + } + } + + colNumber = Math.max(colNumber, 0); + const location = { + start: { + column: colNumber, + line: lineNumber + } + }; + return codeFrameColumns(rawLines, location, opts); +} + +/***/ }), +/* 441 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.shouldHighlight = shouldHighlight; +exports.getChalk = getChalk; +exports.default = highlight; + +function _jsTokens() { + const data = _interopRequireWildcard(__webpack_require__(442)); + + _jsTokens = function () { + return data; + }; + + return data; +} + +function _esutils() { + const data = _interopRequireDefault(__webpack_require__(443)); + + _esutils = function () { + return data; + }; + + return data; +} + +function _chalk() { + const data = _interopRequireDefault(__webpack_require__(447)); + + _chalk = function () { + return data; + }; + + return data; +} + +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } + +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) { var desc = Object.defineProperty && Object.getOwnPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : {}; if (desc.get || desc.set) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } } newObj.default = obj; return newObj; } } + +function getDefs(chalk) { + return { + keyword: chalk.cyan, + capitalized: chalk.yellow, + jsx_tag: chalk.yellow, + punctuator: chalk.yellow, + number: chalk.magenta, + string: chalk.green, + regex: chalk.magenta, + comment: chalk.grey, + invalid: chalk.white.bgRed.bold + }; +} + +const NEWLINE = /\r\n|[\n\r\u2028\u2029]/; +const JSX_TAG = /^[a-z][\w-]*$/i; +const BRACKET = /^[()[\]{}]$/; + +function getTokenType(match) { + const [offset, text] = match.slice(-2); + const token = (0, _jsTokens().matchToToken)(match); + + if (token.type === "name") { + if (_esutils().default.keyword.isReservedWordES6(token.value)) { + return "keyword"; + } + + if (JSX_TAG.test(token.value) && (text[offset - 1] === "<" || text.substr(offset - 2, 2) == " colorize(str)).join("\n"); + } else { + return args[0]; + } + }); +} + +function shouldHighlight(options) { + return _chalk().default.supportsColor || options.forceColor; +} + +function getChalk(options) { + let chalk = _chalk().default; + + if (options.forceColor) { + chalk = new (_chalk().default.constructor)({ + enabled: true, + level: 1 + }); + } + + return chalk; +} + +function highlight(code, options = {}) { + if (shouldHighlight(options)) { + const chalk = getChalk(options); + const defs = getDefs(chalk); + return highlightTokens(defs, code); + } else { + return code; + } +} + +/***/ }), +/* 442 */ +/***/ (function(module, exports) { + +// Copyright 2014, 2015, 2016, 2017, 2018 Simon Lydell +// License: MIT. (See LICENSE.) + +Object.defineProperty(exports, "__esModule", { + value: true +}) + +// This regex comes from regex.coffee, and is inserted here by generate-index.js +// (run `npm run build`). +exports.default = /((['"])(?:(?!\2|\\).|\\(?:\r\n|[\s\S]))*(\2)?|`(?:[^`\\$]|\\[\s\S]|\$(?!\{)|\$\{(?:[^{}]|\{[^}]*\}?)*\}?)*(`)?)|(\/\/.*)|(\/\*(?:[^*]|\*(?!\/))*(\*\/)?)|(\/(?!\*)(?:\[(?:(?![\]\\]).|\\.)*\]|(?![\/\]\\]).|\\.)+\/(?:(?!\s*(?:\b|[\u0080-\uFFFF$\\'"~({]|[+\-!](?!=)|\.?\d))|[gmiyus]{1,6}\b(?![\u0080-\uFFFF$\\]|\s*(?:[+\-*%&|^<>!=?({]|\/(?![\/*])))))|(0[xX][\da-fA-F]+|0[oO][0-7]+|0[bB][01]+|(?:\d*\.\d+|\d+\.?)(?:[eE][+-]?\d+)?)|((?!\d)(?:(?!\s)[$\w\u0080-\uFFFF]|\\u[\da-fA-F]{4}|\\u\{[\da-fA-F]+\})+)|(--|\+\+|&&|\|\||=>|\.{3}|(?:[+\-\/%&|^]|\*{1,2}|<{1,2}|>{1,3}|!=?|={1,2})=?|[?~.,:;[\](){}])|(\s+)|(^$|[\s\S])/g + +exports.matchToToken = function(match) { + var token = {type: "invalid", value: match[0], closed: undefined} + if (match[ 1]) token.type = "string" , token.closed = !!(match[3] || match[4]) + else if (match[ 5]) token.type = "comment" + else if (match[ 6]) token.type = "comment", token.closed = !!match[7] + else if (match[ 8]) token.type = "regex" + else if (match[ 9]) token.type = "number" + else if (match[10]) token.type = "name" + else if (match[11]) token.type = "punctuator" + else if (match[12]) token.type = "whitespace" + return token +} + + +/***/ }), +/* 443 */ +/***/ (function(module, exports, __webpack_require__) { + +/* + Copyright (C) 2013 Yusuke Suzuki + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + + +(function () { + 'use strict'; + + exports.ast = __webpack_require__(444); + exports.code = __webpack_require__(445); + exports.keyword = __webpack_require__(446); +}()); +/* vim: set sw=4 ts=4 et tw=80 : */ + + +/***/ }), +/* 444 */ +/***/ (function(module, exports) { + +/* + Copyright (C) 2013 Yusuke Suzuki + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS IS' + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +(function () { + 'use strict'; + + function isExpression(node) { + if (node == null) { return false; } + switch (node.type) { + case 'ArrayExpression': + case 'AssignmentExpression': + case 'BinaryExpression': + case 'CallExpression': + case 'ConditionalExpression': + case 'FunctionExpression': + case 'Identifier': + case 'Literal': + case 'LogicalExpression': + case 'MemberExpression': + case 'NewExpression': + case 'ObjectExpression': + case 'SequenceExpression': + case 'ThisExpression': + case 'UnaryExpression': + case 'UpdateExpression': + return true; + } + return false; + } + + function isIterationStatement(node) { + if (node == null) { return false; } + switch (node.type) { + case 'DoWhileStatement': + case 'ForInStatement': + case 'ForStatement': + case 'WhileStatement': + return true; + } + return false; + } + + function isStatement(node) { + if (node == null) { return false; } + switch (node.type) { + case 'BlockStatement': + case 'BreakStatement': + case 'ContinueStatement': + case 'DebuggerStatement': + case 'DoWhileStatement': + case 'EmptyStatement': + case 'ExpressionStatement': + case 'ForInStatement': + case 'ForStatement': + case 'IfStatement': + case 'LabeledStatement': + case 'ReturnStatement': + case 'SwitchStatement': + case 'ThrowStatement': + case 'TryStatement': + case 'VariableDeclaration': + case 'WhileStatement': + case 'WithStatement': + return true; + } + return false; + } + + function isSourceElement(node) { + return isStatement(node) || node != null && node.type === 'FunctionDeclaration'; + } + + function trailingStatement(node) { + switch (node.type) { + case 'IfStatement': + if (node.alternate != null) { + return node.alternate; + } + return node.consequent; + + case 'LabeledStatement': + case 'ForStatement': + case 'ForInStatement': + case 'WhileStatement': + case 'WithStatement': + return node.body; + } + return null; + } + + function isProblematicIfStatement(node) { + var current; + + if (node.type !== 'IfStatement') { + return false; + } + if (node.alternate == null) { + return false; + } + current = node.consequent; + do { + if (current.type === 'IfStatement') { + if (current.alternate == null) { + return true; + } + } + current = trailingStatement(current); + } while (current); + + return false; + } + + module.exports = { + isExpression: isExpression, + isStatement: isStatement, + isIterationStatement: isIterationStatement, + isSourceElement: isSourceElement, + isProblematicIfStatement: isProblematicIfStatement, + + trailingStatement: trailingStatement + }; +}()); +/* vim: set sw=4 ts=4 et tw=80 : */ + + +/***/ }), +/* 445 */ +/***/ (function(module, exports) { + +/* + Copyright (C) 2013-2014 Yusuke Suzuki + Copyright (C) 2014 Ivan Nikulin + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +(function () { + 'use strict'; + + var ES6Regex, ES5Regex, NON_ASCII_WHITESPACES, IDENTIFIER_START, IDENTIFIER_PART, ch; + + // See `tools/generate-identifier-regex.js`. + ES5Regex = { + // ECMAScript 5.1/Unicode v7.0.0 NonAsciiIdentifierStart: + NonAsciiIdentifierStart: /[\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0-\u08B2\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0980\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58\u0C59\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D60\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F8\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19C1-\u19C7\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2160-\u2188\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303C\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCC\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA66E\uA67F-\uA69D\uA6A0-\uA6EF\uA717-\uA71F\uA722-\uA788\uA78B-\uA78E\uA790-\uA7AD\uA7B0\uA7B1\uA7F7-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uA9E0-\uA9E4\uA9E6-\uA9EF\uA9FA-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB5F\uAB64\uAB65\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]/, + // ECMAScript 5.1/Unicode v7.0.0 NonAsciiIdentifierPart: + NonAsciiIdentifierPart: /[\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0300-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u0483-\u0487\u048A-\u052F\u0531-\u0556\u0559\u0561-\u0587\u0591-\u05BD\u05BF\u05C1\u05C2\u05C4\u05C5\u05C7\u05D0-\u05EA\u05F0-\u05F2\u0610-\u061A\u0620-\u0669\u066E-\u06D3\u06D5-\u06DC\u06DF-\u06E8\u06EA-\u06FC\u06FF\u0710-\u074A\u074D-\u07B1\u07C0-\u07F5\u07FA\u0800-\u082D\u0840-\u085B\u08A0-\u08B2\u08E4-\u0963\u0966-\u096F\u0971-\u0983\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BC-\u09C4\u09C7\u09C8\u09CB-\u09CE\u09D7\u09DC\u09DD\u09DF-\u09E3\u09E6-\u09F1\u0A01-\u0A03\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A3C\u0A3E-\u0A42\u0A47\u0A48\u0A4B-\u0A4D\u0A51\u0A59-\u0A5C\u0A5E\u0A66-\u0A75\u0A81-\u0A83\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABC-\u0AC5\u0AC7-\u0AC9\u0ACB-\u0ACD\u0AD0\u0AE0-\u0AE3\u0AE6-\u0AEF\u0B01-\u0B03\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3C-\u0B44\u0B47\u0B48\u0B4B-\u0B4D\u0B56\u0B57\u0B5C\u0B5D\u0B5F-\u0B63\u0B66-\u0B6F\u0B71\u0B82\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BBE-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCD\u0BD0\u0BD7\u0BE6-\u0BEF\u0C00-\u0C03\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D-\u0C44\u0C46-\u0C48\u0C4A-\u0C4D\u0C55\u0C56\u0C58\u0C59\u0C60-\u0C63\u0C66-\u0C6F\u0C81-\u0C83\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBC-\u0CC4\u0CC6-\u0CC8\u0CCA-\u0CCD\u0CD5\u0CD6\u0CDE\u0CE0-\u0CE3\u0CE6-\u0CEF\u0CF1\u0CF2\u0D01-\u0D03\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D-\u0D44\u0D46-\u0D48\u0D4A-\u0D4E\u0D57\u0D60-\u0D63\u0D66-\u0D6F\u0D7A-\u0D7F\u0D82\u0D83\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0DCA\u0DCF-\u0DD4\u0DD6\u0DD8-\u0DDF\u0DE6-\u0DEF\u0DF2\u0DF3\u0E01-\u0E3A\u0E40-\u0E4E\u0E50-\u0E59\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB9\u0EBB-\u0EBD\u0EC0-\u0EC4\u0EC6\u0EC8-\u0ECD\u0ED0-\u0ED9\u0EDC-\u0EDF\u0F00\u0F18\u0F19\u0F20-\u0F29\u0F35\u0F37\u0F39\u0F3E-\u0F47\u0F49-\u0F6C\u0F71-\u0F84\u0F86-\u0F97\u0F99-\u0FBC\u0FC6\u1000-\u1049\u1050-\u109D\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u135D-\u135F\u1380-\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F8\u1700-\u170C\u170E-\u1714\u1720-\u1734\u1740-\u1753\u1760-\u176C\u176E-\u1770\u1772\u1773\u1780-\u17D3\u17D7\u17DC\u17DD\u17E0-\u17E9\u180B-\u180D\u1810-\u1819\u1820-\u1877\u1880-\u18AA\u18B0-\u18F5\u1900-\u191E\u1920-\u192B\u1930-\u193B\u1946-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u19D0-\u19D9\u1A00-\u1A1B\u1A20-\u1A5E\u1A60-\u1A7C\u1A7F-\u1A89\u1A90-\u1A99\u1AA7\u1AB0-\u1ABD\u1B00-\u1B4B\u1B50-\u1B59\u1B6B-\u1B73\u1B80-\u1BF3\u1C00-\u1C37\u1C40-\u1C49\u1C4D-\u1C7D\u1CD0-\u1CD2\u1CD4-\u1CF6\u1CF8\u1CF9\u1D00-\u1DF5\u1DFC-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u200C\u200D\u203F\u2040\u2054\u2071\u207F\u2090-\u209C\u20D0-\u20DC\u20E1\u20E5-\u20F0\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2160-\u2188\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D7F-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2DE0-\u2DFF\u2E2F\u3005-\u3007\u3021-\u302F\u3031-\u3035\u3038-\u303C\u3041-\u3096\u3099\u309A\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCC\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA62B\uA640-\uA66F\uA674-\uA67D\uA67F-\uA69D\uA69F-\uA6F1\uA717-\uA71F\uA722-\uA788\uA78B-\uA78E\uA790-\uA7AD\uA7B0\uA7B1\uA7F7-\uA827\uA840-\uA873\uA880-\uA8C4\uA8D0-\uA8D9\uA8E0-\uA8F7\uA8FB\uA900-\uA92D\uA930-\uA953\uA960-\uA97C\uA980-\uA9C0\uA9CF-\uA9D9\uA9E0-\uA9FE\uAA00-\uAA36\uAA40-\uAA4D\uAA50-\uAA59\uAA60-\uAA76\uAA7A-\uAAC2\uAADB-\uAADD\uAAE0-\uAAEF\uAAF2-\uAAF6\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB5F\uAB64\uAB65\uABC0-\uABEA\uABEC\uABED\uABF0-\uABF9\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE00-\uFE0F\uFE20-\uFE2D\uFE33\uFE34\uFE4D-\uFE4F\uFE70-\uFE74\uFE76-\uFEFC\uFF10-\uFF19\uFF21-\uFF3A\uFF3F\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]/ + }; + + ES6Regex = { + // ECMAScript 6/Unicode v7.0.0 NonAsciiIdentifierStart: + NonAsciiIdentifierStart: /[\xAA\xB5\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u048A-\u052F\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0-\u08B2\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0980\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D\u0C58\u0C59\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D\u0D4E\u0D60\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F8\u1700-\u170C\u170E-\u1711\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191E\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19C1-\u19C7\u1A00-\u1A16\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2118-\u211D\u2124\u2126\u2128\u212A-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2160-\u2188\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u3005-\u3007\u3021-\u3029\u3031-\u3035\u3038-\u303C\u3041-\u3096\u309B-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCC\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B\uA640-\uA66E\uA67F-\uA69D\uA6A0-\uA6EF\uA717-\uA71F\uA722-\uA788\uA78B-\uA78E\uA790-\uA7AD\uA7B0\uA7B1\uA7F7-\uA801\uA803-\uA805\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uA9E0-\uA9E4\uA9E6-\uA9EF\uA9FA-\uA9FE\uAA00-\uAA28\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA7E-\uAAAF\uAAB1\uAAB5\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB5F\uAB64\uAB65\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]|\uD800[\uDC00-\uDC0B\uDC0D-\uDC26\uDC28-\uDC3A\uDC3C\uDC3D\uDC3F-\uDC4D\uDC50-\uDC5D\uDC80-\uDCFA\uDD40-\uDD74\uDE80-\uDE9C\uDEA0-\uDED0\uDF00-\uDF1F\uDF30-\uDF4A\uDF50-\uDF75\uDF80-\uDF9D\uDFA0-\uDFC3\uDFC8-\uDFCF\uDFD1-\uDFD5]|\uD801[\uDC00-\uDC9D\uDD00-\uDD27\uDD30-\uDD63\uDE00-\uDF36\uDF40-\uDF55\uDF60-\uDF67]|\uD802[\uDC00-\uDC05\uDC08\uDC0A-\uDC35\uDC37\uDC38\uDC3C\uDC3F-\uDC55\uDC60-\uDC76\uDC80-\uDC9E\uDD00-\uDD15\uDD20-\uDD39\uDD80-\uDDB7\uDDBE\uDDBF\uDE00\uDE10-\uDE13\uDE15-\uDE17\uDE19-\uDE33\uDE60-\uDE7C\uDE80-\uDE9C\uDEC0-\uDEC7\uDEC9-\uDEE4\uDF00-\uDF35\uDF40-\uDF55\uDF60-\uDF72\uDF80-\uDF91]|\uD803[\uDC00-\uDC48]|\uD804[\uDC03-\uDC37\uDC83-\uDCAF\uDCD0-\uDCE8\uDD03-\uDD26\uDD50-\uDD72\uDD76\uDD83-\uDDB2\uDDC1-\uDDC4\uDDDA\uDE00-\uDE11\uDE13-\uDE2B\uDEB0-\uDEDE\uDF05-\uDF0C\uDF0F\uDF10\uDF13-\uDF28\uDF2A-\uDF30\uDF32\uDF33\uDF35-\uDF39\uDF3D\uDF5D-\uDF61]|\uD805[\uDC80-\uDCAF\uDCC4\uDCC5\uDCC7\uDD80-\uDDAE\uDE00-\uDE2F\uDE44\uDE80-\uDEAA]|\uD806[\uDCA0-\uDCDF\uDCFF\uDEC0-\uDEF8]|\uD808[\uDC00-\uDF98]|\uD809[\uDC00-\uDC6E]|[\uD80C\uD840-\uD868\uD86A-\uD86C][\uDC00-\uDFFF]|\uD80D[\uDC00-\uDC2E]|\uD81A[\uDC00-\uDE38\uDE40-\uDE5E\uDED0-\uDEED\uDF00-\uDF2F\uDF40-\uDF43\uDF63-\uDF77\uDF7D-\uDF8F]|\uD81B[\uDF00-\uDF44\uDF50\uDF93-\uDF9F]|\uD82C[\uDC00\uDC01]|\uD82F[\uDC00-\uDC6A\uDC70-\uDC7C\uDC80-\uDC88\uDC90-\uDC99]|\uD835[\uDC00-\uDC54\uDC56-\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD1E-\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD52-\uDEA5\uDEA8-\uDEC0\uDEC2-\uDEDA\uDEDC-\uDEFA\uDEFC-\uDF14\uDF16-\uDF34\uDF36-\uDF4E\uDF50-\uDF6E\uDF70-\uDF88\uDF8A-\uDFA8\uDFAA-\uDFC2\uDFC4-\uDFCB]|\uD83A[\uDC00-\uDCC4]|\uD83B[\uDE00-\uDE03\uDE05-\uDE1F\uDE21\uDE22\uDE24\uDE27\uDE29-\uDE32\uDE34-\uDE37\uDE39\uDE3B\uDE42\uDE47\uDE49\uDE4B\uDE4D-\uDE4F\uDE51\uDE52\uDE54\uDE57\uDE59\uDE5B\uDE5D\uDE5F\uDE61\uDE62\uDE64\uDE67-\uDE6A\uDE6C-\uDE72\uDE74-\uDE77\uDE79-\uDE7C\uDE7E\uDE80-\uDE89\uDE8B-\uDE9B\uDEA1-\uDEA3\uDEA5-\uDEA9\uDEAB-\uDEBB]|\uD869[\uDC00-\uDED6\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF34\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D]|\uD87E[\uDC00-\uDE1D]/, + // ECMAScript 6/Unicode v7.0.0 NonAsciiIdentifierPart: + NonAsciiIdentifierPart: /[\xAA\xB5\xB7\xBA\xC0-\xD6\xD8-\xF6\xF8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0300-\u0374\u0376\u0377\u037A-\u037D\u037F\u0386-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5\u03F7-\u0481\u0483-\u0487\u048A-\u052F\u0531-\u0556\u0559\u0561-\u0587\u0591-\u05BD\u05BF\u05C1\u05C2\u05C4\u05C5\u05C7\u05D0-\u05EA\u05F0-\u05F2\u0610-\u061A\u0620-\u0669\u066E-\u06D3\u06D5-\u06DC\u06DF-\u06E8\u06EA-\u06FC\u06FF\u0710-\u074A\u074D-\u07B1\u07C0-\u07F5\u07FA\u0800-\u082D\u0840-\u085B\u08A0-\u08B2\u08E4-\u0963\u0966-\u096F\u0971-\u0983\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2\u09B6-\u09B9\u09BC-\u09C4\u09C7\u09C8\u09CB-\u09CE\u09D7\u09DC\u09DD\u09DF-\u09E3\u09E6-\u09F1\u0A01-\u0A03\u0A05-\u0A0A\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39\u0A3C\u0A3E-\u0A42\u0A47\u0A48\u0A4B-\u0A4D\u0A51\u0A59-\u0A5C\u0A5E\u0A66-\u0A75\u0A81-\u0A83\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABC-\u0AC5\u0AC7-\u0AC9\u0ACB-\u0ACD\u0AD0\u0AE0-\u0AE3\u0AE6-\u0AEF\u0B01-\u0B03\u0B05-\u0B0C\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3C-\u0B44\u0B47\u0B48\u0B4B-\u0B4D\u0B56\u0B57\u0B5C\u0B5D\u0B5F-\u0B63\u0B66-\u0B6F\u0B71\u0B82\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BBE-\u0BC2\u0BC6-\u0BC8\u0BCA-\u0BCD\u0BD0\u0BD7\u0BE6-\u0BEF\u0C00-\u0C03\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C39\u0C3D-\u0C44\u0C46-\u0C48\u0C4A-\u0C4D\u0C55\u0C56\u0C58\u0C59\u0C60-\u0C63\u0C66-\u0C6F\u0C81-\u0C83\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3\u0CB5-\u0CB9\u0CBC-\u0CC4\u0CC6-\u0CC8\u0CCA-\u0CCD\u0CD5\u0CD6\u0CDE\u0CE0-\u0CE3\u0CE6-\u0CEF\u0CF1\u0CF2\u0D01-\u0D03\u0D05-\u0D0C\u0D0E-\u0D10\u0D12-\u0D3A\u0D3D-\u0D44\u0D46-\u0D48\u0D4A-\u0D4E\u0D57\u0D60-\u0D63\u0D66-\u0D6F\u0D7A-\u0D7F\u0D82\u0D83\u0D85-\u0D96\u0D9A-\u0DB1\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0DCA\u0DCF-\u0DD4\u0DD6\u0DD8-\u0DDF\u0DE6-\u0DEF\u0DF2\u0DF3\u0E01-\u0E3A\u0E40-\u0E4E\u0E50-\u0E59\u0E81\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB9\u0EBB-\u0EBD\u0EC0-\u0EC4\u0EC6\u0EC8-\u0ECD\u0ED0-\u0ED9\u0EDC-\u0EDF\u0F00\u0F18\u0F19\u0F20-\u0F29\u0F35\u0F37\u0F39\u0F3E-\u0F47\u0F49-\u0F6C\u0F71-\u0F84\u0F86-\u0F97\u0F99-\u0FBC\u0FC6\u1000-\u1049\u1050-\u109D\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310\u1312-\u1315\u1318-\u135A\u135D-\u135F\u1369-\u1371\u1380-\u138F\u13A0-\u13F4\u1401-\u166C\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u16EE-\u16F8\u1700-\u170C\u170E-\u1714\u1720-\u1734\u1740-\u1753\u1760-\u176C\u176E-\u1770\u1772\u1773\u1780-\u17D3\u17D7\u17DC\u17DD\u17E0-\u17E9\u180B-\u180D\u1810-\u1819\u1820-\u1877\u1880-\u18AA\u18B0-\u18F5\u1900-\u191E\u1920-\u192B\u1930-\u193B\u1946-\u196D\u1970-\u1974\u1980-\u19AB\u19B0-\u19C9\u19D0-\u19DA\u1A00-\u1A1B\u1A20-\u1A5E\u1A60-\u1A7C\u1A7F-\u1A89\u1A90-\u1A99\u1AA7\u1AB0-\u1ABD\u1B00-\u1B4B\u1B50-\u1B59\u1B6B-\u1B73\u1B80-\u1BF3\u1C00-\u1C37\u1C40-\u1C49\u1C4D-\u1C7D\u1CD0-\u1CD2\u1CD4-\u1CF6\u1CF8\u1CF9\u1D00-\u1DF5\u1DFC-\u1F15\u1F18-\u1F1D\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u200C\u200D\u203F\u2040\u2054\u2071\u207F\u2090-\u209C\u20D0-\u20DC\u20E1\u20E5-\u20F0\u2102\u2107\u210A-\u2113\u2115\u2118-\u211D\u2124\u2126\u2128\u212A-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2160-\u2188\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CF3\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D7F-\u2D96\u2DA0-\u2DA6\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE\u2DD0-\u2DD6\u2DD8-\u2DDE\u2DE0-\u2DFF\u3005-\u3007\u3021-\u302F\u3031-\u3035\u3038-\u303C\u3041-\u3096\u3099-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCC\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA62B\uA640-\uA66F\uA674-\uA67D\uA67F-\uA69D\uA69F-\uA6F1\uA717-\uA71F\uA722-\uA788\uA78B-\uA78E\uA790-\uA7AD\uA7B0\uA7B1\uA7F7-\uA827\uA840-\uA873\uA880-\uA8C4\uA8D0-\uA8D9\uA8E0-\uA8F7\uA8FB\uA900-\uA92D\uA930-\uA953\uA960-\uA97C\uA980-\uA9C0\uA9CF-\uA9D9\uA9E0-\uA9FE\uAA00-\uAA36\uAA40-\uAA4D\uAA50-\uAA59\uAA60-\uAA76\uAA7A-\uAAC2\uAADB-\uAADD\uAAE0-\uAAEF\uAAF2-\uAAF6\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E\uAB30-\uAB5A\uAB5C-\uAB5F\uAB64\uAB65\uABC0-\uABEA\uABEC\uABED\uABF0-\uABF9\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D-\uFB28\uFB2A-\uFB36\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE00-\uFE0F\uFE20-\uFE2D\uFE33\uFE34\uFE4D-\uFE4F\uFE70-\uFE74\uFE76-\uFEFC\uFF10-\uFF19\uFF21-\uFF3A\uFF3F\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF\uFFD2-\uFFD7\uFFDA-\uFFDC]|\uD800[\uDC00-\uDC0B\uDC0D-\uDC26\uDC28-\uDC3A\uDC3C\uDC3D\uDC3F-\uDC4D\uDC50-\uDC5D\uDC80-\uDCFA\uDD40-\uDD74\uDDFD\uDE80-\uDE9C\uDEA0-\uDED0\uDEE0\uDF00-\uDF1F\uDF30-\uDF4A\uDF50-\uDF7A\uDF80-\uDF9D\uDFA0-\uDFC3\uDFC8-\uDFCF\uDFD1-\uDFD5]|\uD801[\uDC00-\uDC9D\uDCA0-\uDCA9\uDD00-\uDD27\uDD30-\uDD63\uDE00-\uDF36\uDF40-\uDF55\uDF60-\uDF67]|\uD802[\uDC00-\uDC05\uDC08\uDC0A-\uDC35\uDC37\uDC38\uDC3C\uDC3F-\uDC55\uDC60-\uDC76\uDC80-\uDC9E\uDD00-\uDD15\uDD20-\uDD39\uDD80-\uDDB7\uDDBE\uDDBF\uDE00-\uDE03\uDE05\uDE06\uDE0C-\uDE13\uDE15-\uDE17\uDE19-\uDE33\uDE38-\uDE3A\uDE3F\uDE60-\uDE7C\uDE80-\uDE9C\uDEC0-\uDEC7\uDEC9-\uDEE6\uDF00-\uDF35\uDF40-\uDF55\uDF60-\uDF72\uDF80-\uDF91]|\uD803[\uDC00-\uDC48]|\uD804[\uDC00-\uDC46\uDC66-\uDC6F\uDC7F-\uDCBA\uDCD0-\uDCE8\uDCF0-\uDCF9\uDD00-\uDD34\uDD36-\uDD3F\uDD50-\uDD73\uDD76\uDD80-\uDDC4\uDDD0-\uDDDA\uDE00-\uDE11\uDE13-\uDE37\uDEB0-\uDEEA\uDEF0-\uDEF9\uDF01-\uDF03\uDF05-\uDF0C\uDF0F\uDF10\uDF13-\uDF28\uDF2A-\uDF30\uDF32\uDF33\uDF35-\uDF39\uDF3C-\uDF44\uDF47\uDF48\uDF4B-\uDF4D\uDF57\uDF5D-\uDF63\uDF66-\uDF6C\uDF70-\uDF74]|\uD805[\uDC80-\uDCC5\uDCC7\uDCD0-\uDCD9\uDD80-\uDDB5\uDDB8-\uDDC0\uDE00-\uDE40\uDE44\uDE50-\uDE59\uDE80-\uDEB7\uDEC0-\uDEC9]|\uD806[\uDCA0-\uDCE9\uDCFF\uDEC0-\uDEF8]|\uD808[\uDC00-\uDF98]|\uD809[\uDC00-\uDC6E]|[\uD80C\uD840-\uD868\uD86A-\uD86C][\uDC00-\uDFFF]|\uD80D[\uDC00-\uDC2E]|\uD81A[\uDC00-\uDE38\uDE40-\uDE5E\uDE60-\uDE69\uDED0-\uDEED\uDEF0-\uDEF4\uDF00-\uDF36\uDF40-\uDF43\uDF50-\uDF59\uDF63-\uDF77\uDF7D-\uDF8F]|\uD81B[\uDF00-\uDF44\uDF50-\uDF7E\uDF8F-\uDF9F]|\uD82C[\uDC00\uDC01]|\uD82F[\uDC00-\uDC6A\uDC70-\uDC7C\uDC80-\uDC88\uDC90-\uDC99\uDC9D\uDC9E]|\uD834[\uDD65-\uDD69\uDD6D-\uDD72\uDD7B-\uDD82\uDD85-\uDD8B\uDDAA-\uDDAD\uDE42-\uDE44]|\uD835[\uDC00-\uDC54\uDC56-\uDC9C\uDC9E\uDC9F\uDCA2\uDCA5\uDCA6\uDCA9-\uDCAC\uDCAE-\uDCB9\uDCBB\uDCBD-\uDCC3\uDCC5-\uDD05\uDD07-\uDD0A\uDD0D-\uDD14\uDD16-\uDD1C\uDD1E-\uDD39\uDD3B-\uDD3E\uDD40-\uDD44\uDD46\uDD4A-\uDD50\uDD52-\uDEA5\uDEA8-\uDEC0\uDEC2-\uDEDA\uDEDC-\uDEFA\uDEFC-\uDF14\uDF16-\uDF34\uDF36-\uDF4E\uDF50-\uDF6E\uDF70-\uDF88\uDF8A-\uDFA8\uDFAA-\uDFC2\uDFC4-\uDFCB\uDFCE-\uDFFF]|\uD83A[\uDC00-\uDCC4\uDCD0-\uDCD6]|\uD83B[\uDE00-\uDE03\uDE05-\uDE1F\uDE21\uDE22\uDE24\uDE27\uDE29-\uDE32\uDE34-\uDE37\uDE39\uDE3B\uDE42\uDE47\uDE49\uDE4B\uDE4D-\uDE4F\uDE51\uDE52\uDE54\uDE57\uDE59\uDE5B\uDE5D\uDE5F\uDE61\uDE62\uDE64\uDE67-\uDE6A\uDE6C-\uDE72\uDE74-\uDE77\uDE79-\uDE7C\uDE7E\uDE80-\uDE89\uDE8B-\uDE9B\uDEA1-\uDEA3\uDEA5-\uDEA9\uDEAB-\uDEBB]|\uD869[\uDC00-\uDED6\uDF00-\uDFFF]|\uD86D[\uDC00-\uDF34\uDF40-\uDFFF]|\uD86E[\uDC00-\uDC1D]|\uD87E[\uDC00-\uDE1D]|\uDB40[\uDD00-\uDDEF]/ + }; + + function isDecimalDigit(ch) { + return 0x30 <= ch && ch <= 0x39; // 0..9 + } + + function isHexDigit(ch) { + return 0x30 <= ch && ch <= 0x39 || // 0..9 + 0x61 <= ch && ch <= 0x66 || // a..f + 0x41 <= ch && ch <= 0x46; // A..F + } + + function isOctalDigit(ch) { + return ch >= 0x30 && ch <= 0x37; // 0..7 + } + + // 7.2 White Space + + NON_ASCII_WHITESPACES = [ + 0x1680, 0x180E, + 0x2000, 0x2001, 0x2002, 0x2003, 0x2004, 0x2005, 0x2006, 0x2007, 0x2008, 0x2009, 0x200A, + 0x202F, 0x205F, + 0x3000, + 0xFEFF + ]; + + function isWhiteSpace(ch) { + return ch === 0x20 || ch === 0x09 || ch === 0x0B || ch === 0x0C || ch === 0xA0 || + ch >= 0x1680 && NON_ASCII_WHITESPACES.indexOf(ch) >= 0; + } + + // 7.3 Line Terminators + + function isLineTerminator(ch) { + return ch === 0x0A || ch === 0x0D || ch === 0x2028 || ch === 0x2029; + } + + // 7.6 Identifier Names and Identifiers + + function fromCodePoint(cp) { + if (cp <= 0xFFFF) { return String.fromCharCode(cp); } + var cu1 = String.fromCharCode(Math.floor((cp - 0x10000) / 0x400) + 0xD800); + var cu2 = String.fromCharCode(((cp - 0x10000) % 0x400) + 0xDC00); + return cu1 + cu2; + } + + IDENTIFIER_START = new Array(0x80); + for(ch = 0; ch < 0x80; ++ch) { + IDENTIFIER_START[ch] = + ch >= 0x61 && ch <= 0x7A || // a..z + ch >= 0x41 && ch <= 0x5A || // A..Z + ch === 0x24 || ch === 0x5F; // $ (dollar) and _ (underscore) + } + + IDENTIFIER_PART = new Array(0x80); + for(ch = 0; ch < 0x80; ++ch) { + IDENTIFIER_PART[ch] = + ch >= 0x61 && ch <= 0x7A || // a..z + ch >= 0x41 && ch <= 0x5A || // A..Z + ch >= 0x30 && ch <= 0x39 || // 0..9 + ch === 0x24 || ch === 0x5F; // $ (dollar) and _ (underscore) + } + + function isIdentifierStartES5(ch) { + return ch < 0x80 ? IDENTIFIER_START[ch] : ES5Regex.NonAsciiIdentifierStart.test(fromCodePoint(ch)); + } + + function isIdentifierPartES5(ch) { + return ch < 0x80 ? IDENTIFIER_PART[ch] : ES5Regex.NonAsciiIdentifierPart.test(fromCodePoint(ch)); + } + + function isIdentifierStartES6(ch) { + return ch < 0x80 ? IDENTIFIER_START[ch] : ES6Regex.NonAsciiIdentifierStart.test(fromCodePoint(ch)); + } + + function isIdentifierPartES6(ch) { + return ch < 0x80 ? IDENTIFIER_PART[ch] : ES6Regex.NonAsciiIdentifierPart.test(fromCodePoint(ch)); + } + + module.exports = { + isDecimalDigit: isDecimalDigit, + isHexDigit: isHexDigit, + isOctalDigit: isOctalDigit, + isWhiteSpace: isWhiteSpace, + isLineTerminator: isLineTerminator, + isIdentifierStartES5: isIdentifierStartES5, + isIdentifierPartES5: isIdentifierPartES5, + isIdentifierStartES6: isIdentifierStartES6, + isIdentifierPartES6: isIdentifierPartES6 + }; +}()); +/* vim: set sw=4 ts=4 et tw=80 : */ + + +/***/ }), +/* 446 */ +/***/ (function(module, exports, __webpack_require__) { + +/* + Copyright (C) 2013 Yusuke Suzuki + + Redistribution and use in source and binary forms, with or without + modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + + THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + ARE DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY + DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +(function () { + 'use strict'; + + var code = __webpack_require__(445); + + function isStrictModeReservedWordES6(id) { + switch (id) { + case 'implements': + case 'interface': + case 'package': + case 'private': + case 'protected': + case 'public': + case 'static': + case 'let': + return true; + default: + return false; + } + } + + function isKeywordES5(id, strict) { + // yield should not be treated as keyword under non-strict mode. + if (!strict && id === 'yield') { + return false; + } + return isKeywordES6(id, strict); + } + + function isKeywordES6(id, strict) { + if (strict && isStrictModeReservedWordES6(id)) { + return true; + } + + switch (id.length) { + case 2: + return (id === 'if') || (id === 'in') || (id === 'do'); + case 3: + return (id === 'var') || (id === 'for') || (id === 'new') || (id === 'try'); + case 4: + return (id === 'this') || (id === 'else') || (id === 'case') || + (id === 'void') || (id === 'with') || (id === 'enum'); + case 5: + return (id === 'while') || (id === 'break') || (id === 'catch') || + (id === 'throw') || (id === 'const') || (id === 'yield') || + (id === 'class') || (id === 'super'); + case 6: + return (id === 'return') || (id === 'typeof') || (id === 'delete') || + (id === 'switch') || (id === 'export') || (id === 'import'); + case 7: + return (id === 'default') || (id === 'finally') || (id === 'extends'); + case 8: + return (id === 'function') || (id === 'continue') || (id === 'debugger'); + case 10: + return (id === 'instanceof'); + default: + return false; + } + } + + function isReservedWordES5(id, strict) { + return id === 'null' || id === 'true' || id === 'false' || isKeywordES5(id, strict); + } + + function isReservedWordES6(id, strict) { + return id === 'null' || id === 'true' || id === 'false' || isKeywordES6(id, strict); + } + + function isRestrictedWord(id) { + return id === 'eval' || id === 'arguments'; + } + + function isIdentifierNameES5(id) { + var i, iz, ch; + + if (id.length === 0) { return false; } + + ch = id.charCodeAt(0); + if (!code.isIdentifierStartES5(ch)) { + return false; + } + + for (i = 1, iz = id.length; i < iz; ++i) { + ch = id.charCodeAt(i); + if (!code.isIdentifierPartES5(ch)) { + return false; + } + } + return true; + } + + function decodeUtf16(lead, trail) { + return (lead - 0xD800) * 0x400 + (trail - 0xDC00) + 0x10000; + } + + function isIdentifierNameES6(id) { + var i, iz, ch, lowCh, check; + + if (id.length === 0) { return false; } + + check = code.isIdentifierStartES6; + for (i = 0, iz = id.length; i < iz; ++i) { + ch = id.charCodeAt(i); + if (0xD800 <= ch && ch <= 0xDBFF) { + ++i; + if (i >= iz) { return false; } + lowCh = id.charCodeAt(i); + if (!(0xDC00 <= lowCh && lowCh <= 0xDFFF)) { + return false; + } + ch = decodeUtf16(ch, lowCh); + } + if (!check(ch)) { + return false; + } + check = code.isIdentifierPartES6; + } + return true; + } + + function isIdentifierES5(id, strict) { + return isIdentifierNameES5(id) && !isReservedWordES5(id, strict); + } + + function isIdentifierES6(id, strict) { + return isIdentifierNameES6(id) && !isReservedWordES6(id, strict); + } + + module.exports = { + isKeywordES5: isKeywordES5, + isKeywordES6: isKeywordES6, + isReservedWordES5: isReservedWordES5, + isReservedWordES6: isReservedWordES6, + isRestrictedWord: isRestrictedWord, + isIdentifierNameES5: isIdentifierNameES5, + isIdentifierNameES6: isIdentifierNameES6, + isIdentifierES5: isIdentifierES5, + isIdentifierES6: isIdentifierES6 + }; +}()); +/* vim: set sw=4 ts=4 et tw=80 : */ + + +/***/ }), +/* 447 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +const escapeStringRegexp = __webpack_require__(3); +const ansiStyles = __webpack_require__(448); +const stdoutColor = __webpack_require__(449).stdout; + +const template = __webpack_require__(450); + +const isSimpleWindowsTerm = process.platform === 'win32' && !(process.env.TERM || '').toLowerCase().startsWith('xterm'); + +// `supportsColor.level` → `ansiStyles.color[name]` mapping +const levelMapping = ['ansi', 'ansi', 'ansi256', 'ansi16m']; + +// `color-convert` models to exclude from the Chalk API due to conflicts and such +const skipModels = new Set(['gray']); + +const styles = Object.create(null); + +function applyOptions(obj, options) { + options = options || {}; + + // Detect level if not set manually + const scLevel = stdoutColor ? stdoutColor.level : 0; + obj.level = options.level === undefined ? scLevel : options.level; + obj.enabled = 'enabled' in options ? options.enabled : obj.level > 0; +} + +function Chalk(options) { + // We check for this.template here since calling `chalk.constructor()` + // by itself will have a `this` of a previously constructed chalk object + if (!this || !(this instanceof Chalk) || this.template) { + const chalk = {}; + applyOptions(chalk, options); + + chalk.template = function () { + const args = [].slice.call(arguments); + return chalkTag.apply(null, [chalk.template].concat(args)); + }; + + Object.setPrototypeOf(chalk, Chalk.prototype); + Object.setPrototypeOf(chalk.template, chalk); + + chalk.template.constructor = Chalk; + + return chalk.template; + } + + applyOptions(this, options); +} + +// Use bright blue on Windows as the normal blue color is illegible +if (isSimpleWindowsTerm) { + ansiStyles.blue.open = '\u001B[94m'; +} + +for (const key of Object.keys(ansiStyles)) { + ansiStyles[key].closeRe = new RegExp(escapeStringRegexp(ansiStyles[key].close), 'g'); + + styles[key] = { + get() { + const codes = ansiStyles[key]; + return build.call(this, this._styles ? this._styles.concat(codes) : [codes], this._empty, key); + } + }; +} + +styles.visible = { + get() { + return build.call(this, this._styles || [], true, 'visible'); + } +}; + +ansiStyles.color.closeRe = new RegExp(escapeStringRegexp(ansiStyles.color.close), 'g'); +for (const model of Object.keys(ansiStyles.color.ansi)) { + if (skipModels.has(model)) { + continue; + } + + styles[model] = { + get() { + const level = this.level; + return function () { + const open = ansiStyles.color[levelMapping[level]][model].apply(null, arguments); + const codes = { + open, + close: ansiStyles.color.close, + closeRe: ansiStyles.color.closeRe + }; + return build.call(this, this._styles ? this._styles.concat(codes) : [codes], this._empty, model); + }; + } + }; +} + +ansiStyles.bgColor.closeRe = new RegExp(escapeStringRegexp(ansiStyles.bgColor.close), 'g'); +for (const model of Object.keys(ansiStyles.bgColor.ansi)) { + if (skipModels.has(model)) { + continue; + } + + const bgModel = 'bg' + model[0].toUpperCase() + model.slice(1); + styles[bgModel] = { + get() { + const level = this.level; + return function () { + const open = ansiStyles.bgColor[levelMapping[level]][model].apply(null, arguments); + const codes = { + open, + close: ansiStyles.bgColor.close, + closeRe: ansiStyles.bgColor.closeRe + }; + return build.call(this, this._styles ? this._styles.concat(codes) : [codes], this._empty, model); + }; + } + }; +} + +const proto = Object.defineProperties(() => {}, styles); + +function build(_styles, _empty, key) { + const builder = function () { + return applyStyle.apply(builder, arguments); + }; + + builder._styles = _styles; + builder._empty = _empty; + + const self = this; + + Object.defineProperty(builder, 'level', { + enumerable: true, + get() { + return self.level; + }, + set(level) { + self.level = level; + } + }); + + Object.defineProperty(builder, 'enabled', { + enumerable: true, + get() { + return self.enabled; + }, + set(enabled) { + self.enabled = enabled; + } + }); + + // See below for fix regarding invisible grey/dim combination on Windows + builder.hasGrey = this.hasGrey || key === 'gray' || key === 'grey'; + + // `__proto__` is used because we must return a function, but there is + // no way to create a function with a different prototype + builder.__proto__ = proto; // eslint-disable-line no-proto + + return builder; +} + +function applyStyle() { + // Support varags, but simply cast to string in case there's only one arg + const args = arguments; + const argsLen = args.length; + let str = String(arguments[0]); + + if (argsLen === 0) { + return ''; + } + + if (argsLen > 1) { + // Don't slice `arguments`, it prevents V8 optimizations + for (let a = 1; a < argsLen; a++) { + str += ' ' + args[a]; + } + } + + if (!this.enabled || this.level <= 0 || !str) { + return this._empty ? '' : str; + } + + // Turns out that on Windows dimmed gray text becomes invisible in cmd.exe, + // see https://github.com/chalk/chalk/issues/58 + // If we're on Windows and we're dealing with a gray color, temporarily make 'dim' a noop. + const originalDim = ansiStyles.dim.open; + if (isSimpleWindowsTerm && this.hasGrey) { + ansiStyles.dim.open = ''; + } + + for (const code of this._styles.slice().reverse()) { + // Replace any instances already present with a re-opening code + // otherwise only the part of the string until said closing code + // will be colored, and the rest will simply be 'plain'. + str = code.open + str.replace(code.closeRe, code.open) + code.close; + + // Close the styling before a linebreak and reopen + // after next line to fix a bleed issue on macOS + // https://github.com/chalk/chalk/pull/92 + str = str.replace(/\r?\n/g, `${code.close}$&${code.open}`); + } + + // Reset the original `dim` if we changed it to work around the Windows dimmed gray issue + ansiStyles.dim.open = originalDim; + + return str; +} + +function chalkTag(chalk, strings) { + if (!Array.isArray(strings)) { + // If chalk() was called by itself or with a string, + // return the string itself as a string. + return [].slice.call(arguments, 1).join(' '); + } + + const args = [].slice.call(arguments, 2); + const parts = [strings.raw[0]]; + + for (let i = 1; i < strings.length; i++) { + parts.push(String(args[i - 1]).replace(/[{}\\]/g, '\\$&')); + parts.push(String(strings.raw[i])); + } + + return template(chalk, parts.join('')); +} + +Object.defineProperties(Chalk.prototype, styles); + +module.exports = Chalk(); // eslint-disable-line new-cap +module.exports.supportsColor = stdoutColor; +module.exports.default = module.exports; // For TypeScript + + +/***/ }), +/* 448 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; +/* WEBPACK VAR INJECTION */(function(module) { +const colorConvert = __webpack_require__(6); + +const wrapAnsi16 = (fn, offset) => function () { + const code = fn.apply(colorConvert, arguments); + return `\u001B[${code + offset}m`; +}; + +const wrapAnsi256 = (fn, offset) => function () { + const code = fn.apply(colorConvert, arguments); + return `\u001B[${38 + offset};5;${code}m`; +}; + +const wrapAnsi16m = (fn, offset) => function () { + const rgb = fn.apply(colorConvert, arguments); + return `\u001B[${38 + offset};2;${rgb[0]};${rgb[1]};${rgb[2]}m`; +}; + +function assembleStyles() { + const codes = new Map(); + const styles = { + modifier: { + reset: [0, 0], + // 21 isn't widely supported and 22 does the same thing + bold: [1, 22], + dim: [2, 22], + italic: [3, 23], + underline: [4, 24], + inverse: [7, 27], + hidden: [8, 28], + strikethrough: [9, 29] + }, + color: { + black: [30, 39], + red: [31, 39], + green: [32, 39], + yellow: [33, 39], + blue: [34, 39], + magenta: [35, 39], + cyan: [36, 39], + white: [37, 39], + gray: [90, 39], + + // Bright color + redBright: [91, 39], + greenBright: [92, 39], + yellowBright: [93, 39], + blueBright: [94, 39], + magentaBright: [95, 39], + cyanBright: [96, 39], + whiteBright: [97, 39] + }, + bgColor: { + bgBlack: [40, 49], + bgRed: [41, 49], + bgGreen: [42, 49], + bgYellow: [43, 49], + bgBlue: [44, 49], + bgMagenta: [45, 49], + bgCyan: [46, 49], + bgWhite: [47, 49], + + // Bright color + bgBlackBright: [100, 49], + bgRedBright: [101, 49], + bgGreenBright: [102, 49], + bgYellowBright: [103, 49], + bgBlueBright: [104, 49], + bgMagentaBright: [105, 49], + bgCyanBright: [106, 49], + bgWhiteBright: [107, 49] + } + }; + + // Fix humans + styles.color.grey = styles.color.gray; + + for (const groupName of Object.keys(styles)) { + const group = styles[groupName]; + + for (const styleName of Object.keys(group)) { + const style = group[styleName]; + + styles[styleName] = { + open: `\u001B[${style[0]}m`, + close: `\u001B[${style[1]}m` + }; + + group[styleName] = styles[styleName]; + + codes.set(style[0], style[1]); + } + + Object.defineProperty(styles, groupName, { + value: group, + enumerable: false + }); + + Object.defineProperty(styles, 'codes', { + value: codes, + enumerable: false + }); + } + + const ansi2ansi = n => n; + const rgb2rgb = (r, g, b) => [r, g, b]; + + styles.color.close = '\u001B[39m'; + styles.bgColor.close = '\u001B[49m'; + + styles.color.ansi = { + ansi: wrapAnsi16(ansi2ansi, 0) + }; + styles.color.ansi256 = { + ansi256: wrapAnsi256(ansi2ansi, 0) + }; + styles.color.ansi16m = { + rgb: wrapAnsi16m(rgb2rgb, 0) + }; + + styles.bgColor.ansi = { + ansi: wrapAnsi16(ansi2ansi, 10) + }; + styles.bgColor.ansi256 = { + ansi256: wrapAnsi256(ansi2ansi, 10) + }; + styles.bgColor.ansi16m = { + rgb: wrapAnsi16m(rgb2rgb, 10) + }; + + for (let key of Object.keys(colorConvert)) { + if (typeof colorConvert[key] !== 'object') { + continue; + } + + const suite = colorConvert[key]; + + if (key === 'ansi16') { + key = 'ansi'; + } + + if ('ansi16' in suite) { + styles.color.ansi[key] = wrapAnsi16(suite.ansi16, 0); + styles.bgColor.ansi[key] = wrapAnsi16(suite.ansi16, 10); + } + + if ('ansi256' in suite) { + styles.color.ansi256[key] = wrapAnsi256(suite.ansi256, 0); + styles.bgColor.ansi256[key] = wrapAnsi256(suite.ansi256, 10); + } + + if ('rgb' in suite) { + styles.color.ansi16m[key] = wrapAnsi16m(suite.rgb, 0); + styles.bgColor.ansi16m[key] = wrapAnsi16m(suite.rgb, 10); + } + } + + return styles; +} + +// Make the export immutable +Object.defineProperty(module, 'exports', { + enumerable: true, + get: assembleStyles +}); + +/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(5)(module))) + +/***/ }), +/* 449 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +const os = __webpack_require__(11); +const hasFlag = __webpack_require__(12); + +const env = process.env; + +let forceColor; +if (hasFlag('no-color') || + hasFlag('no-colors') || + hasFlag('color=false')) { + forceColor = false; +} else if (hasFlag('color') || + hasFlag('colors') || + hasFlag('color=true') || + hasFlag('color=always')) { + forceColor = true; +} +if ('FORCE_COLOR' in env) { + forceColor = env.FORCE_COLOR.length === 0 || parseInt(env.FORCE_COLOR, 10) !== 0; +} + +function translateLevel(level) { + if (level === 0) { + return false; + } + + return { + level, + hasBasic: true, + has256: level >= 2, + has16m: level >= 3 + }; +} + +function supportsColor(stream) { + if (forceColor === false) { + return 0; + } + + if (hasFlag('color=16m') || + hasFlag('color=full') || + hasFlag('color=truecolor')) { + return 3; + } + + if (hasFlag('color=256')) { + return 2; + } + + if (stream && !stream.isTTY && forceColor !== true) { + // VS code debugger doesn't have isTTY set + if (env.VSCODE_PID) { + return 1; + } + return 0; + } + + const min = forceColor ? 1 : 0; + + if (process.platform === 'win32') { + // Node.js 7.5.0 is the first version of Node.js to include a patch to + // libuv that enables 256 color output on Windows. Anything earlier and it + // won't work. However, here we target Node.js 8 at minimum as it is an LTS + // release, and Node.js 7 is not. Windows 10 build 10586 is the first Windows + // release that supports 256 colors. Windows 10 build 14931 is the first release + // that supports 16m/TrueColor. + const osRelease = os.release().split('.'); + if ( + Number(process.versions.node.split('.')[0]) >= 8 && + Number(osRelease[0]) >= 10 && + Number(osRelease[2]) >= 10586 + ) { + return Number(osRelease[2]) >= 14931 ? 3 : 2; + } + + return 1; + } + + if ('CI' in env) { + if (['TRAVIS', 'CIRCLECI', 'APPVEYOR', 'GITLAB_CI'].some(sign => sign in env) || env.CI_NAME === 'codeship') { + return 1; + } + + return min; + } + + if ('TEAMCITY_VERSION' in env) { + return /^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(env.TEAMCITY_VERSION) ? 1 : 0; + } + + if (env.COLORTERM === 'truecolor') { + return 3; + } + + if ('TERM_PROGRAM' in env) { + const version = parseInt((env.TERM_PROGRAM_VERSION || '').split('.')[0], 10); + + switch (env.TERM_PROGRAM) { + case 'iTerm.app': + return version >= 3 ? 3 : 2; + case 'Apple_Terminal': + return 2; + // No default + } + } + + if (/-256(color)?$/i.test(env.TERM)) { + return 2; + } + + if (/^screen|^xterm|^vt100|^rxvt|color|ansi|cygwin|linux/i.test(env.TERM)) { + return 1; + } + + if ('COLORTERM' in env) { + return 1; + } + + if (env.TERM === 'dumb') { + return min; + } + + return min; +} + +function getSupportLevel(stream) { + const level = supportsColor(stream); + return translateLevel(level); +} + +module.exports = { + supportsColor: getSupportLevel, + stdout: getSupportLevel(process.stdout), + stderr: getSupportLevel(process.stderr) +}; + + +/***/ }), +/* 450 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +const TEMPLATE_REGEX = /(?:\\(u[a-f\d]{4}|x[a-f\d]{2}|.))|(?:\{(~)?(\w+(?:\([^)]*\))?(?:\.\w+(?:\([^)]*\))?)*)(?:[ \t]|(?=\r?\n)))|(\})|((?:.|[\r\n\f])+?)/gi; +const STYLE_REGEX = /(?:^|\.)(\w+)(?:\(([^)]*)\))?/g; +const STRING_REGEX = /^(['"])((?:\\.|(?!\1)[^\\])*)\1$/; +const ESCAPE_REGEX = /\\(u[a-f\d]{4}|x[a-f\d]{2}|.)|([^\\])/gi; + +const ESCAPES = new Map([ + ['n', '\n'], + ['r', '\r'], + ['t', '\t'], + ['b', '\b'], + ['f', '\f'], + ['v', '\v'], + ['0', '\0'], + ['\\', '\\'], + ['e', '\u001B'], + ['a', '\u0007'] +]); + +function unescape(c) { + if ((c[0] === 'u' && c.length === 5) || (c[0] === 'x' && c.length === 3)) { + return String.fromCharCode(parseInt(c.slice(1), 16)); + } + + return ESCAPES.get(c) || c; +} + +function parseArguments(name, args) { + const results = []; + const chunks = args.trim().split(/\s*,\s*/g); + let matches; + + for (const chunk of chunks) { + if (!isNaN(chunk)) { + results.push(Number(chunk)); + } else if ((matches = chunk.match(STRING_REGEX))) { + results.push(matches[2].replace(ESCAPE_REGEX, (m, escape, chr) => escape ? unescape(escape) : chr)); + } else { + throw new Error(`Invalid Chalk template style argument: ${chunk} (in style '${name}')`); + } + } + + return results; +} + +function parseStyle(style) { + STYLE_REGEX.lastIndex = 0; + + const results = []; + let matches; + + while ((matches = STYLE_REGEX.exec(style)) !== null) { + const name = matches[1]; + + if (matches[2]) { + const args = parseArguments(name, matches[2]); + results.push([name].concat(args)); + } else { + results.push([name]); + } + } + + return results; +} + +function buildStyle(chalk, styles) { + const enabled = {}; + + for (const layer of styles) { + for (const style of layer.styles) { + enabled[style[0]] = layer.inverse ? null : style.slice(1); + } + } + + let current = chalk; + for (const styleName of Object.keys(enabled)) { + if (Array.isArray(enabled[styleName])) { + if (!(styleName in current)) { + throw new Error(`Unknown Chalk style: ${styleName}`); + } + + if (enabled[styleName].length > 0) { + current = current[styleName].apply(current, enabled[styleName]); + } else { + current = current[styleName]; + } + } + } + + return current; +} + +module.exports = (chalk, tmp) => { + const styles = []; + const chunks = []; + let chunk = []; + + // eslint-disable-next-line max-params + tmp.replace(TEMPLATE_REGEX, (m, escapeChar, inverse, style, close, chr) => { + if (escapeChar) { + chunk.push(unescape(escapeChar)); + } else if (style) { + const str = chunk.join(''); + chunk = []; + chunks.push(styles.length === 0 ? str : buildStyle(chalk, styles)(str)); + styles.push({inverse, styles: parseStyle(style)}); + } else if (close) { + if (styles.length === 0) { + throw new Error('Found extraneous } in Chalk template literal'); + } + + chunks.push(buildStyle(chalk, styles)(chunk.join(''))); + chunk = []; + styles.pop(); + } else { + chunk.push(chr); + } + }); + + chunks.push(chunk.join('')); + + if (styles.length > 0) { + const errMsg = `Chalk template literal is missing ${styles.length} closing bracket${styles.length === 1 ? '' : 's'} (\`}\`)`; + throw new Error(errMsg); + } + + return chunks.join(''); +}; + + +/***/ }), +/* 451 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +Object.defineProperty(exports, "__esModule", { value: true }); +var kbn_client_1 = __webpack_require__(452); +exports.KbnClient = kbn_client_1.KbnClient; +var kbn_client_requester_1 = __webpack_require__(453); +exports.uriencode = kbn_client_requester_1.uriencode; + + +/***/ }), +/* 452 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +Object.defineProperty(exports, "__esModule", { value: true }); +const kbn_client_requester_1 = __webpack_require__(453); +const kbn_client_status_1 = __webpack_require__(495); +const kbn_client_plugins_1 = __webpack_require__(496); +const kbn_client_version_1 = __webpack_require__(497); +const kbn_client_saved_objects_1 = __webpack_require__(498); +const kbn_client_ui_settings_1 = __webpack_require__(499); +class KbnClient { + /** + * Basic Kibana server client that implements common behaviors for talking + * to the Kibana server from dev tooling. + * + * @param log ToolingLog + * @param kibanaUrls Array of kibana server urls to send requests to + * @param uiSettingDefaults Map of uiSetting values that will be merged with all uiSetting resets + */ + constructor(log, kibanaUrls, uiSettingDefaults) { + this.log = log; + this.kibanaUrls = kibanaUrls; + this.uiSettingDefaults = uiSettingDefaults; + this.requester = new kbn_client_requester_1.KbnClientRequester(this.log, this.kibanaUrls); + this.status = new kbn_client_status_1.KbnClientStatus(this.requester); + this.plugins = new kbn_client_plugins_1.KbnClientPlugins(this.status); + this.version = new kbn_client_version_1.KbnClientVersion(this.status); + this.savedObjects = new kbn_client_saved_objects_1.KbnClientSavedObjects(this.log, this.requester); + this.uiSettings = new kbn_client_ui_settings_1.KbnClientUiSettings(this.log, this.requester, this.uiSettingDefaults); + if (!kibanaUrls.length) { + throw new Error('missing Kibana urls'); + } + } + /** + * Make a direct request to the Kibana server + */ + async request(options) { + return await this.requester.request(options); + } + resolveUrl(relativeUrl) { + return this.requester.resolveUrl(relativeUrl); + } +} +exports.KbnClient = KbnClient; + + +/***/ }), +/* 453 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +Object.defineProperty(exports, "__esModule", { value: true }); +const tslib_1 = __webpack_require__(36); +const url_1 = tslib_1.__importDefault(__webpack_require__(454)); +const axios_1 = tslib_1.__importDefault(__webpack_require__(455)); +const axios_2 = __webpack_require__(493); +const isConcliftOnGetError = (error) => { + return (axios_2.isAxiosResponseError(error) && error.config.method === 'GET' && error.response.status === 409); +}; +exports.uriencode = (strings, ...values) => { + const queue = strings.slice(); + if (queue.length === 0) { + throw new Error('how could strings passed to `uriencode` template tag be empty?'); + } + if (queue.length !== values.length + 1) { + throw new Error('strings and values passed to `uriencode` template tag are unbalanced'); + } + // pull the first string off the queue, there is one less item in `values` + // since the values are always wrapped in strings, so we shift the extra string + // off the queue to balance the queue and values array. + const leadingString = queue.shift(); + return queue.reduce((acc, string, i) => `${acc}${encodeURIComponent(values[i])}${string}`, leadingString); +}; +const DEFAULT_MAX_ATTEMPTS = 5; +const delay = (ms) => new Promise(resolve => { + setTimeout(resolve, ms); +}); +class KbnClientRequester { + constructor(log, kibanaUrls) { + this.log = log; + this.kibanaUrls = kibanaUrls; + } + pickUrl() { + const url = this.kibanaUrls.shift(); + this.kibanaUrls.push(url); + return url; + } + resolveUrl(relativeUrl = '/') { + return url_1.default.resolve(this.pickUrl(), relativeUrl); + } + async request(options) { + var _a; + const url = url_1.default.resolve(this.pickUrl(), options.path); + const description = options.description || `${options.method} ${url}`; + let attempt = 0; + const maxAttempts = (_a = options.retries, (_a !== null && _a !== void 0 ? _a : DEFAULT_MAX_ATTEMPTS)); + while (true) { + attempt += 1; + try { + const response = await axios_1.default.request({ + method: options.method, + url, + data: options.body, + params: options.query, + headers: { + 'kbn-xsrf': 'kbn-client', + }, + }); + return response.data; + } + catch (error) { + const conflictOnGet = isConcliftOnGetError(error); + const requestedRetries = options.retries !== undefined; + const failedToGetResponse = axios_2.isAxiosRequestError(error); + let errorMessage; + if (conflictOnGet) { + errorMessage = `Conflict on GET (path=${options.path}, attempt=${attempt}/${maxAttempts})`; + this.log.error(errorMessage); + } + else if (requestedRetries || failedToGetResponse) { + errorMessage = `[${description}] request failed (attempt=${attempt}/${maxAttempts})`; + this.log.error(errorMessage); + } + else { + throw error; + } + if (attempt < maxAttempts) { + await delay(1000 * attempt); + continue; + } + throw new Error(`${errorMessage} -- and ran out of retries`); + } + } + } +} +exports.KbnClientRequester = KbnClientRequester; + + +/***/ }), +/* 454 */ +/***/ (function(module, exports) { + +module.exports = require("url"); + +/***/ }), +/* 455 */ +/***/ (function(module, exports, __webpack_require__) { + +module.exports = __webpack_require__(456); + +/***/ }), +/* 456 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var utils = __webpack_require__(457); +var bind = __webpack_require__(458); +var Axios = __webpack_require__(460); +var defaults = __webpack_require__(461); + +/** + * Create an instance of Axios + * + * @param {Object} defaultConfig The default config for the instance + * @return {Axios} A new instance of Axios + */ +function createInstance(defaultConfig) { + var context = new Axios(defaultConfig); + var instance = bind(Axios.prototype.request, context); + + // Copy axios.prototype to instance + utils.extend(instance, Axios.prototype, context); + + // Copy context to instance + utils.extend(instance, context); + + return instance; +} + +// Create the default instance to be exported +var axios = createInstance(defaults); + +// Expose Axios class to allow class inheritance +axios.Axios = Axios; + +// Factory for creating new instances +axios.create = function create(instanceConfig) { + return createInstance(utils.merge(defaults, instanceConfig)); +}; + +// Expose Cancel & CancelToken +axios.Cancel = __webpack_require__(490); +axios.CancelToken = __webpack_require__(491); +axios.isCancel = __webpack_require__(487); + +// Expose all/spread +axios.all = function all(promises) { + return Promise.all(promises); +}; +axios.spread = __webpack_require__(492); + +module.exports = axios; + +// Allow use of default import syntax in TypeScript +module.exports.default = axios; + + +/***/ }), +/* 457 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var bind = __webpack_require__(458); +var isBuffer = __webpack_require__(459); + +/*global toString:true*/ + +// utils is a library of generic helper functions non-specific to axios + +var toString = Object.prototype.toString; + +/** + * Determine if a value is an Array + * + * @param {Object} val The value to test + * @returns {boolean} True if value is an Array, otherwise false + */ +function isArray(val) { + return toString.call(val) === '[object Array]'; +} + +/** + * Determine if a value is an ArrayBuffer + * + * @param {Object} val The value to test + * @returns {boolean} True if value is an ArrayBuffer, otherwise false + */ +function isArrayBuffer(val) { + return toString.call(val) === '[object ArrayBuffer]'; +} + +/** + * Determine if a value is a FormData + * + * @param {Object} val The value to test + * @returns {boolean} True if value is an FormData, otherwise false + */ +function isFormData(val) { + return (typeof FormData !== 'undefined') && (val instanceof FormData); +} + +/** + * Determine if a value is a view on an ArrayBuffer + * + * @param {Object} val The value to test + * @returns {boolean} True if value is a view on an ArrayBuffer, otherwise false + */ +function isArrayBufferView(val) { + var result; + if ((typeof ArrayBuffer !== 'undefined') && (ArrayBuffer.isView)) { + result = ArrayBuffer.isView(val); + } else { + result = (val) && (val.buffer) && (val.buffer instanceof ArrayBuffer); + } + return result; +} + +/** + * Determine if a value is a String + * + * @param {Object} val The value to test + * @returns {boolean} True if value is a String, otherwise false + */ +function isString(val) { + return typeof val === 'string'; +} + +/** + * Determine if a value is a Number + * + * @param {Object} val The value to test + * @returns {boolean} True if value is a Number, otherwise false + */ +function isNumber(val) { + return typeof val === 'number'; +} + +/** + * Determine if a value is undefined + * + * @param {Object} val The value to test + * @returns {boolean} True if the value is undefined, otherwise false + */ +function isUndefined(val) { + return typeof val === 'undefined'; +} + +/** + * Determine if a value is an Object + * + * @param {Object} val The value to test + * @returns {boolean} True if value is an Object, otherwise false + */ +function isObject(val) { + return val !== null && typeof val === 'object'; +} + +/** + * Determine if a value is a Date + * + * @param {Object} val The value to test + * @returns {boolean} True if value is a Date, otherwise false + */ +function isDate(val) { + return toString.call(val) === '[object Date]'; +} + +/** + * Determine if a value is a File + * + * @param {Object} val The value to test + * @returns {boolean} True if value is a File, otherwise false + */ +function isFile(val) { + return toString.call(val) === '[object File]'; +} + +/** + * Determine if a value is a Blob + * + * @param {Object} val The value to test + * @returns {boolean} True if value is a Blob, otherwise false + */ +function isBlob(val) { + return toString.call(val) === '[object Blob]'; +} + +/** + * Determine if a value is a Function + * + * @param {Object} val The value to test + * @returns {boolean} True if value is a Function, otherwise false + */ +function isFunction(val) { + return toString.call(val) === '[object Function]'; +} + +/** + * Determine if a value is a Stream + * + * @param {Object} val The value to test + * @returns {boolean} True if value is a Stream, otherwise false + */ +function isStream(val) { + return isObject(val) && isFunction(val.pipe); +} + +/** + * Determine if a value is a URLSearchParams object + * + * @param {Object} val The value to test + * @returns {boolean} True if value is a URLSearchParams object, otherwise false + */ +function isURLSearchParams(val) { + return typeof URLSearchParams !== 'undefined' && val instanceof URLSearchParams; +} + +/** + * Trim excess whitespace off the beginning and end of a string + * + * @param {String} str The String to trim + * @returns {String} The String freed of excess whitespace + */ +function trim(str) { + return str.replace(/^\s*/, '').replace(/\s*$/, ''); +} + +/** + * Determine if we're running in a standard browser environment + * + * This allows axios to run in a web worker, and react-native. + * Both environments support XMLHttpRequest, but not fully standard globals. + * + * web workers: + * typeof window -> undefined + * typeof document -> undefined + * + * react-native: + * navigator.product -> 'ReactNative' + */ +function isStandardBrowserEnv() { + if (typeof navigator !== 'undefined' && navigator.product === 'ReactNative') { + return false; + } + return ( + typeof window !== 'undefined' && + typeof document !== 'undefined' + ); +} + +/** + * Iterate over an Array or an Object invoking a function for each item. + * + * If `obj` is an Array callback will be called passing + * the value, index, and complete array for each item. + * + * If 'obj' is an Object callback will be called passing + * the value, key, and complete object for each property. + * + * @param {Object|Array} obj The object to iterate + * @param {Function} fn The callback to invoke for each item + */ +function forEach(obj, fn) { + // Don't bother if no value provided + if (obj === null || typeof obj === 'undefined') { + return; + } + + // Force an array if not already something iterable + if (typeof obj !== 'object') { + /*eslint no-param-reassign:0*/ + obj = [obj]; + } + + if (isArray(obj)) { + // Iterate over array values + for (var i = 0, l = obj.length; i < l; i++) { + fn.call(null, obj[i], i, obj); + } + } else { + // Iterate over object keys + for (var key in obj) { + if (Object.prototype.hasOwnProperty.call(obj, key)) { + fn.call(null, obj[key], key, obj); + } + } + } +} + +/** + * Accepts varargs expecting each argument to be an object, then + * immutably merges the properties of each object and returns result. + * + * When multiple objects contain the same key the later object in + * the arguments list will take precedence. + * + * Example: + * + * ```js + * var result = merge({foo: 123}, {foo: 456}); + * console.log(result.foo); // outputs 456 + * ``` + * + * @param {Object} obj1 Object to merge + * @returns {Object} Result of all merge properties + */ +function merge(/* obj1, obj2, obj3, ... */) { + var result = {}; + function assignValue(val, key) { + if (typeof result[key] === 'object' && typeof val === 'object') { + result[key] = merge(result[key], val); + } else { + result[key] = val; + } + } + + for (var i = 0, l = arguments.length; i < l; i++) { + forEach(arguments[i], assignValue); + } + return result; +} + +/** + * Extends object a by mutably adding to it the properties of object b. + * + * @param {Object} a The object to be extended + * @param {Object} b The object to copy properties from + * @param {Object} thisArg The object to bind function to + * @return {Object} The resulting value of object a + */ +function extend(a, b, thisArg) { + forEach(b, function assignValue(val, key) { + if (thisArg && typeof val === 'function') { + a[key] = bind(val, thisArg); + } else { + a[key] = val; + } + }); + return a; +} + +module.exports = { + isArray: isArray, + isArrayBuffer: isArrayBuffer, + isBuffer: isBuffer, + isFormData: isFormData, + isArrayBufferView: isArrayBufferView, + isString: isString, + isNumber: isNumber, + isObject: isObject, + isUndefined: isUndefined, + isDate: isDate, + isFile: isFile, + isBlob: isBlob, + isFunction: isFunction, + isStream: isStream, + isURLSearchParams: isURLSearchParams, + isStandardBrowserEnv: isStandardBrowserEnv, + forEach: forEach, + merge: merge, + extend: extend, + trim: trim +}; + + +/***/ }), +/* 458 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +module.exports = function bind(fn, thisArg) { + return function wrap() { + var args = new Array(arguments.length); + for (var i = 0; i < args.length; i++) { + args[i] = arguments[i]; + } + return fn.apply(thisArg, args); + }; +}; + + +/***/ }), +/* 459 */ +/***/ (function(module, exports) { + +/*! + * Determine if an object is a Buffer + * + * @author Feross Aboukhadijeh + * @license MIT + */ + +module.exports = function isBuffer (obj) { + return obj != null && obj.constructor != null && + typeof obj.constructor.isBuffer === 'function' && obj.constructor.isBuffer(obj) +} + + +/***/ }), +/* 460 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var defaults = __webpack_require__(461); +var utils = __webpack_require__(457); +var InterceptorManager = __webpack_require__(484); +var dispatchRequest = __webpack_require__(485); + +/** + * Create a new instance of Axios + * + * @param {Object} instanceConfig The default config for the instance + */ +function Axios(instanceConfig) { + this.defaults = instanceConfig; + this.interceptors = { + request: new InterceptorManager(), + response: new InterceptorManager() + }; +} + +/** + * Dispatch a request + * + * @param {Object} config The config specific for this request (merged with this.defaults) + */ +Axios.prototype.request = function request(config) { + /*eslint no-param-reassign:0*/ + // Allow for axios('example/url'[, config]) a la fetch API + if (typeof config === 'string') { + config = utils.merge({ + url: arguments[0] + }, arguments[1]); + } + + config = utils.merge(defaults, {method: 'get'}, this.defaults, config); + config.method = config.method.toLowerCase(); + + // Hook up interceptors middleware + var chain = [dispatchRequest, undefined]; + var promise = Promise.resolve(config); + + this.interceptors.request.forEach(function unshiftRequestInterceptors(interceptor) { + chain.unshift(interceptor.fulfilled, interceptor.rejected); + }); + + this.interceptors.response.forEach(function pushResponseInterceptors(interceptor) { + chain.push(interceptor.fulfilled, interceptor.rejected); + }); + + while (chain.length) { + promise = promise.then(chain.shift(), chain.shift()); + } + + return promise; +}; + +// Provide aliases for supported request methods +utils.forEach(['delete', 'get', 'head', 'options'], function forEachMethodNoData(method) { + /*eslint func-names:0*/ + Axios.prototype[method] = function(url, config) { + return this.request(utils.merge(config || {}, { + method: method, + url: url + })); + }; +}); + +utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) { + /*eslint func-names:0*/ + Axios.prototype[method] = function(url, data, config) { + return this.request(utils.merge(config || {}, { + method: method, + url: url, + data: data + })); + }; +}); + +module.exports = Axios; + + +/***/ }), +/* 461 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var utils = __webpack_require__(457); +var normalizeHeaderName = __webpack_require__(462); + +var DEFAULT_CONTENT_TYPE = { + 'Content-Type': 'application/x-www-form-urlencoded' +}; + +function setContentTypeIfUnset(headers, value) { + if (!utils.isUndefined(headers) && utils.isUndefined(headers['Content-Type'])) { + headers['Content-Type'] = value; + } +} + +function getDefaultAdapter() { + var adapter; + if (typeof XMLHttpRequest !== 'undefined') { + // For browsers use XHR adapter + adapter = __webpack_require__(463); + } else if (typeof process !== 'undefined') { + // For node use HTTP adapter + adapter = __webpack_require__(471); + } + return adapter; +} + +var defaults = { + adapter: getDefaultAdapter(), + + transformRequest: [function transformRequest(data, headers) { + normalizeHeaderName(headers, 'Content-Type'); + if (utils.isFormData(data) || + utils.isArrayBuffer(data) || + utils.isBuffer(data) || + utils.isStream(data) || + utils.isFile(data) || + utils.isBlob(data) + ) { + return data; + } + if (utils.isArrayBufferView(data)) { + return data.buffer; + } + if (utils.isURLSearchParams(data)) { + setContentTypeIfUnset(headers, 'application/x-www-form-urlencoded;charset=utf-8'); + return data.toString(); + } + if (utils.isObject(data)) { + setContentTypeIfUnset(headers, 'application/json;charset=utf-8'); + return JSON.stringify(data); + } + return data; + }], + + transformResponse: [function transformResponse(data) { + /*eslint no-param-reassign:0*/ + if (typeof data === 'string') { + try { + data = JSON.parse(data); + } catch (e) { /* Ignore */ } + } + return data; + }], + + /** + * A timeout in milliseconds to abort a request. If set to 0 (default) a + * timeout is not created. + */ + timeout: 0, + + xsrfCookieName: 'XSRF-TOKEN', + xsrfHeaderName: 'X-XSRF-TOKEN', + + maxContentLength: -1, + + validateStatus: function validateStatus(status) { + return status >= 200 && status < 300; + } +}; + +defaults.headers = { + common: { + 'Accept': 'application/json, text/plain, */*' + } +}; + +utils.forEach(['delete', 'get', 'head'], function forEachMethodNoData(method) { + defaults.headers[method] = {}; +}); + +utils.forEach(['post', 'put', 'patch'], function forEachMethodWithData(method) { + defaults.headers[method] = utils.merge(DEFAULT_CONTENT_TYPE); +}); + +module.exports = defaults; + + +/***/ }), +/* 462 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var utils = __webpack_require__(457); + +module.exports = function normalizeHeaderName(headers, normalizedName) { + utils.forEach(headers, function processHeader(value, name) { + if (name !== normalizedName && name.toUpperCase() === normalizedName.toUpperCase()) { + headers[normalizedName] = value; + delete headers[name]; + } + }); +}; + + +/***/ }), +/* 463 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var utils = __webpack_require__(457); +var settle = __webpack_require__(464); +var buildURL = __webpack_require__(467); +var parseHeaders = __webpack_require__(468); +var isURLSameOrigin = __webpack_require__(469); +var createError = __webpack_require__(465); + +module.exports = function xhrAdapter(config) { + return new Promise(function dispatchXhrRequest(resolve, reject) { + var requestData = config.data; + var requestHeaders = config.headers; + + if (utils.isFormData(requestData)) { + delete requestHeaders['Content-Type']; // Let the browser set it + } + + var request = new XMLHttpRequest(); + + // HTTP basic authentication + if (config.auth) { + var username = config.auth.username || ''; + var password = config.auth.password || ''; + requestHeaders.Authorization = 'Basic ' + btoa(username + ':' + password); + } + + request.open(config.method.toUpperCase(), buildURL(config.url, config.params, config.paramsSerializer), true); + + // Set the request timeout in MS + request.timeout = config.timeout; + + // Listen for ready state + request.onreadystatechange = function handleLoad() { + if (!request || request.readyState !== 4) { + return; + } + + // The request errored out and we didn't get a response, this will be + // handled by onerror instead + // With one exception: request that using file: protocol, most browsers + // will return status as 0 even though it's a successful request + if (request.status === 0 && !(request.responseURL && request.responseURL.indexOf('file:') === 0)) { + return; + } + + // Prepare the response + var responseHeaders = 'getAllResponseHeaders' in request ? parseHeaders(request.getAllResponseHeaders()) : null; + var responseData = !config.responseType || config.responseType === 'text' ? request.responseText : request.response; + var response = { + data: responseData, + status: request.status, + statusText: request.statusText, + headers: responseHeaders, + config: config, + request: request + }; + + settle(resolve, reject, response); + + // Clean up request + request = null; + }; + + // Handle low level network errors + request.onerror = function handleError() { + // Real errors are hidden from us by the browser + // onerror should only fire if it's a network error + reject(createError('Network Error', config, null, request)); + + // Clean up request + request = null; + }; + + // Handle timeout + request.ontimeout = function handleTimeout() { + reject(createError('timeout of ' + config.timeout + 'ms exceeded', config, 'ECONNABORTED', + request)); + + // Clean up request + request = null; + }; + + // Add xsrf header + // This is only done if running in a standard browser environment. + // Specifically not if we're in a web worker, or react-native. + if (utils.isStandardBrowserEnv()) { + var cookies = __webpack_require__(470); + + // Add xsrf header + var xsrfValue = (config.withCredentials || isURLSameOrigin(config.url)) && config.xsrfCookieName ? + cookies.read(config.xsrfCookieName) : + undefined; + + if (xsrfValue) { + requestHeaders[config.xsrfHeaderName] = xsrfValue; + } + } + + // Add headers to the request + if ('setRequestHeader' in request) { + utils.forEach(requestHeaders, function setRequestHeader(val, key) { + if (typeof requestData === 'undefined' && key.toLowerCase() === 'content-type') { + // Remove Content-Type if data is undefined + delete requestHeaders[key]; + } else { + // Otherwise add header to the request + request.setRequestHeader(key, val); + } + }); + } + + // Add withCredentials to request if needed + if (config.withCredentials) { + request.withCredentials = true; + } + + // Add responseType to request if needed + if (config.responseType) { + try { + request.responseType = config.responseType; + } catch (e) { + // Expected DOMException thrown by browsers not compatible XMLHttpRequest Level 2. + // But, this can be suppressed for 'json' type as it can be parsed by default 'transformResponse' function. + if (config.responseType !== 'json') { + throw e; + } + } + } + + // Handle progress if needed + if (typeof config.onDownloadProgress === 'function') { + request.addEventListener('progress', config.onDownloadProgress); + } + + // Not all browsers support upload events + if (typeof config.onUploadProgress === 'function' && request.upload) { + request.upload.addEventListener('progress', config.onUploadProgress); + } + + if (config.cancelToken) { + // Handle cancellation + config.cancelToken.promise.then(function onCanceled(cancel) { + if (!request) { + return; + } + + request.abort(); + reject(cancel); + // Clean up request + request = null; + }); + } + + if (requestData === undefined) { + requestData = null; + } + + // Send the request + request.send(requestData); + }); +}; + + +/***/ }), +/* 464 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var createError = __webpack_require__(465); + +/** + * Resolve or reject a Promise based on response status. + * + * @param {Function} resolve A function that resolves the promise. + * @param {Function} reject A function that rejects the promise. + * @param {object} response The response. + */ +module.exports = function settle(resolve, reject, response) { + var validateStatus = response.config.validateStatus; + // Note: status is not exposed by XDomainRequest + if (!response.status || !validateStatus || validateStatus(response.status)) { + resolve(response); + } else { + reject(createError( + 'Request failed with status code ' + response.status, + response.config, + null, + response.request, + response + )); + } +}; + + +/***/ }), +/* 465 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var enhanceError = __webpack_require__(466); + +/** + * Create an Error with the specified message, config, error code, request and response. + * + * @param {string} message The error message. + * @param {Object} config The config. + * @param {string} [code] The error code (for example, 'ECONNABORTED'). + * @param {Object} [request] The request. + * @param {Object} [response] The response. + * @returns {Error} The created error. + */ +module.exports = function createError(message, config, code, request, response) { + var error = new Error(message); + return enhanceError(error, config, code, request, response); +}; + + +/***/ }), +/* 466 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +/** + * Update an Error with the specified config, error code, and response. + * + * @param {Error} error The error to update. + * @param {Object} config The config. + * @param {string} [code] The error code (for example, 'ECONNABORTED'). + * @param {Object} [request] The request. + * @param {Object} [response] The response. + * @returns {Error} The error. + */ +module.exports = function enhanceError(error, config, code, request, response) { + error.config = config; + if (code) { + error.code = code; + } + error.request = request; + error.response = response; + return error; +}; + + +/***/ }), +/* 467 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var utils = __webpack_require__(457); + +function encode(val) { + return encodeURIComponent(val). + replace(/%40/gi, '@'). + replace(/%3A/gi, ':'). + replace(/%24/g, '$'). + replace(/%2C/gi, ','). + replace(/%20/g, '+'). + replace(/%5B/gi, '['). + replace(/%5D/gi, ']'); +} + +/** + * Build a URL by appending params to the end + * + * @param {string} url The base of the url (e.g., http://www.google.com) + * @param {object} [params] The params to be appended + * @returns {string} The formatted url + */ +module.exports = function buildURL(url, params, paramsSerializer) { + /*eslint no-param-reassign:0*/ + if (!params) { + return url; + } + + var serializedParams; + if (paramsSerializer) { + serializedParams = paramsSerializer(params); + } else if (utils.isURLSearchParams(params)) { + serializedParams = params.toString(); + } else { + var parts = []; + + utils.forEach(params, function serialize(val, key) { + if (val === null || typeof val === 'undefined') { + return; + } + + if (utils.isArray(val)) { + key = key + '[]'; + } else { + val = [val]; + } + + utils.forEach(val, function parseValue(v) { + if (utils.isDate(v)) { + v = v.toISOString(); + } else if (utils.isObject(v)) { + v = JSON.stringify(v); + } + parts.push(encode(key) + '=' + encode(v)); + }); + }); + + serializedParams = parts.join('&'); + } + + if (serializedParams) { + url += (url.indexOf('?') === -1 ? '?' : '&') + serializedParams; + } + + return url; +}; + + +/***/ }), +/* 468 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var utils = __webpack_require__(457); + +// Headers whose duplicates are ignored by node +// c.f. https://nodejs.org/api/http.html#http_message_headers +var ignoreDuplicateOf = [ + 'age', 'authorization', 'content-length', 'content-type', 'etag', + 'expires', 'from', 'host', 'if-modified-since', 'if-unmodified-since', + 'last-modified', 'location', 'max-forwards', 'proxy-authorization', + 'referer', 'retry-after', 'user-agent' +]; + +/** + * Parse headers into an object + * + * ``` + * Date: Wed, 27 Aug 2014 08:58:49 GMT + * Content-Type: application/json + * Connection: keep-alive + * Transfer-Encoding: chunked + * ``` + * + * @param {String} headers Headers needing to be parsed + * @returns {Object} Headers parsed into an object + */ +module.exports = function parseHeaders(headers) { + var parsed = {}; + var key; + var val; + var i; + + if (!headers) { return parsed; } + + utils.forEach(headers.split('\n'), function parser(line) { + i = line.indexOf(':'); + key = utils.trim(line.substr(0, i)).toLowerCase(); + val = utils.trim(line.substr(i + 1)); + + if (key) { + if (parsed[key] && ignoreDuplicateOf.indexOf(key) >= 0) { + return; + } + if (key === 'set-cookie') { + parsed[key] = (parsed[key] ? parsed[key] : []).concat([val]); + } else { + parsed[key] = parsed[key] ? parsed[key] + ', ' + val : val; + } + } + }); + + return parsed; +}; + + +/***/ }), +/* 469 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var utils = __webpack_require__(457); + +module.exports = ( + utils.isStandardBrowserEnv() ? + + // Standard browser envs have full support of the APIs needed to test + // whether the request URL is of the same origin as current location. + (function standardBrowserEnv() { + var msie = /(msie|trident)/i.test(navigator.userAgent); + var urlParsingNode = document.createElement('a'); + var originURL; + + /** + * Parse a URL to discover it's components + * + * @param {String} url The URL to be parsed + * @returns {Object} + */ + function resolveURL(url) { + var href = url; + + if (msie) { + // IE needs attribute set twice to normalize properties + urlParsingNode.setAttribute('href', href); + href = urlParsingNode.href; + } + + urlParsingNode.setAttribute('href', href); + + // urlParsingNode provides the UrlUtils interface - http://url.spec.whatwg.org/#urlutils + return { + href: urlParsingNode.href, + protocol: urlParsingNode.protocol ? urlParsingNode.protocol.replace(/:$/, '') : '', + host: urlParsingNode.host, + search: urlParsingNode.search ? urlParsingNode.search.replace(/^\?/, '') : '', + hash: urlParsingNode.hash ? urlParsingNode.hash.replace(/^#/, '') : '', + hostname: urlParsingNode.hostname, + port: urlParsingNode.port, + pathname: (urlParsingNode.pathname.charAt(0) === '/') ? + urlParsingNode.pathname : + '/' + urlParsingNode.pathname + }; + } + + originURL = resolveURL(window.location.href); + + /** + * Determine if a URL shares the same origin as the current location + * + * @param {String} requestURL The URL to test + * @returns {boolean} True if URL shares the same origin, otherwise false + */ + return function isURLSameOrigin(requestURL) { + var parsed = (utils.isString(requestURL)) ? resolveURL(requestURL) : requestURL; + return (parsed.protocol === originURL.protocol && + parsed.host === originURL.host); + }; + })() : + + // Non standard browser envs (web workers, react-native) lack needed support. + (function nonStandardBrowserEnv() { + return function isURLSameOrigin() { + return true; + }; + })() +); + + +/***/ }), +/* 470 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var utils = __webpack_require__(457); + +module.exports = ( + utils.isStandardBrowserEnv() ? + + // Standard browser envs support document.cookie + (function standardBrowserEnv() { + return { + write: function write(name, value, expires, path, domain, secure) { + var cookie = []; + cookie.push(name + '=' + encodeURIComponent(value)); + + if (utils.isNumber(expires)) { + cookie.push('expires=' + new Date(expires).toGMTString()); + } + + if (utils.isString(path)) { + cookie.push('path=' + path); + } + + if (utils.isString(domain)) { + cookie.push('domain=' + domain); + } + + if (secure === true) { + cookie.push('secure'); + } + + document.cookie = cookie.join('; '); + }, + + read: function read(name) { + var match = document.cookie.match(new RegExp('(^|;\\s*)(' + name + ')=([^;]*)')); + return (match ? decodeURIComponent(match[3]) : null); + }, + + remove: function remove(name) { + this.write(name, '', Date.now() - 86400000); + } + }; + })() : + + // Non standard browser env (web workers, react-native) lack needed support. + (function nonStandardBrowserEnv() { + return { + write: function write() {}, + read: function read() { return null; }, + remove: function remove() {} + }; + })() +); + + +/***/ }), +/* 471 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var utils = __webpack_require__(457); +var settle = __webpack_require__(464); +var buildURL = __webpack_require__(467); +var http = __webpack_require__(472); +var https = __webpack_require__(473); +var httpFollow = __webpack_require__(474).http; +var httpsFollow = __webpack_require__(474).https; +var url = __webpack_require__(454); +var zlib = __webpack_require__(482); +var pkg = __webpack_require__(483); +var createError = __webpack_require__(465); +var enhanceError = __webpack_require__(466); + +/*eslint consistent-return:0*/ +module.exports = function httpAdapter(config) { + return new Promise(function dispatchHttpRequest(resolve, reject) { + var data = config.data; + var headers = config.headers; + var timer; + + // Set User-Agent (required by some servers) + // Only set header if it hasn't been set in config + // See https://github.com/axios/axios/issues/69 + if (!headers['User-Agent'] && !headers['user-agent']) { + headers['User-Agent'] = 'axios/' + pkg.version; + } + + if (data && !utils.isStream(data)) { + if (Buffer.isBuffer(data)) { + // Nothing to do... + } else if (utils.isArrayBuffer(data)) { + data = new Buffer(new Uint8Array(data)); + } else if (utils.isString(data)) { + data = new Buffer(data, 'utf-8'); + } else { + return reject(createError( + 'Data after transformation must be a string, an ArrayBuffer, a Buffer, or a Stream', + config + )); + } + + // Add Content-Length header if data exists + headers['Content-Length'] = data.length; + } + + // HTTP basic authentication + var auth = undefined; + if (config.auth) { + var username = config.auth.username || ''; + var password = config.auth.password || ''; + auth = username + ':' + password; + } + + // Parse url + var parsed = url.parse(config.url); + var protocol = parsed.protocol || 'http:'; + + if (!auth && parsed.auth) { + var urlAuth = parsed.auth.split(':'); + var urlUsername = urlAuth[0] || ''; + var urlPassword = urlAuth[1] || ''; + auth = urlUsername + ':' + urlPassword; + } + + if (auth) { + delete headers.Authorization; + } + + var isHttps = protocol === 'https:'; + var agent = isHttps ? config.httpsAgent : config.httpAgent; + + var options = { + path: buildURL(parsed.path, config.params, config.paramsSerializer).replace(/^\?/, ''), + method: config.method, + headers: headers, + agent: agent, + auth: auth + }; + + if (config.socketPath) { + options.socketPath = config.socketPath; + } else { + options.hostname = parsed.hostname; + options.port = parsed.port; + } + + var proxy = config.proxy; + if (!proxy && proxy !== false) { + var proxyEnv = protocol.slice(0, -1) + '_proxy'; + var proxyUrl = process.env[proxyEnv] || process.env[proxyEnv.toUpperCase()]; + if (proxyUrl) { + var parsedProxyUrl = url.parse(proxyUrl); + proxy = { + host: parsedProxyUrl.hostname, + port: parsedProxyUrl.port + }; + + if (parsedProxyUrl.auth) { + var proxyUrlAuth = parsedProxyUrl.auth.split(':'); + proxy.auth = { + username: proxyUrlAuth[0], + password: proxyUrlAuth[1] + }; + } + } + } + + if (proxy) { + options.hostname = proxy.host; + options.host = proxy.host; + options.headers.host = parsed.hostname + (parsed.port ? ':' + parsed.port : ''); + options.port = proxy.port; + options.path = protocol + '//' + parsed.hostname + (parsed.port ? ':' + parsed.port : '') + options.path; + + // Basic proxy authorization + if (proxy.auth) { + var base64 = new Buffer(proxy.auth.username + ':' + proxy.auth.password, 'utf8').toString('base64'); + options.headers['Proxy-Authorization'] = 'Basic ' + base64; + } + } + + var transport; + if (config.transport) { + transport = config.transport; + } else if (config.maxRedirects === 0) { + transport = isHttps ? https : http; + } else { + if (config.maxRedirects) { + options.maxRedirects = config.maxRedirects; + } + transport = isHttps ? httpsFollow : httpFollow; + } + + if (config.maxContentLength && config.maxContentLength > -1) { + options.maxBodyLength = config.maxContentLength; + } + + // Create the request + var req = transport.request(options, function handleResponse(res) { + if (req.aborted) return; + + // Response has been received so kill timer that handles request timeout + clearTimeout(timer); + timer = null; + + // uncompress the response body transparently if required + var stream = res; + switch (res.headers['content-encoding']) { + /*eslint default-case:0*/ + case 'gzip': + case 'compress': + case 'deflate': + // add the unzipper to the body stream processing pipeline + stream = stream.pipe(zlib.createUnzip()); + + // remove the content-encoding in order to not confuse downstream operations + delete res.headers['content-encoding']; + break; + } + + // return the last request in case of redirects + var lastRequest = res.req || req; + + var response = { + status: res.statusCode, + statusText: res.statusMessage, + headers: res.headers, + config: config, + request: lastRequest + }; + + if (config.responseType === 'stream') { + response.data = stream; + settle(resolve, reject, response); + } else { + var responseBuffer = []; + stream.on('data', function handleStreamData(chunk) { + responseBuffer.push(chunk); + + // make sure the content length is not over the maxContentLength if specified + if (config.maxContentLength > -1 && Buffer.concat(responseBuffer).length > config.maxContentLength) { + stream.destroy(); + reject(createError('maxContentLength size of ' + config.maxContentLength + ' exceeded', + config, null, lastRequest)); + } + }); + + stream.on('error', function handleStreamError(err) { + if (req.aborted) return; + reject(enhanceError(err, config, null, lastRequest)); + }); + + stream.on('end', function handleStreamEnd() { + var responseData = Buffer.concat(responseBuffer); + if (config.responseType !== 'arraybuffer') { + responseData = responseData.toString('utf8'); + } + + response.data = responseData; + settle(resolve, reject, response); + }); + } + }); + + // Handle errors + req.on('error', function handleRequestError(err) { + if (req.aborted) return; + reject(enhanceError(err, config, null, req)); + }); + + // Handle request timeout + if (config.timeout && !timer) { + timer = setTimeout(function handleRequestTimeout() { + req.abort(); + reject(createError('timeout of ' + config.timeout + 'ms exceeded', config, 'ECONNABORTED', req)); + }, config.timeout); + } + + if (config.cancelToken) { + // Handle cancellation + config.cancelToken.promise.then(function onCanceled(cancel) { + if (req.aborted) return; + + req.abort(); + reject(cancel); + }); + } + + // Send the request + if (utils.isStream(data)) { + data.pipe(req); + } else { + req.end(data); + } + }); +}; + + +/***/ }), +/* 472 */ +/***/ (function(module, exports) { + +module.exports = require("http"); + +/***/ }), +/* 473 */ +/***/ (function(module, exports) { + +module.exports = require("https"); + +/***/ }), +/* 474 */ +/***/ (function(module, exports, __webpack_require__) { + +var url = __webpack_require__(454); +var http = __webpack_require__(472); +var https = __webpack_require__(473); +var assert = __webpack_require__(30); +var Writable = __webpack_require__(27).Writable; +var debug = __webpack_require__(475)("follow-redirects"); + +// RFC7231§4.2.1: Of the request methods defined by this specification, +// the GET, HEAD, OPTIONS, and TRACE methods are defined to be safe. +var SAFE_METHODS = { GET: true, HEAD: true, OPTIONS: true, TRACE: true }; + +// Create handlers that pass events from native requests +var eventHandlers = Object.create(null); +["abort", "aborted", "error", "socket", "timeout"].forEach(function (event) { + eventHandlers[event] = function (arg) { + this._redirectable.emit(event, arg); + }; +}); + +// An HTTP(S) request that can be redirected +function RedirectableRequest(options, responseCallback) { + // Initialize the request + Writable.call(this); + options.headers = options.headers || {}; + this._options = options; + this._redirectCount = 0; + this._redirects = []; + this._requestBodyLength = 0; + this._requestBodyBuffers = []; + + // Since http.request treats host as an alias of hostname, + // but the url module interprets host as hostname plus port, + // eliminate the host property to avoid confusion. + if (options.host) { + // Use hostname if set, because it has precedence + if (!options.hostname) { + options.hostname = options.host; + } + delete options.host; + } + + // Attach a callback if passed + if (responseCallback) { + this.on("response", responseCallback); + } + + // React to responses of native requests + var self = this; + this._onNativeResponse = function (response) { + self._processResponse(response); + }; + + // Complete the URL object when necessary + if (!options.pathname && options.path) { + var searchPos = options.path.indexOf("?"); + if (searchPos < 0) { + options.pathname = options.path; + } + else { + options.pathname = options.path.substring(0, searchPos); + options.search = options.path.substring(searchPos); + } + } + + // Perform the first request + this._performRequest(); +} +RedirectableRequest.prototype = Object.create(Writable.prototype); + +// Writes buffered data to the current native request +RedirectableRequest.prototype.write = function (data, encoding, callback) { + // Validate input and shift parameters if necessary + if (!(typeof data === "string" || typeof data === "object" && ("length" in data))) { + throw new Error("data should be a string, Buffer or Uint8Array"); + } + if (typeof encoding === "function") { + callback = encoding; + encoding = null; + } + + // Ignore empty buffers, since writing them doesn't invoke the callback + // https://github.com/nodejs/node/issues/22066 + if (data.length === 0) { + if (callback) { + callback(); + } + return; + } + // Only write when we don't exceed the maximum body length + if (this._requestBodyLength + data.length <= this._options.maxBodyLength) { + this._requestBodyLength += data.length; + this._requestBodyBuffers.push({ data: data, encoding: encoding }); + this._currentRequest.write(data, encoding, callback); + } + // Error when we exceed the maximum body length + else { + this.emit("error", new Error("Request body larger than maxBodyLength limit")); + this.abort(); + } +}; + +// Ends the current native request +RedirectableRequest.prototype.end = function (data, encoding, callback) { + // Shift parameters if necessary + if (typeof data === "function") { + callback = data; + data = encoding = null; + } + else if (typeof encoding === "function") { + callback = encoding; + encoding = null; + } + + // Write data and end + var currentRequest = this._currentRequest; + this.write(data || "", encoding, function () { + currentRequest.end(null, null, callback); + }); +}; + +// Sets a header value on the current native request +RedirectableRequest.prototype.setHeader = function (name, value) { + this._options.headers[name] = value; + this._currentRequest.setHeader(name, value); +}; + +// Clears a header value on the current native request +RedirectableRequest.prototype.removeHeader = function (name) { + delete this._options.headers[name]; + this._currentRequest.removeHeader(name); +}; + +// Proxy all other public ClientRequest methods +[ + "abort", "flushHeaders", "getHeader", + "setNoDelay", "setSocketKeepAlive", "setTimeout", +].forEach(function (method) { + RedirectableRequest.prototype[method] = function (a, b) { + return this._currentRequest[method](a, b); + }; +}); + +// Proxy all public ClientRequest properties +["aborted", "connection", "socket"].forEach(function (property) { + Object.defineProperty(RedirectableRequest.prototype, property, { + get: function () { return this._currentRequest[property]; }, + }); +}); + +// Executes the next native request (initial or redirect) +RedirectableRequest.prototype._performRequest = function () { + // Load the native protocol + var protocol = this._options.protocol; + var nativeProtocol = this._options.nativeProtocols[protocol]; + if (!nativeProtocol) { + this.emit("error", new Error("Unsupported protocol " + protocol)); + return; + } + + // If specified, use the agent corresponding to the protocol + // (HTTP and HTTPS use different types of agents) + if (this._options.agents) { + var scheme = protocol.substr(0, protocol.length - 1); + this._options.agent = this._options.agents[scheme]; + } + + // Create the native request + var request = this._currentRequest = + nativeProtocol.request(this._options, this._onNativeResponse); + this._currentUrl = url.format(this._options); + + // Set up event handlers + request._redirectable = this; + for (var event in eventHandlers) { + /* istanbul ignore else */ + if (event) { + request.on(event, eventHandlers[event]); + } + } + + // End a redirected request + // (The first request must be ended explicitly with RedirectableRequest#end) + if (this._isRedirect) { + // Write the request entity and end. + var i = 0; + var buffers = this._requestBodyBuffers; + (function writeNext() { + if (i < buffers.length) { + var buffer = buffers[i++]; + request.write(buffer.data, buffer.encoding, writeNext); + } + else { + request.end(); + } + }()); + } +}; + +// Processes a response from the current native request +RedirectableRequest.prototype._processResponse = function (response) { + // Store the redirected response + if (this._options.trackRedirects) { + this._redirects.push({ + url: this._currentUrl, + headers: response.headers, + statusCode: response.statusCode, + }); + } + + // RFC7231§6.4: The 3xx (Redirection) class of status code indicates + // that further action needs to be taken by the user agent in order to + // fulfill the request. If a Location header field is provided, + // the user agent MAY automatically redirect its request to the URI + // referenced by the Location field value, + // even if the specific status code is not understood. + var location = response.headers.location; + if (location && this._options.followRedirects !== false && + response.statusCode >= 300 && response.statusCode < 400) { + // RFC7231§6.4: A client SHOULD detect and intervene + // in cyclical redirections (i.e., "infinite" redirection loops). + if (++this._redirectCount > this._options.maxRedirects) { + this.emit("error", new Error("Max redirects exceeded.")); + return; + } + + // RFC7231§6.4: Automatic redirection needs to done with + // care for methods not known to be safe […], + // since the user might not wish to redirect an unsafe request. + // RFC7231§6.4.7: The 307 (Temporary Redirect) status code indicates + // that the target resource resides temporarily under a different URI + // and the user agent MUST NOT change the request method + // if it performs an automatic redirection to that URI. + var header; + var headers = this._options.headers; + if (response.statusCode !== 307 && !(this._options.method in SAFE_METHODS)) { + this._options.method = "GET"; + // Drop a possible entity and headers related to it + this._requestBodyBuffers = []; + for (header in headers) { + if (/^content-/i.test(header)) { + delete headers[header]; + } + } + } + + // Drop the Host header, as the redirect might lead to a different host + if (!this._isRedirect) { + for (header in headers) { + if (/^host$/i.test(header)) { + delete headers[header]; + } + } + } + + // Perform the redirected request + var redirectUrl = url.resolve(this._currentUrl, location); + debug("redirecting to", redirectUrl); + Object.assign(this._options, url.parse(redirectUrl)); + this._isRedirect = true; + this._performRequest(); + + // Discard the remainder of the response to avoid waiting for data + response.destroy(); + } + else { + // The response is not a redirect; return it as-is + response.responseUrl = this._currentUrl; + response.redirects = this._redirects; + this.emit("response", response); + + // Clean up + this._requestBodyBuffers = []; + } +}; + +// Wraps the key/value object of protocols with redirect functionality +function wrap(protocols) { + // Default settings + var exports = { + maxRedirects: 21, + maxBodyLength: 10 * 1024 * 1024, + }; + + // Wrap each protocol + var nativeProtocols = {}; + Object.keys(protocols).forEach(function (scheme) { + var protocol = scheme + ":"; + var nativeProtocol = nativeProtocols[protocol] = protocols[scheme]; + var wrappedProtocol = exports[scheme] = Object.create(nativeProtocol); + + // Executes a request, following redirects + wrappedProtocol.request = function (options, callback) { + if (typeof options === "string") { + options = url.parse(options); + options.maxRedirects = exports.maxRedirects; + } + else { + options = Object.assign({ + protocol: protocol, + maxRedirects: exports.maxRedirects, + maxBodyLength: exports.maxBodyLength, + }, options); + } + options.nativeProtocols = nativeProtocols; + assert.equal(options.protocol, protocol, "protocol mismatch"); + debug("options", options); + return new RedirectableRequest(options, callback); + }; + + // Executes a GET request, following redirects + wrappedProtocol.get = function (options, callback) { + var request = wrappedProtocol.request(options, callback); + request.end(); + return request; + }; + }); + return exports; +} + +// Exports +module.exports = wrap({ http: http, https: https }); +module.exports.wrap = wrap; + + +/***/ }), +/* 475 */ +/***/ (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__(476); +} else { + module.exports = __webpack_require__(479); +} + + +/***/ }), +/* 476 */ +/***/ (function(module, exports, __webpack_require__) { + +/** + * This is the web browser implementation of `debug()`. + * + * Expose `debug()` as the module. + */ + +exports = module.exports = __webpack_require__(477); +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 = [ + '#0000CC', '#0000FF', '#0033CC', '#0033FF', '#0066CC', '#0066FF', '#0099CC', + '#0099FF', '#00CC00', '#00CC33', '#00CC66', '#00CC99', '#00CCCC', '#00CCFF', + '#3300CC', '#3300FF', '#3333CC', '#3333FF', '#3366CC', '#3366FF', '#3399CC', + '#3399FF', '#33CC00', '#33CC33', '#33CC66', '#33CC99', '#33CCCC', '#33CCFF', + '#6600CC', '#6600FF', '#6633CC', '#6633FF', '#66CC00', '#66CC33', '#9900CC', + '#9900FF', '#9933CC', '#9933FF', '#99CC00', '#99CC33', '#CC0000', '#CC0033', + '#CC0066', '#CC0099', '#CC00CC', '#CC00FF', '#CC3300', '#CC3333', '#CC3366', + '#CC3399', '#CC33CC', '#CC33FF', '#CC6600', '#CC6633', '#CC9900', '#CC9933', + '#CCCC00', '#CCCC33', '#FF0000', '#FF0033', '#FF0066', '#FF0099', '#FF00CC', + '#FF00FF', '#FF3300', '#FF3333', '#FF3366', '#FF3399', '#FF33CC', '#FF33FF', + '#FF6600', '#FF6633', '#FF9900', '#FF9933', '#FFCC00', '#FFCC33' +]; + +/** + * 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; + } + + // Internet Explorer and Edge do not support colors. + if (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/(edge|trident)\/(\d+)/)) { + return false; + } + + // 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) {} +} + + +/***/ }), +/* 477 */ +/***/ (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__(478); + +/** + * Active `debug` instances. + */ +exports.instances = []; + +/** + * 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 = {}; + +/** + * 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) { + + var prevTime; + + 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); + debug.destroy = destroy; + + // env-specific initialization logic for debug instances + if ('function' === typeof exports.init) { + exports.init(debug); + } + + exports.instances.push(debug); + + return debug; +} + +function destroy () { + var index = exports.instances.indexOf(this); + if (index !== -1) { + exports.instances.splice(index, 1); + return true; + } else { + return false; + } +} + +/** + * 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 i; + var split = (typeof namespaces === 'string' ? namespaces : '').split(/[\s,]+/); + var len = split.length; + + for (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 + '$')); + } + } + + for (i = 0; i < exports.instances.length; i++) { + var instance = exports.instances[i]; + instance.enabled = exports.enabled(instance.namespace); + } +} + +/** + * 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) { + if (name[name.length - 1] === '*') { + return true; + } + 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; +} + + +/***/ }), +/* 478 */ +/***/ (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'; +} + + +/***/ }), +/* 479 */ +/***/ (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__(477); +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 ]; + +try { + var supportsColor = __webpack_require__(481); + if (supportsColor && supportsColor.level >= 2) { + exports.colors = [ + 20, 21, 26, 27, 32, 33, 38, 39, 40, 41, 42, 43, 44, 45, 56, 57, 62, 63, 68, + 69, 74, 75, 76, 77, 78, 79, 80, 81, 92, 93, 98, 99, 112, 113, 128, 129, 134, + 135, 148, 149, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, + 172, 173, 178, 179, 184, 185, 196, 197, 198, 199, 200, 201, 202, 203, 204, + 205, 206, 207, 208, 209, 214, 215, 220, 221 + ]; + } +} catch (err) { + // swallow - we only care if `supports-color` is available; it doesn't have to be. +} + +/** + * 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; +}, {}); + +/** + * Is stdout a TTY? Colored output is enabled when `true`. + */ + +function useColors() { + return 'colors' in exports.inspectOpts + ? Boolean(exports.inspectOpts.colors) + : tty.isatty(process.stderr.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 colorCode = '\u001b[3' + (c < 8 ? c : '8;5;' + c); + var prefix = ' ' + colorCode + ';1m' + name + ' ' + '\u001b[0m'; + + args[0] = prefix + args[0].split('\n').join('\n' + prefix); + args.push(colorCode + 'm+' + exports.humanize(this.diff) + '\u001b[0m'); + } else { + args[0] = getDate() + name + ' ' + args[0]; + } +} + +function getDate() { + if (exports.inspectOpts.hideDate) { + return ''; + } else { + return new Date().toISOString() + ' '; + } +} + +/** + * Invokes `util.format()` with the specified arguments and writes to stderr. + */ + +function log() { + return process.stderr.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; +} + +/** + * 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()); + + +/***/ }), +/* 480 */ +/***/ (function(module, exports) { + +module.exports = require("tty"); + +/***/ }), +/* 481 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +const os = __webpack_require__(11); +const hasFlag = __webpack_require__(12); + +const {env} = process; + +let forceColor; +if (hasFlag('no-color') || + hasFlag('no-colors') || + hasFlag('color=false') || + hasFlag('color=never')) { + forceColor = 0; +} else if (hasFlag('color') || + hasFlag('colors') || + hasFlag('color=true') || + hasFlag('color=always')) { + forceColor = 1; +} +if ('FORCE_COLOR' in env) { + if (env.FORCE_COLOR === true || env.FORCE_COLOR === 'true') { + forceColor = 1; + } else if (env.FORCE_COLOR === false || env.FORCE_COLOR === 'false') { + forceColor = 0; + } else { + forceColor = env.FORCE_COLOR.length === 0 ? 1 : Math.min(parseInt(env.FORCE_COLOR, 10), 3); + } +} + +function translateLevel(level) { + if (level === 0) { + return false; + } + + return { + level, + hasBasic: true, + has256: level >= 2, + has16m: level >= 3 + }; +} + +function supportsColor(stream) { + if (forceColor === 0) { + return 0; + } + + if (hasFlag('color=16m') || + hasFlag('color=full') || + hasFlag('color=truecolor')) { + return 3; + } + + if (hasFlag('color=256')) { + return 2; + } + + if (stream && !stream.isTTY && forceColor === undefined) { + return 0; + } + + const min = forceColor || 0; + + if (env.TERM === 'dumb') { + return min; + } + + if (process.platform === 'win32') { + // Node.js 7.5.0 is the first version of Node.js to include a patch to + // libuv that enables 256 color output on Windows. Anything earlier and it + // won't work. However, here we target Node.js 8 at minimum as it is an LTS + // release, and Node.js 7 is not. Windows 10 build 10586 is the first Windows + // release that supports 256 colors. Windows 10 build 14931 is the first release + // that supports 16m/TrueColor. + const osRelease = os.release().split('.'); + if ( + Number(process.versions.node.split('.')[0]) >= 8 && + Number(osRelease[0]) >= 10 && + Number(osRelease[2]) >= 10586 + ) { + return Number(osRelease[2]) >= 14931 ? 3 : 2; + } + + return 1; + } + + if ('CI' in env) { + if (['TRAVIS', 'CIRCLECI', 'APPVEYOR', 'GITLAB_CI'].some(sign => sign in env) || env.CI_NAME === 'codeship') { + return 1; + } + + return min; + } + + if ('TEAMCITY_VERSION' in env) { + return /^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(env.TEAMCITY_VERSION) ? 1 : 0; + } + + if (env.COLORTERM === 'truecolor') { + return 3; + } + + if ('TERM_PROGRAM' in env) { + const version = parseInt((env.TERM_PROGRAM_VERSION || '').split('.')[0], 10); + + switch (env.TERM_PROGRAM) { + case 'iTerm.app': + return version >= 3 ? 3 : 2; + case 'Apple_Terminal': + return 2; + // No default + } + } + + if (/-256(color)?$/i.test(env.TERM)) { + return 2; + } + + if (/^screen|^xterm|^vt100|^vt220|^rxvt|color|ansi|cygwin|linux/i.test(env.TERM)) { + return 1; + } + + if ('COLORTERM' in env) { + return 1; + } + + return min; +} + +function getSupportLevel(stream) { + const level = supportsColor(stream); + return translateLevel(level); +} + +module.exports = { + supportsColor: getSupportLevel, + stdout: getSupportLevel(process.stdout), + stderr: getSupportLevel(process.stderr) +}; + + +/***/ }), +/* 482 */ +/***/ (function(module, exports) { + +module.exports = require("zlib"); + +/***/ }), +/* 483 */ +/***/ (function(module) { + +module.exports = JSON.parse("{\"name\":\"axios\",\"version\":\"0.18.1\",\"description\":\"Promise based HTTP client for the browser and node.js\",\"main\":\"index.js\",\"scripts\":{\"test\":\"grunt test && bundlesize\",\"start\":\"node ./sandbox/server.js\",\"build\":\"NODE_ENV=production grunt build\",\"preversion\":\"npm test\",\"version\":\"npm run build && grunt version && git add -A dist && git add CHANGELOG.md bower.json package.json\",\"postversion\":\"git push && git push --tags\",\"examples\":\"node ./examples/server.js\",\"coveralls\":\"cat coverage/lcov.info | ./node_modules/coveralls/bin/coveralls.js\"},\"repository\":{\"type\":\"git\",\"url\":\"https://github.com/axios/axios.git\"},\"keywords\":[\"xhr\",\"http\",\"ajax\",\"promise\",\"node\"],\"author\":\"Matt Zabriskie\",\"license\":\"MIT\",\"bugs\":{\"url\":\"https://github.com/axios/axios/issues\"},\"homepage\":\"https://github.com/axios/axios\",\"devDependencies\":{\"bundlesize\":\"^0.5.7\",\"coveralls\":\"^2.11.9\",\"es6-promise\":\"^4.0.5\",\"grunt\":\"^1.0.1\",\"grunt-banner\":\"^0.6.0\",\"grunt-cli\":\"^1.2.0\",\"grunt-contrib-clean\":\"^1.0.0\",\"grunt-contrib-nodeunit\":\"^1.0.0\",\"grunt-contrib-watch\":\"^1.0.0\",\"grunt-eslint\":\"^19.0.0\",\"grunt-karma\":\"^2.0.0\",\"grunt-ts\":\"^6.0.0-beta.3\",\"grunt-webpack\":\"^1.0.18\",\"istanbul-instrumenter-loader\":\"^1.0.0\",\"jasmine-core\":\"^2.4.1\",\"karma\":\"^1.3.0\",\"karma-chrome-launcher\":\"^2.0.0\",\"karma-coverage\":\"^1.0.0\",\"karma-firefox-launcher\":\"^1.0.0\",\"karma-jasmine\":\"^1.0.2\",\"karma-jasmine-ajax\":\"^0.1.13\",\"karma-opera-launcher\":\"^1.0.0\",\"karma-safari-launcher\":\"^1.0.0\",\"karma-sauce-launcher\":\"^1.1.0\",\"karma-sinon\":\"^1.0.5\",\"karma-sourcemap-loader\":\"^0.3.7\",\"karma-webpack\":\"^1.7.0\",\"load-grunt-tasks\":\"^3.5.2\",\"minimist\":\"^1.2.0\",\"sinon\":\"^1.17.4\",\"webpack\":\"^1.13.1\",\"webpack-dev-server\":\"^1.14.1\",\"url-search-params\":\"^0.6.1\",\"typescript\":\"^2.0.3\"},\"browser\":{\"./lib/adapters/http.js\":\"./lib/adapters/xhr.js\"},\"typings\":\"./index.d.ts\",\"dependencies\":{\"follow-redirects\":\"1.5.10\",\"is-buffer\":\"^2.0.2\"},\"bundlesize\":[{\"path\":\"./dist/axios.min.js\",\"threshold\":\"5kB\"}]}"); + +/***/ }), +/* 484 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var utils = __webpack_require__(457); + +function InterceptorManager() { + this.handlers = []; +} + +/** + * Add a new interceptor to the stack + * + * @param {Function} fulfilled The function to handle `then` for a `Promise` + * @param {Function} rejected The function to handle `reject` for a `Promise` + * + * @return {Number} An ID used to remove interceptor later + */ +InterceptorManager.prototype.use = function use(fulfilled, rejected) { + this.handlers.push({ + fulfilled: fulfilled, + rejected: rejected + }); + return this.handlers.length - 1; +}; + +/** + * Remove an interceptor from the stack + * + * @param {Number} id The ID that was returned by `use` + */ +InterceptorManager.prototype.eject = function eject(id) { + if (this.handlers[id]) { + this.handlers[id] = null; + } +}; + +/** + * Iterate over all the registered interceptors + * + * This method is particularly useful for skipping over any + * interceptors that may have become `null` calling `eject`. + * + * @param {Function} fn The function to call for each interceptor + */ +InterceptorManager.prototype.forEach = function forEach(fn) { + utils.forEach(this.handlers, function forEachHandler(h) { + if (h !== null) { + fn(h); + } + }); +}; + +module.exports = InterceptorManager; + + +/***/ }), +/* 485 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var utils = __webpack_require__(457); +var transformData = __webpack_require__(486); +var isCancel = __webpack_require__(487); +var defaults = __webpack_require__(461); +var isAbsoluteURL = __webpack_require__(488); +var combineURLs = __webpack_require__(489); + +/** + * Throws a `Cancel` if cancellation has been requested. + */ +function throwIfCancellationRequested(config) { + if (config.cancelToken) { + config.cancelToken.throwIfRequested(); + } +} + +/** + * Dispatch a request to the server using the configured adapter. + * + * @param {object} config The config that is to be used for the request + * @returns {Promise} The Promise to be fulfilled + */ +module.exports = function dispatchRequest(config) { + throwIfCancellationRequested(config); + + // Support baseURL config + if (config.baseURL && !isAbsoluteURL(config.url)) { + config.url = combineURLs(config.baseURL, config.url); + } + + // Ensure headers exist + config.headers = config.headers || {}; + + // Transform request data + config.data = transformData( + config.data, + config.headers, + config.transformRequest + ); + + // Flatten headers + config.headers = utils.merge( + config.headers.common || {}, + config.headers[config.method] || {}, + config.headers || {} + ); + + utils.forEach( + ['delete', 'get', 'head', 'post', 'put', 'patch', 'common'], + function cleanHeaderConfig(method) { + delete config.headers[method]; + } + ); + + var adapter = config.adapter || defaults.adapter; + + return adapter(config).then(function onAdapterResolution(response) { + throwIfCancellationRequested(config); + + // Transform response data + response.data = transformData( + response.data, + response.headers, + config.transformResponse + ); + + return response; + }, function onAdapterRejection(reason) { + if (!isCancel(reason)) { + throwIfCancellationRequested(config); + + // Transform response data + if (reason && reason.response) { + reason.response.data = transformData( + reason.response.data, + reason.response.headers, + config.transformResponse + ); + } + } + + return Promise.reject(reason); + }); +}; + + +/***/ }), +/* 486 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var utils = __webpack_require__(457); + +/** + * Transform the data for a request or a response + * + * @param {Object|String} data The data to be transformed + * @param {Array} headers The headers for the request or response + * @param {Array|Function} fns A single function or Array of functions + * @returns {*} The resulting transformed data + */ +module.exports = function transformData(data, headers, fns) { + /*eslint no-param-reassign:0*/ + utils.forEach(fns, function transform(fn) { + data = fn(data, headers); + }); + + return data; +}; + + +/***/ }), +/* 487 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +module.exports = function isCancel(value) { + return !!(value && value.__CANCEL__); +}; + + +/***/ }), +/* 488 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +/** + * Determines whether the specified URL is absolute + * + * @param {string} url The URL to test + * @returns {boolean} True if the specified URL is absolute, otherwise false + */ +module.exports = function isAbsoluteURL(url) { + // A URL is considered absolute if it begins with "://" or "//" (protocol-relative URL). + // RFC 3986 defines scheme name as a sequence of characters beginning with a letter and followed + // by any combination of letters, digits, plus, period, or hyphen. + return /^([a-z][a-z\d\+\-\.]*:)?\/\//i.test(url); +}; + + +/***/ }), +/* 489 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +/** + * Creates a new URL by combining the specified URLs + * + * @param {string} baseURL The base URL + * @param {string} relativeURL The relative URL + * @returns {string} The combined URL + */ +module.exports = function combineURLs(baseURL, relativeURL) { + return relativeURL + ? baseURL.replace(/\/+$/, '') + '/' + relativeURL.replace(/^\/+/, '') + : baseURL; +}; + + +/***/ }), +/* 490 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +/** + * A `Cancel` is an object that is thrown when an operation is canceled. + * + * @class + * @param {string=} message The message. + */ +function Cancel(message) { + this.message = message; +} + +Cancel.prototype.toString = function toString() { + return 'Cancel' + (this.message ? ': ' + this.message : ''); +}; + +Cancel.prototype.__CANCEL__ = true; + +module.exports = Cancel; + + +/***/ }), +/* 491 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var Cancel = __webpack_require__(490); + +/** + * A `CancelToken` is an object that can be used to request cancellation of an operation. + * + * @class + * @param {Function} executor The executor function. + */ +function CancelToken(executor) { + if (typeof executor !== 'function') { + throw new TypeError('executor must be a function.'); + } + + var resolvePromise; + this.promise = new Promise(function promiseExecutor(resolve) { + resolvePromise = resolve; + }); + + var token = this; + executor(function cancel(message) { + if (token.reason) { + // Cancellation has already been requested + return; + } + + token.reason = new Cancel(message); + resolvePromise(token.reason); + }); +} + +/** + * Throws a `Cancel` if cancellation has been requested. + */ +CancelToken.prototype.throwIfRequested = function throwIfRequested() { + if (this.reason) { + throw this.reason; + } +}; + +/** + * Returns an object that contains a new `CancelToken` and a function that, when called, + * cancels the `CancelToken`. + */ +CancelToken.source = function source() { + var cancel; + var token = new CancelToken(function executor(c) { + cancel = c; + }); + return { + token: token, + cancel: cancel + }; +}; + +module.exports = CancelToken; + + +/***/ }), +/* 492 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +/** + * Syntactic sugar for invoking a function and expanding an array for arguments. + * + * Common use case would be to use `Function.prototype.apply`. + * + * ```js + * function f(x, y, z) {} + * var args = [1, 2, 3]; + * f.apply(null, args); + * ``` + * + * With `spread` this example can be re-written. + * + * ```js + * spread(function(x, y, z) {})([1, 2, 3]); + * ``` + * + * @param {Function} callback + * @returns {Function} + */ +module.exports = function spread(callback) { + return function wrap(arr) { + return callback.apply(null, arr); + }; +}; + + +/***/ }), +/* 493 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +Object.defineProperty(exports, "__esModule", { value: true }); +const tslib_1 = __webpack_require__(36); +tslib_1.__exportStar(__webpack_require__(494), exports); + + +/***/ }), +/* 494 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +Object.defineProperty(exports, "__esModule", { value: true }); +exports.isAxiosRequestError = (error) => { + return error && error.config && error.response === undefined; +}; +exports.isAxiosResponseError = (error) => { + return error && error.response && error.response.status !== undefined; +}; + + +/***/ }), +/* 495 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +Object.defineProperty(exports, "__esModule", { value: true }); +class KbnClientStatus { + constructor(requester) { + this.requester = requester; + } + /** + * Get the full server status + */ + async get() { + return await this.requester.request({ + method: 'GET', + path: 'api/status', + }); + } + /** + * Get the overall/merged state + */ + async getOverallState() { + const status = await this.get(); + return status.status.overall.state; + } +} +exports.KbnClientStatus = KbnClientStatus; + + +/***/ }), +/* 496 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +Object.defineProperty(exports, "__esModule", { value: true }); +const PLUGIN_STATUS_ID = /^plugin:(.+?)@/; +class KbnClientPlugins { + constructor(status) { + this.status = status; + } + /** + * Get a list of plugin ids that are enabled on the server + */ + async getEnabledIds() { + const pluginIds = []; + const apiResp = await this.status.get(); + for (const status of apiResp.status.statuses) { + if (status.id) { + const match = status.id.match(PLUGIN_STATUS_ID); + if (match) { + pluginIds.push(match[1]); + } + } + } + return pluginIds; + } +} +exports.KbnClientPlugins = KbnClientPlugins; + + +/***/ }), +/* 497 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +Object.defineProperty(exports, "__esModule", { value: true }); +class KbnClientVersion { + constructor(status) { + this.status = status; + } + async get() { + if (this.versionCache !== undefined) { + return this.versionCache; + } + const status = await this.status.get(); + this.versionCache = status.version.number + (status.version.build_snapshot ? '-SNAPSHOT' : ''); + return this.versionCache; + } +} +exports.KbnClientVersion = KbnClientVersion; + + +/***/ }), +/* 498 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +Object.defineProperty(exports, "__esModule", { value: true }); +const kbn_client_requester_1 = __webpack_require__(453); +class KbnClientSavedObjects { + constructor(log, requester) { + this.log = log; + this.requester = requester; + } + /** + * Get an object + */ + async get(options) { + this.log.debug('Gettings saved object: %j', options); + return await this.requester.request({ + description: 'get saved object', + path: kbn_client_requester_1.uriencode `/api/saved_objects/${options.type}/${options.id}`, + method: 'GET', + }); + } + /** + * Create a saved object + */ + async create(options) { + this.log.debug('Creating saved object: %j', options); + return await this.requester.request({ + description: 'update saved object', + path: options.id + ? kbn_client_requester_1.uriencode `/api/saved_objects/${options.type}/${options.id}` + : kbn_client_requester_1.uriencode `/api/saved_objects/${options.type}`, + query: { + overwrite: options.overwrite, + }, + method: 'POST', + body: { + attributes: options.attributes, + migrationVersion: options.migrationVersion, + references: options.references, + }, + }); + } + /** + * Update a saved object + */ + async update(options) { + this.log.debug('Updating saved object: %j', options); + return await this.requester.request({ + description: 'update saved object', + path: kbn_client_requester_1.uriencode `/api/saved_objects/${options.type}/${options.id}`, + query: { + overwrite: options.overwrite, + }, + method: 'PUT', + body: { + attributes: options.attributes, + migrationVersion: options.migrationVersion, + references: options.references, + }, + }); + } + /** + * Delete an object + */ + async delete(options) { + this.log.debug('Deleting saved object %s/%s', options); + return await this.requester.request({ + description: 'delete saved object', + path: kbn_client_requester_1.uriencode `/api/saved_objects/${options.type}/${options.id}`, + method: 'DELETE', + }); + } +} +exports.KbnClientSavedObjects = KbnClientSavedObjects; + + +/***/ }), +/* 499 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +Object.defineProperty(exports, "__esModule", { value: true }); +const kbn_client_requester_1 = __webpack_require__(453); +class KbnClientUiSettings { + constructor(log, requester, defaults) { + this.log = log; + this.requester = requester; + this.defaults = defaults; + } + async get(setting) { + var _a; + const all = await this.getAll(); + const value = (_a = all[setting]) === null || _a === void 0 ? void 0 : _a.userValue; + this.log.verbose('uiSettings.value: %j', value); + return value; + } + /** + * Gets defaultIndex from the config doc. + */ + async getDefaultIndex() { + return await this.get('defaultIndex'); + } + /** + * Unset a uiSetting + */ + async unset(setting) { + return await this.requester.request({ + path: kbn_client_requester_1.uriencode `/api/kibana/settings/${setting}`, + method: 'DELETE', + }); + } + /** + * Replace all uiSettings with the `doc` values, `doc` is merged + * with some defaults + */ + async replace(doc) { + this.log.debug('replacing kibana config doc: %j', doc); + const changes = { + ...this.defaults, + ...doc, + }; + for (const [name, { isOverridden }] of Object.entries(await this.getAll())) { + if (!isOverridden && !changes.hasOwnProperty(name)) { + changes[name] = null; + } + } + await this.requester.request({ + method: 'POST', + path: '/api/kibana/settings', + body: { changes }, + retries: 5, + }); + } + /** + * Add fields to the config doc (like setting timezone and defaultIndex) + */ + async update(updates) { + this.log.debug('applying update to kibana config: %j', updates); + await this.requester.request({ + path: '/api/kibana/settings', + method: 'POST', + body: { + changes: updates, + }, + }); + } + async getAll() { + const resp = await this.requester.request({ + path: '/api/kibana/settings', + method: 'GET', + }); + return resp.settings; + } +} +exports.KbnClientUiSettings = KbnClientUiSettings; + + +/***/ }), +/* 500 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "parallelizeBatches", function() { return parallelizeBatches; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "parallelize", function() { return parallelize; }); +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +async function parallelizeBatches(batches, fn) { + for (const batch of batches) { + // We need to make sure the entire batch has completed before we can move on + // to the next batch + await parallelize(batch, fn); + } +} +async function parallelize(items, fn, concurrency = 4) { + if (items.length === 0) { + return; + } + + return new Promise((resolve, reject) => { + let activePromises = 0; + const values = items.slice(0); + + async function scheduleItem(item) { + activePromises++; + + try { + await fn(item); + activePromises--; + + if (values.length > 0) { + // We have more work to do, so we schedule the next promise + scheduleItem(values.shift()); + } else if (activePromises === 0) { + // We have no more values left, and all items have completed, so we've + // completed all the work. + resolve(); + } + } catch (error) { + reject(error); + } + } + + values.splice(0, concurrency).map(scheduleItem); + }); +} + +/***/ }), +/* 501 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "getProjects", function() { return getProjects; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "buildProjectGraph", function() { return buildProjectGraph; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "topologicallyBatchProjects", function() { return topologicallyBatchProjects; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "includeTransitiveProjects", function() { return includeTransitiveProjects; }); +/* harmony import */ var glob__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(502); +/* harmony import */ var glob__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(glob__WEBPACK_IMPORTED_MODULE_0__); +/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(16); +/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_1__); +/* harmony import */ var util__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(29); +/* harmony import */ var util__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(util__WEBPACK_IMPORTED_MODULE_2__); +/* harmony import */ var _errors__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(515); +/* harmony import */ var _project__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(516); +/* harmony import */ var _workspaces__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(579); +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + + + + + + +const glob = Object(util__WEBPACK_IMPORTED_MODULE_2__["promisify"])(glob__WEBPACK_IMPORTED_MODULE_0___default.a); +/** a Map of project names to Project instances */ + +async function getProjects(rootPath, projectsPathsPatterns, { + include = [], + exclude = [] +} = {}) { + const projects = new Map(); + const workspaceProjectsPaths = await Object(_workspaces__WEBPACK_IMPORTED_MODULE_5__["workspacePackagePaths"])(rootPath); + + for (const pattern of projectsPathsPatterns) { + const pathsToProcess = await packagesFromGlobPattern({ + pattern, + rootPath + }); + + for (const filePath of pathsToProcess) { + const projectConfigPath = normalize(filePath); + const projectDir = path__WEBPACK_IMPORTED_MODULE_1___default.a.dirname(projectConfigPath); + const project = await _project__WEBPACK_IMPORTED_MODULE_4__["Project"].fromPath(projectDir); + + if (workspaceProjectsPaths.indexOf(filePath) >= 0) { + project.isWorkspaceProject = true; + } + + const excludeProject = exclude.includes(project.name) || include.length > 0 && !include.includes(project.name); + + if (excludeProject) { + continue; + } + + if (projects.has(project.name)) { + throw new _errors__WEBPACK_IMPORTED_MODULE_3__["CliError"](`There are multiple projects with the same name [${project.name}]`, { + name: project.name, + paths: [project.path, projects.get(project.name).path] + }); + } + + projects.set(project.name, project); + } + } + + return projects; +} + +function packagesFromGlobPattern({ + pattern, + rootPath +}) { + const globOptions = { + cwd: rootPath, + // Should throw in case of unusual errors when reading the file system + strict: true, + // Always returns absolute paths for matched files + absolute: true, + // Do not match ** against multiple filenames + // (This is only specified because we currently don't have a need for it.) + noglobstar: true + }; + return glob(path__WEBPACK_IMPORTED_MODULE_1___default.a.join(pattern, 'package.json'), globOptions); +} // https://github.com/isaacs/node-glob/blob/master/common.js#L104 +// glob always returns "\\" as "/" in windows, so everyone +// gets normalized because we can't have nice things. + + +function normalize(dir) { + return path__WEBPACK_IMPORTED_MODULE_1___default.a.normalize(dir); +} + +function buildProjectGraph(projects) { + const projectGraph = new Map(); + + for (const project of projects.values()) { + const projectDeps = []; + const dependencies = project.allDependencies; + + for (const depName of Object.keys(dependencies)) { + if (projects.has(depName)) { + const dep = projects.get(depName); + const dependentProjectIsInWorkspace = project.isWorkspaceProject || project.json.name === 'kibana'; + project.ensureValidProjectDependency(dep, dependentProjectIsInWorkspace); + projectDeps.push(dep); + } + } + + projectGraph.set(project.name, projectDeps); + } + + return projectGraph; +} +function topologicallyBatchProjects(projectsToBatch, projectGraph, { + batchByWorkspace = false +} = {}) { + // We're going to be chopping stuff out of this list, so copy it. + const projectsLeftToBatch = new Set(projectsToBatch.keys()); + const batches = []; + + if (batchByWorkspace) { + const workspaceRootProject = Array.from(projectsToBatch.values()).find(p => p.isWorkspaceRoot); + + if (!workspaceRootProject) { + throw new _errors__WEBPACK_IMPORTED_MODULE_3__["CliError"](`There was no yarn workspace root found.`); + } // Push in the workspace root first. + + + batches.push([workspaceRootProject]); + projectsLeftToBatch.delete(workspaceRootProject.name); // In the next batch, push in all workspace projects. + + const workspaceBatch = []; + + for (const projectName of projectsLeftToBatch) { + const project = projectsToBatch.get(projectName); + + if (project.isWorkspaceProject) { + workspaceBatch.push(project); + projectsLeftToBatch.delete(projectName); + } + } + + batches.push(workspaceBatch); + } + + while (projectsLeftToBatch.size > 0) { + // Get all projects that have no remaining dependencies within the repo + // that haven't yet been picked. + const batch = []; + + for (const projectName of projectsLeftToBatch) { + const projectDeps = projectGraph.get(projectName); + const needsDependenciesBatched = projectDeps.some(dep => projectsLeftToBatch.has(dep.name)); + + if (!needsDependenciesBatched) { + batch.push(projectsToBatch.get(projectName)); + } + } // If we weren't able to find a project with no remaining dependencies, + // then we've encountered a cycle in the dependency graph. + + + const hasCycles = batch.length === 0; + + if (hasCycles) { + const cycleProjectNames = [...projectsLeftToBatch]; + const message = 'Encountered a cycle in the dependency graph. Projects in cycle are:\n' + cycleProjectNames.join(', '); + throw new _errors__WEBPACK_IMPORTED_MODULE_3__["CliError"](message); + } + + batches.push(batch); + batch.forEach(project => projectsLeftToBatch.delete(project.name)); + } + + return batches; +} +function includeTransitiveProjects(subsetOfProjects, allProjects, { + onlyProductionDependencies = false +} = {}) { + const projectsWithDependents = new Map(); // the current list of packages we are expanding using breadth-first-search + + const toProcess = [...subsetOfProjects]; + + while (toProcess.length > 0) { + const project = toProcess.shift(); + const dependencies = onlyProductionDependencies ? project.productionDependencies : project.allDependencies; + Object.keys(dependencies).forEach(dep => { + if (allProjects.has(dep)) { + toProcess.push(allProjects.get(dep)); + } + }); + projectsWithDependents.set(project.name, project); + } + + return projectsWithDependents; +} + +/***/ }), +/* 502 */ +/***/ (function(module, exports, __webpack_require__) { + +// Approach: +// +// 1. Get the minimatch set +// 2. For each pattern in the set, PROCESS(pattern, false) +// 3. Store matches per-set, then uniq them +// +// PROCESS(pattern, inGlobStar) +// Get the first [n] items from pattern that are all strings +// Join these together. This is PREFIX. +// If there is no more remaining, then stat(PREFIX) and +// add to matches if it succeeds. END. +// +// If inGlobStar and PREFIX is symlink and points to dir +// set ENTRIES = [] +// else readdir(PREFIX) as ENTRIES +// If fail, END +// +// with ENTRIES +// If pattern[n] is GLOBSTAR +// // handle the case where the globstar match is empty +// // by pruning it out, and testing the resulting pattern +// PROCESS(pattern[0..n] + pattern[n+1 .. $], false) +// // handle other cases. +// for ENTRY in ENTRIES (not dotfiles) +// // attach globstar + tail onto the entry +// // Mark that this entry is a globstar match +// PROCESS(pattern[0..n] + ENTRY + pattern[n .. $], true) +// +// else // not globstar +// for ENTRY in ENTRIES (not dotfiles, unless pattern[n] is dot) +// Test ENTRY against pattern[n] +// If fails, continue +// If passes, PROCESS(pattern[0..n] + item + pattern[n+1 .. $]) +// +// Caveat: +// Cache all stats and readdirs results to minimize syscall. Since all +// we ever care about is existence and directory-ness, we can just keep +// `true` for files, and [children,...] for directories, or `false` for +// things that don't exist. + +module.exports = glob + +var fs = __webpack_require__(23) +var rp = __webpack_require__(503) +var minimatch = __webpack_require__(505) +var Minimatch = minimatch.Minimatch +var inherits = __webpack_require__(509) +var EE = __webpack_require__(379).EventEmitter +var path = __webpack_require__(16) +var assert = __webpack_require__(30) +var isAbsolute = __webpack_require__(511) +var globSync = __webpack_require__(512) +var common = __webpack_require__(513) +var alphasort = common.alphasort +var alphasorti = common.alphasorti +var setopts = common.setopts +var ownProp = common.ownProp +var inflight = __webpack_require__(514) +var util = __webpack_require__(29) +var childrenIgnored = common.childrenIgnored +var isIgnored = common.isIgnored + +var once = __webpack_require__(385) + +function glob (pattern, options, cb) { + if (typeof options === 'function') cb = options, options = {} + if (!options) options = {} + + if (options.sync) { + if (cb) + throw new TypeError('callback provided to sync glob') + return globSync(pattern, options) + } + + return new Glob(pattern, options, cb) +} + +glob.sync = globSync +var GlobSync = glob.GlobSync = globSync.GlobSync + +// old api surface +glob.glob = glob + +function extend (origin, add) { + if (add === null || typeof add !== 'object') { + return origin + } + + var keys = Object.keys(add) + var i = keys.length + while (i--) { + origin[keys[i]] = add[keys[i]] + } + return origin +} + +glob.hasMagic = function (pattern, options_) { + var options = extend({}, options_) + options.noprocess = true + + var g = new Glob(pattern, options) + var set = g.minimatch.set + + if (!pattern) + return false + + if (set.length > 1) + return true + + for (var j = 0; j < set[0].length; j++) { + if (typeof set[0][j] !== 'string') + return true + } + + return false +} + +glob.Glob = Glob +inherits(Glob, EE) +function Glob (pattern, options, cb) { + if (typeof options === 'function') { + cb = options + options = null + } + + if (options && options.sync) { + if (cb) + throw new TypeError('callback provided to sync glob') + return new GlobSync(pattern, options) + } + + if (!(this instanceof Glob)) + return new Glob(pattern, options, cb) + + setopts(this, pattern, options) + this._didRealPath = false + + // process each pattern in the minimatch set + var n = this.minimatch.set.length + + // The matches are stored as {: true,...} so that + // duplicates are automagically pruned. + // Later, we do an Object.keys() on these. + // Keep them as a list so we can fill in when nonull is set. + this.matches = new Array(n) + + if (typeof cb === 'function') { + cb = once(cb) + this.on('error', cb) + this.on('end', function (matches) { + cb(null, matches) + }) + } + + var self = this + this._processing = 0 + + this._emitQueue = [] + this._processQueue = [] + this.paused = false + + if (this.noprocess) + return this + + if (n === 0) + return done() + + var sync = true + for (var i = 0; i < n; i ++) { + this._process(this.minimatch.set[i], i, false, done) + } + sync = false + + function done () { + --self._processing + if (self._processing <= 0) { + if (sync) { + process.nextTick(function () { + self._finish() + }) + } else { + self._finish() + } + } + } +} + +Glob.prototype._finish = function () { + assert(this instanceof Glob) + if (this.aborted) + return + + if (this.realpath && !this._didRealpath) + return this._realpath() + + common.finish(this) + this.emit('end', this.found) +} + +Glob.prototype._realpath = function () { + if (this._didRealpath) + return + + this._didRealpath = true + + var n = this.matches.length + if (n === 0) + return this._finish() + + var self = this + for (var i = 0; i < this.matches.length; i++) + this._realpathSet(i, next) + + function next () { + if (--n === 0) + self._finish() + } +} + +Glob.prototype._realpathSet = function (index, cb) { + var matchset = this.matches[index] + if (!matchset) + return cb() + + var found = Object.keys(matchset) + var self = this + var n = found.length + + if (n === 0) + return cb() + + var set = this.matches[index] = Object.create(null) + found.forEach(function (p, i) { + // If there's a problem with the stat, then it means that + // one or more of the links in the realpath couldn't be + // resolved. just return the abs value in that case. + p = self._makeAbs(p) + rp.realpath(p, self.realpathCache, function (er, real) { + if (!er) + set[real] = true + else if (er.syscall === 'stat') + set[p] = true + else + self.emit('error', er) // srsly wtf right here + + if (--n === 0) { + self.matches[index] = set + cb() + } + }) + }) +} + +Glob.prototype._mark = function (p) { + return common.mark(this, p) +} + +Glob.prototype._makeAbs = function (f) { + return common.makeAbs(this, f) +} + +Glob.prototype.abort = function () { + this.aborted = true + this.emit('abort') +} + +Glob.prototype.pause = function () { + if (!this.paused) { + this.paused = true + this.emit('pause') + } +} + +Glob.prototype.resume = function () { + if (this.paused) { + this.emit('resume') + this.paused = false + if (this._emitQueue.length) { + var eq = this._emitQueue.slice(0) + this._emitQueue.length = 0 + for (var i = 0; i < eq.length; i ++) { + var e = eq[i] + this._emitMatch(e[0], e[1]) + } + } + if (this._processQueue.length) { + var pq = this._processQueue.slice(0) + this._processQueue.length = 0 + for (var i = 0; i < pq.length; i ++) { + var p = pq[i] + this._processing-- + this._process(p[0], p[1], p[2], p[3]) + } + } + } +} + +Glob.prototype._process = function (pattern, index, inGlobStar, cb) { + assert(this instanceof Glob) + assert(typeof cb === 'function') + + if (this.aborted) + return + + this._processing++ + if (this.paused) { + this._processQueue.push([pattern, index, inGlobStar, cb]) + return + } + + //console.error('PROCESS %d', this._processing, pattern) + + // Get the first [n] parts of pattern that are all strings. + var n = 0 + while (typeof pattern[n] === 'string') { + n ++ + } + // now n is the index of the first one that is *not* a string. + + // see if there's anything else + var prefix + switch (n) { + // if not, then this is rather simple + case pattern.length: + this._processSimple(pattern.join('/'), index, cb) + return + + case 0: + // pattern *starts* with some non-trivial item. + // going to readdir(cwd), but not include the prefix in matches. + prefix = null + break + + default: + // pattern has some string bits in the front. + // whatever it starts with, whether that's 'absolute' like /foo/bar, + // or 'relative' like '../baz' + prefix = pattern.slice(0, n).join('/') + break + } + + var remain = pattern.slice(n) + + // get the list of entries. + var read + if (prefix === null) + read = '.' + else if (isAbsolute(prefix) || isAbsolute(pattern.join('/'))) { + if (!prefix || !isAbsolute(prefix)) + prefix = '/' + prefix + read = prefix + } else + read = prefix + + var abs = this._makeAbs(read) + + //if ignored, skip _processing + if (childrenIgnored(this, read)) + return cb() + + var isGlobStar = remain[0] === minimatch.GLOBSTAR + if (isGlobStar) + this._processGlobStar(prefix, read, abs, remain, index, inGlobStar, cb) + else + this._processReaddir(prefix, read, abs, remain, index, inGlobStar, cb) +} + +Glob.prototype._processReaddir = function (prefix, read, abs, remain, index, inGlobStar, cb) { + var self = this + this._readdir(abs, inGlobStar, function (er, entries) { + return self._processReaddir2(prefix, read, abs, remain, index, inGlobStar, entries, cb) + }) +} + +Glob.prototype._processReaddir2 = function (prefix, read, abs, remain, index, inGlobStar, entries, cb) { + + // if the abs isn't a dir, then nothing can match! + if (!entries) + return cb() + + // It will only match dot entries if it starts with a dot, or if + // dot is set. Stuff like @(.foo|.bar) isn't allowed. + var pn = remain[0] + var negate = !!this.minimatch.negate + var rawGlob = pn._glob + var dotOk = this.dot || rawGlob.charAt(0) === '.' + + var matchedEntries = [] + for (var i = 0; i < entries.length; i++) { + var e = entries[i] + if (e.charAt(0) !== '.' || dotOk) { + var m + if (negate && !prefix) { + m = !e.match(pn) + } else { + m = e.match(pn) + } + if (m) + matchedEntries.push(e) + } + } + + //console.error('prd2', prefix, entries, remain[0]._glob, matchedEntries) + + var len = matchedEntries.length + // If there are no matched entries, then nothing matches. + if (len === 0) + return cb() + + // if this is the last remaining pattern bit, then no need for + // an additional stat *unless* the user has specified mark or + // stat explicitly. We know they exist, since readdir returned + // them. + + if (remain.length === 1 && !this.mark && !this.stat) { + if (!this.matches[index]) + this.matches[index] = Object.create(null) + + for (var i = 0; i < len; i ++) { + var e = matchedEntries[i] + if (prefix) { + if (prefix !== '/') + e = prefix + '/' + e + else + e = prefix + e + } + + if (e.charAt(0) === '/' && !this.nomount) { + e = path.join(this.root, e) + } + this._emitMatch(index, e) + } + // This was the last one, and no stats were needed + return cb() + } + + // now test all matched entries as stand-ins for that part + // of the pattern. + remain.shift() + for (var i = 0; i < len; i ++) { + var e = matchedEntries[i] + var newPattern + if (prefix) { + if (prefix !== '/') + e = prefix + '/' + e + else + e = prefix + e + } + this._process([e].concat(remain), index, inGlobStar, cb) + } + cb() +} + +Glob.prototype._emitMatch = function (index, e) { + if (this.aborted) + return + + if (isIgnored(this, e)) + return + + if (this.paused) { + this._emitQueue.push([index, e]) + return + } + + var abs = isAbsolute(e) ? e : this._makeAbs(e) + + if (this.mark) + e = this._mark(e) + + if (this.absolute) + e = abs + + if (this.matches[index][e]) + return + + if (this.nodir) { + var c = this.cache[abs] + if (c === 'DIR' || Array.isArray(c)) + return + } + + this.matches[index][e] = true + + var st = this.statCache[abs] + if (st) + this.emit('stat', e, st) + + this.emit('match', e) +} + +Glob.prototype._readdirInGlobStar = function (abs, cb) { + if (this.aborted) + return + + // follow all symlinked directories forever + // just proceed as if this is a non-globstar situation + if (this.follow) + return this._readdir(abs, false, cb) + + var lstatkey = 'lstat\0' + abs + var self = this + var lstatcb = inflight(lstatkey, lstatcb_) + + if (lstatcb) + fs.lstat(abs, lstatcb) + + function lstatcb_ (er, lstat) { + if (er && er.code === 'ENOENT') + return cb() + + var isSym = lstat && lstat.isSymbolicLink() + self.symlinks[abs] = isSym + + // If it's not a symlink or a dir, then it's definitely a regular file. + // don't bother doing a readdir in that case. + if (!isSym && lstat && !lstat.isDirectory()) { + self.cache[abs] = 'FILE' + cb() + } else + self._readdir(abs, false, cb) + } +} + +Glob.prototype._readdir = function (abs, inGlobStar, cb) { + if (this.aborted) + return + + cb = inflight('readdir\0'+abs+'\0'+inGlobStar, cb) + if (!cb) + return + + //console.error('RD %j %j', +inGlobStar, abs) + if (inGlobStar && !ownProp(this.symlinks, abs)) + return this._readdirInGlobStar(abs, cb) + + if (ownProp(this.cache, abs)) { + var c = this.cache[abs] + if (!c || c === 'FILE') + return cb() + + if (Array.isArray(c)) + return cb(null, c) + } + + var self = this + fs.readdir(abs, readdirCb(this, abs, cb)) +} + +function readdirCb (self, abs, cb) { + return function (er, entries) { + if (er) + self._readdirError(abs, er, cb) + else + self._readdirEntries(abs, entries, cb) + } +} + +Glob.prototype._readdirEntries = function (abs, entries, cb) { + if (this.aborted) + return + + // if we haven't asked to stat everything, then just + // assume that everything in there exists, so we can avoid + // having to stat it a second time. + if (!this.mark && !this.stat) { + for (var i = 0; i < entries.length; i ++) { + var e = entries[i] + if (abs === '/') + e = abs + e + else + e = abs + '/' + e + this.cache[e] = true + } + } + + this.cache[abs] = entries + return cb(null, entries) +} + +Glob.prototype._readdirError = function (f, er, cb) { + if (this.aborted) + return + + // handle errors, and cache the information + switch (er.code) { + case 'ENOTSUP': // https://github.com/isaacs/node-glob/issues/205 + case 'ENOTDIR': // totally normal. means it *does* exist. + var abs = this._makeAbs(f) + this.cache[abs] = 'FILE' + if (abs === this.cwdAbs) { + var error = new Error(er.code + ' invalid cwd ' + this.cwd) + error.path = this.cwd + error.code = er.code + this.emit('error', error) + this.abort() + } + break + + case 'ENOENT': // not terribly unusual + case 'ELOOP': + case 'ENAMETOOLONG': + case 'UNKNOWN': + this.cache[this._makeAbs(f)] = false + break + + default: // some unusual error. Treat as failure. + this.cache[this._makeAbs(f)] = false + if (this.strict) { + this.emit('error', er) + // If the error is handled, then we abort + // if not, we threw out of here + this.abort() + } + if (!this.silent) + console.error('glob error', er) + break + } + + return cb() +} + +Glob.prototype._processGlobStar = function (prefix, read, abs, remain, index, inGlobStar, cb) { + var self = this + this._readdir(abs, inGlobStar, function (er, entries) { + self._processGlobStar2(prefix, read, abs, remain, index, inGlobStar, entries, cb) + }) +} + + +Glob.prototype._processGlobStar2 = function (prefix, read, abs, remain, index, inGlobStar, entries, cb) { + //console.error('pgs2', prefix, remain[0], entries) + + // no entries means not a dir, so it can never have matches + // foo.txt/** doesn't match foo.txt + if (!entries) + return cb() + + // test without the globstar, and with every child both below + // and replacing the globstar. + var remainWithoutGlobStar = remain.slice(1) + var gspref = prefix ? [ prefix ] : [] + var noGlobStar = gspref.concat(remainWithoutGlobStar) + + // the noGlobStar pattern exits the inGlobStar state + this._process(noGlobStar, index, false, cb) + + var isSym = this.symlinks[abs] + var len = entries.length + + // If it's a symlink, and we're in a globstar, then stop + if (isSym && inGlobStar) + return cb() + + for (var i = 0; i < len; i++) { + var e = entries[i] + if (e.charAt(0) === '.' && !this.dot) + continue + + // these two cases enter the inGlobStar state + var instead = gspref.concat(entries[i], remainWithoutGlobStar) + this._process(instead, index, true, cb) + + var below = gspref.concat(entries[i], remain) + this._process(below, index, true, cb) + } + + cb() +} + +Glob.prototype._processSimple = function (prefix, index, cb) { + // XXX review this. Shouldn't it be doing the mounting etc + // before doing stat? kinda weird? + var self = this + this._stat(prefix, function (er, exists) { + self._processSimple2(prefix, index, er, exists, cb) + }) +} +Glob.prototype._processSimple2 = function (prefix, index, er, exists, cb) { + + //console.error('ps2', prefix, exists) + + if (!this.matches[index]) + this.matches[index] = Object.create(null) + + // If it doesn't exist, then just mark the lack of results + if (!exists) + return cb() + + if (prefix && isAbsolute(prefix) && !this.nomount) { + var trail = /[\/\\]$/.test(prefix) + if (prefix.charAt(0) === '/') { + prefix = path.join(this.root, prefix) + } else { + prefix = path.resolve(this.root, prefix) + if (trail) + prefix += '/' + } + } + + if (process.platform === 'win32') + prefix = prefix.replace(/\\/g, '/') + + // Mark this as a match + this._emitMatch(index, prefix) + cb() +} + +// Returns either 'DIR', 'FILE', or false +Glob.prototype._stat = function (f, cb) { + var abs = this._makeAbs(f) + var needDir = f.slice(-1) === '/' + + if (f.length > this.maxLength) + return cb() + + if (!this.stat && ownProp(this.cache, abs)) { + var c = this.cache[abs] + + if (Array.isArray(c)) + c = 'DIR' + + // It exists, but maybe not how we need it + if (!needDir || c === 'DIR') + return cb(null, c) + + if (needDir && c === 'FILE') + return cb() + + // otherwise we have to stat, because maybe c=true + // if we know it exists, but not what it is. + } + + var exists + var stat = this.statCache[abs] + if (stat !== undefined) { + if (stat === false) + return cb(null, stat) + else { + var type = stat.isDirectory() ? 'DIR' : 'FILE' + if (needDir && type === 'FILE') + return cb() + else + return cb(null, type, stat) + } + } + + var self = this + var statcb = inflight('stat\0' + abs, lstatcb_) + if (statcb) + fs.lstat(abs, statcb) + + function lstatcb_ (er, lstat) { + if (lstat && lstat.isSymbolicLink()) { + // If it's a symlink, then treat it as the target, unless + // the target does not exist, then treat it as a file. + return fs.stat(abs, function (er, stat) { + if (er) + self._stat2(f, abs, null, lstat, cb) + else + self._stat2(f, abs, er, stat, cb) + }) + } else { + self._stat2(f, abs, er, lstat, cb) + } + } +} + +Glob.prototype._stat2 = function (f, abs, er, stat, cb) { + if (er && (er.code === 'ENOENT' || er.code === 'ENOTDIR')) { + this.statCache[abs] = false + return cb() + } + + var needDir = f.slice(-1) === '/' + this.statCache[abs] = stat + + if (abs.slice(-1) === '/' && stat && !stat.isDirectory()) + return cb(null, false, stat) + + var c = true + if (stat) + c = stat.isDirectory() ? 'DIR' : 'FILE' + this.cache[abs] = this.cache[abs] || c + + if (needDir && c === 'FILE') + return cb() + + return cb(null, c, stat) +} + + +/***/ }), +/* 503 */ +/***/ (function(module, exports, __webpack_require__) { + +module.exports = realpath +realpath.realpath = realpath +realpath.sync = realpathSync +realpath.realpathSync = realpathSync +realpath.monkeypatch = monkeypatch +realpath.unmonkeypatch = unmonkeypatch + +var fs = __webpack_require__(23) +var origRealpath = fs.realpath +var origRealpathSync = fs.realpathSync + +var version = process.version +var ok = /^v[0-5]\./.test(version) +var old = __webpack_require__(504) + +function newError (er) { + return er && er.syscall === 'realpath' && ( + er.code === 'ELOOP' || + er.code === 'ENOMEM' || + er.code === 'ENAMETOOLONG' + ) +} + +function realpath (p, cache, cb) { + if (ok) { + return origRealpath(p, cache, cb) + } + + if (typeof cache === 'function') { + cb = cache + cache = null + } + origRealpath(p, cache, function (er, result) { + if (newError(er)) { + old.realpath(p, cache, cb) + } else { + cb(er, result) + } + }) +} + +function realpathSync (p, cache) { + if (ok) { + return origRealpathSync(p, cache) + } + + try { + return origRealpathSync(p, cache) + } catch (er) { + if (newError(er)) { + return old.realpathSync(p, cache) + } else { + throw er + } + } +} + +function monkeypatch () { + fs.realpath = realpath + fs.realpathSync = realpathSync +} + +function unmonkeypatch () { + fs.realpath = origRealpath + fs.realpathSync = origRealpathSync +} + + +/***/ }), +/* 504 */ +/***/ (function(module, exports, __webpack_require__) { + +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. + +var pathModule = __webpack_require__(16); +var isWindows = process.platform === 'win32'; +var fs = __webpack_require__(23); + +// JavaScript implementation of realpath, ported from node pre-v6 + +var DEBUG = process.env.NODE_DEBUG && /fs/.test(process.env.NODE_DEBUG); + +function rethrow() { + // Only enable in debug mode. A backtrace uses ~1000 bytes of heap space and + // is fairly slow to generate. + var callback; + if (DEBUG) { + var backtrace = new Error; + callback = debugCallback; + } else + callback = missingCallback; + + return callback; + + function debugCallback(err) { + if (err) { + backtrace.message = err.message; + err = backtrace; + missingCallback(err); + } + } + + function missingCallback(err) { + if (err) { + if (process.throwDeprecation) + throw err; // Forgot a callback but don't know where? Use NODE_DEBUG=fs + else if (!process.noDeprecation) { + var msg = 'fs: missing callback ' + (err.stack || err.message); + if (process.traceDeprecation) + console.trace(msg); + else + console.error(msg); + } + } + } +} + +function maybeCallback(cb) { + return typeof cb === 'function' ? cb : rethrow(); +} + +var normalize = pathModule.normalize; + +// Regexp that finds the next partion of a (partial) path +// result is [base_with_slash, base], e.g. ['somedir/', 'somedir'] +if (isWindows) { + var nextPartRe = /(.*?)(?:[\/\\]+|$)/g; +} else { + var nextPartRe = /(.*?)(?:[\/]+|$)/g; +} + +// Regex to find the device root, including trailing slash. E.g. 'c:\\'. +if (isWindows) { + var splitRootRe = /^(?:[a-zA-Z]:|[\\\/]{2}[^\\\/]+[\\\/][^\\\/]+)?[\\\/]*/; +} else { + var splitRootRe = /^[\/]*/; +} + +exports.realpathSync = function realpathSync(p, cache) { + // make p is absolute + p = pathModule.resolve(p); + + if (cache && Object.prototype.hasOwnProperty.call(cache, p)) { + return cache[p]; + } + + var original = p, + seenLinks = {}, + knownHard = {}; + + // current character position in p + var pos; + // the partial path so far, including a trailing slash if any + var current; + // the partial path without a trailing slash (except when pointing at a root) + var base; + // the partial path scanned in the previous round, with slash + var previous; + + start(); + + function start() { + // Skip over roots + var m = splitRootRe.exec(p); + pos = m[0].length; + current = m[0]; + base = m[0]; + previous = ''; + + // On windows, check that the root exists. On unix there is no need. + if (isWindows && !knownHard[base]) { + fs.lstatSync(base); + knownHard[base] = true; + } + } + + // walk down the path, swapping out linked pathparts for their real + // values + // NB: p.length changes. + while (pos < p.length) { + // find the next part + nextPartRe.lastIndex = pos; + var result = nextPartRe.exec(p); + previous = current; + current += result[0]; + base = previous + result[1]; + pos = nextPartRe.lastIndex; + + // continue if not a symlink + if (knownHard[base] || (cache && cache[base] === base)) { + continue; + } + + var resolvedLink; + if (cache && Object.prototype.hasOwnProperty.call(cache, base)) { + // some known symbolic link. no need to stat again. + resolvedLink = cache[base]; + } else { + var stat = fs.lstatSync(base); + if (!stat.isSymbolicLink()) { + knownHard[base] = true; + if (cache) cache[base] = base; + continue; + } + + // read the link if it wasn't read before + // dev/ino always return 0 on windows, so skip the check. + var linkTarget = null; + if (!isWindows) { + var id = stat.dev.toString(32) + ':' + stat.ino.toString(32); + if (seenLinks.hasOwnProperty(id)) { + linkTarget = seenLinks[id]; + } + } + if (linkTarget === null) { + fs.statSync(base); + linkTarget = fs.readlinkSync(base); + } + resolvedLink = pathModule.resolve(previous, linkTarget); + // track this, if given a cache. + if (cache) cache[base] = resolvedLink; + if (!isWindows) seenLinks[id] = linkTarget; + } + + // resolve the link, then start over + p = pathModule.resolve(resolvedLink, p.slice(pos)); + start(); + } + + if (cache) cache[original] = p; + + return p; +}; + + +exports.realpath = function realpath(p, cache, cb) { + if (typeof cb !== 'function') { + cb = maybeCallback(cache); + cache = null; + } + + // make p is absolute + p = pathModule.resolve(p); + + if (cache && Object.prototype.hasOwnProperty.call(cache, p)) { + return process.nextTick(cb.bind(null, null, cache[p])); + } + + var original = p, + seenLinks = {}, + knownHard = {}; + + // current character position in p + var pos; + // the partial path so far, including a trailing slash if any + var current; + // the partial path without a trailing slash (except when pointing at a root) + var base; + // the partial path scanned in the previous round, with slash + var previous; + + start(); + + function start() { + // Skip over roots + var m = splitRootRe.exec(p); + pos = m[0].length; + current = m[0]; + base = m[0]; + previous = ''; + + // On windows, check that the root exists. On unix there is no need. + if (isWindows && !knownHard[base]) { + fs.lstat(base, function(err) { + if (err) return cb(err); + knownHard[base] = true; + LOOP(); + }); + } else { + process.nextTick(LOOP); + } + } + + // walk down the path, swapping out linked pathparts for their real + // values + function LOOP() { + // stop if scanned past end of path + if (pos >= p.length) { + if (cache) cache[original] = p; + return cb(null, p); + } + + // find the next part + nextPartRe.lastIndex = pos; + var result = nextPartRe.exec(p); + previous = current; + current += result[0]; + base = previous + result[1]; + pos = nextPartRe.lastIndex; + + // continue if not a symlink + if (knownHard[base] || (cache && cache[base] === base)) { + return process.nextTick(LOOP); + } + + if (cache && Object.prototype.hasOwnProperty.call(cache, base)) { + // known symbolic link. no need to stat again. + return gotResolvedLink(cache[base]); + } + + return fs.lstat(base, gotStat); + } + + function gotStat(err, stat) { + if (err) return cb(err); + + // if not a symlink, skip to the next path part + if (!stat.isSymbolicLink()) { + knownHard[base] = true; + if (cache) cache[base] = base; + return process.nextTick(LOOP); + } + + // stat & read the link if not read before + // call gotTarget as soon as the link target is known + // dev/ino always return 0 on windows, so skip the check. + if (!isWindows) { + var id = stat.dev.toString(32) + ':' + stat.ino.toString(32); + if (seenLinks.hasOwnProperty(id)) { + return gotTarget(null, seenLinks[id], base); + } + } + fs.stat(base, function(err) { + if (err) return cb(err); + + fs.readlink(base, function(err, target) { + if (!isWindows) seenLinks[id] = target; + gotTarget(err, target); + }); + }); + } + + function gotTarget(err, target, base) { + if (err) return cb(err); + + var resolvedLink = pathModule.resolve(previous, target); + if (cache) cache[base] = resolvedLink; + gotResolvedLink(resolvedLink); + } + + function gotResolvedLink(resolvedLink) { + // resolve the link, then start over + p = pathModule.resolve(resolvedLink, p.slice(pos)); + start(); + } +}; + + +/***/ }), +/* 505 */ +/***/ (function(module, exports, __webpack_require__) { + +module.exports = minimatch +minimatch.Minimatch = Minimatch + +var path = { sep: '/' } +try { + path = __webpack_require__(16) +} catch (er) {} + +var GLOBSTAR = minimatch.GLOBSTAR = Minimatch.GLOBSTAR = {} +var expand = __webpack_require__(506) + +var plTypes = { + '!': { open: '(?:(?!(?:', close: '))[^/]*?)'}, + '?': { open: '(?:', close: ')?' }, + '+': { open: '(?:', close: ')+' }, + '*': { open: '(?:', close: ')*' }, + '@': { open: '(?:', close: ')' } +} + +// any single thing other than / +// don't need to escape / when using new RegExp() +var qmark = '[^/]' + +// * => any number of characters +var star = qmark + '*?' + +// ** when dots are allowed. Anything goes, except .. and . +// not (^ or / followed by one or two dots followed by $ or /), +// followed by anything, any number of times. +var twoStarDot = '(?:(?!(?:\\\/|^)(?:\\.{1,2})($|\\\/)).)*?' + +// not a ^ or / followed by a dot, +// followed by anything, any number of times. +var twoStarNoDot = '(?:(?!(?:\\\/|^)\\.).)*?' + +// characters that need to be escaped in RegExp. +var reSpecials = charSet('().*{}+?[]^$\\!') + +// "abc" -> { a:true, b:true, c:true } +function charSet (s) { + return s.split('').reduce(function (set, c) { + set[c] = true + return set + }, {}) +} + +// normalizes slashes. +var slashSplit = /\/+/ + +minimatch.filter = filter +function filter (pattern, options) { + options = options || {} + return function (p, i, list) { + return minimatch(p, pattern, options) + } +} + +function ext (a, b) { + a = a || {} + b = b || {} + var t = {} + Object.keys(b).forEach(function (k) { + t[k] = b[k] + }) + Object.keys(a).forEach(function (k) { + t[k] = a[k] + }) + return t +} + +minimatch.defaults = function (def) { + if (!def || !Object.keys(def).length) return minimatch + + var orig = minimatch + + var m = function minimatch (p, pattern, options) { + return orig.minimatch(p, pattern, ext(def, options)) + } + + m.Minimatch = function Minimatch (pattern, options) { + return new orig.Minimatch(pattern, ext(def, options)) + } + + return m +} + +Minimatch.defaults = function (def) { + if (!def || !Object.keys(def).length) return Minimatch + return minimatch.defaults(def).Minimatch +} + +function minimatch (p, pattern, options) { + if (typeof pattern !== 'string') { + throw new TypeError('glob pattern string required') + } + + if (!options) options = {} + + // shortcut: comments match nothing. + if (!options.nocomment && pattern.charAt(0) === '#') { + return false + } + + // "" only matches "" + if (pattern.trim() === '') return p === '' + + return new Minimatch(pattern, options).match(p) +} + +function Minimatch (pattern, options) { + if (!(this instanceof Minimatch)) { + return new Minimatch(pattern, options) + } + + if (typeof pattern !== 'string') { + throw new TypeError('glob pattern string required') + } + + if (!options) options = {} + pattern = pattern.trim() + + // windows support: need to use /, not \ + if (path.sep !== '/') { + pattern = pattern.split(path.sep).join('/') + } + + this.options = options + this.set = [] + this.pattern = pattern + this.regexp = null + this.negate = false + this.comment = false + this.empty = false + + // make the set of regexps etc. + this.make() +} + +Minimatch.prototype.debug = function () {} + +Minimatch.prototype.make = make +function make () { + // don't do it more than once. + if (this._made) return + + var pattern = this.pattern + var options = this.options + + // empty patterns and comments match nothing. + if (!options.nocomment && pattern.charAt(0) === '#') { + this.comment = true + return + } + if (!pattern) { + this.empty = true + return + } + + // step 1: figure out negation, etc. + this.parseNegate() + + // step 2: expand braces + var set = this.globSet = this.braceExpand() + + if (options.debug) this.debug = console.error + + this.debug(this.pattern, set) + + // step 3: now we have a set, so turn each one into a series of path-portion + // matching patterns. + // These will be regexps, except in the case of "**", which is + // set to the GLOBSTAR object for globstar behavior, + // and will not contain any / characters + set = this.globParts = set.map(function (s) { + return s.split(slashSplit) + }) + + this.debug(this.pattern, set) + + // glob --> regexps + set = set.map(function (s, si, set) { + return s.map(this.parse, this) + }, this) + + this.debug(this.pattern, set) + + // filter out everything that didn't compile properly. + set = set.filter(function (s) { + return s.indexOf(false) === -1 + }) + + this.debug(this.pattern, set) + + this.set = set +} + +Minimatch.prototype.parseNegate = parseNegate +function parseNegate () { + var pattern = this.pattern + var negate = false + var options = this.options + var negateOffset = 0 + + if (options.nonegate) return + + for (var i = 0, l = pattern.length + ; i < l && pattern.charAt(i) === '!' + ; i++) { + negate = !negate + negateOffset++ + } + + if (negateOffset) this.pattern = pattern.substr(negateOffset) + this.negate = negate +} + +// Brace expansion: +// a{b,c}d -> abd acd +// a{b,}c -> abc ac +// a{0..3}d -> a0d a1d a2d a3d +// a{b,c{d,e}f}g -> abg acdfg acefg +// a{b,c}d{e,f}g -> abdeg acdeg abdeg abdfg +// +// Invalid sets are not expanded. +// a{2..}b -> a{2..}b +// a{b}c -> a{b}c +minimatch.braceExpand = function (pattern, options) { + return braceExpand(pattern, options) +} + +Minimatch.prototype.braceExpand = braceExpand + +function braceExpand (pattern, options) { + if (!options) { + if (this instanceof Minimatch) { + options = this.options + } else { + options = {} + } + } + + pattern = typeof pattern === 'undefined' + ? this.pattern : pattern + + if (typeof pattern === 'undefined') { + throw new TypeError('undefined pattern') + } + + if (options.nobrace || + !pattern.match(/\{.*\}/)) { + // shortcut. no need to expand. + return [pattern] + } + + return expand(pattern) } -}, -rules: [/^(?:$)/,/^(?:\s+)/,/^(?:\+)/,/^(?:\()/,/^(?:\))/,/^(?::)/,/^(?:DocumentRef-([0-9A-Za-z-+.]+))/,/^(?:LicenseRef-([0-9A-Za-z-+.]+))/,/^(?:AND)/,/^(?:OR)/,/^(?:WITH)/,/^(?:BSD-3-Clause-No-Nuclear-License-2014)/,/^(?:BSD-3-Clause-No-Nuclear-Warranty)/,/^(?:GPL-2\.0-with-classpath-exception)/,/^(?:GPL-3\.0-with-autoconf-exception)/,/^(?:GPL-2\.0-with-autoconf-exception)/,/^(?:BSD-3-Clause-No-Nuclear-License)/,/^(?:MPL-2\.0-no-copyleft-exception)/,/^(?:GPL-2\.0-with-bison-exception)/,/^(?:GPL-2\.0-with-font-exception)/,/^(?:GPL-2\.0-with-GCC-exception)/,/^(?:CNRI-Python-GPL-Compatible)/,/^(?:GPL-3\.0-with-GCC-exception)/,/^(?:BSD-3-Clause-Attribution)/,/^(?:Classpath-exception-2\.0)/,/^(?:WxWindows-exception-3\.1)/,/^(?:freertos-exception-2\.0)/,/^(?:Autoconf-exception-3\.0)/,/^(?:i2p-gpl-java-exception)/,/^(?:gnu-javamail-exception)/,/^(?:Nokia-Qt-exception-1\.1)/,/^(?:Autoconf-exception-2\.0)/,/^(?:BSD-2-Clause-FreeBSD)/,/^(?:u-boot-exception-2\.0)/,/^(?:zlib-acknowledgement)/,/^(?:Bison-exception-2\.2)/,/^(?:BSD-2-Clause-NetBSD)/,/^(?:CLISP-exception-2\.0)/,/^(?:eCos-exception-2\.0)/,/^(?:BSD-3-Clause-Clear)/,/^(?:Font-exception-2\.0)/,/^(?:FLTK-exception-2\.0)/,/^(?:GCC-exception-2\.0)/,/^(?:Qwt-exception-1\.0)/,/^(?:Libtool-exception)/,/^(?:BSD-3-Clause-LBNL)/,/^(?:GCC-exception-3\.1)/,/^(?:Artistic-1\.0-Perl)/,/^(?:Artistic-1\.0-cl8)/,/^(?:CC-BY-NC-SA-2\.5)/,/^(?:MIT-advertising)/,/^(?:BSD-Source-Code)/,/^(?:CC-BY-NC-SA-4\.0)/,/^(?:LiLiQ-Rplus-1\.1)/,/^(?:CC-BY-NC-SA-3\.0)/,/^(?:BSD-4-Clause-UC)/,/^(?:CC-BY-NC-SA-2\.0)/,/^(?:CC-BY-NC-SA-1\.0)/,/^(?:CC-BY-NC-ND-4\.0)/,/^(?:CC-BY-NC-ND-3\.0)/,/^(?:CC-BY-NC-ND-2\.5)/,/^(?:CC-BY-NC-ND-2\.0)/,/^(?:CC-BY-NC-ND-1\.0)/,/^(?:LZMA-exception)/,/^(?:BitTorrent-1\.1)/,/^(?:CrystalStacker)/,/^(?:FLTK-exception)/,/^(?:SugarCRM-1\.1\.3)/,/^(?:BSD-Protection)/,/^(?:BitTorrent-1\.0)/,/^(?:HaskellReport)/,/^(?:Interbase-1\.0)/,/^(?:StandardML-NJ)/,/^(?:mif-exception)/,/^(?:Frameworx-1\.0)/,/^(?:389-exception)/,/^(?:CC-BY-NC-2\.0)/,/^(?:CC-BY-NC-2\.5)/,/^(?:CC-BY-NC-3\.0)/,/^(?:CC-BY-NC-4\.0)/,/^(?:W3C-19980720)/,/^(?:CC-BY-SA-1\.0)/,/^(?:CC-BY-SA-2\.0)/,/^(?:CC-BY-SA-2\.5)/,/^(?:CC-BY-ND-2\.0)/,/^(?:CC-BY-SA-4\.0)/,/^(?:CC-BY-SA-3\.0)/,/^(?:Artistic-1\.0)/,/^(?:Artistic-2\.0)/,/^(?:CC-BY-ND-2\.5)/,/^(?:CC-BY-ND-3\.0)/,/^(?:CC-BY-ND-4\.0)/,/^(?:CC-BY-ND-1\.0)/,/^(?:BSD-4-Clause)/,/^(?:BSD-3-Clause)/,/^(?:BSD-2-Clause)/,/^(?:CC-BY-NC-1\.0)/,/^(?:bzip2-1\.0\.6)/,/^(?:Unicode-TOU)/,/^(?:CNRI-Jython)/,/^(?:ImageMagick)/,/^(?:Adobe-Glyph)/,/^(?:CUA-OPL-1\.0)/,/^(?:OLDAP-2\.2\.2)/,/^(?:LiLiQ-R-1\.1)/,/^(?:bzip2-1\.0\.5)/,/^(?:LiLiQ-P-1\.1)/,/^(?:OLDAP-2\.0\.1)/,/^(?:OLDAP-2\.2\.1)/,/^(?:CNRI-Python)/,/^(?:XFree86-1\.1)/,/^(?:OSET-PL-2\.1)/,/^(?:Apache-2\.0)/,/^(?:Watcom-1\.0)/,/^(?:PostgreSQL)/,/^(?:Python-2\.0)/,/^(?:RHeCos-1\.1)/,/^(?:EUDatagrid)/,/^(?:Spencer-99)/,/^(?:Intel-ACPI)/,/^(?:CECILL-1\.0)/,/^(?:CECILL-1\.1)/,/^(?:JasPer-2\.0)/,/^(?:CECILL-2\.0)/,/^(?:CECILL-2\.1)/,/^(?:gSOAP-1\.3b)/,/^(?:Spencer-94)/,/^(?:Apache-1\.1)/,/^(?:Spencer-86)/,/^(?:Apache-1\.0)/,/^(?:ClArtistic)/,/^(?:TORQUE-1\.1)/,/^(?:CATOSL-1\.1)/,/^(?:Adobe-2006)/,/^(?:Zimbra-1\.4)/,/^(?:Zimbra-1\.3)/,/^(?:Condor-1\.1)/,/^(?:CC-BY-3\.0)/,/^(?:CC-BY-2\.5)/,/^(?:OLDAP-2\.4)/,/^(?:SGI-B-1\.1)/,/^(?:SISSL-1\.2)/,/^(?:SGI-B-1\.0)/,/^(?:OLDAP-2\.3)/,/^(?:CC-BY-4\.0)/,/^(?:Crossword)/,/^(?:SimPL-2\.0)/,/^(?:OLDAP-2\.2)/,/^(?:OLDAP-2\.1)/,/^(?:ErlPL-1\.1)/,/^(?:LPPL-1\.3a)/,/^(?:LPPL-1\.3c)/,/^(?:OLDAP-2\.0)/,/^(?:Leptonica)/,/^(?:CPOL-1\.02)/,/^(?:OLDAP-1\.4)/,/^(?:OLDAP-1\.3)/,/^(?:CC-BY-2\.0)/,/^(?:Unlicense)/,/^(?:OLDAP-2\.8)/,/^(?:OLDAP-1\.2)/,/^(?:MakeIndex)/,/^(?:OLDAP-2\.7)/,/^(?:OLDAP-1\.1)/,/^(?:Sleepycat)/,/^(?:D-FSL-1\.0)/,/^(?:CC-BY-1\.0)/,/^(?:OLDAP-2\.6)/,/^(?:WXwindows)/,/^(?:NPOSL-3\.0)/,/^(?:FreeImage)/,/^(?:SGI-B-2\.0)/,/^(?:OLDAP-2\.5)/,/^(?:Beerware)/,/^(?:Newsletr)/,/^(?:NBPL-1\.0)/,/^(?:NASA-1\.3)/,/^(?:NLOD-1\.0)/,/^(?:AGPL-1\.0)/,/^(?:OCLC-2\.0)/,/^(?:ODbL-1\.0)/,/^(?:PDDL-1\.0)/,/^(?:Motosoto)/,/^(?:Afmparse)/,/^(?:ANTLR-PD)/,/^(?:LPL-1\.02)/,/^(?:Abstyles)/,/^(?:eCos-2\.0)/,/^(?:APSL-1\.0)/,/^(?:LPPL-1\.2)/,/^(?:LPPL-1\.1)/,/^(?:LPPL-1\.0)/,/^(?:APSL-1\.1)/,/^(?:APSL-2\.0)/,/^(?:Info-ZIP)/,/^(?:Zend-2\.0)/,/^(?:IBM-pibs)/,/^(?:LGPL-2\.0)/,/^(?:LGPL-3\.0)/,/^(?:LGPL-2\.1)/,/^(?:GFDL-1\.3)/,/^(?:PHP-3\.01)/,/^(?:GFDL-1\.2)/,/^(?:GFDL-1\.1)/,/^(?:AGPL-3\.0)/,/^(?:Giftware)/,/^(?:EUPL-1\.1)/,/^(?:RPSL-1\.0)/,/^(?:EUPL-1\.0)/,/^(?:MIT-enna)/,/^(?:CECILL-B)/,/^(?:diffmark)/,/^(?:CECILL-C)/,/^(?:CDDL-1\.0)/,/^(?:Sendmail)/,/^(?:CDDL-1\.1)/,/^(?:CPAL-1\.0)/,/^(?:APSL-1\.2)/,/^(?:NPL-1\.1)/,/^(?:AFL-1\.2)/,/^(?:Caldera)/,/^(?:AFL-2\.0)/,/^(?:FSFULLR)/,/^(?:AFL-2\.1)/,/^(?:VSL-1\.0)/,/^(?:VOSTROM)/,/^(?:UPL-1\.0)/,/^(?:Dotseqn)/,/^(?:CPL-1\.0)/,/^(?:dvipdfm)/,/^(?:EPL-1\.0)/,/^(?:OCCT-PL)/,/^(?:ECL-1\.0)/,/^(?:Latex2e)/,/^(?:ECL-2\.0)/,/^(?:GPL-1\.0)/,/^(?:GPL-2\.0)/,/^(?:GPL-3\.0)/,/^(?:AFL-3\.0)/,/^(?:LAL-1\.2)/,/^(?:LAL-1\.3)/,/^(?:EFL-1\.0)/,/^(?:EFL-2\.0)/,/^(?:gnuplot)/,/^(?:Aladdin)/,/^(?:LPL-1\.0)/,/^(?:libtiff)/,/^(?:Entessa)/,/^(?:AMDPLPA)/,/^(?:IPL-1\.0)/,/^(?:OPL-1\.0)/,/^(?:OSL-1\.0)/,/^(?:OSL-1\.1)/,/^(?:OSL-2\.0)/,/^(?:OSL-2\.1)/,/^(?:OSL-3\.0)/,/^(?:OpenSSL)/,/^(?:ZPL-2\.1)/,/^(?:PHP-3\.0)/,/^(?:ZPL-2\.0)/,/^(?:ZPL-1\.1)/,/^(?:CC0-1\.0)/,/^(?:SPL-1\.0)/,/^(?:psutils)/,/^(?:MPL-1\.0)/,/^(?:QPL-1\.0)/,/^(?:MPL-1\.1)/,/^(?:MPL-2\.0)/,/^(?:APL-1\.0)/,/^(?:RPL-1\.1)/,/^(?:RPL-1\.5)/,/^(?:MIT-CMU)/,/^(?:Multics)/,/^(?:Eurosym)/,/^(?:BSL-1\.0)/,/^(?:MIT-feh)/,/^(?:Saxpath)/,/^(?:Borceux)/,/^(?:OFL-1\.1)/,/^(?:OFL-1\.0)/,/^(?:AFL-1\.1)/,/^(?:YPL-1\.1)/,/^(?:YPL-1\.0)/,/^(?:NPL-1\.0)/,/^(?:iMatix)/,/^(?:mpich2)/,/^(?:APAFML)/,/^(?:Bahyph)/,/^(?:RSA-MD)/,/^(?:psfrag)/,/^(?:Plexus)/,/^(?:eGenix)/,/^(?:Glulxe)/,/^(?:SAX-PD)/,/^(?:Imlib2)/,/^(?:Wsuipa)/,/^(?:LGPLLR)/,/^(?:Libpng)/,/^(?:xinetd)/,/^(?:MITNFA)/,/^(?:NetCDF)/,/^(?:Naumen)/,/^(?:SMPPL)/,/^(?:Nunit)/,/^(?:FSFUL)/,/^(?:GL2PS)/,/^(?:SMLNJ)/,/^(?:Rdisc)/,/^(?:Noweb)/,/^(?:Nokia)/,/^(?:SISSL)/,/^(?:Qhull)/,/^(?:Intel)/,/^(?:Glide)/,/^(?:Xerox)/,/^(?:AMPAS)/,/^(?:WTFPL)/,/^(?:MS-PL)/,/^(?:XSkat)/,/^(?:MS-RL)/,/^(?:MirOS)/,/^(?:RSCPL)/,/^(?:TMate)/,/^(?:OGTSL)/,/^(?:FSFAP)/,/^(?:NCSA)/,/^(?:Zlib)/,/^(?:SCEA)/,/^(?:SNIA)/,/^(?:NGPL)/,/^(?:NOSL)/,/^(?:ADSL)/,/^(?:MTLL)/,/^(?:NLPL)/,/^(?:Ruby)/,/^(?:JSON)/,/^(?:Barr)/,/^(?:0BSD)/,/^(?:Xnet)/,/^(?:Cube)/,/^(?:curl)/,/^(?:DSDP)/,/^(?:Fair)/,/^(?:HPND)/,/^(?:TOSL)/,/^(?:IJG)/,/^(?:SWL)/,/^(?:Vim)/,/^(?:FTL)/,/^(?:ICU)/,/^(?:OML)/,/^(?:NRL)/,/^(?:DOC)/,/^(?:TCL)/,/^(?:W3C)/,/^(?:NTP)/,/^(?:IPA)/,/^(?:ISC)/,/^(?:X11)/,/^(?:AAL)/,/^(?:AML)/,/^(?:xpp)/,/^(?:Zed)/,/^(?:MIT)/,/^(?:Mup)/], -conditions: {"INITIAL":{"rules":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,353,354,355,356,357,358,359,360,361,362,363,364],"inclusive":true}} -}); -return lexer; -})(); -parser.lexer = lexer; -function Parser () { - this.yy = {}; + +// parse a component of the expanded set. +// At this point, no pattern may contain "/" in it +// so we're going to return a 2d array, where each entry is the full +// pattern, split on '/', and then turned into a regular expression. +// A regexp is made at the end which joins each array with an +// escaped /, and another full one which joins each regexp with |. +// +// Following the lead of Bash 4.1, note that "**" only has special meaning +// when it is the *only* thing in a path portion. Otherwise, any series +// of * is equivalent to a single *. Globstar behavior is enabled by +// default, and can be disabled by setting options.noglobstar. +Minimatch.prototype.parse = parse +var SUBPARSE = {} +function parse (pattern, isSub) { + if (pattern.length > 1024 * 64) { + throw new TypeError('pattern is too long') + } + + var options = this.options + + // shortcuts + if (!options.noglobstar && pattern === '**') return GLOBSTAR + if (pattern === '') return '' + + var re = '' + var hasMagic = !!options.nocase + var escaping = false + // ? => one single character + var patternListStack = [] + var negativeLists = [] + var stateChar + var inClass = false + var reClassStart = -1 + var classStart = -1 + // . and .. never match anything that doesn't start with ., + // even when options.dot is set. + var patternStart = pattern.charAt(0) === '.' ? '' // anything + // not (start or / followed by . or .. followed by / or end) + : options.dot ? '(?!(?:^|\\\/)\\.{1,2}(?:$|\\\/))' + : '(?!\\.)' + var self = this + + function clearStateChar () { + if (stateChar) { + // we had some state-tracking character + // that wasn't consumed by this pass. + switch (stateChar) { + case '*': + re += star + hasMagic = true + break + case '?': + re += qmark + hasMagic = true + break + default: + re += '\\' + stateChar + break + } + self.debug('clearStateChar %j %j', stateChar, re) + stateChar = false + } + } + + for (var i = 0, len = pattern.length, c + ; (i < len) && (c = pattern.charAt(i)) + ; i++) { + this.debug('%s\t%s %s %j', pattern, i, re, c) + + // skip over any that are escaped. + if (escaping && reSpecials[c]) { + re += '\\' + c + escaping = false + continue + } + + switch (c) { + case '/': + // completely not allowed, even escaped. + // Should already be path-split by now. + return false + + case '\\': + clearStateChar() + escaping = true + continue + + // the various stateChar values + // for the "extglob" stuff. + case '?': + case '*': + case '+': + case '@': + case '!': + this.debug('%s\t%s %s %j <-- stateChar', pattern, i, re, c) + + // all of those are literals inside a class, except that + // the glob [!a] means [^a] in regexp + if (inClass) { + this.debug(' in class') + if (c === '!' && i === classStart + 1) c = '^' + re += c + continue + } + + // if we already have a stateChar, then it means + // that there was something like ** or +? in there. + // Handle the stateChar, then proceed with this one. + self.debug('call clearStateChar %j', stateChar) + clearStateChar() + stateChar = c + // if extglob is disabled, then +(asdf|foo) isn't a thing. + // just clear the statechar *now*, rather than even diving into + // the patternList stuff. + if (options.noext) clearStateChar() + continue + + case '(': + if (inClass) { + re += '(' + continue + } + + if (!stateChar) { + re += '\\(' + continue + } + + patternListStack.push({ + type: stateChar, + start: i - 1, + reStart: re.length, + open: plTypes[stateChar].open, + close: plTypes[stateChar].close + }) + // negation is (?:(?!js)[^/]*) + re += stateChar === '!' ? '(?:(?!(?:' : '(?:' + this.debug('plType %j %j', stateChar, re) + stateChar = false + continue + + case ')': + if (inClass || !patternListStack.length) { + re += '\\)' + continue + } + + clearStateChar() + hasMagic = true + var pl = patternListStack.pop() + // negation is (?:(?!js)[^/]*) + // The others are (?:) + re += pl.close + if (pl.type === '!') { + negativeLists.push(pl) + } + pl.reEnd = re.length + continue + + case '|': + if (inClass || !patternListStack.length || escaping) { + re += '\\|' + escaping = false + continue + } + + clearStateChar() + re += '|' + continue + + // these are mostly the same in regexp and glob + case '[': + // swallow any state-tracking char before the [ + clearStateChar() + + if (inClass) { + re += '\\' + c + continue + } + + inClass = true + classStart = i + reClassStart = re.length + re += c + continue + + case ']': + // a right bracket shall lose its special + // meaning and represent itself in + // a bracket expression if it occurs + // first in the list. -- POSIX.2 2.8.3.2 + if (i === classStart + 1 || !inClass) { + re += '\\' + c + escaping = false + continue + } + + // handle the case where we left a class open. + // "[z-a]" is valid, equivalent to "\[z-a\]" + if (inClass) { + // split where the last [ was, make sure we don't have + // an invalid re. if so, re-walk the contents of the + // would-be class to re-translate any characters that + // were passed through as-is + // TODO: It would probably be faster to determine this + // without a try/catch and a new RegExp, but it's tricky + // to do safely. For now, this is safe and works. + var cs = pattern.substring(classStart + 1, i) + try { + RegExp('[' + cs + ']') + } catch (er) { + // not a valid class! + var sp = this.parse(cs, SUBPARSE) + re = re.substr(0, reClassStart) + '\\[' + sp[0] + '\\]' + hasMagic = hasMagic || sp[1] + inClass = false + continue + } + } + + // finish up the class. + hasMagic = true + inClass = false + re += c + continue + + default: + // swallow any state char that wasn't consumed + clearStateChar() + + if (escaping) { + // no need + escaping = false + } else if (reSpecials[c] + && !(c === '^' && inClass)) { + re += '\\' + } + + re += c + + } // switch + } // for + + // handle the case where we left a class open. + // "[abc" is valid, equivalent to "\[abc" + if (inClass) { + // split where the last [ was, and escape it + // this is a huge pita. We now have to re-walk + // the contents of the would-be class to re-translate + // any characters that were passed through as-is + cs = pattern.substr(classStart + 1) + sp = this.parse(cs, SUBPARSE) + re = re.substr(0, reClassStart) + '\\[' + sp[0] + hasMagic = hasMagic || sp[1] + } + + // handle the case where we had a +( thing at the *end* + // of the pattern. + // each pattern list stack adds 3 chars, and we need to go through + // and escape any | chars that were passed through as-is for the regexp. + // Go through and escape them, taking care not to double-escape any + // | chars that were already escaped. + for (pl = patternListStack.pop(); pl; pl = patternListStack.pop()) { + var tail = re.slice(pl.reStart + pl.open.length) + this.debug('setting tail', re, pl) + // maybe some even number of \, then maybe 1 \, followed by a | + tail = tail.replace(/((?:\\{2}){0,64})(\\?)\|/g, function (_, $1, $2) { + if (!$2) { + // the | isn't already escaped, so escape it. + $2 = '\\' + } + + // need to escape all those slashes *again*, without escaping the + // one that we need for escaping the | character. As it works out, + // escaping an even number of slashes can be done by simply repeating + // it exactly after itself. That's why this trick works. + // + // I am sorry that you have to see this. + return $1 + $1 + $2 + '|' + }) + + this.debug('tail=%j\n %s', tail, tail, pl, re) + var t = pl.type === '*' ? star + : pl.type === '?' ? qmark + : '\\' + pl.type + + hasMagic = true + re = re.slice(0, pl.reStart) + t + '\\(' + tail + } + + // handle trailing things that only matter at the very end. + clearStateChar() + if (escaping) { + // trailing \\ + re += '\\\\' + } + + // only need to apply the nodot start if the re starts with + // something that could conceivably capture a dot + var addPatternStart = false + switch (re.charAt(0)) { + case '.': + case '[': + case '(': addPatternStart = true + } + + // Hack to work around lack of negative lookbehind in JS + // A pattern like: *.!(x).!(y|z) needs to ensure that a name + // like 'a.xyz.yz' doesn't match. So, the first negative + // lookahead, has to look ALL the way ahead, to the end of + // the pattern. + for (var n = negativeLists.length - 1; n > -1; n--) { + var nl = negativeLists[n] + + var nlBefore = re.slice(0, nl.reStart) + var nlFirst = re.slice(nl.reStart, nl.reEnd - 8) + var nlLast = re.slice(nl.reEnd - 8, nl.reEnd) + var nlAfter = re.slice(nl.reEnd) + + nlLast += nlAfter + + // Handle nested stuff like *(*.js|!(*.json)), where open parens + // mean that we should *not* include the ) in the bit that is considered + // "after" the negated section. + var openParensBefore = nlBefore.split('(').length - 1 + var cleanAfter = nlAfter + for (i = 0; i < openParensBefore; i++) { + cleanAfter = cleanAfter.replace(/\)[+*?]?/, '') + } + nlAfter = cleanAfter + + var dollar = '' + if (nlAfter === '' && isSub !== SUBPARSE) { + dollar = '$' + } + var newRe = nlBefore + nlFirst + nlAfter + dollar + nlLast + re = newRe + } + + // if the re is not "" at this point, then we need to make sure + // it doesn't match against an empty path part. + // Otherwise a/* will match a/, which it should not. + if (re !== '' && hasMagic) { + re = '(?=.)' + re + } + + if (addPatternStart) { + re = patternStart + re + } + + // parsing just a piece of a larger pattern. + if (isSub === SUBPARSE) { + return [re, hasMagic] + } + + // skip the regexp for non-magical patterns + // unescape anything in it, though, so that it'll be + // an exact match against a file etc. + if (!hasMagic) { + return globUnescape(pattern) + } + + var flags = options.nocase ? 'i' : '' + try { + var regExp = new RegExp('^' + re + '$', flags) + } catch (er) { + // If it was an invalid regular expression, then it can't match + // anything. This trick looks for a character after the end of + // the string, which is of course impossible, except in multi-line + // mode, but it's not a /m regex. + return new RegExp('$.') + } + + regExp._glob = pattern + regExp._src = re + + return regExp +} + +minimatch.makeRe = function (pattern, options) { + return new Minimatch(pattern, options || {}).makeRe() +} + +Minimatch.prototype.makeRe = makeRe +function makeRe () { + if (this.regexp || this.regexp === false) return this.regexp + + // at this point, this.set is a 2d array of partial + // pattern strings, or "**". + // + // It's better to use .match(). This function shouldn't + // be used, really, but it's pretty convenient sometimes, + // when you just want to work with a regex. + var set = this.set + + if (!set.length) { + this.regexp = false + return this.regexp + } + var options = this.options + + var twoStar = options.noglobstar ? star + : options.dot ? twoStarDot + : twoStarNoDot + var flags = options.nocase ? 'i' : '' + + var re = set.map(function (pattern) { + return pattern.map(function (p) { + return (p === GLOBSTAR) ? twoStar + : (typeof p === 'string') ? regExpEscape(p) + : p._src + }).join('\\\/') + }).join('|') + + // must match entire pattern + // ending in a * or ** will make it less strict. + re = '^(?:' + re + ')$' + + // can match anything, as long as it's not this. + if (this.negate) re = '^(?!' + re + ').*$' + + try { + this.regexp = new RegExp(re, flags) + } catch (ex) { + this.regexp = false + } + return this.regexp +} + +minimatch.match = function (list, pattern, options) { + options = options || {} + var mm = new Minimatch(pattern, options) + list = list.filter(function (f) { + return mm.match(f) + }) + if (mm.options.nonull && !list.length) { + list.push(pattern) + } + return list +} + +Minimatch.prototype.match = match +function match (f, partial) { + this.debug('match', f, this.pattern) + // short-circuit in the case of busted things. + // comments, etc. + if (this.comment) return false + if (this.empty) return f === '' + + if (f === '/' && partial) return true + + var options = this.options + + // windows: need to use /, not \ + if (path.sep !== '/') { + f = f.split(path.sep).join('/') + } + + // treat the test path as a set of pathparts. + f = f.split(slashSplit) + this.debug(this.pattern, 'split', f) + + // just ONE of the pattern sets in this.set needs to match + // in order for it to be valid. If negating, then just one + // match means that we have failed. + // Either way, return on the first hit. + + var set = this.set + this.debug(this.pattern, 'set', set) + + // Find the basename of the path by looking for the last non-empty segment + var filename + var i + for (i = f.length - 1; i >= 0; i--) { + filename = f[i] + if (filename) break + } + + for (i = 0; i < set.length; i++) { + var pattern = set[i] + var file = f + if (options.matchBase && pattern.length === 1) { + file = [filename] + } + var hit = this.matchOne(file, pattern, partial) + if (hit) { + if (options.flipNegate) return true + return !this.negate + } + } + + // didn't get any hits. this is success if it's a negative + // pattern, failure otherwise. + if (options.flipNegate) return false + return this.negate } -Parser.prototype = parser;parser.Parser = Parser; -return new Parser; -})(); +// set partial to true to test if, for example, +// "/a/b" matches the start of "/*/b/*/d" +// Partial means, if you run out of file before you run +// out of pattern, then that's fine, as long as all +// the parts match. +Minimatch.prototype.matchOne = function (file, pattern, partial) { + var options = this.options + + this.debug('matchOne', + { 'this': this, file: file, pattern: pattern }) -if (true) { -exports.parser = spdxparse; -exports.Parser = spdxparse.Parser; -exports.parse = function () { return spdxparse.parse.apply(spdxparse, arguments); }; -exports.main = function commonjsMain(args) { - if (!args[1]) { - console.log('Usage: '+args[0]+' FILE'); - process.exit(1); + this.debug('matchOne', file.length, pattern.length) + + for (var fi = 0, + pi = 0, + fl = file.length, + pl = pattern.length + ; (fi < fl) && (pi < pl) + ; fi++, pi++) { + this.debug('matchOne loop') + var p = pattern[pi] + var f = file[fi] + + this.debug(pattern, p, f) + + // should be impossible. + // some invalid regexp stuff in the set. + if (p === false) return false + + if (p === GLOBSTAR) { + this.debug('GLOBSTAR', [pattern, p, f]) + + // "**" + // a/**/b/**/c would match the following: + // a/b/x/y/z/c + // a/x/y/z/b/c + // a/b/x/b/x/c + // a/b/c + // To do this, take the rest of the pattern after + // the **, and see if it would match the file remainder. + // If so, return success. + // If not, the ** "swallows" a segment, and try again. + // This is recursively awful. + // + // a/**/b/**/c matching a/b/x/y/z/c + // - a matches a + // - doublestar + // - matchOne(b/x/y/z/c, b/**/c) + // - b matches b + // - doublestar + // - matchOne(x/y/z/c, c) -> no + // - matchOne(y/z/c, c) -> no + // - matchOne(z/c, c) -> no + // - matchOne(c, c) yes, hit + var fr = fi + var pr = pi + 1 + if (pr === pl) { + this.debug('** at the end') + // a ** at the end will just swallow the rest. + // We have found a match. + // however, it will not swallow /.x, unless + // options.dot is set. + // . and .. are *never* matched by **, for explosively + // exponential reasons. + for (; fi < fl; fi++) { + if (file[fi] === '.' || file[fi] === '..' || + (!options.dot && file[fi].charAt(0) === '.')) return false + } + return true + } + + // ok, let's see if we can swallow whatever we can. + while (fr < fl) { + var swallowee = file[fr] + + this.debug('\nglobstar while', file, fr, pattern, pr, swallowee) + + // XXX remove this slice. Just pass the start index. + if (this.matchOne(file.slice(fr), pattern.slice(pr), partial)) { + this.debug('globstar found match!', fr, fl, swallowee) + // found a match. + return true + } else { + // can't swallow "." or ".." ever. + // can only swallow ".foo" when explicitly asked. + if (swallowee === '.' || swallowee === '..' || + (!options.dot && swallowee.charAt(0) === '.')) { + this.debug('dot detected!', file, fr, pattern, pr) + break + } + + // ** swallows a segment, and continue. + this.debug('globstar swallow a segment, and continue') + fr++ + } + } + + // no match was found. + // However, in partial mode, we can't say this is necessarily over. + // If there's more *pattern* left, then + if (partial) { + // ran out of file + this.debug('\n>>> no match, partial?', file, fr, pattern, pr) + if (fr === fl) return true + } + return false } - var source = __webpack_require__(23).readFileSync(__webpack_require__(16).normalize(args[1]), "utf8"); - return exports.parser.parse(source); -}; -if ( true && __webpack_require__.c[__webpack_require__.s] === module) { - exports.main(process.argv.slice(1)); + + // something other than ** + // non-magic patterns just have to match exactly + // patterns with magic have been turned into regexps. + var hit + if (typeof p === 'string') { + if (options.nocase) { + hit = f.toLowerCase() === p.toLowerCase() + } else { + hit = f === p + } + this.debug('string match', p, f, hit) + } else { + hit = f.match(p) + this.debug('pattern match', p, f, hit) + } + + if (!hit) return false + } + + // Note: ending in / means that we'll get a final "" + // at the end of the pattern. This can only match a + // corresponding "" at the end of the file. + // If the file ends in /, then it can only match a + // a pattern that ends in /, unless the pattern just + // doesn't have any more for it. But, a/b/ should *not* + // match "a/b/*", even though "" matches against the + // [^/]*? pattern, except in partial mode, where it might + // simply not be reached yet. + // However, a/b/ should still satisfy a/* + + // now either we fell off the end of the pattern, or we're done. + if (fi === fl && pi === pl) { + // ran out of pattern and filename at the same time. + // an exact hit! + return true + } else if (fi === fl) { + // ran out of file, but still had pattern left. + // this is ok if we're doing the match as part of + // a glob fs traversal. + return partial + } else if (pi === pl) { + // ran out of pattern, still have file left. + // this is only acceptable if we're on the very last + // empty segment of a file with a trailing slash. + // a/* should match a/b/ + var emptyFileEnd = (fi === fl - 1) && (file[fi] === '') + return emptyFileEnd + } + + // should be unreachable. + throw new Error('wtf?') +} + +// replace stuff like \* with * +function globUnescape (s) { + return s.replace(/\\(.)/g, '$1') } + +function regExpEscape (s) { + return s.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&') } -/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(5)(module))) /***/ }), -/* 79 */ +/* 506 */ /***/ (function(module, exports, __webpack_require__) { -var licenseIDs = __webpack_require__(80); +var concatMap = __webpack_require__(507); +var balanced = __webpack_require__(508); -function valid(string) { - return licenseIDs.indexOf(string) > -1; +module.exports = expandTop; + +var escSlash = '\0SLASH'+Math.random()+'\0'; +var escOpen = '\0OPEN'+Math.random()+'\0'; +var escClose = '\0CLOSE'+Math.random()+'\0'; +var escComma = '\0COMMA'+Math.random()+'\0'; +var escPeriod = '\0PERIOD'+Math.random()+'\0'; + +function numeric(str) { + return parseInt(str, 10) == str + ? parseInt(str, 10) + : str.charCodeAt(0); } -// Common transpositions of license identifier acronyms -var transpositions = [ - ['APGL', 'AGPL'], - ['Gpl', 'GPL'], - ['GLP', 'GPL'], - ['APL', 'Apache'], - ['ISD', 'ISC'], - ['GLP', 'GPL'], - ['IST', 'ISC'], - ['Claude', 'Clause'], - [' or later', '+'], - [' International', ''], - ['GNU', 'GPL'], - ['GUN', 'GPL'], - ['+', ''], - ['GNU GPL', 'GPL'], - ['GNU/GPL', 'GPL'], - ['GNU GLP', 'GPL'], - ['GNU General Public License', 'GPL'], - ['Gnu public license', 'GPL'], - ['GNU Public License', 'GPL'], - ['GNU GENERAL PUBLIC LICENSE', 'GPL'], - ['MTI', 'MIT'], - ['Mozilla Public License', 'MPL'], - ['WTH', 'WTF'], - ['-License', ''] -]; +function escapeBraces(str) { + return str.split('\\\\').join(escSlash) + .split('\\{').join(escOpen) + .split('\\}').join(escClose) + .split('\\,').join(escComma) + .split('\\.').join(escPeriod); +} -var TRANSPOSED = 0; -var CORRECT = 1; +function unescapeBraces(str) { + return str.split(escSlash).join('\\') + .split(escOpen).join('{') + .split(escClose).join('}') + .split(escComma).join(',') + .split(escPeriod).join('.'); +} -// Simple corrections to nearly valid identifiers. -var transforms = [ - // e.g. 'mit' - function(argument) { - return argument.toUpperCase(); - }, - // e.g. 'MIT ' - function(argument) { - return argument.trim(); - }, - // e.g. 'M.I.T.' - function(argument) { - return argument.replace(/\./g, ''); - }, - // e.g. 'Apache- 2.0' - function(argument) { - return argument.replace(/\s+/g, ''); - }, - // e.g. 'CC BY 4.0'' - function(argument) { - return argument.replace(/\s+/g, '-'); - }, - // e.g. 'LGPLv2.1' - function(argument) { - return argument.replace('v', '-'); - }, - // e.g. 'Apache 2.0' - function(argument) { - return argument.replace(/,?\s*(\d)/, '-$1'); - }, - // e.g. 'GPL 2' - function(argument) { - return argument.replace(/,?\s*(\d)/, '-$1.0'); - }, - // e.g. 'Apache Version 2.0' - function(argument) { - return argument.replace(/,?\s*(V\.|v\.|V|v|Version|version)\s*(\d)/, '-$2'); - }, - // e.g. 'Apache Version 2' - function(argument) { - return argument.replace(/,?\s*(V\.|v\.|V|v|Version|version)\s*(\d)/, '-$2.0'); - }, - // e.g. 'ZLIB' - function(argument) { - return argument[0].toUpperCase() + argument.slice(1); - }, - // e.g. 'MPL/2.0' - function(argument) { - return argument.replace('/', '-'); - }, - // e.g. 'Apache 2' - function(argument) { - return argument - .replace(/\s*V\s*(\d)/, '-$1') - .replace(/(\d)$/, '$1.0'); - }, - // e.g. 'GPL-2.0-' - function(argument) { - return argument.slice(0, argument.length - 1); - }, - // e.g. 'GPL2' - function(argument) { - return argument.replace(/(\d)$/, '-$1.0'); - }, - // e.g. 'BSD 3' - function(argument) { - return argument.replace(/(-| )?(\d)$/, '-$2-Clause'); - }, - // e.g. 'BSD clause 3' - function(argument) { - return argument.replace(/(-| )clause(-| )(\d)/, '-$3-Clause'); - }, - // e.g. 'BY-NC-4.0' - function(argument) { - return 'CC-' + argument; - }, - // e.g. 'BY-NC' - function(argument) { - return 'CC-' + argument + '-4.0'; - }, - // e.g. 'Attribution-NonCommercial' - function(argument) { - return argument - .replace('Attribution', 'BY') - .replace('NonCommercial', 'NC') - .replace('NoDerivatives', 'ND') - .replace(/ (\d)/, '-$1') - .replace(/ ?International/, ''); - }, - // e.g. 'Attribution-NonCommercial' - function(argument) { - return 'CC-' + - argument - .replace('Attribution', 'BY') - .replace('NonCommercial', 'NC') - .replace('NoDerivatives', 'ND') - .replace(/ (\d)/, '-$1') - .replace(/ ?International/, '') + - '-4.0'; + +// Basically just str.split(","), but handling cases +// where we have nested braced sections, which should be +// treated as individual members, like {a,{b,c},d} +function parseCommaParts(str) { + if (!str) + return ['']; + + var parts = []; + var m = balanced('{', '}', str); + + if (!m) + return str.split(','); + + var pre = m.pre; + var body = m.body; + var post = m.post; + var p = pre.split(','); + + p[p.length-1] += '{' + body + '}'; + var postParts = parseCommaParts(post); + if (post.length) { + p[p.length-1] += postParts.shift(); + p.push.apply(p, postParts); + } + + parts.push.apply(parts, p); + + return parts; +} + +function expandTop(str) { + if (!str) + return []; + + // I don't know why Bash 4.3 does this, but it does. + // Anything starting with {} will have the first two bytes preserved + // but *only* at the top level, so {},a}b will not expand to anything, + // but a{},b}c will be expanded to [a}c,abc]. + // One could argue that this is a bug in Bash, but since the goal of + // this module is to match Bash's rules, we escape a leading {} + if (str.substr(0, 2) === '{}') { + str = '\\{\\}' + str.substr(2); + } + + return expand(escapeBraces(str), true).map(unescapeBraces); +} + +function identity(e) { + return e; +} + +function embrace(str) { + return '{' + str + '}'; +} +function isPadded(el) { + return /^-?0\d/.test(el); +} + +function lte(i, y) { + return i <= y; +} +function gte(i, y) { + return i >= y; +} + +function expand(str, isTop) { + var expansions = []; + + var m = balanced('{', '}', str); + if (!m || /\$$/.test(m.pre)) return [str]; + + var isNumericSequence = /^-?\d+\.\.-?\d+(?:\.\.-?\d+)?$/.test(m.body); + var isAlphaSequence = /^[a-zA-Z]\.\.[a-zA-Z](?:\.\.-?\d+)?$/.test(m.body); + var isSequence = isNumericSequence || isAlphaSequence; + var isOptions = m.body.indexOf(',') >= 0; + if (!isSequence && !isOptions) { + // {a},b} + if (m.post.match(/,.*\}/)) { + str = m.pre + '{' + m.body + escClose + m.post; + return expand(str); + } + return [str]; + } + + var n; + if (isSequence) { + n = m.body.split(/\.\./); + } else { + n = parseCommaParts(m.body); + if (n.length === 1) { + // x{{a,b}}y ==> x{a}y x{b}y + n = expand(n[0], false).map(embrace); + if (n.length === 1) { + var post = m.post.length + ? expand(m.post, false) + : ['']; + return post.map(function(p) { + return m.pre + n[0] + p; + }); + } + } + } + + // at this point, n is the parts, and we know it's not a comma set + // with a single entry. + + // no need to expand pre, since it is guaranteed to be free of brace-sets + var pre = m.pre; + var post = m.post.length + ? expand(m.post, false) + : ['']; + + var N; + + if (isSequence) { + var x = numeric(n[0]); + var y = numeric(n[1]); + var width = Math.max(n[0].length, n[1].length) + var incr = n.length == 3 + ? Math.abs(numeric(n[2])) + : 1; + var test = lte; + var reverse = y < x; + if (reverse) { + incr *= -1; + test = gte; + } + var pad = n.some(isPadded); + + N = []; + + for (var i = x; test(i, y); i += incr) { + var c; + if (isAlphaSequence) { + c = String.fromCharCode(i); + if (c === '\\') + c = ''; + } else { + c = String(i); + if (pad) { + var need = width - c.length; + if (need > 0) { + var z = new Array(need + 1).join('0'); + if (i < 0) + c = '-' + z + c.slice(1); + else + c = z + c; + } + } + } + N.push(c); + } + } else { + N = concatMap(n, function(el) { return expand(el, false) }); + } + + for (var j = 0; j < N.length; j++) { + for (var k = 0; k < post.length; k++) { + var expansion = pre + N[j] + post[k]; + if (!isTop || isSequence || expansion) + expansions.push(expansion); + } } -]; -// If all else fails, guess that strings containing certain substrings -// meant to identify certain licenses. -var lastResorts = [ - ['UNLI', 'Unlicense'], - ['WTF', 'WTFPL'], - ['2 CLAUSE', 'BSD-2-Clause'], - ['2-CLAUSE', 'BSD-2-Clause'], - ['3 CLAUSE', 'BSD-3-Clause'], - ['3-CLAUSE', 'BSD-3-Clause'], - ['AFFERO', 'AGPL-3.0'], - ['AGPL', 'AGPL-3.0'], - ['APACHE', 'Apache-2.0'], - ['ARTISTIC', 'Artistic-2.0'], - ['Affero', 'AGPL-3.0'], - ['BEER', 'Beerware'], - ['BOOST', 'BSL-1.0'], - ['BSD', 'BSD-2-Clause'], - ['ECLIPSE', 'EPL-1.0'], - ['FUCK', 'WTFPL'], - ['GNU', 'GPL-3.0'], - ['LGPL', 'LGPL-3.0'], - ['GPL', 'GPL-3.0'], - ['MIT', 'MIT'], - ['MPL', 'MPL-2.0'], - ['X11', 'X11'], - ['ZLIB', 'Zlib'] -]; + return expansions; +} -var SUBSTRING = 0; -var IDENTIFIER = 1; -var validTransformation = function(identifier) { - for (var i = 0; i < transforms.length; i++) { - var transformed = transforms[i](identifier); - if (transformed !== identifier && valid(transformed)) { - return transformed; + +/***/ }), +/* 507 */ +/***/ (function(module, exports) { + +module.exports = function (xs, fn) { + var res = []; + for (var i = 0; i < xs.length; i++) { + var x = fn(xs[i], i); + if (isArray(x)) res.push.apply(res, x); + else res.push(x); } - } - return null; + return res; }; -var validLastResort = function(identifier) { - var upperCased = identifier.toUpperCase(); - for (var i = 0; i < lastResorts.length; i++) { - var lastResort = lastResorts[i]; - if (upperCased.indexOf(lastResort[SUBSTRING]) > -1) { - return lastResort[IDENTIFIER]; - } - } - return null; +var isArray = Array.isArray || function (xs) { + return Object.prototype.toString.call(xs) === '[object Array]'; }; -var anyCorrection = function(identifier, check) { - for (var i = 0; i < transpositions.length; i++) { - var transposition = transpositions[i]; - var transposed = transposition[TRANSPOSED]; - if (identifier.indexOf(transposed) > -1) { - var corrected = identifier.replace( - transposed, - transposition[CORRECT] - ); - var checked = check(corrected); - if (checked !== null) { - return checked; + +/***/ }), +/* 508 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +module.exports = balanced; +function balanced(a, b, str) { + if (a instanceof RegExp) a = maybeMatch(a, str); + if (b instanceof RegExp) b = maybeMatch(b, str); + + var r = range(a, b, str); + + return r && { + start: r[0], + end: r[1], + pre: str.slice(0, r[0]), + body: str.slice(r[0] + a.length, r[1]), + post: str.slice(r[1] + b.length) + }; +} + +function maybeMatch(reg, str) { + var m = str.match(reg); + return m ? m[0] : null; +} + +balanced.range = range; +function range(a, b, str) { + var begs, beg, left, right, result; + var ai = str.indexOf(a); + var bi = str.indexOf(b, ai + 1); + var i = ai; + + if (ai >= 0 && bi > 0) { + begs = []; + left = str.length; + + while (i >= 0 && !result) { + if (i == ai) { + begs.push(i); + ai = str.indexOf(a, i + 1); + } else if (begs.length == 1) { + result = [ begs.pop(), bi ]; + } else { + beg = begs.pop(); + if (beg < left) { + left = beg; + right = bi; + } + + bi = str.indexOf(b, i + 1); } + + i = ai < bi && ai >= 0 ? ai : bi; } - } - return null; -}; -module.exports = function(identifier) { - identifier = identifier.replace(/\+$/, ''); - if (valid(identifier)) { - return identifier; - } - var transformed = validTransformation(identifier); - if (transformed !== null) { - return transformed; - } - transformed = anyCorrection(identifier, function(argument) { - if (valid(argument)) { - return argument; + if (begs.length) { + result = [ left, right ]; } - return validTransformation(argument); - }); - if (transformed !== null) { - return transformed; - } - transformed = validLastResort(identifier); - if (transformed !== null) { - return transformed; - } - transformed = anyCorrection(identifier, validLastResort); - if (transformed !== null) { - return transformed; } - return null; -}; + + return result; +} /***/ }), -/* 80 */ -/***/ (function(module) { +/* 509 */ +/***/ (function(module, exports, __webpack_require__) { + +try { + var util = __webpack_require__(29); + if (typeof util.inherits !== 'function') throw ''; + module.exports = util.inherits; +} catch (e) { + module.exports = __webpack_require__(510); +} -module.exports = JSON.parse("[\"Glide\",\"Abstyles\",\"AFL-1.1\",\"AFL-1.2\",\"AFL-2.0\",\"AFL-2.1\",\"AFL-3.0\",\"AMPAS\",\"APL-1.0\",\"Adobe-Glyph\",\"APAFML\",\"Adobe-2006\",\"AGPL-1.0\",\"Afmparse\",\"Aladdin\",\"ADSL\",\"AMDPLPA\",\"ANTLR-PD\",\"Apache-1.0\",\"Apache-1.1\",\"Apache-2.0\",\"AML\",\"APSL-1.0\",\"APSL-1.1\",\"APSL-1.2\",\"APSL-2.0\",\"Artistic-1.0\",\"Artistic-1.0-Perl\",\"Artistic-1.0-cl8\",\"Artistic-2.0\",\"AAL\",\"Bahyph\",\"Barr\",\"Beerware\",\"BitTorrent-1.0\",\"BitTorrent-1.1\",\"BSL-1.0\",\"Borceux\",\"BSD-2-Clause\",\"BSD-2-Clause-FreeBSD\",\"BSD-2-Clause-NetBSD\",\"BSD-3-Clause\",\"BSD-3-Clause-Clear\",\"BSD-4-Clause\",\"BSD-Protection\",\"BSD-Source-Code\",\"BSD-3-Clause-Attribution\",\"0BSD\",\"BSD-4-Clause-UC\",\"bzip2-1.0.5\",\"bzip2-1.0.6\",\"Caldera\",\"CECILL-1.0\",\"CECILL-1.1\",\"CECILL-2.0\",\"CECILL-2.1\",\"CECILL-B\",\"CECILL-C\",\"ClArtistic\",\"MIT-CMU\",\"CNRI-Jython\",\"CNRI-Python\",\"CNRI-Python-GPL-Compatible\",\"CPOL-1.02\",\"CDDL-1.0\",\"CDDL-1.1\",\"CPAL-1.0\",\"CPL-1.0\",\"CATOSL-1.1\",\"Condor-1.1\",\"CC-BY-1.0\",\"CC-BY-2.0\",\"CC-BY-2.5\",\"CC-BY-3.0\",\"CC-BY-4.0\",\"CC-BY-ND-1.0\",\"CC-BY-ND-2.0\",\"CC-BY-ND-2.5\",\"CC-BY-ND-3.0\",\"CC-BY-ND-4.0\",\"CC-BY-NC-1.0\",\"CC-BY-NC-2.0\",\"CC-BY-NC-2.5\",\"CC-BY-NC-3.0\",\"CC-BY-NC-4.0\",\"CC-BY-NC-ND-1.0\",\"CC-BY-NC-ND-2.0\",\"CC-BY-NC-ND-2.5\",\"CC-BY-NC-ND-3.0\",\"CC-BY-NC-ND-4.0\",\"CC-BY-NC-SA-1.0\",\"CC-BY-NC-SA-2.0\",\"CC-BY-NC-SA-2.5\",\"CC-BY-NC-SA-3.0\",\"CC-BY-NC-SA-4.0\",\"CC-BY-SA-1.0\",\"CC-BY-SA-2.0\",\"CC-BY-SA-2.5\",\"CC-BY-SA-3.0\",\"CC-BY-SA-4.0\",\"CC0-1.0\",\"Crossword\",\"CrystalStacker\",\"CUA-OPL-1.0\",\"Cube\",\"curl\",\"D-FSL-1.0\",\"diffmark\",\"WTFPL\",\"DOC\",\"Dotseqn\",\"DSDP\",\"dvipdfm\",\"EPL-1.0\",\"ECL-1.0\",\"ECL-2.0\",\"eGenix\",\"EFL-1.0\",\"EFL-2.0\",\"MIT-advertising\",\"MIT-enna\",\"Entessa\",\"ErlPL-1.1\",\"EUDatagrid\",\"EUPL-1.0\",\"EUPL-1.1\",\"Eurosym\",\"Fair\",\"MIT-feh\",\"Frameworx-1.0\",\"FreeImage\",\"FTL\",\"FSFAP\",\"FSFUL\",\"FSFULLR\",\"Giftware\",\"GL2PS\",\"Glulxe\",\"AGPL-3.0\",\"GFDL-1.1\",\"GFDL-1.2\",\"GFDL-1.3\",\"GPL-1.0\",\"GPL-2.0\",\"GPL-3.0\",\"LGPL-2.1\",\"LGPL-3.0\",\"LGPL-2.0\",\"gnuplot\",\"gSOAP-1.3b\",\"HaskellReport\",\"HPND\",\"IBM-pibs\",\"IPL-1.0\",\"ICU\",\"ImageMagick\",\"iMatix\",\"Imlib2\",\"IJG\",\"Info-ZIP\",\"Intel-ACPI\",\"Intel\",\"Interbase-1.0\",\"IPA\",\"ISC\",\"JasPer-2.0\",\"JSON\",\"LPPL-1.0\",\"LPPL-1.1\",\"LPPL-1.2\",\"LPPL-1.3a\",\"LPPL-1.3c\",\"Latex2e\",\"BSD-3-Clause-LBNL\",\"Leptonica\",\"LGPLLR\",\"Libpng\",\"libtiff\",\"LAL-1.2\",\"LAL-1.3\",\"LiLiQ-P-1.1\",\"LiLiQ-Rplus-1.1\",\"LiLiQ-R-1.1\",\"LPL-1.02\",\"LPL-1.0\",\"MakeIndex\",\"MTLL\",\"MS-PL\",\"MS-RL\",\"MirOS\",\"MITNFA\",\"MIT\",\"Motosoto\",\"MPL-1.0\",\"MPL-1.1\",\"MPL-2.0\",\"MPL-2.0-no-copyleft-exception\",\"mpich2\",\"Multics\",\"Mup\",\"NASA-1.3\",\"Naumen\",\"NBPL-1.0\",\"NetCDF\",\"NGPL\",\"NOSL\",\"NPL-1.0\",\"NPL-1.1\",\"Newsletr\",\"NLPL\",\"Nokia\",\"NPOSL-3.0\",\"NLOD-1.0\",\"Noweb\",\"NRL\",\"NTP\",\"Nunit\",\"OCLC-2.0\",\"ODbL-1.0\",\"PDDL-1.0\",\"OCCT-PL\",\"OGTSL\",\"OLDAP-2.2.2\",\"OLDAP-1.1\",\"OLDAP-1.2\",\"OLDAP-1.3\",\"OLDAP-1.4\",\"OLDAP-2.0\",\"OLDAP-2.0.1\",\"OLDAP-2.1\",\"OLDAP-2.2\",\"OLDAP-2.2.1\",\"OLDAP-2.3\",\"OLDAP-2.4\",\"OLDAP-2.5\",\"OLDAP-2.6\",\"OLDAP-2.7\",\"OLDAP-2.8\",\"OML\",\"OPL-1.0\",\"OSL-1.0\",\"OSL-1.1\",\"OSL-2.0\",\"OSL-2.1\",\"OSL-3.0\",\"OpenSSL\",\"OSET-PL-2.1\",\"PHP-3.0\",\"PHP-3.01\",\"Plexus\",\"PostgreSQL\",\"psfrag\",\"psutils\",\"Python-2.0\",\"QPL-1.0\",\"Qhull\",\"Rdisc\",\"RPSL-1.0\",\"RPL-1.1\",\"RPL-1.5\",\"RHeCos-1.1\",\"RSCPL\",\"RSA-MD\",\"Ruby\",\"SAX-PD\",\"Saxpath\",\"SCEA\",\"SWL\",\"SMPPL\",\"Sendmail\",\"SGI-B-1.0\",\"SGI-B-1.1\",\"SGI-B-2.0\",\"OFL-1.0\",\"OFL-1.1\",\"SimPL-2.0\",\"Sleepycat\",\"SNIA\",\"Spencer-86\",\"Spencer-94\",\"Spencer-99\",\"SMLNJ\",\"SugarCRM-1.1.3\",\"SISSL\",\"SISSL-1.2\",\"SPL-1.0\",\"Watcom-1.0\",\"TCL\",\"Unlicense\",\"TMate\",\"TORQUE-1.1\",\"TOSL\",\"Unicode-TOU\",\"UPL-1.0\",\"NCSA\",\"Vim\",\"VOSTROM\",\"VSL-1.0\",\"W3C-19980720\",\"W3C\",\"Wsuipa\",\"Xnet\",\"X11\",\"Xerox\",\"XFree86-1.1\",\"xinetd\",\"xpp\",\"XSkat\",\"YPL-1.0\",\"YPL-1.1\",\"Zed\",\"Zend-2.0\",\"Zimbra-1.3\",\"Zimbra-1.4\",\"Zlib\",\"zlib-acknowledgement\",\"ZPL-1.1\",\"ZPL-2.0\",\"ZPL-2.1\",\"BSD-3-Clause-No-Nuclear-License\",\"BSD-3-Clause-No-Nuclear-Warranty\",\"BSD-3-Clause-No-Nuclear-License-2014\",\"eCos-2.0\",\"GPL-2.0-with-autoconf-exception\",\"GPL-2.0-with-bison-exception\",\"GPL-2.0-with-classpath-exception\",\"GPL-2.0-with-font-exception\",\"GPL-2.0-with-GCC-exception\",\"GPL-3.0-with-autoconf-exception\",\"GPL-3.0-with-GCC-exception\",\"StandardML-NJ\",\"WXwindows\"]"); /***/ }), -/* 81 */ +/* 510 */ +/***/ (function(module, exports) { + +if (typeof Object.create === 'function') { + // implementation from standard node.js 'util' module + module.exports = function inherits(ctor, superCtor) { + ctor.super_ = superCtor + ctor.prototype = Object.create(superCtor.prototype, { + constructor: { + value: ctor, + enumerable: false, + writable: true, + configurable: true + } + }); + }; +} else { + // old school shim for old browsers + module.exports = function inherits(ctor, superCtor) { + ctor.super_ = superCtor + var TempCtor = function () {} + TempCtor.prototype = superCtor.prototype + ctor.prototype = new TempCtor() + ctor.prototype.constructor = ctor + } +} + + +/***/ }), +/* 511 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var url = __webpack_require__(82) -var gitHosts = __webpack_require__(83) -var GitHost = module.exports = __webpack_require__(84) -var protocolToRepresentationMap = { - 'git+ssh': 'sshurl', - 'git+https': 'https', - 'ssh': 'sshurl', - 'git': 'git' +function posix(path) { + return path.charAt(0) === '/'; } -function protocolToRepresentation (protocol) { - if (protocol.substr(-1) === ':') protocol = protocol.slice(0, -1) - return protocolToRepresentationMap[protocol] || protocol +function win32(path) { + // https://github.com/nodejs/node/blob/b3fcc245fb25539909ef1d5eaa01dbf92e168633/lib/path.js#L56 + var splitDeviceRe = /^([a-zA-Z]:|[\\\/]{2}[^\\\/]+[\\\/]+[^\\\/]+)?([\\\/])?([\s\S]*?)$/; + var result = splitDeviceRe.exec(path); + var device = result[1] || ''; + var isUnc = Boolean(device && device.charAt(1) !== ':'); + + // UNC paths are always absolute + return Boolean(result[2] || isUnc); } -var authProtocols = { - 'git:': true, - 'https:': true, - 'git+https:': true, - 'http:': true, - 'git+http:': true +module.exports = process.platform === 'win32' ? win32 : posix; +module.exports.posix = posix; +module.exports.win32 = win32; + + +/***/ }), +/* 512 */ +/***/ (function(module, exports, __webpack_require__) { + +module.exports = globSync +globSync.GlobSync = GlobSync + +var fs = __webpack_require__(23) +var rp = __webpack_require__(503) +var minimatch = __webpack_require__(505) +var Minimatch = minimatch.Minimatch +var Glob = __webpack_require__(502).Glob +var util = __webpack_require__(29) +var path = __webpack_require__(16) +var assert = __webpack_require__(30) +var isAbsolute = __webpack_require__(511) +var common = __webpack_require__(513) +var alphasort = common.alphasort +var alphasorti = common.alphasorti +var setopts = common.setopts +var ownProp = common.ownProp +var childrenIgnored = common.childrenIgnored +var isIgnored = common.isIgnored + +function globSync (pattern, options) { + if (typeof options === 'function' || arguments.length === 3) + throw new TypeError('callback provided to sync glob\n'+ + 'See: https://github.com/isaacs/node-glob/issues/167') + + return new GlobSync(pattern, options).found } -var cache = {} +function GlobSync (pattern, options) { + if (!pattern) + throw new Error('must provide pattern') -module.exports.fromUrl = function (giturl, opts) { - var key = giturl + JSON.stringify(opts || {}) + if (typeof options === 'function' || arguments.length === 3) + throw new TypeError('callback provided to sync glob\n'+ + 'See: https://github.com/isaacs/node-glob/issues/167') - if (!(key in cache)) { - cache[key] = fromUrl(giturl, opts) - } + if (!(this instanceof GlobSync)) + return new GlobSync(pattern, options) - return cache[key] + setopts(this, pattern, options) + + if (this.noprocess) + return this + + var n = this.minimatch.set.length + this.matches = new Array(n) + for (var i = 0; i < n; i ++) { + this._process(this.minimatch.set[i], i, false) + } + this._finish() } -function fromUrl (giturl, opts) { - if (giturl == null || giturl === '') return - var url = fixupUnqualifiedGist( - isGitHubShorthand(giturl) ? 'github:' + giturl : giturl - ) - var parsed = parseGitUrl(url) - var shortcutMatch = url.match(new RegExp('^([^:]+):(?:(?:[^@:]+(?:[^@]+)?@)?([^/]*))[/](.+?)(?:[.]git)?($|#)')) - var matches = Object.keys(gitHosts).map(function (gitHostName) { - try { - var gitHostInfo = gitHosts[gitHostName] - var auth = null - if (parsed.auth && authProtocols[parsed.protocol]) { - auth = decodeURIComponent(parsed.auth) +GlobSync.prototype._finish = function () { + assert(this instanceof GlobSync) + if (this.realpath) { + var self = this + this.matches.forEach(function (matchset, index) { + var set = self.matches[index] = Object.create(null) + for (var p in matchset) { + try { + p = self._makeAbs(p) + var real = rp.realpathSync(p, self.realpathCache) + set[real] = true + } catch (er) { + if (er.syscall === 'stat') + set[self._makeAbs(p)] = true + else + throw er + } } - var committish = parsed.hash ? decodeURIComponent(parsed.hash.substr(1)) : null - var user = null - var project = null - var defaultRepresentation = null - if (shortcutMatch && shortcutMatch[1] === gitHostName) { - user = shortcutMatch[2] && decodeURIComponent(shortcutMatch[2]) - project = decodeURIComponent(shortcutMatch[3]) - defaultRepresentation = 'shortcut' + }) + } + common.finish(this) +} + + +GlobSync.prototype._process = function (pattern, index, inGlobStar) { + assert(this instanceof GlobSync) + + // Get the first [n] parts of pattern that are all strings. + var n = 0 + while (typeof pattern[n] === 'string') { + n ++ + } + // now n is the index of the first one that is *not* a string. + + // See if there's anything else + var prefix + switch (n) { + // if not, then this is rather simple + case pattern.length: + this._processSimple(pattern.join('/'), index) + return + + case 0: + // pattern *starts* with some non-trivial item. + // going to readdir(cwd), but not include the prefix in matches. + prefix = null + break + + default: + // pattern has some string bits in the front. + // whatever it starts with, whether that's 'absolute' like /foo/bar, + // or 'relative' like '../baz' + prefix = pattern.slice(0, n).join('/') + break + } + + var remain = pattern.slice(n) + + // get the list of entries. + var read + if (prefix === null) + read = '.' + else if (isAbsolute(prefix) || isAbsolute(pattern.join('/'))) { + if (!prefix || !isAbsolute(prefix)) + prefix = '/' + prefix + read = prefix + } else + read = prefix + + var abs = this._makeAbs(read) + + //if ignored, skip processing + if (childrenIgnored(this, read)) + return + + var isGlobStar = remain[0] === minimatch.GLOBSTAR + if (isGlobStar) + this._processGlobStar(prefix, read, abs, remain, index, inGlobStar) + else + this._processReaddir(prefix, read, abs, remain, index, inGlobStar) +} + + +GlobSync.prototype._processReaddir = function (prefix, read, abs, remain, index, inGlobStar) { + var entries = this._readdir(abs, inGlobStar) + + // if the abs isn't a dir, then nothing can match! + if (!entries) + return + + // It will only match dot entries if it starts with a dot, or if + // dot is set. Stuff like @(.foo|.bar) isn't allowed. + var pn = remain[0] + var negate = !!this.minimatch.negate + var rawGlob = pn._glob + var dotOk = this.dot || rawGlob.charAt(0) === '.' + + var matchedEntries = [] + for (var i = 0; i < entries.length; i++) { + var e = entries[i] + if (e.charAt(0) !== '.' || dotOk) { + var m + if (negate && !prefix) { + m = !e.match(pn) } else { - if (parsed.host !== gitHostInfo.domain) return - if (!gitHostInfo.protocols_re.test(parsed.protocol)) return - if (!parsed.path) return - var pathmatch = gitHostInfo.pathmatch - var matched = parsed.path.match(pathmatch) - if (!matched) return - if (matched[1] != null) user = decodeURIComponent(matched[1].replace(/^:/, '')) - if (matched[2] != null) project = decodeURIComponent(matched[2]) - defaultRepresentation = protocolToRepresentation(parsed.protocol) + m = e.match(pn) } - return new GitHost(gitHostName, user, auth, project, committish, defaultRepresentation, opts) - } catch (ex) { - if (!(ex instanceof URIError)) throw ex + if (m) + matchedEntries.push(e) } - }).filter(function (gitHostInfo) { return gitHostInfo }) - if (matches.length !== 1) return - return matches[0] -} + } -function isGitHubShorthand (arg) { - // Note: This does not fully test the git ref format. - // See https://www.kernel.org/pub/software/scm/git/docs/git-check-ref-format.html - // - // The only way to do this properly would be to shell out to - // git-check-ref-format, and as this is a fast sync function, - // we don't want to do that. Just let git fail if it turns - // out that the commit-ish is invalid. - // GH usernames cannot start with . or - - return /^[^:@%/\s.-][^:@%/\s]*[/][^:@\s/%]+(?:#.*)?$/.test(arg) -} + var len = matchedEntries.length + // If there are no matched entries, then nothing matches. + if (len === 0) + return -function fixupUnqualifiedGist (giturl) { - // necessary for round-tripping gists - var parsed = url.parse(giturl) - if (parsed.protocol === 'gist:' && parsed.host && !parsed.path) { - return parsed.protocol + '/' + parsed.host - } else { - return giturl + // if this is the last remaining pattern bit, then no need for + // an additional stat *unless* the user has specified mark or + // stat explicitly. We know they exist, since readdir returned + // them. + + if (remain.length === 1 && !this.mark && !this.stat) { + if (!this.matches[index]) + this.matches[index] = Object.create(null) + + for (var i = 0; i < len; i ++) { + var e = matchedEntries[i] + if (prefix) { + if (prefix.slice(-1) !== '/') + e = prefix + '/' + e + else + e = prefix + e + } + + if (e.charAt(0) === '/' && !this.nomount) { + e = path.join(this.root, e) + } + this._emitMatch(index, e) + } + // This was the last one, and no stats were needed + return } -} -function parseGitUrl (giturl) { - if (typeof giturl !== 'string') giturl = '' + giturl - var matched = giturl.match(/^([^@]+)@([^:/]+):[/]?((?:[^/]+[/])?[^/]+?)(?:[.]git)?(#.*)?$/) - if (!matched) return url.parse(giturl) - return { - protocol: 'git+ssh:', - slashes: true, - auth: matched[1], - host: matched[2], - port: null, - hostname: matched[2], - hash: matched[4], - search: null, - query: null, - pathname: '/' + matched[3], - path: '/' + matched[3], - href: 'git+ssh://' + matched[1] + '@' + matched[2] + - '/' + matched[3] + (matched[4] || '') + // now test all matched entries as stand-ins for that part + // of the pattern. + remain.shift() + for (var i = 0; i < len; i ++) { + var e = matchedEntries[i] + var newPattern + if (prefix) + newPattern = [prefix, e] + else + newPattern = [e] + this._process(newPattern.concat(remain), index, inGlobStar) } } -/***/ }), -/* 82 */ -/***/ (function(module, exports) { +GlobSync.prototype._emitMatch = function (index, e) { + if (isIgnored(this, e)) + return -module.exports = require("url"); + var abs = this._makeAbs(e) -/***/ }), -/* 83 */ -/***/ (function(module, exports, __webpack_require__) { + if (this.mark) + e = this._mark(e) -"use strict"; + if (this.absolute) { + e = abs + } + if (this.matches[index][e]) + return -var gitHosts = module.exports = { - github: { - // First two are insecure and generally shouldn't be used any more, but - // they are still supported. - 'protocols': [ 'git', 'http', 'git+ssh', 'git+https', 'ssh', 'https' ], - 'domain': 'github.com', - 'treepath': 'tree', - 'filetemplate': 'https://{auth@}raw.githubusercontent.com/{user}/{project}/{committish}/{path}', - 'bugstemplate': 'https://{domain}/{user}/{project}/issues', - 'gittemplate': 'git://{auth@}{domain}/{user}/{project}.git{#committish}', - 'tarballtemplate': 'https://{domain}/{user}/{project}/archive/{committish}.tar.gz' - }, - bitbucket: { - 'protocols': [ 'git+ssh', 'git+https', 'ssh', 'https' ], - 'domain': 'bitbucket.org', - 'treepath': 'src', - 'tarballtemplate': 'https://{domain}/{user}/{project}/get/{committish}.tar.gz' - }, - gitlab: { - 'protocols': [ 'git+ssh', 'git+https', 'ssh', 'https' ], - 'domain': 'gitlab.com', - 'treepath': 'tree', - 'docstemplate': 'https://{domain}/{user}/{project}{/tree/committish}#README', - 'bugstemplate': 'https://{domain}/{user}/{project}/issues', - 'tarballtemplate': 'https://{domain}/{user}/{project}/repository/archive.tar.gz?ref={committish}' - }, - gist: { - 'protocols': [ 'git', 'git+ssh', 'git+https', 'ssh', 'https' ], - 'domain': 'gist.github.com', - 'pathmatch': /^[/](?:([^/]+)[/])?([a-z0-9]+)(?:[.]git)?$/, - 'filetemplate': 'https://gist.githubusercontent.com/{user}/{project}/raw{/committish}/{path}', - 'bugstemplate': 'https://{domain}/{project}', - 'gittemplate': 'git://{domain}/{project}.git{#committish}', - 'sshtemplate': 'git@{domain}:/{project}.git{#committish}', - 'sshurltemplate': 'git+ssh://git@{domain}/{project}.git{#committish}', - 'browsetemplate': 'https://{domain}/{project}{/committish}', - 'docstemplate': 'https://{domain}/{project}{/committish}', - 'httpstemplate': 'git+https://{domain}/{project}.git{#committish}', - 'shortcuttemplate': '{type}:{project}{#committish}', - 'pathtemplate': '{project}{#committish}', - 'tarballtemplate': 'https://{domain}/{user}/{project}/archive/{committish}.tar.gz' + if (this.nodir) { + var c = this.cache[abs] + if (c === 'DIR' || Array.isArray(c)) + return } -} -var gitHostDefaults = { - 'sshtemplate': 'git@{domain}:{user}/{project}.git{#committish}', - 'sshurltemplate': 'git+ssh://git@{domain}/{user}/{project}.git{#committish}', - 'browsetemplate': 'https://{domain}/{user}/{project}{/tree/committish}', - 'docstemplate': 'https://{domain}/{user}/{project}{/tree/committish}#readme', - 'httpstemplate': 'git+https://{auth@}{domain}/{user}/{project}.git{#committish}', - 'filetemplate': 'https://{domain}/{user}/{project}/raw/{committish}/{path}', - 'shortcuttemplate': '{type}:{user}/{project}{#committish}', - 'pathtemplate': '{user}/{project}{#committish}', - 'pathmatch': /^[/]([^/]+)[/]([^/]+?)(?:[.]git|[/])?$/ + this.matches[index][e] = true + + if (this.stat) + this._stat(e) } -Object.keys(gitHosts).forEach(function (name) { - Object.keys(gitHostDefaults).forEach(function (key) { - if (gitHosts[name][key]) return - gitHosts[name][key] = gitHostDefaults[key] - }) - gitHosts[name].protocols_re = RegExp('^(' + - gitHosts[name].protocols.map(function (protocol) { - return protocol.replace(/([\\+*{}()[\]$^|])/g, '\\$1') - }).join('|') + '):$') -}) +GlobSync.prototype._readdirInGlobStar = function (abs) { + // follow all symlinked directories forever + // just proceed as if this is a non-globstar situation + if (this.follow) + return this._readdir(abs, false) -/***/ }), -/* 84 */ -/***/ (function(module, exports, __webpack_require__) { + var entries + var lstat + var stat + try { + lstat = fs.lstatSync(abs) + } catch (er) { + if (er.code === 'ENOENT') { + // lstat failed, doesn't exist + return null + } + } -"use strict"; + var isSym = lstat && lstat.isSymbolicLink() + this.symlinks[abs] = isSym -var gitHosts = __webpack_require__(83) -var extend = Object.assign || __webpack_require__(29)._extend + // If it's not a symlink or a dir, then it's definitely a regular file. + // don't bother doing a readdir in that case. + if (!isSym && lstat && !lstat.isDirectory()) + this.cache[abs] = 'FILE' + else + entries = this._readdir(abs, false) -var GitHost = module.exports = function (type, user, auth, project, committish, defaultRepresentation, opts) { - var gitHostInfo = this - gitHostInfo.type = type - Object.keys(gitHosts[type]).forEach(function (key) { - gitHostInfo[key] = gitHosts[type][key] - }) - gitHostInfo.user = user - gitHostInfo.auth = auth - gitHostInfo.project = project - gitHostInfo.committish = committish - gitHostInfo.default = defaultRepresentation - gitHostInfo.opts = opts || {} + return entries } -GitHost.prototype = {} -GitHost.prototype.hash = function () { - return this.committish ? '#' + this.committish : '' -} +GlobSync.prototype._readdir = function (abs, inGlobStar) { + var entries -GitHost.prototype._fill = function (template, opts) { - if (!template) return - var vars = extend({}, opts) - opts = extend(extend({}, this.opts), opts) - var self = this - Object.keys(this).forEach(function (key) { - if (self[key] != null && vars[key] == null) vars[key] = self[key] - }) - var rawAuth = vars.auth - var rawComittish = vars.committish - Object.keys(vars).forEach(function (key) { - vars[key] = encodeURIComponent(vars[key]) - }) - vars['auth@'] = rawAuth ? rawAuth + '@' : '' - if (opts.noCommittish) { - vars['#committish'] = '' - vars['/tree/committish'] = '' - vars['/comittish'] = '' - vars.comittish = '' - } else { - vars['#committish'] = rawComittish ? '#' + rawComittish : '' - vars['/tree/committish'] = vars.committish - ? '/' + vars.treepath + '/' + vars.committish - : '' - vars['/committish'] = vars.committish ? '/' + vars.committish : '' - vars.committish = vars.committish || 'master' + if (inGlobStar && !ownProp(this.symlinks, abs)) + return this._readdirInGlobStar(abs) + + if (ownProp(this.cache, abs)) { + var c = this.cache[abs] + if (!c || c === 'FILE') + return null + + if (Array.isArray(c)) + return c } - var res = template - Object.keys(vars).forEach(function (key) { - res = res.replace(new RegExp('[{]' + key + '[}]', 'g'), vars[key]) - }) - if (opts.noGitPlus) { - return res.replace(/^git[+]/, '') - } else { - return res + + try { + return this._readdirEntries(abs, fs.readdirSync(abs)) + } catch (er) { + this._readdirError(abs, er) + return null } } -GitHost.prototype.ssh = function (opts) { - return this._fill(this.sshtemplate, opts) -} +GlobSync.prototype._readdirEntries = function (abs, entries) { + // if we haven't asked to stat everything, then just + // assume that everything in there exists, so we can avoid + // having to stat it a second time. + if (!this.mark && !this.stat) { + for (var i = 0; i < entries.length; i ++) { + var e = entries[i] + if (abs === '/') + e = abs + e + else + e = abs + '/' + e + this.cache[e] = true + } + } -GitHost.prototype.sshurl = function (opts) { - return this._fill(this.sshurltemplate, opts) -} + this.cache[abs] = entries -GitHost.prototype.browse = function (opts) { - return this._fill(this.browsetemplate, opts) + // mark and cache dir-ness + return entries } -GitHost.prototype.docs = function (opts) { - return this._fill(this.docstemplate, opts) -} +GlobSync.prototype._readdirError = function (f, er) { + // handle errors, and cache the information + switch (er.code) { + case 'ENOTSUP': // https://github.com/isaacs/node-glob/issues/205 + case 'ENOTDIR': // totally normal. means it *does* exist. + var abs = this._makeAbs(f) + this.cache[abs] = 'FILE' + if (abs === this.cwdAbs) { + var error = new Error(er.code + ' invalid cwd ' + this.cwd) + error.path = this.cwd + error.code = er.code + throw error + } + break -GitHost.prototype.bugs = function (opts) { - return this._fill(this.bugstemplate, opts) -} + case 'ENOENT': // not terribly unusual + case 'ELOOP': + case 'ENAMETOOLONG': + case 'UNKNOWN': + this.cache[this._makeAbs(f)] = false + break -GitHost.prototype.https = function (opts) { - return this._fill(this.httpstemplate, opts) + default: // some unusual error. Treat as failure. + this.cache[this._makeAbs(f)] = false + if (this.strict) + throw er + if (!this.silent) + console.error('glob error', er) + break + } } -GitHost.prototype.git = function (opts) { - return this._fill(this.gittemplate, opts) -} +GlobSync.prototype._processGlobStar = function (prefix, read, abs, remain, index, inGlobStar) { -GitHost.prototype.shortcut = function (opts) { - return this._fill(this.shortcuttemplate, opts) -} + var entries = this._readdir(abs, inGlobStar) -GitHost.prototype.path = function (opts) { - return this._fill(this.pathtemplate, opts) -} + // no entries means not a dir, so it can never have matches + // foo.txt/** doesn't match foo.txt + if (!entries) + return -GitHost.prototype.tarball = function (opts) { - return this._fill(this.tarballtemplate, opts) -} + // test without the globstar, and with every child both below + // and replacing the globstar. + var remainWithoutGlobStar = remain.slice(1) + var gspref = prefix ? [ prefix ] : [] + var noGlobStar = gspref.concat(remainWithoutGlobStar) -GitHost.prototype.file = function (P, opts) { - return this._fill(this.filetemplate, extend({ - path: P.replace(/^[/]+/g, '') - }, opts)) -} + // the noGlobStar pattern exits the inGlobStar state + this._process(noGlobStar, index, false) -GitHost.prototype.getDefaultRepresentation = function () { - return this.default + var len = entries.length + var isSym = this.symlinks[abs] + + // If it's a symlink, and we're in a globstar, then stop + if (isSym && inGlobStar) + return + + for (var i = 0; i < len; i++) { + var e = entries[i] + if (e.charAt(0) === '.' && !this.dot) + continue + + // these two cases enter the inGlobStar state + var instead = gspref.concat(entries[i], remainWithoutGlobStar) + this._process(instead, index, true) + + var below = gspref.concat(entries[i], remain) + this._process(below, index, true) + } } -GitHost.prototype.toString = function (opts) { - return (this[this.default] || this.sshurl).call(this, opts) +GlobSync.prototype._processSimple = function (prefix, index) { + // XXX review this. Shouldn't it be doing the mounting etc + // before doing stat? kinda weird? + var exists = this._stat(prefix) + + if (!this.matches[index]) + this.matches[index] = Object.create(null) + + // If it doesn't exist, then just mark the lack of results + if (!exists) + return + + if (prefix && isAbsolute(prefix) && !this.nomount) { + var trail = /[\/\\]$/.test(prefix) + if (prefix.charAt(0) === '/') { + prefix = path.join(this.root, prefix) + } else { + prefix = path.resolve(this.root, prefix) + if (trail) + prefix += '/' + } + } + + if (process.platform === 'win32') + prefix = prefix.replace(/\\/g, '/') + + // Mark this as a match + this._emitMatch(index, prefix) } +// Returns either 'DIR', 'FILE', or false +GlobSync.prototype._stat = function (f) { + var abs = this._makeAbs(f) + var needDir = f.slice(-1) === '/' -/***/ }), -/* 85 */ -/***/ (function(module, exports, __webpack_require__) { + if (f.length > this.maxLength) + return false -var core = __webpack_require__(86); -var async = __webpack_require__(88); -async.core = core; -async.isCore = function isCore(x) { return core[x]; }; -async.sync = __webpack_require__(93); + if (!this.stat && ownProp(this.cache, abs)) { + var c = this.cache[abs] -exports = async; -module.exports = async; + if (Array.isArray(c)) + c = 'DIR' + // It exists, but maybe not how we need it + if (!needDir || c === 'DIR') + return c -/***/ }), -/* 86 */ -/***/ (function(module, exports, __webpack_require__) { + if (needDir && c === 'FILE') + return false -var current = (process.versions && process.versions.node && process.versions.node.split('.')) || []; + // otherwise we have to stat, because maybe c=true + // if we know it exists, but not what it is. + } -function specifierIncluded(specifier) { - var parts = specifier.split(' '); - var op = parts.length > 1 ? parts[0] : '='; - var versionParts = (parts.length > 1 ? parts[1] : parts[0]).split('.'); + var exists + var stat = this.statCache[abs] + if (!stat) { + var lstat + try { + lstat = fs.lstatSync(abs) + } catch (er) { + if (er && (er.code === 'ENOENT' || er.code === 'ENOTDIR')) { + this.statCache[abs] = false + return false + } + } - for (var i = 0; i < 3; ++i) { - var cur = Number(current[i] || 0); - var ver = Number(versionParts[i] || 0); - if (cur === ver) { - continue; // eslint-disable-line no-restricted-syntax, no-continue - } - if (op === '<') { - return cur < ver; - } else if (op === '>=') { - return cur >= ver; - } else { - return false; - } + if (lstat && lstat.isSymbolicLink()) { + try { + stat = fs.statSync(abs) + } catch (er) { + stat = lstat + } + } else { + stat = lstat } - return op === '>='; + } + + this.statCache[abs] = stat + + var c = true + if (stat) + c = stat.isDirectory() ? 'DIR' : 'FILE' + + this.cache[abs] = this.cache[abs] || c + + if (needDir && c === 'FILE') + return false + + return c } -function matchesRange(range) { - var specifiers = range.split(/ ?&& ?/); - if (specifiers.length === 0) { return false; } - for (var i = 0; i < specifiers.length; ++i) { - if (!specifierIncluded(specifiers[i])) { return false; } - } - return true; +GlobSync.prototype._mark = function (p) { + return common.mark(this, p) } -function versionIncluded(specifierValue) { - if (typeof specifierValue === 'boolean') { return specifierValue; } - if (specifierValue && typeof specifierValue === 'object') { - for (var i = 0; i < specifierValue.length; ++i) { - if (matchesRange(specifierValue[i])) { return true; } - } - return false; - } - return matchesRange(specifierValue); +GlobSync.prototype._makeAbs = function (f) { + return common.makeAbs(this, f) } -var data = __webpack_require__(87); -var core = {}; -for (var mod in data) { // eslint-disable-line no-restricted-syntax - if (Object.prototype.hasOwnProperty.call(data, mod)) { - core[mod] = versionIncluded(data[mod]); - } +/***/ }), +/* 513 */ +/***/ (function(module, exports, __webpack_require__) { + +exports.alphasort = alphasort +exports.alphasorti = alphasorti +exports.setopts = setopts +exports.ownProp = ownProp +exports.makeAbs = makeAbs +exports.finish = finish +exports.mark = mark +exports.isIgnored = isIgnored +exports.childrenIgnored = childrenIgnored + +function ownProp (obj, field) { + return Object.prototype.hasOwnProperty.call(obj, field) } -module.exports = core; +var path = __webpack_require__(16) +var minimatch = __webpack_require__(505) +var isAbsolute = __webpack_require__(511) +var Minimatch = minimatch.Minimatch -/***/ }), -/* 87 */ -/***/ (function(module) { +function alphasorti (a, b) { + return a.toLowerCase().localeCompare(b.toLowerCase()) +} -module.exports = JSON.parse("{\"assert\":true,\"async_hooks\":\">= 8\",\"buffer_ieee754\":\"< 0.9.7\",\"buffer\":true,\"child_process\":true,\"cluster\":true,\"console\":true,\"constants\":true,\"crypto\":true,\"_debugger\":\"< 8\",\"dgram\":true,\"dns\":true,\"domain\":true,\"events\":true,\"freelist\":\"< 6\",\"fs\":true,\"fs/promises\":\">= 10 && < 10.1\",\"_http_agent\":\">= 0.11.1\",\"_http_client\":\">= 0.11.1\",\"_http_common\":\">= 0.11.1\",\"_http_incoming\":\">= 0.11.1\",\"_http_outgoing\":\">= 0.11.1\",\"_http_server\":\">= 0.11.1\",\"http\":true,\"http2\":\">= 8.8\",\"https\":true,\"inspector\":\">= 8.0.0\",\"_linklist\":\"< 8\",\"module\":true,\"net\":true,\"node-inspect/lib/_inspect\":\">= 7.6.0\",\"node-inspect/lib/internal/inspect_client\":\">= 7.6.0\",\"node-inspect/lib/internal/inspect_repl\":\">= 7.6.0\",\"os\":true,\"path\":true,\"perf_hooks\":\">= 8.5\",\"process\":\">= 1\",\"punycode\":true,\"querystring\":true,\"readline\":true,\"repl\":true,\"smalloc\":\">= 0.11.5 && < 3\",\"_stream_duplex\":\">= 0.9.4\",\"_stream_transform\":\">= 0.9.4\",\"_stream_wrap\":\">= 1.4.1\",\"_stream_passthrough\":\">= 0.9.4\",\"_stream_readable\":\">= 0.9.4\",\"_stream_writable\":\">= 0.9.4\",\"stream\":true,\"string_decoder\":true,\"sys\":true,\"timers\":true,\"_tls_common\":\">= 0.11.13\",\"_tls_legacy\":\">= 0.11.3 && < 10\",\"_tls_wrap\":\">= 0.11.3\",\"tls\":true,\"trace_events\":\">= 10\",\"tty\":true,\"url\":true,\"util\":true,\"v8/tools/arguments\":\">= 10\",\"v8/tools/codemap\":[\">= 4.4.0 && < 5\",\">= 5.2.0\"],\"v8/tools/consarray\":[\">= 4.4.0 && < 5\",\">= 5.2.0\"],\"v8/tools/csvparser\":[\">= 4.4.0 && < 5\",\">= 5.2.0\"],\"v8/tools/logreader\":[\">= 4.4.0 && < 5\",\">= 5.2.0\"],\"v8/tools/profile_view\":[\">= 4.4.0 && < 5\",\">= 5.2.0\"],\"v8/tools/splaytree\":[\">= 4.4.0 && < 5\",\">= 5.2.0\"],\"v8\":\">= 1\",\"vm\":true,\"worker_threads\":\">= 11.7\",\"zlib\":true}"); +function alphasort (a, b) { + return a.localeCompare(b) +} -/***/ }), -/* 88 */ -/***/ (function(module, exports, __webpack_require__) { +function setupIgnores (self, options) { + self.ignore = options.ignore || [] -var core = __webpack_require__(86); -var fs = __webpack_require__(23); -var path = __webpack_require__(16); -var caller = __webpack_require__(89); -var nodeModulesPaths = __webpack_require__(90); -var normalizeOptions = __webpack_require__(92); + if (!Array.isArray(self.ignore)) + self.ignore = [self.ignore] -var defaultIsFile = function isFile(file, cb) { - fs.stat(file, function (err, stat) { - if (!err) { - return cb(null, stat.isFile() || stat.isFIFO()); - } - if (err.code === 'ENOENT' || err.code === 'ENOTDIR') return cb(null, false); - return cb(err); - }); -}; + if (self.ignore.length) { + self.ignore = self.ignore.map(ignoreMap) + } +} -module.exports = function resolve(x, options, callback) { - var cb = callback; - var opts = options; - if (typeof options === 'function') { - cb = opts; - opts = {}; - } - if (typeof x !== 'string') { - var err = new TypeError('Path must be a string.'); - return process.nextTick(function () { - cb(err); - }); - } +// ignore patterns are always in dot:true mode. +function ignoreMap (pattern) { + var gmatcher = null + if (pattern.slice(-3) === '/**') { + var gpattern = pattern.replace(/(\/\*\*)+$/, '') + gmatcher = new Minimatch(gpattern, { dot: true }) + } - opts = normalizeOptions(x, opts); + return { + matcher: new Minimatch(pattern, { dot: true }), + gmatcher: gmatcher + } +} - var isFile = opts.isFile || defaultIsFile; - var readFile = opts.readFile || fs.readFile; +function setopts (self, pattern, options) { + if (!options) + options = {} - var extensions = opts.extensions || ['.js']; - var basedir = opts.basedir || path.dirname(caller()); - var parent = opts.filename || basedir; + // base-matching: just use globstar for that. + if (options.matchBase && -1 === pattern.indexOf("/")) { + if (options.noglobstar) { + throw new Error("base matching requires globstar") + } + pattern = "**/" + pattern + } - opts.paths = opts.paths || []; + self.silent = !!options.silent + self.pattern = pattern + self.strict = options.strict !== false + self.realpath = !!options.realpath + self.realpathCache = options.realpathCache || Object.create(null) + self.follow = !!options.follow + self.dot = !!options.dot + self.mark = !!options.mark + self.nodir = !!options.nodir + if (self.nodir) + self.mark = true + self.sync = !!options.sync + self.nounique = !!options.nounique + self.nonull = !!options.nonull + self.nosort = !!options.nosort + self.nocase = !!options.nocase + self.stat = !!options.stat + self.noprocess = !!options.noprocess + self.absolute = !!options.absolute - // ensure that `basedir` is an absolute path at this point, resolving against the process' current working directory - var absoluteStart = path.resolve(basedir); + self.maxLength = options.maxLength || Infinity + self.cache = options.cache || Object.create(null) + self.statCache = options.statCache || Object.create(null) + self.symlinks = options.symlinks || Object.create(null) - if (opts.preserveSymlinks === false) { - fs.realpath(absoluteStart, function (realPathErr, realStart) { - if (realPathErr && realPathErr.code !== 'ENOENT') cb(err); - else init(realPathErr ? absoluteStart : realStart); - }); - } else { - init(absoluteStart); - } + setupIgnores(self, options) - var res; - function init(basedir) { - if ((/^(?:\.\.?(?:\/|$)|\/|([A-Za-z]:)?[/\\])/).test(x)) { - res = path.resolve(basedir, x); - if (x === '..' || x.slice(-1) === '/') res += '/'; - if ((/\/$/).test(x) && res === basedir) { - loadAsDirectory(res, opts.package, onfile); - } else loadAsFile(res, opts.package, onfile); - } else loadNodeModules(x, basedir, function (err, n, pkg) { - if (err) cb(err); - else if (n) cb(null, n, pkg); - else if (core[x]) return cb(null, x); - else { - var moduleError = new Error("Cannot find module '" + x + "' from '" + parent + "'"); - moduleError.code = 'MODULE_NOT_FOUND'; - cb(moduleError); - } - }); - } + self.changedCwd = false + var cwd = process.cwd() + if (!ownProp(options, "cwd")) + self.cwd = cwd + else { + self.cwd = path.resolve(options.cwd) + self.changedCwd = self.cwd !== cwd + } - function onfile(err, m, pkg) { - if (err) cb(err); - else if (m) cb(null, m, pkg); - else loadAsDirectory(res, function (err, d, pkg) { - if (err) cb(err); - else if (d) cb(null, d, pkg); - else { - var moduleError = new Error("Cannot find module '" + x + "' from '" + parent + "'"); - moduleError.code = 'MODULE_NOT_FOUND'; - cb(moduleError); - } - }); - } + self.root = options.root || path.resolve(self.cwd, "/") + self.root = path.resolve(self.root) + if (process.platform === "win32") + self.root = self.root.replace(/\\/g, "/") - function loadAsFile(x, thePackage, callback) { - var loadAsFilePackage = thePackage; - var cb = callback; - if (typeof loadAsFilePackage === 'function') { - cb = loadAsFilePackage; - loadAsFilePackage = undefined; - } + // TODO: is an absolute `cwd` supposed to be resolved against `root`? + // e.g. { cwd: '/test', root: __dirname } === path.join(__dirname, '/test') + self.cwdAbs = isAbsolute(self.cwd) ? self.cwd : makeAbs(self, self.cwd) + if (process.platform === "win32") + self.cwdAbs = self.cwdAbs.replace(/\\/g, "/") + self.nomount = !!options.nomount - var exts = [''].concat(extensions); - load(exts, x, loadAsFilePackage); + // disable comments and negation in Minimatch. + // Note that they are not supported in Glob itself anyway. + options.nonegate = true + options.nocomment = true - function load(exts, x, loadPackage) { - if (exts.length === 0) return cb(null, undefined, loadPackage); - var file = x + exts[0]; + self.minimatch = new Minimatch(pattern, options) + self.options = self.minimatch.options +} - var pkg = loadPackage; - if (pkg) onpkg(null, pkg); - else loadpkg(path.dirname(file), onpkg); +function finish (self) { + var nou = self.nounique + var all = nou ? [] : Object.create(null) - function onpkg(err, pkg_, dir) { - pkg = pkg_; - if (err) return cb(err); - if (dir && pkg && opts.pathFilter) { - var rfile = path.relative(dir, file); - var rel = rfile.slice(0, rfile.length - exts[0].length); - var r = opts.pathFilter(pkg, x, rel); - if (r) return load( - [''].concat(extensions.slice()), - path.resolve(dir, r), - pkg - ); - } - isFile(file, onex); - } - function onex(err, ex) { - if (err) return cb(err); - if (ex) return cb(null, file, pkg); - load(exts.slice(1), x, pkg); - } - } + for (var i = 0, l = self.matches.length; i < l; i ++) { + var matches = self.matches[i] + if (!matches || Object.keys(matches).length === 0) { + if (self.nonull) { + // do like the shell, and spit out the literal glob + var literal = self.minimatch.globSet[i] + if (nou) + all.push(literal) + else + all[literal] = true + } + } else { + // had matches + var m = Object.keys(matches) + if (nou) + all.push.apply(all, m) + else + m.forEach(function (m) { + all[m] = true + }) } + } - function loadpkg(dir, cb) { - if (dir === '' || dir === '/') return cb(null); - if (process.platform === 'win32' && (/^\w:[/\\]*$/).test(dir)) { - return cb(null); - } - if ((/[/\\]node_modules[/\\]*$/).test(dir)) return cb(null); - - var pkgfile = path.join(dir, 'package.json'); - isFile(pkgfile, function (err, ex) { - // on err, ex is false - if (!ex) return loadpkg(path.dirname(dir), cb); + if (!nou) + all = Object.keys(all) - readFile(pkgfile, function (err, body) { - if (err) cb(err); - try { var pkg = JSON.parse(body); } catch (jsonErr) {} + if (!self.nosort) + all = all.sort(self.nocase ? alphasorti : alphasort) - if (pkg && opts.packageFilter) { - pkg = opts.packageFilter(pkg, pkgfile); - } - cb(null, pkg, dir); - }); - }); + // at *some* point we statted all of these + if (self.mark) { + for (var i = 0; i < all.length; i++) { + all[i] = self._mark(all[i]) + } + if (self.nodir) { + all = all.filter(function (e) { + var notDir = !(/\/$/.test(e)) + var c = self.cache[e] || self.cache[makeAbs(self, e)] + if (notDir && c) + notDir = c !== 'DIR' && !Array.isArray(c) + return notDir + }) } + } - function loadAsDirectory(x, loadAsDirectoryPackage, callback) { - var cb = callback; - var fpkg = loadAsDirectoryPackage; - if (typeof fpkg === 'function') { - cb = fpkg; - fpkg = opts.package; - } + if (self.ignore.length) + all = all.filter(function(m) { + return !isIgnored(self, m) + }) - var pkgfile = path.join(x, 'package.json'); - isFile(pkgfile, function (err, ex) { - if (err) return cb(err); - if (!ex) return loadAsFile(path.join(x, 'index'), fpkg, cb); + self.found = all +} - readFile(pkgfile, function (err, body) { - if (err) return cb(err); - try { - var pkg = JSON.parse(body); - } catch (jsonErr) {} +function mark (self, p) { + var abs = makeAbs(self, p) + var c = self.cache[abs] + var m = p + if (c) { + var isDir = c === 'DIR' || Array.isArray(c) + var slash = p.slice(-1) === '/' - if (opts.packageFilter) { - pkg = opts.packageFilter(pkg, pkgfile); - } + if (isDir && !slash) + m += '/' + else if (!isDir && slash) + m = m.slice(0, -1) - if (pkg.main) { - if (typeof pkg.main !== 'string') { - var mainError = new TypeError('package “' + pkg.name + '” `main` must be a string'); - mainError.code = 'INVALID_PACKAGE_MAIN'; - return cb(mainError); - } - if (pkg.main === '.' || pkg.main === './') { - pkg.main = 'index'; - } - loadAsFile(path.resolve(x, pkg.main), pkg, function (err, m, pkg) { - if (err) return cb(err); - if (m) return cb(null, m, pkg); - if (!pkg) return loadAsFile(path.join(x, 'index'), pkg, cb); + if (m !== p) { + var mabs = makeAbs(self, m) + self.statCache[mabs] = self.statCache[abs] + self.cache[mabs] = self.cache[abs] + } + } - var dir = path.resolve(x, pkg.main); - loadAsDirectory(dir, pkg, function (err, n, pkg) { - if (err) return cb(err); - if (n) return cb(null, n, pkg); - loadAsFile(path.join(x, 'index'), pkg, cb); - }); - }); - return; - } + return m +} - loadAsFile(path.join(x, '/index'), pkg, cb); - }); - }); - } +// lotta situps... +function makeAbs (self, f) { + var abs = f + if (f.charAt(0) === '/') { + abs = path.join(self.root, f) + } else if (isAbsolute(f) || f === '') { + abs = f + } else if (self.changedCwd) { + abs = path.resolve(self.cwd, f) + } else { + abs = path.resolve(f) + } - function processDirs(cb, dirs) { - if (dirs.length === 0) return cb(null, undefined); - var dir = dirs[0]; + if (process.platform === 'win32') + abs = abs.replace(/\\/g, '/') - var file = path.join(dir, x); - loadAsFile(file, opts.package, onfile); + return abs +} - function onfile(err, m, pkg) { - if (err) return cb(err); - if (m) return cb(null, m, pkg); - loadAsDirectory(path.join(dir, x), opts.package, ondir); - } - function ondir(err, n, pkg) { - if (err) return cb(err); - if (n) return cb(null, n, pkg); - processDirs(cb, dirs.slice(1)); - } - } - function loadNodeModules(x, start, cb) { - processDirs(cb, nodeModulesPaths(start, opts, x)); - } -}; +// Return true, if pattern ends with globstar '**', for the accompanying parent directory. +// Ex:- If node_modules/** is the pattern, add 'node_modules' to ignore list along with it's contents +function isIgnored (self, path) { + if (!self.ignore.length) + return false + return self.ignore.some(function(item) { + return item.matcher.match(path) || !!(item.gmatcher && item.gmatcher.match(path)) + }) +} -/***/ }), -/* 89 */ -/***/ (function(module, exports) { +function childrenIgnored (self, path) { + if (!self.ignore.length) + return false -module.exports = function () { - // see https://code.google.com/p/v8/wiki/JavaScriptStackTraceApi - var origPrepareStackTrace = Error.prepareStackTrace; - Error.prepareStackTrace = function (_, stack) { return stack; }; - var stack = (new Error()).stack; - Error.prepareStackTrace = origPrepareStackTrace; - return stack[2].getFileName(); -}; + return self.ignore.some(function(item) { + return !!(item.gmatcher && item.gmatcher.match(path)) + }) +} /***/ }), -/* 90 */ +/* 514 */ /***/ (function(module, exports, __webpack_require__) { -var path = __webpack_require__(16); -var parse = path.parse || __webpack_require__(91); - -var getNodeModulesDirs = function getNodeModulesDirs(absoluteStart, modules) { - var prefix = '/'; - if ((/^([A-Za-z]:)/).test(absoluteStart)) { - prefix = ''; - } else if ((/^\\\\/).test(absoluteStart)) { - prefix = '\\\\'; - } +var wrappy = __webpack_require__(386) +var reqs = Object.create(null) +var once = __webpack_require__(385) - var paths = [absoluteStart]; - var parsed = parse(absoluteStart); - while (parsed.dir !== paths[paths.length - 1]) { - paths.push(parsed.dir); - parsed = parse(parsed.dir); - } +module.exports = wrappy(inflight) - return paths.reduce(function (dirs, aPath) { - return dirs.concat(modules.map(function (moduleDir) { - return path.join(prefix, aPath, moduleDir); - })); - }, []); -}; +function inflight (key, cb) { + if (reqs[key]) { + reqs[key].push(cb) + return null + } else { + reqs[key] = [cb] + return makeres(key) + } +} -module.exports = function nodeModulesPaths(start, opts, request) { - var modules = opts && opts.moduleDirectory - ? [].concat(opts.moduleDirectory) - : ['node_modules']; +function makeres (key) { + return once(function RES () { + var cbs = reqs[key] + var len = cbs.length + var args = slice(arguments) - if (opts && typeof opts.paths === 'function') { - return opts.paths( - request, - start, - function () { return getNodeModulesDirs(start, modules); }, - opts - ); + // XXX It's somewhat ambiguous whether a new callback added in this + // pass should be queued for later execution if something in the + // list of callbacks throws, or if it should just be discarded. + // However, it's such an edge case that it hardly matters, and either + // choice is likely as surprising as the other. + // As it happens, we do go ahead and schedule it for later execution. + try { + for (var i = 0; i < len; i++) { + cbs[i].apply(null, args) + } + } finally { + if (cbs.length > len) { + // added more in the interim. + // de-zalgo, just in case, but don't call again. + cbs.splice(0, len) + process.nextTick(function () { + RES.apply(null, args) + }) + } else { + delete reqs[key] + } } + }) +} - var dirs = getNodeModulesDirs(start, modules); - return opts && opts.paths ? dirs.concat(opts.paths) : dirs; -}; +function slice (args) { + var length = args.length + var array = [] + + for (var i = 0; i < length; i++) array[i] = args[i] + return array +} /***/ }), -/* 91 */ -/***/ (function(module, exports, __webpack_require__) { +/* 515 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "CliError", function() { return CliError; }); +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +class CliError extends Error { + constructor(message, meta = {}) { + super(message); + this.meta = meta; + } +} -var isWindows = process.platform === 'win32'; - -// Regex to split a windows path into three parts: [*, device, slash, -// tail] windows-only -var splitDeviceRe = - /^([a-zA-Z]:|[\\\/]{2}[^\\\/]+[\\\/]+[^\\\/]+)?([\\\/])?([\s\S]*?)$/; +/***/ }), +/* 516 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { -// Regex to split the tail part of the above into [*, dir, basename, ext] -var splitTailRe = - /^([\s\S]*?)((?:\.{1,2}|[^\\\/]+?|)(\.[^.\/\\]*|))(?:[\\\/]*)$/; +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Project", function() { return Project; }); +/* harmony import */ var chalk__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2); +/* harmony import */ var chalk__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(chalk__WEBPACK_IMPORTED_MODULE_0__); +/* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(23); +/* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(fs__WEBPACK_IMPORTED_MODULE_1__); +/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(16); +/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_2__); +/* harmony import */ var util__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(29); +/* harmony import */ var util__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(util__WEBPACK_IMPORTED_MODULE_3__); +/* harmony import */ var _errors__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(515); +/* harmony import */ var _log__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(34); +/* harmony import */ var _package_json__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(517); +/* harmony import */ var _scripts__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(564); +function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } -var win32 = {}; +function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(source, true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(source).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } -// Function to split a filename into [root, dir, basename, ext] -function win32SplitPath(filename) { - // Separate device+slash from tail - var result = splitDeviceRe.exec(filename), - device = (result[1] || '') + (result[2] || ''), - tail = result[3] || ''; - // Split the tail into dir, basename and extension - var result2 = splitTailRe.exec(tail), - dir = result2[1], - basename = result2[2], - ext = result2[3]; - return [device, dir, basename, ext]; -} +function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } -win32.parse = function(pathString) { - if (typeof pathString !== 'string') { - throw new TypeError( - "Parameter 'pathString' must be a string, not " + typeof pathString - ); - } - var allParts = win32SplitPath(pathString); - if (!allParts || allParts.length !== 4) { - throw new TypeError("Invalid path '" + pathString + "'"); - } - return { - root: allParts[0], - dir: allParts[0] + allParts[1].slice(0, -1), - base: allParts[2], - ext: allParts[3], - name: allParts[2].slice(0, allParts[2].length - allParts[3].length) - }; -}; +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ -// Split a filename into [root, dir, basename, ext], unix version -// 'root' is just a slash, or nothing. -var splitPathRe = - /^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/; -var posix = {}; -function posixSplitPath(filename) { - return splitPathRe.exec(filename).slice(1); -} -posix.parse = function(pathString) { - if (typeof pathString !== 'string') { - throw new TypeError( - "Parameter 'pathString' must be a string, not " + typeof pathString - ); - } - var allParts = posixSplitPath(pathString); - if (!allParts || allParts.length !== 4) { - throw new TypeError("Invalid path '" + pathString + "'"); + +class Project { + static async fromPath(path) { + const pkgJson = await Object(_package_json__WEBPACK_IMPORTED_MODULE_6__["readPackageJson"])(path); + return new Project(pkgJson, path); } - allParts[1] = allParts[1] || ''; - allParts[2] = allParts[2] || ''; - allParts[3] = allParts[3] || ''; + /** parsed package.json */ - return { - root: allParts[0], - dir: allParts[0] + allParts[1].slice(0, -1), - base: allParts[2], - ext: allParts[3], - name: allParts[2].slice(0, allParts[2].length - allParts[3].length) - }; -}; + constructor(packageJson, projectPath) { + _defineProperty(this, "json", void 0); -if (isWindows) - module.exports = win32.parse; -else /* posix */ - module.exports = posix.parse; + _defineProperty(this, "packageJsonLocation", void 0); -module.exports.posix = posix.parse; -module.exports.win32 = win32.parse; + _defineProperty(this, "nodeModulesLocation", void 0); + _defineProperty(this, "targetLocation", void 0); -/***/ }), -/* 92 */ -/***/ (function(module, exports) { + _defineProperty(this, "path", void 0); -module.exports = function (x, opts) { - /** - * This file is purposefully a passthrough. It's expected that third-party - * environments will override it at runtime in order to inject special logic - * into `resolve` (by manipulating the options). One such example is the PnP - * code path in Yarn. - */ + _defineProperty(this, "version", void 0); - return opts || {}; -}; + _defineProperty(this, "allDependencies", void 0); + _defineProperty(this, "productionDependencies", void 0); -/***/ }), -/* 93 */ -/***/ (function(module, exports, __webpack_require__) { + _defineProperty(this, "devDependencies", void 0); -var core = __webpack_require__(86); -var fs = __webpack_require__(23); -var path = __webpack_require__(16); -var caller = __webpack_require__(89); -var nodeModulesPaths = __webpack_require__(90); -var normalizeOptions = __webpack_require__(92); + _defineProperty(this, "scripts", void 0); -var defaultIsFile = function isFile(file) { - try { - var stat = fs.statSync(file); - } catch (e) { - if (e && (e.code === 'ENOENT' || e.code === 'ENOTDIR')) return false; - throw e; - } - return stat.isFile() || stat.isFIFO(); -}; + _defineProperty(this, "isWorkspaceRoot", false); -module.exports = function (x, options) { - if (typeof x !== 'string') { - throw new TypeError('Path must be a string.'); - } - var opts = normalizeOptions(x, options); + _defineProperty(this, "isWorkspaceProject", false); - var isFile = opts.isFile || defaultIsFile; - var readFileSync = opts.readFileSync || fs.readFileSync; + this.json = Object.freeze(packageJson); + this.path = projectPath; + this.packageJsonLocation = path__WEBPACK_IMPORTED_MODULE_2___default.a.resolve(this.path, 'package.json'); + this.nodeModulesLocation = path__WEBPACK_IMPORTED_MODULE_2___default.a.resolve(this.path, 'node_modules'); + this.targetLocation = path__WEBPACK_IMPORTED_MODULE_2___default.a.resolve(this.path, 'target'); + this.version = this.json.version; + this.productionDependencies = this.json.dependencies || {}; + this.devDependencies = this.json.devDependencies || {}; + this.allDependencies = _objectSpread({}, this.devDependencies, {}, this.productionDependencies); + this.isWorkspaceRoot = this.json.hasOwnProperty('workspaces'); + this.scripts = this.json.scripts || {}; + } - var extensions = opts.extensions || ['.js']; - var basedir = opts.basedir || path.dirname(caller()); - var parent = opts.filename || basedir; + get name() { + return this.json.name; + } - opts.paths = opts.paths || []; + ensureValidProjectDependency(project, dependentProjectIsInWorkspace) { + const versionInPackageJson = this.allDependencies[project.name]; + let expectedVersionInPackageJson; - // ensure that `basedir` is an absolute path at this point, resolving against the process' current working directory - var absoluteStart = path.resolve(basedir); + if (dependentProjectIsInWorkspace) { + expectedVersionInPackageJson = project.json.version; + } else { + const relativePathToProject = normalizePath(path__WEBPACK_IMPORTED_MODULE_2___default.a.relative(this.path, project.path)); + expectedVersionInPackageJson = `link:${relativePathToProject}`; + } // No issues! - if (opts.preserveSymlinks === false) { - try { - absoluteStart = fs.realpathSync(absoluteStart); - } catch (realPathErr) { - if (realPathErr.code !== 'ENOENT') { - throw realPathErr; - } - } + + if (versionInPackageJson === expectedVersionInPackageJson) { + return; } - if ((/^(?:\.\.?(?:\/|$)|\/|([A-Za-z]:)?[/\\])/).test(x)) { - var res = path.resolve(absoluteStart, x); - if (x === '..' || x.slice(-1) === '/') res += '/'; - var m = loadAsFileSync(res) || loadAsDirectorySync(res); - if (m) return m; + let problemMsg; + + if (Object(_package_json__WEBPACK_IMPORTED_MODULE_6__["isLinkDependency"])(versionInPackageJson) && dependentProjectIsInWorkspace) { + problemMsg = `but should be using a workspace`; + } else if (Object(_package_json__WEBPACK_IMPORTED_MODULE_6__["isLinkDependency"])(versionInPackageJson)) { + problemMsg = `using 'link:', but the path is wrong`; } else { - var n = loadNodeModulesSync(x, absoluteStart); - if (n) return n; + problemMsg = `but it's not using the local package`; } - if (core[x]) return x; + throw new _errors__WEBPACK_IMPORTED_MODULE_4__["CliError"](`[${this.name}] depends on [${project.name}] ${problemMsg}. Update its package.json to the expected value below.`, { + actual: `"${project.name}": "${versionInPackageJson}"`, + expected: `"${project.name}": "${expectedVersionInPackageJson}"`, + package: `${this.name} (${this.packageJsonLocation})` + }); + } - var err = new Error("Cannot find module '" + x + "' from '" + parent + "'"); - err.code = 'MODULE_NOT_FOUND'; - throw err; + getBuildConfig() { + return this.json.kibana && this.json.kibana.build || {}; + } + /** + * Returns the directory that should be copied into the Kibana build artifact. + * This config can be specified to only include the project's build artifacts + * instead of everything located in the project directory. + */ - function loadAsFileSync(x) { - var pkg = loadpkg(path.dirname(x)); - if (pkg && pkg.dir && pkg.pkg && opts.pathFilter) { - var rfile = path.relative(pkg.dir, x); - var r = opts.pathFilter(pkg.pkg, x, rfile); - if (r) { - x = path.resolve(pkg.dir, r); // eslint-disable-line no-param-reassign - } - } + getIntermediateBuildDirectory() { + return path__WEBPACK_IMPORTED_MODULE_2___default.a.resolve(this.path, this.getBuildConfig().intermediateBuildDirectory || '.'); + } - if (isFile(x)) { - return x; - } + getCleanConfig() { + return this.json.kibana && this.json.kibana.clean || {}; + } - for (var i = 0; i < extensions.length; i++) { - var file = x + extensions[i]; - if (isFile(file)) { - return file; - } - } + hasScript(name) { + return name in this.scripts; + } + + getExecutables() { + const raw = this.json.bin; + + if (!raw) { + return {}; } - function loadpkg(dir) { - if (dir === '' || dir === '/') return; - if (process.platform === 'win32' && (/^\w:[/\\]*$/).test(dir)) { - return; - } - if ((/[/\\]node_modules[/\\]*$/).test(dir)) return; + if (typeof raw === 'string') { + return { + [this.name]: path__WEBPACK_IMPORTED_MODULE_2___default.a.resolve(this.path, raw) + }; + } - var pkgfile = path.join(dir, 'package.json'); + if (typeof raw === 'object') { + const binsConfig = {}; - if (!isFile(pkgfile)) { - return loadpkg(path.dirname(dir)); - } + for (const binName of Object.keys(raw)) { + binsConfig[binName] = path__WEBPACK_IMPORTED_MODULE_2___default.a.resolve(this.path, raw[binName]); + } - var body = readFileSync(pkgfile); + return binsConfig; + } - try { - var pkg = JSON.parse(body); - } catch (jsonErr) {} + throw new _errors__WEBPACK_IMPORTED_MODULE_4__["CliError"](`[${this.name}] has an invalid "bin" field in its package.json, ` + `expected an object or a string`, { + binConfig: Object(util__WEBPACK_IMPORTED_MODULE_3__["inspect"])(raw), + package: `${this.name} (${this.packageJsonLocation})` + }); + } - if (pkg && opts.packageFilter) { - pkg = opts.packageFilter(pkg, dir); - } + async runScript(scriptName, args = []) { + _log__WEBPACK_IMPORTED_MODULE_5__["log"].write(chalk__WEBPACK_IMPORTED_MODULE_0___default.a.bold(`\n\nRunning script [${chalk__WEBPACK_IMPORTED_MODULE_0___default.a.green(scriptName)}] in [${chalk__WEBPACK_IMPORTED_MODULE_0___default.a.green(this.name)}]:\n`)); + return Object(_scripts__WEBPACK_IMPORTED_MODULE_7__["runScriptInPackage"])(scriptName, args, this); + } - return { pkg: pkg, dir: dir }; - } + runScriptStreaming(scriptName, args = []) { + return Object(_scripts__WEBPACK_IMPORTED_MODULE_7__["runScriptInPackageStreaming"])(scriptName, args, this); + } - function loadAsDirectorySync(x) { - var pkgfile = path.join(x, '/package.json'); - if (isFile(pkgfile)) { - try { - var body = readFileSync(pkgfile, 'UTF8'); - var pkg = JSON.parse(body); - } catch (e) {} + hasDependencies() { + return Object.keys(this.allDependencies).length > 0; + } - if (opts.packageFilter) { - pkg = opts.packageFilter(pkg, x); - } + async installDependencies({ + extraArgs + }) { + _log__WEBPACK_IMPORTED_MODULE_5__["log"].write(chalk__WEBPACK_IMPORTED_MODULE_0___default.a.bold(`\n\nInstalling dependencies in [${chalk__WEBPACK_IMPORTED_MODULE_0___default.a.green(this.name)}]:\n`)); + await Object(_scripts__WEBPACK_IMPORTED_MODULE_7__["installInDir"])(this.path, extraArgs); + await this.removeExtraneousNodeModules(); + } + /** + * Yarn workspaces symlinks workspace projects to the root node_modules, even + * when there is no depenency on the project. This results in unnecicary, and + * often duplicated code in the build archives. + */ - if (pkg.main) { - if (typeof pkg.main !== 'string') { - var mainError = new TypeError('package “' + pkg.name + '” `main` must be a string'); - mainError.code = 'INVALID_PACKAGE_MAIN'; - throw mainError; - } - if (pkg.main === '.' || pkg.main === './') { - pkg.main = 'index'; - } - try { - var m = loadAsFileSync(path.resolve(x, pkg.main)); - if (m) return m; - var n = loadAsDirectorySync(path.resolve(x, pkg.main)); - if (n) return n; - } catch (e) {} - } - } - return loadAsFileSync(path.join(x, '/index')); + async removeExtraneousNodeModules() { + // this is only relevant for the root workspace + if (!this.isWorkspaceRoot) { + return; } - function loadNodeModulesSync(x, start) { - var dirs = nodeModulesPaths(start, opts, x); - for (var i = 0; i < dirs.length; i++) { - var dir = dirs[i]; - var m = loadAsFileSync(path.join(dir, '/', x)); - if (m) return m; - var n = loadAsDirectorySync(path.join(dir, '/', x)); - if (n) return n; - } + const workspacesInfo = await Object(_scripts__WEBPACK_IMPORTED_MODULE_7__["yarnWorkspacesInfo"])(this.path); + const unusedWorkspaces = new Set(Object.keys(workspacesInfo)); // check for any cross-project dependency + + for (const name of Object.keys(workspacesInfo)) { + const workspace = workspacesInfo[name]; + workspace.workspaceDependencies.forEach(w => unusedWorkspaces.delete(w)); } -}; + unusedWorkspaces.forEach(name => { + const { + dependencies, + devDependencies + } = this.json; + const nodeModulesPath = path__WEBPACK_IMPORTED_MODULE_2___default.a.resolve(this.nodeModulesLocation, name); + const isDependency = dependencies && dependencies.hasOwnProperty(name); + const isDevDependency = devDependencies && devDependencies.hasOwnProperty(name); -/***/ }), -/* 94 */ -/***/ (function(module, exports) { + if (!isDependency && !isDevDependency && fs__WEBPACK_IMPORTED_MODULE_1___default.a.existsSync(nodeModulesPath)) { + _log__WEBPACK_IMPORTED_MODULE_5__["log"].write(`No dependency on ${name}, removing link in node_modules`); + fs__WEBPACK_IMPORTED_MODULE_1___default.a.unlinkSync(nodeModulesPath); + } + }); + } -module.exports = extractDescription +} // We normalize all path separators to `/` in generated files -// Extracts description from contents of a readme file in markdown format -function extractDescription (d) { - if (!d) return; - if (d === "ERROR: No README data found!") return; - // the first block of text before the first heading - // that isn't the first line heading - d = d.trim().split('\n') - for (var s = 0; d[s] && d[s].trim().match(/^(#|$)/); s ++); - var l = d.length - for (var e = s + 1; e < l && d[e].trim(); e ++); - return d.slice(s, e).join(' ').trim() +function normalizePath(path) { + return path.replace(/[\\\/]+/g, '/'); } - /***/ }), -/* 95 */ -/***/ (function(module) { - -module.exports = JSON.parse("{\"topLevel\":{\"dependancies\":\"dependencies\",\"dependecies\":\"dependencies\",\"depdenencies\":\"dependencies\",\"devEependencies\":\"devDependencies\",\"depends\":\"dependencies\",\"dev-dependencies\":\"devDependencies\",\"devDependences\":\"devDependencies\",\"devDepenencies\":\"devDependencies\",\"devdependencies\":\"devDependencies\",\"repostitory\":\"repository\",\"repo\":\"repository\",\"prefereGlobal\":\"preferGlobal\",\"hompage\":\"homepage\",\"hampage\":\"homepage\",\"autohr\":\"author\",\"autor\":\"author\",\"contributers\":\"contributors\",\"publicationConfig\":\"publishConfig\",\"script\":\"scripts\"},\"bugs\":{\"web\":\"url\",\"name\":\"url\"},\"script\":{\"server\":\"start\",\"tests\":\"test\"}}"); +/* 517 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { -/***/ }), -/* 96 */ -/***/ (function(module, exports, __webpack_require__) { +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "readPackageJson", function() { return readPackageJson; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "writePackageJson", function() { return writePackageJson; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "isLinkDependency", function() { return isLinkDependency; }); +/* harmony import */ var read_pkg__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(518); +/* harmony import */ var read_pkg__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(read_pkg__WEBPACK_IMPORTED_MODULE_0__); +/* harmony import */ var write_pkg__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(544); +/* harmony import */ var write_pkg__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(write_pkg__WEBPACK_IMPORTED_MODULE_1__); +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ -var util = __webpack_require__(29) -var messages = __webpack_require__(97) -module.exports = function() { - var args = Array.prototype.slice.call(arguments, 0) - var warningName = args.shift() - if (warningName == "typo") { - return makeTypoWarning.apply(null,args) - } - else { - var msgTemplate = messages[warningName] ? messages[warningName] : warningName + ": '%s'" - args.unshift(msgTemplate) - return util.format.apply(null, args) - } +function readPackageJson(cwd) { + return read_pkg__WEBPACK_IMPORTED_MODULE_0___default()({ + cwd, + normalize: false + }); } - -function makeTypoWarning (providedName, probableName, field) { - if (field) { - providedName = field + "['" + providedName + "']" - probableName = field + "['" + probableName + "']" - } - return util.format(messages.typo, providedName, probableName) +function writePackageJson(path, json) { + return write_pkg__WEBPACK_IMPORTED_MODULE_1___default()(path, json); } - - -/***/ }), -/* 97 */ -/***/ (function(module) { - -module.exports = JSON.parse("{\"repositories\":\"'repositories' (plural) Not supported. Please pick one as the 'repository' field\",\"missingRepository\":\"No repository field.\",\"brokenGitUrl\":\"Probably broken git url: %s\",\"nonObjectScripts\":\"scripts must be an object\",\"nonStringScript\":\"script values must be string commands\",\"nonArrayFiles\":\"Invalid 'files' member\",\"invalidFilename\":\"Invalid filename in 'files' list: %s\",\"nonArrayBundleDependencies\":\"Invalid 'bundleDependencies' list. Must be array of package names\",\"nonStringBundleDependency\":\"Invalid bundleDependencies member: %s\",\"nonDependencyBundleDependency\":\"Non-dependency in bundleDependencies: %s\",\"nonObjectDependencies\":\"%s field must be an object\",\"nonStringDependency\":\"Invalid dependency: %s %s\",\"deprecatedArrayDependencies\":\"specifying %s as array is deprecated\",\"deprecatedModules\":\"modules field is deprecated\",\"nonArrayKeywords\":\"keywords should be an array of strings\",\"nonStringKeyword\":\"keywords should be an array of strings\",\"conflictingName\":\"%s is also the name of a node core module.\",\"nonStringDescription\":\"'description' field should be a string\",\"missingDescription\":\"No description\",\"missingReadme\":\"No README data\",\"missingLicense\":\"No license field.\",\"nonEmailUrlBugsString\":\"Bug string field must be url, email, or {email,url}\",\"nonUrlBugsUrlField\":\"bugs.url field must be a string url. Deleted.\",\"nonEmailBugsEmailField\":\"bugs.email field must be a string email. Deleted.\",\"emptyNormalizedBugs\":\"Normalized value of bugs field is an empty object. Deleted.\",\"nonUrlHomepage\":\"homepage field must be a string url. Deleted.\",\"invalidLicense\":\"license should be a valid SPDX license expression\",\"typo\":\"%s should probably be %s.\"}"); +const isLinkDependency = depVersion => depVersion.startsWith('link:'); /***/ }), -/* 98 */ +/* 518 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; +const {promisify} = __webpack_require__(29); +const fs = __webpack_require__(23); const path = __webpack_require__(16); -const writeJsonFile = __webpack_require__(99); -const sortKeys = __webpack_require__(113); - -const dependencyKeys = new Set([ - 'dependencies', - 'devDependencies', - 'optionalDependencies', - 'peerDependencies' -]); - -function normalize(packageJson) { - const result = {}; - - for (const key of Object.keys(packageJson)) { - if (!dependencyKeys.has(key)) { - result[key] = packageJson[key]; - } else if (Object.keys(packageJson[key]).length !== 0) { - result[key] = sortKeys(packageJson[key]); - } - } - - return result; -} +const parseJson = __webpack_require__(519); -module.exports = async (filePath, data, options) => { - if (typeof filePath !== 'string') { - options = data; - data = filePath; - filePath = '.'; - } +const readFileAsync = promisify(fs.readFile); +module.exports = async options => { options = { + cwd: process.cwd(), normalize: true, - ...options, - detectIndent: true + ...options }; - filePath = path.basename(filePath) === 'package.json' ? filePath : path.join(filePath, 'package.json'); + const filePath = path.resolve(options.cwd, 'package.json'); + const json = parseJson(await readFileAsync(filePath, 'utf8')); - data = options.normalize ? normalize(data) : data; + if (options.normalize) { + __webpack_require__(520)(json); + } - return writeJsonFile(filePath, data, options); + return json; }; -module.exports.sync = (filePath, data, options) => { - if (typeof filePath !== 'string') { - options = data; - data = filePath; - filePath = '.'; - } - +module.exports.sync = options => { options = { + cwd: process.cwd(), normalize: true, - ...options, - detectIndent: true + ...options }; - filePath = path.basename(filePath) === 'package.json' ? filePath : path.join(filePath, 'package.json'); + const filePath = path.resolve(options.cwd, 'package.json'); + const json = parseJson(fs.readFileSync(filePath, 'utf8')); - data = options.normalize ? normalize(data) : data; + if (options.normalize) { + __webpack_require__(520)(json); + } - writeJsonFile.sync(filePath, data, options); + return json; }; /***/ }), -/* 99 */ +/* 519 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const path = __webpack_require__(16); -const fs = __webpack_require__(100); -const writeFileAtomic = __webpack_require__(104); -const sortKeys = __webpack_require__(113); -const makeDir = __webpack_require__(115); -const pify = __webpack_require__(118); -const detectIndent = __webpack_require__(119); - -const init = (fn, filePath, data, options) => { - if (!filePath) { - throw new TypeError('Expected a filepath'); - } - - if (data === undefined) { - throw new TypeError('Expected data to stringify'); - } - - options = Object.assign({ - indent: '\t', - sortKeys: false - }, options); - - if (options.sortKeys) { - data = sortKeys(data, { - deep: true, - compare: typeof options.sortKeys === 'function' ? options.sortKeys : undefined - }); - } - - return fn(filePath, data, options); -}; - -const readFile = filePath => pify(fs.readFile)(filePath, 'utf8').catch(() => {}); - -const main = (filePath, data, options) => { - return (options.detectIndent ? readFile(filePath) : Promise.resolve()) - .then(string => { - const indent = string ? detectIndent(string).indent : options.indent; - const json = JSON.stringify(data, options.replacer, indent); +const errorEx = __webpack_require__(436); +const fallback = __webpack_require__(438); +const {default: LinesAndColumns} = __webpack_require__(439); +const {codeFrameColumns} = __webpack_require__(440); - return pify(writeFileAtomic)(filePath, `${json}\n`, {mode: options.mode}); - }); -}; +const JSONError = errorEx('JSONError', { + fileName: errorEx.append('in %s'), + codeFrame: errorEx.append('\n\n%s\n') +}); -const mainSync = (filePath, data, options) => { - let {indent} = options; +module.exports = (string, reviver, filename) => { + if (typeof reviver === 'string') { + filename = reviver; + reviver = null; + } - if (options.detectIndent) { + try { try { - const file = fs.readFileSync(filePath, 'utf8'); - indent = detectIndent(file).indent; + return JSON.parse(string, reviver); } catch (error) { - if (error.code !== 'ENOENT') { - throw error; - } + fallback(string, reviver); + throw error; } - } + } catch (error) { + error.message = error.message.replace(/\n/g, ''); + const indexMatch = error.message.match(/in JSON at position (\d+) while parsing near/); - const json = JSON.stringify(data, options.replacer, indent); + const jsonError = new JSONError(error); + if (filename) { + jsonError.fileName = filename; + } - return writeFileAtomic.sync(filePath, `${json}\n`, {mode: options.mode}); -}; + if (indexMatch && indexMatch.length > 0) { + const lines = new LinesAndColumns(string); + const index = Number(indexMatch[1]); + const location = lines.locationForIndex(index); -const writeJsonFile = (filePath, data, options) => { - return makeDir(path.dirname(filePath), {fs}) - .then(() => init(main, filePath, data, options)); -}; + const codeFrame = codeFrameColumns( + string, + {start: {line: location.line + 1, column: location.column + 1}}, + {highlightCode: true} + ); -module.exports = writeJsonFile; -// TODO: Remove this for the next major release -module.exports.default = writeJsonFile; -module.exports.sync = (filePath, data, options) => { - makeDir.sync(path.dirname(filePath), {fs}); - init(mainSync, filePath, data, options); + jsonError.codeFrame = codeFrame; + } + + throw jsonError; + } }; /***/ }), -/* 100 */ +/* 520 */ /***/ (function(module, exports, __webpack_require__) { -var fs = __webpack_require__(23) -var polyfills = __webpack_require__(101) -var legacy = __webpack_require__(102) -var clone = __webpack_require__(103) +module.exports = normalize -var queue = [] +var fixer = __webpack_require__(521) +normalize.fixer = fixer -var util = __webpack_require__(29) +var makeWarning = __webpack_require__(542) -function noop () {} +var fieldsToFix = ['name','version','description','repository','modules','scripts' + ,'files','bin','man','bugs','keywords','readme','homepage','license'] +var otherThingsToFix = ['dependencies','people', 'typos'] -var debug = noop -if (util.debuglog) - debug = util.debuglog('gfs4') -else if (/\bgfs4\b/i.test(process.env.NODE_DEBUG || '')) - debug = function() { - var m = util.format.apply(util, arguments) - m = 'GFS4: ' + m.split(/\n/).join('\nGFS4: ') - console.error(m) - } +var thingsToFix = fieldsToFix.map(function(fieldName) { + return ucFirst(fieldName) + "Field" +}) +// two ways to do this in CoffeeScript on only one line, sub-70 chars: +// thingsToFix = fieldsToFix.map (name) -> ucFirst(name) + "Field" +// thingsToFix = (ucFirst(name) + "Field" for name in fieldsToFix) +thingsToFix = thingsToFix.concat(otherThingsToFix) -if (/\bgfs4\b/i.test(process.env.NODE_DEBUG || '')) { - process.on('exit', function() { - debug(queue) - __webpack_require__(30).equal(queue.length, 0) +function normalize (data, warn, strict) { + if(warn === true) warn = null, strict = true + if(!strict) strict = false + if(!warn || data.private) warn = function(msg) { /* noop */ } + + if (data.scripts && + data.scripts.install === "node-gyp rebuild" && + !data.scripts.preinstall) { + data.gypfile = true + } + fixer.warn = function() { warn(makeWarning.apply(null, arguments)) } + thingsToFix.forEach(function(thingName) { + fixer["fix" + ucFirst(thingName)](data, strict) }) + data._id = data.name + "@" + data.version } -module.exports = patch(clone(fs)) -if (process.env.TEST_GRACEFUL_FS_GLOBAL_PATCH && !fs.__patched) { - module.exports = patch(fs) - fs.__patched = true; +function ucFirst (string) { + return string.charAt(0).toUpperCase() + string.slice(1); } -// Always patch fs.close/closeSync, because we want to -// retry() whenever a close happens *anywhere* in the program. -// This is essential when multiple graceful-fs instances are -// in play at the same time. -module.exports.close = (function (fs$close) { return function (fd, cb) { - return fs$close.call(fs, fd, function (err) { - if (!err) - retry() - - if (typeof cb === 'function') - cb.apply(this, arguments) - }) -}})(fs.close) -module.exports.closeSync = (function (fs$closeSync) { return function (fd) { - // Note that graceful-fs also retries when fs.closeSync() fails. - // Looks like a bug to me, although it's probably a harmless one. - var rval = fs$closeSync.apply(fs, arguments) - retry() - return rval -}})(fs.closeSync) +/***/ }), +/* 521 */ +/***/ (function(module, exports, __webpack_require__) { -// Only patch fs once, otherwise we'll run into a memory leak if -// graceful-fs is loaded multiple times, such as in test environments that -// reset the loaded modules between tests. -// We look for the string `graceful-fs` from the comment above. This -// way we are not adding any extra properties and it will detect if older -// versions of graceful-fs are installed. -if (!/\bgraceful-fs\b/.test(fs.closeSync.toString())) { - fs.closeSync = module.exports.closeSync; - fs.close = module.exports.close; -} +var semver = __webpack_require__(522) +var validateLicense = __webpack_require__(523); +var hostedGitInfo = __webpack_require__(528) +var isBuiltinModule = __webpack_require__(531).isCore +var depTypes = ["dependencies","devDependencies","optionalDependencies"] +var extractDescription = __webpack_require__(540) +var url = __webpack_require__(454) +var typos = __webpack_require__(541) -function patch (fs) { - // Everything that references the open() function needs to be in here - polyfills(fs) - fs.gracefulify = patch - fs.FileReadStream = ReadStream; // Legacy name. - fs.FileWriteStream = WriteStream; // Legacy name. - fs.createReadStream = createReadStream - fs.createWriteStream = createWriteStream - var fs$readFile = fs.readFile - fs.readFile = readFile - function readFile (path, options, cb) { - if (typeof options === 'function') - cb = options, options = null +var fixer = module.exports = { + // default warning function + warn: function() {}, - return go$readFile(path, options, cb) + fixRepositoryField: function(data) { + if (data.repositories) { + this.warn("repositories"); + data.repository = data.repositories[0] + } + if (!data.repository) return this.warn("missingRepository") + if (typeof data.repository === "string") { + data.repository = { + type: "git", + url: data.repository + } + } + var r = data.repository.url || "" + if (r) { + var hosted = hostedGitInfo.fromUrl(r) + if (hosted) { + r = data.repository.url + = hosted.getDefaultRepresentation() == "shortcut" ? hosted.https() : hosted.toString() + } + } - function go$readFile (path, options, cb) { - return fs$readFile(path, options, function (err) { - if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) - enqueue([go$readFile, [path, options, cb]]) - else { - if (typeof cb === 'function') - cb.apply(this, arguments) - retry() - } - }) + if (r.match(/github.com\/[^\/]+\/[^\/]+\.git\.git$/)) { + this.warn("brokenGitUrl", r) } } - var fs$writeFile = fs.writeFile - fs.writeFile = writeFile - function writeFile (path, data, options, cb) { - if (typeof options === 'function') - cb = options, options = null - - return go$writeFile(path, data, options, cb) +, fixTypos: function(data) { + Object.keys(typos.topLevel).forEach(function (d) { + if (data.hasOwnProperty(d)) { + this.warn("typo", d, typos.topLevel[d]) + } + }, this) + } - function go$writeFile (path, data, options, cb) { - return fs$writeFile(path, data, options, function (err) { - if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) - enqueue([go$writeFile, [path, data, options, cb]]) - else { - if (typeof cb === 'function') - cb.apply(this, arguments) - retry() - } - }) +, fixScriptsField: function(data) { + if (!data.scripts) return + if (typeof data.scripts !== "object") { + this.warn("nonObjectScripts") + delete data.scripts + return } + Object.keys(data.scripts).forEach(function (k) { + if (typeof data.scripts[k] !== "string") { + this.warn("nonStringScript") + delete data.scripts[k] + } else if (typos.script[k] && !data.scripts[typos.script[k]]) { + this.warn("typo", k, typos.script[k], "scripts") + } + }, this) } - var fs$appendFile = fs.appendFile - if (fs$appendFile) - fs.appendFile = appendFile - function appendFile (path, data, options, cb) { - if (typeof options === 'function') - cb = options, options = null - - return go$appendFile(path, data, options, cb) - - function go$appendFile (path, data, options, cb) { - return fs$appendFile(path, data, options, function (err) { - if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) - enqueue([go$appendFile, [path, data, options, cb]]) - else { - if (typeof cb === 'function') - cb.apply(this, arguments) - retry() +, fixFilesField: function(data) { + var files = data.files + if (files && !Array.isArray(files)) { + this.warn("nonArrayFiles") + delete data.files + } else if (data.files) { + data.files = data.files.filter(function(file) { + if (!file || typeof file !== "string") { + this.warn("invalidFilename", file) + return false + } else { + return true } - }) + }, this) } } - var fs$readdir = fs.readdir - fs.readdir = readdir - function readdir (path, options, cb) { - var args = [path] - if (typeof options !== 'function') { - args.push(options) - } else { - cb = options - } - args.push(go$readdir$cb) - - return go$readdir(args) - - function go$readdir$cb (err, files) { - if (files && files.sort) - files.sort() - - if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) - enqueue([go$readdir, [args]]) - - else { - if (typeof cb === 'function') - cb.apply(this, arguments) - retry() +, fixBinField: function(data) { + if (!data.bin) return; + if (typeof data.bin === "string") { + var b = {} + var match + if (match = data.name.match(/^@[^/]+[/](.*)$/)) { + b[match[1]] = data.bin + } else { + b[data.name] = data.bin } + data.bin = b } } - function go$readdir (args) { - return fs$readdir.apply(fs, args) +, fixManField: function(data) { + if (!data.man) return; + if (typeof data.man === "string") { + data.man = [ data.man ] + } } - - if (process.version.substr(0, 4) === 'v0.8') { - var legStreams = legacy(fs) - ReadStream = legStreams.ReadStream - WriteStream = legStreams.WriteStream +, fixBundleDependenciesField: function(data) { + var bdd = "bundledDependencies" + var bd = "bundleDependencies" + if (data[bdd] && !data[bd]) { + data[bd] = data[bdd] + delete data[bdd] + } + if (data[bd] && !Array.isArray(data[bd])) { + this.warn("nonArrayBundleDependencies") + delete data[bd] + } else if (data[bd]) { + data[bd] = data[bd].filter(function(bd) { + if (!bd || typeof bd !== 'string') { + this.warn("nonStringBundleDependency", bd) + return false + } else { + if (!data.dependencies) { + data.dependencies = {} + } + if (!data.dependencies.hasOwnProperty(bd)) { + this.warn("nonDependencyBundleDependency", bd) + data.dependencies[bd] = "*" + } + return true + } + }, this) + } } - var fs$ReadStream = fs.ReadStream - if (fs$ReadStream) { - ReadStream.prototype = Object.create(fs$ReadStream.prototype) - ReadStream.prototype.open = ReadStream$open - } +, fixDependencies: function(data, strict) { + var loose = !strict + objectifyDeps(data, this.warn) + addOptionalDepsToDeps(data, this.warn) + this.fixBundleDependenciesField(data) - var fs$WriteStream = fs.WriteStream - if (fs$WriteStream) { - WriteStream.prototype = Object.create(fs$WriteStream.prototype) - WriteStream.prototype.open = WriteStream$open + ;['dependencies','devDependencies'].forEach(function(deps) { + if (!(deps in data)) return + if (!data[deps] || typeof data[deps] !== "object") { + this.warn("nonObjectDependencies", deps) + delete data[deps] + return + } + Object.keys(data[deps]).forEach(function (d) { + var r = data[deps][d] + if (typeof r !== 'string') { + this.warn("nonStringDependency", d, JSON.stringify(r)) + delete data[deps][d] + } + var hosted = hostedGitInfo.fromUrl(data[deps][d]) + if (hosted) data[deps][d] = hosted.toString() + }, this) + }, this) } - fs.ReadStream = ReadStream - fs.WriteStream = WriteStream - - function ReadStream (path, options) { - if (this instanceof ReadStream) - return fs$ReadStream.apply(this, arguments), this - else - return ReadStream.apply(Object.create(ReadStream.prototype), arguments) +, fixModulesField: function (data) { + if (data.modules) { + this.warn("deprecatedModules") + delete data.modules + } } - function ReadStream$open () { - var that = this - open(that.path, that.flags, that.mode, function (err, fd) { - if (err) { - if (that.autoClose) - that.destroy() - - that.emit('error', err) - } else { - that.fd = fd - that.emit('open', fd) - that.read() - } - }) +, fixKeywordsField: function (data) { + if (typeof data.keywords === "string") { + data.keywords = data.keywords.split(/,\s+/) + } + if (data.keywords && !Array.isArray(data.keywords)) { + delete data.keywords + this.warn("nonArrayKeywords") + } else if (data.keywords) { + data.keywords = data.keywords.filter(function(kw) { + if (typeof kw !== "string" || !kw) { + this.warn("nonStringKeyword"); + return false + } else { + return true + } + }, this) + } } - function WriteStream (path, options) { - if (this instanceof WriteStream) - return fs$WriteStream.apply(this, arguments), this - else - return WriteStream.apply(Object.create(WriteStream.prototype), arguments) +, fixVersionField: function(data, strict) { + // allow "loose" semver 1.0 versions in non-strict mode + // enforce strict semver 2.0 compliance in strict mode + var loose = !strict + if (!data.version) { + data.version = "" + return true + } + if (!semver.valid(data.version, loose)) { + throw new Error('Invalid version: "'+ data.version + '"') + } + data.version = semver.clean(data.version, loose) + return true } - function WriteStream$open () { - var that = this - open(that.path, that.flags, that.mode, function (err, fd) { - if (err) { - that.destroy() - that.emit('error', err) - } else { - that.fd = fd - that.emit('open', fd) - } - }) +, fixPeople: function(data) { + modifyPeople(data, unParsePerson) + modifyPeople(data, parsePerson) } - function createReadStream (path, options) { - return new ReadStream(path, options) +, fixNameField: function(data, options) { + if (typeof options === "boolean") options = {strict: options} + else if (typeof options === "undefined") options = {} + var strict = options.strict + if (!data.name && !strict) { + data.name = "" + return + } + if (typeof data.name !== "string") { + throw new Error("name field must be a string.") + } + if (!strict) + data.name = data.name.trim() + ensureValidName(data.name, strict, options.allowLegacyCase) + if (isBuiltinModule(data.name)) + this.warn("conflictingName", data.name) } - function createWriteStream (path, options) { - return new WriteStream(path, options) - } - var fs$open = fs.open - fs.open = open - function open (path, flags, mode, cb) { - if (typeof mode === 'function') - cb = mode, mode = null +, fixDescriptionField: function (data) { + if (data.description && typeof data.description !== 'string') { + this.warn("nonStringDescription") + delete data.description + } + if (data.readme && !data.description) + data.description = extractDescription(data.readme) + if(data.description === undefined) delete data.description; + if (!data.description) this.warn("missingDescription") + } - return go$open(path, flags, mode, cb) +, fixReadmeField: function (data) { + if (!data.readme) { + this.warn("missingReadme") + data.readme = "ERROR: No README data found!" + } + } - function go$open (path, flags, mode, cb) { - return fs$open(path, flags, mode, function (err, fd) { - if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) - enqueue([go$open, [path, flags, mode, cb]]) - else { - if (typeof cb === 'function') - cb.apply(this, arguments) - retry() +, fixBugsField: function(data) { + if (!data.bugs && data.repository && data.repository.url) { + var hosted = hostedGitInfo.fromUrl(data.repository.url) + if(hosted && hosted.bugs()) { + data.bugs = {url: hosted.bugs()} + } + } + else if(data.bugs) { + var emailRe = /^.+@.*\..+$/ + if(typeof data.bugs == "string") { + if(emailRe.test(data.bugs)) + data.bugs = {email:data.bugs} + else if(url.parse(data.bugs).protocol) + data.bugs = {url: data.bugs} + else + this.warn("nonEmailUrlBugsString") + } + else { + bugsTypos(data.bugs, this.warn) + var oldBugs = data.bugs + data.bugs = {} + if(oldBugs.url) { + if(typeof(oldBugs.url) == "string" && url.parse(oldBugs.url).protocol) + data.bugs.url = oldBugs.url + else + this.warn("nonUrlBugsUrlField") + } + if(oldBugs.email) { + if(typeof(oldBugs.email) == "string" && emailRe.test(oldBugs.email)) + data.bugs.email = oldBugs.email + else + this.warn("nonEmailBugsEmailField") } - }) + } + if(!data.bugs.email && !data.bugs.url) { + delete data.bugs + this.warn("emptyNormalizedBugs") + } } } - return fs -} +, fixHomepageField: function(data) { + if (!data.homepage && data.repository && data.repository.url) { + var hosted = hostedGitInfo.fromUrl(data.repository.url) + if (hosted && hosted.docs()) data.homepage = hosted.docs() + } + if (!data.homepage) return -function enqueue (elem) { - debug('ENQUEUE', elem[0].name, elem[1]) - queue.push(elem) -} + if(typeof data.homepage !== "string") { + this.warn("nonUrlHomepage") + return delete data.homepage + } + if(!url.parse(data.homepage).protocol) { + data.homepage = "http://" + data.homepage + } + } -function retry () { - var elem = queue.shift() - if (elem) { - debug('RETRY', elem[0].name, elem[1]) - elem[0].apply(null, elem[1]) +, fixLicenseField: function(data) { + if (!data.license) { + return this.warn("missingLicense") + } else{ + if ( + typeof(data.license) !== 'string' || + data.license.length < 1 || + data.license.trim() === '' + ) { + this.warn("invalidLicense") + } else { + if (!validateLicense(data.license).validForNewPackages) + this.warn("invalidLicense") + } + } } } +function isValidScopedPackageName(spec) { + if (spec.charAt(0) !== '@') return false -/***/ }), -/* 101 */ -/***/ (function(module, exports, __webpack_require__) { + var rest = spec.slice(1).split('/') + if (rest.length !== 2) return false -var constants = __webpack_require__(26) + return rest[0] && rest[1] && + rest[0] === encodeURIComponent(rest[0]) && + rest[1] === encodeURIComponent(rest[1]) +} -var origCwd = process.cwd -var cwd = null +function isCorrectlyEncodedName(spec) { + return !spec.match(/[\/@\s\+%:]/) && + spec === encodeURIComponent(spec) +} -var platform = process.env.GRACEFUL_FS_PLATFORM || process.platform +function ensureValidName (name, strict, allowLegacyCase) { + if (name.charAt(0) === "." || + !(isValidScopedPackageName(name) || isCorrectlyEncodedName(name)) || + (strict && (!allowLegacyCase) && name !== name.toLowerCase()) || + name.toLowerCase() === "node_modules" || + name.toLowerCase() === "favicon.ico") { + throw new Error("Invalid name: " + JSON.stringify(name)) + } +} -process.cwd = function() { - if (!cwd) - cwd = origCwd.call(process) - return cwd +function modifyPeople (data, fn) { + if (data.author) data.author = fn(data.author) + ;["maintainers", "contributors"].forEach(function (set) { + if (!Array.isArray(data[set])) return; + data[set] = data[set].map(fn) + }) + return data } -try { - process.cwd() -} catch (er) {} -var chdir = process.chdir -process.chdir = function(d) { - cwd = null - chdir.call(process, d) +function unParsePerson (person) { + if (typeof person === "string") return person + var name = person.name || "" + var u = person.url || person.web + var url = u ? (" ("+u+")") : "" + var e = person.email || person.mail + var email = e ? (" <"+e+">") : "" + return name+email+url } -module.exports = patch +function parsePerson (person) { + if (typeof person !== "string") return person + var name = person.match(/^([^\(<]+)/) + var url = person.match(/\(([^\)]+)\)/) + var email = person.match(/<([^>]+)>/) + var obj = {} + if (name && name[0].trim()) obj.name = name[0].trim() + if (email) obj.email = email[1]; + if (url) obj.url = url[1]; + return obj +} -function patch (fs) { - // (re-)implement some things that are known busted or missing. +function addOptionalDepsToDeps (data, warn) { + var o = data.optionalDependencies + if (!o) return; + var d = data.dependencies || {} + Object.keys(o).forEach(function (k) { + d[k] = o[k] + }) + data.dependencies = d +} - // lchmod, broken prior to 0.6.2 - // back-port the fix here. - if (constants.hasOwnProperty('O_SYMLINK') && - process.version.match(/^v0\.6\.[0-2]|^v0\.5\./)) { - patchLchmod(fs) +function depObjectify (deps, type, warn) { + if (!deps) return {} + if (typeof deps === "string") { + deps = deps.trim().split(/[\n\r\s\t ,]+/) } + if (!Array.isArray(deps)) return deps + warn("deprecatedArrayDependencies", type) + var o = {} + deps.filter(function (d) { + return typeof d === "string" + }).forEach(function(d) { + d = d.trim().split(/(:?[@\s><=])/) + var dn = d.shift() + var dv = d.join("") + dv = dv.trim() + dv = dv.replace(/^@/, "") + o[dn] = dv + }) + return o +} - // lutimes implementation, or no-op - if (!fs.lutimes) { - patchLutimes(fs) - } +function objectifyDeps (data, warn) { + depTypes.forEach(function (type) { + if (!data[type]) return; + data[type] = depObjectify(data[type], type, warn) + }) +} - // https://github.com/isaacs/node-graceful-fs/issues/4 - // Chown should not fail on einval or eperm if non-root. - // It should not fail on enosys ever, as this just indicates - // that a fs doesn't support the intended operation. +function bugsTypos(bugs, warn) { + if (!bugs) return + Object.keys(bugs).forEach(function (k) { + if (typos.bugs[k]) { + warn("typo", k, typos.bugs[k], "bugs") + bugs[typos.bugs[k]] = bugs[k] + delete bugs[k] + } + }) +} - fs.chown = chownFix(fs.chown) - fs.fchown = chownFix(fs.fchown) - fs.lchown = chownFix(fs.lchown) - fs.chmod = chmodFix(fs.chmod) - fs.fchmod = chmodFix(fs.fchmod) - fs.lchmod = chmodFix(fs.lchmod) +/***/ }), +/* 522 */ +/***/ (function(module, exports) { - fs.chownSync = chownFixSync(fs.chownSync) - fs.fchownSync = chownFixSync(fs.fchownSync) - fs.lchownSync = chownFixSync(fs.lchownSync) +exports = module.exports = SemVer; - fs.chmodSync = chmodFixSync(fs.chmodSync) - fs.fchmodSync = chmodFixSync(fs.fchmodSync) - fs.lchmodSync = chmodFixSync(fs.lchmodSync) +// The debug function is excluded entirely from the minified version. +/* nomin */ var debug; +/* nomin */ if (typeof process === 'object' && + /* nomin */ process.env && + /* nomin */ process.env.NODE_DEBUG && + /* nomin */ /\bsemver\b/i.test(process.env.NODE_DEBUG)) + /* nomin */ debug = function() { + /* nomin */ var args = Array.prototype.slice.call(arguments, 0); + /* nomin */ args.unshift('SEMVER'); + /* nomin */ console.log.apply(console, args); + /* nomin */ }; +/* nomin */ else + /* nomin */ debug = function() {}; - fs.stat = statFix(fs.stat) - fs.fstat = statFix(fs.fstat) - fs.lstat = statFix(fs.lstat) +// Note: this is the semver.org version of the spec that it implements +// Not necessarily the package version of this code. +exports.SEMVER_SPEC_VERSION = '2.0.0'; - fs.statSync = statFixSync(fs.statSync) - fs.fstatSync = statFixSync(fs.fstatSync) - fs.lstatSync = statFixSync(fs.lstatSync) +var MAX_LENGTH = 256; +var MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER || 9007199254740991; - // if lchmod/lchown do not exist, then make them no-ops - if (!fs.lchmod) { - fs.lchmod = function (path, mode, cb) { - if (cb) process.nextTick(cb) - } - fs.lchmodSync = function () {} - } - if (!fs.lchown) { - fs.lchown = function (path, uid, gid, cb) { - if (cb) process.nextTick(cb) - } - fs.lchownSync = function () {} - } +// The actual regexps go on exports.re +var re = exports.re = []; +var src = exports.src = []; +var R = 0; - // on Windows, A/V software can lock the directory, causing this - // to fail with an EACCES or EPERM if the directory contains newly - // created files. Try again on failure, for up to 60 seconds. +// The following Regular Expressions can be used for tokenizing, +// validating, and parsing SemVer version strings. - // Set the timeout this long because some Windows Anti-Virus, such as Parity - // bit9, may lock files for up to a minute, causing npm package install - // failures. Also, take care to yield the scheduler. Windows scheduling gives - // CPU to a busy looping process, which can cause the program causing the lock - // contention to be starved of CPU by node, so the contention doesn't resolve. - if (platform === "win32") { - fs.rename = (function (fs$rename) { return function (from, to, cb) { - var start = Date.now() - var backoff = 0; - fs$rename(from, to, function CB (er) { - if (er - && (er.code === "EACCES" || er.code === "EPERM") - && Date.now() - start < 60000) { - setTimeout(function() { - fs.stat(to, function (stater, st) { - if (stater && stater.code === "ENOENT") - fs$rename(from, to, CB); - else - cb(er) - }) - }, backoff) - if (backoff < 100) - backoff += 10; - return; - } - if (cb) cb(er) - }) - }})(fs.rename) - } +// ## Numeric Identifier +// A single `0`, or a non-zero digit followed by zero or more digits. - // if read() returns EAGAIN, then just try it again. - fs.read = (function (fs$read) { return function (fd, buffer, offset, length, position, callback_) { - var callback - if (callback_ && typeof callback_ === 'function') { - var eagCounter = 0 - callback = function (er, _, __) { - if (er && er.code === 'EAGAIN' && eagCounter < 10) { - eagCounter ++ - return fs$read.call(fs, fd, buffer, offset, length, position, callback) - } - callback_.apply(this, arguments) - } - } - return fs$read.call(fs, fd, buffer, offset, length, position, callback) - }})(fs.read) +var NUMERICIDENTIFIER = R++; +src[NUMERICIDENTIFIER] = '0|[1-9]\\d*'; +var NUMERICIDENTIFIERLOOSE = R++; +src[NUMERICIDENTIFIERLOOSE] = '[0-9]+'; - fs.readSync = (function (fs$readSync) { return function (fd, buffer, offset, length, position) { - var eagCounter = 0 - while (true) { - try { - return fs$readSync.call(fs, fd, buffer, offset, length, position) - } catch (er) { - if (er.code === 'EAGAIN' && eagCounter < 10) { - eagCounter ++ - continue - } - throw er - } - } - }})(fs.readSync) - function patchLchmod (fs) { - fs.lchmod = function (path, mode, callback) { - fs.open( path - , constants.O_WRONLY | constants.O_SYMLINK - , mode - , function (err, fd) { - if (err) { - if (callback) callback(err) - return - } - // prefer to return the chmod error, if one occurs, - // but still try to close, and report closing errors if they occur. - fs.fchmod(fd, mode, function (err) { - fs.close(fd, function(err2) { - if (callback) callback(err || err2) - }) - }) - }) - } +// ## Non-numeric Identifier +// Zero or more digits, followed by a letter or hyphen, and then zero or +// more letters, digits, or hyphens. - fs.lchmodSync = function (path, mode) { - var fd = fs.openSync(path, constants.O_WRONLY | constants.O_SYMLINK, mode) +var NONNUMERICIDENTIFIER = R++; +src[NONNUMERICIDENTIFIER] = '\\d*[a-zA-Z-][a-zA-Z0-9-]*'; - // prefer to return the chmod error, if one occurs, - // but still try to close, and report closing errors if they occur. - var threw = true - var ret - try { - ret = fs.fchmodSync(fd, mode) - threw = false - } finally { - if (threw) { - try { - fs.closeSync(fd) - } catch (er) {} - } else { - fs.closeSync(fd) - } - } - return ret - } - } - function patchLutimes (fs) { - if (constants.hasOwnProperty("O_SYMLINK")) { - fs.lutimes = function (path, at, mt, cb) { - fs.open(path, constants.O_SYMLINK, function (er, fd) { - if (er) { - if (cb) cb(er) - return - } - fs.futimes(fd, at, mt, function (er) { - fs.close(fd, function (er2) { - if (cb) cb(er || er2) - }) - }) - }) - } +// ## Main Version +// Three dot-separated numeric identifiers. - fs.lutimesSync = function (path, at, mt) { - var fd = fs.openSync(path, constants.O_SYMLINK) - var ret - var threw = true - try { - ret = fs.futimesSync(fd, at, mt) - threw = false - } finally { - if (threw) { - try { - fs.closeSync(fd) - } catch (er) {} - } else { - fs.closeSync(fd) - } - } - return ret - } +var MAINVERSION = R++; +src[MAINVERSION] = '(' + src[NUMERICIDENTIFIER] + ')\\.' + + '(' + src[NUMERICIDENTIFIER] + ')\\.' + + '(' + src[NUMERICIDENTIFIER] + ')'; - } else { - fs.lutimes = function (_a, _b, _c, cb) { if (cb) process.nextTick(cb) } - fs.lutimesSync = function () {} - } - } +var MAINVERSIONLOOSE = R++; +src[MAINVERSIONLOOSE] = '(' + src[NUMERICIDENTIFIERLOOSE] + ')\\.' + + '(' + src[NUMERICIDENTIFIERLOOSE] + ')\\.' + + '(' + src[NUMERICIDENTIFIERLOOSE] + ')'; - function chmodFix (orig) { - if (!orig) return orig - return function (target, mode, cb) { - return orig.call(fs, target, mode, function (er) { - if (chownErOk(er)) er = null - if (cb) cb.apply(this, arguments) - }) - } - } +// ## Pre-release Version Identifier +// A numeric identifier, or a non-numeric identifier. - function chmodFixSync (orig) { - if (!orig) return orig - return function (target, mode) { - try { - return orig.call(fs, target, mode) - } catch (er) { - if (!chownErOk(er)) throw er - } - } - } +var PRERELEASEIDENTIFIER = R++; +src[PRERELEASEIDENTIFIER] = '(?:' + src[NUMERICIDENTIFIER] + + '|' + src[NONNUMERICIDENTIFIER] + ')'; +var PRERELEASEIDENTIFIERLOOSE = R++; +src[PRERELEASEIDENTIFIERLOOSE] = '(?:' + src[NUMERICIDENTIFIERLOOSE] + + '|' + src[NONNUMERICIDENTIFIER] + ')'; - function chownFix (orig) { - if (!orig) return orig - return function (target, uid, gid, cb) { - return orig.call(fs, target, uid, gid, function (er) { - if (chownErOk(er)) er = null - if (cb) cb.apply(this, arguments) - }) - } - } - function chownFixSync (orig) { - if (!orig) return orig - return function (target, uid, gid) { - try { - return orig.call(fs, target, uid, gid) - } catch (er) { - if (!chownErOk(er)) throw er - } - } - } +// ## Pre-release Version +// Hyphen, followed by one or more dot-separated pre-release version +// identifiers. +var PRERELEASE = R++; +src[PRERELEASE] = '(?:-(' + src[PRERELEASEIDENTIFIER] + + '(?:\\.' + src[PRERELEASEIDENTIFIER] + ')*))'; - function statFix (orig) { - if (!orig) return orig - // Older versions of Node erroneously returned signed integers for - // uid + gid. - return function (target, cb) { - return orig.call(fs, target, function (er, stats) { - if (!stats) return cb.apply(this, arguments) - if (stats.uid < 0) stats.uid += 0x100000000 - if (stats.gid < 0) stats.gid += 0x100000000 - if (cb) cb.apply(this, arguments) - }) - } - } +var PRERELEASELOOSE = R++; +src[PRERELEASELOOSE] = '(?:-?(' + src[PRERELEASEIDENTIFIERLOOSE] + + '(?:\\.' + src[PRERELEASEIDENTIFIERLOOSE] + ')*))'; - function statFixSync (orig) { - if (!orig) return orig - // Older versions of Node erroneously returned signed integers for - // uid + gid. - return function (target) { - var stats = orig.call(fs, target) - if (stats.uid < 0) stats.uid += 0x100000000 - if (stats.gid < 0) stats.gid += 0x100000000 - return stats; - } - } +// ## Build Metadata Identifier +// Any combination of digits, letters, or hyphens. - // ENOSYS means that the fs doesn't support the op. Just ignore - // that, because it doesn't matter. - // - // if there's no getuid, or if getuid() is something other - // than 0, and the error is EINVAL or EPERM, then just ignore - // it. - // - // This specific case is a silent failure in cp, install, tar, - // and most other unix tools that manage permissions. - // - // When running as root, or if other types of errors are - // encountered, then it's strict. - function chownErOk (er) { - if (!er) - return true +var BUILDIDENTIFIER = R++; +src[BUILDIDENTIFIER] = '[0-9A-Za-z-]+'; - if (er.code === "ENOSYS") - return true +// ## Build Metadata +// Plus sign, followed by one or more period-separated build metadata +// identifiers. - var nonroot = !process.getuid || process.getuid() !== 0 - if (nonroot) { - if (er.code === "EINVAL" || er.code === "EPERM") - return true - } +var BUILD = R++; +src[BUILD] = '(?:\\+(' + src[BUILDIDENTIFIER] + + '(?:\\.' + src[BUILDIDENTIFIER] + ')*))'; - return false - } -} +// ## Full Version String +// A main version, followed optionally by a pre-release version and +// build metadata. + +// Note that the only major, minor, patch, and pre-release sections of +// the version string are capturing groups. The build metadata is not a +// capturing group, because it should not ever be used in version +// comparison. + +var FULL = R++; +var FULLPLAIN = 'v?' + src[MAINVERSION] + + src[PRERELEASE] + '?' + + src[BUILD] + '?'; -/***/ }), -/* 102 */ -/***/ (function(module, exports, __webpack_require__) { +src[FULL] = '^' + FULLPLAIN + '$'; -var Stream = __webpack_require__(28).Stream +// like full, but allows v1.2.3 and =1.2.3, which people do sometimes. +// also, 1.0.0alpha1 (prerelease without the hyphen) which is pretty +// common in the npm registry. +var LOOSEPLAIN = '[v=\\s]*' + src[MAINVERSIONLOOSE] + + src[PRERELEASELOOSE] + '?' + + src[BUILD] + '?'; -module.exports = legacy +var LOOSE = R++; +src[LOOSE] = '^' + LOOSEPLAIN + '$'; -function legacy (fs) { - return { - ReadStream: ReadStream, - WriteStream: WriteStream - } +var GTLT = R++; +src[GTLT] = '((?:<|>)?=?)'; - function ReadStream (path, options) { - if (!(this instanceof ReadStream)) return new ReadStream(path, options); +// Something like "2.*" or "1.2.x". +// Note that "x.x" is a valid xRange identifer, meaning "any version" +// Only the first item is strictly required. +var XRANGEIDENTIFIERLOOSE = R++; +src[XRANGEIDENTIFIERLOOSE] = src[NUMERICIDENTIFIERLOOSE] + '|x|X|\\*'; +var XRANGEIDENTIFIER = R++; +src[XRANGEIDENTIFIER] = src[NUMERICIDENTIFIER] + '|x|X|\\*'; - Stream.call(this); +var XRANGEPLAIN = R++; +src[XRANGEPLAIN] = '[v=\\s]*(' + src[XRANGEIDENTIFIER] + ')' + + '(?:\\.(' + src[XRANGEIDENTIFIER] + ')' + + '(?:\\.(' + src[XRANGEIDENTIFIER] + ')' + + '(?:' + src[PRERELEASE] + ')?' + + src[BUILD] + '?' + + ')?)?'; - var self = this; +var XRANGEPLAINLOOSE = R++; +src[XRANGEPLAINLOOSE] = '[v=\\s]*(' + src[XRANGEIDENTIFIERLOOSE] + ')' + + '(?:\\.(' + src[XRANGEIDENTIFIERLOOSE] + ')' + + '(?:\\.(' + src[XRANGEIDENTIFIERLOOSE] + ')' + + '(?:' + src[PRERELEASELOOSE] + ')?' + + src[BUILD] + '?' + + ')?)?'; - this.path = path; - this.fd = null; - this.readable = true; - this.paused = false; +var XRANGE = R++; +src[XRANGE] = '^' + src[GTLT] + '\\s*' + src[XRANGEPLAIN] + '$'; +var XRANGELOOSE = R++; +src[XRANGELOOSE] = '^' + src[GTLT] + '\\s*' + src[XRANGEPLAINLOOSE] + '$'; - this.flags = 'r'; - this.mode = 438; /*=0666*/ - this.bufferSize = 64 * 1024; +// Tilde ranges. +// Meaning is "reasonably at or greater than" +var LONETILDE = R++; +src[LONETILDE] = '(?:~>?)'; - options = options || {}; +var TILDETRIM = R++; +src[TILDETRIM] = '(\\s*)' + src[LONETILDE] + '\\s+'; +re[TILDETRIM] = new RegExp(src[TILDETRIM], 'g'); +var tildeTrimReplace = '$1~'; - // Mixin options into this - var keys = Object.keys(options); - for (var index = 0, length = keys.length; index < length; index++) { - var key = keys[index]; - this[key] = options[key]; - } +var TILDE = R++; +src[TILDE] = '^' + src[LONETILDE] + src[XRANGEPLAIN] + '$'; +var TILDELOOSE = R++; +src[TILDELOOSE] = '^' + src[LONETILDE] + src[XRANGEPLAINLOOSE] + '$'; - if (this.encoding) this.setEncoding(this.encoding); +// Caret ranges. +// Meaning is "at least and backwards compatible with" +var LONECARET = R++; +src[LONECARET] = '(?:\\^)'; - if (this.start !== undefined) { - if ('number' !== typeof this.start) { - throw TypeError('start must be a Number'); - } - if (this.end === undefined) { - this.end = Infinity; - } else if ('number' !== typeof this.end) { - throw TypeError('end must be a Number'); - } +var CARETTRIM = R++; +src[CARETTRIM] = '(\\s*)' + src[LONECARET] + '\\s+'; +re[CARETTRIM] = new RegExp(src[CARETTRIM], 'g'); +var caretTrimReplace = '$1^'; - if (this.start > this.end) { - throw new Error('start must be <= end'); - } +var CARET = R++; +src[CARET] = '^' + src[LONECARET] + src[XRANGEPLAIN] + '$'; +var CARETLOOSE = R++; +src[CARETLOOSE] = '^' + src[LONECARET] + src[XRANGEPLAINLOOSE] + '$'; - this.pos = this.start; - } +// A simple gt/lt/eq thing, or just "" to indicate "any version" +var COMPARATORLOOSE = R++; +src[COMPARATORLOOSE] = '^' + src[GTLT] + '\\s*(' + LOOSEPLAIN + ')$|^$'; +var COMPARATOR = R++; +src[COMPARATOR] = '^' + src[GTLT] + '\\s*(' + FULLPLAIN + ')$|^$'; - if (this.fd !== null) { - process.nextTick(function() { - self._read(); - }); - return; - } - fs.open(this.path, this.flags, this.mode, function (err, fd) { - if (err) { - self.emit('error', err); - self.readable = false; - return; - } +// An expression to strip any whitespace between the gtlt and the thing +// it modifies, so that `> 1.2.3` ==> `>1.2.3` +var COMPARATORTRIM = R++; +src[COMPARATORTRIM] = '(\\s*)' + src[GTLT] + + '\\s*(' + LOOSEPLAIN + '|' + src[XRANGEPLAIN] + ')'; - self.fd = fd; - self.emit('open', fd); - self._read(); - }) - } +// this one has to use the /g flag +re[COMPARATORTRIM] = new RegExp(src[COMPARATORTRIM], 'g'); +var comparatorTrimReplace = '$1$2$3'; - function WriteStream (path, options) { - if (!(this instanceof WriteStream)) return new WriteStream(path, options); - Stream.call(this); +// Something like `1.2.3 - 1.2.4` +// Note that these all use the loose form, because they'll be +// checked against either the strict or loose comparator form +// later. +var HYPHENRANGE = R++; +src[HYPHENRANGE] = '^\\s*(' + src[XRANGEPLAIN] + ')' + + '\\s+-\\s+' + + '(' + src[XRANGEPLAIN] + ')' + + '\\s*$'; - this.path = path; - this.fd = null; - this.writable = true; +var HYPHENRANGELOOSE = R++; +src[HYPHENRANGELOOSE] = '^\\s*(' + src[XRANGEPLAINLOOSE] + ')' + + '\\s+-\\s+' + + '(' + src[XRANGEPLAINLOOSE] + ')' + + '\\s*$'; - this.flags = 'w'; - this.encoding = 'binary'; - this.mode = 438; /*=0666*/ - this.bytesWritten = 0; +// Star ranges basically just allow anything at all. +var STAR = R++; +src[STAR] = '(<|>)?=?\\s*\\*'; - options = options || {}; +// Compile to actual regexp objects. +// All are flag-free, unless they were created above with a flag. +for (var i = 0; i < R; i++) { + debug(i, src[i]); + if (!re[i]) + re[i] = new RegExp(src[i]); +} - // Mixin options into this - var keys = Object.keys(options); - for (var index = 0, length = keys.length; index < length; index++) { - var key = keys[index]; - this[key] = options[key]; - } +exports.parse = parse; +function parse(version, loose) { + if (version instanceof SemVer) + return version; - if (this.start !== undefined) { - if ('number' !== typeof this.start) { - throw TypeError('start must be a Number'); - } - if (this.start < 0) { - throw new Error('start must be >= zero'); - } + if (typeof version !== 'string') + return null; - this.pos = this.start; - } + if (version.length > MAX_LENGTH) + return null; - this.busy = false; - this._queue = []; + var r = loose ? re[LOOSE] : re[FULL]; + if (!r.test(version)) + return null; - if (this.fd === null) { - this._open = fs.open; - this._queue.push([this._open, this.path, this.flags, this.mode, undefined]); - this.flush(); - } + try { + return new SemVer(version, loose); + } catch (er) { + return null; } } +exports.valid = valid; +function valid(version, loose) { + var v = parse(version, loose); + return v ? v.version : null; +} -/***/ }), -/* 103 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; +exports.clean = clean; +function clean(version, loose) { + var s = parse(version.trim().replace(/^[=v]+/, ''), loose); + return s ? s.version : null; +} -module.exports = clone +exports.SemVer = SemVer; -function clone (obj) { - if (obj === null || typeof obj !== 'object') - return obj +function SemVer(version, loose) { + if (version instanceof SemVer) { + if (version.loose === loose) + return version; + else + version = version.version; + } else if (typeof version !== 'string') { + throw new TypeError('Invalid Version: ' + version); + } - if (obj instanceof Object) - var copy = { __proto__: obj.__proto__ } - else - var copy = Object.create(null) + if (version.length > MAX_LENGTH) + throw new TypeError('version is longer than ' + MAX_LENGTH + ' characters') - Object.getOwnPropertyNames(obj).forEach(function (key) { - Object.defineProperty(copy, key, Object.getOwnPropertyDescriptor(obj, key)) - }) + if (!(this instanceof SemVer)) + return new SemVer(version, loose); - return copy -} + debug('SemVer', version, loose); + this.loose = loose; + var m = version.trim().match(loose ? re[LOOSE] : re[FULL]); + if (!m) + throw new TypeError('Invalid Version: ' + version); -/***/ }), -/* 104 */ -/***/ (function(module, exports, __webpack_require__) { + this.raw = version; -"use strict"; + // these are actually numbers + this.major = +m[1]; + this.minor = +m[2]; + this.patch = +m[3]; -module.exports = writeFile -module.exports.sync = writeFileSync -module.exports._getTmpname = getTmpname // for testing -module.exports._cleanupOnExit = cleanupOnExit + if (this.major > MAX_SAFE_INTEGER || this.major < 0) + throw new TypeError('Invalid major version') -var fs = __webpack_require__(105) -var MurmurHash3 = __webpack_require__(109) -var onExit = __webpack_require__(110) -var path = __webpack_require__(16) -var activeFiles = {} + if (this.minor > MAX_SAFE_INTEGER || this.minor < 0) + throw new TypeError('Invalid minor version') -// if we run inside of a worker_thread, `process.pid` is not unique -/* istanbul ignore next */ -var threadId = (function getId () { - try { - var workerThreads = __webpack_require__(112) + if (this.patch > MAX_SAFE_INTEGER || this.patch < 0) + throw new TypeError('Invalid patch version') - /// if we are in main thread, this is set to `0` - return workerThreads.threadId - } catch (e) { - // worker_threads are not available, fallback to 0 - return 0 - } -})() + // numberify any prerelease numeric ids + if (!m[4]) + this.prerelease = []; + else + this.prerelease = m[4].split('.').map(function(id) { + if (/^[0-9]+$/.test(id)) { + var num = +id; + if (num >= 0 && num < MAX_SAFE_INTEGER) + return num; + } + return id; + }); -var invocations = 0 -function getTmpname (filename) { - return filename + '.' + - MurmurHash3(__filename) - .hash(String(process.pid)) - .hash(String(threadId)) - .hash(String(++invocations)) - .result() + this.build = m[5] ? m[5].split('.') : []; + this.format(); } -function cleanupOnExit (tmpfile) { - return function () { - try { - fs.unlinkSync(typeof tmpfile === 'function' ? tmpfile() : tmpfile) - } catch (_) {} - } -} +SemVer.prototype.format = function() { + this.version = this.major + '.' + this.minor + '.' + this.patch; + if (this.prerelease.length) + this.version += '-' + this.prerelease.join('.'); + return this.version; +}; -function writeFile (filename, data, options, callback) { - if (options) { - if (options instanceof Function) { - callback = options - options = {} - } else if (typeof options === 'string') { - options = { encoding: options } - } - } else { - options = {} - } +SemVer.prototype.toString = function() { + return this.version; +}; - var Promise = options.Promise || global.Promise - var truename - var fd - var tmpfile - /* istanbul ignore next -- The closure only gets called when onExit triggers */ - var removeOnExitHandler = onExit(cleanupOnExit(() => tmpfile)) - var absoluteName = path.resolve(filename) +SemVer.prototype.compare = function(other) { + debug('SemVer.compare', this.version, this.loose, other); + if (!(other instanceof SemVer)) + other = new SemVer(other, this.loose); - new Promise(function serializeSameFile (resolve) { - // make a queue if it doesn't already exist - if (!activeFiles[absoluteName]) activeFiles[absoluteName] = [] + return this.compareMain(other) || this.comparePre(other); +}; - activeFiles[absoluteName].push(resolve) // add this job to the queue - if (activeFiles[absoluteName].length === 1) resolve() // kick off the first one - }).then(function getRealPath () { - return new Promise(function (resolve) { - fs.realpath(filename, function (_, realname) { - truename = realname || filename - tmpfile = getTmpname(truename) - resolve() - }) - }) - }).then(function stat () { - return new Promise(function stat (resolve) { - if (options.mode && options.chown) resolve() - else { - // Either mode or chown is not explicitly set - // Default behavior is to copy it from original file - fs.stat(truename, function (err, stats) { - if (err || !stats) resolve() - else { - options = Object.assign({}, options) +SemVer.prototype.compareMain = function(other) { + if (!(other instanceof SemVer)) + other = new SemVer(other, this.loose); - if (options.mode == null) { - options.mode = stats.mode - } - if (options.chown == null && process.getuid) { - options.chown = { uid: stats.uid, gid: stats.gid } - } - resolve() - } - }) - } - }) - }).then(function thenWriteFile () { - return new Promise(function (resolve, reject) { - fs.open(tmpfile, 'w', options.mode, function (err, _fd) { - fd = _fd - if (err) reject(err) - else resolve() - }) - }) - }).then(function write () { - return new Promise(function (resolve, reject) { - if (Buffer.isBuffer(data)) { - fs.write(fd, data, 0, data.length, 0, function (err) { - if (err) reject(err) - else resolve() - }) - } else if (data != null) { - fs.write(fd, String(data), 0, String(options.encoding || 'utf8'), function (err) { - if (err) reject(err) - else resolve() - }) - } else resolve() - }) - }).then(function syncAndClose () { - return new Promise(function (resolve, reject) { - if (options.fsync !== false) { - fs.fsync(fd, function (err) { - if (err) fs.close(fd, () => reject(err)) - else fs.close(fd, resolve) - }) - } else { - fs.close(fd, resolve) + return compareIdentifiers(this.major, other.major) || + compareIdentifiers(this.minor, other.minor) || + compareIdentifiers(this.patch, other.patch); +}; + +SemVer.prototype.comparePre = function(other) { + if (!(other instanceof SemVer)) + other = new SemVer(other, this.loose); + + // NOT having a prerelease is > having one + if (this.prerelease.length && !other.prerelease.length) + return -1; + else if (!this.prerelease.length && other.prerelease.length) + return 1; + else if (!this.prerelease.length && !other.prerelease.length) + return 0; + + var i = 0; + do { + var a = this.prerelease[i]; + var b = other.prerelease[i]; + debug('prerelease compare', i, a, b); + if (a === undefined && b === undefined) + return 0; + else if (b === undefined) + return 1; + else if (a === undefined) + return -1; + else if (a === b) + continue; + else + return compareIdentifiers(a, b); + } while (++i); +}; + +// preminor will bump the version up to the next minor release, and immediately +// down to pre-release. premajor and prepatch work the same way. +SemVer.prototype.inc = function(release, identifier) { + switch (release) { + case 'premajor': + this.prerelease.length = 0; + this.patch = 0; + this.minor = 0; + this.major++; + this.inc('pre', identifier); + break; + case 'preminor': + this.prerelease.length = 0; + this.patch = 0; + this.minor++; + this.inc('pre', identifier); + break; + case 'prepatch': + // If this is already a prerelease, it will bump to the next version + // drop any prereleases that might already exist, since they are not + // relevant at this point. + this.prerelease.length = 0; + this.inc('patch', identifier); + this.inc('pre', identifier); + break; + // If the input is a non-prerelease version, this acts the same as + // prepatch. + case 'prerelease': + if (this.prerelease.length === 0) + this.inc('patch', identifier); + this.inc('pre', identifier); + break; + + case 'major': + // If this is a pre-major version, bump up to the same major version. + // Otherwise increment major. + // 1.0.0-5 bumps to 1.0.0 + // 1.1.0 bumps to 2.0.0 + if (this.minor !== 0 || this.patch !== 0 || this.prerelease.length === 0) + this.major++; + this.minor = 0; + this.patch = 0; + this.prerelease = []; + break; + case 'minor': + // If this is a pre-minor version, bump up to the same minor version. + // Otherwise increment minor. + // 1.2.0-5 bumps to 1.2.0 + // 1.2.1 bumps to 1.3.0 + if (this.patch !== 0 || this.prerelease.length === 0) + this.minor++; + this.patch = 0; + this.prerelease = []; + break; + case 'patch': + // If this is not a pre-release version, it will increment the patch. + // If it is a pre-release it will bump up to the same patch version. + // 1.2.0-5 patches to 1.2.0 + // 1.2.0 patches to 1.2.1 + if (this.prerelease.length === 0) + this.patch++; + this.prerelease = []; + break; + // This probably shouldn't be used publicly. + // 1.0.0 "pre" would become 1.0.0-0 which is the wrong direction. + case 'pre': + if (this.prerelease.length === 0) + this.prerelease = [0]; + else { + var i = this.prerelease.length; + while (--i >= 0) { + if (typeof this.prerelease[i] === 'number') { + this.prerelease[i]++; + i = -2; + } + } + if (i === -1) // didn't increment anything + this.prerelease.push(0); } - }) - }).then(function chown () { - fd = null - if (options.chown) { - return new Promise(function (resolve, reject) { - fs.chown(tmpfile, options.chown.uid, options.chown.gid, function (err) { - if (err) reject(err) - else resolve() - }) - }) - } - }).then(function chmod () { - if (options.mode) { - return new Promise(function (resolve, reject) { - fs.chmod(tmpfile, options.mode, function (err) { - if (err) reject(err) - else resolve() - }) - }) - } - }).then(function rename () { - return new Promise(function (resolve, reject) { - fs.rename(tmpfile, truename, function (err) { - if (err) reject(err) - else resolve() - }) - }) - }).then(function success () { - removeOnExitHandler() - callback() - }, function fail (err) { - return new Promise(resolve => { - return fd ? fs.close(fd, resolve) : resolve() - }).then(() => { - removeOnExitHandler() - fs.unlink(tmpfile, function () { - callback(err) - }) - }) - }).then(function checkQueue () { - activeFiles[absoluteName].shift() // remove the element added by serializeSameFile - if (activeFiles[absoluteName].length > 0) { - activeFiles[absoluteName][0]() // start next job if one is pending - } else delete activeFiles[absoluteName] - }) -} + if (identifier) { + // 1.2.0-beta.1 bumps to 1.2.0-beta.2, + // 1.2.0-beta.fooblz or 1.2.0-beta bumps to 1.2.0-beta.0 + if (this.prerelease[0] === identifier) { + if (isNaN(this.prerelease[1])) + this.prerelease = [identifier, 0]; + } else + this.prerelease = [identifier, 0]; + } + break; + + default: + throw new Error('invalid increment argument: ' + release); + } + this.format(); + this.raw = this.version; + return this; +}; + +exports.inc = inc; +function inc(version, release, loose, identifier) { + if (typeof(loose) === 'string') { + identifier = loose; + loose = undefined; + } -function writeFileSync (filename, data, options) { - if (typeof options === 'string') options = { encoding: options } - else if (!options) options = {} try { - filename = fs.realpathSync(filename) - } catch (ex) { - // it's ok, it'll happen on a not yet existing file + return new SemVer(version, loose).inc(release, identifier).version; + } catch (er) { + return null; } - var tmpfile = getTmpname(filename) +} - if (!options.mode || !options.chown) { - // Either mode or chown is not explicitly set - // Default behavior is to copy it from original file - try { - var stats = fs.statSync(filename) - options = Object.assign({}, options) - if (!options.mode) { - options.mode = stats.mode +exports.diff = diff; +function diff(version1, version2) { + if (eq(version1, version2)) { + return null; + } else { + var v1 = parse(version1); + var v2 = parse(version2); + if (v1.prerelease.length || v2.prerelease.length) { + for (var key in v1) { + if (key === 'major' || key === 'minor' || key === 'patch') { + if (v1[key] !== v2[key]) { + return 'pre'+key; + } + } } - if (!options.chown && process.getuid) { - options.chown = { uid: stats.uid, gid: stats.gid } + return 'prerelease'; + } + for (var key in v1) { + if (key === 'major' || key === 'minor' || key === 'patch') { + if (v1[key] !== v2[key]) { + return key; + } } - } catch (ex) { - // ignore stat errors } } +} - var fd - var cleanup = cleanupOnExit(tmpfile) - var removeOnExitHandler = onExit(cleanup) +exports.compareIdentifiers = compareIdentifiers; - try { - fd = fs.openSync(tmpfile, 'w', options.mode) - if (Buffer.isBuffer(data)) { - fs.writeSync(fd, data, 0, data.length, 0) - } else if (data != null) { - fs.writeSync(fd, String(data), 0, String(options.encoding || 'utf8')) - } - if (options.fsync !== false) { - fs.fsyncSync(fd) - } - fs.closeSync(fd) - if (options.chown) fs.chownSync(tmpfile, options.chown.uid, options.chown.gid) - if (options.mode) fs.chmodSync(tmpfile, options.mode) - fs.renameSync(tmpfile, filename) - removeOnExitHandler() - } catch (err) { - if (fd) { - try { - fs.closeSync(fd) - } catch (ex) { - // ignore close errors at this stage, error may have closed fd already. - } - } - removeOnExitHandler() - cleanup() - throw err +var numeric = /^[0-9]+$/; +function compareIdentifiers(a, b) { + var anum = numeric.test(a); + var bnum = numeric.test(b); + + if (anum && bnum) { + a = +a; + b = +b; } + + return (anum && !bnum) ? -1 : + (bnum && !anum) ? 1 : + a < b ? -1 : + a > b ? 1 : + 0; } +exports.rcompareIdentifiers = rcompareIdentifiers; +function rcompareIdentifiers(a, b) { + return compareIdentifiers(b, a); +} -/***/ }), -/* 105 */ -/***/ (function(module, exports, __webpack_require__) { +exports.major = major; +function major(a, loose) { + return new SemVer(a, loose).major; +} -var fs = __webpack_require__(23) -var polyfills = __webpack_require__(106) -var legacy = __webpack_require__(108) -var queue = [] +exports.minor = minor; +function minor(a, loose) { + return new SemVer(a, loose).minor; +} -var util = __webpack_require__(29) +exports.patch = patch; +function patch(a, loose) { + return new SemVer(a, loose).patch; +} -function noop () {} +exports.compare = compare; +function compare(a, b, loose) { + return new SemVer(a, loose).compare(new SemVer(b, loose)); +} -var debug = noop -if (util.debuglog) - debug = util.debuglog('gfs4') -else if (/\bgfs4\b/i.test(process.env.NODE_DEBUG || '')) - debug = function() { - var m = util.format.apply(util, arguments) - m = 'GFS4: ' + m.split(/\n/).join('\nGFS4: ') - console.error(m) - } +exports.compareLoose = compareLoose; +function compareLoose(a, b) { + return compare(a, b, true); +} -if (/\bgfs4\b/i.test(process.env.NODE_DEBUG || '')) { - process.on('exit', function() { - debug(queue) - __webpack_require__(30).equal(queue.length, 0) - }) +exports.rcompare = rcompare; +function rcompare(a, b, loose) { + return compare(b, a, loose); } -module.exports = patch(__webpack_require__(107)) -if (process.env.TEST_GRACEFUL_FS_GLOBAL_PATCH) { - module.exports = patch(fs) +exports.sort = sort; +function sort(list, loose) { + return list.sort(function(a, b) { + return exports.compare(a, b, loose); + }); } -// Always patch fs.close/closeSync, because we want to -// retry() whenever a close happens *anywhere* in the program. -// This is essential when multiple graceful-fs instances are -// in play at the same time. -module.exports.close = -fs.close = (function (fs$close) { return function (fd, cb) { - return fs$close.call(fs, fd, function (err) { - if (!err) - retry() +exports.rsort = rsort; +function rsort(list, loose) { + return list.sort(function(a, b) { + return exports.rcompare(a, b, loose); + }); +} - if (typeof cb === 'function') - cb.apply(this, arguments) - }) -}})(fs.close) +exports.gt = gt; +function gt(a, b, loose) { + return compare(a, b, loose) > 0; +} -module.exports.closeSync = -fs.closeSync = (function (fs$closeSync) { return function (fd) { - // Note that graceful-fs also retries when fs.closeSync() fails. - // Looks like a bug to me, although it's probably a harmless one. - var rval = fs$closeSync.apply(fs, arguments) - retry() - return rval -}})(fs.closeSync) +exports.lt = lt; +function lt(a, b, loose) { + return compare(a, b, loose) < 0; +} -function patch (fs) { - // Everything that references the open() function needs to be in here - polyfills(fs) - fs.gracefulify = patch - fs.FileReadStream = ReadStream; // Legacy name. - fs.FileWriteStream = WriteStream; // Legacy name. - fs.createReadStream = createReadStream - fs.createWriteStream = createWriteStream - var fs$readFile = fs.readFile - fs.readFile = readFile - function readFile (path, options, cb) { - if (typeof options === 'function') - cb = options, options = null +exports.eq = eq; +function eq(a, b, loose) { + return compare(a, b, loose) === 0; +} - return go$readFile(path, options, cb) +exports.neq = neq; +function neq(a, b, loose) { + return compare(a, b, loose) !== 0; +} - function go$readFile (path, options, cb) { - return fs$readFile(path, options, function (err) { - if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) - enqueue([go$readFile, [path, options, cb]]) - else { - if (typeof cb === 'function') - cb.apply(this, arguments) - retry() - } - }) - } - } +exports.gte = gte; +function gte(a, b, loose) { + return compare(a, b, loose) >= 0; +} - var fs$writeFile = fs.writeFile - fs.writeFile = writeFile - function writeFile (path, data, options, cb) { - if (typeof options === 'function') - cb = options, options = null +exports.lte = lte; +function lte(a, b, loose) { + return compare(a, b, loose) <= 0; +} - return go$writeFile(path, data, options, cb) +exports.cmp = cmp; +function cmp(a, op, b, loose) { + var ret; + switch (op) { + case '===': + if (typeof a === 'object') a = a.version; + if (typeof b === 'object') b = b.version; + ret = a === b; + break; + case '!==': + if (typeof a === 'object') a = a.version; + if (typeof b === 'object') b = b.version; + ret = a !== b; + break; + case '': case '=': case '==': ret = eq(a, b, loose); break; + case '!=': ret = neq(a, b, loose); break; + case '>': ret = gt(a, b, loose); break; + case '>=': ret = gte(a, b, loose); break; + case '<': ret = lt(a, b, loose); break; + case '<=': ret = lte(a, b, loose); break; + default: throw new TypeError('Invalid operator: ' + op); + } + return ret; +} - function go$writeFile (path, data, options, cb) { - return fs$writeFile(path, data, options, function (err) { - if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) - enqueue([go$writeFile, [path, data, options, cb]]) - else { - if (typeof cb === 'function') - cb.apply(this, arguments) - retry() - } - }) - } +exports.Comparator = Comparator; +function Comparator(comp, loose) { + if (comp instanceof Comparator) { + if (comp.loose === loose) + return comp; + else + comp = comp.value; } - var fs$appendFile = fs.appendFile - if (fs$appendFile) - fs.appendFile = appendFile - function appendFile (path, data, options, cb) { - if (typeof options === 'function') - cb = options, options = null + if (!(this instanceof Comparator)) + return new Comparator(comp, loose); - return go$appendFile(path, data, options, cb) + debug('comparator', comp, loose); + this.loose = loose; + this.parse(comp); - function go$appendFile (path, data, options, cb) { - return fs$appendFile(path, data, options, function (err) { - if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) - enqueue([go$appendFile, [path, data, options, cb]]) - else { - if (typeof cb === 'function') - cb.apply(this, arguments) - retry() - } - }) - } + if (this.semver === ANY) + this.value = ''; + else + this.value = this.operator + this.semver.version; + + debug('comp', this); +} + +var ANY = {}; +Comparator.prototype.parse = function(comp) { + var r = this.loose ? re[COMPARATORLOOSE] : re[COMPARATOR]; + var m = comp.match(r); + + if (!m) + throw new TypeError('Invalid comparator: ' + comp); + + this.operator = m[1]; + if (this.operator === '=') + this.operator = ''; + + // if it literally is just '>' or '' then allow anything. + if (!m[2]) + this.semver = ANY; + else + this.semver = new SemVer(m[2], this.loose); +}; + +Comparator.prototype.toString = function() { + return this.value; +}; + +Comparator.prototype.test = function(version) { + debug('Comparator.test', version, this.loose); + + if (this.semver === ANY) + return true; + + if (typeof version === 'string') + version = new SemVer(version, this.loose); + + return cmp(version, this.operator, this.semver, this.loose); +}; + +Comparator.prototype.intersects = function(comp, loose) { + if (!(comp instanceof Comparator)) { + throw new TypeError('a Comparator is required'); } - var fs$readdir = fs.readdir - fs.readdir = readdir - function readdir (path, options, cb) { - var args = [path] - if (typeof options !== 'function') { - args.push(options) - } else { - cb = options - } - args.push(go$readdir$cb) + var rangeTmp; - return go$readdir(args) + if (this.operator === '') { + rangeTmp = new Range(comp.value, loose); + return satisfies(this.value, rangeTmp, loose); + } else if (comp.operator === '') { + rangeTmp = new Range(this.value, loose); + return satisfies(comp.semver, rangeTmp, loose); + } - function go$readdir$cb (err, files) { - if (files && files.sort) - files.sort() + var sameDirectionIncreasing = + (this.operator === '>=' || this.operator === '>') && + (comp.operator === '>=' || comp.operator === '>'); + var sameDirectionDecreasing = + (this.operator === '<=' || this.operator === '<') && + (comp.operator === '<=' || comp.operator === '<'); + var sameSemVer = this.semver.version === comp.semver.version; + var differentDirectionsInclusive = + (this.operator === '>=' || this.operator === '<=') && + (comp.operator === '>=' || comp.operator === '<='); + var oppositeDirectionsLessThan = + cmp(this.semver, '<', comp.semver, loose) && + ((this.operator === '>=' || this.operator === '>') && + (comp.operator === '<=' || comp.operator === '<')); + var oppositeDirectionsGreaterThan = + cmp(this.semver, '>', comp.semver, loose) && + ((this.operator === '<=' || this.operator === '<') && + (comp.operator === '>=' || comp.operator === '>')); - if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) - enqueue([go$readdir, [args]]) - else { - if (typeof cb === 'function') - cb.apply(this, arguments) - retry() - } + return sameDirectionIncreasing || sameDirectionDecreasing || + (sameSemVer && differentDirectionsInclusive) || + oppositeDirectionsLessThan || oppositeDirectionsGreaterThan; +}; + + +exports.Range = Range; +function Range(range, loose) { + if (range instanceof Range) { + if (range.loose === loose) { + return range; + } else { + return new Range(range.raw, loose); } } - function go$readdir (args) { - return fs$readdir.apply(fs, args) + if (range instanceof Comparator) { + return new Range(range.value, loose); } - if (process.version.substr(0, 4) === 'v0.8') { - var legStreams = legacy(fs) - ReadStream = legStreams.ReadStream - WriteStream = legStreams.WriteStream + if (!(this instanceof Range)) + return new Range(range, loose); + + this.loose = loose; + + // First, split based on boolean or || + this.raw = range; + this.set = range.split(/\s*\|\|\s*/).map(function(range) { + return this.parseRange(range.trim()); + }, this).filter(function(c) { + // throw out any that are not relevant for whatever reason + return c.length; + }); + + if (!this.set.length) { + throw new TypeError('Invalid SemVer Range: ' + range); } - var fs$ReadStream = fs.ReadStream - ReadStream.prototype = Object.create(fs$ReadStream.prototype) - ReadStream.prototype.open = ReadStream$open + this.format(); +} - var fs$WriteStream = fs.WriteStream - WriteStream.prototype = Object.create(fs$WriteStream.prototype) - WriteStream.prototype.open = WriteStream$open +Range.prototype.format = function() { + this.range = this.set.map(function(comps) { + return comps.join(' ').trim(); + }).join('||').trim(); + return this.range; +}; - fs.ReadStream = ReadStream - fs.WriteStream = WriteStream +Range.prototype.toString = function() { + return this.range; +}; - function ReadStream (path, options) { - if (this instanceof ReadStream) - return fs$ReadStream.apply(this, arguments), this - else - return ReadStream.apply(Object.create(ReadStream.prototype), arguments) - } +Range.prototype.parseRange = function(range) { + var loose = this.loose; + range = range.trim(); + debug('range', range, loose); + // `1.2.3 - 1.2.4` => `>=1.2.3 <=1.2.4` + var hr = loose ? re[HYPHENRANGELOOSE] : re[HYPHENRANGE]; + range = range.replace(hr, hyphenReplace); + debug('hyphen replace', range); + // `> 1.2.3 < 1.2.5` => `>1.2.3 <1.2.5` + range = range.replace(re[COMPARATORTRIM], comparatorTrimReplace); + debug('comparator trim', range, re[COMPARATORTRIM]); - function ReadStream$open () { - var that = this - open(that.path, that.flags, that.mode, function (err, fd) { - if (err) { - if (that.autoClose) - that.destroy() + // `~ 1.2.3` => `~1.2.3` + range = range.replace(re[TILDETRIM], tildeTrimReplace); - that.emit('error', err) - } else { - that.fd = fd - that.emit('open', fd) - that.read() - } - }) - } + // `^ 1.2.3` => `^1.2.3` + range = range.replace(re[CARETTRIM], caretTrimReplace); - function WriteStream (path, options) { - if (this instanceof WriteStream) - return fs$WriteStream.apply(this, arguments), this - else - return WriteStream.apply(Object.create(WriteStream.prototype), arguments) - } + // normalize spaces + range = range.split(/\s+/).join(' '); - function WriteStream$open () { - var that = this - open(that.path, that.flags, that.mode, function (err, fd) { - if (err) { - that.destroy() - that.emit('error', err) - } else { - that.fd = fd - that.emit('open', fd) - } - }) - } + // At this point, the range is completely trimmed and + // ready to be split into comparators. - function createReadStream (path, options) { - return new ReadStream(path, options) + var compRe = loose ? re[COMPARATORLOOSE] : re[COMPARATOR]; + var set = range.split(' ').map(function(comp) { + return parseComparator(comp, loose); + }).join(' ').split(/\s+/); + if (this.loose) { + // in loose mode, throw out any that are not valid comparators + set = set.filter(function(comp) { + return !!comp.match(compRe); + }); } + set = set.map(function(comp) { + return new Comparator(comp, loose); + }); - function createWriteStream (path, options) { - return new WriteStream(path, options) - } + return set; +}; - var fs$open = fs.open - fs.open = open - function open (path, flags, mode, cb) { - if (typeof mode === 'function') - cb = mode, mode = null +Range.prototype.intersects = function(range, loose) { + if (!(range instanceof Range)) { + throw new TypeError('a Range is required'); + } - return go$open(path, flags, mode, cb) + return this.set.some(function(thisComparators) { + return thisComparators.every(function(thisComparator) { + return range.set.some(function(rangeComparators) { + return rangeComparators.every(function(rangeComparator) { + return thisComparator.intersects(rangeComparator, loose); + }); + }); + }); + }); +}; - function go$open (path, flags, mode, cb) { - return fs$open(path, flags, mode, function (err, fd) { - if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) - enqueue([go$open, [path, flags, mode, cb]]) - else { - if (typeof cb === 'function') - cb.apply(this, arguments) - retry() - } - }) - } - } +// Mostly just for testing and legacy API reasons +exports.toComparators = toComparators; +function toComparators(range, loose) { + return new Range(range, loose).set.map(function(comp) { + return comp.map(function(c) { + return c.value; + }).join(' ').trim().split(' '); + }); +} - return fs +// comprised of xranges, tildes, stars, and gtlt's at this point. +// already replaced the hyphen ranges +// turn into a set of JUST comparators. +function parseComparator(comp, loose) { + debug('comp', comp); + comp = replaceCarets(comp, loose); + debug('caret', comp); + comp = replaceTildes(comp, loose); + debug('tildes', comp); + comp = replaceXRanges(comp, loose); + debug('xrange', comp); + comp = replaceStars(comp, loose); + debug('stars', comp); + return comp; } -function enqueue (elem) { - debug('ENQUEUE', elem[0].name, elem[1]) - queue.push(elem) +function isX(id) { + return !id || id.toLowerCase() === 'x' || id === '*'; } -function retry () { - var elem = queue.shift() - if (elem) { - debug('RETRY', elem[0].name, elem[1]) - elem[0].apply(null, elem[1]) - } +// ~, ~> --> * (any, kinda silly) +// ~2, ~2.x, ~2.x.x, ~>2, ~>2.x ~>2.x.x --> >=2.0.0 <3.0.0 +// ~2.0, ~2.0.x, ~>2.0, ~>2.0.x --> >=2.0.0 <2.1.0 +// ~1.2, ~1.2.x, ~>1.2, ~>1.2.x --> >=1.2.0 <1.3.0 +// ~1.2.3, ~>1.2.3 --> >=1.2.3 <1.3.0 +// ~1.2.0, ~>1.2.0 --> >=1.2.0 <1.3.0 +function replaceTildes(comp, loose) { + return comp.trim().split(/\s+/).map(function(comp) { + return replaceTilde(comp, loose); + }).join(' '); } +function replaceTilde(comp, loose) { + var r = loose ? re[TILDELOOSE] : re[TILDE]; + return comp.replace(r, function(_, M, m, p, pr) { + debug('tilde', comp, _, M, m, p, pr); + var ret; -/***/ }), -/* 106 */ -/***/ (function(module, exports, __webpack_require__) { + if (isX(M)) + ret = ''; + else if (isX(m)) + ret = '>=' + M + '.0.0 <' + (+M + 1) + '.0.0'; + else if (isX(p)) + // ~1.2 == >=1.2.0 <1.3.0 + ret = '>=' + M + '.' + m + '.0 <' + M + '.' + (+m + 1) + '.0'; + else if (pr) { + debug('replaceTilde pr', pr); + if (pr.charAt(0) !== '-') + pr = '-' + pr; + ret = '>=' + M + '.' + m + '.' + p + pr + + ' <' + M + '.' + (+m + 1) + '.0'; + } else + // ~1.2.3 == >=1.2.3 <1.3.0 + ret = '>=' + M + '.' + m + '.' + p + + ' <' + M + '.' + (+m + 1) + '.0'; -var fs = __webpack_require__(107) -var constants = __webpack_require__(26) + debug('tilde return', ret); + return ret; + }); +} -var origCwd = process.cwd -var cwd = null +// ^ --> * (any, kinda silly) +// ^2, ^2.x, ^2.x.x --> >=2.0.0 <3.0.0 +// ^2.0, ^2.0.x --> >=2.0.0 <3.0.0 +// ^1.2, ^1.2.x --> >=1.2.0 <2.0.0 +// ^1.2.3 --> >=1.2.3 <2.0.0 +// ^1.2.0 --> >=1.2.0 <2.0.0 +function replaceCarets(comp, loose) { + return comp.trim().split(/\s+/).map(function(comp) { + return replaceCaret(comp, loose); + }).join(' '); +} -var platform = process.env.GRACEFUL_FS_PLATFORM || process.platform +function replaceCaret(comp, loose) { + debug('caret', comp, loose); + var r = loose ? re[CARETLOOSE] : re[CARET]; + return comp.replace(r, function(_, M, m, p, pr) { + debug('caret', comp, _, M, m, p, pr); + var ret; -process.cwd = function() { - if (!cwd) - cwd = origCwd.call(process) - return cwd -} -try { - process.cwd() -} catch (er) {} + if (isX(M)) + ret = ''; + else if (isX(m)) + ret = '>=' + M + '.0.0 <' + (+M + 1) + '.0.0'; + else if (isX(p)) { + if (M === '0') + ret = '>=' + M + '.' + m + '.0 <' + M + '.' + (+m + 1) + '.0'; + else + ret = '>=' + M + '.' + m + '.0 <' + (+M + 1) + '.0.0'; + } else if (pr) { + debug('replaceCaret pr', pr); + if (pr.charAt(0) !== '-') + pr = '-' + pr; + if (M === '0') { + if (m === '0') + ret = '>=' + M + '.' + m + '.' + p + pr + + ' <' + M + '.' + m + '.' + (+p + 1); + else + ret = '>=' + M + '.' + m + '.' + p + pr + + ' <' + M + '.' + (+m + 1) + '.0'; + } else + ret = '>=' + M + '.' + m + '.' + p + pr + + ' <' + (+M + 1) + '.0.0'; + } else { + debug('no pr'); + if (M === '0') { + if (m === '0') + ret = '>=' + M + '.' + m + '.' + p + + ' <' + M + '.' + m + '.' + (+p + 1); + else + ret = '>=' + M + '.' + m + '.' + p + + ' <' + M + '.' + (+m + 1) + '.0'; + } else + ret = '>=' + M + '.' + m + '.' + p + + ' <' + (+M + 1) + '.0.0'; + } -var chdir = process.chdir -process.chdir = function(d) { - cwd = null - chdir.call(process, d) + debug('caret return', ret); + return ret; + }); } -module.exports = patch +function replaceXRanges(comp, loose) { + debug('replaceXRanges', comp, loose); + return comp.split(/\s+/).map(function(comp) { + return replaceXRange(comp, loose); + }).join(' '); +} -function patch (fs) { - // (re-)implement some things that are known busted or missing. +function replaceXRange(comp, loose) { + comp = comp.trim(); + var r = loose ? re[XRANGELOOSE] : re[XRANGE]; + return comp.replace(r, function(ret, gtlt, M, m, p, pr) { + debug('xRange', comp, ret, gtlt, M, m, p, pr); + var xM = isX(M); + var xm = xM || isX(m); + var xp = xm || isX(p); + var anyX = xp; - // lchmod, broken prior to 0.6.2 - // back-port the fix here. - if (constants.hasOwnProperty('O_SYMLINK') && - process.version.match(/^v0\.6\.[0-2]|^v0\.5\./)) { - patchLchmod(fs) - } + if (gtlt === '=' && anyX) + gtlt = ''; - // lutimes implementation, or no-op - if (!fs.lutimes) { - patchLutimes(fs) - } + if (xM) { + if (gtlt === '>' || gtlt === '<') { + // nothing is allowed + ret = '<0.0.0'; + } else { + // nothing is forbidden + ret = '*'; + } + } else if (gtlt && anyX) { + // replace X with 0 + if (xm) + m = 0; + if (xp) + p = 0; - // https://github.com/isaacs/node-graceful-fs/issues/4 - // Chown should not fail on einval or eperm if non-root. - // It should not fail on enosys ever, as this just indicates - // that a fs doesn't support the intended operation. + if (gtlt === '>') { + // >1 => >=2.0.0 + // >1.2 => >=1.3.0 + // >1.2.3 => >= 1.2.4 + gtlt = '>='; + if (xm) { + M = +M + 1; + m = 0; + p = 0; + } else if (xp) { + m = +m + 1; + p = 0; + } + } else if (gtlt === '<=') { + // <=0.7.x is actually <0.8.0, since any 0.7.x should + // pass. Similarly, <=7.x is actually <8.0.0, etc. + gtlt = '<'; + if (xm) + M = +M + 1; + else + m = +m + 1; + } - fs.chown = chownFix(fs.chown) - fs.fchown = chownFix(fs.fchown) - fs.lchown = chownFix(fs.lchown) + ret = gtlt + M + '.' + m + '.' + p; + } else if (xm) { + ret = '>=' + M + '.0.0 <' + (+M + 1) + '.0.0'; + } else if (xp) { + ret = '>=' + M + '.' + m + '.0 <' + M + '.' + (+m + 1) + '.0'; + } - fs.chmod = chmodFix(fs.chmod) - fs.fchmod = chmodFix(fs.fchmod) - fs.lchmod = chmodFix(fs.lchmod) + debug('xRange return', ret); - fs.chownSync = chownFixSync(fs.chownSync) - fs.fchownSync = chownFixSync(fs.fchownSync) - fs.lchownSync = chownFixSync(fs.lchownSync) + return ret; + }); +} - fs.chmodSync = chmodFixSync(fs.chmodSync) - fs.fchmodSync = chmodFixSync(fs.fchmodSync) - fs.lchmodSync = chmodFixSync(fs.lchmodSync) +// Because * is AND-ed with everything else in the comparator, +// and '' means "any version", just remove the *s entirely. +function replaceStars(comp, loose) { + debug('replaceStars', comp, loose); + // Looseness is ignored here. star is always as loose as it gets! + return comp.trim().replace(re[STAR], ''); +} - fs.stat = statFix(fs.stat) - fs.fstat = statFix(fs.fstat) - fs.lstat = statFix(fs.lstat) +// This function is passed to string.replace(re[HYPHENRANGE]) +// M, m, patch, prerelease, build +// 1.2 - 3.4.5 => >=1.2.0 <=3.4.5 +// 1.2.3 - 3.4 => >=1.2.0 <3.5.0 Any 3.4.x will do +// 1.2 - 3.4 => >=1.2.0 <3.5.0 +function hyphenReplace($0, + from, fM, fm, fp, fpr, fb, + to, tM, tm, tp, tpr, tb) { - fs.statSync = statFixSync(fs.statSync) - fs.fstatSync = statFixSync(fs.fstatSync) - fs.lstatSync = statFixSync(fs.lstatSync) + if (isX(fM)) + from = ''; + else if (isX(fm)) + from = '>=' + fM + '.0.0'; + else if (isX(fp)) + from = '>=' + fM + '.' + fm + '.0'; + else + from = '>=' + from; - // if lchmod/lchown do not exist, then make them no-ops - if (!fs.lchmod) { - fs.lchmod = function (path, mode, cb) { - if (cb) process.nextTick(cb) - } - fs.lchmodSync = function () {} - } - if (!fs.lchown) { - fs.lchown = function (path, uid, gid, cb) { - if (cb) process.nextTick(cb) - } - fs.lchownSync = function () {} - } + if (isX(tM)) + to = ''; + else if (isX(tm)) + to = '<' + (+tM + 1) + '.0.0'; + else if (isX(tp)) + to = '<' + tM + '.' + (+tm + 1) + '.0'; + else if (tpr) + to = '<=' + tM + '.' + tm + '.' + tp + '-' + tpr; + else + to = '<=' + to; - // on Windows, A/V software can lock the directory, causing this - // to fail with an EACCES or EPERM if the directory contains newly - // created files. Try again on failure, for up to 60 seconds. + return (from + ' ' + to).trim(); +} - // Set the timeout this long because some Windows Anti-Virus, such as Parity - // bit9, may lock files for up to a minute, causing npm package install - // failures. Also, take care to yield the scheduler. Windows scheduling gives - // CPU to a busy looping process, which can cause the program causing the lock - // contention to be starved of CPU by node, so the contention doesn't resolve. - if (platform === "win32") { - fs.rename = (function (fs$rename) { return function (from, to, cb) { - var start = Date.now() - var backoff = 0; - fs$rename(from, to, function CB (er) { - if (er - && (er.code === "EACCES" || er.code === "EPERM") - && Date.now() - start < 60000) { - setTimeout(function() { - fs.stat(to, function (stater, st) { - if (stater && stater.code === "ENOENT") - fs$rename(from, to, CB); - else - cb(er) - }) - }, backoff) - if (backoff < 100) - backoff += 10; - return; - } - if (cb) cb(er) - }) - }})(fs.rename) - } - // if read() returns EAGAIN, then just try it again. - fs.read = (function (fs$read) { return function (fd, buffer, offset, length, position, callback_) { - var callback - if (callback_ && typeof callback_ === 'function') { - var eagCounter = 0 - callback = function (er, _, __) { - if (er && er.code === 'EAGAIN' && eagCounter < 10) { - eagCounter ++ - return fs$read.call(fs, fd, buffer, offset, length, position, callback) - } - callback_.apply(this, arguments) - } - } - return fs$read.call(fs, fd, buffer, offset, length, position, callback) - }})(fs.read) +// if ANY of the sets match ALL of its comparators, then pass +Range.prototype.test = function(version) { + if (!version) + return false; - fs.readSync = (function (fs$readSync) { return function (fd, buffer, offset, length, position) { - var eagCounter = 0 - while (true) { - try { - return fs$readSync.call(fs, fd, buffer, offset, length, position) - } catch (er) { - if (er.code === 'EAGAIN' && eagCounter < 10) { - eagCounter ++ - continue - } - throw er - } - } - }})(fs.readSync) -} + if (typeof version === 'string') + version = new SemVer(version, this.loose); -function patchLchmod (fs) { - fs.lchmod = function (path, mode, callback) { - fs.open( path - , constants.O_WRONLY | constants.O_SYMLINK - , mode - , function (err, fd) { - if (err) { - if (callback) callback(err) - return - } - // prefer to return the chmod error, if one occurs, - // but still try to close, and report closing errors if they occur. - fs.fchmod(fd, mode, function (err) { - fs.close(fd, function(err2) { - if (callback) callback(err || err2) - }) - }) - }) + for (var i = 0; i < this.set.length; i++) { + if (testSet(this.set[i], version)) + return true; } + return false; +}; - fs.lchmodSync = function (path, mode) { - var fd = fs.openSync(path, constants.O_WRONLY | constants.O_SYMLINK, mode) - - // prefer to return the chmod error, if one occurs, - // but still try to close, and report closing errors if they occur. - var threw = true - var ret - try { - ret = fs.fchmodSync(fd, mode) - threw = false - } finally { - if (threw) { - try { - fs.closeSync(fd) - } catch (er) {} - } else { - fs.closeSync(fd) - } - } - return ret +function testSet(set, version) { + for (var i = 0; i < set.length; i++) { + if (!set[i].test(version)) + return false; } -} -function patchLutimes (fs) { - if (constants.hasOwnProperty("O_SYMLINK")) { - fs.lutimes = function (path, at, mt, cb) { - fs.open(path, constants.O_SYMLINK, function (er, fd) { - if (er) { - if (cb) cb(er) - return - } - fs.futimes(fd, at, mt, function (er) { - fs.close(fd, function (er2) { - if (cb) cb(er || er2) - }) - }) - }) - } + if (version.prerelease.length) { + // Find the set of versions that are allowed to have prereleases + // For example, ^1.2.3-pr.1 desugars to >=1.2.3-pr.1 <2.0.0 + // That should allow `1.2.3-pr.2` to pass. + // However, `1.2.4-alpha.notready` should NOT be allowed, + // even though it's within the range set by the comparators. + for (var i = 0; i < set.length; i++) { + debug(set[i].semver); + if (set[i].semver === ANY) + continue; - fs.lutimesSync = function (path, at, mt) { - var fd = fs.openSync(path, constants.O_SYMLINK) - var ret - var threw = true - try { - ret = fs.futimesSync(fd, at, mt) - threw = false - } finally { - if (threw) { - try { - fs.closeSync(fd) - } catch (er) {} - } else { - fs.closeSync(fd) - } + if (set[i].semver.prerelease.length > 0) { + var allowed = set[i].semver; + if (allowed.major === version.major && + allowed.minor === version.minor && + allowed.patch === version.patch) + return true; } - return ret } - } else { - fs.lutimes = function (_a, _b, _c, cb) { if (cb) process.nextTick(cb) } - fs.lutimesSync = function () {} + // Version has a -pre, but it's not one of the ones we like. + return false; } -} -function chmodFix (orig) { - if (!orig) return orig - return function (target, mode, cb) { - return orig.call(fs, target, mode, function (er) { - if (chownErOk(er)) er = null - if (cb) cb.apply(this, arguments) - }) - } + return true; } -function chmodFixSync (orig) { - if (!orig) return orig - return function (target, mode) { - try { - return orig.call(fs, target, mode) - } catch (er) { - if (!chownErOk(er)) throw er - } +exports.satisfies = satisfies; +function satisfies(version, range, loose) { + try { + range = new Range(range, loose); + } catch (er) { + return false; } + return range.test(version); } - -function chownFix (orig) { - if (!orig) return orig - return function (target, uid, gid, cb) { - return orig.call(fs, target, uid, gid, function (er) { - if (chownErOk(er)) er = null - if (cb) cb.apply(this, arguments) - }) +exports.maxSatisfying = maxSatisfying; +function maxSatisfying(versions, range, loose) { + var max = null; + var maxSV = null; + try { + var rangeObj = new Range(range, loose); + } catch (er) { + return null; } + versions.forEach(function (v) { + if (rangeObj.test(v)) { // satisfies(v, range, loose) + if (!max || maxSV.compare(v) === -1) { // compare(max, v, true) + max = v; + maxSV = new SemVer(max, loose); + } + } + }) + return max; } -function chownFixSync (orig) { - if (!orig) return orig - return function (target, uid, gid) { - try { - return orig.call(fs, target, uid, gid) - } catch (er) { - if (!chownErOk(er)) throw er - } +exports.minSatisfying = minSatisfying; +function minSatisfying(versions, range, loose) { + var min = null; + var minSV = null; + try { + var rangeObj = new Range(range, loose); + } catch (er) { + return null; } + versions.forEach(function (v) { + if (rangeObj.test(v)) { // satisfies(v, range, loose) + if (!min || minSV.compare(v) === 1) { // compare(min, v, true) + min = v; + minSV = new SemVer(min, loose); + } + } + }) + return min; } - -function statFix (orig) { - if (!orig) return orig - // Older versions of Node erroneously returned signed integers for - // uid + gid. - return function (target, cb) { - return orig.call(fs, target, function (er, stats) { - if (!stats) return cb.apply(this, arguments) - if (stats.uid < 0) stats.uid += 0x100000000 - if (stats.gid < 0) stats.gid += 0x100000000 - if (cb) cb.apply(this, arguments) - }) +exports.validRange = validRange; +function validRange(range, loose) { + try { + // Return '*' instead of '' so that truthiness works. + // This will throw if it's invalid anyway + return new Range(range, loose).range || '*'; + } catch (er) { + return null; } } -function statFixSync (orig) { - if (!orig) return orig - // Older versions of Node erroneously returned signed integers for - // uid + gid. - return function (target) { - var stats = orig.call(fs, target) - if (stats.uid < 0) stats.uid += 0x100000000 - if (stats.gid < 0) stats.gid += 0x100000000 - return stats; - } +// Determine if version is less than all the versions possible in the range +exports.ltr = ltr; +function ltr(version, range, loose) { + return outside(version, range, '<', loose); } -// ENOSYS means that the fs doesn't support the op. Just ignore -// that, because it doesn't matter. -// -// if there's no getuid, or if getuid() is something other -// than 0, and the error is EINVAL or EPERM, then just ignore -// it. -// -// This specific case is a silent failure in cp, install, tar, -// and most other unix tools that manage permissions. -// -// When running as root, or if other types of errors are -// encountered, then it's strict. -function chownErOk (er) { - if (!er) - return true +// Determine if version is greater than all the versions possible in the range. +exports.gtr = gtr; +function gtr(version, range, loose) { + return outside(version, range, '>', loose); +} - if (er.code === "ENOSYS") - return true +exports.outside = outside; +function outside(version, range, hilo, loose) { + version = new SemVer(version, loose); + range = new Range(range, loose); - var nonroot = !process.getuid || process.getuid() !== 0 - if (nonroot) { - if (er.code === "EINVAL" || er.code === "EPERM") - return true + var gtfn, ltefn, ltfn, comp, ecomp; + switch (hilo) { + case '>': + gtfn = gt; + ltefn = lte; + ltfn = lt; + comp = '>'; + ecomp = '>='; + break; + case '<': + gtfn = lt; + ltefn = gte; + ltfn = gt; + comp = '<'; + ecomp = '<='; + break; + default: + throw new TypeError('Must provide a hilo val of "<" or ">"'); } - return false -} - - -/***/ }), -/* 107 */ -/***/ (function(module, exports, __webpack_require__) { + // If it satisifes the range it is not outside + if (satisfies(version, range, loose)) { + return false; + } -"use strict"; + // From now on, variable terms are as if we're in "gtr" mode. + // but note that everything is flipped for the "ltr" function. + for (var i = 0; i < range.set.length; ++i) { + var comparators = range.set[i]; -var fs = __webpack_require__(23) + var high = null; + var low = null; -module.exports = clone(fs) + comparators.forEach(function(comparator) { + if (comparator.semver === ANY) { + comparator = new Comparator('>=0.0.0') + } + high = high || comparator; + low = low || comparator; + if (gtfn(comparator.semver, high.semver, loose)) { + high = comparator; + } else if (ltfn(comparator.semver, low.semver, loose)) { + low = comparator; + } + }); -function clone (obj) { - if (obj === null || typeof obj !== 'object') - return obj + // If the edge version comparator has a operator then our version + // isn't outside it + if (high.operator === comp || high.operator === ecomp) { + return false; + } - if (obj instanceof Object) - var copy = { __proto__: obj.__proto__ } - else - var copy = Object.create(null) + // If the lowest version comparator has an operator and our version + // is less than it then it isn't higher than the range + if ((!low.operator || low.operator === comp) && + ltefn(version, low.semver)) { + return false; + } else if (low.operator === ecomp && ltfn(version, low.semver)) { + return false; + } + } + return true; +} - Object.getOwnPropertyNames(obj).forEach(function (key) { - Object.defineProperty(copy, key, Object.getOwnPropertyDescriptor(obj, key)) - }) +exports.prerelease = prerelease; +function prerelease(version, loose) { + var parsed = parse(version, loose); + return (parsed && parsed.prerelease.length) ? parsed.prerelease : null; +} - return copy +exports.intersects = intersects; +function intersects(r1, r2, loose) { + r1 = new Range(r1, loose) + r2 = new Range(r2, loose) + return r1.intersects(r2) } /***/ }), -/* 108 */ +/* 523 */ /***/ (function(module, exports, __webpack_require__) { -var Stream = __webpack_require__(28).Stream - -module.exports = legacy - -function legacy (fs) { - return { - ReadStream: ReadStream, - WriteStream: WriteStream - } - - function ReadStream (path, options) { - if (!(this instanceof ReadStream)) return new ReadStream(path, options); - - Stream.call(this); - - var self = this; - - this.path = path; - this.fd = null; - this.readable = true; - this.paused = false; +var parse = __webpack_require__(524); +var correct = __webpack_require__(526); - this.flags = 'r'; - this.mode = 438; /*=0666*/ - this.bufferSize = 64 * 1024; +var genericWarning = ( + 'license should be ' + + 'a valid SPDX license expression (without "LicenseRef"), ' + + '"UNLICENSED", or ' + + '"SEE LICENSE IN "' +); - options = options || {}; +var fileReferenceRE = /^SEE LICEN[CS]E IN (.+)$/; - // Mixin options into this - var keys = Object.keys(options); - for (var index = 0, length = keys.length; index < length; index++) { - var key = keys[index]; - this[key] = options[key]; - } +function startsWith(prefix, string) { + return string.slice(0, prefix.length) === prefix; +} - if (this.encoding) this.setEncoding(this.encoding); +function usesLicenseRef(ast) { + if (ast.hasOwnProperty('license')) { + var license = ast.license; + return ( + startsWith('LicenseRef', license) || + startsWith('DocumentRef', license) + ); + } else { + return ( + usesLicenseRef(ast.left) || + usesLicenseRef(ast.right) + ); + } +} - if (this.start !== undefined) { - if ('number' !== typeof this.start) { - throw TypeError('start must be a Number'); - } - if (this.end === undefined) { - this.end = Infinity; - } else if ('number' !== typeof this.end) { - throw TypeError('end must be a Number'); - } +module.exports = function(argument) { + var ast; - if (this.start > this.end) { - throw new Error('start must be <= end'); + try { + ast = parse(argument); + } catch (e) { + var match + if ( + argument === 'UNLICENSED' || + argument === 'UNLICENCED' + ) { + return { + validForOldPackages: true, + validForNewPackages: true, + unlicensed: true + }; + } else if (match = fileReferenceRE.exec(argument)) { + return { + validForOldPackages: true, + validForNewPackages: true, + inFile: match[1] + }; + } else { + var result = { + validForOldPackages: false, + validForNewPackages: false, + warnings: [genericWarning] + }; + var corrected = correct(argument); + if (corrected) { + result.warnings.push( + 'license is similar to the valid expression "' + corrected + '"' + ); } - - this.pos = this.start; + return result; } + } - if (this.fd !== null) { - process.nextTick(function() { - self._read(); - }); - return; - } + if (usesLicenseRef(ast)) { + return { + validForNewPackages: false, + validForOldPackages: false, + spdx: true, + warnings: [genericWarning] + }; + } else { + return { + validForNewPackages: true, + validForOldPackages: true, + spdx: true + }; + } +}; - fs.open(this.path, this.flags, this.mode, function (err, fd) { - if (err) { - self.emit('error', err); - self.readable = false; - return; - } - self.fd = fd; - self.emit('open', fd); - self._read(); - }) - } +/***/ }), +/* 524 */ +/***/ (function(module, exports, __webpack_require__) { - function WriteStream (path, options) { - if (!(this instanceof WriteStream)) return new WriteStream(path, options); +var parser = __webpack_require__(525).parser - Stream.call(this); +module.exports = function (argument) { + return parser.parse(argument) +} - this.path = path; - this.fd = null; - this.writable = true; - this.flags = 'w'; - this.encoding = 'binary'; - this.mode = 438; /*=0666*/ - this.bytesWritten = 0; +/***/ }), +/* 525 */ +/***/ (function(module, exports, __webpack_require__) { - options = options || {}; +/* WEBPACK VAR INJECTION */(function(module) {/* parser generated by jison 0.4.17 */ +/* + Returns a Parser object of the following structure: - // Mixin options into this - var keys = Object.keys(options); - for (var index = 0, length = keys.length; index < length; index++) { - var key = keys[index]; - this[key] = options[key]; - } + Parser: { + yy: {} + } - if (this.start !== undefined) { - if ('number' !== typeof this.start) { - throw TypeError('start must be a Number'); - } - if (this.start < 0) { - throw new Error('start must be >= zero'); - } + Parser.prototype: { + yy: {}, + trace: function(), + symbols_: {associative list: name ==> number}, + terminals_: {associative list: number ==> name}, + productions_: [...], + performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate, $$, _$), + table: [...], + defaultActions: {...}, + parseError: function(str, hash), + parse: function(input), - this.pos = this.start; - } + lexer: { + EOF: 1, + parseError: function(str, hash), + setInput: function(input), + input: function(), + unput: function(str), + more: function(), + less: function(n), + pastInput: function(), + upcomingInput: function(), + showPosition: function(), + test_match: function(regex_match_array, rule_index), + next: function(), + lex: function(), + begin: function(condition), + popState: function(), + _currentRules: function(), + topState: function(), + pushState: function(condition), - this.busy = false; - this._queue = []; + options: { + ranges: boolean (optional: true ==> token location info will include a .range[] member) + flex: boolean (optional: true ==> flex-like lexing behaviour where the rules are tested exhaustively to find the longest match) + backtrack_lexer: boolean (optional: true ==> lexer regexes are tested in order and for each matching regex the action code is invoked; the lexer terminates the scan when a token is returned by the action code) + }, - if (this.fd === null) { - this._open = fs.open; - this._queue.push([this._open, this.path, this.flags, this.mode, undefined]); - this.flush(); + performAction: function(yy, yy_, $avoiding_name_collisions, YY_START), + rules: [...], + conditions: {associative list: name ==> set}, } } -} -/***/ }), -/* 109 */ -/***/ (function(module, exports, __webpack_require__) { + token location info (@$, _$, etc.): { + first_line: n, + last_line: n, + first_column: n, + last_column: n, + range: [start_number, end_number] (where the numbers are indexes into the input string, regular zero-based) + } -/** - * @preserve - * JS Implementation of incremental MurmurHash3 (r150) (as of May 10, 2013) - * - * @author Jens Taylor - * @see http://github.com/homebrewing/brauhaus-diff - * @author Gary Court - * @see http://github.com/garycourt/murmurhash-js - * @author Austin Appleby - * @see http://sites.google.com/site/murmurhash/ - */ -(function(){ - var cache; - // Call this function without `new` to use the cached object (good for - // single-threaded environments), or with `new` to create a new object. - // - // @param {string} key A UTF-16 or ASCII string - // @param {number} seed An optional positive integer - // @return {object} A MurmurHash3 object for incremental hashing - function MurmurHash3(key, seed) { - var m = this instanceof MurmurHash3 ? this : cache; - m.reset(seed) - if (typeof key === 'string' && key.length > 0) { - m.hash(key); - } + the parseError function receives a 'hash' object with these members for lexer and parser errors: { + text: (matched text) + token: (the produced terminal token, if any) + line: (yylineno) + } + while parser (grammar) errors will also provide these members, i.e. parser errors deliver a superset of attributes: { + loc: (yylloc) + expected: (string describing the set of expected tokens) + recoverable: (boolean: TRUE when the parser has a error recovery rule available for this particular error) + } +*/ +var spdxparse = (function(){ +var o=function(k,v,o,l){for(o=o||{},l=k.length;l--;o[k[l]]=v);return o},$V0=[1,5],$V1=[1,6],$V2=[1,7],$V3=[1,4],$V4=[1,9],$V5=[1,10],$V6=[5,14,15,17],$V7=[5,12,14,15,17]; +var parser = {trace: function trace() { }, +yy: {}, +symbols_: {"error":2,"start":3,"expression":4,"EOS":5,"simpleExpression":6,"LICENSE":7,"PLUS":8,"LICENSEREF":9,"DOCUMENTREF":10,"COLON":11,"WITH":12,"EXCEPTION":13,"AND":14,"OR":15,"OPEN":16,"CLOSE":17,"$accept":0,"$end":1}, +terminals_: {2:"error",5:"EOS",7:"LICENSE",8:"PLUS",9:"LICENSEREF",10:"DOCUMENTREF",11:"COLON",12:"WITH",13:"EXCEPTION",14:"AND",15:"OR",16:"OPEN",17:"CLOSE"}, +productions_: [0,[3,2],[6,1],[6,2],[6,1],[6,3],[4,1],[4,3],[4,3],[4,3],[4,3]], +performAction: function anonymous(yytext, yyleng, yylineno, yy, yystate /* action[1] */, $$ /* vstack */, _$ /* lstack */) { +/* this == yyval */ - if (m !== this) { - return m; +var $0 = $$.length - 1; +switch (yystate) { +case 1: +return this.$ = $$[$0-1] +break; +case 2: case 4: case 5: +this.$ = {license: yytext} +break; +case 3: +this.$ = {license: $$[$0-1], plus: true} +break; +case 6: +this.$ = $$[$0] +break; +case 7: +this.$ = {exception: $$[$0]} +this.$.license = $$[$0-2].license +if ($$[$0-2].hasOwnProperty('plus')) { + this.$.plus = $$[$0-2].plus +} +break; +case 8: +this.$ = {conjunction: 'and', left: $$[$0-2], right: $$[$0]} +break; +case 9: +this.$ = {conjunction: 'or', left: $$[$0-2], right: $$[$0]} +break; +case 10: +this.$ = $$[$0-1] +break; +} +}, +table: [{3:1,4:2,6:3,7:$V0,9:$V1,10:$V2,16:$V3},{1:[3]},{5:[1,8],14:$V4,15:$V5},o($V6,[2,6],{12:[1,11]}),{4:12,6:3,7:$V0,9:$V1,10:$V2,16:$V3},o($V7,[2,2],{8:[1,13]}),o($V7,[2,4]),{11:[1,14]},{1:[2,1]},{4:15,6:3,7:$V0,9:$V1,10:$V2,16:$V3},{4:16,6:3,7:$V0,9:$V1,10:$V2,16:$V3},{13:[1,17]},{14:$V4,15:$V5,17:[1,18]},o($V7,[2,3]),{9:[1,19]},o($V6,[2,8]),o([5,15,17],[2,9],{14:$V4}),o($V6,[2,7]),o($V6,[2,10]),o($V7,[2,5])], +defaultActions: {8:[2,1]}, +parseError: function parseError(str, hash) { + if (hash.recoverable) { + this.trace(str); + } else { + function _parseError (msg, hash) { + this.message = msg; + this.hash = hash; } - }; - - // Incrementally add a string to this hash - // - // @param {string} key A UTF-16 or ASCII string - // @return {object} this - MurmurHash3.prototype.hash = function(key) { - var h1, k1, i, top, len; - - len = key.length; - this.len += len; + _parseError.prototype = Error; - k1 = this.k1; - i = 0; - switch (this.rem) { - case 0: k1 ^= len > i ? (key.charCodeAt(i++) & 0xffff) : 0; - case 1: k1 ^= len > i ? (key.charCodeAt(i++) & 0xffff) << 8 : 0; - case 2: k1 ^= len > i ? (key.charCodeAt(i++) & 0xffff) << 16 : 0; - case 3: - k1 ^= len > i ? (key.charCodeAt(i) & 0xff) << 24 : 0; - k1 ^= len > i ? (key.charCodeAt(i++) & 0xff00) >> 8 : 0; + throw new _parseError(str, hash); + } +}, +parse: function parse(input) { + var self = this, stack = [0], tstack = [], vstack = [null], lstack = [], table = this.table, yytext = '', yylineno = 0, yyleng = 0, recovering = 0, TERROR = 2, EOF = 1; + var args = lstack.slice.call(arguments, 1); + var lexer = Object.create(this.lexer); + var sharedState = { yy: {} }; + for (var k in this.yy) { + if (Object.prototype.hasOwnProperty.call(this.yy, k)) { + sharedState.yy[k] = this.yy[k]; } - - this.rem = (len + this.rem) & 3; // & 3 is same as % 4 - len -= this.rem; - if (len > 0) { - h1 = this.h1; - while (1) { - k1 = (k1 * 0x2d51 + (k1 & 0xffff) * 0xcc9e0000) & 0xffffffff; - k1 = (k1 << 15) | (k1 >>> 17); - k1 = (k1 * 0x3593 + (k1 & 0xffff) * 0x1b870000) & 0xffffffff; - - h1 ^= k1; - h1 = (h1 << 13) | (h1 >>> 19); - h1 = (h1 * 5 + 0xe6546b64) & 0xffffffff; - - if (i >= len) { - break; + } + lexer.setInput(input, sharedState.yy); + sharedState.yy.lexer = lexer; + sharedState.yy.parser = this; + if (typeof lexer.yylloc == 'undefined') { + lexer.yylloc = {}; + } + var yyloc = lexer.yylloc; + lstack.push(yyloc); + var ranges = lexer.options && lexer.options.ranges; + if (typeof sharedState.yy.parseError === 'function') { + this.parseError = sharedState.yy.parseError; + } else { + this.parseError = Object.getPrototypeOf(this).parseError; + } + function popStack(n) { + stack.length = stack.length - 2 * n; + vstack.length = vstack.length - n; + lstack.length = lstack.length - n; + } + _token_stack: + var lex = function () { + var token; + token = lexer.lex() || EOF; + if (typeof token !== 'number') { + token = self.symbols_[token] || token; + } + return token; + }; + var symbol, preErrorSymbol, state, action, a, r, yyval = {}, p, len, newState, expected; + while (true) { + state = stack[stack.length - 1]; + if (this.defaultActions[state]) { + action = this.defaultActions[state]; + } else { + if (symbol === null || typeof symbol == 'undefined') { + symbol = lex(); + } + action = table[state] && table[state][symbol]; + } + if (typeof action === 'undefined' || !action.length || !action[0]) { + var errStr = ''; + expected = []; + for (p in table[state]) { + if (this.terminals_[p] && p > TERROR) { + expected.push('\'' + this.terminals_[p] + '\''); + } } - - k1 = ((key.charCodeAt(i++) & 0xffff)) ^ - ((key.charCodeAt(i++) & 0xffff) << 8) ^ - ((key.charCodeAt(i++) & 0xffff) << 16); - top = key.charCodeAt(i++); - k1 ^= ((top & 0xff) << 24) ^ - ((top & 0xff00) >> 8); + if (lexer.showPosition) { + errStr = 'Parse error on line ' + (yylineno + 1) + ':\n' + lexer.showPosition() + '\nExpecting ' + expected.join(', ') + ', got \'' + (this.terminals_[symbol] || symbol) + '\''; + } else { + errStr = 'Parse error on line ' + (yylineno + 1) + ': Unexpected ' + (symbol == EOF ? 'end of input' : '\'' + (this.terminals_[symbol] || symbol) + '\''); + } + this.parseError(errStr, { + text: lexer.match, + token: this.terminals_[symbol] || symbol, + line: lexer.yylineno, + loc: yyloc, + expected: expected + }); } - - k1 = 0; - switch (this.rem) { - case 3: k1 ^= (key.charCodeAt(i + 2) & 0xffff) << 16; - case 2: k1 ^= (key.charCodeAt(i + 1) & 0xffff) << 8; - case 1: k1 ^= (key.charCodeAt(i) & 0xffff); + if (action[0] instanceof Array && action.length > 1) { + throw new Error('Parse Error: multiple actions possible at state: ' + state + ', token: ' + symbol); + } + switch (action[0]) { + case 1: + stack.push(symbol); + vstack.push(lexer.yytext); + lstack.push(lexer.yylloc); + stack.push(action[1]); + symbol = null; + if (!preErrorSymbol) { + yyleng = lexer.yyleng; + yytext = lexer.yytext; + yylineno = lexer.yylineno; + yyloc = lexer.yylloc; + if (recovering > 0) { + recovering--; + } + } else { + symbol = preErrorSymbol; + preErrorSymbol = null; } - - this.h1 = h1; + break; + case 2: + len = this.productions_[action[1]][1]; + yyval.$ = vstack[vstack.length - len]; + yyval._$ = { + first_line: lstack[lstack.length - (len || 1)].first_line, + last_line: lstack[lstack.length - 1].last_line, + first_column: lstack[lstack.length - (len || 1)].first_column, + last_column: lstack[lstack.length - 1].last_column + }; + if (ranges) { + yyval._$.range = [ + lstack[lstack.length - (len || 1)].range[0], + lstack[lstack.length - 1].range[1] + ]; + } + r = this.performAction.apply(yyval, [ + yytext, + yyleng, + yylineno, + sharedState.yy, + action[1], + vstack, + lstack + ].concat(args)); + if (typeof r !== 'undefined') { + return r; + } + if (len) { + stack = stack.slice(0, -1 * len * 2); + vstack = vstack.slice(0, -1 * len); + lstack = lstack.slice(0, -1 * len); + } + stack.push(this.productions_[action[1]][0]); + vstack.push(yyval.$); + lstack.push(yyval._$); + newState = table[stack[stack.length - 2]][stack[stack.length - 1]]; + stack.push(newState); + break; + case 3: + return true; } + } + return true; +}}; +/* generated by jison-lex 0.3.4 */ +var lexer = (function(){ +var lexer = ({ - this.k1 = k1; - return this; - }; - - // Get the result of this hash - // - // @return {number} The 32-bit hash - MurmurHash3.prototype.result = function() { - var k1, h1; - - k1 = this.k1; - h1 = this.h1; +EOF:1, - if (k1 > 0) { - k1 = (k1 * 0x2d51 + (k1 & 0xffff) * 0xcc9e0000) & 0xffffffff; - k1 = (k1 << 15) | (k1 >>> 17); - k1 = (k1 * 0x3593 + (k1 & 0xffff) * 0x1b870000) & 0xffffffff; - h1 ^= k1; +parseError:function parseError(str, hash) { + if (this.yy.parser) { + this.yy.parser.parseError(str, hash); + } else { + throw new Error(str); } + }, - h1 ^= this.len; - - h1 ^= h1 >>> 16; - h1 = (h1 * 0xca6b + (h1 & 0xffff) * 0x85eb0000) & 0xffffffff; - h1 ^= h1 >>> 13; - h1 = (h1 * 0xae35 + (h1 & 0xffff) * 0xc2b20000) & 0xffffffff; - h1 ^= h1 >>> 16; - - return h1 >>> 0; - }; - - // Reset the hash object for reuse - // - // @param {number} seed An optional positive integer - MurmurHash3.prototype.reset = function(seed) { - this.h1 = typeof seed === 'number' ? seed : 0; - this.rem = this.k1 = this.len = 0; +// resets the lexer, sets new input +setInput:function (input, yy) { + this.yy = yy || this.yy || {}; + this._input = input; + this._more = this._backtrack = this.done = false; + this.yylineno = this.yyleng = 0; + this.yytext = this.matched = this.match = ''; + this.conditionStack = ['INITIAL']; + this.yylloc = { + first_line: 1, + first_column: 0, + last_line: 1, + last_column: 0 + }; + if (this.options.ranges) { + this.yylloc.range = [0,0]; + } + this.offset = 0; return this; - }; - - // A cached object to use. This can be safely used if you're in a single- - // threaded environment, otherwise you need to create new hashes to use. - cache = new MurmurHash3(); - - if (true) { - module.exports = MurmurHash3; - } else {} -}()); - - -/***/ }), -/* 110 */ -/***/ (function(module, exports, __webpack_require__) { - -// Note: since nyc uses this module to output coverage, any lines -// that are in the direct sync flow of nyc's outputCoverage are -// ignored, since we can never get coverage for them. -var assert = __webpack_require__(30) -var signals = __webpack_require__(111) + }, -var EE = __webpack_require__(46) -/* istanbul ignore if */ -if (typeof EE !== 'function') { - EE = EE.EventEmitter -} +// consumes and returns one char from the input +input:function () { + var ch = this._input[0]; + this.yytext += ch; + this.yyleng++; + this.offset++; + this.match += ch; + this.matched += ch; + var lines = ch.match(/(?:\r\n?|\n).*/g); + if (lines) { + this.yylineno++; + this.yylloc.last_line++; + } else { + this.yylloc.last_column++; + } + if (this.options.ranges) { + this.yylloc.range[1]++; + } -var emitter -if (process.__signal_exit_emitter__) { - emitter = process.__signal_exit_emitter__ -} else { - emitter = process.__signal_exit_emitter__ = new EE() - emitter.count = 0 - emitter.emitted = {} -} + this._input = this._input.slice(1); + return ch; + }, -// Because this emitter is a global, we have to check to see if a -// previous version of this library failed to enable infinite listeners. -// I know what you're about to say. But literally everything about -// signal-exit is a compromise with evil. Get used to it. -if (!emitter.infinite) { - emitter.setMaxListeners(Infinity) - emitter.infinite = true -} +// unshifts one char (or a string) into the input +unput:function (ch) { + var len = ch.length; + var lines = ch.split(/(?:\r\n?|\n)/g); -module.exports = function (cb, opts) { - assert.equal(typeof cb, 'function', 'a callback must be provided for exit handler') + this._input = ch + this._input; + this.yytext = this.yytext.substr(0, this.yytext.length - len); + //this.yyleng -= len; + this.offset -= len; + var oldLines = this.match.split(/(?:\r\n?|\n)/g); + this.match = this.match.substr(0, this.match.length - 1); + this.matched = this.matched.substr(0, this.matched.length - 1); - if (loaded === false) { - load() - } + if (lines.length - 1) { + this.yylineno -= lines.length - 1; + } + var r = this.yylloc.range; - var ev = 'exit' - if (opts && opts.alwaysLast) { - ev = 'afterexit' - } + this.yylloc = { + first_line: this.yylloc.first_line, + last_line: this.yylineno + 1, + first_column: this.yylloc.first_column, + last_column: lines ? + (lines.length === oldLines.length ? this.yylloc.first_column : 0) + + oldLines[oldLines.length - lines.length].length - lines[0].length : + this.yylloc.first_column - len + }; - var remove = function () { - emitter.removeListener(ev, cb) - if (emitter.listeners('exit').length === 0 && - emitter.listeners('afterexit').length === 0) { - unload() - } - } - emitter.on(ev, cb) + if (this.options.ranges) { + this.yylloc.range = [r[0], r[0] + this.yyleng - len]; + } + this.yyleng = this.yytext.length; + return this; + }, - return remove -} +// When called from action, caches matched text and appends it on next action +more:function () { + this._more = true; + return this; + }, -module.exports.unload = unload -function unload () { - if (!loaded) { - return - } - loaded = false +// When called from action, signals the lexer that this rule fails to match the input, so the next matching rule (regex) should be tested instead. +reject:function () { + if (this.options.backtrack_lexer) { + this._backtrack = true; + } else { + return this.parseError('Lexical error on line ' + (this.yylineno + 1) + '. You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n' + this.showPosition(), { + text: "", + token: null, + line: this.yylineno + }); - signals.forEach(function (sig) { - try { - process.removeListener(sig, sigListeners[sig]) - } catch (er) {} - }) - process.emit = originalProcessEmit - process.reallyExit = originalProcessReallyExit - emitter.count -= 1 -} + } + return this; + }, -function emit (event, code, signal) { - if (emitter.emitted[event]) { - return - } - emitter.emitted[event] = true - emitter.emit(event, code, signal) -} +// retain first n characters of the match +less:function (n) { + this.unput(this.match.slice(n)); + }, -// { : , ... } -var sigListeners = {} -signals.forEach(function (sig) { - sigListeners[sig] = function listener () { - // If there are no other listeners, an exit is coming! - // Simplest way: remove us and then re-send the signal. - // We know that this will kill the process, so we can - // safely emit now. - var listeners = process.listeners(sig) - if (listeners.length === emitter.count) { - unload() - emit('exit', null, sig) - /* istanbul ignore next */ - emit('afterexit', null, sig) - /* istanbul ignore next */ - process.kill(process.pid, sig) - } - } -}) +// displays already matched input, i.e. for error messages +pastInput:function () { + var past = this.matched.substr(0, this.matched.length - this.match.length); + return (past.length > 20 ? '...':'') + past.substr(-20).replace(/\n/g, ""); + }, -module.exports.signals = function () { - return signals -} +// displays upcoming input, i.e. for error messages +upcomingInput:function () { + var next = this.match; + if (next.length < 20) { + next += this._input.substr(0, 20-next.length); + } + return (next.substr(0,20) + (next.length > 20 ? '...' : '')).replace(/\n/g, ""); + }, -module.exports.load = load +// displays the character position where the lexing error occurred, i.e. for error messages +showPosition:function () { + var pre = this.pastInput(); + var c = new Array(pre.length + 1).join("-"); + return pre + this.upcomingInput() + "\n" + c + "^"; + }, -var loaded = false +// test the lexed token: return FALSE when not a match, otherwise return token +test_match:function (match, indexed_rule) { + var token, + lines, + backup; -function load () { - if (loaded) { - return - } - loaded = true + if (this.options.backtrack_lexer) { + // save context + backup = { + yylineno: this.yylineno, + yylloc: { + first_line: this.yylloc.first_line, + last_line: this.last_line, + first_column: this.yylloc.first_column, + last_column: this.yylloc.last_column + }, + yytext: this.yytext, + match: this.match, + matches: this.matches, + matched: this.matched, + yyleng: this.yyleng, + offset: this.offset, + _more: this._more, + _input: this._input, + yy: this.yy, + conditionStack: this.conditionStack.slice(0), + done: this.done + }; + if (this.options.ranges) { + backup.yylloc.range = this.yylloc.range.slice(0); + } + } - // This is the number of onSignalExit's that are in play. - // It's important so that we can count the correct number of - // listeners on signals, and don't wait for the other one to - // handle it instead of us. - emitter.count += 1 + lines = match[0].match(/(?:\r\n?|\n).*/g); + if (lines) { + this.yylineno += lines.length; + } + this.yylloc = { + first_line: this.yylloc.last_line, + last_line: this.yylineno + 1, + first_column: this.yylloc.last_column, + last_column: lines ? + lines[lines.length - 1].length - lines[lines.length - 1].match(/\r?\n?/)[0].length : + this.yylloc.last_column + match[0].length + }; + this.yytext += match[0]; + this.match += match[0]; + this.matches = match; + this.yyleng = this.yytext.length; + if (this.options.ranges) { + this.yylloc.range = [this.offset, this.offset += this.yyleng]; + } + this._more = false; + this._backtrack = false; + this._input = this._input.slice(match[0].length); + this.matched += match[0]; + token = this.performAction.call(this, this.yy, this, indexed_rule, this.conditionStack[this.conditionStack.length - 1]); + if (this.done && this._input) { + this.done = false; + } + if (token) { + return token; + } else if (this._backtrack) { + // recover context + for (var k in backup) { + this[k] = backup[k]; + } + return false; // rule action called reject() implying the next rule should be tested instead. + } + return false; + }, - signals = signals.filter(function (sig) { - try { - process.on(sig, sigListeners[sig]) - return true - } catch (er) { - return false - } - }) +// return next match in input +next:function () { + if (this.done) { + return this.EOF; + } + if (!this._input) { + this.done = true; + } - process.emit = processEmit - process.reallyExit = processReallyExit -} + var token, + match, + tempMatch, + index; + if (!this._more) { + this.yytext = ''; + this.match = ''; + } + var rules = this._currentRules(); + for (var i = 0; i < rules.length; i++) { + tempMatch = this._input.match(this.rules[rules[i]]); + if (tempMatch && (!match || tempMatch[0].length > match[0].length)) { + match = tempMatch; + index = i; + if (this.options.backtrack_lexer) { + token = this.test_match(tempMatch, rules[i]); + if (token !== false) { + return token; + } else if (this._backtrack) { + match = false; + continue; // rule action called reject() implying a rule MISmatch. + } else { + // else: this is a lexer rule which consumes input without producing a token (e.g. whitespace) + return false; + } + } else if (!this.options.flex) { + break; + } + } + } + if (match) { + token = this.test_match(match, rules[index]); + if (token !== false) { + return token; + } + // else: this is a lexer rule which consumes input without producing a token (e.g. whitespace) + return false; + } + if (this._input === "") { + return this.EOF; + } else { + return this.parseError('Lexical error on line ' + (this.yylineno + 1) + '. Unrecognized text.\n' + this.showPosition(), { + text: "", + token: null, + line: this.yylineno + }); + } + }, -var originalProcessReallyExit = process.reallyExit -function processReallyExit (code) { - process.exitCode = code || 0 - emit('exit', process.exitCode, null) - /* istanbul ignore next */ - emit('afterexit', process.exitCode, null) - /* istanbul ignore next */ - originalProcessReallyExit.call(process, process.exitCode) -} +// return next match that has a token +lex:function lex() { + var r = this.next(); + if (r) { + return r; + } else { + return this.lex(); + } + }, -var originalProcessEmit = process.emit -function processEmit (ev, arg) { - if (ev === 'exit') { - if (arg !== undefined) { - process.exitCode = arg - } - var ret = originalProcessEmit.apply(this, arguments) - emit('exit', process.exitCode, null) - /* istanbul ignore next */ - emit('afterexit', process.exitCode, null) - return ret - } else { - return originalProcessEmit.apply(this, arguments) - } -} +// activates a new lexer condition state (pushes the new lexer condition state onto the condition stack) +begin:function begin(condition) { + this.conditionStack.push(condition); + }, +// pop the previously active lexer condition state off the condition stack +popState:function popState() { + var n = this.conditionStack.length - 1; + if (n > 0) { + return this.conditionStack.pop(); + } else { + return this.conditionStack[0]; + } + }, -/***/ }), -/* 111 */ -/***/ (function(module, exports) { +// produce the lexer rule set which is active for the currently active lexer condition state +_currentRules:function _currentRules() { + if (this.conditionStack.length && this.conditionStack[this.conditionStack.length - 1]) { + return this.conditions[this.conditionStack[this.conditionStack.length - 1]].rules; + } else { + return this.conditions["INITIAL"].rules; + } + }, -// This is not the set of all possible signals. -// -// It IS, however, the set of all signals that trigger -// an exit on either Linux or BSD systems. Linux is a -// superset of the signal names supported on BSD, and -// the unknown signals just fail to register, so we can -// catch that easily enough. -// -// Don't bother with SIGKILL. It's uncatchable, which -// means that we can't fire any callbacks anyway. -// -// If a user does happen to register a handler on a non- -// fatal signal like SIGWINCH or something, and then -// exit, it'll end up firing `process.emit('exit')`, so -// the handler will be fired anyway. -// -// SIGBUS, SIGFPE, SIGSEGV and SIGILL, when not raised -// artificially, inherently leave the process in a -// state from which it is not safe to try and enter JS -// listeners. -module.exports = [ - 'SIGABRT', - 'SIGALRM', - 'SIGHUP', - 'SIGINT', - 'SIGTERM' -] +// return the currently active lexer condition state; when an index argument is provided it produces the N-th previous condition state, if available +topState:function topState(n) { + n = this.conditionStack.length - 1 - Math.abs(n || 0); + if (n >= 0) { + return this.conditionStack[n]; + } else { + return "INITIAL"; + } + }, -if (process.platform !== 'win32') { - module.exports.push( - 'SIGVTALRM', - 'SIGXCPU', - 'SIGXFSZ', - 'SIGUSR2', - 'SIGTRAP', - 'SIGSYS', - 'SIGQUIT', - 'SIGIOT' - // should detect profiler and enable/disable accordingly. - // see #21 - // 'SIGPROF' - ) +// alias for begin(condition) +pushState:function pushState(condition) { + this.begin(condition); + }, + +// return the number of states currently on the stack +stateStackSize:function stateStackSize() { + return this.conditionStack.length; + }, +options: {}, +performAction: function anonymous(yy,yy_,$avoiding_name_collisions,YY_START) { +var YYSTATE=YY_START; +switch($avoiding_name_collisions) { +case 0:return 5 +break; +case 1:/* skip whitespace */ +break; +case 2:return 8 +break; +case 3:return 16 +break; +case 4:return 17 +break; +case 5:return 11 +break; +case 6:return 10 +break; +case 7:return 9 +break; +case 8:return 14 +break; +case 9:return 15 +break; +case 10:return 12 +break; +case 11:return 7 +break; +case 12:return 7 +break; +case 13:return 7 +break; +case 14:return 7 +break; +case 15:return 7 +break; +case 16:return 7 +break; +case 17:return 7 +break; +case 18:return 7 +break; +case 19:return 7 +break; +case 20:return 7 +break; +case 21:return 7 +break; +case 22:return 7 +break; +case 23:return 7 +break; +case 24:return 13 +break; +case 25:return 13 +break; +case 26:return 13 +break; +case 27:return 13 +break; +case 28:return 13 +break; +case 29:return 13 +break; +case 30:return 13 +break; +case 31:return 13 +break; +case 32:return 7 +break; +case 33:return 13 +break; +case 34:return 7 +break; +case 35:return 13 +break; +case 36:return 7 +break; +case 37:return 13 +break; +case 38:return 13 +break; +case 39:return 7 +break; +case 40:return 13 +break; +case 41:return 13 +break; +case 42:return 13 +break; +case 43:return 13 +break; +case 44:return 13 +break; +case 45:return 7 +break; +case 46:return 13 +break; +case 47:return 7 +break; +case 48:return 7 +break; +case 49:return 7 +break; +case 50:return 7 +break; +case 51:return 7 +break; +case 52:return 7 +break; +case 53:return 7 +break; +case 54:return 7 +break; +case 55:return 7 +break; +case 56:return 7 +break; +case 57:return 7 +break; +case 58:return 7 +break; +case 59:return 7 +break; +case 60:return 7 +break; +case 61:return 7 +break; +case 62:return 7 +break; +case 63:return 13 +break; +case 64:return 7 +break; +case 65:return 7 +break; +case 66:return 13 +break; +case 67:return 7 +break; +case 68:return 7 +break; +case 69:return 7 +break; +case 70:return 7 +break; +case 71:return 7 +break; +case 72:return 7 +break; +case 73:return 13 +break; +case 74:return 7 +break; +case 75:return 13 +break; +case 76:return 7 +break; +case 77:return 7 +break; +case 78:return 7 +break; +case 79:return 7 +break; +case 80:return 7 +break; +case 81:return 7 +break; +case 82:return 7 +break; +case 83:return 7 +break; +case 84:return 7 +break; +case 85:return 7 +break; +case 86:return 7 +break; +case 87:return 7 +break; +case 88:return 7 +break; +case 89:return 7 +break; +case 90:return 7 +break; +case 91:return 7 +break; +case 92:return 7 +break; +case 93:return 7 +break; +case 94:return 7 +break; +case 95:return 7 +break; +case 96:return 7 +break; +case 97:return 7 +break; +case 98:return 7 +break; +case 99:return 7 +break; +case 100:return 7 +break; +case 101:return 7 +break; +case 102:return 7 +break; +case 103:return 7 +break; +case 104:return 7 +break; +case 105:return 7 +break; +case 106:return 7 +break; +case 107:return 7 +break; +case 108:return 7 +break; +case 109:return 7 +break; +case 110:return 7 +break; +case 111:return 7 +break; +case 112:return 7 +break; +case 113:return 7 +break; +case 114:return 7 +break; +case 115:return 7 +break; +case 116:return 7 +break; +case 117:return 7 +break; +case 118:return 7 +break; +case 119:return 7 +break; +case 120:return 7 +break; +case 121:return 7 +break; +case 122:return 7 +break; +case 123:return 7 +break; +case 124:return 7 +break; +case 125:return 7 +break; +case 126:return 7 +break; +case 127:return 7 +break; +case 128:return 7 +break; +case 129:return 7 +break; +case 130:return 7 +break; +case 131:return 7 +break; +case 132:return 7 +break; +case 133:return 7 +break; +case 134:return 7 +break; +case 135:return 7 +break; +case 136:return 7 +break; +case 137:return 7 +break; +case 138:return 7 +break; +case 139:return 7 +break; +case 140:return 7 +break; +case 141:return 7 +break; +case 142:return 7 +break; +case 143:return 7 +break; +case 144:return 7 +break; +case 145:return 7 +break; +case 146:return 7 +break; +case 147:return 7 +break; +case 148:return 7 +break; +case 149:return 7 +break; +case 150:return 7 +break; +case 151:return 7 +break; +case 152:return 7 +break; +case 153:return 7 +break; +case 154:return 7 +break; +case 155:return 7 +break; +case 156:return 7 +break; +case 157:return 7 +break; +case 158:return 7 +break; +case 159:return 7 +break; +case 160:return 7 +break; +case 161:return 7 +break; +case 162:return 7 +break; +case 163:return 7 +break; +case 164:return 7 +break; +case 165:return 7 +break; +case 166:return 7 +break; +case 167:return 7 +break; +case 168:return 7 +break; +case 169:return 7 +break; +case 170:return 7 +break; +case 171:return 7 +break; +case 172:return 7 +break; +case 173:return 7 +break; +case 174:return 7 +break; +case 175:return 7 +break; +case 176:return 7 +break; +case 177:return 7 +break; +case 178:return 7 +break; +case 179:return 7 +break; +case 180:return 7 +break; +case 181:return 7 +break; +case 182:return 7 +break; +case 183:return 7 +break; +case 184:return 7 +break; +case 185:return 7 +break; +case 186:return 7 +break; +case 187:return 7 +break; +case 188:return 7 +break; +case 189:return 7 +break; +case 190:return 7 +break; +case 191:return 7 +break; +case 192:return 7 +break; +case 193:return 7 +break; +case 194:return 7 +break; +case 195:return 7 +break; +case 196:return 7 +break; +case 197:return 7 +break; +case 198:return 7 +break; +case 199:return 7 +break; +case 200:return 7 +break; +case 201:return 7 +break; +case 202:return 7 +break; +case 203:return 7 +break; +case 204:return 7 +break; +case 205:return 7 +break; +case 206:return 7 +break; +case 207:return 7 +break; +case 208:return 7 +break; +case 209:return 7 +break; +case 210:return 7 +break; +case 211:return 7 +break; +case 212:return 7 +break; +case 213:return 7 +break; +case 214:return 7 +break; +case 215:return 7 +break; +case 216:return 7 +break; +case 217:return 7 +break; +case 218:return 7 +break; +case 219:return 7 +break; +case 220:return 7 +break; +case 221:return 7 +break; +case 222:return 7 +break; +case 223:return 7 +break; +case 224:return 7 +break; +case 225:return 7 +break; +case 226:return 7 +break; +case 227:return 7 +break; +case 228:return 7 +break; +case 229:return 7 +break; +case 230:return 7 +break; +case 231:return 7 +break; +case 232:return 7 +break; +case 233:return 7 +break; +case 234:return 7 +break; +case 235:return 7 +break; +case 236:return 7 +break; +case 237:return 7 +break; +case 238:return 7 +break; +case 239:return 7 +break; +case 240:return 7 +break; +case 241:return 7 +break; +case 242:return 7 +break; +case 243:return 7 +break; +case 244:return 7 +break; +case 245:return 7 +break; +case 246:return 7 +break; +case 247:return 7 +break; +case 248:return 7 +break; +case 249:return 7 +break; +case 250:return 7 +break; +case 251:return 7 +break; +case 252:return 7 +break; +case 253:return 7 +break; +case 254:return 7 +break; +case 255:return 7 +break; +case 256:return 7 +break; +case 257:return 7 +break; +case 258:return 7 +break; +case 259:return 7 +break; +case 260:return 7 +break; +case 261:return 7 +break; +case 262:return 7 +break; +case 263:return 7 +break; +case 264:return 7 +break; +case 265:return 7 +break; +case 266:return 7 +break; +case 267:return 7 +break; +case 268:return 7 +break; +case 269:return 7 +break; +case 270:return 7 +break; +case 271:return 7 +break; +case 272:return 7 +break; +case 273:return 7 +break; +case 274:return 7 +break; +case 275:return 7 +break; +case 276:return 7 +break; +case 277:return 7 +break; +case 278:return 7 +break; +case 279:return 7 +break; +case 280:return 7 +break; +case 281:return 7 +break; +case 282:return 7 +break; +case 283:return 7 +break; +case 284:return 7 +break; +case 285:return 7 +break; +case 286:return 7 +break; +case 287:return 7 +break; +case 288:return 7 +break; +case 289:return 7 +break; +case 290:return 7 +break; +case 291:return 7 +break; +case 292:return 7 +break; +case 293:return 7 +break; +case 294:return 7 +break; +case 295:return 7 +break; +case 296:return 7 +break; +case 297:return 7 +break; +case 298:return 7 +break; +case 299:return 7 +break; +case 300:return 7 +break; +case 301:return 7 +break; +case 302:return 7 +break; +case 303:return 7 +break; +case 304:return 7 +break; +case 305:return 7 +break; +case 306:return 7 +break; +case 307:return 7 +break; +case 308:return 7 +break; +case 309:return 7 +break; +case 310:return 7 +break; +case 311:return 7 +break; +case 312:return 7 +break; +case 313:return 7 +break; +case 314:return 7 +break; +case 315:return 7 +break; +case 316:return 7 +break; +case 317:return 7 +break; +case 318:return 7 +break; +case 319:return 7 +break; +case 320:return 7 +break; +case 321:return 7 +break; +case 322:return 7 +break; +case 323:return 7 +break; +case 324:return 7 +break; +case 325:return 7 +break; +case 326:return 7 +break; +case 327:return 7 +break; +case 328:return 7 +break; +case 329:return 7 +break; +case 330:return 7 +break; +case 331:return 7 +break; +case 332:return 7 +break; +case 333:return 7 +break; +case 334:return 7 +break; +case 335:return 7 +break; +case 336:return 7 +break; +case 337:return 7 +break; +case 338:return 7 +break; +case 339:return 7 +break; +case 340:return 7 +break; +case 341:return 7 +break; +case 342:return 7 +break; +case 343:return 7 +break; +case 344:return 7 +break; +case 345:return 7 +break; +case 346:return 7 +break; +case 347:return 7 +break; +case 348:return 7 +break; +case 349:return 7 +break; +case 350:return 7 +break; +case 351:return 7 +break; +case 352:return 7 +break; +case 353:return 7 +break; +case 354:return 7 +break; +case 355:return 7 +break; +case 356:return 7 +break; +case 357:return 7 +break; +case 358:return 7 +break; +case 359:return 7 +break; +case 360:return 7 +break; +case 361:return 7 +break; +case 362:return 7 +break; +case 363:return 7 +break; +case 364:return 7 +break; } - -if (process.platform === 'linux') { - module.exports.push( - 'SIGIO', - 'SIGPOLL', - 'SIGPWR', - 'SIGSTKFLT', - 'SIGUNUSED' - ) +}, +rules: [/^(?:$)/,/^(?:\s+)/,/^(?:\+)/,/^(?:\()/,/^(?:\))/,/^(?::)/,/^(?:DocumentRef-([0-9A-Za-z-+.]+))/,/^(?:LicenseRef-([0-9A-Za-z-+.]+))/,/^(?:AND)/,/^(?:OR)/,/^(?:WITH)/,/^(?:BSD-3-Clause-No-Nuclear-License-2014)/,/^(?:BSD-3-Clause-No-Nuclear-Warranty)/,/^(?:GPL-2\.0-with-classpath-exception)/,/^(?:GPL-3\.0-with-autoconf-exception)/,/^(?:GPL-2\.0-with-autoconf-exception)/,/^(?:BSD-3-Clause-No-Nuclear-License)/,/^(?:MPL-2\.0-no-copyleft-exception)/,/^(?:GPL-2\.0-with-bison-exception)/,/^(?:GPL-2\.0-with-font-exception)/,/^(?:GPL-2\.0-with-GCC-exception)/,/^(?:CNRI-Python-GPL-Compatible)/,/^(?:GPL-3\.0-with-GCC-exception)/,/^(?:BSD-3-Clause-Attribution)/,/^(?:Classpath-exception-2\.0)/,/^(?:WxWindows-exception-3\.1)/,/^(?:freertos-exception-2\.0)/,/^(?:Autoconf-exception-3\.0)/,/^(?:i2p-gpl-java-exception)/,/^(?:gnu-javamail-exception)/,/^(?:Nokia-Qt-exception-1\.1)/,/^(?:Autoconf-exception-2\.0)/,/^(?:BSD-2-Clause-FreeBSD)/,/^(?:u-boot-exception-2\.0)/,/^(?:zlib-acknowledgement)/,/^(?:Bison-exception-2\.2)/,/^(?:BSD-2-Clause-NetBSD)/,/^(?:CLISP-exception-2\.0)/,/^(?:eCos-exception-2\.0)/,/^(?:BSD-3-Clause-Clear)/,/^(?:Font-exception-2\.0)/,/^(?:FLTK-exception-2\.0)/,/^(?:GCC-exception-2\.0)/,/^(?:Qwt-exception-1\.0)/,/^(?:Libtool-exception)/,/^(?:BSD-3-Clause-LBNL)/,/^(?:GCC-exception-3\.1)/,/^(?:Artistic-1\.0-Perl)/,/^(?:Artistic-1\.0-cl8)/,/^(?:CC-BY-NC-SA-2\.5)/,/^(?:MIT-advertising)/,/^(?:BSD-Source-Code)/,/^(?:CC-BY-NC-SA-4\.0)/,/^(?:LiLiQ-Rplus-1\.1)/,/^(?:CC-BY-NC-SA-3\.0)/,/^(?:BSD-4-Clause-UC)/,/^(?:CC-BY-NC-SA-2\.0)/,/^(?:CC-BY-NC-SA-1\.0)/,/^(?:CC-BY-NC-ND-4\.0)/,/^(?:CC-BY-NC-ND-3\.0)/,/^(?:CC-BY-NC-ND-2\.5)/,/^(?:CC-BY-NC-ND-2\.0)/,/^(?:CC-BY-NC-ND-1\.0)/,/^(?:LZMA-exception)/,/^(?:BitTorrent-1\.1)/,/^(?:CrystalStacker)/,/^(?:FLTK-exception)/,/^(?:SugarCRM-1\.1\.3)/,/^(?:BSD-Protection)/,/^(?:BitTorrent-1\.0)/,/^(?:HaskellReport)/,/^(?:Interbase-1\.0)/,/^(?:StandardML-NJ)/,/^(?:mif-exception)/,/^(?:Frameworx-1\.0)/,/^(?:389-exception)/,/^(?:CC-BY-NC-2\.0)/,/^(?:CC-BY-NC-2\.5)/,/^(?:CC-BY-NC-3\.0)/,/^(?:CC-BY-NC-4\.0)/,/^(?:W3C-19980720)/,/^(?:CC-BY-SA-1\.0)/,/^(?:CC-BY-SA-2\.0)/,/^(?:CC-BY-SA-2\.5)/,/^(?:CC-BY-ND-2\.0)/,/^(?:CC-BY-SA-4\.0)/,/^(?:CC-BY-SA-3\.0)/,/^(?:Artistic-1\.0)/,/^(?:Artistic-2\.0)/,/^(?:CC-BY-ND-2\.5)/,/^(?:CC-BY-ND-3\.0)/,/^(?:CC-BY-ND-4\.0)/,/^(?:CC-BY-ND-1\.0)/,/^(?:BSD-4-Clause)/,/^(?:BSD-3-Clause)/,/^(?:BSD-2-Clause)/,/^(?:CC-BY-NC-1\.0)/,/^(?:bzip2-1\.0\.6)/,/^(?:Unicode-TOU)/,/^(?:CNRI-Jython)/,/^(?:ImageMagick)/,/^(?:Adobe-Glyph)/,/^(?:CUA-OPL-1\.0)/,/^(?:OLDAP-2\.2\.2)/,/^(?:LiLiQ-R-1\.1)/,/^(?:bzip2-1\.0\.5)/,/^(?:LiLiQ-P-1\.1)/,/^(?:OLDAP-2\.0\.1)/,/^(?:OLDAP-2\.2\.1)/,/^(?:CNRI-Python)/,/^(?:XFree86-1\.1)/,/^(?:OSET-PL-2\.1)/,/^(?:Apache-2\.0)/,/^(?:Watcom-1\.0)/,/^(?:PostgreSQL)/,/^(?:Python-2\.0)/,/^(?:RHeCos-1\.1)/,/^(?:EUDatagrid)/,/^(?:Spencer-99)/,/^(?:Intel-ACPI)/,/^(?:CECILL-1\.0)/,/^(?:CECILL-1\.1)/,/^(?:JasPer-2\.0)/,/^(?:CECILL-2\.0)/,/^(?:CECILL-2\.1)/,/^(?:gSOAP-1\.3b)/,/^(?:Spencer-94)/,/^(?:Apache-1\.1)/,/^(?:Spencer-86)/,/^(?:Apache-1\.0)/,/^(?:ClArtistic)/,/^(?:TORQUE-1\.1)/,/^(?:CATOSL-1\.1)/,/^(?:Adobe-2006)/,/^(?:Zimbra-1\.4)/,/^(?:Zimbra-1\.3)/,/^(?:Condor-1\.1)/,/^(?:CC-BY-3\.0)/,/^(?:CC-BY-2\.5)/,/^(?:OLDAP-2\.4)/,/^(?:SGI-B-1\.1)/,/^(?:SISSL-1\.2)/,/^(?:SGI-B-1\.0)/,/^(?:OLDAP-2\.3)/,/^(?:CC-BY-4\.0)/,/^(?:Crossword)/,/^(?:SimPL-2\.0)/,/^(?:OLDAP-2\.2)/,/^(?:OLDAP-2\.1)/,/^(?:ErlPL-1\.1)/,/^(?:LPPL-1\.3a)/,/^(?:LPPL-1\.3c)/,/^(?:OLDAP-2\.0)/,/^(?:Leptonica)/,/^(?:CPOL-1\.02)/,/^(?:OLDAP-1\.4)/,/^(?:OLDAP-1\.3)/,/^(?:CC-BY-2\.0)/,/^(?:Unlicense)/,/^(?:OLDAP-2\.8)/,/^(?:OLDAP-1\.2)/,/^(?:MakeIndex)/,/^(?:OLDAP-2\.7)/,/^(?:OLDAP-1\.1)/,/^(?:Sleepycat)/,/^(?:D-FSL-1\.0)/,/^(?:CC-BY-1\.0)/,/^(?:OLDAP-2\.6)/,/^(?:WXwindows)/,/^(?:NPOSL-3\.0)/,/^(?:FreeImage)/,/^(?:SGI-B-2\.0)/,/^(?:OLDAP-2\.5)/,/^(?:Beerware)/,/^(?:Newsletr)/,/^(?:NBPL-1\.0)/,/^(?:NASA-1\.3)/,/^(?:NLOD-1\.0)/,/^(?:AGPL-1\.0)/,/^(?:OCLC-2\.0)/,/^(?:ODbL-1\.0)/,/^(?:PDDL-1\.0)/,/^(?:Motosoto)/,/^(?:Afmparse)/,/^(?:ANTLR-PD)/,/^(?:LPL-1\.02)/,/^(?:Abstyles)/,/^(?:eCos-2\.0)/,/^(?:APSL-1\.0)/,/^(?:LPPL-1\.2)/,/^(?:LPPL-1\.1)/,/^(?:LPPL-1\.0)/,/^(?:APSL-1\.1)/,/^(?:APSL-2\.0)/,/^(?:Info-ZIP)/,/^(?:Zend-2\.0)/,/^(?:IBM-pibs)/,/^(?:LGPL-2\.0)/,/^(?:LGPL-3\.0)/,/^(?:LGPL-2\.1)/,/^(?:GFDL-1\.3)/,/^(?:PHP-3\.01)/,/^(?:GFDL-1\.2)/,/^(?:GFDL-1\.1)/,/^(?:AGPL-3\.0)/,/^(?:Giftware)/,/^(?:EUPL-1\.1)/,/^(?:RPSL-1\.0)/,/^(?:EUPL-1\.0)/,/^(?:MIT-enna)/,/^(?:CECILL-B)/,/^(?:diffmark)/,/^(?:CECILL-C)/,/^(?:CDDL-1\.0)/,/^(?:Sendmail)/,/^(?:CDDL-1\.1)/,/^(?:CPAL-1\.0)/,/^(?:APSL-1\.2)/,/^(?:NPL-1\.1)/,/^(?:AFL-1\.2)/,/^(?:Caldera)/,/^(?:AFL-2\.0)/,/^(?:FSFULLR)/,/^(?:AFL-2\.1)/,/^(?:VSL-1\.0)/,/^(?:VOSTROM)/,/^(?:UPL-1\.0)/,/^(?:Dotseqn)/,/^(?:CPL-1\.0)/,/^(?:dvipdfm)/,/^(?:EPL-1\.0)/,/^(?:OCCT-PL)/,/^(?:ECL-1\.0)/,/^(?:Latex2e)/,/^(?:ECL-2\.0)/,/^(?:GPL-1\.0)/,/^(?:GPL-2\.0)/,/^(?:GPL-3\.0)/,/^(?:AFL-3\.0)/,/^(?:LAL-1\.2)/,/^(?:LAL-1\.3)/,/^(?:EFL-1\.0)/,/^(?:EFL-2\.0)/,/^(?:gnuplot)/,/^(?:Aladdin)/,/^(?:LPL-1\.0)/,/^(?:libtiff)/,/^(?:Entessa)/,/^(?:AMDPLPA)/,/^(?:IPL-1\.0)/,/^(?:OPL-1\.0)/,/^(?:OSL-1\.0)/,/^(?:OSL-1\.1)/,/^(?:OSL-2\.0)/,/^(?:OSL-2\.1)/,/^(?:OSL-3\.0)/,/^(?:OpenSSL)/,/^(?:ZPL-2\.1)/,/^(?:PHP-3\.0)/,/^(?:ZPL-2\.0)/,/^(?:ZPL-1\.1)/,/^(?:CC0-1\.0)/,/^(?:SPL-1\.0)/,/^(?:psutils)/,/^(?:MPL-1\.0)/,/^(?:QPL-1\.0)/,/^(?:MPL-1\.1)/,/^(?:MPL-2\.0)/,/^(?:APL-1\.0)/,/^(?:RPL-1\.1)/,/^(?:RPL-1\.5)/,/^(?:MIT-CMU)/,/^(?:Multics)/,/^(?:Eurosym)/,/^(?:BSL-1\.0)/,/^(?:MIT-feh)/,/^(?:Saxpath)/,/^(?:Borceux)/,/^(?:OFL-1\.1)/,/^(?:OFL-1\.0)/,/^(?:AFL-1\.1)/,/^(?:YPL-1\.1)/,/^(?:YPL-1\.0)/,/^(?:NPL-1\.0)/,/^(?:iMatix)/,/^(?:mpich2)/,/^(?:APAFML)/,/^(?:Bahyph)/,/^(?:RSA-MD)/,/^(?:psfrag)/,/^(?:Plexus)/,/^(?:eGenix)/,/^(?:Glulxe)/,/^(?:SAX-PD)/,/^(?:Imlib2)/,/^(?:Wsuipa)/,/^(?:LGPLLR)/,/^(?:Libpng)/,/^(?:xinetd)/,/^(?:MITNFA)/,/^(?:NetCDF)/,/^(?:Naumen)/,/^(?:SMPPL)/,/^(?:Nunit)/,/^(?:FSFUL)/,/^(?:GL2PS)/,/^(?:SMLNJ)/,/^(?:Rdisc)/,/^(?:Noweb)/,/^(?:Nokia)/,/^(?:SISSL)/,/^(?:Qhull)/,/^(?:Intel)/,/^(?:Glide)/,/^(?:Xerox)/,/^(?:AMPAS)/,/^(?:WTFPL)/,/^(?:MS-PL)/,/^(?:XSkat)/,/^(?:MS-RL)/,/^(?:MirOS)/,/^(?:RSCPL)/,/^(?:TMate)/,/^(?:OGTSL)/,/^(?:FSFAP)/,/^(?:NCSA)/,/^(?:Zlib)/,/^(?:SCEA)/,/^(?:SNIA)/,/^(?:NGPL)/,/^(?:NOSL)/,/^(?:ADSL)/,/^(?:MTLL)/,/^(?:NLPL)/,/^(?:Ruby)/,/^(?:JSON)/,/^(?:Barr)/,/^(?:0BSD)/,/^(?:Xnet)/,/^(?:Cube)/,/^(?:curl)/,/^(?:DSDP)/,/^(?:Fair)/,/^(?:HPND)/,/^(?:TOSL)/,/^(?:IJG)/,/^(?:SWL)/,/^(?:Vim)/,/^(?:FTL)/,/^(?:ICU)/,/^(?:OML)/,/^(?:NRL)/,/^(?:DOC)/,/^(?:TCL)/,/^(?:W3C)/,/^(?:NTP)/,/^(?:IPA)/,/^(?:ISC)/,/^(?:X11)/,/^(?:AAL)/,/^(?:AML)/,/^(?:xpp)/,/^(?:Zed)/,/^(?:MIT)/,/^(?:Mup)/], +conditions: {"INITIAL":{"rules":[0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,353,354,355,356,357,358,359,360,361,362,363,364],"inclusive":true}} +}); +return lexer; +})(); +parser.lexer = lexer; +function Parser () { + this.yy = {}; } +Parser.prototype = parser;parser.Parser = Parser; +return new Parser; +})(); -/***/ }), -/* 112 */ -/***/ (function(module, exports) { - -module.exports = require(undefined); - -/***/ }), -/* 113 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - -const isPlainObj = __webpack_require__(114); - -module.exports = (obj, opts) => { - if (!isPlainObj(obj)) { - throw new TypeError('Expected a plain object'); - } - - opts = opts || {}; - - // DEPRECATED - if (typeof opts === 'function') { - throw new TypeError('Specify the compare function as an option instead'); - } - - const deep = opts.deep; - const seenInput = []; - const seenOutput = []; - - const sortKeys = x => { - const seenIndex = seenInput.indexOf(x); - - if (seenIndex !== -1) { - return seenOutput[seenIndex]; - } - - const ret = {}; - const keys = Object.keys(x).sort(opts.compare); - - seenInput.push(x); - seenOutput.push(ret); - - for (let i = 0; i < keys.length; i++) { - const key = keys[i]; - const val = x[key]; - - if (deep && Array.isArray(val)) { - const retArr = []; - - for (let j = 0; j < val.length; j++) { - retArr[j] = isPlainObj(val[j]) ? sortKeys(val[j]) : val[j]; - } - - ret[key] = retArr; - continue; - } - - ret[key] = deep && isPlainObj(val) ? sortKeys(val) : val; - } - - return ret; - }; - - return sortKeys(obj); -}; - - -/***/ }), -/* 114 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - -var toString = Object.prototype.toString; - -module.exports = function (x) { - var prototype; - return toString.call(x) === '[object Object]' && (prototype = Object.getPrototypeOf(x), prototype === null || prototype === Object.getPrototypeOf({})); +if (true) { +exports.parser = spdxparse; +exports.Parser = spdxparse.Parser; +exports.parse = function () { return spdxparse.parse.apply(spdxparse, arguments); }; +exports.main = function commonjsMain(args) { + if (!args[1]) { + console.log('Usage: '+args[0]+' FILE'); + process.exit(1); + } + var source = __webpack_require__(23).readFileSync(__webpack_require__(16).normalize(args[1]), "utf8"); + return exports.parser.parse(source); }; +if ( true && __webpack_require__.c[__webpack_require__.s] === module) { + exports.main(process.argv.slice(1)); +} +} +/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(5)(module))) /***/ }), -/* 115 */ +/* 526 */ /***/ (function(module, exports, __webpack_require__) { -"use strict"; - -const fs = __webpack_require__(23); -const path = __webpack_require__(16); -const pify = __webpack_require__(116); -const semver = __webpack_require__(117); - -const defaults = { - mode: 0o777 & (~process.umask()), - fs -}; - -const useNativeRecursiveOption = semver.satisfies(process.version, '>=10.12.0'); - -// https://github.com/nodejs/node/issues/8987 -// https://github.com/libuv/libuv/pull/1088 -const checkPath = pth => { - if (process.platform === 'win32') { - const pathHasInvalidWinCharacters = /[<>:"|?*]/.test(pth.replace(path.parse(pth).root, '')); - - if (pathHasInvalidWinCharacters) { - const error = new Error(`Path contains invalid characters: ${pth}`); - error.code = 'EINVAL'; - throw error; - } - } -}; - -const permissionError = pth => { - // This replicates the exception of `fs.mkdir` with native the - // `recusive` option when run on an invalid drive under Windows. - const error = new Error(`operation not permitted, mkdir '${pth}'`); - error.code = 'EPERM'; - error.errno = -4048; - error.path = pth; - error.syscall = 'mkdir'; - return error; -}; - -const makeDir = (input, options) => Promise.resolve().then(() => { - checkPath(input); - options = Object.assign({}, defaults, options); - - // TODO: Use util.promisify when targeting Node.js 8 - const mkdir = pify(options.fs.mkdir); - const stat = pify(options.fs.stat); - - if (useNativeRecursiveOption && options.fs.mkdir === fs.mkdir) { - const pth = path.resolve(input); - - return mkdir(pth, { - mode: options.mode, - recursive: true - }).then(() => pth); - } - - const make = pth => { - return mkdir(pth, options.mode) - .then(() => pth) - .catch(error => { - if (error.code === 'EPERM') { - throw error; - } - - if (error.code === 'ENOENT') { - if (path.dirname(pth) === pth) { - throw permissionError(pth); - } - - if (error.message.includes('null bytes')) { - throw error; - } - - return make(path.dirname(pth)).then(() => make(pth)); - } - - return stat(pth) - .then(stats => stats.isDirectory() ? pth : Promise.reject()) - .catch(() => { - throw error; - }); - }); - }; - - return make(path.resolve(input)); -}); - -module.exports = makeDir; -module.exports.default = makeDir; - -module.exports.sync = (input, options) => { - checkPath(input); - options = Object.assign({}, defaults, options); - - if (useNativeRecursiveOption && options.fs.mkdirSync === fs.mkdirSync) { - const pth = path.resolve(input); - - fs.mkdirSync(pth, { - mode: options.mode, - recursive: true - }); - - return pth; - } +var licenseIDs = __webpack_require__(527); - const make = pth => { - try { - options.fs.mkdirSync(pth, options.mode); - } catch (error) { - if (error.code === 'EPERM') { - throw error; - } +function valid(string) { + return licenseIDs.indexOf(string) > -1; +} - if (error.code === 'ENOENT') { - if (path.dirname(pth) === pth) { - throw permissionError(pth); - } +// Common transpositions of license identifier acronyms +var transpositions = [ + ['APGL', 'AGPL'], + ['Gpl', 'GPL'], + ['GLP', 'GPL'], + ['APL', 'Apache'], + ['ISD', 'ISC'], + ['GLP', 'GPL'], + ['IST', 'ISC'], + ['Claude', 'Clause'], + [' or later', '+'], + [' International', ''], + ['GNU', 'GPL'], + ['GUN', 'GPL'], + ['+', ''], + ['GNU GPL', 'GPL'], + ['GNU/GPL', 'GPL'], + ['GNU GLP', 'GPL'], + ['GNU General Public License', 'GPL'], + ['Gnu public license', 'GPL'], + ['GNU Public License', 'GPL'], + ['GNU GENERAL PUBLIC LICENSE', 'GPL'], + ['MTI', 'MIT'], + ['Mozilla Public License', 'MPL'], + ['WTH', 'WTF'], + ['-License', ''] +]; - if (error.message.includes('null bytes')) { - throw error; - } +var TRANSPOSED = 0; +var CORRECT = 1; - make(path.dirname(pth)); - return make(pth); - } +// Simple corrections to nearly valid identifiers. +var transforms = [ + // e.g. 'mit' + function(argument) { + return argument.toUpperCase(); + }, + // e.g. 'MIT ' + function(argument) { + return argument.trim(); + }, + // e.g. 'M.I.T.' + function(argument) { + return argument.replace(/\./g, ''); + }, + // e.g. 'Apache- 2.0' + function(argument) { + return argument.replace(/\s+/g, ''); + }, + // e.g. 'CC BY 4.0'' + function(argument) { + return argument.replace(/\s+/g, '-'); + }, + // e.g. 'LGPLv2.1' + function(argument) { + return argument.replace('v', '-'); + }, + // e.g. 'Apache 2.0' + function(argument) { + return argument.replace(/,?\s*(\d)/, '-$1'); + }, + // e.g. 'GPL 2' + function(argument) { + return argument.replace(/,?\s*(\d)/, '-$1.0'); + }, + // e.g. 'Apache Version 2.0' + function(argument) { + return argument.replace(/,?\s*(V\.|v\.|V|v|Version|version)\s*(\d)/, '-$2'); + }, + // e.g. 'Apache Version 2' + function(argument) { + return argument.replace(/,?\s*(V\.|v\.|V|v|Version|version)\s*(\d)/, '-$2.0'); + }, + // e.g. 'ZLIB' + function(argument) { + return argument[0].toUpperCase() + argument.slice(1); + }, + // e.g. 'MPL/2.0' + function(argument) { + return argument.replace('/', '-'); + }, + // e.g. 'Apache 2' + function(argument) { + return argument + .replace(/\s*V\s*(\d)/, '-$1') + .replace(/(\d)$/, '$1.0'); + }, + // e.g. 'GPL-2.0-' + function(argument) { + return argument.slice(0, argument.length - 1); + }, + // e.g. 'GPL2' + function(argument) { + return argument.replace(/(\d)$/, '-$1.0'); + }, + // e.g. 'BSD 3' + function(argument) { + return argument.replace(/(-| )?(\d)$/, '-$2-Clause'); + }, + // e.g. 'BSD clause 3' + function(argument) { + return argument.replace(/(-| )clause(-| )(\d)/, '-$3-Clause'); + }, + // e.g. 'BY-NC-4.0' + function(argument) { + return 'CC-' + argument; + }, + // e.g. 'BY-NC' + function(argument) { + return 'CC-' + argument + '-4.0'; + }, + // e.g. 'Attribution-NonCommercial' + function(argument) { + return argument + .replace('Attribution', 'BY') + .replace('NonCommercial', 'NC') + .replace('NoDerivatives', 'ND') + .replace(/ (\d)/, '-$1') + .replace(/ ?International/, ''); + }, + // e.g. 'Attribution-NonCommercial' + function(argument) { + return 'CC-' + + argument + .replace('Attribution', 'BY') + .replace('NonCommercial', 'NC') + .replace('NoDerivatives', 'ND') + .replace(/ (\d)/, '-$1') + .replace(/ ?International/, '') + + '-4.0'; + } +]; - try { - if (!options.fs.statSync(pth).isDirectory()) { - throw new Error('The path is not a directory'); - } - } catch (_) { - throw error; - } - } +// If all else fails, guess that strings containing certain substrings +// meant to identify certain licenses. +var lastResorts = [ + ['UNLI', 'Unlicense'], + ['WTF', 'WTFPL'], + ['2 CLAUSE', 'BSD-2-Clause'], + ['2-CLAUSE', 'BSD-2-Clause'], + ['3 CLAUSE', 'BSD-3-Clause'], + ['3-CLAUSE', 'BSD-3-Clause'], + ['AFFERO', 'AGPL-3.0'], + ['AGPL', 'AGPL-3.0'], + ['APACHE', 'Apache-2.0'], + ['ARTISTIC', 'Artistic-2.0'], + ['Affero', 'AGPL-3.0'], + ['BEER', 'Beerware'], + ['BOOST', 'BSL-1.0'], + ['BSD', 'BSD-2-Clause'], + ['ECLIPSE', 'EPL-1.0'], + ['FUCK', 'WTFPL'], + ['GNU', 'GPL-3.0'], + ['LGPL', 'LGPL-3.0'], + ['GPL', 'GPL-3.0'], + ['MIT', 'MIT'], + ['MPL', 'MPL-2.0'], + ['X11', 'X11'], + ['ZLIB', 'Zlib'] +]; - return pth; - }; +var SUBSTRING = 0; +var IDENTIFIER = 1; - return make(path.resolve(input)); +var validTransformation = function(identifier) { + for (var i = 0; i < transforms.length; i++) { + var transformed = transforms[i](identifier); + if (transformed !== identifier && valid(transformed)) { + return transformed; + } + } + return null; }; - -/***/ }), -/* 116 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -const processFn = (fn, options) => function (...args) { - const P = options.promiseModule; - - return new P((resolve, reject) => { - if (options.multiArgs) { - args.push((...result) => { - if (options.errorFirst) { - if (result[0]) { - reject(result); - } else { - result.shift(); - resolve(result); - } - } else { - resolve(result); - } - }); - } else if (options.errorFirst) { - args.push((error, result) => { - if (error) { - reject(error); - } else { - resolve(result); - } - }); - } else { - args.push(resolve); - } - - fn.apply(this, args); - }); +var validLastResort = function(identifier) { + var upperCased = identifier.toUpperCase(); + for (var i = 0; i < lastResorts.length; i++) { + var lastResort = lastResorts[i]; + if (upperCased.indexOf(lastResort[SUBSTRING]) > -1) { + return lastResort[IDENTIFIER]; + } + } + return null; }; -module.exports = (input, options) => { - options = Object.assign({ - exclude: [/.+(Sync|Stream)$/], - errorFirst: true, - promiseModule: Promise - }, options); - - const objType = typeof input; - if (!(input !== null && (objType === 'object' || objType === 'function'))) { - throw new TypeError(`Expected \`input\` to be a \`Function\` or \`Object\`, got \`${input === null ? 'null' : objType}\``); - } - - const filter = key => { - const match = pattern => typeof pattern === 'string' ? key === pattern : pattern.test(key); - return options.include ? options.include.some(match) : !options.exclude.some(match); - }; +var anyCorrection = function(identifier, check) { + for (var i = 0; i < transpositions.length; i++) { + var transposition = transpositions[i]; + var transposed = transposition[TRANSPOSED]; + if (identifier.indexOf(transposed) > -1) { + var corrected = identifier.replace( + transposed, + transposition[CORRECT] + ); + var checked = check(corrected); + if (checked !== null) { + return checked; + } + } + } + return null; +}; - let ret; - if (objType === 'function') { - ret = function (...args) { - return options.excludeMain ? input(...args) : processFn(input, options).apply(this, args); - }; - } else { - ret = Object.create(Object.getPrototypeOf(input)); - } +module.exports = function(identifier) { + identifier = identifier.replace(/\+$/, ''); + if (valid(identifier)) { + return identifier; + } + var transformed = validTransformation(identifier); + if (transformed !== null) { + return transformed; + } + transformed = anyCorrection(identifier, function(argument) { + if (valid(argument)) { + return argument; + } + return validTransformation(argument); + }); + if (transformed !== null) { + return transformed; + } + transformed = validLastResort(identifier); + if (transformed !== null) { + return transformed; + } + transformed = anyCorrection(identifier, validLastResort); + if (transformed !== null) { + return transformed; + } + return null; +}; - for (const key in input) { // eslint-disable-line guard-for-in - const property = input[key]; - ret[key] = typeof property === 'function' && filter(key) ? processFn(property, options) : property; - } - return ret; -}; +/***/ }), +/* 527 */ +/***/ (function(module) { +module.exports = JSON.parse("[\"Glide\",\"Abstyles\",\"AFL-1.1\",\"AFL-1.2\",\"AFL-2.0\",\"AFL-2.1\",\"AFL-3.0\",\"AMPAS\",\"APL-1.0\",\"Adobe-Glyph\",\"APAFML\",\"Adobe-2006\",\"AGPL-1.0\",\"Afmparse\",\"Aladdin\",\"ADSL\",\"AMDPLPA\",\"ANTLR-PD\",\"Apache-1.0\",\"Apache-1.1\",\"Apache-2.0\",\"AML\",\"APSL-1.0\",\"APSL-1.1\",\"APSL-1.2\",\"APSL-2.0\",\"Artistic-1.0\",\"Artistic-1.0-Perl\",\"Artistic-1.0-cl8\",\"Artistic-2.0\",\"AAL\",\"Bahyph\",\"Barr\",\"Beerware\",\"BitTorrent-1.0\",\"BitTorrent-1.1\",\"BSL-1.0\",\"Borceux\",\"BSD-2-Clause\",\"BSD-2-Clause-FreeBSD\",\"BSD-2-Clause-NetBSD\",\"BSD-3-Clause\",\"BSD-3-Clause-Clear\",\"BSD-4-Clause\",\"BSD-Protection\",\"BSD-Source-Code\",\"BSD-3-Clause-Attribution\",\"0BSD\",\"BSD-4-Clause-UC\",\"bzip2-1.0.5\",\"bzip2-1.0.6\",\"Caldera\",\"CECILL-1.0\",\"CECILL-1.1\",\"CECILL-2.0\",\"CECILL-2.1\",\"CECILL-B\",\"CECILL-C\",\"ClArtistic\",\"MIT-CMU\",\"CNRI-Jython\",\"CNRI-Python\",\"CNRI-Python-GPL-Compatible\",\"CPOL-1.02\",\"CDDL-1.0\",\"CDDL-1.1\",\"CPAL-1.0\",\"CPL-1.0\",\"CATOSL-1.1\",\"Condor-1.1\",\"CC-BY-1.0\",\"CC-BY-2.0\",\"CC-BY-2.5\",\"CC-BY-3.0\",\"CC-BY-4.0\",\"CC-BY-ND-1.0\",\"CC-BY-ND-2.0\",\"CC-BY-ND-2.5\",\"CC-BY-ND-3.0\",\"CC-BY-ND-4.0\",\"CC-BY-NC-1.0\",\"CC-BY-NC-2.0\",\"CC-BY-NC-2.5\",\"CC-BY-NC-3.0\",\"CC-BY-NC-4.0\",\"CC-BY-NC-ND-1.0\",\"CC-BY-NC-ND-2.0\",\"CC-BY-NC-ND-2.5\",\"CC-BY-NC-ND-3.0\",\"CC-BY-NC-ND-4.0\",\"CC-BY-NC-SA-1.0\",\"CC-BY-NC-SA-2.0\",\"CC-BY-NC-SA-2.5\",\"CC-BY-NC-SA-3.0\",\"CC-BY-NC-SA-4.0\",\"CC-BY-SA-1.0\",\"CC-BY-SA-2.0\",\"CC-BY-SA-2.5\",\"CC-BY-SA-3.0\",\"CC-BY-SA-4.0\",\"CC0-1.0\",\"Crossword\",\"CrystalStacker\",\"CUA-OPL-1.0\",\"Cube\",\"curl\",\"D-FSL-1.0\",\"diffmark\",\"WTFPL\",\"DOC\",\"Dotseqn\",\"DSDP\",\"dvipdfm\",\"EPL-1.0\",\"ECL-1.0\",\"ECL-2.0\",\"eGenix\",\"EFL-1.0\",\"EFL-2.0\",\"MIT-advertising\",\"MIT-enna\",\"Entessa\",\"ErlPL-1.1\",\"EUDatagrid\",\"EUPL-1.0\",\"EUPL-1.1\",\"Eurosym\",\"Fair\",\"MIT-feh\",\"Frameworx-1.0\",\"FreeImage\",\"FTL\",\"FSFAP\",\"FSFUL\",\"FSFULLR\",\"Giftware\",\"GL2PS\",\"Glulxe\",\"AGPL-3.0\",\"GFDL-1.1\",\"GFDL-1.2\",\"GFDL-1.3\",\"GPL-1.0\",\"GPL-2.0\",\"GPL-3.0\",\"LGPL-2.1\",\"LGPL-3.0\",\"LGPL-2.0\",\"gnuplot\",\"gSOAP-1.3b\",\"HaskellReport\",\"HPND\",\"IBM-pibs\",\"IPL-1.0\",\"ICU\",\"ImageMagick\",\"iMatix\",\"Imlib2\",\"IJG\",\"Info-ZIP\",\"Intel-ACPI\",\"Intel\",\"Interbase-1.0\",\"IPA\",\"ISC\",\"JasPer-2.0\",\"JSON\",\"LPPL-1.0\",\"LPPL-1.1\",\"LPPL-1.2\",\"LPPL-1.3a\",\"LPPL-1.3c\",\"Latex2e\",\"BSD-3-Clause-LBNL\",\"Leptonica\",\"LGPLLR\",\"Libpng\",\"libtiff\",\"LAL-1.2\",\"LAL-1.3\",\"LiLiQ-P-1.1\",\"LiLiQ-Rplus-1.1\",\"LiLiQ-R-1.1\",\"LPL-1.02\",\"LPL-1.0\",\"MakeIndex\",\"MTLL\",\"MS-PL\",\"MS-RL\",\"MirOS\",\"MITNFA\",\"MIT\",\"Motosoto\",\"MPL-1.0\",\"MPL-1.1\",\"MPL-2.0\",\"MPL-2.0-no-copyleft-exception\",\"mpich2\",\"Multics\",\"Mup\",\"NASA-1.3\",\"Naumen\",\"NBPL-1.0\",\"NetCDF\",\"NGPL\",\"NOSL\",\"NPL-1.0\",\"NPL-1.1\",\"Newsletr\",\"NLPL\",\"Nokia\",\"NPOSL-3.0\",\"NLOD-1.0\",\"Noweb\",\"NRL\",\"NTP\",\"Nunit\",\"OCLC-2.0\",\"ODbL-1.0\",\"PDDL-1.0\",\"OCCT-PL\",\"OGTSL\",\"OLDAP-2.2.2\",\"OLDAP-1.1\",\"OLDAP-1.2\",\"OLDAP-1.3\",\"OLDAP-1.4\",\"OLDAP-2.0\",\"OLDAP-2.0.1\",\"OLDAP-2.1\",\"OLDAP-2.2\",\"OLDAP-2.2.1\",\"OLDAP-2.3\",\"OLDAP-2.4\",\"OLDAP-2.5\",\"OLDAP-2.6\",\"OLDAP-2.7\",\"OLDAP-2.8\",\"OML\",\"OPL-1.0\",\"OSL-1.0\",\"OSL-1.1\",\"OSL-2.0\",\"OSL-2.1\",\"OSL-3.0\",\"OpenSSL\",\"OSET-PL-2.1\",\"PHP-3.0\",\"PHP-3.01\",\"Plexus\",\"PostgreSQL\",\"psfrag\",\"psutils\",\"Python-2.0\",\"QPL-1.0\",\"Qhull\",\"Rdisc\",\"RPSL-1.0\",\"RPL-1.1\",\"RPL-1.5\",\"RHeCos-1.1\",\"RSCPL\",\"RSA-MD\",\"Ruby\",\"SAX-PD\",\"Saxpath\",\"SCEA\",\"SWL\",\"SMPPL\",\"Sendmail\",\"SGI-B-1.0\",\"SGI-B-1.1\",\"SGI-B-2.0\",\"OFL-1.0\",\"OFL-1.1\",\"SimPL-2.0\",\"Sleepycat\",\"SNIA\",\"Spencer-86\",\"Spencer-94\",\"Spencer-99\",\"SMLNJ\",\"SugarCRM-1.1.3\",\"SISSL\",\"SISSL-1.2\",\"SPL-1.0\",\"Watcom-1.0\",\"TCL\",\"Unlicense\",\"TMate\",\"TORQUE-1.1\",\"TOSL\",\"Unicode-TOU\",\"UPL-1.0\",\"NCSA\",\"Vim\",\"VOSTROM\",\"VSL-1.0\",\"W3C-19980720\",\"W3C\",\"Wsuipa\",\"Xnet\",\"X11\",\"Xerox\",\"XFree86-1.1\",\"xinetd\",\"xpp\",\"XSkat\",\"YPL-1.0\",\"YPL-1.1\",\"Zed\",\"Zend-2.0\",\"Zimbra-1.3\",\"Zimbra-1.4\",\"Zlib\",\"zlib-acknowledgement\",\"ZPL-1.1\",\"ZPL-2.0\",\"ZPL-2.1\",\"BSD-3-Clause-No-Nuclear-License\",\"BSD-3-Clause-No-Nuclear-Warranty\",\"BSD-3-Clause-No-Nuclear-License-2014\",\"eCos-2.0\",\"GPL-2.0-with-autoconf-exception\",\"GPL-2.0-with-bison-exception\",\"GPL-2.0-with-classpath-exception\",\"GPL-2.0-with-font-exception\",\"GPL-2.0-with-GCC-exception\",\"GPL-3.0-with-autoconf-exception\",\"GPL-3.0-with-GCC-exception\",\"StandardML-NJ\",\"WXwindows\"]"); /***/ }), -/* 117 */ -/***/ (function(module, exports) { +/* 528 */ +/***/ (function(module, exports, __webpack_require__) { -exports = module.exports = SemVer +"use strict"; -var debug -/* istanbul ignore next */ -if (typeof process === 'object' && - process.env && - process.env.NODE_DEBUG && - /\bsemver\b/i.test(process.env.NODE_DEBUG)) { - debug = function () { - var args = Array.prototype.slice.call(arguments, 0) - args.unshift('SEMVER') - console.log.apply(console, args) - } -} else { - debug = function () {} -} +var url = __webpack_require__(454) +var gitHosts = __webpack_require__(529) +var GitHost = module.exports = __webpack_require__(530) -// Note: this is the semver.org version of the spec that it implements -// Not necessarily the package version of this code. -exports.SEMVER_SPEC_VERSION = '2.0.0' +var protocolToRepresentationMap = { + 'git+ssh': 'sshurl', + 'git+https': 'https', + 'ssh': 'sshurl', + 'git': 'git' +} -var MAX_LENGTH = 256 -var MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER || - /* istanbul ignore next */ 9007199254740991 +function protocolToRepresentation (protocol) { + if (protocol.substr(-1) === ':') protocol = protocol.slice(0, -1) + return protocolToRepresentationMap[protocol] || protocol +} -// Max safe segment length for coercion. -var MAX_SAFE_COMPONENT_LENGTH = 16 +var authProtocols = { + 'git:': true, + 'https:': true, + 'git+https:': true, + 'http:': true, + 'git+http:': true +} -// The actual regexps go on exports.re -var re = exports.re = [] -var src = exports.src = [] -var R = 0 +var cache = {} -// The following Regular Expressions can be used for tokenizing, -// validating, and parsing SemVer version strings. +module.exports.fromUrl = function (giturl, opts) { + var key = giturl + JSON.stringify(opts || {}) -// ## Numeric Identifier -// A single `0`, or a non-zero digit followed by zero or more digits. + if (!(key in cache)) { + cache[key] = fromUrl(giturl, opts) + } -var NUMERICIDENTIFIER = R++ -src[NUMERICIDENTIFIER] = '0|[1-9]\\d*' -var NUMERICIDENTIFIERLOOSE = R++ -src[NUMERICIDENTIFIERLOOSE] = '[0-9]+' + return cache[key] +} -// ## Non-numeric Identifier -// Zero or more digits, followed by a letter or hyphen, and then zero or -// more letters, digits, or hyphens. +function fromUrl (giturl, opts) { + if (giturl == null || giturl === '') return + var url = fixupUnqualifiedGist( + isGitHubShorthand(giturl) ? 'github:' + giturl : giturl + ) + var parsed = parseGitUrl(url) + var shortcutMatch = url.match(new RegExp('^([^:]+):(?:(?:[^@:]+(?:[^@]+)?@)?([^/]*))[/](.+?)(?:[.]git)?($|#)')) + var matches = Object.keys(gitHosts).map(function (gitHostName) { + try { + var gitHostInfo = gitHosts[gitHostName] + var auth = null + if (parsed.auth && authProtocols[parsed.protocol]) { + auth = decodeURIComponent(parsed.auth) + } + var committish = parsed.hash ? decodeURIComponent(parsed.hash.substr(1)) : null + var user = null + var project = null + var defaultRepresentation = null + if (shortcutMatch && shortcutMatch[1] === gitHostName) { + user = shortcutMatch[2] && decodeURIComponent(shortcutMatch[2]) + project = decodeURIComponent(shortcutMatch[3]) + defaultRepresentation = 'shortcut' + } else { + if (parsed.host !== gitHostInfo.domain) return + if (!gitHostInfo.protocols_re.test(parsed.protocol)) return + if (!parsed.path) return + var pathmatch = gitHostInfo.pathmatch + var matched = parsed.path.match(pathmatch) + if (!matched) return + if (matched[1] != null) user = decodeURIComponent(matched[1].replace(/^:/, '')) + if (matched[2] != null) project = decodeURIComponent(matched[2]) + defaultRepresentation = protocolToRepresentation(parsed.protocol) + } + return new GitHost(gitHostName, user, auth, project, committish, defaultRepresentation, opts) + } catch (ex) { + if (!(ex instanceof URIError)) throw ex + } + }).filter(function (gitHostInfo) { return gitHostInfo }) + if (matches.length !== 1) return + return matches[0] +} -var NONNUMERICIDENTIFIER = R++ -src[NONNUMERICIDENTIFIER] = '\\d*[a-zA-Z-][a-zA-Z0-9-]*' +function isGitHubShorthand (arg) { + // Note: This does not fully test the git ref format. + // See https://www.kernel.org/pub/software/scm/git/docs/git-check-ref-format.html + // + // The only way to do this properly would be to shell out to + // git-check-ref-format, and as this is a fast sync function, + // we don't want to do that. Just let git fail if it turns + // out that the commit-ish is invalid. + // GH usernames cannot start with . or - + return /^[^:@%/\s.-][^:@%/\s]*[/][^:@\s/%]+(?:#.*)?$/.test(arg) +} -// ## Main Version -// Three dot-separated numeric identifiers. +function fixupUnqualifiedGist (giturl) { + // necessary for round-tripping gists + var parsed = url.parse(giturl) + if (parsed.protocol === 'gist:' && parsed.host && !parsed.path) { + return parsed.protocol + '/' + parsed.host + } else { + return giturl + } +} -var MAINVERSION = R++ -src[MAINVERSION] = '(' + src[NUMERICIDENTIFIER] + ')\\.' + - '(' + src[NUMERICIDENTIFIER] + ')\\.' + - '(' + src[NUMERICIDENTIFIER] + ')' +function parseGitUrl (giturl) { + if (typeof giturl !== 'string') giturl = '' + giturl + var matched = giturl.match(/^([^@]+)@([^:/]+):[/]?((?:[^/]+[/])?[^/]+?)(?:[.]git)?(#.*)?$/) + if (!matched) return url.parse(giturl) + return { + protocol: 'git+ssh:', + slashes: true, + auth: matched[1], + host: matched[2], + port: null, + hostname: matched[2], + hash: matched[4], + search: null, + query: null, + pathname: '/' + matched[3], + path: '/' + matched[3], + href: 'git+ssh://' + matched[1] + '@' + matched[2] + + '/' + matched[3] + (matched[4] || '') + } +} -var MAINVERSIONLOOSE = R++ -src[MAINVERSIONLOOSE] = '(' + src[NUMERICIDENTIFIERLOOSE] + ')\\.' + - '(' + src[NUMERICIDENTIFIERLOOSE] + ')\\.' + - '(' + src[NUMERICIDENTIFIERLOOSE] + ')' -// ## Pre-release Version Identifier -// A numeric identifier, or a non-numeric identifier. +/***/ }), +/* 529 */ +/***/ (function(module, exports, __webpack_require__) { -var PRERELEASEIDENTIFIER = R++ -src[PRERELEASEIDENTIFIER] = '(?:' + src[NUMERICIDENTIFIER] + - '|' + src[NONNUMERICIDENTIFIER] + ')' +"use strict"; -var PRERELEASEIDENTIFIERLOOSE = R++ -src[PRERELEASEIDENTIFIERLOOSE] = '(?:' + src[NUMERICIDENTIFIERLOOSE] + - '|' + src[NONNUMERICIDENTIFIER] + ')' -// ## Pre-release Version -// Hyphen, followed by one or more dot-separated pre-release version -// identifiers. +var gitHosts = module.exports = { + github: { + // First two are insecure and generally shouldn't be used any more, but + // they are still supported. + 'protocols': [ 'git', 'http', 'git+ssh', 'git+https', 'ssh', 'https' ], + 'domain': 'github.com', + 'treepath': 'tree', + 'filetemplate': 'https://{auth@}raw.githubusercontent.com/{user}/{project}/{committish}/{path}', + 'bugstemplate': 'https://{domain}/{user}/{project}/issues', + 'gittemplate': 'git://{auth@}{domain}/{user}/{project}.git{#committish}', + 'tarballtemplate': 'https://{domain}/{user}/{project}/archive/{committish}.tar.gz' + }, + bitbucket: { + 'protocols': [ 'git+ssh', 'git+https', 'ssh', 'https' ], + 'domain': 'bitbucket.org', + 'treepath': 'src', + 'tarballtemplate': 'https://{domain}/{user}/{project}/get/{committish}.tar.gz' + }, + gitlab: { + 'protocols': [ 'git+ssh', 'git+https', 'ssh', 'https' ], + 'domain': 'gitlab.com', + 'treepath': 'tree', + 'docstemplate': 'https://{domain}/{user}/{project}{/tree/committish}#README', + 'bugstemplate': 'https://{domain}/{user}/{project}/issues', + 'tarballtemplate': 'https://{domain}/{user}/{project}/repository/archive.tar.gz?ref={committish}' + }, + gist: { + 'protocols': [ 'git', 'git+ssh', 'git+https', 'ssh', 'https' ], + 'domain': 'gist.github.com', + 'pathmatch': /^[/](?:([^/]+)[/])?([a-z0-9]+)(?:[.]git)?$/, + 'filetemplate': 'https://gist.githubusercontent.com/{user}/{project}/raw{/committish}/{path}', + 'bugstemplate': 'https://{domain}/{project}', + 'gittemplate': 'git://{domain}/{project}.git{#committish}', + 'sshtemplate': 'git@{domain}:/{project}.git{#committish}', + 'sshurltemplate': 'git+ssh://git@{domain}/{project}.git{#committish}', + 'browsetemplate': 'https://{domain}/{project}{/committish}', + 'docstemplate': 'https://{domain}/{project}{/committish}', + 'httpstemplate': 'git+https://{domain}/{project}.git{#committish}', + 'shortcuttemplate': '{type}:{project}{#committish}', + 'pathtemplate': '{project}{#committish}', + 'tarballtemplate': 'https://{domain}/{user}/{project}/archive/{committish}.tar.gz' + } +} -var PRERELEASE = R++ -src[PRERELEASE] = '(?:-(' + src[PRERELEASEIDENTIFIER] + - '(?:\\.' + src[PRERELEASEIDENTIFIER] + ')*))' +var gitHostDefaults = { + 'sshtemplate': 'git@{domain}:{user}/{project}.git{#committish}', + 'sshurltemplate': 'git+ssh://git@{domain}/{user}/{project}.git{#committish}', + 'browsetemplate': 'https://{domain}/{user}/{project}{/tree/committish}', + 'docstemplate': 'https://{domain}/{user}/{project}{/tree/committish}#readme', + 'httpstemplate': 'git+https://{auth@}{domain}/{user}/{project}.git{#committish}', + 'filetemplate': 'https://{domain}/{user}/{project}/raw/{committish}/{path}', + 'shortcuttemplate': '{type}:{user}/{project}{#committish}', + 'pathtemplate': '{user}/{project}{#committish}', + 'pathmatch': /^[/]([^/]+)[/]([^/]+?)(?:[.]git|[/])?$/ +} -var PRERELEASELOOSE = R++ -src[PRERELEASELOOSE] = '(?:-?(' + src[PRERELEASEIDENTIFIERLOOSE] + - '(?:\\.' + src[PRERELEASEIDENTIFIERLOOSE] + ')*))' +Object.keys(gitHosts).forEach(function (name) { + Object.keys(gitHostDefaults).forEach(function (key) { + if (gitHosts[name][key]) return + gitHosts[name][key] = gitHostDefaults[key] + }) + gitHosts[name].protocols_re = RegExp('^(' + + gitHosts[name].protocols.map(function (protocol) { + return protocol.replace(/([\\+*{}()[\]$^|])/g, '\\$1') + }).join('|') + '):$') +}) -// ## Build Metadata Identifier -// Any combination of digits, letters, or hyphens. -var BUILDIDENTIFIER = R++ -src[BUILDIDENTIFIER] = '[0-9A-Za-z-]+' +/***/ }), +/* 530 */ +/***/ (function(module, exports, __webpack_require__) { -// ## Build Metadata -// Plus sign, followed by one or more period-separated build metadata -// identifiers. +"use strict"; -var BUILD = R++ -src[BUILD] = '(?:\\+(' + src[BUILDIDENTIFIER] + - '(?:\\.' + src[BUILDIDENTIFIER] + ')*))' +var gitHosts = __webpack_require__(529) +var extend = Object.assign || __webpack_require__(29)._extend -// ## Full Version String -// A main version, followed optionally by a pre-release version and -// build metadata. +var GitHost = module.exports = function (type, user, auth, project, committish, defaultRepresentation, opts) { + var gitHostInfo = this + gitHostInfo.type = type + Object.keys(gitHosts[type]).forEach(function (key) { + gitHostInfo[key] = gitHosts[type][key] + }) + gitHostInfo.user = user + gitHostInfo.auth = auth + gitHostInfo.project = project + gitHostInfo.committish = committish + gitHostInfo.default = defaultRepresentation + gitHostInfo.opts = opts || {} +} +GitHost.prototype = {} -// Note that the only major, minor, patch, and pre-release sections of -// the version string are capturing groups. The build metadata is not a -// capturing group, because it should not ever be used in version -// comparison. +GitHost.prototype.hash = function () { + return this.committish ? '#' + this.committish : '' +} -var FULL = R++ -var FULLPLAIN = 'v?' + src[MAINVERSION] + - src[PRERELEASE] + '?' + - src[BUILD] + '?' +GitHost.prototype._fill = function (template, opts) { + if (!template) return + var vars = extend({}, opts) + opts = extend(extend({}, this.opts), opts) + var self = this + Object.keys(this).forEach(function (key) { + if (self[key] != null && vars[key] == null) vars[key] = self[key] + }) + var rawAuth = vars.auth + var rawComittish = vars.committish + Object.keys(vars).forEach(function (key) { + vars[key] = encodeURIComponent(vars[key]) + }) + vars['auth@'] = rawAuth ? rawAuth + '@' : '' + if (opts.noCommittish) { + vars['#committish'] = '' + vars['/tree/committish'] = '' + vars['/comittish'] = '' + vars.comittish = '' + } else { + vars['#committish'] = rawComittish ? '#' + rawComittish : '' + vars['/tree/committish'] = vars.committish + ? '/' + vars.treepath + '/' + vars.committish + : '' + vars['/committish'] = vars.committish ? '/' + vars.committish : '' + vars.committish = vars.committish || 'master' + } + var res = template + Object.keys(vars).forEach(function (key) { + res = res.replace(new RegExp('[{]' + key + '[}]', 'g'), vars[key]) + }) + if (opts.noGitPlus) { + return res.replace(/^git[+]/, '') + } else { + return res + } +} -src[FULL] = '^' + FULLPLAIN + '$' +GitHost.prototype.ssh = function (opts) { + return this._fill(this.sshtemplate, opts) +} -// like full, but allows v1.2.3 and =1.2.3, which people do sometimes. -// also, 1.0.0alpha1 (prerelease without the hyphen) which is pretty -// common in the npm registry. -var LOOSEPLAIN = '[v=\\s]*' + src[MAINVERSIONLOOSE] + - src[PRERELEASELOOSE] + '?' + - src[BUILD] + '?' +GitHost.prototype.sshurl = function (opts) { + return this._fill(this.sshurltemplate, opts) +} -var LOOSE = R++ -src[LOOSE] = '^' + LOOSEPLAIN + '$' +GitHost.prototype.browse = function (opts) { + return this._fill(this.browsetemplate, opts) +} -var GTLT = R++ -src[GTLT] = '((?:<|>)?=?)' +GitHost.prototype.docs = function (opts) { + return this._fill(this.docstemplate, opts) +} -// Something like "2.*" or "1.2.x". -// Note that "x.x" is a valid xRange identifer, meaning "any version" -// Only the first item is strictly required. -var XRANGEIDENTIFIERLOOSE = R++ -src[XRANGEIDENTIFIERLOOSE] = src[NUMERICIDENTIFIERLOOSE] + '|x|X|\\*' -var XRANGEIDENTIFIER = R++ -src[XRANGEIDENTIFIER] = src[NUMERICIDENTIFIER] + '|x|X|\\*' +GitHost.prototype.bugs = function (opts) { + return this._fill(this.bugstemplate, opts) +} -var XRANGEPLAIN = R++ -src[XRANGEPLAIN] = '[v=\\s]*(' + src[XRANGEIDENTIFIER] + ')' + - '(?:\\.(' + src[XRANGEIDENTIFIER] + ')' + - '(?:\\.(' + src[XRANGEIDENTIFIER] + ')' + - '(?:' + src[PRERELEASE] + ')?' + - src[BUILD] + '?' + - ')?)?' +GitHost.prototype.https = function (opts) { + return this._fill(this.httpstemplate, opts) +} -var XRANGEPLAINLOOSE = R++ -src[XRANGEPLAINLOOSE] = '[v=\\s]*(' + src[XRANGEIDENTIFIERLOOSE] + ')' + - '(?:\\.(' + src[XRANGEIDENTIFIERLOOSE] + ')' + - '(?:\\.(' + src[XRANGEIDENTIFIERLOOSE] + ')' + - '(?:' + src[PRERELEASELOOSE] + ')?' + - src[BUILD] + '?' + - ')?)?' +GitHost.prototype.git = function (opts) { + return this._fill(this.gittemplate, opts) +} -var XRANGE = R++ -src[XRANGE] = '^' + src[GTLT] + '\\s*' + src[XRANGEPLAIN] + '$' -var XRANGELOOSE = R++ -src[XRANGELOOSE] = '^' + src[GTLT] + '\\s*' + src[XRANGEPLAINLOOSE] + '$' +GitHost.prototype.shortcut = function (opts) { + return this._fill(this.shortcuttemplate, opts) +} -// Coercion. -// Extract anything that could conceivably be a part of a valid semver -var COERCE = R++ -src[COERCE] = '(?:^|[^\\d])' + - '(\\d{1,' + MAX_SAFE_COMPONENT_LENGTH + '})' + - '(?:\\.(\\d{1,' + MAX_SAFE_COMPONENT_LENGTH + '}))?' + - '(?:\\.(\\d{1,' + MAX_SAFE_COMPONENT_LENGTH + '}))?' + - '(?:$|[^\\d])' +GitHost.prototype.path = function (opts) { + return this._fill(this.pathtemplate, opts) +} -// Tilde ranges. -// Meaning is "reasonably at or greater than" -var LONETILDE = R++ -src[LONETILDE] = '(?:~>?)' +GitHost.prototype.tarball = function (opts) { + return this._fill(this.tarballtemplate, opts) +} -var TILDETRIM = R++ -src[TILDETRIM] = '(\\s*)' + src[LONETILDE] + '\\s+' -re[TILDETRIM] = new RegExp(src[TILDETRIM], 'g') -var tildeTrimReplace = '$1~' +GitHost.prototype.file = function (P, opts) { + return this._fill(this.filetemplate, extend({ + path: P.replace(/^[/]+/g, '') + }, opts)) +} -var TILDE = R++ -src[TILDE] = '^' + src[LONETILDE] + src[XRANGEPLAIN] + '$' -var TILDELOOSE = R++ -src[TILDELOOSE] = '^' + src[LONETILDE] + src[XRANGEPLAINLOOSE] + '$' +GitHost.prototype.getDefaultRepresentation = function () { + return this.default +} -// Caret ranges. -// Meaning is "at least and backwards compatible with" -var LONECARET = R++ -src[LONECARET] = '(?:\\^)' +GitHost.prototype.toString = function (opts) { + return (this[this.default] || this.sshurl).call(this, opts) +} -var CARETTRIM = R++ -src[CARETTRIM] = '(\\s*)' + src[LONECARET] + '\\s+' -re[CARETTRIM] = new RegExp(src[CARETTRIM], 'g') -var caretTrimReplace = '$1^' -var CARET = R++ -src[CARET] = '^' + src[LONECARET] + src[XRANGEPLAIN] + '$' -var CARETLOOSE = R++ -src[CARETLOOSE] = '^' + src[LONECARET] + src[XRANGEPLAINLOOSE] + '$' +/***/ }), +/* 531 */ +/***/ (function(module, exports, __webpack_require__) { -// A simple gt/lt/eq thing, or just "" to indicate "any version" -var COMPARATORLOOSE = R++ -src[COMPARATORLOOSE] = '^' + src[GTLT] + '\\s*(' + LOOSEPLAIN + ')$|^$' -var COMPARATOR = R++ -src[COMPARATOR] = '^' + src[GTLT] + '\\s*(' + FULLPLAIN + ')$|^$' +var core = __webpack_require__(532); +var async = __webpack_require__(534); +async.core = core; +async.isCore = function isCore(x) { return core[x]; }; +async.sync = __webpack_require__(539); -// An expression to strip any whitespace between the gtlt and the thing -// it modifies, so that `> 1.2.3` ==> `>1.2.3` -var COMPARATORTRIM = R++ -src[COMPARATORTRIM] = '(\\s*)' + src[GTLT] + - '\\s*(' + LOOSEPLAIN + '|' + src[XRANGEPLAIN] + ')' +exports = async; +module.exports = async; -// this one has to use the /g flag -re[COMPARATORTRIM] = new RegExp(src[COMPARATORTRIM], 'g') -var comparatorTrimReplace = '$1$2$3' -// Something like `1.2.3 - 1.2.4` -// Note that these all use the loose form, because they'll be -// checked against either the strict or loose comparator form -// later. -var HYPHENRANGE = R++ -src[HYPHENRANGE] = '^\\s*(' + src[XRANGEPLAIN] + ')' + - '\\s+-\\s+' + - '(' + src[XRANGEPLAIN] + ')' + - '\\s*$' +/***/ }), +/* 532 */ +/***/ (function(module, exports, __webpack_require__) { -var HYPHENRANGELOOSE = R++ -src[HYPHENRANGELOOSE] = '^\\s*(' + src[XRANGEPLAINLOOSE] + ')' + - '\\s+-\\s+' + - '(' + src[XRANGEPLAINLOOSE] + ')' + - '\\s*$' +var current = (process.versions && process.versions.node && process.versions.node.split('.')) || []; -// Star ranges basically just allow anything at all. -var STAR = R++ -src[STAR] = '(<|>)?=?\\s*\\*' +function specifierIncluded(specifier) { + var parts = specifier.split(' '); + var op = parts.length > 1 ? parts[0] : '='; + var versionParts = (parts.length > 1 ? parts[1] : parts[0]).split('.'); -// Compile to actual regexp objects. -// All are flag-free, unless they were created above with a flag. -for (var i = 0; i < R; i++) { - debug(i, src[i]) - if (!re[i]) { - re[i] = new RegExp(src[i]) - } + for (var i = 0; i < 3; ++i) { + var cur = Number(current[i] || 0); + var ver = Number(versionParts[i] || 0); + if (cur === ver) { + continue; // eslint-disable-line no-restricted-syntax, no-continue + } + if (op === '<') { + return cur < ver; + } else if (op === '>=') { + return cur >= ver; + } else { + return false; + } + } + return op === '>='; } -exports.parse = parse -function parse (version, options) { - if (!options || typeof options !== 'object') { - options = { - loose: !!options, - includePrerelease: false +function matchesRange(range) { + var specifiers = range.split(/ ?&& ?/); + if (specifiers.length === 0) { return false; } + for (var i = 0; i < specifiers.length; ++i) { + if (!specifierIncluded(specifiers[i])) { return false; } } - } + return true; +} - if (version instanceof SemVer) { - return version - } +function versionIncluded(specifierValue) { + if (typeof specifierValue === 'boolean') { return specifierValue; } + if (specifierValue && typeof specifierValue === 'object') { + for (var i = 0; i < specifierValue.length; ++i) { + if (matchesRange(specifierValue[i])) { return true; } + } + return false; + } + return matchesRange(specifierValue); +} - if (typeof version !== 'string') { - return null - } +var data = __webpack_require__(533); - if (version.length > MAX_LENGTH) { - return null - } +var core = {}; +for (var mod in data) { // eslint-disable-line no-restricted-syntax + if (Object.prototype.hasOwnProperty.call(data, mod)) { + core[mod] = versionIncluded(data[mod]); + } +} +module.exports = core; - var r = options.loose ? re[LOOSE] : re[FULL] - if (!r.test(version)) { - return null - } - try { - return new SemVer(version, options) - } catch (er) { - return null - } -} +/***/ }), +/* 533 */ +/***/ (function(module) { -exports.valid = valid -function valid (version, options) { - var v = parse(version, options) - return v ? v.version : null -} +module.exports = JSON.parse("{\"assert\":true,\"async_hooks\":\">= 8\",\"buffer_ieee754\":\"< 0.9.7\",\"buffer\":true,\"child_process\":true,\"cluster\":true,\"console\":true,\"constants\":true,\"crypto\":true,\"_debugger\":\"< 8\",\"dgram\":true,\"dns\":true,\"domain\":true,\"events\":true,\"freelist\":\"< 6\",\"fs\":true,\"fs/promises\":\">= 10 && < 10.1\",\"_http_agent\":\">= 0.11.1\",\"_http_client\":\">= 0.11.1\",\"_http_common\":\">= 0.11.1\",\"_http_incoming\":\">= 0.11.1\",\"_http_outgoing\":\">= 0.11.1\",\"_http_server\":\">= 0.11.1\",\"http\":true,\"http2\":\">= 8.8\",\"https\":true,\"inspector\":\">= 8.0.0\",\"_linklist\":\"< 8\",\"module\":true,\"net\":true,\"node-inspect/lib/_inspect\":\">= 7.6.0\",\"node-inspect/lib/internal/inspect_client\":\">= 7.6.0\",\"node-inspect/lib/internal/inspect_repl\":\">= 7.6.0\",\"os\":true,\"path\":true,\"perf_hooks\":\">= 8.5\",\"process\":\">= 1\",\"punycode\":true,\"querystring\":true,\"readline\":true,\"repl\":true,\"smalloc\":\">= 0.11.5 && < 3\",\"_stream_duplex\":\">= 0.9.4\",\"_stream_transform\":\">= 0.9.4\",\"_stream_wrap\":\">= 1.4.1\",\"_stream_passthrough\":\">= 0.9.4\",\"_stream_readable\":\">= 0.9.4\",\"_stream_writable\":\">= 0.9.4\",\"stream\":true,\"string_decoder\":true,\"sys\":true,\"timers\":true,\"_tls_common\":\">= 0.11.13\",\"_tls_legacy\":\">= 0.11.3 && < 10\",\"_tls_wrap\":\">= 0.11.3\",\"tls\":true,\"trace_events\":\">= 10\",\"tty\":true,\"url\":true,\"util\":true,\"v8/tools/arguments\":\">= 10\",\"v8/tools/codemap\":[\">= 4.4.0 && < 5\",\">= 5.2.0\"],\"v8/tools/consarray\":[\">= 4.4.0 && < 5\",\">= 5.2.0\"],\"v8/tools/csvparser\":[\">= 4.4.0 && < 5\",\">= 5.2.0\"],\"v8/tools/logreader\":[\">= 4.4.0 && < 5\",\">= 5.2.0\"],\"v8/tools/profile_view\":[\">= 4.4.0 && < 5\",\">= 5.2.0\"],\"v8/tools/splaytree\":[\">= 4.4.0 && < 5\",\">= 5.2.0\"],\"v8\":\">= 1\",\"vm\":true,\"worker_threads\":\">= 11.7\",\"zlib\":true}"); -exports.clean = clean -function clean (version, options) { - var s = parse(version.trim().replace(/^[=v]+/, ''), options) - return s ? s.version : null -} +/***/ }), +/* 534 */ +/***/ (function(module, exports, __webpack_require__) { -exports.SemVer = SemVer +var core = __webpack_require__(532); +var fs = __webpack_require__(23); +var path = __webpack_require__(16); +var caller = __webpack_require__(535); +var nodeModulesPaths = __webpack_require__(536); +var normalizeOptions = __webpack_require__(538); -function SemVer (version, options) { - if (!options || typeof options !== 'object') { - options = { - loose: !!options, - includePrerelease: false +var defaultIsFile = function isFile(file, cb) { + fs.stat(file, function (err, stat) { + if (!err) { + return cb(null, stat.isFile() || stat.isFIFO()); + } + if (err.code === 'ENOENT' || err.code === 'ENOTDIR') return cb(null, false); + return cb(err); + }); +}; + +module.exports = function resolve(x, options, callback) { + var cb = callback; + var opts = options; + if (typeof options === 'function') { + cb = opts; + opts = {}; } - } - if (version instanceof SemVer) { - if (version.loose === options.loose) { - return version - } else { - version = version.version + if (typeof x !== 'string') { + var err = new TypeError('Path must be a string.'); + return process.nextTick(function () { + cb(err); + }); } - } else if (typeof version !== 'string') { - throw new TypeError('Invalid Version: ' + version) - } - if (version.length > MAX_LENGTH) { - throw new TypeError('version is longer than ' + MAX_LENGTH + ' characters') - } + opts = normalizeOptions(x, opts); - if (!(this instanceof SemVer)) { - return new SemVer(version, options) - } + var isFile = opts.isFile || defaultIsFile; + var readFile = opts.readFile || fs.readFile; - debug('SemVer', version, options) - this.options = options - this.loose = !!options.loose + var extensions = opts.extensions || ['.js']; + var basedir = opts.basedir || path.dirname(caller()); + var parent = opts.filename || basedir; - var m = version.trim().match(options.loose ? re[LOOSE] : re[FULL]) + opts.paths = opts.paths || []; - if (!m) { - throw new TypeError('Invalid Version: ' + version) - } + // ensure that `basedir` is an absolute path at this point, resolving against the process' current working directory + var absoluteStart = path.resolve(basedir); - this.raw = version + if (opts.preserveSymlinks === false) { + fs.realpath(absoluteStart, function (realPathErr, realStart) { + if (realPathErr && realPathErr.code !== 'ENOENT') cb(err); + else init(realPathErr ? absoluteStart : realStart); + }); + } else { + init(absoluteStart); + } - // these are actually numbers - this.major = +m[1] - this.minor = +m[2] - this.patch = +m[3] + var res; + function init(basedir) { + if ((/^(?:\.\.?(?:\/|$)|\/|([A-Za-z]:)?[/\\])/).test(x)) { + res = path.resolve(basedir, x); + if (x === '..' || x.slice(-1) === '/') res += '/'; + if ((/\/$/).test(x) && res === basedir) { + loadAsDirectory(res, opts.package, onfile); + } else loadAsFile(res, opts.package, onfile); + } else loadNodeModules(x, basedir, function (err, n, pkg) { + if (err) cb(err); + else if (n) cb(null, n, pkg); + else if (core[x]) return cb(null, x); + else { + var moduleError = new Error("Cannot find module '" + x + "' from '" + parent + "'"); + moduleError.code = 'MODULE_NOT_FOUND'; + cb(moduleError); + } + }); + } - if (this.major > MAX_SAFE_INTEGER || this.major < 0) { - throw new TypeError('Invalid major version') - } + function onfile(err, m, pkg) { + if (err) cb(err); + else if (m) cb(null, m, pkg); + else loadAsDirectory(res, function (err, d, pkg) { + if (err) cb(err); + else if (d) cb(null, d, pkg); + else { + var moduleError = new Error("Cannot find module '" + x + "' from '" + parent + "'"); + moduleError.code = 'MODULE_NOT_FOUND'; + cb(moduleError); + } + }); + } - if (this.minor > MAX_SAFE_INTEGER || this.minor < 0) { - throw new TypeError('Invalid minor version') - } + function loadAsFile(x, thePackage, callback) { + var loadAsFilePackage = thePackage; + var cb = callback; + if (typeof loadAsFilePackage === 'function') { + cb = loadAsFilePackage; + loadAsFilePackage = undefined; + } - if (this.patch > MAX_SAFE_INTEGER || this.patch < 0) { - throw new TypeError('Invalid patch version') - } + var exts = [''].concat(extensions); + load(exts, x, loadAsFilePackage); - // numberify any prerelease numeric ids - if (!m[4]) { - this.prerelease = [] - } else { - this.prerelease = m[4].split('.').map(function (id) { - if (/^[0-9]+$/.test(id)) { - var num = +id - if (num >= 0 && num < MAX_SAFE_INTEGER) { - return num + function load(exts, x, loadPackage) { + if (exts.length === 0) return cb(null, undefined, loadPackage); + var file = x + exts[0]; + + var pkg = loadPackage; + if (pkg) onpkg(null, pkg); + else loadpkg(path.dirname(file), onpkg); + + function onpkg(err, pkg_, dir) { + pkg = pkg_; + if (err) return cb(err); + if (dir && pkg && opts.pathFilter) { + var rfile = path.relative(dir, file); + var rel = rfile.slice(0, rfile.length - exts[0].length); + var r = opts.pathFilter(pkg, x, rel); + if (r) return load( + [''].concat(extensions.slice()), + path.resolve(dir, r), + pkg + ); + } + isFile(file, onex); + } + function onex(err, ex) { + if (err) return cb(err); + if (ex) return cb(null, file, pkg); + load(exts.slice(1), x, pkg); + } } - } - return id - }) - } + } - this.build = m[5] ? m[5].split('.') : [] - this.format() -} + function loadpkg(dir, cb) { + if (dir === '' || dir === '/') return cb(null); + if (process.platform === 'win32' && (/^\w:[/\\]*$/).test(dir)) { + return cb(null); + } + if ((/[/\\]node_modules[/\\]*$/).test(dir)) return cb(null); -SemVer.prototype.format = function () { - this.version = this.major + '.' + this.minor + '.' + this.patch - if (this.prerelease.length) { - this.version += '-' + this.prerelease.join('.') - } - return this.version -} + var pkgfile = path.join(dir, 'package.json'); + isFile(pkgfile, function (err, ex) { + // on err, ex is false + if (!ex) return loadpkg(path.dirname(dir), cb); -SemVer.prototype.toString = function () { - return this.version -} + readFile(pkgfile, function (err, body) { + if (err) cb(err); + try { var pkg = JSON.parse(body); } catch (jsonErr) {} -SemVer.prototype.compare = function (other) { - debug('SemVer.compare', this.version, this.options, other) - if (!(other instanceof SemVer)) { - other = new SemVer(other, this.options) - } + if (pkg && opts.packageFilter) { + pkg = opts.packageFilter(pkg, pkgfile); + } + cb(null, pkg, dir); + }); + }); + } - return this.compareMain(other) || this.comparePre(other) -} + function loadAsDirectory(x, loadAsDirectoryPackage, callback) { + var cb = callback; + var fpkg = loadAsDirectoryPackage; + if (typeof fpkg === 'function') { + cb = fpkg; + fpkg = opts.package; + } -SemVer.prototype.compareMain = function (other) { - if (!(other instanceof SemVer)) { - other = new SemVer(other, this.options) - } + var pkgfile = path.join(x, 'package.json'); + isFile(pkgfile, function (err, ex) { + if (err) return cb(err); + if (!ex) return loadAsFile(path.join(x, 'index'), fpkg, cb); - return compareIdentifiers(this.major, other.major) || - compareIdentifiers(this.minor, other.minor) || - compareIdentifiers(this.patch, other.patch) -} + readFile(pkgfile, function (err, body) { + if (err) return cb(err); + try { + var pkg = JSON.parse(body); + } catch (jsonErr) {} -SemVer.prototype.comparePre = function (other) { - if (!(other instanceof SemVer)) { - other = new SemVer(other, this.options) - } + if (opts.packageFilter) { + pkg = opts.packageFilter(pkg, pkgfile); + } - // NOT having a prerelease is > having one - if (this.prerelease.length && !other.prerelease.length) { - return -1 - } else if (!this.prerelease.length && other.prerelease.length) { - return 1 - } else if (!this.prerelease.length && !other.prerelease.length) { - return 0 - } + if (pkg.main) { + if (typeof pkg.main !== 'string') { + var mainError = new TypeError('package “' + pkg.name + '” `main` must be a string'); + mainError.code = 'INVALID_PACKAGE_MAIN'; + return cb(mainError); + } + if (pkg.main === '.' || pkg.main === './') { + pkg.main = 'index'; + } + loadAsFile(path.resolve(x, pkg.main), pkg, function (err, m, pkg) { + if (err) return cb(err); + if (m) return cb(null, m, pkg); + if (!pkg) return loadAsFile(path.join(x, 'index'), pkg, cb); - var i = 0 - do { - var a = this.prerelease[i] - var b = other.prerelease[i] - debug('prerelease compare', i, a, b) - if (a === undefined && b === undefined) { - return 0 - } else if (b === undefined) { - return 1 - } else if (a === undefined) { - return -1 - } else if (a === b) { - continue - } else { - return compareIdentifiers(a, b) + var dir = path.resolve(x, pkg.main); + loadAsDirectory(dir, pkg, function (err, n, pkg) { + if (err) return cb(err); + if (n) return cb(null, n, pkg); + loadAsFile(path.join(x, 'index'), pkg, cb); + }); + }); + return; + } + + loadAsFile(path.join(x, '/index'), pkg, cb); + }); + }); } - } while (++i) -} -// preminor will bump the version up to the next minor release, and immediately -// down to pre-release. premajor and prepatch work the same way. -SemVer.prototype.inc = function (release, identifier) { - switch (release) { - case 'premajor': - this.prerelease.length = 0 - this.patch = 0 - this.minor = 0 - this.major++ - this.inc('pre', identifier) - break - case 'preminor': - this.prerelease.length = 0 - this.patch = 0 - this.minor++ - this.inc('pre', identifier) - break - case 'prepatch': - // If this is already a prerelease, it will bump to the next version - // drop any prereleases that might already exist, since they are not - // relevant at this point. - this.prerelease.length = 0 - this.inc('patch', identifier) - this.inc('pre', identifier) - break - // If the input is a non-prerelease version, this acts the same as - // prepatch. - case 'prerelease': - if (this.prerelease.length === 0) { - this.inc('patch', identifier) - } - this.inc('pre', identifier) - break + function processDirs(cb, dirs) { + if (dirs.length === 0) return cb(null, undefined); + var dir = dirs[0]; - case 'major': - // If this is a pre-major version, bump up to the same major version. - // Otherwise increment major. - // 1.0.0-5 bumps to 1.0.0 - // 1.1.0 bumps to 2.0.0 - if (this.minor !== 0 || - this.patch !== 0 || - this.prerelease.length === 0) { - this.major++ - } - this.minor = 0 - this.patch = 0 - this.prerelease = [] - break - case 'minor': - // If this is a pre-minor version, bump up to the same minor version. - // Otherwise increment minor. - // 1.2.0-5 bumps to 1.2.0 - // 1.2.1 bumps to 1.3.0 - if (this.patch !== 0 || this.prerelease.length === 0) { - this.minor++ - } - this.patch = 0 - this.prerelease = [] - break - case 'patch': - // If this is not a pre-release version, it will increment the patch. - // If it is a pre-release it will bump up to the same patch version. - // 1.2.0-5 patches to 1.2.0 - // 1.2.0 patches to 1.2.1 - if (this.prerelease.length === 0) { - this.patch++ - } - this.prerelease = [] - break - // This probably shouldn't be used publicly. - // 1.0.0 "pre" would become 1.0.0-0 which is the wrong direction. - case 'pre': - if (this.prerelease.length === 0) { - this.prerelease = [0] - } else { - var i = this.prerelease.length - while (--i >= 0) { - if (typeof this.prerelease[i] === 'number') { - this.prerelease[i]++ - i = -2 - } - } - if (i === -1) { - // didn't increment anything - this.prerelease.push(0) + var file = path.join(dir, x); + loadAsFile(file, opts.package, onfile); + + function onfile(err, m, pkg) { + if (err) return cb(err); + if (m) return cb(null, m, pkg); + loadAsDirectory(path.join(dir, x), opts.package, ondir); } - } - if (identifier) { - // 1.2.0-beta.1 bumps to 1.2.0-beta.2, - // 1.2.0-beta.fooblz or 1.2.0-beta bumps to 1.2.0-beta.0 - if (this.prerelease[0] === identifier) { - if (isNaN(this.prerelease[1])) { - this.prerelease = [identifier, 0] - } - } else { - this.prerelease = [identifier, 0] + + function ondir(err, n, pkg) { + if (err) return cb(err); + if (n) return cb(null, n, pkg); + processDirs(cb, dirs.slice(1)); } - } - break + } + function loadNodeModules(x, start, cb) { + processDirs(cb, nodeModulesPaths(start, opts, x)); + } +}; + + +/***/ }), +/* 535 */ +/***/ (function(module, exports) { - default: - throw new Error('invalid increment argument: ' + release) - } - this.format() - this.raw = this.version - return this -} +module.exports = function () { + // see https://code.google.com/p/v8/wiki/JavaScriptStackTraceApi + var origPrepareStackTrace = Error.prepareStackTrace; + Error.prepareStackTrace = function (_, stack) { return stack; }; + var stack = (new Error()).stack; + Error.prepareStackTrace = origPrepareStackTrace; + return stack[2].getFileName(); +}; -exports.inc = inc -function inc (version, release, loose, identifier) { - if (typeof (loose) === 'string') { - identifier = loose - loose = undefined - } - try { - return new SemVer(version, loose).inc(release, identifier).version - } catch (er) { - return null - } -} +/***/ }), +/* 536 */ +/***/ (function(module, exports, __webpack_require__) { -exports.diff = diff -function diff (version1, version2) { - if (eq(version1, version2)) { - return null - } else { - var v1 = parse(version1) - var v2 = parse(version2) - var prefix = '' - if (v1.prerelease.length || v2.prerelease.length) { - prefix = 'pre' - var defaultResult = 'prerelease' +var path = __webpack_require__(16); +var parse = path.parse || __webpack_require__(537); + +var getNodeModulesDirs = function getNodeModulesDirs(absoluteStart, modules) { + var prefix = '/'; + if ((/^([A-Za-z]:)/).test(absoluteStart)) { + prefix = ''; + } else if ((/^\\\\/).test(absoluteStart)) { + prefix = '\\\\'; } - for (var key in v1) { - if (key === 'major' || key === 'minor' || key === 'patch') { - if (v1[key] !== v2[key]) { - return prefix + key - } - } + + var paths = [absoluteStart]; + var parsed = parse(absoluteStart); + while (parsed.dir !== paths[paths.length - 1]) { + paths.push(parsed.dir); + parsed = parse(parsed.dir); } - return defaultResult // may be undefined - } -} -exports.compareIdentifiers = compareIdentifiers + return paths.reduce(function (dirs, aPath) { + return dirs.concat(modules.map(function (moduleDir) { + return path.join(prefix, aPath, moduleDir); + })); + }, []); +}; -var numeric = /^[0-9]+$/ -function compareIdentifiers (a, b) { - var anum = numeric.test(a) - var bnum = numeric.test(b) +module.exports = function nodeModulesPaths(start, opts, request) { + var modules = opts && opts.moduleDirectory + ? [].concat(opts.moduleDirectory) + : ['node_modules']; - if (anum && bnum) { - a = +a - b = +b - } + if (opts && typeof opts.paths === 'function') { + return opts.paths( + request, + start, + function () { return getNodeModulesDirs(start, modules); }, + opts + ); + } - return a === b ? 0 - : (anum && !bnum) ? -1 - : (bnum && !anum) ? 1 - : a < b ? -1 - : 1 -} + var dirs = getNodeModulesDirs(start, modules); + return opts && opts.paths ? dirs.concat(opts.paths) : dirs; +}; -exports.rcompareIdentifiers = rcompareIdentifiers -function rcompareIdentifiers (a, b) { - return compareIdentifiers(b, a) -} -exports.major = major -function major (a, loose) { - return new SemVer(a, loose).major -} +/***/ }), +/* 537 */ +/***/ (function(module, exports, __webpack_require__) { -exports.minor = minor -function minor (a, loose) { - return new SemVer(a, loose).minor -} +"use strict"; -exports.patch = patch -function patch (a, loose) { - return new SemVer(a, loose).patch -} -exports.compare = compare -function compare (a, b, loose) { - return new SemVer(a, loose).compare(new SemVer(b, loose)) -} +var isWindows = process.platform === 'win32'; -exports.compareLoose = compareLoose -function compareLoose (a, b) { - return compare(a, b, true) -} +// Regex to split a windows path into three parts: [*, device, slash, +// tail] windows-only +var splitDeviceRe = + /^([a-zA-Z]:|[\\\/]{2}[^\\\/]+[\\\/]+[^\\\/]+)?([\\\/])?([\s\S]*?)$/; -exports.rcompare = rcompare -function rcompare (a, b, loose) { - return compare(b, a, loose) -} +// Regex to split the tail part of the above into [*, dir, basename, ext] +var splitTailRe = + /^([\s\S]*?)((?:\.{1,2}|[^\\\/]+?|)(\.[^.\/\\]*|))(?:[\\\/]*)$/; -exports.sort = sort -function sort (list, loose) { - return list.sort(function (a, b) { - return exports.compare(a, b, loose) - }) -} +var win32 = {}; -exports.rsort = rsort -function rsort (list, loose) { - return list.sort(function (a, b) { - return exports.rcompare(a, b, loose) - }) +// Function to split a filename into [root, dir, basename, ext] +function win32SplitPath(filename) { + // Separate device+slash from tail + var result = splitDeviceRe.exec(filename), + device = (result[1] || '') + (result[2] || ''), + tail = result[3] || ''; + // Split the tail into dir, basename and extension + var result2 = splitTailRe.exec(tail), + dir = result2[1], + basename = result2[2], + ext = result2[3]; + return [device, dir, basename, ext]; } -exports.gt = gt -function gt (a, b, loose) { - return compare(a, b, loose) > 0 -} +win32.parse = function(pathString) { + if (typeof pathString !== 'string') { + throw new TypeError( + "Parameter 'pathString' must be a string, not " + typeof pathString + ); + } + var allParts = win32SplitPath(pathString); + if (!allParts || allParts.length !== 4) { + throw new TypeError("Invalid path '" + pathString + "'"); + } + return { + root: allParts[0], + dir: allParts[0] + allParts[1].slice(0, -1), + base: allParts[2], + ext: allParts[3], + name: allParts[2].slice(0, allParts[2].length - allParts[3].length) + }; +}; -exports.lt = lt -function lt (a, b, loose) { - return compare(a, b, loose) < 0 -} -exports.eq = eq -function eq (a, b, loose) { - return compare(a, b, loose) === 0 -} -exports.neq = neq -function neq (a, b, loose) { - return compare(a, b, loose) !== 0 -} +// Split a filename into [root, dir, basename, ext], unix version +// 'root' is just a slash, or nothing. +var splitPathRe = + /^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/; +var posix = {}; -exports.gte = gte -function gte (a, b, loose) { - return compare(a, b, loose) >= 0 -} -exports.lte = lte -function lte (a, b, loose) { - return compare(a, b, loose) <= 0 +function posixSplitPath(filename) { + return splitPathRe.exec(filename).slice(1); } -exports.cmp = cmp -function cmp (a, op, b, loose) { - switch (op) { - case '===': - if (typeof a === 'object') - a = a.version - if (typeof b === 'object') - b = b.version - return a === b - case '!==': - if (typeof a === 'object') - a = a.version - if (typeof b === 'object') - b = b.version - return a !== b +posix.parse = function(pathString) { + if (typeof pathString !== 'string') { + throw new TypeError( + "Parameter 'pathString' must be a string, not " + typeof pathString + ); + } + var allParts = posixSplitPath(pathString); + if (!allParts || allParts.length !== 4) { + throw new TypeError("Invalid path '" + pathString + "'"); + } + allParts[1] = allParts[1] || ''; + allParts[2] = allParts[2] || ''; + allParts[3] = allParts[3] || ''; - case '': - case '=': - case '==': - return eq(a, b, loose) + return { + root: allParts[0], + dir: allParts[0] + allParts[1].slice(0, -1), + base: allParts[2], + ext: allParts[3], + name: allParts[2].slice(0, allParts[2].length - allParts[3].length) + }; +}; - case '!=': - return neq(a, b, loose) - case '>': - return gt(a, b, loose) +if (isWindows) + module.exports = win32.parse; +else /* posix */ + module.exports = posix.parse; - case '>=': - return gte(a, b, loose) +module.exports.posix = posix.parse; +module.exports.win32 = win32.parse; - case '<': - return lt(a, b, loose) - case '<=': - return lte(a, b, loose) +/***/ }), +/* 538 */ +/***/ (function(module, exports) { - default: - throw new TypeError('Invalid operator: ' + op) - } -} +module.exports = function (x, opts) { + /** + * This file is purposefully a passthrough. It's expected that third-party + * environments will override it at runtime in order to inject special logic + * into `resolve` (by manipulating the options). One such example is the PnP + * code path in Yarn. + */ -exports.Comparator = Comparator -function Comparator (comp, options) { - if (!options || typeof options !== 'object') { - options = { - loose: !!options, - includePrerelease: false + return opts || {}; +}; + + +/***/ }), +/* 539 */ +/***/ (function(module, exports, __webpack_require__) { + +var core = __webpack_require__(532); +var fs = __webpack_require__(23); +var path = __webpack_require__(16); +var caller = __webpack_require__(535); +var nodeModulesPaths = __webpack_require__(536); +var normalizeOptions = __webpack_require__(538); + +var defaultIsFile = function isFile(file) { + try { + var stat = fs.statSync(file); + } catch (e) { + if (e && (e.code === 'ENOENT' || e.code === 'ENOTDIR')) return false; + throw e; } - } + return stat.isFile() || stat.isFIFO(); +}; - if (comp instanceof Comparator) { - if (comp.loose === !!options.loose) { - return comp +module.exports = function (x, options) { + if (typeof x !== 'string') { + throw new TypeError('Path must be a string.'); + } + var opts = normalizeOptions(x, options); + + var isFile = opts.isFile || defaultIsFile; + var readFileSync = opts.readFileSync || fs.readFileSync; + + var extensions = opts.extensions || ['.js']; + var basedir = opts.basedir || path.dirname(caller()); + var parent = opts.filename || basedir; + + opts.paths = opts.paths || []; + + // ensure that `basedir` is an absolute path at this point, resolving against the process' current working directory + var absoluteStart = path.resolve(basedir); + + if (opts.preserveSymlinks === false) { + try { + absoluteStart = fs.realpathSync(absoluteStart); + } catch (realPathErr) { + if (realPathErr.code !== 'ENOENT') { + throw realPathErr; + } + } + } + + if ((/^(?:\.\.?(?:\/|$)|\/|([A-Za-z]:)?[/\\])/).test(x)) { + var res = path.resolve(absoluteStart, x); + if (x === '..' || x.slice(-1) === '/') res += '/'; + var m = loadAsFileSync(res) || loadAsDirectorySync(res); + if (m) return m; } else { - comp = comp.value + var n = loadNodeModulesSync(x, absoluteStart); + if (n) return n; } - } - if (!(this instanceof Comparator)) { - return new Comparator(comp, options) - } + if (core[x]) return x; - debug('comparator', comp, options) - this.options = options - this.loose = !!options.loose - this.parse(comp) + var err = new Error("Cannot find module '" + x + "' from '" + parent + "'"); + err.code = 'MODULE_NOT_FOUND'; + throw err; - if (this.semver === ANY) { - this.value = '' - } else { - this.value = this.operator + this.semver.version - } + function loadAsFileSync(x) { + var pkg = loadpkg(path.dirname(x)); - debug('comp', this) -} + if (pkg && pkg.dir && pkg.pkg && opts.pathFilter) { + var rfile = path.relative(pkg.dir, x); + var r = opts.pathFilter(pkg.pkg, x, rfile); + if (r) { + x = path.resolve(pkg.dir, r); // eslint-disable-line no-param-reassign + } + } -var ANY = {} -Comparator.prototype.parse = function (comp) { - var r = this.options.loose ? re[COMPARATORLOOSE] : re[COMPARATOR] - var m = comp.match(r) + if (isFile(x)) { + return x; + } - if (!m) { - throw new TypeError('Invalid comparator: ' + comp) - } + for (var i = 0; i < extensions.length; i++) { + var file = x + extensions[i]; + if (isFile(file)) { + return file; + } + } + } - this.operator = m[1] - if (this.operator === '=') { - this.operator = '' - } + function loadpkg(dir) { + if (dir === '' || dir === '/') return; + if (process.platform === 'win32' && (/^\w:[/\\]*$/).test(dir)) { + return; + } + if ((/[/\\]node_modules[/\\]*$/).test(dir)) return; - // if it literally is just '>' or '' then allow anything. - if (!m[2]) { - this.semver = ANY - } else { - this.semver = new SemVer(m[2], this.options.loose) - } -} + var pkgfile = path.join(dir, 'package.json'); -Comparator.prototype.toString = function () { - return this.value -} + if (!isFile(pkgfile)) { + return loadpkg(path.dirname(dir)); + } -Comparator.prototype.test = function (version) { - debug('Comparator.test', version, this.options.loose) + var body = readFileSync(pkgfile); - if (this.semver === ANY) { - return true - } + try { + var pkg = JSON.parse(body); + } catch (jsonErr) {} - if (typeof version === 'string') { - version = new SemVer(version, this.options) - } + if (pkg && opts.packageFilter) { + pkg = opts.packageFilter(pkg, dir); + } - return cmp(version, this.operator, this.semver, this.options) -} + return { pkg: pkg, dir: dir }; + } -Comparator.prototype.intersects = function (comp, options) { - if (!(comp instanceof Comparator)) { - throw new TypeError('a Comparator is required') - } + function loadAsDirectorySync(x) { + var pkgfile = path.join(x, '/package.json'); + if (isFile(pkgfile)) { + try { + var body = readFileSync(pkgfile, 'UTF8'); + var pkg = JSON.parse(body); + } catch (e) {} - if (!options || typeof options !== 'object') { - options = { - loose: !!options, - includePrerelease: false + if (opts.packageFilter) { + pkg = opts.packageFilter(pkg, x); + } + + if (pkg.main) { + if (typeof pkg.main !== 'string') { + var mainError = new TypeError('package “' + pkg.name + '” `main` must be a string'); + mainError.code = 'INVALID_PACKAGE_MAIN'; + throw mainError; + } + if (pkg.main === '.' || pkg.main === './') { + pkg.main = 'index'; + } + try { + var m = loadAsFileSync(path.resolve(x, pkg.main)); + if (m) return m; + var n = loadAsDirectorySync(path.resolve(x, pkg.main)); + if (n) return n; + } catch (e) {} + } + } + + return loadAsFileSync(path.join(x, '/index')); } - } - var rangeTmp + function loadNodeModulesSync(x, start) { + var dirs = nodeModulesPaths(start, opts, x); + for (var i = 0; i < dirs.length; i++) { + var dir = dirs[i]; + var m = loadAsFileSync(path.join(dir, '/', x)); + if (m) return m; + var n = loadAsDirectorySync(path.join(dir, '/', x)); + if (n) return n; + } + } +}; - if (this.operator === '') { - rangeTmp = new Range(comp.value, options) - return satisfies(this.value, rangeTmp, options) - } else if (comp.operator === '') { - rangeTmp = new Range(this.value, options) - return satisfies(comp.semver, rangeTmp, options) - } - var sameDirectionIncreasing = - (this.operator === '>=' || this.operator === '>') && - (comp.operator === '>=' || comp.operator === '>') - var sameDirectionDecreasing = - (this.operator === '<=' || this.operator === '<') && - (comp.operator === '<=' || comp.operator === '<') - var sameSemVer = this.semver.version === comp.semver.version - var differentDirectionsInclusive = - (this.operator === '>=' || this.operator === '<=') && - (comp.operator === '>=' || comp.operator === '<=') - var oppositeDirectionsLessThan = - cmp(this.semver, '<', comp.semver, options) && - ((this.operator === '>=' || this.operator === '>') && - (comp.operator === '<=' || comp.operator === '<')) - var oppositeDirectionsGreaterThan = - cmp(this.semver, '>', comp.semver, options) && - ((this.operator === '<=' || this.operator === '<') && - (comp.operator === '>=' || comp.operator === '>')) +/***/ }), +/* 540 */ +/***/ (function(module, exports) { - return sameDirectionIncreasing || sameDirectionDecreasing || - (sameSemVer && differentDirectionsInclusive) || - oppositeDirectionsLessThan || oppositeDirectionsGreaterThan -} +module.exports = extractDescription -exports.Range = Range -function Range (range, options) { - if (!options || typeof options !== 'object') { - options = { - loose: !!options, - includePrerelease: false - } - } +// Extracts description from contents of a readme file in markdown format +function extractDescription (d) { + if (!d) return; + if (d === "ERROR: No README data found!") return; + // the first block of text before the first heading + // that isn't the first line heading + d = d.trim().split('\n') + for (var s = 0; d[s] && d[s].trim().match(/^(#|$)/); s ++); + var l = d.length + for (var e = s + 1; e < l && d[e].trim(); e ++); + return d.slice(s, e).join(' ').trim() +} - if (range instanceof Range) { - if (range.loose === !!options.loose && - range.includePrerelease === !!options.includePrerelease) { - return range - } else { - return new Range(range.raw, options) - } - } - if (range instanceof Comparator) { - return new Range(range.value, options) - } +/***/ }), +/* 541 */ +/***/ (function(module) { - if (!(this instanceof Range)) { - return new Range(range, options) - } +module.exports = JSON.parse("{\"topLevel\":{\"dependancies\":\"dependencies\",\"dependecies\":\"dependencies\",\"depdenencies\":\"dependencies\",\"devEependencies\":\"devDependencies\",\"depends\":\"dependencies\",\"dev-dependencies\":\"devDependencies\",\"devDependences\":\"devDependencies\",\"devDepenencies\":\"devDependencies\",\"devdependencies\":\"devDependencies\",\"repostitory\":\"repository\",\"repo\":\"repository\",\"prefereGlobal\":\"preferGlobal\",\"hompage\":\"homepage\",\"hampage\":\"homepage\",\"autohr\":\"author\",\"autor\":\"author\",\"contributers\":\"contributors\",\"publicationConfig\":\"publishConfig\",\"script\":\"scripts\"},\"bugs\":{\"web\":\"url\",\"name\":\"url\"},\"script\":{\"server\":\"start\",\"tests\":\"test\"}}"); - this.options = options - this.loose = !!options.loose - this.includePrerelease = !!options.includePrerelease +/***/ }), +/* 542 */ +/***/ (function(module, exports, __webpack_require__) { - // First, split based on boolean or || - this.raw = range - this.set = range.split(/\s*\|\|\s*/).map(function (range) { - return this.parseRange(range.trim()) - }, this).filter(function (c) { - // throw out any that are not relevant for whatever reason - return c.length - }) +var util = __webpack_require__(29) +var messages = __webpack_require__(543) - if (!this.set.length) { - throw new TypeError('Invalid SemVer Range: ' + range) +module.exports = function() { + var args = Array.prototype.slice.call(arguments, 0) + var warningName = args.shift() + if (warningName == "typo") { + return makeTypoWarning.apply(null,args) + } + else { + var msgTemplate = messages[warningName] ? messages[warningName] : warningName + ": '%s'" + args.unshift(msgTemplate) + return util.format.apply(null, args) } - - this.format() } -Range.prototype.format = function () { - this.range = this.set.map(function (comps) { - return comps.join(' ').trim() - }).join('||').trim() - return this.range +function makeTypoWarning (providedName, probableName, field) { + if (field) { + providedName = field + "['" + providedName + "']" + probableName = field + "['" + probableName + "']" + } + return util.format(messages.typo, providedName, probableName) } -Range.prototype.toString = function () { - return this.range -} -Range.prototype.parseRange = function (range) { - var loose = this.options.loose - range = range.trim() - // `1.2.3 - 1.2.4` => `>=1.2.3 <=1.2.4` - var hr = loose ? re[HYPHENRANGELOOSE] : re[HYPHENRANGE] - range = range.replace(hr, hyphenReplace) - debug('hyphen replace', range) - // `> 1.2.3 < 1.2.5` => `>1.2.3 <1.2.5` - range = range.replace(re[COMPARATORTRIM], comparatorTrimReplace) - debug('comparator trim', range, re[COMPARATORTRIM]) +/***/ }), +/* 543 */ +/***/ (function(module) { - // `~ 1.2.3` => `~1.2.3` - range = range.replace(re[TILDETRIM], tildeTrimReplace) +module.exports = JSON.parse("{\"repositories\":\"'repositories' (plural) Not supported. Please pick one as the 'repository' field\",\"missingRepository\":\"No repository field.\",\"brokenGitUrl\":\"Probably broken git url: %s\",\"nonObjectScripts\":\"scripts must be an object\",\"nonStringScript\":\"script values must be string commands\",\"nonArrayFiles\":\"Invalid 'files' member\",\"invalidFilename\":\"Invalid filename in 'files' list: %s\",\"nonArrayBundleDependencies\":\"Invalid 'bundleDependencies' list. Must be array of package names\",\"nonStringBundleDependency\":\"Invalid bundleDependencies member: %s\",\"nonDependencyBundleDependency\":\"Non-dependency in bundleDependencies: %s\",\"nonObjectDependencies\":\"%s field must be an object\",\"nonStringDependency\":\"Invalid dependency: %s %s\",\"deprecatedArrayDependencies\":\"specifying %s as array is deprecated\",\"deprecatedModules\":\"modules field is deprecated\",\"nonArrayKeywords\":\"keywords should be an array of strings\",\"nonStringKeyword\":\"keywords should be an array of strings\",\"conflictingName\":\"%s is also the name of a node core module.\",\"nonStringDescription\":\"'description' field should be a string\",\"missingDescription\":\"No description\",\"missingReadme\":\"No README data\",\"missingLicense\":\"No license field.\",\"nonEmailUrlBugsString\":\"Bug string field must be url, email, or {email,url}\",\"nonUrlBugsUrlField\":\"bugs.url field must be a string url. Deleted.\",\"nonEmailBugsEmailField\":\"bugs.email field must be a string email. Deleted.\",\"emptyNormalizedBugs\":\"Normalized value of bugs field is an empty object. Deleted.\",\"nonUrlHomepage\":\"homepage field must be a string url. Deleted.\",\"invalidLicense\":\"license should be a valid SPDX license expression\",\"typo\":\"%s should probably be %s.\"}"); - // `^ 1.2.3` => `^1.2.3` - range = range.replace(re[CARETTRIM], caretTrimReplace) +/***/ }), +/* 544 */ +/***/ (function(module, exports, __webpack_require__) { - // normalize spaces - range = range.split(/\s+/).join(' ') +"use strict"; - // At this point, the range is completely trimmed and - // ready to be split into comparators. +const path = __webpack_require__(16); +const writeJsonFile = __webpack_require__(545); +const sortKeys = __webpack_require__(557); - var compRe = loose ? re[COMPARATORLOOSE] : re[COMPARATOR] - var set = range.split(' ').map(function (comp) { - return parseComparator(comp, this.options) - }, this).join(' ').split(/\s+/) - if (this.options.loose) { - // in loose mode, throw out any that are not valid comparators - set = set.filter(function (comp) { - return !!comp.match(compRe) - }) - } - set = set.map(function (comp) { - return new Comparator(comp, this.options) - }, this) +const dependencyKeys = new Set([ + 'dependencies', + 'devDependencies', + 'optionalDependencies', + 'peerDependencies' +]); - return set -} +function normalize(packageJson) { + const result = {}; -Range.prototype.intersects = function (range, options) { - if (!(range instanceof Range)) { - throw new TypeError('a Range is required') - } + for (const key of Object.keys(packageJson)) { + if (!dependencyKeys.has(key)) { + result[key] = packageJson[key]; + } else if (Object.keys(packageJson[key]).length !== 0) { + result[key] = sortKeys(packageJson[key]); + } + } - return this.set.some(function (thisComparators) { - return thisComparators.every(function (thisComparator) { - return range.set.some(function (rangeComparators) { - return rangeComparators.every(function (rangeComparator) { - return thisComparator.intersects(rangeComparator, options) - }) - }) - }) - }) + return result; } -// Mostly just for testing and legacy API reasons -exports.toComparators = toComparators -function toComparators (range, options) { - return new Range(range, options).set.map(function (comp) { - return comp.map(function (c) { - return c.value - }).join(' ').trim().split(' ') - }) -} +module.exports = async (filePath, data, options) => { + if (typeof filePath !== 'string') { + options = data; + data = filePath; + filePath = '.'; + } -// comprised of xranges, tildes, stars, and gtlt's at this point. -// already replaced the hyphen ranges -// turn into a set of JUST comparators. -function parseComparator (comp, options) { - debug('comp', comp, options) - comp = replaceCarets(comp, options) - debug('caret', comp) - comp = replaceTildes(comp, options) - debug('tildes', comp) - comp = replaceXRanges(comp, options) - debug('xrange', comp) - comp = replaceStars(comp, options) - debug('stars', comp) - return comp -} + options = { + normalize: true, + ...options, + detectIndent: true + }; -function isX (id) { - return !id || id.toLowerCase() === 'x' || id === '*' -} + filePath = path.basename(filePath) === 'package.json' ? filePath : path.join(filePath, 'package.json'); -// ~, ~> --> * (any, kinda silly) -// ~2, ~2.x, ~2.x.x, ~>2, ~>2.x ~>2.x.x --> >=2.0.0 <3.0.0 -// ~2.0, ~2.0.x, ~>2.0, ~>2.0.x --> >=2.0.0 <2.1.0 -// ~1.2, ~1.2.x, ~>1.2, ~>1.2.x --> >=1.2.0 <1.3.0 -// ~1.2.3, ~>1.2.3 --> >=1.2.3 <1.3.0 -// ~1.2.0, ~>1.2.0 --> >=1.2.0 <1.3.0 -function replaceTildes (comp, options) { - return comp.trim().split(/\s+/).map(function (comp) { - return replaceTilde(comp, options) - }).join(' ') -} + data = options.normalize ? normalize(data) : data; -function replaceTilde (comp, options) { - var r = options.loose ? re[TILDELOOSE] : re[TILDE] - return comp.replace(r, function (_, M, m, p, pr) { - debug('tilde', comp, _, M, m, p, pr) - var ret + return writeJsonFile(filePath, data, options); +}; - if (isX(M)) { - ret = '' - } else if (isX(m)) { - ret = '>=' + M + '.0.0 <' + (+M + 1) + '.0.0' - } else if (isX(p)) { - // ~1.2 == >=1.2.0 <1.3.0 - ret = '>=' + M + '.' + m + '.0 <' + M + '.' + (+m + 1) + '.0' - } else if (pr) { - debug('replaceTilde pr', pr) - ret = '>=' + M + '.' + m + '.' + p + '-' + pr + - ' <' + M + '.' + (+m + 1) + '.0' - } else { - // ~1.2.3 == >=1.2.3 <1.3.0 - ret = '>=' + M + '.' + m + '.' + p + - ' <' + M + '.' + (+m + 1) + '.0' - } +module.exports.sync = (filePath, data, options) => { + if (typeof filePath !== 'string') { + options = data; + data = filePath; + filePath = '.'; + } - debug('tilde return', ret) - return ret - }) -} + options = { + normalize: true, + ...options, + detectIndent: true + }; -// ^ --> * (any, kinda silly) -// ^2, ^2.x, ^2.x.x --> >=2.0.0 <3.0.0 -// ^2.0, ^2.0.x --> >=2.0.0 <3.0.0 -// ^1.2, ^1.2.x --> >=1.2.0 <2.0.0 -// ^1.2.3 --> >=1.2.3 <2.0.0 -// ^1.2.0 --> >=1.2.0 <2.0.0 -function replaceCarets (comp, options) { - return comp.trim().split(/\s+/).map(function (comp) { - return replaceCaret(comp, options) - }).join(' ') -} + filePath = path.basename(filePath) === 'package.json' ? filePath : path.join(filePath, 'package.json'); -function replaceCaret (comp, options) { - debug('caret', comp, options) - var r = options.loose ? re[CARETLOOSE] : re[CARET] - return comp.replace(r, function (_, M, m, p, pr) { - debug('caret', comp, _, M, m, p, pr) - var ret + data = options.normalize ? normalize(data) : data; - if (isX(M)) { - ret = '' - } else if (isX(m)) { - ret = '>=' + M + '.0.0 <' + (+M + 1) + '.0.0' - } else if (isX(p)) { - if (M === '0') { - ret = '>=' + M + '.' + m + '.0 <' + M + '.' + (+m + 1) + '.0' - } else { - ret = '>=' + M + '.' + m + '.0 <' + (+M + 1) + '.0.0' - } - } else if (pr) { - debug('replaceCaret pr', pr) - if (M === '0') { - if (m === '0') { - ret = '>=' + M + '.' + m + '.' + p + '-' + pr + - ' <' + M + '.' + m + '.' + (+p + 1) - } else { - ret = '>=' + M + '.' + m + '.' + p + '-' + pr + - ' <' + M + '.' + (+m + 1) + '.0' - } - } else { - ret = '>=' + M + '.' + m + '.' + p + '-' + pr + - ' <' + (+M + 1) + '.0.0' - } - } else { - debug('no pr') - if (M === '0') { - if (m === '0') { - ret = '>=' + M + '.' + m + '.' + p + - ' <' + M + '.' + m + '.' + (+p + 1) - } else { - ret = '>=' + M + '.' + m + '.' + p + - ' <' + M + '.' + (+m + 1) + '.0' - } - } else { - ret = '>=' + M + '.' + m + '.' + p + - ' <' + (+M + 1) + '.0.0' - } - } + writeJsonFile.sync(filePath, data, options); +}; - debug('caret return', ret) - return ret - }) -} -function replaceXRanges (comp, options) { - debug('replaceXRanges', comp, options) - return comp.split(/\s+/).map(function (comp) { - return replaceXRange(comp, options) - }).join(' ') -} +/***/ }), +/* 545 */ +/***/ (function(module, exports, __webpack_require__) { -function replaceXRange (comp, options) { - comp = comp.trim() - var r = options.loose ? re[XRANGELOOSE] : re[XRANGE] - return comp.replace(r, function (ret, gtlt, M, m, p, pr) { - debug('xRange', comp, ret, gtlt, M, m, p, pr) - var xM = isX(M) - var xm = xM || isX(m) - var xp = xm || isX(p) - var anyX = xp +"use strict"; - if (gtlt === '=' && anyX) { - gtlt = '' - } +const path = __webpack_require__(16); +const fs = __webpack_require__(546); +const writeFileAtomic = __webpack_require__(550); +const sortKeys = __webpack_require__(557); +const makeDir = __webpack_require__(559); +const pify = __webpack_require__(562); +const detectIndent = __webpack_require__(563); - if (xM) { - if (gtlt === '>' || gtlt === '<') { - // nothing is allowed - ret = '<0.0.0' - } else { - // nothing is forbidden - ret = '*' - } - } else if (gtlt && anyX) { - // we know patch is an x, because we have any x at all. - // replace X with 0 - if (xm) { - m = 0 - } - p = 0 +const init = (fn, filePath, data, options) => { + if (!filePath) { + throw new TypeError('Expected a filepath'); + } - if (gtlt === '>') { - // >1 => >=2.0.0 - // >1.2 => >=1.3.0 - // >1.2.3 => >= 1.2.4 - gtlt = '>=' - if (xm) { - M = +M + 1 - m = 0 - p = 0 - } else { - m = +m + 1 - p = 0 - } - } else if (gtlt === '<=') { - // <=0.7.x is actually <0.8.0, since any 0.7.x should - // pass. Similarly, <=7.x is actually <8.0.0, etc. - gtlt = '<' - if (xm) { - M = +M + 1 - } else { - m = +m + 1 - } - } + if (data === undefined) { + throw new TypeError('Expected data to stringify'); + } - ret = gtlt + M + '.' + m + '.' + p - } else if (xm) { - ret = '>=' + M + '.0.0 <' + (+M + 1) + '.0.0' - } else if (xp) { - ret = '>=' + M + '.' + m + '.0 <' + M + '.' + (+m + 1) + '.0' - } + options = Object.assign({ + indent: '\t', + sortKeys: false + }, options); - debug('xRange return', ret) + if (options.sortKeys) { + data = sortKeys(data, { + deep: true, + compare: typeof options.sortKeys === 'function' ? options.sortKeys : undefined + }); + } - return ret - }) -} + return fn(filePath, data, options); +}; -// Because * is AND-ed with everything else in the comparator, -// and '' means "any version", just remove the *s entirely. -function replaceStars (comp, options) { - debug('replaceStars', comp, options) - // Looseness is ignored here. star is always as loose as it gets! - return comp.trim().replace(re[STAR], '') -} +const readFile = filePath => pify(fs.readFile)(filePath, 'utf8').catch(() => {}); -// This function is passed to string.replace(re[HYPHENRANGE]) -// M, m, patch, prerelease, build -// 1.2 - 3.4.5 => >=1.2.0 <=3.4.5 -// 1.2.3 - 3.4 => >=1.2.0 <3.5.0 Any 3.4.x will do -// 1.2 - 3.4 => >=1.2.0 <3.5.0 -function hyphenReplace ($0, - from, fM, fm, fp, fpr, fb, - to, tM, tm, tp, tpr, tb) { - if (isX(fM)) { - from = '' - } else if (isX(fm)) { - from = '>=' + fM + '.0.0' - } else if (isX(fp)) { - from = '>=' + fM + '.' + fm + '.0' - } else { - from = '>=' + from - } +const main = (filePath, data, options) => { + return (options.detectIndent ? readFile(filePath) : Promise.resolve()) + .then(string => { + const indent = string ? detectIndent(string).indent : options.indent; + const json = JSON.stringify(data, options.replacer, indent); - if (isX(tM)) { - to = '' - } else if (isX(tm)) { - to = '<' + (+tM + 1) + '.0.0' - } else if (isX(tp)) { - to = '<' + tM + '.' + (+tm + 1) + '.0' - } else if (tpr) { - to = '<=' + tM + '.' + tm + '.' + tp + '-' + tpr - } else { - to = '<=' + to - } + return pify(writeFileAtomic)(filePath, `${json}\n`, {mode: options.mode}); + }); +}; - return (from + ' ' + to).trim() -} +const mainSync = (filePath, data, options) => { + let {indent} = options; -// if ANY of the sets match ALL of its comparators, then pass -Range.prototype.test = function (version) { - if (!version) { - return false - } + if (options.detectIndent) { + try { + const file = fs.readFileSync(filePath, 'utf8'); + indent = detectIndent(file).indent; + } catch (error) { + if (error.code !== 'ENOENT') { + throw error; + } + } + } - if (typeof version === 'string') { - version = new SemVer(version, this.options) - } + const json = JSON.stringify(data, options.replacer, indent); - for (var i = 0; i < this.set.length; i++) { - if (testSet(this.set[i], version, this.options)) { - return true - } - } - return false -} + return writeFileAtomic.sync(filePath, `${json}\n`, {mode: options.mode}); +}; -function testSet (set, version, options) { - for (var i = 0; i < set.length; i++) { - if (!set[i].test(version)) { - return false - } - } +const writeJsonFile = (filePath, data, options) => { + return makeDir(path.dirname(filePath), {fs}) + .then(() => init(main, filePath, data, options)); +}; - if (version.prerelease.length && !options.includePrerelease) { - // Find the set of versions that are allowed to have prereleases - // For example, ^1.2.3-pr.1 desugars to >=1.2.3-pr.1 <2.0.0 - // That should allow `1.2.3-pr.2` to pass. - // However, `1.2.4-alpha.notready` should NOT be allowed, - // even though it's within the range set by the comparators. - for (i = 0; i < set.length; i++) { - debug(set[i].semver) - if (set[i].semver === ANY) { - continue - } +module.exports = writeJsonFile; +// TODO: Remove this for the next major release +module.exports.default = writeJsonFile; +module.exports.sync = (filePath, data, options) => { + makeDir.sync(path.dirname(filePath), {fs}); + init(mainSync, filePath, data, options); +}; + + +/***/ }), +/* 546 */ +/***/ (function(module, exports, __webpack_require__) { + +var fs = __webpack_require__(23) +var polyfills = __webpack_require__(547) +var legacy = __webpack_require__(548) +var clone = __webpack_require__(549) - if (set[i].semver.prerelease.length > 0) { - var allowed = set[i].semver - if (allowed.major === version.major && - allowed.minor === version.minor && - allowed.patch === version.patch) { - return true - } - } - } +var queue = [] - // Version has a -pre, but it's not one of the ones we like. - return false - } +var util = __webpack_require__(29) - return true -} +function noop () {} -exports.satisfies = satisfies -function satisfies (version, range, options) { - try { - range = new Range(range, options) - } catch (er) { - return false +var debug = noop +if (util.debuglog) + debug = util.debuglog('gfs4') +else if (/\bgfs4\b/i.test(process.env.NODE_DEBUG || '')) + debug = function() { + var m = util.format.apply(util, arguments) + m = 'GFS4: ' + m.split(/\n/).join('\nGFS4: ') + console.error(m) } - return range.test(version) -} -exports.maxSatisfying = maxSatisfying -function maxSatisfying (versions, range, options) { - var max = null - var maxSV = null - try { - var rangeObj = new Range(range, options) - } catch (er) { - return null - } - versions.forEach(function (v) { - if (rangeObj.test(v)) { - // satisfies(v, range, options) - if (!max || maxSV.compare(v) === -1) { - // compare(max, v, true) - max = v - maxSV = new SemVer(max, options) - } - } +if (/\bgfs4\b/i.test(process.env.NODE_DEBUG || '')) { + process.on('exit', function() { + debug(queue) + __webpack_require__(30).equal(queue.length, 0) }) - return max } -exports.minSatisfying = minSatisfying -function minSatisfying (versions, range, options) { - var min = null - var minSV = null - try { - var rangeObj = new Range(range, options) - } catch (er) { - return null - } - versions.forEach(function (v) { - if (rangeObj.test(v)) { - // satisfies(v, range, options) - if (!min || minSV.compare(v) === 1) { - // compare(min, v, true) - min = v - minSV = new SemVer(min, options) - } - } - }) - return min +module.exports = patch(clone(fs)) +if (process.env.TEST_GRACEFUL_FS_GLOBAL_PATCH && !fs.__patched) { + module.exports = patch(fs) + fs.__patched = true; } -exports.minVersion = minVersion -function minVersion (range, loose) { - range = new Range(range, loose) +// Always patch fs.close/closeSync, because we want to +// retry() whenever a close happens *anywhere* in the program. +// This is essential when multiple graceful-fs instances are +// in play at the same time. +module.exports.close = (function (fs$close) { return function (fd, cb) { + return fs$close.call(fs, fd, function (err) { + if (!err) + retry() - var minver = new SemVer('0.0.0') - if (range.test(minver)) { - return minver - } + if (typeof cb === 'function') + cb.apply(this, arguments) + }) +}})(fs.close) - minver = new SemVer('0.0.0-0') - if (range.test(minver)) { - return minver - } +module.exports.closeSync = (function (fs$closeSync) { return function (fd) { + // Note that graceful-fs also retries when fs.closeSync() fails. + // Looks like a bug to me, although it's probably a harmless one. + var rval = fs$closeSync.apply(fs, arguments) + retry() + return rval +}})(fs.closeSync) - minver = null - for (var i = 0; i < range.set.length; ++i) { - var comparators = range.set[i] +// Only patch fs once, otherwise we'll run into a memory leak if +// graceful-fs is loaded multiple times, such as in test environments that +// reset the loaded modules between tests. +// We look for the string `graceful-fs` from the comment above. This +// way we are not adding any extra properties and it will detect if older +// versions of graceful-fs are installed. +if (!/\bgraceful-fs\b/.test(fs.closeSync.toString())) { + fs.closeSync = module.exports.closeSync; + fs.close = module.exports.close; +} - comparators.forEach(function (comparator) { - // Clone to avoid manipulating the comparator's semver object. - var compver = new SemVer(comparator.semver.version) - switch (comparator.operator) { - case '>': - if (compver.prerelease.length === 0) { - compver.patch++ - } else { - compver.prerelease.push(0) - } - compver.raw = compver.format() - /* fallthrough */ - case '': - case '>=': - if (!minver || gt(minver, compver)) { - minver = compver - } - break - case '<': - case '<=': - /* Ignore maximum versions */ - break - /* istanbul ignore next */ - default: - throw new Error('Unexpected operation: ' + comparator.operator) - } - }) - } +function patch (fs) { + // Everything that references the open() function needs to be in here + polyfills(fs) + fs.gracefulify = patch + fs.FileReadStream = ReadStream; // Legacy name. + fs.FileWriteStream = WriteStream; // Legacy name. + fs.createReadStream = createReadStream + fs.createWriteStream = createWriteStream + var fs$readFile = fs.readFile + fs.readFile = readFile + function readFile (path, options, cb) { + if (typeof options === 'function') + cb = options, options = null - if (minver && range.test(minver)) { - return minver + return go$readFile(path, options, cb) + + function go$readFile (path, options, cb) { + return fs$readFile(path, options, function (err) { + if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) + enqueue([go$readFile, [path, options, cb]]) + else { + if (typeof cb === 'function') + cb.apply(this, arguments) + retry() + } + }) + } } - return null -} + var fs$writeFile = fs.writeFile + fs.writeFile = writeFile + function writeFile (path, data, options, cb) { + if (typeof options === 'function') + cb = options, options = null -exports.validRange = validRange -function validRange (range, options) { - try { - // Return '*' instead of '' so that truthiness works. - // This will throw if it's invalid anyway - return new Range(range, options).range || '*' - } catch (er) { - return null - } -} + return go$writeFile(path, data, options, cb) -// Determine if version is less than all the versions possible in the range -exports.ltr = ltr -function ltr (version, range, options) { - return outside(version, range, '<', options) -} + function go$writeFile (path, data, options, cb) { + return fs$writeFile(path, data, options, function (err) { + if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) + enqueue([go$writeFile, [path, data, options, cb]]) + else { + if (typeof cb === 'function') + cb.apply(this, arguments) + retry() + } + }) + } + } -// Determine if version is greater than all the versions possible in the range. -exports.gtr = gtr -function gtr (version, range, options) { - return outside(version, range, '>', options) -} + var fs$appendFile = fs.appendFile + if (fs$appendFile) + fs.appendFile = appendFile + function appendFile (path, data, options, cb) { + if (typeof options === 'function') + cb = options, options = null -exports.outside = outside -function outside (version, range, hilo, options) { - version = new SemVer(version, options) - range = new Range(range, options) + return go$appendFile(path, data, options, cb) - var gtfn, ltefn, ltfn, comp, ecomp - switch (hilo) { - case '>': - gtfn = gt - ltefn = lte - ltfn = lt - comp = '>' - ecomp = '>=' - break - case '<': - gtfn = lt - ltefn = gte - ltfn = gt - comp = '<' - ecomp = '<=' - break - default: - throw new TypeError('Must provide a hilo val of "<" or ">"') + function go$appendFile (path, data, options, cb) { + return fs$appendFile(path, data, options, function (err) { + if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) + enqueue([go$appendFile, [path, data, options, cb]]) + else { + if (typeof cb === 'function') + cb.apply(this, arguments) + retry() + } + }) + } } - // If it satisifes the range it is not outside - if (satisfies(version, range, options)) { - return false - } + var fs$readdir = fs.readdir + fs.readdir = readdir + function readdir (path, options, cb) { + var args = [path] + if (typeof options !== 'function') { + args.push(options) + } else { + cb = options + } + args.push(go$readdir$cb) - // From now on, variable terms are as if we're in "gtr" mode. - // but note that everything is flipped for the "ltr" function. + return go$readdir(args) - for (var i = 0; i < range.set.length; ++i) { - var comparators = range.set[i] + function go$readdir$cb (err, files) { + if (files && files.sort) + files.sort() - var high = null - var low = null + if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) + enqueue([go$readdir, [args]]) - comparators.forEach(function (comparator) { - if (comparator.semver === ANY) { - comparator = new Comparator('>=0.0.0') - } - high = high || comparator - low = low || comparator - if (gtfn(comparator.semver, high.semver, options)) { - high = comparator - } else if (ltfn(comparator.semver, low.semver, options)) { - low = comparator + else { + if (typeof cb === 'function') + cb.apply(this, arguments) + retry() } - }) - - // If the edge version comparator has a operator then our version - // isn't outside it - if (high.operator === comp || high.operator === ecomp) { - return false - } - - // If the lowest version comparator has an operator and our version - // is less than it then it isn't higher than the range - if ((!low.operator || low.operator === comp) && - ltefn(version, low.semver)) { - return false - } else if (low.operator === ecomp && ltfn(version, low.semver)) { - return false } } - return true -} - -exports.prerelease = prerelease -function prerelease (version, options) { - var parsed = parse(version, options) - return (parsed && parsed.prerelease.length) ? parsed.prerelease : null -} - -exports.intersects = intersects -function intersects (r1, r2, options) { - r1 = new Range(r1, options) - r2 = new Range(r2, options) - return r1.intersects(r2) -} -exports.coerce = coerce -function coerce (version) { - if (version instanceof SemVer) { - return version + function go$readdir (args) { + return fs$readdir.apply(fs, args) } - if (typeof version !== 'string') { - return null + if (process.version.substr(0, 4) === 'v0.8') { + var legStreams = legacy(fs) + ReadStream = legStreams.ReadStream + WriteStream = legStreams.WriteStream } - var match = version.match(re[COERCE]) - - if (match == null) { - return null + var fs$ReadStream = fs.ReadStream + if (fs$ReadStream) { + ReadStream.prototype = Object.create(fs$ReadStream.prototype) + ReadStream.prototype.open = ReadStream$open } - return parse(match[1] + - '.' + (match[2] || '0') + - '.' + (match[3] || '0')) -} - - -/***/ }), -/* 118 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; + var fs$WriteStream = fs.WriteStream + if (fs$WriteStream) { + WriteStream.prototype = Object.create(fs$WriteStream.prototype) + WriteStream.prototype.open = WriteStream$open + } + fs.ReadStream = ReadStream + fs.WriteStream = WriteStream -const processFn = (fn, options) => function (...args) { - const P = options.promiseModule; + function ReadStream (path, options) { + if (this instanceof ReadStream) + return fs$ReadStream.apply(this, arguments), this + else + return ReadStream.apply(Object.create(ReadStream.prototype), arguments) + } - return new P((resolve, reject) => { - if (options.multiArgs) { - args.push((...result) => { - if (options.errorFirst) { - if (result[0]) { - reject(result); - } else { - result.shift(); - resolve(result); - } - } else { - resolve(result); - } - }); - } else if (options.errorFirst) { - args.push((error, result) => { - if (error) { - reject(error); - } else { - resolve(result); - } - }); - } else { - args.push(resolve); - } + function ReadStream$open () { + var that = this + open(that.path, that.flags, that.mode, function (err, fd) { + if (err) { + if (that.autoClose) + that.destroy() - fn.apply(this, args); - }); -}; + that.emit('error', err) + } else { + that.fd = fd + that.emit('open', fd) + that.read() + } + }) + } -module.exports = (input, options) => { - options = Object.assign({ - exclude: [/.+(Sync|Stream)$/], - errorFirst: true, - promiseModule: Promise - }, options); + function WriteStream (path, options) { + if (this instanceof WriteStream) + return fs$WriteStream.apply(this, arguments), this + else + return WriteStream.apply(Object.create(WriteStream.prototype), arguments) + } - const objType = typeof input; - if (!(input !== null && (objType === 'object' || objType === 'function'))) { - throw new TypeError(`Expected \`input\` to be a \`Function\` or \`Object\`, got \`${input === null ? 'null' : objType}\``); - } + function WriteStream$open () { + var that = this + open(that.path, that.flags, that.mode, function (err, fd) { + if (err) { + that.destroy() + that.emit('error', err) + } else { + that.fd = fd + that.emit('open', fd) + } + }) + } - const filter = key => { - const match = pattern => typeof pattern === 'string' ? key === pattern : pattern.test(key); - return options.include ? options.include.some(match) : !options.exclude.some(match); - }; + function createReadStream (path, options) { + return new ReadStream(path, options) + } - let ret; - if (objType === 'function') { - ret = function (...args) { - return options.excludeMain ? input(...args) : processFn(input, options).apply(this, args); - }; - } else { - ret = Object.create(Object.getPrototypeOf(input)); - } + function createWriteStream (path, options) { + return new WriteStream(path, options) + } - for (const key in input) { // eslint-disable-line guard-for-in - const property = input[key]; - ret[key] = typeof property === 'function' && filter(key) ? processFn(property, options) : property; - } + var fs$open = fs.open + fs.open = open + function open (path, flags, mode, cb) { + if (typeof mode === 'function') + cb = mode, mode = null - return ret; -}; + return go$open(path, flags, mode, cb) + function go$open (path, flags, mode, cb) { + return fs$open(path, flags, mode, function (err, fd) { + if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) + enqueue([go$open, [path, flags, mode, cb]]) + else { + if (typeof cb === 'function') + cb.apply(this, arguments) + retry() + } + }) + } + } -/***/ }), -/* 119 */ -/***/ (function(module, exports, __webpack_require__) { + return fs +} -"use strict"; +function enqueue (elem) { + debug('ENQUEUE', elem[0].name, elem[1]) + queue.push(elem) +} +function retry () { + var elem = queue.shift() + if (elem) { + debug('RETRY', elem[0].name, elem[1]) + elem[0].apply(null, elem[1]) + } +} -// detect either spaces or tabs but not both to properly handle tabs -// for indentation and spaces for alignment -const INDENT_RE = /^(?:( )+|\t+)/; -function getMostUsed(indents) { - let result = 0; - let maxUsed = 0; - let maxWeight = 0; +/***/ }), +/* 547 */ +/***/ (function(module, exports, __webpack_require__) { - for (const entry of indents) { - // TODO: use destructuring when targeting Node.js 6 - const key = entry[0]; - const val = entry[1]; +var constants = __webpack_require__(25) - const u = val[0]; - const w = val[1]; +var origCwd = process.cwd +var cwd = null - if (u > maxUsed || (u === maxUsed && w > maxWeight)) { - maxUsed = u; - maxWeight = w; - result = Number(key); - } - } +var platform = process.env.GRACEFUL_FS_PLATFORM || process.platform - return result; +process.cwd = function() { + if (!cwd) + cwd = origCwd.call(process) + return cwd } +try { + process.cwd() +} catch (er) {} -module.exports = str => { - if (typeof str !== 'string') { - throw new TypeError('Expected a string'); - } - - // used to see if tabs or spaces are the most used - let tabs = 0; - let spaces = 0; +var chdir = process.chdir +process.chdir = function(d) { + cwd = null + chdir.call(process, d) +} - // remember the size of previous line's indentation - let prev = 0; +module.exports = patch - // remember how many indents/unindents as occurred for a given size - // and how much lines follow a given indentation - // - // indents = { - // 3: [1, 0], - // 4: [1, 5], - // 5: [1, 0], - // 12: [1, 0], - // } - const indents = new Map(); +function patch (fs) { + // (re-)implement some things that are known busted or missing. - // pointer to the array of last used indent - let current; + // lchmod, broken prior to 0.6.2 + // back-port the fix here. + if (constants.hasOwnProperty('O_SYMLINK') && + process.version.match(/^v0\.6\.[0-2]|^v0\.5\./)) { + patchLchmod(fs) + } - // whether the last action was an indent (opposed to an unindent) - let isIndent; + // lutimes implementation, or no-op + if (!fs.lutimes) { + patchLutimes(fs) + } - for (const line of str.split(/\n/g)) { - if (!line) { - // ignore empty lines - continue; - } + // https://github.com/isaacs/node-graceful-fs/issues/4 + // Chown should not fail on einval or eperm if non-root. + // It should not fail on enosys ever, as this just indicates + // that a fs doesn't support the intended operation. - let indent; - const matches = line.match(INDENT_RE); + fs.chown = chownFix(fs.chown) + fs.fchown = chownFix(fs.fchown) + fs.lchown = chownFix(fs.lchown) - if (matches) { - indent = matches[0].length; + fs.chmod = chmodFix(fs.chmod) + fs.fchmod = chmodFix(fs.fchmod) + fs.lchmod = chmodFix(fs.lchmod) - if (matches[1]) { - spaces++; - } else { - tabs++; - } - } else { - indent = 0; - } + fs.chownSync = chownFixSync(fs.chownSync) + fs.fchownSync = chownFixSync(fs.fchownSync) + fs.lchownSync = chownFixSync(fs.lchownSync) - const diff = indent - prev; - prev = indent; + fs.chmodSync = chmodFixSync(fs.chmodSync) + fs.fchmodSync = chmodFixSync(fs.fchmodSync) + fs.lchmodSync = chmodFixSync(fs.lchmodSync) - if (diff) { - // an indent or unindent has been detected + fs.stat = statFix(fs.stat) + fs.fstat = statFix(fs.fstat) + fs.lstat = statFix(fs.lstat) - isIndent = diff > 0; + fs.statSync = statFixSync(fs.statSync) + fs.fstatSync = statFixSync(fs.fstatSync) + fs.lstatSync = statFixSync(fs.lstatSync) - current = indents.get(isIndent ? diff : -diff); + // if lchmod/lchown do not exist, then make them no-ops + if (!fs.lchmod) { + fs.lchmod = function (path, mode, cb) { + if (cb) process.nextTick(cb) + } + fs.lchmodSync = function () {} + } + if (!fs.lchown) { + fs.lchown = function (path, uid, gid, cb) { + if (cb) process.nextTick(cb) + } + fs.lchownSync = function () {} + } - if (current) { - current[0]++; - } else { - current = [1, 0]; - indents.set(diff, current); - } - } else if (current) { - // if the last action was an indent, increment the weight - current[1] += Number(isIndent); - } - } + // on Windows, A/V software can lock the directory, causing this + // to fail with an EACCES or EPERM if the directory contains newly + // created files. Try again on failure, for up to 60 seconds. - const amount = getMostUsed(indents); + // Set the timeout this long because some Windows Anti-Virus, such as Parity + // bit9, may lock files for up to a minute, causing npm package install + // failures. Also, take care to yield the scheduler. Windows scheduling gives + // CPU to a busy looping process, which can cause the program causing the lock + // contention to be starved of CPU by node, so the contention doesn't resolve. + if (platform === "win32") { + fs.rename = (function (fs$rename) { return function (from, to, cb) { + var start = Date.now() + var backoff = 0; + fs$rename(from, to, function CB (er) { + if (er + && (er.code === "EACCES" || er.code === "EPERM") + && Date.now() - start < 60000) { + setTimeout(function() { + fs.stat(to, function (stater, st) { + if (stater && stater.code === "ENOENT") + fs$rename(from, to, CB); + else + cb(er) + }) + }, backoff) + if (backoff < 100) + backoff += 10; + return; + } + if (cb) cb(er) + }) + }})(fs.rename) + } - let type; - let indent; - if (!amount) { - type = null; - indent = ''; - } else if (spaces >= tabs) { - type = 'space'; - indent = ' '.repeat(amount); - } else { - type = 'tab'; - indent = '\t'.repeat(amount); - } + // if read() returns EAGAIN, then just try it again. + fs.read = (function (fs$read) { return function (fd, buffer, offset, length, position, callback_) { + var callback + if (callback_ && typeof callback_ === 'function') { + var eagCounter = 0 + callback = function (er, _, __) { + if (er && er.code === 'EAGAIN' && eagCounter < 10) { + eagCounter ++ + return fs$read.call(fs, fd, buffer, offset, length, position, callback) + } + callback_.apply(this, arguments) + } + } + return fs$read.call(fs, fd, buffer, offset, length, position, callback) + }})(fs.read) - return { - amount, - type, - indent - }; -}; + fs.readSync = (function (fs$readSync) { return function (fd, buffer, offset, length, position) { + var eagCounter = 0 + while (true) { + try { + return fs$readSync.call(fs, fd, buffer, offset, length, position) + } catch (er) { + if (er.code === 'EAGAIN' && eagCounter < 10) { + eagCounter ++ + continue + } + throw er + } + } + }})(fs.readSync) + function patchLchmod (fs) { + fs.lchmod = function (path, mode, callback) { + fs.open( path + , constants.O_WRONLY | constants.O_SYMLINK + , mode + , function (err, fd) { + if (err) { + if (callback) callback(err) + return + } + // prefer to return the chmod error, if one occurs, + // but still try to close, and report closing errors if they occur. + fs.fchmod(fd, mode, function (err) { + fs.close(fd, function(err2) { + if (callback) callback(err || err2) + }) + }) + }) + } -/***/ }), -/* 120 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + fs.lchmodSync = function (path, mode) { + var fd = fs.openSync(path, constants.O_WRONLY | constants.O_SYMLINK, mode) -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "installInDir", function() { return installInDir; }); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "runScriptInPackage", function() { return runScriptInPackage; }); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "runScriptInPackageStreaming", function() { return runScriptInPackageStreaming; }); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "yarnWorkspacesInfo", function() { return yarnWorkspacesInfo; }); -/* harmony import */ var _child_process__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(121); -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ + // prefer to return the chmod error, if one occurs, + // but still try to close, and report closing errors if they occur. + var threw = true + var ret + try { + ret = fs.fchmodSync(fd, mode) + threw = false + } finally { + if (threw) { + try { + fs.closeSync(fd) + } catch (er) {} + } else { + fs.closeSync(fd) + } + } + return ret + } + } + function patchLutimes (fs) { + if (constants.hasOwnProperty("O_SYMLINK")) { + fs.lutimes = function (path, at, mt, cb) { + fs.open(path, constants.O_SYMLINK, function (er, fd) { + if (er) { + if (cb) cb(er) + return + } + fs.futimes(fd, at, mt, function (er) { + fs.close(fd, function (er2) { + if (cb) cb(er || er2) + }) + }) + }) + } -/** - * Install all dependencies in the given directory - */ -async function installInDir(directory, extraArgs = []) { - const options = ['install', '--non-interactive', ...extraArgs]; // We pass the mutex flag to ensure only one instance of yarn runs at any - // given time (e.g. to avoid conflicts). + fs.lutimesSync = function (path, at, mt) { + var fd = fs.openSync(path, constants.O_SYMLINK) + var ret + var threw = true + try { + ret = fs.futimesSync(fd, at, mt) + threw = false + } finally { + if (threw) { + try { + fs.closeSync(fd) + } catch (er) {} + } else { + fs.closeSync(fd) + } + } + return ret + } - await Object(_child_process__WEBPACK_IMPORTED_MODULE_0__["spawn"])('yarn', options, { - cwd: directory - }); -} -/** - * Run script in the given directory - */ + } else { + fs.lutimes = function (_a, _b, _c, cb) { if (cb) process.nextTick(cb) } + fs.lutimesSync = function () {} + } + } -async function runScriptInPackage(script, args, pkg) { - const execOpts = { - cwd: pkg.path - }; - await Object(_child_process__WEBPACK_IMPORTED_MODULE_0__["spawn"])('yarn', ['run', script, ...args], execOpts); -} -/** - * Run script in the given directory - */ + function chmodFix (orig) { + if (!orig) return orig + return function (target, mode, cb) { + return orig.call(fs, target, mode, function (er) { + if (chownErOk(er)) er = null + if (cb) cb.apply(this, arguments) + }) + } + } -function runScriptInPackageStreaming(script, args, pkg) { - const execOpts = { - cwd: pkg.path - }; - return Object(_child_process__WEBPACK_IMPORTED_MODULE_0__["spawnStreaming"])('yarn', ['run', script, ...args], execOpts, { - prefix: pkg.name - }); -} -async function yarnWorkspacesInfo(directory) { - const workspacesInfo = await Object(_child_process__WEBPACK_IMPORTED_MODULE_0__["spawn"])('yarn', ['workspaces', 'info', '--json'], { - cwd: directory, - stdio: 'pipe' - }); - const stdout = JSON.parse(workspacesInfo.stdout); - return JSON.parse(stdout.data); -} + function chmodFixSync (orig) { + if (!orig) return orig + return function (target, mode) { + try { + return orig.call(fs, target, mode) + } catch (er) { + if (!chownErOk(er)) throw er + } + } + } -/***/ }), -/* 121 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "spawn", function() { return spawn; }); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "spawnStreaming", function() { return spawnStreaming; }); -/* harmony import */ var chalk__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2); -/* harmony import */ var chalk__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(chalk__WEBPACK_IMPORTED_MODULE_0__); -/* harmony import */ var execa__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(122); -/* harmony import */ var execa__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(execa__WEBPACK_IMPORTED_MODULE_1__); -/* harmony import */ var log_symbols__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(158); -/* harmony import */ var log_symbols__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(log_symbols__WEBPACK_IMPORTED_MODULE_2__); -/* harmony import */ var strong_log_transformer__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(163); -/* harmony import */ var strong_log_transformer__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(strong_log_transformer__WEBPACK_IMPORTED_MODULE_3__); -function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } + function chownFix (orig) { + if (!orig) return orig + return function (target, uid, gid, cb) { + return orig.call(fs, target, uid, gid, function (er) { + if (chownErOk(er)) er = null + if (cb) cb.apply(this, arguments) + }) + } + } -function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(source, true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(source).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } + function chownFixSync (orig) { + if (!orig) return orig + return function (target, uid, gid) { + try { + return orig.call(fs, target, uid, gid) + } catch (er) { + if (!chownErOk(er)) throw er + } + } + } -function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ + function statFix (orig) { + if (!orig) return orig + // Older versions of Node erroneously returned signed integers for + // uid + gid. + return function (target, cb) { + return orig.call(fs, target, function (er, stats) { + if (!stats) return cb.apply(this, arguments) + if (stats.uid < 0) stats.uid += 0x100000000 + if (stats.gid < 0) stats.gid += 0x100000000 + if (cb) cb.apply(this, arguments) + }) + } + } + function statFixSync (orig) { + if (!orig) return orig + // Older versions of Node erroneously returned signed integers for + // uid + gid. + return function (target) { + var stats = orig.call(fs, target) + if (stats.uid < 0) stats.uid += 0x100000000 + if (stats.gid < 0) stats.gid += 0x100000000 + return stats; + } + } + // ENOSYS means that the fs doesn't support the op. Just ignore + // that, because it doesn't matter. + // + // if there's no getuid, or if getuid() is something other + // than 0, and the error is EINVAL or EPERM, then just ignore + // it. + // + // This specific case is a silent failure in cp, install, tar, + // and most other unix tools that manage permissions. + // + // When running as root, or if other types of errors are + // encountered, then it's strict. + function chownErOk (er) { + if (!er) + return true + if (er.code === "ENOSYS") + return true + var nonroot = !process.getuid || process.getuid() !== 0 + if (nonroot) { + if (er.code === "EINVAL" || er.code === "EPERM") + return true + } -function generateColors() { - const colorWheel = [chalk__WEBPACK_IMPORTED_MODULE_0___default.a.cyan, chalk__WEBPACK_IMPORTED_MODULE_0___default.a.magenta, chalk__WEBPACK_IMPORTED_MODULE_0___default.a.blue, chalk__WEBPACK_IMPORTED_MODULE_0___default.a.yellow, chalk__WEBPACK_IMPORTED_MODULE_0___default.a.green]; - const count = colorWheel.length; - let children = 0; - return () => colorWheel[children++ % count]; + return false + } } -function spawn(command, args, opts) { - return execa__WEBPACK_IMPORTED_MODULE_1___default()(command, args, _objectSpread({ - stdio: 'inherit', - preferLocal: true - }, opts)); -} -const nextColor = generateColors(); -function spawnStreaming(command, args, opts, { - prefix -}) { - const spawned = execa__WEBPACK_IMPORTED_MODULE_1___default()(command, args, _objectSpread({ - stdio: ['ignore', 'pipe', 'pipe'], - preferLocal: true - }, opts)); - const color = nextColor(); - const prefixedStdout = strong_log_transformer__WEBPACK_IMPORTED_MODULE_3___default()({ - tag: `${color.bold(prefix)}:` - }); - const prefixedStderr = strong_log_transformer__WEBPACK_IMPORTED_MODULE_3___default()({ - mergeMultiline: true, - tag: `${log_symbols__WEBPACK_IMPORTED_MODULE_2___default.a.error} ${color.bold(prefix)}:` - }); - spawned.stdout.pipe(prefixedStdout).pipe(process.stdout); - spawned.stderr.pipe(prefixedStderr).pipe(process.stderr); - return spawned; -} /***/ }), -/* 122 */ +/* 548 */ /***/ (function(module, exports, __webpack_require__) { -"use strict"; +var Stream = __webpack_require__(27).Stream -const path = __webpack_require__(16); -const childProcess = __webpack_require__(123); -const crossSpawn = __webpack_require__(124); -const stripFinalNewline = __webpack_require__(137); -const npmRunPath = __webpack_require__(138); -const onetime = __webpack_require__(139); -const makeError = __webpack_require__(141); -const normalizeStdio = __webpack_require__(146); -const {spawnedKill, spawnedCancel, setupTimeout, setExitHandler} = __webpack_require__(147); -const {handleInput, getSpawnedResult, makeAllStream, validateInputSync} = __webpack_require__(149); -const {mergePromise, getSpawnedPromise} = __webpack_require__(156); -const {joinCommand, parseCommand} = __webpack_require__(157); +module.exports = legacy -const DEFAULT_MAX_BUFFER = 1000 * 1000 * 100; +function legacy (fs) { + return { + ReadStream: ReadStream, + WriteStream: WriteStream + } -const getEnv = ({env: envOption, extendEnv, preferLocal, localDir, execPath}) => { - const env = extendEnv ? {...process.env, ...envOption} : envOption; + function ReadStream (path, options) { + if (!(this instanceof ReadStream)) return new ReadStream(path, options); - if (preferLocal) { - return npmRunPath.env({env, cwd: localDir, execPath}); - } + Stream.call(this); - return env; -}; + var self = this; -const handleArgs = (file, args, options = {}) => { - const parsed = crossSpawn._parse(file, args, options); - file = parsed.command; - args = parsed.args; - options = parsed.options; + this.path = path; + this.fd = null; + this.readable = true; + this.paused = false; - options = { - maxBuffer: DEFAULT_MAX_BUFFER, - buffer: true, - stripFinalNewline: true, - extendEnv: true, - preferLocal: false, - localDir: options.cwd || process.cwd(), - execPath: process.execPath, - encoding: 'utf8', - reject: true, - cleanup: true, - all: false, - ...options, - windowsHide: true - }; + this.flags = 'r'; + this.mode = 438; /*=0666*/ + this.bufferSize = 64 * 1024; - options.env = getEnv(options); + options = options || {}; - options.stdio = normalizeStdio(options); + // Mixin options into this + var keys = Object.keys(options); + for (var index = 0, length = keys.length; index < length; index++) { + var key = keys[index]; + this[key] = options[key]; + } - if (process.platform === 'win32' && path.basename(file, '.exe') === 'cmd') { - // #116 - args.unshift('/q'); - } + if (this.encoding) this.setEncoding(this.encoding); - return {file, args, options, parsed}; -}; + if (this.start !== undefined) { + if ('number' !== typeof this.start) { + throw TypeError('start must be a Number'); + } + if (this.end === undefined) { + this.end = Infinity; + } else if ('number' !== typeof this.end) { + throw TypeError('end must be a Number'); + } -const handleOutput = (options, value, error) => { - if (typeof value !== 'string' && !Buffer.isBuffer(value)) { - // When `execa.sync()` errors, we normalize it to '' to mimic `execa()` - return error === undefined ? undefined : ''; - } + if (this.start > this.end) { + throw new Error('start must be <= end'); + } - if (options.stripFinalNewline) { - return stripFinalNewline(value); - } + this.pos = this.start; + } - return value; -}; + if (this.fd !== null) { + process.nextTick(function() { + self._read(); + }); + return; + } -const execa = (file, args, options) => { - const parsed = handleArgs(file, args, options); - const command = joinCommand(file, args); + fs.open(this.path, this.flags, this.mode, function (err, fd) { + if (err) { + self.emit('error', err); + self.readable = false; + return; + } - let spawned; - try { - spawned = childProcess.spawn(parsed.file, parsed.args, parsed.options); - } catch (error) { - // Ensure the returned error is always both a promise and a child process - const dummySpawned = new childProcess.ChildProcess(); - const errorPromise = Promise.reject(makeError({ - error, - stdout: '', - stderr: '', - all: '', - command, - parsed, - timedOut: false, - isCanceled: false, - killed: false - })); - return mergePromise(dummySpawned, errorPromise); - } + self.fd = fd; + self.emit('open', fd); + self._read(); + }) + } - const spawnedPromise = getSpawnedPromise(spawned); - const timedPromise = setupTimeout(spawned, parsed.options, spawnedPromise); - const processDone = setExitHandler(spawned, parsed.options, timedPromise); + function WriteStream (path, options) { + if (!(this instanceof WriteStream)) return new WriteStream(path, options); - const context = {isCanceled: false}; + Stream.call(this); - spawned.kill = spawnedKill.bind(null, spawned.kill.bind(spawned)); - spawned.cancel = spawnedCancel.bind(null, spawned, context); + this.path = path; + this.fd = null; + this.writable = true; - const handlePromise = async () => { - const [{error, exitCode, signal, timedOut}, stdoutResult, stderrResult, allResult] = await getSpawnedResult(spawned, parsed.options, processDone); - const stdout = handleOutput(parsed.options, stdoutResult); - const stderr = handleOutput(parsed.options, stderrResult); - const all = handleOutput(parsed.options, allResult); + this.flags = 'w'; + this.encoding = 'binary'; + this.mode = 438; /*=0666*/ + this.bytesWritten = 0; - if (error || exitCode !== 0 || signal !== null) { - const returnedError = makeError({ - error, - exitCode, - signal, - stdout, - stderr, - all, - command, - parsed, - timedOut, - isCanceled: context.isCanceled, - killed: spawned.killed - }); + options = options || {}; - if (!parsed.options.reject) { - return returnedError; - } + // Mixin options into this + var keys = Object.keys(options); + for (var index = 0, length = keys.length; index < length; index++) { + var key = keys[index]; + this[key] = options[key]; + } - throw returnedError; - } + if (this.start !== undefined) { + if ('number' !== typeof this.start) { + throw TypeError('start must be a Number'); + } + if (this.start < 0) { + throw new Error('start must be >= zero'); + } - return { - command, - exitCode: 0, - stdout, - stderr, - all, - failed: false, - timedOut: false, - isCanceled: false, - killed: false - }; - }; + this.pos = this.start; + } - const handlePromiseOnce = onetime(handlePromise); + this.busy = false; + this._queue = []; - crossSpawn._enoent.hookChildProcess(spawned, parsed.parsed); + if (this.fd === null) { + this._open = fs.open; + this._queue.push([this._open, this.path, this.flags, this.mode, undefined]); + this.flush(); + } + } +} - handleInput(spawned, parsed.options.input); - spawned.all = makeAllStream(spawned, parsed.options); +/***/ }), +/* 549 */ +/***/ (function(module, exports, __webpack_require__) { - return mergePromise(spawned, handlePromiseOnce); -}; +"use strict"; -module.exports = execa; -module.exports.sync = (file, args, options) => { - const parsed = handleArgs(file, args, options); - const command = joinCommand(file, args); +module.exports = clone - validateInputSync(parsed.options); +function clone (obj) { + if (obj === null || typeof obj !== 'object') + return obj - let result; - try { - result = childProcess.spawnSync(parsed.file, parsed.args, parsed.options); - } catch (error) { - throw makeError({ - error, - stdout: '', - stderr: '', - all: '', - command, - parsed, - timedOut: false, - isCanceled: false, - killed: false - }); - } + if (obj instanceof Object) + var copy = { __proto__: obj.__proto__ } + else + var copy = Object.create(null) - const stdout = handleOutput(parsed.options, result.stdout, result.error); - const stderr = handleOutput(parsed.options, result.stderr, result.error); + Object.getOwnPropertyNames(obj).forEach(function (key) { + Object.defineProperty(copy, key, Object.getOwnPropertyDescriptor(obj, key)) + }) - if (result.error || result.status !== 0 || result.signal !== null) { - const error = makeError({ - stdout, - stderr, - error: result.error, - signal: result.signal, - exitCode: result.status, - command, - parsed, - timedOut: result.error && result.error.code === 'ETIMEDOUT', - isCanceled: false, - killed: result.signal !== null - }); + return copy +} - if (!parsed.options.reject) { - return error; - } - throw error; - } +/***/ }), +/* 550 */ +/***/ (function(module, exports, __webpack_require__) { - return { - command, - exitCode: 0, - stdout, - stderr, - failed: false, - timedOut: false, - isCanceled: false, - killed: false - }; -}; +"use strict"; -module.exports.command = (command, options) => { - const [file, ...args] = parseCommand(command); - return execa(file, args, options); -}; +module.exports = writeFile +module.exports.sync = writeFileSync +module.exports._getTmpname = getTmpname // for testing +module.exports._cleanupOnExit = cleanupOnExit -module.exports.commandSync = (command, options) => { - const [file, ...args] = parseCommand(command); - return execa.sync(file, args, options); -}; +var fs = __webpack_require__(551) +var MurmurHash3 = __webpack_require__(555) +var onExit = __webpack_require__(377) +var path = __webpack_require__(16) +var activeFiles = {} -module.exports.node = (scriptPath, args, options = {}) => { - if (args && !Array.isArray(args) && typeof args === 'object') { - options = args; - args = []; - } +// if we run inside of a worker_thread, `process.pid` is not unique +/* istanbul ignore next */ +var threadId = (function getId () { + try { + var workerThreads = __webpack_require__(556) - const stdio = normalizeStdio.node(options); + /// if we are in main thread, this is set to `0` + return workerThreads.threadId + } catch (e) { + // worker_threads are not available, fallback to 0 + return 0 + } +})() - const {nodePath = process.execPath, nodeOptions = process.execArgv} = options; +var invocations = 0 +function getTmpname (filename) { + return filename + '.' + + MurmurHash3(__filename) + .hash(String(process.pid)) + .hash(String(threadId)) + .hash(String(++invocations)) + .result() +} - return execa( - nodePath, - [ - ...nodeOptions, - scriptPath, - ...(Array.isArray(args) ? args : []) - ], - { - ...options, - stdin: undefined, - stdout: undefined, - stderr: undefined, - stdio, - shell: false - } - ); -}; +function cleanupOnExit (tmpfile) { + return function () { + try { + fs.unlinkSync(typeof tmpfile === 'function' ? tmpfile() : tmpfile) + } catch (_) {} + } +} + +function writeFile (filename, data, options, callback) { + if (options) { + if (options instanceof Function) { + callback = options + options = {} + } else if (typeof options === 'string') { + options = { encoding: options } + } + } else { + options = {} + } + + var Promise = options.Promise || global.Promise + var truename + var fd + var tmpfile + /* istanbul ignore next -- The closure only gets called when onExit triggers */ + var removeOnExitHandler = onExit(cleanupOnExit(() => tmpfile)) + var absoluteName = path.resolve(filename) + + new Promise(function serializeSameFile (resolve) { + // make a queue if it doesn't already exist + if (!activeFiles[absoluteName]) activeFiles[absoluteName] = [] + + activeFiles[absoluteName].push(resolve) // add this job to the queue + if (activeFiles[absoluteName].length === 1) resolve() // kick off the first one + }).then(function getRealPath () { + return new Promise(function (resolve) { + fs.realpath(filename, function (_, realname) { + truename = realname || filename + tmpfile = getTmpname(truename) + resolve() + }) + }) + }).then(function stat () { + return new Promise(function stat (resolve) { + if (options.mode && options.chown) resolve() + else { + // Either mode or chown is not explicitly set + // Default behavior is to copy it from original file + fs.stat(truename, function (err, stats) { + if (err || !stats) resolve() + else { + options = Object.assign({}, options) + + if (options.mode == null) { + options.mode = stats.mode + } + if (options.chown == null && process.getuid) { + options.chown = { uid: stats.uid, gid: stats.gid } + } + resolve() + } + }) + } + }) + }).then(function thenWriteFile () { + return new Promise(function (resolve, reject) { + fs.open(tmpfile, 'w', options.mode, function (err, _fd) { + fd = _fd + if (err) reject(err) + else resolve() + }) + }) + }).then(function write () { + return new Promise(function (resolve, reject) { + if (Buffer.isBuffer(data)) { + fs.write(fd, data, 0, data.length, 0, function (err) { + if (err) reject(err) + else resolve() + }) + } else if (data != null) { + fs.write(fd, String(data), 0, String(options.encoding || 'utf8'), function (err) { + if (err) reject(err) + else resolve() + }) + } else resolve() + }) + }).then(function syncAndClose () { + return new Promise(function (resolve, reject) { + if (options.fsync !== false) { + fs.fsync(fd, function (err) { + if (err) fs.close(fd, () => reject(err)) + else fs.close(fd, resolve) + }) + } else { + fs.close(fd, resolve) + } + }) + }).then(function chown () { + fd = null + if (options.chown) { + return new Promise(function (resolve, reject) { + fs.chown(tmpfile, options.chown.uid, options.chown.gid, function (err) { + if (err) reject(err) + else resolve() + }) + }) + } + }).then(function chmod () { + if (options.mode) { + return new Promise(function (resolve, reject) { + fs.chmod(tmpfile, options.mode, function (err) { + if (err) reject(err) + else resolve() + }) + }) + } + }).then(function rename () { + return new Promise(function (resolve, reject) { + fs.rename(tmpfile, truename, function (err) { + if (err) reject(err) + else resolve() + }) + }) + }).then(function success () { + removeOnExitHandler() + callback() + }, function fail (err) { + return new Promise(resolve => { + return fd ? fs.close(fd, resolve) : resolve() + }).then(() => { + removeOnExitHandler() + fs.unlink(tmpfile, function () { + callback(err) + }) + }) + }).then(function checkQueue () { + activeFiles[absoluteName].shift() // remove the element added by serializeSameFile + if (activeFiles[absoluteName].length > 0) { + activeFiles[absoluteName][0]() // start next job if one is pending + } else delete activeFiles[absoluteName] + }) +} + +function writeFileSync (filename, data, options) { + if (typeof options === 'string') options = { encoding: options } + else if (!options) options = {} + try { + filename = fs.realpathSync(filename) + } catch (ex) { + // it's ok, it'll happen on a not yet existing file + } + var tmpfile = getTmpname(filename) + + if (!options.mode || !options.chown) { + // Either mode or chown is not explicitly set + // Default behavior is to copy it from original file + try { + var stats = fs.statSync(filename) + options = Object.assign({}, options) + if (!options.mode) { + options.mode = stats.mode + } + if (!options.chown && process.getuid) { + options.chown = { uid: stats.uid, gid: stats.gid } + } + } catch (ex) { + // ignore stat errors + } + } + var fd + var cleanup = cleanupOnExit(tmpfile) + var removeOnExitHandler = onExit(cleanup) -/***/ }), -/* 123 */ -/***/ (function(module, exports) { + try { + fd = fs.openSync(tmpfile, 'w', options.mode) + if (Buffer.isBuffer(data)) { + fs.writeSync(fd, data, 0, data.length, 0) + } else if (data != null) { + fs.writeSync(fd, String(data), 0, String(options.encoding || 'utf8')) + } + if (options.fsync !== false) { + fs.fsyncSync(fd) + } + fs.closeSync(fd) + if (options.chown) fs.chownSync(tmpfile, options.chown.uid, options.chown.gid) + if (options.mode) fs.chmodSync(tmpfile, options.mode) + fs.renameSync(tmpfile, filename) + removeOnExitHandler() + } catch (err) { + if (fd) { + try { + fs.closeSync(fd) + } catch (ex) { + // ignore close errors at this stage, error may have closed fd already. + } + } + removeOnExitHandler() + cleanup() + throw err + } +} -module.exports = require("child_process"); /***/ }), -/* 124 */ +/* 551 */ /***/ (function(module, exports, __webpack_require__) { -"use strict"; - - -const cp = __webpack_require__(123); -const parse = __webpack_require__(125); -const enoent = __webpack_require__(136); +var fs = __webpack_require__(23) +var polyfills = __webpack_require__(552) +var legacy = __webpack_require__(554) +var queue = [] -function spawn(command, args, options) { - // Parse the arguments - const parsed = parse(command, args, options); +var util = __webpack_require__(29) - // Spawn the child process - const spawned = cp.spawn(parsed.command, parsed.args, parsed.options); +function noop () {} - // Hook into child process "exit" event to emit an error if the command - // does not exists, see: https://github.com/IndigoUnited/node-cross-spawn/issues/16 - enoent.hookChildProcess(spawned, parsed); +var debug = noop +if (util.debuglog) + debug = util.debuglog('gfs4') +else if (/\bgfs4\b/i.test(process.env.NODE_DEBUG || '')) + debug = function() { + var m = util.format.apply(util, arguments) + m = 'GFS4: ' + m.split(/\n/).join('\nGFS4: ') + console.error(m) + } - return spawned; +if (/\bgfs4\b/i.test(process.env.NODE_DEBUG || '')) { + process.on('exit', function() { + debug(queue) + __webpack_require__(30).equal(queue.length, 0) + }) } -function spawnSync(command, args, options) { - // Parse the arguments - const parsed = parse(command, args, options); - - // Spawn the child process - const result = cp.spawnSync(parsed.command, parsed.args, parsed.options); - - // Analyze if the command does not exist, see: https://github.com/IndigoUnited/node-cross-spawn/issues/16 - result.error = result.error || enoent.verifyENOENTSync(result.status, parsed); - - return result; +module.exports = patch(__webpack_require__(553)) +if (process.env.TEST_GRACEFUL_FS_GLOBAL_PATCH) { + module.exports = patch(fs) } -module.exports = spawn; -module.exports.spawn = spawn; -module.exports.sync = spawnSync; +// Always patch fs.close/closeSync, because we want to +// retry() whenever a close happens *anywhere* in the program. +// This is essential when multiple graceful-fs instances are +// in play at the same time. +module.exports.close = +fs.close = (function (fs$close) { return function (fd, cb) { + return fs$close.call(fs, fd, function (err) { + if (!err) + retry() -module.exports._parse = parse; -module.exports._enoent = enoent; + if (typeof cb === 'function') + cb.apply(this, arguments) + }) +}})(fs.close) +module.exports.closeSync = +fs.closeSync = (function (fs$closeSync) { return function (fd) { + // Note that graceful-fs also retries when fs.closeSync() fails. + // Looks like a bug to me, although it's probably a harmless one. + var rval = fs$closeSync.apply(fs, arguments) + retry() + return rval +}})(fs.closeSync) -/***/ }), -/* 125 */ -/***/ (function(module, exports, __webpack_require__) { +function patch (fs) { + // Everything that references the open() function needs to be in here + polyfills(fs) + fs.gracefulify = patch + fs.FileReadStream = ReadStream; // Legacy name. + fs.FileWriteStream = WriteStream; // Legacy name. + fs.createReadStream = createReadStream + fs.createWriteStream = createWriteStream + var fs$readFile = fs.readFile + fs.readFile = readFile + function readFile (path, options, cb) { + if (typeof options === 'function') + cb = options, options = null -"use strict"; + return go$readFile(path, options, cb) + function go$readFile (path, options, cb) { + return fs$readFile(path, options, function (err) { + if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) + enqueue([go$readFile, [path, options, cb]]) + else { + if (typeof cb === 'function') + cb.apply(this, arguments) + retry() + } + }) + } + } -const path = __webpack_require__(16); -const resolveCommand = __webpack_require__(126); -const escape = __webpack_require__(132); -const readShebang = __webpack_require__(133); + var fs$writeFile = fs.writeFile + fs.writeFile = writeFile + function writeFile (path, data, options, cb) { + if (typeof options === 'function') + cb = options, options = null -const isWin = process.platform === 'win32'; -const isExecutableRegExp = /\.(?:com|exe)$/i; -const isCmdShimRegExp = /node_modules[\\/].bin[\\/][^\\/]+\.cmd$/i; + return go$writeFile(path, data, options, cb) -function detectShebang(parsed) { - parsed.file = resolveCommand(parsed); + function go$writeFile (path, data, options, cb) { + return fs$writeFile(path, data, options, function (err) { + if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) + enqueue([go$writeFile, [path, data, options, cb]]) + else { + if (typeof cb === 'function') + cb.apply(this, arguments) + retry() + } + }) + } + } - const shebang = parsed.file && readShebang(parsed.file); + var fs$appendFile = fs.appendFile + if (fs$appendFile) + fs.appendFile = appendFile + function appendFile (path, data, options, cb) { + if (typeof options === 'function') + cb = options, options = null - if (shebang) { - parsed.args.unshift(parsed.file); - parsed.command = shebang; + return go$appendFile(path, data, options, cb) - return resolveCommand(parsed); + function go$appendFile (path, data, options, cb) { + return fs$appendFile(path, data, options, function (err) { + if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) + enqueue([go$appendFile, [path, data, options, cb]]) + else { + if (typeof cb === 'function') + cb.apply(this, arguments) + retry() + } + }) } + } - return parsed.file; -} - -function parseNonShell(parsed) { - if (!isWin) { - return parsed; + var fs$readdir = fs.readdir + fs.readdir = readdir + function readdir (path, options, cb) { + var args = [path] + if (typeof options !== 'function') { + args.push(options) + } else { + cb = options } + args.push(go$readdir$cb) - // Detect & add support for shebangs - const commandFile = detectShebang(parsed); - - // We don't need a shell if the command filename is an executable - const needsShell = !isExecutableRegExp.test(commandFile); - - // If a shell is required, use cmd.exe and take care of escaping everything correctly - // Note that `forceShell` is an hidden option used only in tests - if (parsed.options.forceShell || needsShell) { - // Need to double escape meta chars if the command is a cmd-shim located in `node_modules/.bin/` - // The cmd-shim simply calls execute the package bin file with NodeJS, proxying any argument - // Because the escape of metachars with ^ gets interpreted when the cmd.exe is first called, - // we need to double escape them - const needsDoubleEscapeMetaChars = isCmdShimRegExp.test(commandFile); - - // Normalize posix paths into OS compatible paths (e.g.: foo/bar -> foo\bar) - // This is necessary otherwise it will always fail with ENOENT in those cases - parsed.command = path.normalize(parsed.command); - - // Escape command & arguments - parsed.command = escape.command(parsed.command); - parsed.args = parsed.args.map((arg) => escape.argument(arg, needsDoubleEscapeMetaChars)); + return go$readdir(args) - const shellCommand = [parsed.command].concat(parsed.args).join(' '); + function go$readdir$cb (err, files) { + if (files && files.sort) + files.sort() - parsed.args = ['/d', '/s', '/c', `"${shellCommand}"`]; - parsed.command = process.env.comspec || 'cmd.exe'; - parsed.options.windowsVerbatimArguments = true; // Tell node's spawn that the arguments are already escaped + if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) + enqueue([go$readdir, [args]]) + else { + if (typeof cb === 'function') + cb.apply(this, arguments) + retry() + } } + } - return parsed; -} + function go$readdir (args) { + return fs$readdir.apply(fs, args) + } -function parse(command, args, options) { - // Normalize arguments, similar to nodejs - if (args && !Array.isArray(args)) { - options = args; - args = null; - } + if (process.version.substr(0, 4) === 'v0.8') { + var legStreams = legacy(fs) + ReadStream = legStreams.ReadStream + WriteStream = legStreams.WriteStream + } - args = args ? args.slice(0) : []; // Clone array to avoid changing the original - options = Object.assign({}, options); // Clone object to avoid changing the original + var fs$ReadStream = fs.ReadStream + ReadStream.prototype = Object.create(fs$ReadStream.prototype) + ReadStream.prototype.open = ReadStream$open - // Build our parsed object - const parsed = { - command, - args, - options, - file: undefined, - original: { - command, - args, - }, - }; + var fs$WriteStream = fs.WriteStream + WriteStream.prototype = Object.create(fs$WriteStream.prototype) + WriteStream.prototype.open = WriteStream$open - // Delegate further parsing to shell or non-shell - return options.shell ? parsed : parseNonShell(parsed); -} + fs.ReadStream = ReadStream + fs.WriteStream = WriteStream -module.exports = parse; + function ReadStream (path, options) { + if (this instanceof ReadStream) + return fs$ReadStream.apply(this, arguments), this + else + return ReadStream.apply(Object.create(ReadStream.prototype), arguments) + } + function ReadStream$open () { + var that = this + open(that.path, that.flags, that.mode, function (err, fd) { + if (err) { + if (that.autoClose) + that.destroy() -/***/ }), -/* 126 */ -/***/ (function(module, exports, __webpack_require__) { + that.emit('error', err) + } else { + that.fd = fd + that.emit('open', fd) + that.read() + } + }) + } -"use strict"; + function WriteStream (path, options) { + if (this instanceof WriteStream) + return fs$WriteStream.apply(this, arguments), this + else + return WriteStream.apply(Object.create(WriteStream.prototype), arguments) + } + function WriteStream$open () { + var that = this + open(that.path, that.flags, that.mode, function (err, fd) { + if (err) { + that.destroy() + that.emit('error', err) + } else { + that.fd = fd + that.emit('open', fd) + } + }) + } -const path = __webpack_require__(16); -const which = __webpack_require__(127); -const pathKey = __webpack_require__(131)(); + function createReadStream (path, options) { + return new ReadStream(path, options) + } -function resolveCommandAttempt(parsed, withoutPathExt) { - const cwd = process.cwd(); - const hasCustomCwd = parsed.options.cwd != null; - // Worker threads do not have process.chdir() - const shouldSwitchCwd = hasCustomCwd && process.chdir !== undefined; + function createWriteStream (path, options) { + return new WriteStream(path, options) + } - // If a custom `cwd` was specified, we need to change the process cwd - // because `which` will do stat calls but does not support a custom cwd - if (shouldSwitchCwd) { - try { - process.chdir(parsed.options.cwd); - } catch (err) { - /* Empty */ - } - } + var fs$open = fs.open + fs.open = open + function open (path, flags, mode, cb) { + if (typeof mode === 'function') + cb = mode, mode = null - let resolved; + return go$open(path, flags, mode, cb) - try { - resolved = which.sync(parsed.command, { - path: (parsed.options.env || process.env)[pathKey], - pathExt: withoutPathExt ? path.delimiter : undefined, - }); - } catch (e) { - /* Empty */ - } finally { - if (shouldSwitchCwd) { - process.chdir(cwd); + function go$open (path, flags, mode, cb) { + return fs$open(path, flags, mode, function (err, fd) { + if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) + enqueue([go$open, [path, flags, mode, cb]]) + else { + if (typeof cb === 'function') + cb.apply(this, arguments) + retry() } + }) } + } - // If we successfully resolved, ensure that an absolute path is returned - // Note that when a custom `cwd` was used, we need to resolve to an absolute path based on it - if (resolved) { - resolved = path.resolve(hasCustomCwd ? parsed.options.cwd : '', resolved); - } - - return resolved; + return fs } -function resolveCommand(parsed) { - return resolveCommandAttempt(parsed) || resolveCommandAttempt(parsed, true); +function enqueue (elem) { + debug('ENQUEUE', elem[0].name, elem[1]) + queue.push(elem) } -module.exports = resolveCommand; +function retry () { + var elem = queue.shift() + if (elem) { + debug('RETRY', elem[0].name, elem[1]) + elem[0].apply(null, elem[1]) + } +} /***/ }), -/* 127 */ +/* 552 */ /***/ (function(module, exports, __webpack_require__) { -const isWindows = process.platform === 'win32' || - process.env.OSTYPE === 'cygwin' || - process.env.OSTYPE === 'msys' - -const path = __webpack_require__(16) -const COLON = isWindows ? ';' : ':' -const isexe = __webpack_require__(128) - -const getNotFoundError = (cmd) => - Object.assign(new Error(`not found: ${cmd}`), { code: 'ENOENT' }) - -const getPathInfo = (cmd, opt) => { - const colon = opt.colon || COLON +var fs = __webpack_require__(553) +var constants = __webpack_require__(25) - // If it has a slash, then we don't bother searching the pathenv. - // just check the file itself, and that's it. - const pathEnv = cmd.match(/\//) || isWindows && cmd.match(/\\/) ? [''] - : ( - [ - // windows always checks the cwd first - ...(isWindows ? [process.cwd()] : []), - ...(opt.path || process.env.PATH || - /* istanbul ignore next: very unusual */ '').split(colon), - ] - ) - const pathExtExe = isWindows - ? opt.pathExt || process.env.PATHEXT || '.EXE;.CMD;.BAT;.COM' - : '' - const pathExt = isWindows ? pathExtExe.split(colon) : [''] +var origCwd = process.cwd +var cwd = null - if (isWindows) { - if (cmd.indexOf('.') !== -1 && pathExt[0] !== '') - pathExt.unshift('') - } +var platform = process.env.GRACEFUL_FS_PLATFORM || process.platform - return { - pathEnv, - pathExt, - pathExtExe, - } +process.cwd = function() { + if (!cwd) + cwd = origCwd.call(process) + return cwd } +try { + process.cwd() +} catch (er) {} -const which = (cmd, opt, cb) => { - if (typeof opt === 'function') { - cb = opt - opt = {} - } - if (!opt) - opt = {} - - const { pathEnv, pathExt, pathExtExe } = getPathInfo(cmd, opt) - const found = [] - - const step = i => new Promise((resolve, reject) => { - if (i === pathEnv.length) - return opt.all && found.length ? resolve(found) - : reject(getNotFoundError(cmd)) - - const ppRaw = pathEnv[i] - const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw - - const pCmd = path.join(pathPart, cmd) - const p = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd - : pCmd - - resolve(subStep(p, i, 0)) - }) - - const subStep = (p, i, ii) => new Promise((resolve, reject) => { - if (ii === pathExt.length) - return resolve(step(i + 1)) - const ext = pathExt[ii] - isexe(p + ext, { pathExt: pathExtExe }, (er, is) => { - if (!er && is) { - if (opt.all) - found.push(p + ext) - else - return resolve(p + ext) - } - return resolve(subStep(p, i, ii + 1)) - }) - }) - - return cb ? step(0).then(res => cb(null, res), cb) : step(0) +var chdir = process.chdir +process.chdir = function(d) { + cwd = null + chdir.call(process, d) } -const whichSync = (cmd, opt) => { - opt = opt || {} - - const { pathEnv, pathExt, pathExtExe } = getPathInfo(cmd, opt) - const found = [] +module.exports = patch - for (let i = 0; i < pathEnv.length; i ++) { - const ppRaw = pathEnv[i] - const pathPart = /^".*"$/.test(ppRaw) ? ppRaw.slice(1, -1) : ppRaw +function patch (fs) { + // (re-)implement some things that are known busted or missing. - const pCmd = path.join(pathPart, cmd) - const p = !pathPart && /^\.[\\\/]/.test(cmd) ? cmd.slice(0, 2) + pCmd - : pCmd + // lchmod, broken prior to 0.6.2 + // back-port the fix here. + if (constants.hasOwnProperty('O_SYMLINK') && + process.version.match(/^v0\.6\.[0-2]|^v0\.5\./)) { + patchLchmod(fs) + } - for (let j = 0; j < pathExt.length; j ++) { - const cur = p + pathExt[j] - try { - const is = isexe.sync(cur, { pathExt: pathExtExe }) - if (is) { - if (opt.all) - found.push(cur) - else - return cur - } - } catch (ex) {} - } + // lutimes implementation, or no-op + if (!fs.lutimes) { + patchLutimes(fs) } - if (opt.all && found.length) - return found + // https://github.com/isaacs/node-graceful-fs/issues/4 + // Chown should not fail on einval or eperm if non-root. + // It should not fail on enosys ever, as this just indicates + // that a fs doesn't support the intended operation. - if (opt.nothrow) - return null + fs.chown = chownFix(fs.chown) + fs.fchown = chownFix(fs.fchown) + fs.lchown = chownFix(fs.lchown) - throw getNotFoundError(cmd) -} + fs.chmod = chmodFix(fs.chmod) + fs.fchmod = chmodFix(fs.fchmod) + fs.lchmod = chmodFix(fs.lchmod) -module.exports = which -which.sync = whichSync + fs.chownSync = chownFixSync(fs.chownSync) + fs.fchownSync = chownFixSync(fs.fchownSync) + fs.lchownSync = chownFixSync(fs.lchownSync) + fs.chmodSync = chmodFixSync(fs.chmodSync) + fs.fchmodSync = chmodFixSync(fs.fchmodSync) + fs.lchmodSync = chmodFixSync(fs.lchmodSync) -/***/ }), -/* 128 */ -/***/ (function(module, exports, __webpack_require__) { + fs.stat = statFix(fs.stat) + fs.fstat = statFix(fs.fstat) + fs.lstat = statFix(fs.lstat) -var fs = __webpack_require__(23) -var core -if (process.platform === 'win32' || global.TESTING_WINDOWS) { - core = __webpack_require__(129) -} else { - core = __webpack_require__(130) -} + fs.statSync = statFixSync(fs.statSync) + fs.fstatSync = statFixSync(fs.fstatSync) + fs.lstatSync = statFixSync(fs.lstatSync) -module.exports = isexe -isexe.sync = sync + // if lchmod/lchown do not exist, then make them no-ops + if (!fs.lchmod) { + fs.lchmod = function (path, mode, cb) { + if (cb) process.nextTick(cb) + } + fs.lchmodSync = function () {} + } + if (!fs.lchown) { + fs.lchown = function (path, uid, gid, cb) { + if (cb) process.nextTick(cb) + } + fs.lchownSync = function () {} + } -function isexe (path, options, cb) { - if (typeof options === 'function') { - cb = options - options = {} + // on Windows, A/V software can lock the directory, causing this + // to fail with an EACCES or EPERM if the directory contains newly + // created files. Try again on failure, for up to 60 seconds. + + // Set the timeout this long because some Windows Anti-Virus, such as Parity + // bit9, may lock files for up to a minute, causing npm package install + // failures. Also, take care to yield the scheduler. Windows scheduling gives + // CPU to a busy looping process, which can cause the program causing the lock + // contention to be starved of CPU by node, so the contention doesn't resolve. + if (platform === "win32") { + fs.rename = (function (fs$rename) { return function (from, to, cb) { + var start = Date.now() + var backoff = 0; + fs$rename(from, to, function CB (er) { + if (er + && (er.code === "EACCES" || er.code === "EPERM") + && Date.now() - start < 60000) { + setTimeout(function() { + fs.stat(to, function (stater, st) { + if (stater && stater.code === "ENOENT") + fs$rename(from, to, CB); + else + cb(er) + }) + }, backoff) + if (backoff < 100) + backoff += 10; + return; + } + if (cb) cb(er) + }) + }})(fs.rename) } - if (!cb) { - if (typeof Promise !== 'function') { - throw new TypeError('callback not provided') + // if read() returns EAGAIN, then just try it again. + fs.read = (function (fs$read) { return function (fd, buffer, offset, length, position, callback_) { + var callback + if (callback_ && typeof callback_ === 'function') { + var eagCounter = 0 + callback = function (er, _, __) { + if (er && er.code === 'EAGAIN' && eagCounter < 10) { + eagCounter ++ + return fs$read.call(fs, fd, buffer, offset, length, position, callback) + } + callback_.apply(this, arguments) + } } + return fs$read.call(fs, fd, buffer, offset, length, position, callback) + }})(fs.read) - return new Promise(function (resolve, reject) { - isexe(path, options || {}, function (er, is) { - if (er) { - reject(er) - } else { - resolve(is) + fs.readSync = (function (fs$readSync) { return function (fd, buffer, offset, length, position) { + var eagCounter = 0 + while (true) { + try { + return fs$readSync.call(fs, fd, buffer, offset, length, position) + } catch (er) { + if (er.code === 'EAGAIN' && eagCounter < 10) { + eagCounter ++ + continue } + throw er + } + } + }})(fs.readSync) +} + +function patchLchmod (fs) { + fs.lchmod = function (path, mode, callback) { + fs.open( path + , constants.O_WRONLY | constants.O_SYMLINK + , mode + , function (err, fd) { + if (err) { + if (callback) callback(err) + return + } + // prefer to return the chmod error, if one occurs, + // but still try to close, and report closing errors if they occur. + fs.fchmod(fd, mode, function (err) { + fs.close(fd, function(err2) { + if (callback) callback(err || err2) + }) }) }) } - core(path, options || {}, function (er, is) { - // ignore EACCES because that just means we aren't allowed to run it - if (er) { - if (er.code === 'EACCES' || options && options.ignoreErrors) { - er = null - is = false + fs.lchmodSync = function (path, mode) { + var fd = fs.openSync(path, constants.O_WRONLY | constants.O_SYMLINK, mode) + + // prefer to return the chmod error, if one occurs, + // but still try to close, and report closing errors if they occur. + var threw = true + var ret + try { + ret = fs.fchmodSync(fd, mode) + threw = false + } finally { + if (threw) { + try { + fs.closeSync(fd) + } catch (er) {} + } else { + fs.closeSync(fd) } } - cb(er, is) - }) + return ret + } } -function sync (path, options) { - // my kingdom for a filtered catch - try { - return core.sync(path, options || {}) - } catch (er) { - if (options && options.ignoreErrors || er.code === 'EACCES') { - return false - } else { - throw er +function patchLutimes (fs) { + if (constants.hasOwnProperty("O_SYMLINK")) { + fs.lutimes = function (path, at, mt, cb) { + fs.open(path, constants.O_SYMLINK, function (er, fd) { + if (er) { + if (cb) cb(er) + return + } + fs.futimes(fd, at, mt, function (er) { + fs.close(fd, function (er2) { + if (cb) cb(er || er2) + }) + }) + }) } - } -} + fs.lutimesSync = function (path, at, mt) { + var fd = fs.openSync(path, constants.O_SYMLINK) + var ret + var threw = true + try { + ret = fs.futimesSync(fd, at, mt) + threw = false + } finally { + if (threw) { + try { + fs.closeSync(fd) + } catch (er) {} + } else { + fs.closeSync(fd) + } + } + return ret + } -/***/ }), -/* 129 */ -/***/ (function(module, exports, __webpack_require__) { + } else { + fs.lutimes = function (_a, _b, _c, cb) { if (cb) process.nextTick(cb) } + fs.lutimesSync = function () {} + } +} -module.exports = isexe -isexe.sync = sync +function chmodFix (orig) { + if (!orig) return orig + return function (target, mode, cb) { + return orig.call(fs, target, mode, function (er) { + if (chownErOk(er)) er = null + if (cb) cb.apply(this, arguments) + }) + } +} -var fs = __webpack_require__(23) +function chmodFixSync (orig) { + if (!orig) return orig + return function (target, mode) { + try { + return orig.call(fs, target, mode) + } catch (er) { + if (!chownErOk(er)) throw er + } + } +} -function checkPathExt (path, options) { - var pathext = options.pathExt !== undefined ? - options.pathExt : process.env.PATHEXT - if (!pathext) { - return true +function chownFix (orig) { + if (!orig) return orig + return function (target, uid, gid, cb) { + return orig.call(fs, target, uid, gid, function (er) { + if (chownErOk(er)) er = null + if (cb) cb.apply(this, arguments) + }) } +} - pathext = pathext.split(';') - if (pathext.indexOf('') !== -1) { - return true - } - for (var i = 0; i < pathext.length; i++) { - var p = pathext[i].toLowerCase() - if (p && path.substr(-p.length).toLowerCase() === p) { - return true +function chownFixSync (orig) { + if (!orig) return orig + return function (target, uid, gid) { + try { + return orig.call(fs, target, uid, gid) + } catch (er) { + if (!chownErOk(er)) throw er } } - return false } -function checkStat (stat, path, options) { - if (!stat.isSymbolicLink() && !stat.isFile()) { - return false + +function statFix (orig) { + if (!orig) return orig + // Older versions of Node erroneously returned signed integers for + // uid + gid. + return function (target, cb) { + return orig.call(fs, target, function (er, stats) { + if (!stats) return cb.apply(this, arguments) + if (stats.uid < 0) stats.uid += 0x100000000 + if (stats.gid < 0) stats.gid += 0x100000000 + if (cb) cb.apply(this, arguments) + }) } - return checkPathExt(path, options) } -function isexe (path, options, cb) { - fs.stat(path, function (er, stat) { - cb(er, er ? false : checkStat(stat, path, options)) - }) +function statFixSync (orig) { + if (!orig) return orig + // Older versions of Node erroneously returned signed integers for + // uid + gid. + return function (target) { + var stats = orig.call(fs, target) + if (stats.uid < 0) stats.uid += 0x100000000 + if (stats.gid < 0) stats.gid += 0x100000000 + return stats; + } } -function sync (path, options) { - return checkStat(fs.statSync(path), path, options) +// ENOSYS means that the fs doesn't support the op. Just ignore +// that, because it doesn't matter. +// +// if there's no getuid, or if getuid() is something other +// than 0, and the error is EINVAL or EPERM, then just ignore +// it. +// +// This specific case is a silent failure in cp, install, tar, +// and most other unix tools that manage permissions. +// +// When running as root, or if other types of errors are +// encountered, then it's strict. +function chownErOk (er) { + if (!er) + return true + + if (er.code === "ENOSYS") + return true + + var nonroot = !process.getuid || process.getuid() !== 0 + if (nonroot) { + if (er.code === "EINVAL" || er.code === "EPERM") + return true + } + + return false } /***/ }), -/* 130 */ +/* 553 */ /***/ (function(module, exports, __webpack_require__) { -module.exports = isexe -isexe.sync = sync - -var fs = __webpack_require__(23) - -function isexe (path, options, cb) { - fs.stat(path, function (er, stat) { - cb(er, er ? false : checkStat(stat, options)) - }) -} +"use strict"; -function sync (path, options) { - return checkStat(fs.statSync(path), options) -} -function checkStat (stat, options) { - return stat.isFile() && checkMode(stat, options) -} +var fs = __webpack_require__(23) -function checkMode (stat, options) { - var mod = stat.mode - var uid = stat.uid - var gid = stat.gid +module.exports = clone(fs) - var myUid = options.uid !== undefined ? - options.uid : process.getuid && process.getuid() - var myGid = options.gid !== undefined ? - options.gid : process.getgid && process.getgid() +function clone (obj) { + if (obj === null || typeof obj !== 'object') + return obj - var u = parseInt('100', 8) - var g = parseInt('010', 8) - var o = parseInt('001', 8) - var ug = u | g + if (obj instanceof Object) + var copy = { __proto__: obj.__proto__ } + else + var copy = Object.create(null) - var ret = (mod & o) || - (mod & g) && gid === myGid || - (mod & u) && uid === myUid || - (mod & ug) && myUid === 0 + Object.getOwnPropertyNames(obj).forEach(function (key) { + Object.defineProperty(copy, key, Object.getOwnPropertyDescriptor(obj, key)) + }) - return ret + return copy } /***/ }), -/* 131 */ +/* 554 */ /***/ (function(module, exports, __webpack_require__) { -"use strict"; +var Stream = __webpack_require__(27).Stream +module.exports = legacy -const pathKey = (options = {}) => { - const environment = options.env || process.env; - const platform = options.platform || process.platform; +function legacy (fs) { + return { + ReadStream: ReadStream, + WriteStream: WriteStream + } - if (platform !== 'win32') { - return 'PATH'; - } + function ReadStream (path, options) { + if (!(this instanceof ReadStream)) return new ReadStream(path, options); - return Object.keys(environment).find(key => key.toUpperCase() === 'PATH') || 'Path'; -}; + Stream.call(this); -module.exports = pathKey; -// TODO: Remove this for the next major release -module.exports.default = pathKey; + var self = this; + this.path = path; + this.fd = null; + this.readable = true; + this.paused = false; -/***/ }), -/* 132 */ -/***/ (function(module, exports, __webpack_require__) { + this.flags = 'r'; + this.mode = 438; /*=0666*/ + this.bufferSize = 64 * 1024; -"use strict"; + options = options || {}; + // Mixin options into this + var keys = Object.keys(options); + for (var index = 0, length = keys.length; index < length; index++) { + var key = keys[index]; + this[key] = options[key]; + } -// See http://www.robvanderwoude.com/escapechars.php -const metaCharsRegExp = /([()\][%!^"`<>&|;, *?])/g; + if (this.encoding) this.setEncoding(this.encoding); -function escapeCommand(arg) { - // Escape meta chars - arg = arg.replace(metaCharsRegExp, '^$1'); + if (this.start !== undefined) { + if ('number' !== typeof this.start) { + throw TypeError('start must be a Number'); + } + if (this.end === undefined) { + this.end = Infinity; + } else if ('number' !== typeof this.end) { + throw TypeError('end must be a Number'); + } - return arg; -} + if (this.start > this.end) { + throw new Error('start must be <= end'); + } -function escapeArgument(arg, doubleEscapeMetaChars) { - // Convert to string - arg = `${arg}`; + this.pos = this.start; + } - // Algorithm below is based on https://qntm.org/cmd + if (this.fd !== null) { + process.nextTick(function() { + self._read(); + }); + return; + } - // Sequence of backslashes followed by a double quote: - // double up all the backslashes and escape the double quote - arg = arg.replace(/(\\*)"/g, '$1$1\\"'); + fs.open(this.path, this.flags, this.mode, function (err, fd) { + if (err) { + self.emit('error', err); + self.readable = false; + return; + } - // Sequence of backslashes followed by the end of the string - // (which will become a double quote later): - // double up all the backslashes - arg = arg.replace(/(\\*)$/, '$1$1'); + self.fd = fd; + self.emit('open', fd); + self._read(); + }) + } - // All other backslashes occur literally + function WriteStream (path, options) { + if (!(this instanceof WriteStream)) return new WriteStream(path, options); - // Quote the whole thing: - arg = `"${arg}"`; + Stream.call(this); - // Escape meta chars - arg = arg.replace(metaCharsRegExp, '^$1'); + this.path = path; + this.fd = null; + this.writable = true; - // Double escape meta chars if necessary - if (doubleEscapeMetaChars) { - arg = arg.replace(metaCharsRegExp, '^$1'); + this.flags = 'w'; + this.encoding = 'binary'; + this.mode = 438; /*=0666*/ + this.bytesWritten = 0; + + options = options || {}; + + // Mixin options into this + var keys = Object.keys(options); + for (var index = 0, length = keys.length; index < length; index++) { + var key = keys[index]; + this[key] = options[key]; + } + + if (this.start !== undefined) { + if ('number' !== typeof this.start) { + throw TypeError('start must be a Number'); + } + if (this.start < 0) { + throw new Error('start must be >= zero'); + } + + this.pos = this.start; } - return arg; + this.busy = false; + this._queue = []; + + if (this.fd === null) { + this._open = fs.open; + this._queue.push([this._open, this.path, this.flags, this.mode, undefined]); + this.flush(); + } + } } -module.exports.command = escapeCommand; -module.exports.argument = escapeArgument; - /***/ }), -/* 133 */ +/* 555 */ /***/ (function(module, exports, __webpack_require__) { -"use strict"; +/** + * @preserve + * JS Implementation of incremental MurmurHash3 (r150) (as of May 10, 2013) + * + * @author Jens Taylor + * @see http://github.com/homebrewing/brauhaus-diff + * @author Gary Court + * @see http://github.com/garycourt/murmurhash-js + * @author Austin Appleby + * @see http://sites.google.com/site/murmurhash/ + */ +(function(){ + var cache; + // Call this function without `new` to use the cached object (good for + // single-threaded environments), or with `new` to create a new object. + // + // @param {string} key A UTF-16 or ASCII string + // @param {number} seed An optional positive integer + // @return {object} A MurmurHash3 object for incremental hashing + function MurmurHash3(key, seed) { + var m = this instanceof MurmurHash3 ? this : cache; + m.reset(seed) + if (typeof key === 'string' && key.length > 0) { + m.hash(key); + } -const fs = __webpack_require__(23); -const shebangCommand = __webpack_require__(134); + if (m !== this) { + return m; + } + }; -function readShebang(command) { - // Read the first 150 bytes from the file - const size = 150; - const buffer = Buffer.alloc(size); + // Incrementally add a string to this hash + // + // @param {string} key A UTF-16 or ASCII string + // @return {object} this + MurmurHash3.prototype.hash = function(key) { + var h1, k1, i, top, len; - let fd; + len = key.length; + this.len += len; - try { - fd = fs.openSync(command, 'r'); - fs.readSync(fd, buffer, 0, size, 0); - fs.closeSync(fd); - } catch (e) { /* Empty */ } + k1 = this.k1; + i = 0; + switch (this.rem) { + case 0: k1 ^= len > i ? (key.charCodeAt(i++) & 0xffff) : 0; + case 1: k1 ^= len > i ? (key.charCodeAt(i++) & 0xffff) << 8 : 0; + case 2: k1 ^= len > i ? (key.charCodeAt(i++) & 0xffff) << 16 : 0; + case 3: + k1 ^= len > i ? (key.charCodeAt(i) & 0xff) << 24 : 0; + k1 ^= len > i ? (key.charCodeAt(i++) & 0xff00) >> 8 : 0; + } - // Attempt to extract shebang (null is returned if not a shebang) - return shebangCommand(buffer.toString()); -} + this.rem = (len + this.rem) & 3; // & 3 is same as % 4 + len -= this.rem; + if (len > 0) { + h1 = this.h1; + while (1) { + k1 = (k1 * 0x2d51 + (k1 & 0xffff) * 0xcc9e0000) & 0xffffffff; + k1 = (k1 << 15) | (k1 >>> 17); + k1 = (k1 * 0x3593 + (k1 & 0xffff) * 0x1b870000) & 0xffffffff; -module.exports = readShebang; + h1 ^= k1; + h1 = (h1 << 13) | (h1 >>> 19); + h1 = (h1 * 5 + 0xe6546b64) & 0xffffffff; + if (i >= len) { + break; + } -/***/ }), -/* 134 */ -/***/ (function(module, exports, __webpack_require__) { + k1 = ((key.charCodeAt(i++) & 0xffff)) ^ + ((key.charCodeAt(i++) & 0xffff) << 8) ^ + ((key.charCodeAt(i++) & 0xffff) << 16); + top = key.charCodeAt(i++); + k1 ^= ((top & 0xff) << 24) ^ + ((top & 0xff00) >> 8); + } -"use strict"; + k1 = 0; + switch (this.rem) { + case 3: k1 ^= (key.charCodeAt(i + 2) & 0xffff) << 16; + case 2: k1 ^= (key.charCodeAt(i + 1) & 0xffff) << 8; + case 1: k1 ^= (key.charCodeAt(i) & 0xffff); + } -const shebangRegex = __webpack_require__(135); + this.h1 = h1; + } -module.exports = (string = '') => { - const match = string.match(shebangRegex); + this.k1 = k1; + return this; + }; - if (!match) { - return null; - } + // Get the result of this hash + // + // @return {number} The 32-bit hash + MurmurHash3.prototype.result = function() { + var k1, h1; + + k1 = this.k1; + h1 = this.h1; - const [path, argument] = match[0].replace(/#! ?/, '').split(' '); - const binary = path.split('/').pop(); + if (k1 > 0) { + k1 = (k1 * 0x2d51 + (k1 & 0xffff) * 0xcc9e0000) & 0xffffffff; + k1 = (k1 << 15) | (k1 >>> 17); + k1 = (k1 * 0x3593 + (k1 & 0xffff) * 0x1b870000) & 0xffffffff; + h1 ^= k1; + } - if (binary === 'env') { - return argument; - } + h1 ^= this.len; - return argument ? `${binary} ${argument}` : binary; -}; + h1 ^= h1 >>> 16; + h1 = (h1 * 0xca6b + (h1 & 0xffff) * 0x85eb0000) & 0xffffffff; + h1 ^= h1 >>> 13; + h1 = (h1 * 0xae35 + (h1 & 0xffff) * 0xc2b20000) & 0xffffffff; + h1 ^= h1 >>> 16; + return h1 >>> 0; + }; -/***/ }), -/* 135 */ -/***/ (function(module, exports, __webpack_require__) { + // Reset the hash object for reuse + // + // @param {number} seed An optional positive integer + MurmurHash3.prototype.reset = function(seed) { + this.h1 = typeof seed === 'number' ? seed : 0; + this.rem = this.k1 = this.len = 0; + return this; + }; -"use strict"; + // A cached object to use. This can be safely used if you're in a single- + // threaded environment, otherwise you need to create new hashes to use. + cache = new MurmurHash3(); -module.exports = /^#!(.*)/; + if (true) { + module.exports = MurmurHash3; + } else {} +}()); /***/ }), -/* 136 */ +/* 556 */ +/***/ (function(module, exports) { + +module.exports = require(undefined); + +/***/ }), +/* 557 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; +const isPlainObj = __webpack_require__(558); -const isWin = process.platform === 'win32'; +module.exports = (obj, opts) => { + if (!isPlainObj(obj)) { + throw new TypeError('Expected a plain object'); + } -function notFoundError(original, syscall) { - return Object.assign(new Error(`${syscall} ${original.command} ENOENT`), { - code: 'ENOENT', - errno: 'ENOENT', - syscall: `${syscall} ${original.command}`, - path: original.command, - spawnargs: original.args, - }); -} + opts = opts || {}; -function hookChildProcess(cp, parsed) { - if (!isWin) { - return; - } + // DEPRECATED + if (typeof opts === 'function') { + throw new TypeError('Specify the compare function as an option instead'); + } - const originalEmit = cp.emit; + const deep = opts.deep; + const seenInput = []; + const seenOutput = []; - cp.emit = function (name, arg1) { - // If emitting "exit" event and exit code is 1, we need to check if - // the command exists and emit an "error" instead - // See https://github.com/IndigoUnited/node-cross-spawn/issues/16 - if (name === 'exit') { - const err = verifyENOENT(arg1, parsed, 'spawn'); + const sortKeys = x => { + const seenIndex = seenInput.indexOf(x); - if (err) { - return originalEmit.call(cp, 'error', err); - } - } + if (seenIndex !== -1) { + return seenOutput[seenIndex]; + } - return originalEmit.apply(cp, arguments); // eslint-disable-line prefer-rest-params - }; -} + const ret = {}; + const keys = Object.keys(x).sort(opts.compare); -function verifyENOENT(status, parsed) { - if (isWin && status === 1 && !parsed.file) { - return notFoundError(parsed.original, 'spawn'); - } + seenInput.push(x); + seenOutput.push(ret); - return null; -} + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + const val = x[key]; -function verifyENOENTSync(status, parsed) { - if (isWin && status === 1 && !parsed.file) { - return notFoundError(parsed.original, 'spawnSync'); - } + if (deep && Array.isArray(val)) { + const retArr = []; - return null; -} + for (let j = 0; j < val.length; j++) { + retArr[j] = isPlainObj(val[j]) ? sortKeys(val[j]) : val[j]; + } -module.exports = { - hookChildProcess, - verifyENOENT, - verifyENOENTSync, - notFoundError, + ret[key] = retArr; + continue; + } + + ret[key] = deep && isPlainObj(val) ? sortKeys(val) : val; + } + + return ret; + }; + + return sortKeys(obj); }; /***/ }), -/* 137 */ +/* 558 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; +var toString = Object.prototype.toString; -module.exports = input => { - const LF = typeof input === 'string' ? '\n' : '\n'.charCodeAt(); - const CR = typeof input === 'string' ? '\r' : '\r'.charCodeAt(); - - if (input[input.length - 1] === LF) { - input = input.slice(0, input.length - 1); - } - - if (input[input.length - 1] === CR) { - input = input.slice(0, input.length - 1); - } - - return input; +module.exports = function (x) { + var prototype; + return toString.call(x) === '[object Object]' && (prototype = Object.getPrototypeOf(x), prototype === null || prototype === Object.getPrototypeOf({})); }; /***/ }), -/* 138 */ +/* 559 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; +const fs = __webpack_require__(23); const path = __webpack_require__(16); -const pathKey = __webpack_require__(131); - -const npmRunPath = options => { - options = { - cwd: process.cwd(), - path: process.env[pathKey()], - execPath: process.execPath, - ...options - }; - - let previous; - let cwdPath = path.resolve(options.cwd); - const result = []; - - while (previous !== cwdPath) { - result.push(path.join(cwdPath, 'node_modules/.bin')); - previous = cwdPath; - cwdPath = path.resolve(cwdPath, '..'); - } - - // Ensure the running `node` binary is used - const execPathDir = path.resolve(options.cwd, options.execPath, '..'); - result.unshift(execPathDir); +const pify = __webpack_require__(560); +const semver = __webpack_require__(561); - return result.concat(options.path).join(path.delimiter); +const defaults = { + mode: 0o777 & (~process.umask()), + fs }; -module.exports = npmRunPath; -// TODO: Remove this for the next major release -module.exports.default = npmRunPath; - -module.exports.env = options => { - options = { - env: process.env, - ...options - }; - - const env = {...options.env}; - const path = pathKey({env}); +const useNativeRecursiveOption = semver.satisfies(process.version, '>=10.12.0'); - options.path = env[path]; - env[path] = module.exports(options); +// https://github.com/nodejs/node/issues/8987 +// https://github.com/libuv/libuv/pull/1088 +const checkPath = pth => { + if (process.platform === 'win32') { + const pathHasInvalidWinCharacters = /[<>:"|?*]/.test(pth.replace(path.parse(pth).root, '')); - return env; + if (pathHasInvalidWinCharacters) { + const error = new Error(`Path contains invalid characters: ${pth}`); + error.code = 'EINVAL'; + throw error; + } + } }; +const permissionError = pth => { + // This replicates the exception of `fs.mkdir` with native the + // `recusive` option when run on an invalid drive under Windows. + const error = new Error(`operation not permitted, mkdir '${pth}'`); + error.code = 'EPERM'; + error.errno = -4048; + error.path = pth; + error.syscall = 'mkdir'; + return error; +}; -/***/ }), -/* 139 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; +const makeDir = (input, options) => Promise.resolve().then(() => { + checkPath(input); + options = Object.assign({}, defaults, options); -const mimicFn = __webpack_require__(140); + // TODO: Use util.promisify when targeting Node.js 8 + const mkdir = pify(options.fs.mkdir); + const stat = pify(options.fs.stat); -const calledFunctions = new WeakMap(); + if (useNativeRecursiveOption && options.fs.mkdir === fs.mkdir) { + const pth = path.resolve(input); -const oneTime = (fn, options = {}) => { - if (typeof fn !== 'function') { - throw new TypeError('Expected a function'); + return mkdir(pth, { + mode: options.mode, + recursive: true + }).then(() => pth); } - let ret; - let isCalled = false; - let callCount = 0; - const functionName = fn.displayName || fn.name || ''; - - const onetime = function (...args) { - calledFunctions.set(onetime, ++callCount); + const make = pth => { + return mkdir(pth, options.mode) + .then(() => pth) + .catch(error => { + if (error.code === 'EPERM') { + throw error; + } - if (isCalled) { - if (options.throw === true) { - throw new Error(`Function \`${functionName}\` can only be called once`); - } + if (error.code === 'ENOENT') { + if (path.dirname(pth) === pth) { + throw permissionError(pth); + } - return ret; - } + if (error.message.includes('null bytes')) { + throw error; + } - isCalled = true; - ret = fn.apply(this, args); - fn = null; + return make(path.dirname(pth)).then(() => make(pth)); + } - return ret; + return stat(pth) + .then(stats => stats.isDirectory() ? pth : Promise.reject()) + .catch(() => { + throw error; + }); + }); }; - mimicFn(onetime, fn); - calledFunctions.set(onetime, callCount); + return make(path.resolve(input)); +}); - return onetime; -}; +module.exports = makeDir; +module.exports.default = makeDir; -module.exports = oneTime; -// TODO: Remove this for the next major release -module.exports.default = oneTime; +module.exports.sync = (input, options) => { + checkPath(input); + options = Object.assign({}, defaults, options); -module.exports.callCount = fn => { - if (!calledFunctions.has(fn)) { - throw new Error(`The given function \`${fn.name}\` is not wrapped by the \`onetime\` package`); + if (useNativeRecursiveOption && options.fs.mkdirSync === fs.mkdirSync) { + const pth = path.resolve(input); + + fs.mkdirSync(pth, { + mode: options.mode, + recursive: true + }); + + return pth; } - return calledFunctions.get(fn); -}; + const make = pth => { + try { + options.fs.mkdirSync(pth, options.mode); + } catch (error) { + if (error.code === 'EPERM') { + throw error; + } + if (error.code === 'ENOENT') { + if (path.dirname(pth) === pth) { + throw permissionError(pth); + } -/***/ }), -/* 140 */ -/***/ (function(module, exports, __webpack_require__) { + if (error.message.includes('null bytes')) { + throw error; + } -"use strict"; + make(path.dirname(pth)); + return make(pth); + } + try { + if (!options.fs.statSync(pth).isDirectory()) { + throw new Error('The path is not a directory'); + } + } catch (_) { + throw error; + } + } -const mimicFn = (to, from) => { - for (const prop of Reflect.ownKeys(from)) { - Object.defineProperty(to, prop, Object.getOwnPropertyDescriptor(from, prop)); - } + return pth; + }; - return to; + return make(path.resolve(input)); }; -module.exports = mimicFn; -// TODO: Remove this for the next major release -module.exports.default = mimicFn; - /***/ }), -/* 141 */ +/* 560 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const {signalsByName} = __webpack_require__(142); - -const getErrorPrefix = ({timedOut, timeout, errorCode, signal, signalDescription, exitCode, isCanceled}) => { - if (timedOut) { - return `timed out after ${timeout} milliseconds`; - } - - if (isCanceled) { - return 'was canceled'; - } - - if (errorCode !== undefined) { - return `failed with ${errorCode}`; - } - if (signal !== undefined) { - return `was killed with ${signal} (${signalDescription})`; - } +const processFn = (fn, options) => function (...args) { + const P = options.promiseModule; - if (exitCode !== undefined) { - return `failed with exit code ${exitCode}`; - } + return new P((resolve, reject) => { + if (options.multiArgs) { + args.push((...result) => { + if (options.errorFirst) { + if (result[0]) { + reject(result); + } else { + result.shift(); + resolve(result); + } + } else { + resolve(result); + } + }); + } else if (options.errorFirst) { + args.push((error, result) => { + if (error) { + reject(error); + } else { + resolve(result); + } + }); + } else { + args.push(resolve); + } - return 'failed'; + fn.apply(this, args); + }); }; -const makeError = ({ - stdout, - stderr, - all, - error, - signal, - exitCode, - command, - timedOut, - isCanceled, - killed, - parsed: {options: {timeout}} -}) => { - // `signal` and `exitCode` emitted on `spawned.on('exit')` event can be `null`. - // We normalize them to `undefined` - exitCode = exitCode === null ? undefined : exitCode; - signal = signal === null ? undefined : signal; - const signalDescription = signal === undefined ? undefined : signalsByName[signal].description; - - const errorCode = error && error.code; - - const prefix = getErrorPrefix({timedOut, timeout, errorCode, signal, signalDescription, exitCode, isCanceled}); - const message = `Command ${prefix}: ${command}`; +module.exports = (input, options) => { + options = Object.assign({ + exclude: [/.+(Sync|Stream)$/], + errorFirst: true, + promiseModule: Promise + }, options); - if (error instanceof Error) { - error.originalMessage = error.message; - error.message = `${message}\n${error.message}`; - } else { - error = new Error(message); + const objType = typeof input; + if (!(input !== null && (objType === 'object' || objType === 'function'))) { + throw new TypeError(`Expected \`input\` to be a \`Function\` or \`Object\`, got \`${input === null ? 'null' : objType}\``); } - error.command = command; - error.exitCode = exitCode; - error.signal = signal; - error.signalDescription = signalDescription; - error.stdout = stdout; - error.stderr = stderr; + const filter = key => { + const match = pattern => typeof pattern === 'string' ? key === pattern : pattern.test(key); + return options.include ? options.include.some(match) : !options.exclude.some(match); + }; - if (all !== undefined) { - error.all = all; + let ret; + if (objType === 'function') { + ret = function (...args) { + return options.excludeMain ? input(...args) : processFn(input, options).apply(this, args); + }; + } else { + ret = Object.create(Object.getPrototypeOf(input)); } - if ('bufferedData' in error) { - delete error.bufferedData; + for (const key in input) { // eslint-disable-line guard-for-in + const property = input[key]; + ret[key] = typeof property === 'function' && filter(key) ? processFn(property, options) : property; } - error.failed = true; - error.timedOut = Boolean(timedOut); - error.isCanceled = isCanceled; - error.killed = killed && !timedOut; - - return error; + return ret; }; -module.exports = makeError; - /***/ }), -/* 142 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; -Object.defineProperty(exports,"__esModule",{value:true});exports.signalsByNumber=exports.signalsByName=void 0;var _os=__webpack_require__(11); - -var _signals=__webpack_require__(143); -var _realtime=__webpack_require__(145); - +/* 561 */ +/***/ (function(module, exports) { +exports = module.exports = SemVer -const getSignalsByName=function(){ -const signals=(0,_signals.getSignals)(); -return signals.reduce(getSignalByName,{}); -}; +var debug +/* istanbul ignore next */ +if (typeof process === 'object' && + process.env && + process.env.NODE_DEBUG && + /\bsemver\b/i.test(process.env.NODE_DEBUG)) { + debug = function () { + var args = Array.prototype.slice.call(arguments, 0) + args.unshift('SEMVER') + console.log.apply(console, args) + } +} else { + debug = function () {} +} -const getSignalByName=function( -signalByNameMemo, -{name,number,description,supported,action,forced,standard}) -{ -return{ -...signalByNameMemo, -[name]:{name,number,description,supported,action,forced,standard}}; +// Note: this is the semver.org version of the spec that it implements +// Not necessarily the package version of this code. +exports.SEMVER_SPEC_VERSION = '2.0.0' -}; +var MAX_LENGTH = 256 +var MAX_SAFE_INTEGER = Number.MAX_SAFE_INTEGER || + /* istanbul ignore next */ 9007199254740991 -const signalsByName=getSignalsByName();exports.signalsByName=signalsByName; +// Max safe segment length for coercion. +var MAX_SAFE_COMPONENT_LENGTH = 16 +// The actual regexps go on exports.re +var re = exports.re = [] +var src = exports.src = [] +var R = 0 +// The following Regular Expressions can be used for tokenizing, +// validating, and parsing SemVer version strings. +// ## Numeric Identifier +// A single `0`, or a non-zero digit followed by zero or more digits. -const getSignalsByNumber=function(){ -const signals=(0,_signals.getSignals)(); -const length=_realtime.SIGRTMAX+1; -const signalsA=Array.from({length},(value,number)=> -getSignalByNumber(number,signals)); +var NUMERICIDENTIFIER = R++ +src[NUMERICIDENTIFIER] = '0|[1-9]\\d*' +var NUMERICIDENTIFIERLOOSE = R++ +src[NUMERICIDENTIFIERLOOSE] = '[0-9]+' -return Object.assign({},...signalsA); -}; +// ## Non-numeric Identifier +// Zero or more digits, followed by a letter or hyphen, and then zero or +// more letters, digits, or hyphens. -const getSignalByNumber=function(number,signals){ -const signal=findSignalByNumber(number,signals); +var NONNUMERICIDENTIFIER = R++ +src[NONNUMERICIDENTIFIER] = '\\d*[a-zA-Z-][a-zA-Z0-9-]*' -if(signal===undefined){ -return{}; -} +// ## Main Version +// Three dot-separated numeric identifiers. -const{name,description,supported,action,forced,standard}=signal; -return{ -[number]:{ -name, -number, -description, -supported, -action, -forced, -standard}}; +var MAINVERSION = R++ +src[MAINVERSION] = '(' + src[NUMERICIDENTIFIER] + ')\\.' + + '(' + src[NUMERICIDENTIFIER] + ')\\.' + + '(' + src[NUMERICIDENTIFIER] + ')' +var MAINVERSIONLOOSE = R++ +src[MAINVERSIONLOOSE] = '(' + src[NUMERICIDENTIFIERLOOSE] + ')\\.' + + '(' + src[NUMERICIDENTIFIERLOOSE] + ')\\.' + + '(' + src[NUMERICIDENTIFIERLOOSE] + ')' -}; +// ## Pre-release Version Identifier +// A numeric identifier, or a non-numeric identifier. +var PRERELEASEIDENTIFIER = R++ +src[PRERELEASEIDENTIFIER] = '(?:' + src[NUMERICIDENTIFIER] + + '|' + src[NONNUMERICIDENTIFIER] + ')' +var PRERELEASEIDENTIFIERLOOSE = R++ +src[PRERELEASEIDENTIFIERLOOSE] = '(?:' + src[NUMERICIDENTIFIERLOOSE] + + '|' + src[NONNUMERICIDENTIFIER] + ')' -const findSignalByNumber=function(number,signals){ -const signal=signals.find(({name})=>_os.constants.signals[name]===number); +// ## Pre-release Version +// Hyphen, followed by one or more dot-separated pre-release version +// identifiers. -if(signal!==undefined){ -return signal; -} +var PRERELEASE = R++ +src[PRERELEASE] = '(?:-(' + src[PRERELEASEIDENTIFIER] + + '(?:\\.' + src[PRERELEASEIDENTIFIER] + ')*))' -return signals.find(signalA=>signalA.number===number); -}; +var PRERELEASELOOSE = R++ +src[PRERELEASELOOSE] = '(?:-?(' + src[PRERELEASEIDENTIFIERLOOSE] + + '(?:\\.' + src[PRERELEASEIDENTIFIERLOOSE] + ')*))' -const signalsByNumber=getSignalsByNumber();exports.signalsByNumber=signalsByNumber; -//# sourceMappingURL=main.js.map +// ## Build Metadata Identifier +// Any combination of digits, letters, or hyphens. -/***/ }), -/* 143 */ -/***/ (function(module, exports, __webpack_require__) { +var BUILDIDENTIFIER = R++ +src[BUILDIDENTIFIER] = '[0-9A-Za-z-]+' -"use strict"; -Object.defineProperty(exports,"__esModule",{value:true});exports.getSignals=void 0;var _os=__webpack_require__(11); +// ## Build Metadata +// Plus sign, followed by one or more period-separated build metadata +// identifiers. -var _core=__webpack_require__(144); -var _realtime=__webpack_require__(145); +var BUILD = R++ +src[BUILD] = '(?:\\+(' + src[BUILDIDENTIFIER] + + '(?:\\.' + src[BUILDIDENTIFIER] + ')*))' +// ## Full Version String +// A main version, followed optionally by a pre-release version and +// build metadata. +// Note that the only major, minor, patch, and pre-release sections of +// the version string are capturing groups. The build metadata is not a +// capturing group, because it should not ever be used in version +// comparison. -const getSignals=function(){ -const realtimeSignals=(0,_realtime.getRealtimeSignals)(); -const signals=[..._core.SIGNALS,...realtimeSignals].map(normalizeSignal); -return signals; -};exports.getSignals=getSignals; +var FULL = R++ +var FULLPLAIN = 'v?' + src[MAINVERSION] + + src[PRERELEASE] + '?' + + src[BUILD] + '?' +src[FULL] = '^' + FULLPLAIN + '$' +// like full, but allows v1.2.3 and =1.2.3, which people do sometimes. +// also, 1.0.0alpha1 (prerelease without the hyphen) which is pretty +// common in the npm registry. +var LOOSEPLAIN = '[v=\\s]*' + src[MAINVERSIONLOOSE] + + src[PRERELEASELOOSE] + '?' + + src[BUILD] + '?' +var LOOSE = R++ +src[LOOSE] = '^' + LOOSEPLAIN + '$' +var GTLT = R++ +src[GTLT] = '((?:<|>)?=?)' +// Something like "2.*" or "1.2.x". +// Note that "x.x" is a valid xRange identifer, meaning "any version" +// Only the first item is strictly required. +var XRANGEIDENTIFIERLOOSE = R++ +src[XRANGEIDENTIFIERLOOSE] = src[NUMERICIDENTIFIERLOOSE] + '|x|X|\\*' +var XRANGEIDENTIFIER = R++ +src[XRANGEIDENTIFIER] = src[NUMERICIDENTIFIER] + '|x|X|\\*' +var XRANGEPLAIN = R++ +src[XRANGEPLAIN] = '[v=\\s]*(' + src[XRANGEIDENTIFIER] + ')' + + '(?:\\.(' + src[XRANGEIDENTIFIER] + ')' + + '(?:\\.(' + src[XRANGEIDENTIFIER] + ')' + + '(?:' + src[PRERELEASE] + ')?' + + src[BUILD] + '?' + + ')?)?' -const normalizeSignal=function({ -name, -number:defaultNumber, -description, -action, -forced=false, -standard}) -{ -const{ -signals:{[name]:constantSignal}}= -_os.constants; -const supported=constantSignal!==undefined; -const number=supported?constantSignal:defaultNumber; -return{name,number,description,supported,action,forced,standard}; -}; -//# sourceMappingURL=signals.js.map +var XRANGEPLAINLOOSE = R++ +src[XRANGEPLAINLOOSE] = '[v=\\s]*(' + src[XRANGEIDENTIFIERLOOSE] + ')' + + '(?:\\.(' + src[XRANGEIDENTIFIERLOOSE] + ')' + + '(?:\\.(' + src[XRANGEIDENTIFIERLOOSE] + ')' + + '(?:' + src[PRERELEASELOOSE] + ')?' + + src[BUILD] + '?' + + ')?)?' -/***/ }), -/* 144 */ -/***/ (function(module, exports, __webpack_require__) { +var XRANGE = R++ +src[XRANGE] = '^' + src[GTLT] + '\\s*' + src[XRANGEPLAIN] + '$' +var XRANGELOOSE = R++ +src[XRANGELOOSE] = '^' + src[GTLT] + '\\s*' + src[XRANGEPLAINLOOSE] + '$' -"use strict"; -Object.defineProperty(exports,"__esModule",{value:true});exports.SIGNALS=void 0; +// Coercion. +// Extract anything that could conceivably be a part of a valid semver +var COERCE = R++ +src[COERCE] = '(?:^|[^\\d])' + + '(\\d{1,' + MAX_SAFE_COMPONENT_LENGTH + '})' + + '(?:\\.(\\d{1,' + MAX_SAFE_COMPONENT_LENGTH + '}))?' + + '(?:\\.(\\d{1,' + MAX_SAFE_COMPONENT_LENGTH + '}))?' + + '(?:$|[^\\d])' -const SIGNALS=[ -{ -name:"SIGHUP", -number:1, -action:"terminate", -description:"Terminal closed", -standard:"posix"}, +// Tilde ranges. +// Meaning is "reasonably at or greater than" +var LONETILDE = R++ +src[LONETILDE] = '(?:~>?)' -{ -name:"SIGINT", -number:2, -action:"terminate", -description:"User interruption with CTRL-C", -standard:"ansi"}, +var TILDETRIM = R++ +src[TILDETRIM] = '(\\s*)' + src[LONETILDE] + '\\s+' +re[TILDETRIM] = new RegExp(src[TILDETRIM], 'g') +var tildeTrimReplace = '$1~' -{ -name:"SIGQUIT", -number:3, -action:"core", -description:"User interruption with CTRL-\\", -standard:"posix"}, +var TILDE = R++ +src[TILDE] = '^' + src[LONETILDE] + src[XRANGEPLAIN] + '$' +var TILDELOOSE = R++ +src[TILDELOOSE] = '^' + src[LONETILDE] + src[XRANGEPLAINLOOSE] + '$' -{ -name:"SIGILL", -number:4, -action:"core", -description:"Invalid machine instruction", -standard:"ansi"}, +// Caret ranges. +// Meaning is "at least and backwards compatible with" +var LONECARET = R++ +src[LONECARET] = '(?:\\^)' -{ -name:"SIGTRAP", -number:5, -action:"core", -description:"Debugger breakpoint", -standard:"posix"}, +var CARETTRIM = R++ +src[CARETTRIM] = '(\\s*)' + src[LONECARET] + '\\s+' +re[CARETTRIM] = new RegExp(src[CARETTRIM], 'g') +var caretTrimReplace = '$1^' -{ -name:"SIGABRT", -number:6, -action:"core", -description:"Aborted", -standard:"ansi"}, +var CARET = R++ +src[CARET] = '^' + src[LONECARET] + src[XRANGEPLAIN] + '$' +var CARETLOOSE = R++ +src[CARETLOOSE] = '^' + src[LONECARET] + src[XRANGEPLAINLOOSE] + '$' -{ -name:"SIGIOT", -number:6, -action:"core", -description:"Aborted", -standard:"bsd"}, +// A simple gt/lt/eq thing, or just "" to indicate "any version" +var COMPARATORLOOSE = R++ +src[COMPARATORLOOSE] = '^' + src[GTLT] + '\\s*(' + LOOSEPLAIN + ')$|^$' +var COMPARATOR = R++ +src[COMPARATOR] = '^' + src[GTLT] + '\\s*(' + FULLPLAIN + ')$|^$' -{ -name:"SIGBUS", -number:7, -action:"core", -description: -"Bus error due to misaligned, non-existing address or paging error", -standard:"bsd"}, +// An expression to strip any whitespace between the gtlt and the thing +// it modifies, so that `> 1.2.3` ==> `>1.2.3` +var COMPARATORTRIM = R++ +src[COMPARATORTRIM] = '(\\s*)' + src[GTLT] + + '\\s*(' + LOOSEPLAIN + '|' + src[XRANGEPLAIN] + ')' -{ -name:"SIGEMT", -number:7, -action:"terminate", -description:"Command should be emulated but is not implemented", -standard:"other"}, +// this one has to use the /g flag +re[COMPARATORTRIM] = new RegExp(src[COMPARATORTRIM], 'g') +var comparatorTrimReplace = '$1$2$3' -{ -name:"SIGFPE", -number:8, -action:"core", -description:"Floating point arithmetic error", -standard:"ansi"}, +// Something like `1.2.3 - 1.2.4` +// Note that these all use the loose form, because they'll be +// checked against either the strict or loose comparator form +// later. +var HYPHENRANGE = R++ +src[HYPHENRANGE] = '^\\s*(' + src[XRANGEPLAIN] + ')' + + '\\s+-\\s+' + + '(' + src[XRANGEPLAIN] + ')' + + '\\s*$' -{ -name:"SIGKILL", -number:9, -action:"terminate", -description:"Forced termination", -standard:"posix", -forced:true}, +var HYPHENRANGELOOSE = R++ +src[HYPHENRANGELOOSE] = '^\\s*(' + src[XRANGEPLAINLOOSE] + ')' + + '\\s+-\\s+' + + '(' + src[XRANGEPLAINLOOSE] + ')' + + '\\s*$' -{ -name:"SIGUSR1", -number:10, -action:"terminate", -description:"Application-specific signal", -standard:"posix"}, +// Star ranges basically just allow anything at all. +var STAR = R++ +src[STAR] = '(<|>)?=?\\s*\\*' -{ -name:"SIGSEGV", -number:11, -action:"core", -description:"Segmentation fault", -standard:"ansi"}, +// Compile to actual regexp objects. +// All are flag-free, unless they were created above with a flag. +for (var i = 0; i < R; i++) { + debug(i, src[i]) + if (!re[i]) { + re[i] = new RegExp(src[i]) + } +} -{ -name:"SIGUSR2", -number:12, -action:"terminate", -description:"Application-specific signal", -standard:"posix"}, +exports.parse = parse +function parse (version, options) { + if (!options || typeof options !== 'object') { + options = { + loose: !!options, + includePrerelease: false + } + } -{ -name:"SIGPIPE", -number:13, -action:"terminate", -description:"Broken pipe or socket", -standard:"posix"}, + if (version instanceof SemVer) { + return version + } -{ -name:"SIGALRM", -number:14, -action:"terminate", -description:"Timeout or timer", -standard:"posix"}, + if (typeof version !== 'string') { + return null + } -{ -name:"SIGTERM", -number:15, -action:"terminate", -description:"Termination", -standard:"ansi"}, + if (version.length > MAX_LENGTH) { + return null + } -{ -name:"SIGSTKFLT", -number:16, -action:"terminate", -description:"Stack is empty or overflowed", -standard:"other"}, + var r = options.loose ? re[LOOSE] : re[FULL] + if (!r.test(version)) { + return null + } -{ -name:"SIGCHLD", -number:17, -action:"ignore", -description:"Child process terminated, paused or unpaused", -standard:"posix"}, + try { + return new SemVer(version, options) + } catch (er) { + return null + } +} -{ -name:"SIGCLD", -number:17, -action:"ignore", -description:"Child process terminated, paused or unpaused", -standard:"other"}, +exports.valid = valid +function valid (version, options) { + var v = parse(version, options) + return v ? v.version : null +} -{ -name:"SIGCONT", -number:18, -action:"unpause", -description:"Unpaused", -standard:"posix", -forced:true}, +exports.clean = clean +function clean (version, options) { + var s = parse(version.trim().replace(/^[=v]+/, ''), options) + return s ? s.version : null +} -{ -name:"SIGSTOP", -number:19, -action:"pause", -description:"Paused", -standard:"posix", -forced:true}, +exports.SemVer = SemVer -{ -name:"SIGTSTP", -number:20, -action:"pause", -description:"Paused using CTRL-Z or \"suspend\"", -standard:"posix"}, +function SemVer (version, options) { + if (!options || typeof options !== 'object') { + options = { + loose: !!options, + includePrerelease: false + } + } + if (version instanceof SemVer) { + if (version.loose === options.loose) { + return version + } else { + version = version.version + } + } else if (typeof version !== 'string') { + throw new TypeError('Invalid Version: ' + version) + } -{ -name:"SIGTTIN", -number:21, -action:"pause", -description:"Background process cannot read terminal input", -standard:"posix"}, + if (version.length > MAX_LENGTH) { + throw new TypeError('version is longer than ' + MAX_LENGTH + ' characters') + } -{ -name:"SIGBREAK", -number:21, -action:"terminate", -description:"User interruption with CTRL-BREAK", -standard:"other"}, + if (!(this instanceof SemVer)) { + return new SemVer(version, options) + } -{ -name:"SIGTTOU", -number:22, -action:"pause", -description:"Background process cannot write to terminal output", -standard:"posix"}, + debug('SemVer', version, options) + this.options = options + this.loose = !!options.loose -{ -name:"SIGURG", -number:23, -action:"ignore", -description:"Socket received out-of-band data", -standard:"bsd"}, + var m = version.trim().match(options.loose ? re[LOOSE] : re[FULL]) -{ -name:"SIGXCPU", -number:24, -action:"core", -description:"Process timed out", -standard:"bsd"}, + if (!m) { + throw new TypeError('Invalid Version: ' + version) + } -{ -name:"SIGXFSZ", -number:25, -action:"core", -description:"File too big", -standard:"bsd"}, + this.raw = version -{ -name:"SIGVTALRM", -number:26, -action:"terminate", -description:"Timeout or timer", -standard:"bsd"}, + // these are actually numbers + this.major = +m[1] + this.minor = +m[2] + this.patch = +m[3] -{ -name:"SIGPROF", -number:27, -action:"terminate", -description:"Timeout or timer", -standard:"bsd"}, + if (this.major > MAX_SAFE_INTEGER || this.major < 0) { + throw new TypeError('Invalid major version') + } -{ -name:"SIGWINCH", -number:28, -action:"ignore", -description:"Terminal window size changed", -standard:"bsd"}, + if (this.minor > MAX_SAFE_INTEGER || this.minor < 0) { + throw new TypeError('Invalid minor version') + } -{ -name:"SIGIO", -number:29, -action:"terminate", -description:"I/O is available", -standard:"other"}, + if (this.patch > MAX_SAFE_INTEGER || this.patch < 0) { + throw new TypeError('Invalid patch version') + } -{ -name:"SIGPOLL", -number:29, -action:"terminate", -description:"Watched event", -standard:"other"}, + // numberify any prerelease numeric ids + if (!m[4]) { + this.prerelease = [] + } else { + this.prerelease = m[4].split('.').map(function (id) { + if (/^[0-9]+$/.test(id)) { + var num = +id + if (num >= 0 && num < MAX_SAFE_INTEGER) { + return num + } + } + return id + }) + } -{ -name:"SIGINFO", -number:29, -action:"ignore", -description:"Request for process information", -standard:"other"}, + this.build = m[5] ? m[5].split('.') : [] + this.format() +} -{ -name:"SIGPWR", -number:30, -action:"terminate", -description:"Device running out of power", -standard:"systemv"}, +SemVer.prototype.format = function () { + this.version = this.major + '.' + this.minor + '.' + this.patch + if (this.prerelease.length) { + this.version += '-' + this.prerelease.join('.') + } + return this.version +} -{ -name:"SIGSYS", -number:31, -action:"core", -description:"Invalid system call", -standard:"other"}, +SemVer.prototype.toString = function () { + return this.version +} -{ -name:"SIGUNUSED", -number:31, -action:"terminate", -description:"Invalid system call", -standard:"other"}];exports.SIGNALS=SIGNALS; -//# sourceMappingURL=core.js.map +SemVer.prototype.compare = function (other) { + debug('SemVer.compare', this.version, this.options, other) + if (!(other instanceof SemVer)) { + other = new SemVer(other, this.options) + } -/***/ }), -/* 145 */ -/***/ (function(module, exports, __webpack_require__) { + return this.compareMain(other) || this.comparePre(other) +} -"use strict"; -Object.defineProperty(exports,"__esModule",{value:true});exports.SIGRTMAX=exports.getRealtimeSignals=void 0; -const getRealtimeSignals=function(){ -const length=SIGRTMAX-SIGRTMIN+1; -return Array.from({length},getRealtimeSignal); -};exports.getRealtimeSignals=getRealtimeSignals; +SemVer.prototype.compareMain = function (other) { + if (!(other instanceof SemVer)) { + other = new SemVer(other, this.options) + } -const getRealtimeSignal=function(value,index){ -return{ -name:`SIGRT${index+1}`, -number:SIGRTMIN+index, -action:"terminate", -description:"Application-specific signal (realtime)", -standard:"posix"}; + return compareIdentifiers(this.major, other.major) || + compareIdentifiers(this.minor, other.minor) || + compareIdentifiers(this.patch, other.patch) +} -}; +SemVer.prototype.comparePre = function (other) { + if (!(other instanceof SemVer)) { + other = new SemVer(other, this.options) + } -const SIGRTMIN=34; -const SIGRTMAX=64;exports.SIGRTMAX=SIGRTMAX; -//# sourceMappingURL=realtime.js.map + // NOT having a prerelease is > having one + if (this.prerelease.length && !other.prerelease.length) { + return -1 + } else if (!this.prerelease.length && other.prerelease.length) { + return 1 + } else if (!this.prerelease.length && !other.prerelease.length) { + return 0 + } -/***/ }), -/* 146 */ -/***/ (function(module, exports, __webpack_require__) { + var i = 0 + do { + var a = this.prerelease[i] + var b = other.prerelease[i] + debug('prerelease compare', i, a, b) + if (a === undefined && b === undefined) { + return 0 + } else if (b === undefined) { + return 1 + } else if (a === undefined) { + return -1 + } else if (a === b) { + continue + } else { + return compareIdentifiers(a, b) + } + } while (++i) +} -"use strict"; +// preminor will bump the version up to the next minor release, and immediately +// down to pre-release. premajor and prepatch work the same way. +SemVer.prototype.inc = function (release, identifier) { + switch (release) { + case 'premajor': + this.prerelease.length = 0 + this.patch = 0 + this.minor = 0 + this.major++ + this.inc('pre', identifier) + break + case 'preminor': + this.prerelease.length = 0 + this.patch = 0 + this.minor++ + this.inc('pre', identifier) + break + case 'prepatch': + // If this is already a prerelease, it will bump to the next version + // drop any prereleases that might already exist, since they are not + // relevant at this point. + this.prerelease.length = 0 + this.inc('patch', identifier) + this.inc('pre', identifier) + break + // If the input is a non-prerelease version, this acts the same as + // prepatch. + case 'prerelease': + if (this.prerelease.length === 0) { + this.inc('patch', identifier) + } + this.inc('pre', identifier) + break -const aliases = ['stdin', 'stdout', 'stderr']; + case 'major': + // If this is a pre-major version, bump up to the same major version. + // Otherwise increment major. + // 1.0.0-5 bumps to 1.0.0 + // 1.1.0 bumps to 2.0.0 + if (this.minor !== 0 || + this.patch !== 0 || + this.prerelease.length === 0) { + this.major++ + } + this.minor = 0 + this.patch = 0 + this.prerelease = [] + break + case 'minor': + // If this is a pre-minor version, bump up to the same minor version. + // Otherwise increment minor. + // 1.2.0-5 bumps to 1.2.0 + // 1.2.1 bumps to 1.3.0 + if (this.patch !== 0 || this.prerelease.length === 0) { + this.minor++ + } + this.patch = 0 + this.prerelease = [] + break + case 'patch': + // If this is not a pre-release version, it will increment the patch. + // If it is a pre-release it will bump up to the same patch version. + // 1.2.0-5 patches to 1.2.0 + // 1.2.0 patches to 1.2.1 + if (this.prerelease.length === 0) { + this.patch++ + } + this.prerelease = [] + break + // This probably shouldn't be used publicly. + // 1.0.0 "pre" would become 1.0.0-0 which is the wrong direction. + case 'pre': + if (this.prerelease.length === 0) { + this.prerelease = [0] + } else { + var i = this.prerelease.length + while (--i >= 0) { + if (typeof this.prerelease[i] === 'number') { + this.prerelease[i]++ + i = -2 + } + } + if (i === -1) { + // didn't increment anything + this.prerelease.push(0) + } + } + if (identifier) { + // 1.2.0-beta.1 bumps to 1.2.0-beta.2, + // 1.2.0-beta.fooblz or 1.2.0-beta bumps to 1.2.0-beta.0 + if (this.prerelease[0] === identifier) { + if (isNaN(this.prerelease[1])) { + this.prerelease = [identifier, 0] + } + } else { + this.prerelease = [identifier, 0] + } + } + break -const hasAlias = opts => aliases.some(alias => opts[alias] !== undefined); + default: + throw new Error('invalid increment argument: ' + release) + } + this.format() + this.raw = this.version + return this +} -const normalizeStdio = opts => { - if (!opts) { - return; - } +exports.inc = inc +function inc (version, release, loose, identifier) { + if (typeof (loose) === 'string') { + identifier = loose + loose = undefined + } - const {stdio} = opts; + try { + return new SemVer(version, loose).inc(release, identifier).version + } catch (er) { + return null + } +} - if (stdio === undefined) { - return aliases.map(alias => opts[alias]); - } +exports.diff = diff +function diff (version1, version2) { + if (eq(version1, version2)) { + return null + } else { + var v1 = parse(version1) + var v2 = parse(version2) + var prefix = '' + if (v1.prerelease.length || v2.prerelease.length) { + prefix = 'pre' + var defaultResult = 'prerelease' + } + for (var key in v1) { + if (key === 'major' || key === 'minor' || key === 'patch') { + if (v1[key] !== v2[key]) { + return prefix + key + } + } + } + return defaultResult // may be undefined + } +} - if (hasAlias(opts)) { - throw new Error(`It's not possible to provide \`stdio\` in combination with one of ${aliases.map(alias => `\`${alias}\``).join(', ')}`); - } +exports.compareIdentifiers = compareIdentifiers - if (typeof stdio === 'string') { - return stdio; - } +var numeric = /^[0-9]+$/ +function compareIdentifiers (a, b) { + var anum = numeric.test(a) + var bnum = numeric.test(b) - if (!Array.isArray(stdio)) { - throw new TypeError(`Expected \`stdio\` to be of type \`string\` or \`Array\`, got \`${typeof stdio}\``); - } + if (anum && bnum) { + a = +a + b = +b + } - const length = Math.max(stdio.length, aliases.length); - return Array.from({length}, (value, index) => stdio[index]); -}; + return a === b ? 0 + : (anum && !bnum) ? -1 + : (bnum && !anum) ? 1 + : a < b ? -1 + : 1 +} -module.exports = normalizeStdio; +exports.rcompareIdentifiers = rcompareIdentifiers +function rcompareIdentifiers (a, b) { + return compareIdentifiers(b, a) +} -// `ipc` is pushed unless it is already present -module.exports.node = opts => { - const stdio = normalizeStdio(opts); +exports.major = major +function major (a, loose) { + return new SemVer(a, loose).major +} - if (stdio === 'ipc') { - return 'ipc'; - } +exports.minor = minor +function minor (a, loose) { + return new SemVer(a, loose).minor +} - if (stdio === undefined || typeof stdio === 'string') { - return [stdio, stdio, stdio, 'ipc']; - } +exports.patch = patch +function patch (a, loose) { + return new SemVer(a, loose).patch +} - if (stdio.includes('ipc')) { - return stdio; - } +exports.compare = compare +function compare (a, b, loose) { + return new SemVer(a, loose).compare(new SemVer(b, loose)) +} - return [...stdio, 'ipc']; -}; +exports.compareLoose = compareLoose +function compareLoose (a, b) { + return compare(a, b, true) +} +exports.rcompare = rcompare +function rcompare (a, b, loose) { + return compare(b, a, loose) +} -/***/ }), -/* 147 */ -/***/ (function(module, exports, __webpack_require__) { +exports.sort = sort +function sort (list, loose) { + return list.sort(function (a, b) { + return exports.compare(a, b, loose) + }) +} -"use strict"; +exports.rsort = rsort +function rsort (list, loose) { + return list.sort(function (a, b) { + return exports.rcompare(a, b, loose) + }) +} -const os = __webpack_require__(11); -const onExit = __webpack_require__(110); -const pFinally = __webpack_require__(148); +exports.gt = gt +function gt (a, b, loose) { + return compare(a, b, loose) > 0 +} -const DEFAULT_FORCE_KILL_TIMEOUT = 1000 * 5; +exports.lt = lt +function lt (a, b, loose) { + return compare(a, b, loose) < 0 +} -// Monkey-patches `childProcess.kill()` to add `forceKillAfterTimeout` behavior -const spawnedKill = (kill, signal = 'SIGTERM', options = {}) => { - const killResult = kill(signal); - setKillTimeout(kill, signal, options, killResult); - return killResult; -}; +exports.eq = eq +function eq (a, b, loose) { + return compare(a, b, loose) === 0 +} -const setKillTimeout = (kill, signal, options, killResult) => { - if (!shouldForceKill(signal, options, killResult)) { - return; - } +exports.neq = neq +function neq (a, b, loose) { + return compare(a, b, loose) !== 0 +} - const timeout = getForceKillAfterTimeout(options); - setTimeout(() => { - kill('SIGKILL'); - }, timeout).unref(); -}; +exports.gte = gte +function gte (a, b, loose) { + return compare(a, b, loose) >= 0 +} -const shouldForceKill = (signal, {forceKillAfterTimeout}, killResult) => { - return isSigterm(signal) && forceKillAfterTimeout !== false && killResult; -}; +exports.lte = lte +function lte (a, b, loose) { + return compare(a, b, loose) <= 0 +} -const isSigterm = signal => { - return signal === os.constants.signals.SIGTERM || - (typeof signal === 'string' && signal.toUpperCase() === 'SIGTERM'); -}; +exports.cmp = cmp +function cmp (a, op, b, loose) { + switch (op) { + case '===': + if (typeof a === 'object') + a = a.version + if (typeof b === 'object') + b = b.version + return a === b -const getForceKillAfterTimeout = ({forceKillAfterTimeout = true}) => { - if (forceKillAfterTimeout === true) { - return DEFAULT_FORCE_KILL_TIMEOUT; - } + case '!==': + if (typeof a === 'object') + a = a.version + if (typeof b === 'object') + b = b.version + return a !== b - if (!Number.isInteger(forceKillAfterTimeout) || forceKillAfterTimeout < 0) { - throw new TypeError(`Expected the \`forceKillAfterTimeout\` option to be a non-negative integer, got \`${forceKillAfterTimeout}\` (${typeof forceKillAfterTimeout})`); - } + case '': + case '=': + case '==': + return eq(a, b, loose) - return forceKillAfterTimeout; -}; + case '!=': + return neq(a, b, loose) -// `childProcess.cancel()` -const spawnedCancel = (spawned, context) => { - const killResult = spawned.kill(); + case '>': + return gt(a, b, loose) - if (killResult) { - context.isCanceled = true; - } -}; + case '>=': + return gte(a, b, loose) -const timeoutKill = (spawned, signal, reject) => { - spawned.kill(signal); - reject(Object.assign(new Error('Timed out'), {timedOut: true, signal})); -}; + case '<': + return lt(a, b, loose) -// `timeout` option handling -const setupTimeout = (spawned, {timeout, killSignal = 'SIGTERM'}, spawnedPromise) => { - if (timeout === 0 || timeout === undefined) { - return spawnedPromise; - } + case '<=': + return lte(a, b, loose) - if (!Number.isInteger(timeout) || timeout < 0) { - throw new TypeError(`Expected the \`timeout\` option to be a non-negative integer, got \`${timeout}\` (${typeof timeout})`); - } + default: + throw new TypeError('Invalid operator: ' + op) + } +} - let timeoutId; - const timeoutPromise = new Promise((resolve, reject) => { - timeoutId = setTimeout(() => { - timeoutKill(spawned, killSignal, reject); - }, timeout); - }); +exports.Comparator = Comparator +function Comparator (comp, options) { + if (!options || typeof options !== 'object') { + options = { + loose: !!options, + includePrerelease: false + } + } - const safeSpawnedPromise = pFinally(spawnedPromise, () => { - clearTimeout(timeoutId); - }); + if (comp instanceof Comparator) { + if (comp.loose === !!options.loose) { + return comp + } else { + comp = comp.value + } + } - return Promise.race([timeoutPromise, safeSpawnedPromise]); -}; + if (!(this instanceof Comparator)) { + return new Comparator(comp, options) + } -// `cleanup` option handling -const setExitHandler = (spawned, {cleanup, detached}, timedPromise) => { - if (!cleanup || detached) { - return timedPromise; - } + debug('comparator', comp, options) + this.options = options + this.loose = !!options.loose + this.parse(comp) - const removeExitHandler = onExit(() => { - spawned.kill(); - }); + if (this.semver === ANY) { + this.value = '' + } else { + this.value = this.operator + this.semver.version + } - // TODO: Use native "finally" syntax when targeting Node.js 10 - return pFinally(timedPromise, removeExitHandler); -}; + debug('comp', this) +} -module.exports = { - spawnedKill, - spawnedCancel, - setupTimeout, - setExitHandler -}; +var ANY = {} +Comparator.prototype.parse = function (comp) { + var r = this.options.loose ? re[COMPARATORLOOSE] : re[COMPARATOR] + var m = comp.match(r) + if (!m) { + throw new TypeError('Invalid comparator: ' + comp) + } -/***/ }), -/* 148 */ -/***/ (function(module, exports, __webpack_require__) { + this.operator = m[1] + if (this.operator === '=') { + this.operator = '' + } -"use strict"; + // if it literally is just '>' or '' then allow anything. + if (!m[2]) { + this.semver = ANY + } else { + this.semver = new SemVer(m[2], this.options.loose) + } +} +Comparator.prototype.toString = function () { + return this.value +} -module.exports = async ( - promise, - onFinally = (() => {}) -) => { - let value; - try { - value = await promise; - } catch (error) { - await onFinally(); - throw error; - } +Comparator.prototype.test = function (version) { + debug('Comparator.test', version, this.options.loose) - await onFinally(); - return value; -}; + if (this.semver === ANY) { + return true + } + if (typeof version === 'string') { + version = new SemVer(version, this.options) + } -/***/ }), -/* 149 */ -/***/ (function(module, exports, __webpack_require__) { + return cmp(version, this.operator, this.semver, this.options) +} -"use strict"; +Comparator.prototype.intersects = function (comp, options) { + if (!(comp instanceof Comparator)) { + throw new TypeError('a Comparator is required') + } -const isStream = __webpack_require__(150); -const getStream = __webpack_require__(151); -const mergeStream = __webpack_require__(155); + if (!options || typeof options !== 'object') { + options = { + loose: !!options, + includePrerelease: false + } + } -// `input` option -const handleInput = (spawned, input) => { - // Checking for stdin is workaround for https://github.com/nodejs/node/issues/26852 - // TODO: Remove `|| spawned.stdin === undefined` once we drop support for Node.js <=12.2.0 - if (input === undefined || spawned.stdin === undefined) { - return; - } + var rangeTmp - if (isStream(input)) { - input.pipe(spawned.stdin); - } else { - spawned.stdin.end(input); - } -}; + if (this.operator === '') { + rangeTmp = new Range(comp.value, options) + return satisfies(this.value, rangeTmp, options) + } else if (comp.operator === '') { + rangeTmp = new Range(this.value, options) + return satisfies(comp.semver, rangeTmp, options) + } -// `all` interleaves `stdout` and `stderr` -const makeAllStream = (spawned, {all}) => { - if (!all || (!spawned.stdout && !spawned.stderr)) { - return; - } + var sameDirectionIncreasing = + (this.operator === '>=' || this.operator === '>') && + (comp.operator === '>=' || comp.operator === '>') + var sameDirectionDecreasing = + (this.operator === '<=' || this.operator === '<') && + (comp.operator === '<=' || comp.operator === '<') + var sameSemVer = this.semver.version === comp.semver.version + var differentDirectionsInclusive = + (this.operator === '>=' || this.operator === '<=') && + (comp.operator === '>=' || comp.operator === '<=') + var oppositeDirectionsLessThan = + cmp(this.semver, '<', comp.semver, options) && + ((this.operator === '>=' || this.operator === '>') && + (comp.operator === '<=' || comp.operator === '<')) + var oppositeDirectionsGreaterThan = + cmp(this.semver, '>', comp.semver, options) && + ((this.operator === '<=' || this.operator === '<') && + (comp.operator === '>=' || comp.operator === '>')) - const mixed = mergeStream(); + return sameDirectionIncreasing || sameDirectionDecreasing || + (sameSemVer && differentDirectionsInclusive) || + oppositeDirectionsLessThan || oppositeDirectionsGreaterThan +} - if (spawned.stdout) { - mixed.add(spawned.stdout); - } +exports.Range = Range +function Range (range, options) { + if (!options || typeof options !== 'object') { + options = { + loose: !!options, + includePrerelease: false + } + } - if (spawned.stderr) { - mixed.add(spawned.stderr); - } + if (range instanceof Range) { + if (range.loose === !!options.loose && + range.includePrerelease === !!options.includePrerelease) { + return range + } else { + return new Range(range.raw, options) + } + } - return mixed; -}; + if (range instanceof Comparator) { + return new Range(range.value, options) + } -// On failure, `result.stdout|stderr|all` should contain the currently buffered stream -const getBufferedData = async (stream, streamPromise) => { - if (!stream) { - return; - } + if (!(this instanceof Range)) { + return new Range(range, options) + } - stream.destroy(); + this.options = options + this.loose = !!options.loose + this.includePrerelease = !!options.includePrerelease - try { - return await streamPromise; - } catch (error) { - return error.bufferedData; - } -}; + // First, split based on boolean or || + this.raw = range + this.set = range.split(/\s*\|\|\s*/).map(function (range) { + return this.parseRange(range.trim()) + }, this).filter(function (c) { + // throw out any that are not relevant for whatever reason + return c.length + }) -const getStreamPromise = (stream, {encoding, buffer, maxBuffer}) => { - if (!stream || !buffer) { - return; - } + if (!this.set.length) { + throw new TypeError('Invalid SemVer Range: ' + range) + } - if (encoding) { - return getStream(stream, {encoding, maxBuffer}); - } + this.format() +} - return getStream.buffer(stream, {maxBuffer}); -}; +Range.prototype.format = function () { + this.range = this.set.map(function (comps) { + return comps.join(' ').trim() + }).join('||').trim() + return this.range +} -// Retrieve result of child process: exit code, signal, error, streams (stdout/stderr/all) -const getSpawnedResult = async ({stdout, stderr, all}, {encoding, buffer, maxBuffer}, processDone) => { - const stdoutPromise = getStreamPromise(stdout, {encoding, buffer, maxBuffer}); - const stderrPromise = getStreamPromise(stderr, {encoding, buffer, maxBuffer}); - const allPromise = getStreamPromise(all, {encoding, buffer, maxBuffer: maxBuffer * 2}); +Range.prototype.toString = function () { + return this.range +} - try { - return await Promise.all([processDone, stdoutPromise, stderrPromise, allPromise]); - } catch (error) { - return Promise.all([ - {error, signal: error.signal, timedOut: error.timedOut}, - getBufferedData(stdout, stdoutPromise), - getBufferedData(stderr, stderrPromise), - getBufferedData(all, allPromise) - ]); - } -}; +Range.prototype.parseRange = function (range) { + var loose = this.options.loose + range = range.trim() + // `1.2.3 - 1.2.4` => `>=1.2.3 <=1.2.4` + var hr = loose ? re[HYPHENRANGELOOSE] : re[HYPHENRANGE] + range = range.replace(hr, hyphenReplace) + debug('hyphen replace', range) + // `> 1.2.3 < 1.2.5` => `>1.2.3 <1.2.5` + range = range.replace(re[COMPARATORTRIM], comparatorTrimReplace) + debug('comparator trim', range, re[COMPARATORTRIM]) -const validateInputSync = ({input}) => { - if (isStream(input)) { - throw new TypeError('The `input` option cannot be a stream in sync mode'); - } -}; + // `~ 1.2.3` => `~1.2.3` + range = range.replace(re[TILDETRIM], tildeTrimReplace) -module.exports = { - handleInput, - makeAllStream, - getSpawnedResult, - validateInputSync -}; + // `^ 1.2.3` => `^1.2.3` + range = range.replace(re[CARETTRIM], caretTrimReplace) + // normalize spaces + range = range.split(/\s+/).join(' ') + // At this point, the range is completely trimmed and + // ready to be split into comparators. -/***/ }), -/* 150 */ -/***/ (function(module, exports, __webpack_require__) { + var compRe = loose ? re[COMPARATORLOOSE] : re[COMPARATOR] + var set = range.split(' ').map(function (comp) { + return parseComparator(comp, this.options) + }, this).join(' ').split(/\s+/) + if (this.options.loose) { + // in loose mode, throw out any that are not valid comparators + set = set.filter(function (comp) { + return !!comp.match(compRe) + }) + } + set = set.map(function (comp) { + return new Comparator(comp, this.options) + }, this) -"use strict"; + return set +} +Range.prototype.intersects = function (range, options) { + if (!(range instanceof Range)) { + throw new TypeError('a Range is required') + } -const isStream = stream => - stream !== null && - typeof stream === 'object' && - typeof stream.pipe === 'function'; + return this.set.some(function (thisComparators) { + return thisComparators.every(function (thisComparator) { + return range.set.some(function (rangeComparators) { + return rangeComparators.every(function (rangeComparator) { + return thisComparator.intersects(rangeComparator, options) + }) + }) + }) + }) +} -isStream.writable = stream => - isStream(stream) && - stream.writable !== false && - typeof stream._write === 'function' && - typeof stream._writableState === 'object'; +// Mostly just for testing and legacy API reasons +exports.toComparators = toComparators +function toComparators (range, options) { + return new Range(range, options).set.map(function (comp) { + return comp.map(function (c) { + return c.value + }).join(' ').trim().split(' ') + }) +} -isStream.readable = stream => - isStream(stream) && - stream.readable !== false && - typeof stream._read === 'function' && - typeof stream._readableState === 'object'; +// comprised of xranges, tildes, stars, and gtlt's at this point. +// already replaced the hyphen ranges +// turn into a set of JUST comparators. +function parseComparator (comp, options) { + debug('comp', comp, options) + comp = replaceCarets(comp, options) + debug('caret', comp) + comp = replaceTildes(comp, options) + debug('tildes', comp) + comp = replaceXRanges(comp, options) + debug('xrange', comp) + comp = replaceStars(comp, options) + debug('stars', comp) + return comp +} -isStream.duplex = stream => - isStream.writable(stream) && - isStream.readable(stream); +function isX (id) { + return !id || id.toLowerCase() === 'x' || id === '*' +} -isStream.transform = stream => - isStream.duplex(stream) && - typeof stream._transform === 'function' && - typeof stream._transformState === 'object'; +// ~, ~> --> * (any, kinda silly) +// ~2, ~2.x, ~2.x.x, ~>2, ~>2.x ~>2.x.x --> >=2.0.0 <3.0.0 +// ~2.0, ~2.0.x, ~>2.0, ~>2.0.x --> >=2.0.0 <2.1.0 +// ~1.2, ~1.2.x, ~>1.2, ~>1.2.x --> >=1.2.0 <1.3.0 +// ~1.2.3, ~>1.2.3 --> >=1.2.3 <1.3.0 +// ~1.2.0, ~>1.2.0 --> >=1.2.0 <1.3.0 +function replaceTildes (comp, options) { + return comp.trim().split(/\s+/).map(function (comp) { + return replaceTilde(comp, options) + }).join(' ') +} -module.exports = isStream; +function replaceTilde (comp, options) { + var r = options.loose ? re[TILDELOOSE] : re[TILDE] + return comp.replace(r, function (_, M, m, p, pr) { + debug('tilde', comp, _, M, m, p, pr) + var ret + if (isX(M)) { + ret = '' + } else if (isX(m)) { + ret = '>=' + M + '.0.0 <' + (+M + 1) + '.0.0' + } else if (isX(p)) { + // ~1.2 == >=1.2.0 <1.3.0 + ret = '>=' + M + '.' + m + '.0 <' + M + '.' + (+m + 1) + '.0' + } else if (pr) { + debug('replaceTilde pr', pr) + ret = '>=' + M + '.' + m + '.' + p + '-' + pr + + ' <' + M + '.' + (+m + 1) + '.0' + } else { + // ~1.2.3 == >=1.2.3 <1.3.0 + ret = '>=' + M + '.' + m + '.' + p + + ' <' + M + '.' + (+m + 1) + '.0' + } -/***/ }), -/* 151 */ -/***/ (function(module, exports, __webpack_require__) { + debug('tilde return', ret) + return ret + }) +} + +// ^ --> * (any, kinda silly) +// ^2, ^2.x, ^2.x.x --> >=2.0.0 <3.0.0 +// ^2.0, ^2.0.x --> >=2.0.0 <3.0.0 +// ^1.2, ^1.2.x --> >=1.2.0 <2.0.0 +// ^1.2.3 --> >=1.2.3 <2.0.0 +// ^1.2.0 --> >=1.2.0 <2.0.0 +function replaceCarets (comp, options) { + return comp.trim().split(/\s+/).map(function (comp) { + return replaceCaret(comp, options) + }).join(' ') +} -"use strict"; +function replaceCaret (comp, options) { + debug('caret', comp, options) + var r = options.loose ? re[CARETLOOSE] : re[CARET] + return comp.replace(r, function (_, M, m, p, pr) { + debug('caret', comp, _, M, m, p, pr) + var ret -const pump = __webpack_require__(152); -const bufferStream = __webpack_require__(154); + if (isX(M)) { + ret = '' + } else if (isX(m)) { + ret = '>=' + M + '.0.0 <' + (+M + 1) + '.0.0' + } else if (isX(p)) { + if (M === '0') { + ret = '>=' + M + '.' + m + '.0 <' + M + '.' + (+m + 1) + '.0' + } else { + ret = '>=' + M + '.' + m + '.0 <' + (+M + 1) + '.0.0' + } + } else if (pr) { + debug('replaceCaret pr', pr) + if (M === '0') { + if (m === '0') { + ret = '>=' + M + '.' + m + '.' + p + '-' + pr + + ' <' + M + '.' + m + '.' + (+p + 1) + } else { + ret = '>=' + M + '.' + m + '.' + p + '-' + pr + + ' <' + M + '.' + (+m + 1) + '.0' + } + } else { + ret = '>=' + M + '.' + m + '.' + p + '-' + pr + + ' <' + (+M + 1) + '.0.0' + } + } else { + debug('no pr') + if (M === '0') { + if (m === '0') { + ret = '>=' + M + '.' + m + '.' + p + + ' <' + M + '.' + m + '.' + (+p + 1) + } else { + ret = '>=' + M + '.' + m + '.' + p + + ' <' + M + '.' + (+m + 1) + '.0' + } + } else { + ret = '>=' + M + '.' + m + '.' + p + + ' <' + (+M + 1) + '.0.0' + } + } -class MaxBufferError extends Error { - constructor() { - super('maxBuffer exceeded'); - this.name = 'MaxBufferError'; - } + debug('caret return', ret) + return ret + }) } -async function getStream(inputStream, options) { - if (!inputStream) { - return Promise.reject(new Error('Expected a stream')); - } - - options = { - maxBuffer: Infinity, - ...options - }; +function replaceXRanges (comp, options) { + debug('replaceXRanges', comp, options) + return comp.split(/\s+/).map(function (comp) { + return replaceXRange(comp, options) + }).join(' ') +} - const {maxBuffer} = options; +function replaceXRange (comp, options) { + comp = comp.trim() + var r = options.loose ? re[XRANGELOOSE] : re[XRANGE] + return comp.replace(r, function (ret, gtlt, M, m, p, pr) { + debug('xRange', comp, ret, gtlt, M, m, p, pr) + var xM = isX(M) + var xm = xM || isX(m) + var xp = xm || isX(p) + var anyX = xp - let stream; - await new Promise((resolve, reject) => { - const rejectPromise = error => { - if (error) { // A null check - error.bufferedData = stream.getBufferedValue(); - } + if (gtlt === '=' && anyX) { + gtlt = '' + } - reject(error); - }; + if (xM) { + if (gtlt === '>' || gtlt === '<') { + // nothing is allowed + ret = '<0.0.0' + } else { + // nothing is forbidden + ret = '*' + } + } else if (gtlt && anyX) { + // we know patch is an x, because we have any x at all. + // replace X with 0 + if (xm) { + m = 0 + } + p = 0 - stream = pump(inputStream, bufferStream(options), error => { - if (error) { - rejectPromise(error); - return; - } + if (gtlt === '>') { + // >1 => >=2.0.0 + // >1.2 => >=1.3.0 + // >1.2.3 => >= 1.2.4 + gtlt = '>=' + if (xm) { + M = +M + 1 + m = 0 + p = 0 + } else { + m = +m + 1 + p = 0 + } + } else if (gtlt === '<=') { + // <=0.7.x is actually <0.8.0, since any 0.7.x should + // pass. Similarly, <=7.x is actually <8.0.0, etc. + gtlt = '<' + if (xm) { + M = +M + 1 + } else { + m = +m + 1 + } + } - resolve(); - }); + ret = gtlt + M + '.' + m + '.' + p + } else if (xm) { + ret = '>=' + M + '.0.0 <' + (+M + 1) + '.0.0' + } else if (xp) { + ret = '>=' + M + '.' + m + '.0 <' + M + '.' + (+m + 1) + '.0' + } - stream.on('data', () => { - if (stream.getBufferedLength() > maxBuffer) { - rejectPromise(new MaxBufferError()); - } - }); - }); + debug('xRange return', ret) - return stream.getBufferedValue(); + return ret + }) } -module.exports = getStream; -// TODO: Remove this for the next major release -module.exports.default = getStream; -module.exports.buffer = (stream, options) => getStream(stream, {...options, encoding: 'buffer'}); -module.exports.array = (stream, options) => getStream(stream, {...options, array: true}); -module.exports.MaxBufferError = MaxBufferError; - - -/***/ }), -/* 152 */ -/***/ (function(module, exports, __webpack_require__) { +// Because * is AND-ed with everything else in the comparator, +// and '' means "any version", just remove the *s entirely. +function replaceStars (comp, options) { + debug('replaceStars', comp, options) + // Looseness is ignored here. star is always as loose as it gets! + return comp.trim().replace(re[STAR], '') +} -var once = __webpack_require__(52) -var eos = __webpack_require__(153) -var fs = __webpack_require__(23) // we only need fs to get the ReadStream and WriteStream prototypes +// This function is passed to string.replace(re[HYPHENRANGE]) +// M, m, patch, prerelease, build +// 1.2 - 3.4.5 => >=1.2.0 <=3.4.5 +// 1.2.3 - 3.4 => >=1.2.0 <3.5.0 Any 3.4.x will do +// 1.2 - 3.4 => >=1.2.0 <3.5.0 +function hyphenReplace ($0, + from, fM, fm, fp, fpr, fb, + to, tM, tm, tp, tpr, tb) { + if (isX(fM)) { + from = '' + } else if (isX(fm)) { + from = '>=' + fM + '.0.0' + } else if (isX(fp)) { + from = '>=' + fM + '.' + fm + '.0' + } else { + from = '>=' + from + } -var noop = function () {} -var ancient = /^v?\.0/.test(process.version) + if (isX(tM)) { + to = '' + } else if (isX(tm)) { + to = '<' + (+tM + 1) + '.0.0' + } else if (isX(tp)) { + to = '<' + tM + '.' + (+tm + 1) + '.0' + } else if (tpr) { + to = '<=' + tM + '.' + tm + '.' + tp + '-' + tpr + } else { + to = '<=' + to + } -var isFn = function (fn) { - return typeof fn === 'function' + return (from + ' ' + to).trim() } -var isFS = function (stream) { - if (!ancient) return false // newer node version do not need to care about fs is a special way - if (!fs) return false // browser - return (stream instanceof (fs.ReadStream || noop) || stream instanceof (fs.WriteStream || noop)) && isFn(stream.close) -} +// if ANY of the sets match ALL of its comparators, then pass +Range.prototype.test = function (version) { + if (!version) { + return false + } -var isRequest = function (stream) { - return stream.setHeader && isFn(stream.abort) -} + if (typeof version === 'string') { + version = new SemVer(version, this.options) + } -var destroyer = function (stream, reading, writing, callback) { - callback = once(callback) + for (var i = 0; i < this.set.length; i++) { + if (testSet(this.set[i], version, this.options)) { + return true + } + } + return false +} - var closed = false - stream.on('close', function () { - closed = true - }) +function testSet (set, version, options) { + for (var i = 0; i < set.length; i++) { + if (!set[i].test(version)) { + return false + } + } - eos(stream, {readable: reading, writable: writing}, function (err) { - if (err) return callback(err) - closed = true - callback() - }) + if (version.prerelease.length && !options.includePrerelease) { + // Find the set of versions that are allowed to have prereleases + // For example, ^1.2.3-pr.1 desugars to >=1.2.3-pr.1 <2.0.0 + // That should allow `1.2.3-pr.2` to pass. + // However, `1.2.4-alpha.notready` should NOT be allowed, + // even though it's within the range set by the comparators. + for (i = 0; i < set.length; i++) { + debug(set[i].semver) + if (set[i].semver === ANY) { + continue + } - var destroyed = false - return function (err) { - if (closed) return - if (destroyed) return - destroyed = true + if (set[i].semver.prerelease.length > 0) { + var allowed = set[i].semver + if (allowed.major === version.major && + allowed.minor === version.minor && + allowed.patch === version.patch) { + return true + } + } + } - if (isFS(stream)) return stream.close(noop) // use close for fs streams to avoid fd leaks - if (isRequest(stream)) return stream.abort() // request.destroy just do .end - .abort is what we want + // Version has a -pre, but it's not one of the ones we like. + return false + } - if (isFn(stream.destroy)) return stream.destroy() + return true +} - callback(err || new Error('stream was destroyed')) +exports.satisfies = satisfies +function satisfies (version, range, options) { + try { + range = new Range(range, options) + } catch (er) { + return false } + return range.test(version) } -var call = function (fn) { - fn() +exports.maxSatisfying = maxSatisfying +function maxSatisfying (versions, range, options) { + var max = null + var maxSV = null + try { + var rangeObj = new Range(range, options) + } catch (er) { + return null + } + versions.forEach(function (v) { + if (rangeObj.test(v)) { + // satisfies(v, range, options) + if (!max || maxSV.compare(v) === -1) { + // compare(max, v, true) + max = v + maxSV = new SemVer(max, options) + } + } + }) + return max } -var pipe = function (from, to) { - return from.pipe(to) +exports.minSatisfying = minSatisfying +function minSatisfying (versions, range, options) { + var min = null + var minSV = null + try { + var rangeObj = new Range(range, options) + } catch (er) { + return null + } + versions.forEach(function (v) { + if (rangeObj.test(v)) { + // satisfies(v, range, options) + if (!min || minSV.compare(v) === 1) { + // compare(min, v, true) + min = v + minSV = new SemVer(min, options) + } + } + }) + return min } -var pump = function () { - var streams = Array.prototype.slice.call(arguments) - var callback = isFn(streams[streams.length - 1] || noop) && streams.pop() || noop +exports.minVersion = minVersion +function minVersion (range, loose) { + range = new Range(range, loose) - if (Array.isArray(streams[0])) streams = streams[0] - if (streams.length < 2) throw new Error('pump requires two streams per minimum') + var minver = new SemVer('0.0.0') + if (range.test(minver)) { + return minver + } - var error - var destroys = streams.map(function (stream, i) { - var reading = i < streams.length - 1 - var writing = i > 0 - return destroyer(stream, reading, writing, function (err) { - if (!error) error = err - if (err) destroys.forEach(call) - if (reading) return - destroys.forEach(call) - callback(error) - }) - }) + minver = new SemVer('0.0.0-0') + if (range.test(minver)) { + return minver + } - return streams.reduce(pipe) -} + minver = null + for (var i = 0; i < range.set.length; ++i) { + var comparators = range.set[i] -module.exports = pump + comparators.forEach(function (comparator) { + // Clone to avoid manipulating the comparator's semver object. + var compver = new SemVer(comparator.semver.version) + switch (comparator.operator) { + case '>': + if (compver.prerelease.length === 0) { + compver.patch++ + } else { + compver.prerelease.push(0) + } + compver.raw = compver.format() + /* fallthrough */ + case '': + case '>=': + if (!minver || gt(minver, compver)) { + minver = compver + } + break + case '<': + case '<=': + /* Ignore maximum versions */ + break + /* istanbul ignore next */ + default: + throw new Error('Unexpected operation: ' + comparator.operator) + } + }) + } + if (minver && range.test(minver)) { + return minver + } -/***/ }), -/* 153 */ -/***/ (function(module, exports, __webpack_require__) { + return null +} -var once = __webpack_require__(52); +exports.validRange = validRange +function validRange (range, options) { + try { + // Return '*' instead of '' so that truthiness works. + // This will throw if it's invalid anyway + return new Range(range, options).range || '*' + } catch (er) { + return null + } +} -var noop = function() {}; +// Determine if version is less than all the versions possible in the range +exports.ltr = ltr +function ltr (version, range, options) { + return outside(version, range, '<', options) +} -var isRequest = function(stream) { - return stream.setHeader && typeof stream.abort === 'function'; -}; +// Determine if version is greater than all the versions possible in the range. +exports.gtr = gtr +function gtr (version, range, options) { + return outside(version, range, '>', options) +} -var isChildProcess = function(stream) { - return stream.stdio && Array.isArray(stream.stdio) && stream.stdio.length === 3 -}; +exports.outside = outside +function outside (version, range, hilo, options) { + version = new SemVer(version, options) + range = new Range(range, options) -var eos = function(stream, opts, callback) { - if (typeof opts === 'function') return eos(stream, null, opts); - if (!opts) opts = {}; + var gtfn, ltefn, ltfn, comp, ecomp + switch (hilo) { + case '>': + gtfn = gt + ltefn = lte + ltfn = lt + comp = '>' + ecomp = '>=' + break + case '<': + gtfn = lt + ltefn = gte + ltfn = gt + comp = '<' + ecomp = '<=' + break + default: + throw new TypeError('Must provide a hilo val of "<" or ">"') + } - callback = once(callback || noop); + // If it satisifes the range it is not outside + if (satisfies(version, range, options)) { + return false + } - var ws = stream._writableState; - var rs = stream._readableState; - var readable = opts.readable || (opts.readable !== false && stream.readable); - var writable = opts.writable || (opts.writable !== false && stream.writable); + // From now on, variable terms are as if we're in "gtr" mode. + // but note that everything is flipped for the "ltr" function. - var onlegacyfinish = function() { - if (!stream.writable) onfinish(); - }; + for (var i = 0; i < range.set.length; ++i) { + var comparators = range.set[i] - var onfinish = function() { - writable = false; - if (!readable) callback.call(stream); - }; + var high = null + var low = null - var onend = function() { - readable = false; - if (!writable) callback.call(stream); - }; + comparators.forEach(function (comparator) { + if (comparator.semver === ANY) { + comparator = new Comparator('>=0.0.0') + } + high = high || comparator + low = low || comparator + if (gtfn(comparator.semver, high.semver, options)) { + high = comparator + } else if (ltfn(comparator.semver, low.semver, options)) { + low = comparator + } + }) - var onexit = function(exitCode) { - callback.call(stream, exitCode ? new Error('exited with error code: ' + exitCode) : null); - }; + // If the edge version comparator has a operator then our version + // isn't outside it + if (high.operator === comp || high.operator === ecomp) { + return false + } - var onerror = function(err) { - callback.call(stream, err); - }; + // If the lowest version comparator has an operator and our version + // is less than it then it isn't higher than the range + if ((!low.operator || low.operator === comp) && + ltefn(version, low.semver)) { + return false + } else if (low.operator === ecomp && ltfn(version, low.semver)) { + return false + } + } + return true +} - var onclose = function() { - if (readable && !(rs && rs.ended)) return callback.call(stream, new Error('premature close')); - if (writable && !(ws && ws.ended)) return callback.call(stream, new Error('premature close')); - }; +exports.prerelease = prerelease +function prerelease (version, options) { + var parsed = parse(version, options) + return (parsed && parsed.prerelease.length) ? parsed.prerelease : null +} - var onrequest = function() { - stream.req.on('finish', onfinish); - }; +exports.intersects = intersects +function intersects (r1, r2, options) { + r1 = new Range(r1, options) + r2 = new Range(r2, options) + return r1.intersects(r2) +} - if (isRequest(stream)) { - stream.on('complete', onfinish); - stream.on('abort', onclose); - if (stream.req) onrequest(); - else stream.on('request', onrequest); - } else if (writable && !ws) { // legacy streams - stream.on('end', onlegacyfinish); - stream.on('close', onlegacyfinish); - } +exports.coerce = coerce +function coerce (version) { + if (version instanceof SemVer) { + return version + } - if (isChildProcess(stream)) stream.on('exit', onexit); + if (typeof version !== 'string') { + return null + } - stream.on('end', onend); - stream.on('finish', onfinish); - if (opts.error !== false) stream.on('error', onerror); - stream.on('close', onclose); + var match = version.match(re[COERCE]) - return function() { - stream.removeListener('complete', onfinish); - stream.removeListener('abort', onclose); - stream.removeListener('request', onrequest); - if (stream.req) stream.req.removeListener('finish', onfinish); - stream.removeListener('end', onlegacyfinish); - stream.removeListener('close', onlegacyfinish); - stream.removeListener('finish', onfinish); - stream.removeListener('exit', onexit); - stream.removeListener('end', onend); - stream.removeListener('error', onerror); - stream.removeListener('close', onclose); - }; -}; + if (match == null) { + return null + } -module.exports = eos; + return parse(match[1] + + '.' + (match[2] || '0') + + '.' + (match[3] || '0')) +} /***/ }), -/* 154 */ +/* 562 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const {PassThrough: PassThroughStream} = __webpack_require__(28); -module.exports = options => { - options = {...options}; +const processFn = (fn, options) => function (...args) { + const P = options.promiseModule; - const {array} = options; - let {encoding} = options; - const isBuffer = encoding === 'buffer'; - let objectMode = false; + return new P((resolve, reject) => { + if (options.multiArgs) { + args.push((...result) => { + if (options.errorFirst) { + if (result[0]) { + reject(result); + } else { + result.shift(); + resolve(result); + } + } else { + resolve(result); + } + }); + } else if (options.errorFirst) { + args.push((error, result) => { + if (error) { + reject(error); + } else { + resolve(result); + } + }); + } else { + args.push(resolve); + } - if (array) { - objectMode = !(encoding || isBuffer); - } else { - encoding = encoding || 'utf8'; - } + fn.apply(this, args); + }); +}; - if (isBuffer) { - encoding = null; +module.exports = (input, options) => { + options = Object.assign({ + exclude: [/.+(Sync|Stream)$/], + errorFirst: true, + promiseModule: Promise + }, options); + + const objType = typeof input; + if (!(input !== null && (objType === 'object' || objType === 'function'))) { + throw new TypeError(`Expected \`input\` to be a \`Function\` or \`Object\`, got \`${input === null ? 'null' : objType}\``); } - const stream = new PassThroughStream({objectMode}); + const filter = key => { + const match = pattern => typeof pattern === 'string' ? key === pattern : pattern.test(key); + return options.include ? options.include.some(match) : !options.exclude.some(match); + }; - if (encoding) { - stream.setEncoding(encoding); + let ret; + if (objType === 'function') { + ret = function (...args) { + return options.excludeMain ? input(...args) : processFn(input, options).apply(this, args); + }; + } else { + ret = Object.create(Object.getPrototypeOf(input)); } - let length = 0; - const chunks = []; + for (const key in input) { // eslint-disable-line guard-for-in + const property = input[key]; + ret[key] = typeof property === 'function' && filter(key) ? processFn(property, options) : property; + } - stream.on('data', chunk => { - chunks.push(chunk); + return ret; +}; - if (objectMode) { - length = chunks.length; - } else { - length += chunk.length; - } - }); - stream.getBufferedValue = () => { - if (array) { - return chunks; - } +/***/ }), +/* 563 */ +/***/ (function(module, exports, __webpack_require__) { - return isBuffer ? Buffer.concat(chunks, length) : chunks.join(''); - }; +"use strict"; - stream.getBufferedLength = () => length; - return stream; -}; +// detect either spaces or tabs but not both to properly handle tabs +// for indentation and spaces for alignment +const INDENT_RE = /^(?:( )+|\t+)/; +function getMostUsed(indents) { + let result = 0; + let maxUsed = 0; + let maxWeight = 0; -/***/ }), -/* 155 */ -/***/ (function(module, exports, __webpack_require__) { + for (const entry of indents) { + // TODO: use destructuring when targeting Node.js 6 + const key = entry[0]; + const val = entry[1]; -"use strict"; + const u = val[0]; + const w = val[1]; + if (u > maxUsed || (u === maxUsed && w > maxWeight)) { + maxUsed = u; + maxWeight = w; + result = Number(key); + } + } -const { PassThrough } = __webpack_require__(28); + return result; +} -module.exports = function (/*streams...*/) { - var sources = [] - var output = new PassThrough({objectMode: true}) +module.exports = str => { + if (typeof str !== 'string') { + throw new TypeError('Expected a string'); + } - output.setMaxListeners(0) + // used to see if tabs or spaces are the most used + let tabs = 0; + let spaces = 0; - output.add = add - output.isEmpty = isEmpty + // remember the size of previous line's indentation + let prev = 0; - output.on('unpipe', remove) + // remember how many indents/unindents as occurred for a given size + // and how much lines follow a given indentation + // + // indents = { + // 3: [1, 0], + // 4: [1, 5], + // 5: [1, 0], + // 12: [1, 0], + // } + const indents = new Map(); - Array.prototype.slice.call(arguments).forEach(add) + // pointer to the array of last used indent + let current; - return output + // whether the last action was an indent (opposed to an unindent) + let isIndent; - function add (source) { - if (Array.isArray(source)) { - source.forEach(add) - return this - } + for (const line of str.split(/\n/g)) { + if (!line) { + // ignore empty lines + continue; + } - sources.push(source); - source.once('end', remove.bind(null, source)) - source.once('error', output.emit.bind(output, 'error')) - source.pipe(output, {end: false}) - return this - } + let indent; + const matches = line.match(INDENT_RE); - function isEmpty () { - return sources.length == 0; - } + if (matches) { + indent = matches[0].length; - function remove (source) { - sources = sources.filter(function (it) { return it !== source }) - if (!sources.length && output.readable) { output.end() } - } -} + if (matches[1]) { + spaces++; + } else { + tabs++; + } + } else { + indent = 0; + } + const diff = indent - prev; + prev = indent; -/***/ }), -/* 156 */ -/***/ (function(module, exports, __webpack_require__) { + if (diff) { + // an indent or unindent has been detected -"use strict"; + isIndent = diff > 0; -const mergePromiseProperty = (spawned, promise, property) => { - // Starting the main `promise` is deferred to avoid consuming streams - const value = typeof promise === 'function' ? - (...args) => promise()[property](...args) : - promise[property].bind(promise); + current = indents.get(isIndent ? diff : -diff); - Object.defineProperty(spawned, property, { - value, - writable: true, - enumerable: false, - configurable: true - }); -}; + if (current) { + current[0]++; + } else { + current = [1, 0]; + indents.set(diff, current); + } + } else if (current) { + // if the last action was an indent, increment the weight + current[1] += Number(isIndent); + } + } -// The return value is a mixin of `childProcess` and `Promise` -const mergePromise = (spawned, promise) => { - mergePromiseProperty(spawned, promise, 'then'); - mergePromiseProperty(spawned, promise, 'catch'); + const amount = getMostUsed(indents); - // TODO: Remove the `if`-guard when targeting Node.js 10 - if (Promise.prototype.finally) { - mergePromiseProperty(spawned, promise, 'finally'); + let type; + let indent; + if (!amount) { + type = null; + indent = ''; + } else if (spaces >= tabs) { + type = 'space'; + indent = ' '.repeat(amount); + } else { + type = 'tab'; + indent = '\t'.repeat(amount); } - return spawned; + return { + amount, + type, + indent + }; }; -// Use promises instead of `child_process` events -const getSpawnedPromise = spawned => { - return new Promise((resolve, reject) => { - spawned.on('exit', (exitCode, signal) => { - resolve({exitCode, signal}); - }); - spawned.on('error', error => { - reject(error); - }); +/***/ }), +/* 564 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - if (spawned.stdin) { - spawned.stdin.on('error', error => { - reject(error); - }); - } - }); -}; +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "installInDir", function() { return installInDir; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "runScriptInPackage", function() { return runScriptInPackage; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "runScriptInPackageStreaming", function() { return runScriptInPackageStreaming; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "yarnWorkspacesInfo", function() { return yarnWorkspacesInfo; }); +/* harmony import */ var _child_process__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(565); +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ -module.exports = { - mergePromise, - getSpawnedPromise -}; +/** + * Install all dependencies in the given directory + */ +async function installInDir(directory, extraArgs = []) { + const options = ['install', '--non-interactive', ...extraArgs]; // We pass the mutex flag to ensure only one instance of yarn runs at any + // given time (e.g. to avoid conflicts). + + await Object(_child_process__WEBPACK_IMPORTED_MODULE_0__["spawn"])('yarn', options, { + cwd: directory + }); +} +/** + * Run script in the given directory + */ + +async function runScriptInPackage(script, args, pkg) { + const execOpts = { + cwd: pkg.path + }; + await Object(_child_process__WEBPACK_IMPORTED_MODULE_0__["spawn"])('yarn', ['run', script, ...args], execOpts); +} +/** + * Run script in the given directory + */ +function runScriptInPackageStreaming(script, args, pkg) { + const execOpts = { + cwd: pkg.path + }; + return Object(_child_process__WEBPACK_IMPORTED_MODULE_0__["spawnStreaming"])('yarn', ['run', script, ...args], execOpts, { + prefix: pkg.name + }); +} +async function yarnWorkspacesInfo(directory) { + const workspacesInfo = await Object(_child_process__WEBPACK_IMPORTED_MODULE_0__["spawn"])('yarn', ['workspaces', 'info', '--json'], { + cwd: directory, + stdio: 'pipe' + }); + const stdout = JSON.parse(workspacesInfo.stdout); + return JSON.parse(stdout.data); +} /***/ }), -/* 157 */ -/***/ (function(module, exports, __webpack_require__) { +/* 565 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "spawn", function() { return spawn; }); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "spawnStreaming", function() { return spawnStreaming; }); +/* harmony import */ var chalk__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2); +/* harmony import */ var chalk__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(chalk__WEBPACK_IMPORTED_MODULE_0__); +/* harmony import */ var execa__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(351); +/* harmony import */ var execa__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(execa__WEBPACK_IMPORTED_MODULE_1__); +/* harmony import */ var log_symbols__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(566); +/* harmony import */ var log_symbols__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(log_symbols__WEBPACK_IMPORTED_MODULE_2__); +/* harmony import */ var strong_log_transformer__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(571); +/* harmony import */ var strong_log_transformer__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(strong_log_transformer__WEBPACK_IMPORTED_MODULE_3__); +function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } -const SPACES_REGEXP = / +/g; - -const joinCommand = (file, args = []) => { - if (!Array.isArray(args)) { - return file; - } +function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(source, true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(source).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } - return [file, ...args].join(' '); -}; +function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } -// Allow spaces to be escaped by a backslash if not meant as a delimiter -const handleEscaping = (tokens, token, index) => { - if (index === 0) { - return [token]; - } +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ - const previousToken = tokens[tokens.length - 1]; - if (previousToken.endsWith('\\')) { - return [...tokens.slice(0, -1), `${previousToken.slice(0, -1)} ${token}`]; - } - return [...tokens, token]; -}; -// Handle `execa.command()` -const parseCommand = command => { - return command - .trim() - .split(SPACES_REGEXP) - .reduce(handleEscaping, []); -}; -module.exports = { - joinCommand, - parseCommand -}; +function generateColors() { + const colorWheel = [chalk__WEBPACK_IMPORTED_MODULE_0___default.a.cyan, chalk__WEBPACK_IMPORTED_MODULE_0___default.a.magenta, chalk__WEBPACK_IMPORTED_MODULE_0___default.a.blue, chalk__WEBPACK_IMPORTED_MODULE_0___default.a.yellow, chalk__WEBPACK_IMPORTED_MODULE_0___default.a.green]; + const count = colorWheel.length; + let children = 0; + return () => colorWheel[children++ % count]; +} +function spawn(command, args, opts) { + return execa__WEBPACK_IMPORTED_MODULE_1___default()(command, args, _objectSpread({ + stdio: 'inherit', + preferLocal: true + }, opts)); +} +const nextColor = generateColors(); +function spawnStreaming(command, args, opts, { + prefix +}) { + const spawned = execa__WEBPACK_IMPORTED_MODULE_1___default()(command, args, _objectSpread({ + stdio: ['ignore', 'pipe', 'pipe'], + preferLocal: true + }, opts)); + const color = nextColor(); + const prefixedStdout = strong_log_transformer__WEBPACK_IMPORTED_MODULE_3___default()({ + tag: `${color.bold(prefix)}:` + }); + const prefixedStderr = strong_log_transformer__WEBPACK_IMPORTED_MODULE_3___default()({ + mergeMultiline: true, + tag: `${log_symbols__WEBPACK_IMPORTED_MODULE_2___default.a.error} ${color.bold(prefix)}:` + }); + spawned.stdout.pipe(prefixedStdout).pipe(process.stdout); + spawned.stderr.pipe(prefixedStderr).pipe(process.stderr); + return spawned; +} /***/ }), -/* 158 */ +/* 566 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const chalk = __webpack_require__(159); +const chalk = __webpack_require__(567); const isSupported = process.platform !== 'win32' || process.env.CI || process.env.TERM === 'xterm-256color'; @@ -21599,16 +56674,16 @@ module.exports = isSupported ? main : fallbacks; /***/ }), -/* 159 */ +/* 567 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const escapeStringRegexp = __webpack_require__(3); -const ansiStyles = __webpack_require__(160); -const stdoutColor = __webpack_require__(161).stdout; +const ansiStyles = __webpack_require__(568); +const stdoutColor = __webpack_require__(569).stdout; -const template = __webpack_require__(162); +const template = __webpack_require__(570); const isSimpleWindowsTerm = process.platform === 'win32' && !(process.env.TERM || '').toLowerCase().startsWith('xterm'); @@ -21834,7 +56909,7 @@ module.exports.default = module.exports; // For TypeScript /***/ }), -/* 160 */ +/* 568 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -22007,7 +57082,7 @@ Object.defineProperty(module, 'exports', { /* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(5)(module))) /***/ }), -/* 161 */ +/* 569 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -22149,7 +57224,7 @@ module.exports = { /***/ }), -/* 162 */ +/* 570 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -22284,7 +57359,7 @@ module.exports = (chalk, tmp) => { /***/ }), -/* 163 */ +/* 571 */ /***/ (function(module, exports, __webpack_require__) { // Copyright IBM Corp. 2014,2018. All Rights Reserved. @@ -22292,12 +57367,12 @@ module.exports = (chalk, tmp) => { // This file is licensed under the Apache License 2.0. // License text available at https://opensource.org/licenses/Apache-2.0 -module.exports = __webpack_require__(164); -module.exports.cli = __webpack_require__(168); +module.exports = __webpack_require__(572); +module.exports.cli = __webpack_require__(576); /***/ }), -/* 164 */ +/* 572 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -22308,13 +57383,13 @@ module.exports.cli = __webpack_require__(168); -var stream = __webpack_require__(28); +var stream = __webpack_require__(27); var util = __webpack_require__(29); var fs = __webpack_require__(23); -var through = __webpack_require__(165); -var duplexer = __webpack_require__(166); -var StringDecoder = __webpack_require__(167).StringDecoder; +var through = __webpack_require__(573); +var duplexer = __webpack_require__(574); +var StringDecoder = __webpack_require__(575).StringDecoder; module.exports = Logger; @@ -22503,10 +57578,10 @@ function lineMerger(host) { /***/ }), -/* 165 */ +/* 573 */ /***/ (function(module, exports, __webpack_require__) { -var Stream = __webpack_require__(28) +var Stream = __webpack_require__(27) // through // @@ -22617,10 +57692,10 @@ function through (write, end, opts) { /***/ }), -/* 166 */ +/* 574 */ /***/ (function(module, exports, __webpack_require__) { -var Stream = __webpack_require__(28) +var Stream = __webpack_require__(27) var writeMethods = ["write", "end", "destroy"] var readMethods = ["resume", "pause"] var readEvents = ["data", "close"] @@ -22710,13 +57785,13 @@ function duplex(writer, reader) { /***/ }), -/* 167 */ +/* 575 */ /***/ (function(module, exports) { module.exports = require("string_decoder"); /***/ }), -/* 168 */ +/* 576 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -22727,11 +57802,11 @@ module.exports = require("string_decoder"); -var minimist = __webpack_require__(169); +var minimist = __webpack_require__(577); var path = __webpack_require__(16); -var Logger = __webpack_require__(164); -var pkg = __webpack_require__(170); +var Logger = __webpack_require__(572); +var pkg = __webpack_require__(578); module.exports = cli; @@ -22785,7 +57860,7 @@ function usage($0, p) { /***/ }), -/* 169 */ +/* 577 */ /***/ (function(module, exports) { module.exports = function (args, opts) { @@ -23027,29 +58102,29 @@ function isNumber (x) { /***/ }), -/* 170 */ +/* 578 */ /***/ (function(module) { module.exports = JSON.parse("{\"name\":\"strong-log-transformer\",\"version\":\"2.1.0\",\"description\":\"Stream transformer that prefixes lines with timestamps and other things.\",\"author\":\"Ryan Graham \",\"license\":\"Apache-2.0\",\"repository\":{\"type\":\"git\",\"url\":\"git://github.com/strongloop/strong-log-transformer\"},\"keywords\":[\"logging\",\"streams\"],\"bugs\":{\"url\":\"https://github.com/strongloop/strong-log-transformer/issues\"},\"homepage\":\"https://github.com/strongloop/strong-log-transformer\",\"directories\":{\"test\":\"test\"},\"bin\":{\"sl-log-transformer\":\"bin/sl-log-transformer.js\"},\"main\":\"index.js\",\"scripts\":{\"test\":\"tap --100 test/test-*\"},\"dependencies\":{\"duplexer\":\"^0.1.1\",\"minimist\":\"^1.2.0\",\"through\":\"^2.3.4\"},\"devDependencies\":{\"tap\":\"^12.0.1\"},\"engines\":{\"node\":\">=4\"}}"); /***/ }), -/* 171 */ +/* 579 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "workspacePackagePaths", function() { return workspacePackagePaths; }); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "copyWorkspacePackages", function() { return copyWorkspacePackages; }); -/* harmony import */ var glob__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(37); +/* harmony import */ var glob__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(502); /* harmony import */ var glob__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(glob__WEBPACK_IMPORTED_MODULE_0__); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(16); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_1__); /* harmony import */ var util__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(29); /* harmony import */ var util__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(util__WEBPACK_IMPORTED_MODULE_2__); -/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(172); +/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(580); /* harmony import */ var _fs__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(20); -/* harmony import */ var _package_json__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(55); -/* harmony import */ var _projects__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(36); +/* harmony import */ var _package_json__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(517); +/* harmony import */ var _projects__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(501); /* * Licensed to Elasticsearch B.V. under one or more contributor * license agreements. See the NOTICE file distributed with @@ -23104,7 +58179,9 @@ async function workspacePackagePaths(rootPath) { return workspaceProjectsPaths; } async function copyWorkspacePackages(rootPath) { - const projectPaths = Object(_config__WEBPACK_IMPORTED_MODULE_3__["getProjectPaths"])(rootPath, {}); + const projectPaths = Object(_config__WEBPACK_IMPORTED_MODULE_3__["getProjectPaths"])({ + rootPath + }); const projects = await Object(_projects__WEBPACK_IMPORTED_MODULE_6__["getProjects"])(rootPath, projectPaths); for (const project of projects.values()) { @@ -23115,9951 +58192,10682 @@ async function copyWorkspacePackages(rootPath) { } // Remove the symlink - await Object(_fs__WEBPACK_IMPORTED_MODULE_4__["unlink"])(dest); // Copy in the package - - await Object(_fs__WEBPACK_IMPORTED_MODULE_4__["copyDirectory"])(project.path, dest); - } -} - -function packagesFromGlobPattern({ - pattern, - rootPath -}) { - const globOptions = { - cwd: rootPath, - // Should throw in case of unusual errors when reading the file system - strict: true, - // Always returns absolute paths for matched files - absolute: true, - // Do not match ** against multiple filenames - // (This is only specified because we currently don't have a need for it.) - noglobstar: true - }; - return glob(path__WEBPACK_IMPORTED_MODULE_1___default.a.join(pattern, 'package.json'), globOptions); -} - -/***/ }), -/* 172 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { - -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "getProjectPaths", function() { return getProjectPaths; }); -/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(16); -/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_0__); -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - - -/** - * Returns all the paths where plugins are located - */ -function getProjectPaths(rootPath, options = {}) { - const skipKibanaPlugins = Boolean(options['skip-kibana-plugins']); - const ossOnly = Boolean(options.oss); - const projectPaths = [rootPath, Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'packages/*')]; // This is needed in order to install the dependencies for the declared - // plugin functional used in the selenium functional tests. - // As we are now using the webpack dll for the client vendors dependencies - // when we run the plugin functional tests against the distributable - // dependencies used by such plugins like @eui, react and react-dom can't - // be loaded from the dll as the context is different from the one declared - // into the webpack dll reference plugin. - // In anyway, have a plugin declaring their own dependencies is the - // correct and the expect behavior. - - projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'test/plugin_functional/plugins/*')); - projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'test/interpreter_functional/plugins/*')); - projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'examples/*')); - - if (!ossOnly) { - projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'x-pack')); - projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'x-pack/legacy/plugins/*')); - } - - if (!skipKibanaPlugins) { - projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, '../kibana-extra/*')); - projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, '../kibana-extra/*/packages/*')); - projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, '../kibana-extra/*/plugins/*')); - projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'plugins/*')); - projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'plugins/*/packages/*')); - projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'plugins/*/plugins/*')); - } - - return projectPaths; -} - -/***/ }), -/* 173 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { - -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "CleanCommand", function() { return CleanCommand; }); -/* harmony import */ var chalk__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2); -/* harmony import */ var chalk__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(chalk__WEBPACK_IMPORTED_MODULE_0__); -/* harmony import */ var del__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(174); -/* harmony import */ var del__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(del__WEBPACK_IMPORTED_MODULE_1__); -/* harmony import */ var ora__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(261); -/* harmony import */ var ora__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(ora__WEBPACK_IMPORTED_MODULE_2__); -/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(16); -/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_3__); -/* harmony import */ var _utils_fs__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(20); -/* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(34); -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - - - - - - -const CleanCommand = { - description: 'Remove the node_modules and target directories from all projects.', - name: 'clean', - - async run(projects) { - const toDelete = []; - - for (const project of projects.values()) { - if (await Object(_utils_fs__WEBPACK_IMPORTED_MODULE_4__["isDirectory"])(project.nodeModulesLocation)) { - toDelete.push({ - cwd: project.path, - pattern: Object(path__WEBPACK_IMPORTED_MODULE_3__["relative"])(project.path, project.nodeModulesLocation) - }); - } - - if (await Object(_utils_fs__WEBPACK_IMPORTED_MODULE_4__["isDirectory"])(project.targetLocation)) { - toDelete.push({ - cwd: project.path, - pattern: Object(path__WEBPACK_IMPORTED_MODULE_3__["relative"])(project.path, project.targetLocation) - }); - } - - const { - extraPatterns - } = project.getCleanConfig(); - - if (extraPatterns) { - toDelete.push({ - cwd: project.path, - pattern: extraPatterns - }); - } - } - - if (toDelete.length === 0) { - _utils_log__WEBPACK_IMPORTED_MODULE_5__["log"].write(chalk__WEBPACK_IMPORTED_MODULE_0___default.a.bold.green('\n\nNothing to delete')); - } else { - _utils_log__WEBPACK_IMPORTED_MODULE_5__["log"].write(chalk__WEBPACK_IMPORTED_MODULE_0___default.a.bold.red('\n\nDeleting:\n')); - /** - * In order to avoid patterns like `/build` in packages from accidentally - * impacting files outside the package we use `process.chdir()` to change - * the cwd to the package and execute `del()` without the `force` option - * so it will check that each file being deleted is within the package. - * - * `del()` does support a `cwd` option, but it's only for resolving the - * patterns and does not impact the cwd check. - */ - - const originalCwd = process.cwd(); - - try { - for (const _ref of toDelete) { - const { - pattern, - cwd - } = _ref; - process.chdir(cwd); - const promise = del__WEBPACK_IMPORTED_MODULE_1___default()(pattern); - ora__WEBPACK_IMPORTED_MODULE_2___default.a.promise(promise, Object(path__WEBPACK_IMPORTED_MODULE_3__["relative"])(originalCwd, Object(path__WEBPACK_IMPORTED_MODULE_3__["join"])(cwd, String(pattern)))); - await promise; - } - } finally { - process.chdir(originalCwd); - } - } - } - -}; - -/***/ }), -/* 174 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - -const {promisify} = __webpack_require__(29); -const path = __webpack_require__(16); -const globby = __webpack_require__(175); -const isGlob = __webpack_require__(187); -const slash = __webpack_require__(248); -const gracefulFs = __webpack_require__(250); -const isPathCwd = __webpack_require__(254); -const isPathInside = __webpack_require__(255); -const rimraf = __webpack_require__(256); -const pMap = __webpack_require__(257); - -const rimrafP = promisify(rimraf); - -const rimrafOptions = { - glob: false, - unlink: gracefulFs.unlink, - unlinkSync: gracefulFs.unlinkSync, - chmod: gracefulFs.chmod, - chmodSync: gracefulFs.chmodSync, - stat: gracefulFs.stat, - statSync: gracefulFs.statSync, - lstat: gracefulFs.lstat, - lstatSync: gracefulFs.lstatSync, - rmdir: gracefulFs.rmdir, - rmdirSync: gracefulFs.rmdirSync, - readdir: gracefulFs.readdir, - readdirSync: gracefulFs.readdirSync -}; - -function safeCheck(file, cwd) { - if (isPathCwd(file)) { - throw new Error('Cannot delete the current working directory. Can be overridden with the `force` option.'); - } - - if (!isPathInside(file, cwd)) { - throw new Error('Cannot delete files/directories outside the current working directory. Can be overridden with the `force` option.'); - } -} - -function normalizePatterns(patterns) { - patterns = Array.isArray(patterns) ? patterns : [patterns]; - - patterns = patterns.map(pattern => { - if (process.platform === 'win32' && isGlob(pattern) === false) { - return slash(pattern); - } - - return pattern; - }); - - return patterns; -} - -module.exports = async (patterns, {force, dryRun, cwd = process.cwd(), ...options} = {}) => { - options = { - expandDirectories: false, - onlyFiles: false, - followSymbolicLinks: false, - cwd, - ...options - }; - - patterns = normalizePatterns(patterns); - - const files = (await globby(patterns, options)) - .sort((a, b) => b.localeCompare(a)); + await Object(_fs__WEBPACK_IMPORTED_MODULE_4__["unlink"])(dest); // Copy in the package - const mapper = async file => { - file = path.resolve(cwd, file); + await Object(_fs__WEBPACK_IMPORTED_MODULE_4__["copyDirectory"])(project.path, dest); + } +} - if (!force) { - safeCheck(file, cwd); - } +function packagesFromGlobPattern({ + pattern, + rootPath +}) { + const globOptions = { + cwd: rootPath, + // Should throw in case of unusual errors when reading the file system + strict: true, + // Always returns absolute paths for matched files + absolute: true, + // Do not match ** against multiple filenames + // (This is only specified because we currently don't have a need for it.) + noglobstar: true + }; + return glob(path__WEBPACK_IMPORTED_MODULE_1___default.a.join(pattern, 'package.json'), globOptions); +} - if (!dryRun) { - await rimrafP(file, rimrafOptions); - } +/***/ }), +/* 580 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - return file; - }; +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "getProjectPaths", function() { return getProjectPaths; }); +/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(16); +/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_0__); +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ - const removedFiles = await pMap(files, mapper, options); - removedFiles.sort((a, b) => a.localeCompare(b)); +/** + * Returns all the paths where plugins are located + */ +function getProjectPaths({ + rootPath, + ossOnly, + skipKibanaPlugins +}) { + const projectPaths = [rootPath, Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'packages/*')]; // This is needed in order to install the dependencies for the declared + // plugin functional used in the selenium functional tests. + // As we are now using the webpack dll for the client vendors dependencies + // when we run the plugin functional tests against the distributable + // dependencies used by such plugins like @eui, react and react-dom can't + // be loaded from the dll as the context is different from the one declared + // into the webpack dll reference plugin. + // In anyway, have a plugin declaring their own dependencies is the + // correct and the expect behavior. - return removedFiles; -}; + projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'test/plugin_functional/plugins/*')); + projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'test/interpreter_functional/plugins/*')); + projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'examples/*')); -module.exports.sync = (patterns, {force, dryRun, cwd = process.cwd(), ...options} = {}) => { - options = { - expandDirectories: false, - onlyFiles: false, - followSymbolicLinks: false, - cwd, - ...options - }; + if (!ossOnly) { + projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'x-pack')); + projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'x-pack/plugins/*')); + projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'x-pack/legacy/plugins/*')); + } - patterns = normalizePatterns(patterns); + if (!skipKibanaPlugins) { + projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, '../kibana-extra/*')); + projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, '../kibana-extra/*/packages/*')); + projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, '../kibana-extra/*/plugins/*')); + projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'plugins/*')); + projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'plugins/*/packages/*')); + projectPaths.push(Object(path__WEBPACK_IMPORTED_MODULE_0__["resolve"])(rootPath, 'plugins/*/plugins/*')); + } - const files = globby.sync(patterns, options) - .sort((a, b) => b.localeCompare(a)); + return projectPaths; +} - const removedFiles = files.map(file => { - file = path.resolve(cwd, file); +/***/ }), +/* 581 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { - if (!force) { - safeCheck(file, cwd); - } +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "getAllChecksums", function() { return getAllChecksums; }); +/* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(23); +/* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(fs__WEBPACK_IMPORTED_MODULE_0__); +/* harmony import */ var crypto__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(582); +/* harmony import */ var crypto__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(crypto__WEBPACK_IMPORTED_MODULE_1__); +/* harmony import */ var util__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(29); +/* harmony import */ var util__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(util__WEBPACK_IMPORTED_MODULE_2__); +/* harmony import */ var execa__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(351); +/* harmony import */ var execa__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(execa__WEBPACK_IMPORTED_MODULE_3__); +/* harmony import */ var _yarn_lock__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(583); +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ - if (!dryRun) { - rimraf.sync(file, rimrafOptions); - } - return file; - }); - removedFiles.sort((a, b) => a.localeCompare(b)); - return removedFiles; -}; +const statAsync = Object(util__WEBPACK_IMPORTED_MODULE_2__["promisify"])(fs__WEBPACK_IMPORTED_MODULE_0___default.a.stat); -/***/ }), -/* 175 */ -/***/ (function(module, exports, __webpack_require__) { +const projectBySpecificitySorter = (a, b) => b.path.length - a.path.length; +/** Get the changed files for a set of projects */ -"use strict"; -const fs = __webpack_require__(23); -const arrayUnion = __webpack_require__(176); -const merge2 = __webpack_require__(177); -const glob = __webpack_require__(37); -const fastGlob = __webpack_require__(178); -const dirGlob = __webpack_require__(244); -const gitignore = __webpack_require__(246); -const {FilterStream, UniqueStream} = __webpack_require__(249); +async function getChangesForProjects(projects, kbn, log) { + log.verbose('getting changed files'); + const { + stdout + } = await execa__WEBPACK_IMPORTED_MODULE_3___default()('git', ['ls-files', '-dmt', '--', ...Array.from(projects.values()).map(p => p.path)], { + cwd: kbn.getAbsolute() + }); + const output = stdout.trim(); + const unassignedChanges = new Map(); + + if (output) { + for (const line of output.split('\n')) { + const [tag, ...pathParts] = line.trim().split(' '); + const path = pathParts.join(' '); + + switch (tag) { + case 'M': + case 'C': + // for some reason ls-files returns deleted files as both deleted + // and modified, so make sure not to overwrite changes already + // tracked as "deleted" + if (unassignedChanges.get(path) !== 'deleted') { + unassignedChanges.set(path, 'modified'); + } -const DEFAULT_FILTER = () => false; + break; -const isNegative = pattern => pattern[0] === '!'; + case 'R': + unassignedChanges.set(path, 'deleted'); + break; -const assertPatternsInput = patterns => { - if (!patterns.every(pattern => typeof pattern === 'string')) { - throw new TypeError('Patterns must be a string or an array of strings'); - } -}; + case 'H': + case 'S': + case 'K': + case '?': + default: + log.warning(`unexpected modification status "${tag}" for ${path}, please report this!`); + unassignedChanges.set(path, 'invalid'); + break; + } + } + } -const checkCwdOption = (options = {}) => { - if (!options.cwd) { - return; - } + const sortedRelevantProjects = Array.from(projects.values()).sort(projectBySpecificitySorter); + const changesByProject = new Map(); - let stat; - try { - stat = fs.statSync(options.cwd); - } catch (_) { - return; - } + for (const project of sortedRelevantProjects) { + const ownChanges = new Map(); + const prefix = kbn.getRelative(project.path); - if (!stat.isDirectory()) { - throw new Error('The `cwd` option must be a path to a directory'); - } -}; + for (const [path, type] of unassignedChanges) { + if (path.startsWith(prefix)) { + ownChanges.set(path, type); + unassignedChanges.delete(path); + } + } -const getPathString = p => p.stats instanceof fs.Stats ? p.path : p; + log.verbose(`[${project.name}] found ${ownChanges.size} changes`); + changesByProject.set(project, ownChanges); + } -const generateGlobTasks = (patterns, taskOptions) => { - patterns = arrayUnion([].concat(patterns)); - assertPatternsInput(patterns); - checkCwdOption(taskOptions); + if (unassignedChanges.size) { + throw new Error(`unable to assign all change paths to a project: ${JSON.stringify(Array.from(unassignedChanges.entries()))}`); + } - const globTasks = []; + return changesByProject; +} +/** Get the latest commit sha for a project */ - taskOptions = { - ignore: [], - expandDirectories: true, - ...taskOptions - }; - for (const [index, pattern] of patterns.entries()) { - if (isNegative(pattern)) { - continue; - } +async function getLatestSha(project, kbn) { + const { + stdout + } = await execa__WEBPACK_IMPORTED_MODULE_3___default()('git', ['log', '-n', '1', '--pretty=format:%H', '--', project.path], { + cwd: kbn.getAbsolute() + }); + return stdout.trim() || undefined; +} +/** + * Get a list of the absolute dependencies of this project, as resolved + * in the yarn.lock file, does not include other projects in the workspace + * or their dependencies + */ - const ignore = patterns - .slice(index) - .filter(isNegative) - .map(pattern => pattern.slice(1)); - const options = { - ...taskOptions, - ignore: taskOptions.ignore.concat(ignore) - }; +function resolveDepsForProject(project, yarnLock, kbn, log) { + /** map of [name@range, name@resolved] */ + const resolved = new Map(); + const queue = Object.entries(project.allDependencies); - globTasks.push({pattern, options}); - } + while (queue.length) { + const [name, versionRange] = queue.shift(); + const req = `${name}@${versionRange}`; - return globTasks; -}; + if (resolved.has(req)) { + continue; + } -const globDirs = (task, fn) => { - let options = {}; - if (task.options.cwd) { - options.cwd = task.options.cwd; - } + if (!kbn.hasProject(name)) { + const pkg = yarnLock[req]; - if (Array.isArray(task.options.expandDirectories)) { - options = { - ...options, - files: task.options.expandDirectories - }; - } else if (typeof task.options.expandDirectories === 'object') { - options = { - ...options, - ...task.options.expandDirectories - }; - } + if (!pkg) { + log.warning('yarn.lock file is out of date, please run `yarn kbn bootstrap` to re-enable caching'); + return; + } - return fn(task.pattern, options); -}; + const res = `${name}@${pkg.version}`; + resolved.set(req, res); + const allDepsEntries = [...Object.entries(pkg.dependencies || {}), ...Object.entries(pkg.optionalDependencies || {})]; -const getPattern = (task, fn) => task.options.expandDirectories ? globDirs(task, fn) : [task.pattern]; + for (const [childName, childVersionRange] of allDepsEntries) { + queue.push([childName, childVersionRange]); + } + } + } -const getFilterSync = options => { - return options && options.gitignore ? - gitignore.sync({cwd: options.cwd, ignore: options.ignore}) : - DEFAULT_FILTER; -}; + return Array.from(resolved.values()).sort((a, b) => a.localeCompare(b)); +} +/** + * Get the checksum for a specific project in the workspace + */ -const globToTask = task => glob => { - const {options} = task; - if (options.ignore && Array.isArray(options.ignore) && options.expandDirectories) { - options.ignore = dirGlob.sync(options.ignore); - } - return { - pattern: glob, - options - }; -}; +async function getChecksum(project, changes, yarnLock, kbn, log) { + const sha = await getLatestSha(project, kbn); -module.exports = async (patterns, options) => { - const globTasks = generateGlobTasks(patterns, options); + if (sha) { + log.verbose(`[${project.name}] local sha:`, sha); + } - const getFilter = async () => { - return options && options.gitignore ? - gitignore({cwd: options.cwd, ignore: options.ignore}) : - DEFAULT_FILTER; - }; + if (Array.from(changes.values()).includes('invalid')) { + log.warning(`[${project.name}] unable to determine local changes, caching disabled`); + return; + } - const getTasks = async () => { - const tasks = await Promise.all(globTasks.map(async task => { - const globs = await getPattern(task, dirGlob); - return Promise.all(globs.map(globToTask(task))); - })); + const changesSummary = await Promise.all(Array.from(changes).sort((a, b) => a[0].localeCompare(b[0])).map(async ([path, type]) => { + if (type === 'deleted') { + return `${path}:deleted`; + } - return arrayUnion(...tasks); - }; + const stats = await statAsync(kbn.getAbsolute(path)); + log.verbose(`[${project.name}] modified time ${stats.mtimeMs} for ${path}`); + return `${path}:${stats.mtimeMs}`; + })); + const deps = await resolveDepsForProject(project, yarnLock, kbn, log); - const [filter, tasks] = await Promise.all([getFilter(), getTasks()]); - const paths = await Promise.all(tasks.map(task => fastGlob(task.pattern, task.options))); + if (!deps) { + return; + } - return arrayUnion(...paths).filter(path_ => !filter(getPathString(path_))); -}; + log.verbose(`[${project.name}] resolved %d deps`, deps.length); + const checksum = JSON.stringify({ + sha, + changes: changesSummary, + deps + }, null, 2); -module.exports.sync = (patterns, options) => { - const globTasks = generateGlobTasks(patterns, options); + if (process.env.BOOTSTRAP_CACHE_DEBUG_CHECKSUM) { + return checksum; + } - const tasks = globTasks.reduce((tasks, task) => { - const newTask = getPattern(task, dirGlob.sync).map(globToTask(task)); - return tasks.concat(newTask); - }, []); + const hash = crypto__WEBPACK_IMPORTED_MODULE_1___default.a.createHash('sha1'); + hash.update(checksum); + return hash.digest('hex'); +} +/** + * Calculate checksums for all projects in the workspace based on + * - last git commit to project directory + * - un-committed changes + * - resolved dependencies from yarn.lock referenced by project package.json + */ - const filter = getFilterSync(options); - return tasks.reduce( - (matches, task) => arrayUnion(matches, fastGlob.sync(task.pattern, task.options)), - [] - ).filter(path_ => !filter(path_)); -}; +async function getAllChecksums(kbn, log) { + const projects = kbn.getAllProjects(); + const changesByProject = await getChangesForProjects(projects, kbn, log); + const yarnLock = await Object(_yarn_lock__WEBPACK_IMPORTED_MODULE_4__["readYarnLock"])(kbn); + /** map of [project.name, cacheKey] */ -module.exports.stream = (patterns, options) => { - const globTasks = generateGlobTasks(patterns, options); + const cacheKeys = new Map(); + await Promise.all(Array.from(projects.values()).map(async project => { + cacheKeys.set(project.name, (await getChecksum(project, changesByProject.get(project), yarnLock, kbn, log))); + })); + return cacheKeys; +} - const tasks = globTasks.reduce((tasks, task) => { - const newTask = getPattern(task, dirGlob.sync).map(globToTask(task)); - return tasks.concat(newTask); - }, []); +/***/ }), +/* 582 */ +/***/ (function(module, exports) { - const filter = getFilterSync(options); - const filterStream = new FilterStream(p => !filter(p)); - const uniqueStream = new UniqueStream(); +module.exports = require("crypto"); - return merge2(tasks.map(task => fastGlob.stream(task.pattern, task.options))) - .pipe(filterStream) - .pipe(uniqueStream); -}; +/***/ }), +/* 583 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { -module.exports.generateGlobTasks = generateGlobTasks; +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "readYarnLock", function() { return readYarnLock; }); +/* harmony import */ var _yarnpkg_lockfile__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(584); +/* harmony import */ var _yarnpkg_lockfile__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_yarnpkg_lockfile__WEBPACK_IMPORTED_MODULE_0__); +/* harmony import */ var _utils_fs__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(20); +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +// @ts-ignore published types are worthless -module.exports.hasMagic = (patterns, options) => [] - .concat(patterns) - .some(pattern => glob.hasMagic(pattern, options)); -module.exports.gitignore = gitignore; +async function readYarnLock(kbn) { + try { + const contents = await Object(_utils_fs__WEBPACK_IMPORTED_MODULE_1__["readFile"])(kbn.getAbsolute('yarn.lock'), 'utf8'); + const yarnLock = Object(_yarnpkg_lockfile__WEBPACK_IMPORTED_MODULE_0__["parse"])(contents); + if (yarnLock.type === 'success') { + return yarnLock.object; + } -/***/ }), -/* 176 */ -/***/ (function(module, exports, __webpack_require__) { + throw new Error('unable to read yarn.lock file, please run `yarn kbn bootstrap`'); + } catch (error) { + if (error.code !== 'ENOENT') { + throw error; + } + } -"use strict"; + return {}; +} +/***/ }), +/* 584 */ +/***/ (function(module, exports, __webpack_require__) { -module.exports = (...arguments_) => { - return [...new Set([].concat(...arguments_))]; -}; +module.exports = +/******/ (function(modules) { // webpackBootstrap +/******/ // The module cache +/******/ var installedModules = {}; +/******/ +/******/ // The require function +/******/ function __webpack_require__(moduleId) { +/******/ +/******/ // Check if module is in cache +/******/ if(installedModules[moduleId]) { +/******/ return installedModules[moduleId].exports; +/******/ } +/******/ // Create a new module (and put it into the cache) +/******/ var module = installedModules[moduleId] = { +/******/ i: moduleId, +/******/ l: false, +/******/ exports: {} +/******/ }; +/******/ +/******/ // Execute the module function +/******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); +/******/ +/******/ // Flag the module as loaded +/******/ module.l = true; +/******/ +/******/ // Return the exports of the module +/******/ return module.exports; +/******/ } +/******/ +/******/ +/******/ // expose the modules object (__webpack_modules__) +/******/ __webpack_require__.m = modules; +/******/ +/******/ // expose the module cache +/******/ __webpack_require__.c = installedModules; +/******/ +/******/ // identity function for calling harmony imports with the correct context +/******/ __webpack_require__.i = function(value) { return value; }; +/******/ +/******/ // define getter function for harmony exports +/******/ __webpack_require__.d = function(exports, name, getter) { +/******/ if(!__webpack_require__.o(exports, name)) { +/******/ Object.defineProperty(exports, name, { +/******/ configurable: false, +/******/ enumerable: true, +/******/ get: getter +/******/ }); +/******/ } +/******/ }; +/******/ +/******/ // getDefaultExport function for compatibility with non-harmony modules +/******/ __webpack_require__.n = function(module) { +/******/ var getter = module && module.__esModule ? +/******/ function getDefault() { return module['default']; } : +/******/ function getModuleExports() { return module; }; +/******/ __webpack_require__.d(getter, 'a', getter); +/******/ return getter; +/******/ }; +/******/ +/******/ // Object.prototype.hasOwnProperty.call +/******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; +/******/ +/******/ // __webpack_public_path__ +/******/ __webpack_require__.p = ""; +/******/ +/******/ // Load entry module and return exports +/******/ return __webpack_require__(__webpack_require__.s = 14); +/******/ }) +/************************************************************************/ +/******/ ([ +/* 0 */ +/***/ (function(module, exports) { +module.exports = __webpack_require__(16); /***/ }), -/* 177 */ +/* 1 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -/* - * merge2 - * https://github.com/teambition/merge2 - * - * Copyright (c) 2014-2016 Teambition - * Licensed under the MIT license. - */ -const Stream = __webpack_require__(28) -const PassThrough = Stream.PassThrough -const slice = Array.prototype.slice -module.exports = merge2 +exports.__esModule = true; -function merge2 () { - const streamsQueue = [] - let merging = false - const args = slice.call(arguments) - let options = args[args.length - 1] +var _promise = __webpack_require__(173); - if (options && !Array.isArray(options) && options.pipe == null) args.pop() - else options = {} +var _promise2 = _interopRequireDefault(_promise); - const doEnd = options.end !== false - if (options.objectMode == null) options.objectMode = true - if (options.highWaterMark == null) options.highWaterMark = 64 * 1024 - const mergedStream = PassThrough(options) +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - function addStream () { - for (let i = 0, len = arguments.length; i < len; i++) { - streamsQueue.push(pauseStreams(arguments[i], options)) - } - mergeStream() - return this - } +exports.default = function (fn) { + return function () { + var gen = fn.apply(this, arguments); + return new _promise2.default(function (resolve, reject) { + function step(key, arg) { + try { + var info = gen[key](arg); + var value = info.value; + } catch (error) { + reject(error); + return; + } - function mergeStream () { - if (merging) return - merging = true + if (info.done) { + resolve(value); + } else { + return _promise2.default.resolve(value).then(function (value) { + step("next", value); + }, function (err) { + step("throw", err); + }); + } + } - let streams = streamsQueue.shift() - if (!streams) { - process.nextTick(endStream) - return - } - if (!Array.isArray(streams)) streams = [streams] + return step("next"); + }); + }; +}; - let pipesCount = streams.length + 1 +/***/ }), +/* 2 */ +/***/ (function(module, exports) { - function next () { - if (--pipesCount > 0) return - merging = false - mergeStream() - } +module.exports = __webpack_require__(29); - function pipe (stream) { - function onend () { - stream.removeListener('merge2UnpipeEnd', onend) - stream.removeListener('end', onend) - next() - } - // skip ended stream - if (stream._readableState.endEmitted) return next() +/***/ }), +/* 3 */ +/***/ (function(module, exports) { - stream.on('merge2UnpipeEnd', onend) - stream.on('end', onend) - stream.pipe(mergedStream, { end: false }) - // compatible for old stream - stream.resume() - } +module.exports = __webpack_require__(23); - for (let i = 0; i < streams.length; i++) pipe(streams[i]) +/***/ }), +/* 4 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; - next() - } - function endStream () { - merging = false - // emit 'queueDrain' when all streams merged. - mergedStream.emit('queueDrain') - return doEnd && mergedStream.end() +Object.defineProperty(exports, "__esModule", { + value: true +}); +class MessageError extends Error { + constructor(msg, code) { + super(msg); + this.code = code; } - mergedStream.setMaxListeners(0) - mergedStream.add = addStream - mergedStream.on('unpipe', function (stream) { - stream.emit('merge2UnpipeEnd') - }) - - if (args.length) addStream.apply(null, args) - return mergedStream } -// check and pause streams for pipe. -function pauseStreams (streams, options) { - if (!Array.isArray(streams)) { - // Backwards-compat with old-style streams - if (!streams._readableState && streams.pipe) streams = streams.pipe(PassThrough(options)) - if (!streams._readableState || !streams.pause || !streams.pipe) { - throw new Error('Only readable stream can be merged.') - } - streams.pause() - } else { - for (let i = 0, len = streams.length; i < len; i++) streams[i] = pauseStreams(streams[i], options) +exports.MessageError = MessageError; +class ProcessSpawnError extends MessageError { + constructor(msg, code, process) { + super(msg, code); + this.process = process; } - return streams + } +exports.ProcessSpawnError = ProcessSpawnError; +class SecurityError extends MessageError {} -/***/ }), -/* 178 */ -/***/ (function(module, exports, __webpack_require__) { +exports.SecurityError = SecurityError; +class ProcessTermError extends MessageError {} -"use strict"; - -const taskManager = __webpack_require__(179); -const async_1 = __webpack_require__(207); -const stream_1 = __webpack_require__(240); -const sync_1 = __webpack_require__(241); -const settings_1 = __webpack_require__(243); -const utils = __webpack_require__(180); -function FastGlob(source, options) { - try { - assertPatternsInput(source); - } - catch (error) { - return Promise.reject(error); - } - const works = getWorks(source, async_1.default, options); - return Promise.all(works).then(utils.array.flatten); -} -(function (FastGlob) { - function sync(source, options) { - assertPatternsInput(source); - const works = getWorks(source, sync_1.default, options); - return utils.array.flatten(works); - } - FastGlob.sync = sync; - function stream(source, options) { - assertPatternsInput(source); - const works = getWorks(source, stream_1.default, options); - /** - * The stream returned by the provider cannot work with an asynchronous iterator. - * To support asynchronous iterators, regardless of the number of tasks, we always multiplex streams. - * This affects performance (+25%). I don't see best solution right now. - */ - return utils.stream.merge(works); - } - FastGlob.stream = stream; - function generateTasks(source, options) { - assertPatternsInput(source); - const patterns = [].concat(source); - const settings = new settings_1.default(options); - return taskManager.generate(patterns, settings); - } - FastGlob.generateTasks = generateTasks; -})(FastGlob || (FastGlob = {})); -function getWorks(source, _Provider, options) { - const patterns = [].concat(source); - const settings = new settings_1.default(options); - const tasks = taskManager.generate(patterns, settings); - const provider = new _Provider(settings); - return tasks.map(provider.read, provider); -} -function assertPatternsInput(source) { - if ([].concat(source).every(isString)) { - return; - } - throw new TypeError('Patterns must be a string or an array of strings'); -} -function isString(source) { - /* tslint:disable-next-line strict-type-predicates */ - return typeof source === 'string'; -} -module.exports = FastGlob; +exports.ProcessTermError = ProcessTermError; +class ResponseError extends Error { + constructor(msg, responseCode) { + super(msg); + this.responseCode = responseCode; + } +} +exports.ResponseError = ResponseError; /***/ }), -/* 179 */ +/* 5 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -const utils = __webpack_require__(180); -function generate(patterns, settings) { - const positivePatterns = getPositivePatterns(patterns); - const negativePatterns = getNegativePatternsAsPositive(patterns, settings.ignore); - /** - * When the `caseSensitiveMatch` option is disabled, all patterns must be marked as dynamic, because we cannot check - * filepath directly (without read directory). - */ - const staticPatterns = !settings.caseSensitiveMatch ? [] : positivePatterns.filter(utils.pattern.isStaticPattern); - const dynamicPatterns = !settings.caseSensitiveMatch ? positivePatterns : positivePatterns.filter(utils.pattern.isDynamicPattern); - const staticTasks = convertPatternsToTasks(staticPatterns, negativePatterns, /* dynamic */ false); - const dynamicTasks = convertPatternsToTasks(dynamicPatterns, negativePatterns, /* dynamic */ true); - return staticTasks.concat(dynamicTasks); -} -exports.generate = generate; -function convertPatternsToTasks(positive, negative, dynamic) { - const positivePatternsGroup = groupPatternsByBaseDirectory(positive); - // When we have a global group – there is no reason to divide the patterns into independent tasks. - // In this case, the global task covers the rest. - if ('.' in positivePatternsGroup) { - const task = convertPatternGroupToTask('.', positive, negative, dynamic); - return [task]; - } - return convertPatternGroupsToTasks(positivePatternsGroup, negative, dynamic); -} -exports.convertPatternsToTasks = convertPatternsToTasks; -function getPositivePatterns(patterns) { - return utils.pattern.getPositivePatterns(patterns); -} -exports.getPositivePatterns = getPositivePatterns; -function getNegativePatternsAsPositive(patterns, ignore) { - const negative = utils.pattern.getNegativePatterns(patterns).concat(ignore); - const positive = negative.map(utils.pattern.convertToPositivePattern); - return positive; -} -exports.getNegativePatternsAsPositive = getNegativePatternsAsPositive; -function groupPatternsByBaseDirectory(patterns) { - return patterns.reduce((collection, pattern) => { - const base = utils.pattern.getBaseDirectory(pattern); - if (base in collection) { - collection[base].push(pattern); - } - else { - collection[base] = [pattern]; - } - return collection; - }, {}); -} -exports.groupPatternsByBaseDirectory = groupPatternsByBaseDirectory; -function convertPatternGroupsToTasks(positive, negative, dynamic) { - return Object.keys(positive).map((base) => { - return convertPatternGroupToTask(base, positive[base], negative, dynamic); - }); -} -exports.convertPatternGroupsToTasks = convertPatternGroupsToTasks; -function convertPatternGroupToTask(base, positive, negative, dynamic) { - return { - dynamic, - positive, - negative, - base, - patterns: [].concat(positive, negative.map(utils.pattern.convertToNegativePattern)) - }; -} -exports.convertPatternGroupToTask = convertPatternGroupToTask; -/***/ }), -/* 180 */ -/***/ (function(module, exports, __webpack_require__) { +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.getFirstSuitableFolder = exports.readFirstAvailableStream = exports.makeTempDir = exports.hardlinksWork = exports.writeFilePreservingEol = exports.getFileSizeOnDisk = exports.walk = exports.symlink = exports.find = exports.readJsonAndFile = exports.readJson = exports.readFileAny = exports.hardlinkBulk = exports.copyBulk = exports.unlink = exports.glob = exports.link = exports.chmod = exports.lstat = exports.exists = exports.mkdirp = exports.stat = exports.access = exports.rename = exports.readdir = exports.realpath = exports.readlink = exports.writeFile = exports.open = exports.readFileBuffer = exports.lockQueue = exports.constants = undefined; -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -const array = __webpack_require__(181); -exports.array = array; -const errno = __webpack_require__(182); -exports.errno = errno; -const fs = __webpack_require__(183); -exports.fs = fs; -const path = __webpack_require__(184); -exports.path = path; -const pattern = __webpack_require__(185); -exports.pattern = pattern; -const stream = __webpack_require__(206); -exports.stream = stream; +var _asyncToGenerator2; +function _load_asyncToGenerator() { + return _asyncToGenerator2 = _interopRequireDefault(__webpack_require__(1)); +} -/***/ }), -/* 181 */ -/***/ (function(module, exports, __webpack_require__) { +let buildActionsForCopy = (() => { + var _ref = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (queue, events, possibleExtraneous, reporter) { -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -function flatten(items) { - return items.reduce((collection, item) => [].concat(collection, item), []); -} -exports.flatten = flatten; + // + let build = (() => { + var _ref5 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (data) { + const src = data.src, + dest = data.dest, + type = data.type; + + const onFresh = data.onFresh || noop; + const onDone = data.onDone || noop; + + // TODO https://github.com/yarnpkg/yarn/issues/3751 + // related to bundled dependencies handling + if (files.has(dest.toLowerCase())) { + reporter.verbose(`The case-insensitive file ${dest} shouldn't be copied twice in one bulk copy`); + } else { + files.add(dest.toLowerCase()); + } + + if (type === 'symlink') { + yield mkdirp((_path || _load_path()).default.dirname(dest)); + onFresh(); + actions.symlink.push({ + dest, + linkname: src + }); + onDone(); + return; + } + if (events.ignoreBasenames.indexOf((_path || _load_path()).default.basename(src)) >= 0) { + // ignored file + return; + } -/***/ }), -/* 182 */ -/***/ (function(module, exports, __webpack_require__) { + const srcStat = yield lstat(src); + let srcFiles; -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -function isEnoentCodeError(error) { - return error.code === 'ENOENT'; -} -exports.isEnoentCodeError = isEnoentCodeError; + if (srcStat.isDirectory()) { + srcFiles = yield readdir(src); + } + let destStat; + try { + // try accessing the destination + destStat = yield lstat(dest); + } catch (e) { + // proceed if destination doesn't exist, otherwise error + if (e.code !== 'ENOENT') { + throw e; + } + } -/***/ }), -/* 183 */ -/***/ (function(module, exports, __webpack_require__) { + // if destination exists + if (destStat) { + const bothSymlinks = srcStat.isSymbolicLink() && destStat.isSymbolicLink(); + const bothFolders = srcStat.isDirectory() && destStat.isDirectory(); + const bothFiles = srcStat.isFile() && destStat.isFile(); -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -class DirentFromStats { - constructor(name, stats) { - this.name = name; - this.isBlockDevice = stats.isBlockDevice.bind(stats); - this.isCharacterDevice = stats.isCharacterDevice.bind(stats); - this.isDirectory = stats.isDirectory.bind(stats); - this.isFIFO = stats.isFIFO.bind(stats); - this.isFile = stats.isFile.bind(stats); - this.isSocket = stats.isSocket.bind(stats); - this.isSymbolicLink = stats.isSymbolicLink.bind(stats); - } -} -function createDirentFromStats(name, stats) { - return new DirentFromStats(name, stats); -} -exports.createDirentFromStats = createDirentFromStats; + // EINVAL access errors sometimes happen which shouldn't because node shouldn't be giving + // us modes that aren't valid. investigate this, it's generally safe to proceed. + /* if (srcStat.mode !== destStat.mode) { + try { + await access(dest, srcStat.mode); + } catch (err) {} + } */ + + if (bothFiles && artifactFiles.has(dest)) { + // this file gets changed during build, likely by a custom install script. Don't bother checking it. + onDone(); + reporter.verbose(reporter.lang('verboseFileSkipArtifact', src)); + return; + } -/***/ }), -/* 184 */ -/***/ (function(module, exports, __webpack_require__) { + if (bothFiles && srcStat.size === destStat.size && (0, (_fsNormalized || _load_fsNormalized()).fileDatesEqual)(srcStat.mtime, destStat.mtime)) { + // we can safely assume this is the same file + onDone(); + reporter.verbose(reporter.lang('verboseFileSkip', src, dest, srcStat.size, +srcStat.mtime)); + return; + } -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -const path = __webpack_require__(16); -/** - * Designed to work only with simple paths: `dir\\file`. - */ -function unixify(filepath) { - return filepath.replace(/\\/g, '/'); -} -exports.unixify = unixify; -function makeAbsolute(cwd, filepath) { - return path.resolve(cwd, filepath); -} -exports.makeAbsolute = makeAbsolute; + if (bothSymlinks) { + const srcReallink = yield readlink(src); + if (srcReallink === (yield readlink(dest))) { + // if both symlinks are the same then we can continue on + onDone(); + reporter.verbose(reporter.lang('verboseFileSkipSymlink', src, dest, srcReallink)); + return; + } + } + if (bothFolders) { + // mark files that aren't in this folder as possibly extraneous + const destFiles = yield readdir(dest); + invariant(srcFiles, 'src files not initialised'); + + for (var _iterator4 = destFiles, _isArray4 = Array.isArray(_iterator4), _i4 = 0, _iterator4 = _isArray4 ? _iterator4 : _iterator4[Symbol.iterator]();;) { + var _ref6; + + if (_isArray4) { + if (_i4 >= _iterator4.length) break; + _ref6 = _iterator4[_i4++]; + } else { + _i4 = _iterator4.next(); + if (_i4.done) break; + _ref6 = _i4.value; + } -/***/ }), -/* 185 */ -/***/ (function(module, exports, __webpack_require__) { + const file = _ref6; -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -const path = __webpack_require__(16); -const globParent = __webpack_require__(186); -const isGlob = __webpack_require__(187); -const micromatch = __webpack_require__(189); -const GLOBSTAR = '**'; -function isStaticPattern(pattern) { - return !isDynamicPattern(pattern); -} -exports.isStaticPattern = isStaticPattern; -function isDynamicPattern(pattern) { - return isGlob(pattern, { strict: false }); -} -exports.isDynamicPattern = isDynamicPattern; -function convertToPositivePattern(pattern) { - return isNegativePattern(pattern) ? pattern.slice(1) : pattern; -} -exports.convertToPositivePattern = convertToPositivePattern; -function convertToNegativePattern(pattern) { - return '!' + pattern; -} -exports.convertToNegativePattern = convertToNegativePattern; -function isNegativePattern(pattern) { - return pattern.startsWith('!') && pattern[1] !== '('; -} -exports.isNegativePattern = isNegativePattern; -function isPositivePattern(pattern) { - return !isNegativePattern(pattern); -} -exports.isPositivePattern = isPositivePattern; -function getNegativePatterns(patterns) { - return patterns.filter(isNegativePattern); -} -exports.getNegativePatterns = getNegativePatterns; -function getPositivePatterns(patterns) { - return patterns.filter(isPositivePattern); -} -exports.getPositivePatterns = getPositivePatterns; -function getBaseDirectory(pattern) { - return globParent(pattern); -} -exports.getBaseDirectory = getBaseDirectory; -function hasGlobStar(pattern) { - return pattern.indexOf(GLOBSTAR) !== -1; -} -exports.hasGlobStar = hasGlobStar; -function endsWithSlashGlobStar(pattern) { - return pattern.endsWith('/' + GLOBSTAR); -} -exports.endsWithSlashGlobStar = endsWithSlashGlobStar; -function isAffectDepthOfReadingPattern(pattern) { - const basename = path.basename(pattern); - return endsWithSlashGlobStar(pattern) || isStaticPattern(basename); -} -exports.isAffectDepthOfReadingPattern = isAffectDepthOfReadingPattern; -function getNaiveDepth(pattern) { - const base = getBaseDirectory(pattern); - const patternDepth = pattern.split('/').length; - const patternBaseDepth = base.split('/').length; - /** - * This is a hack for pattern that has no base directory. - * - * This is related to the `*\something\*` pattern. - */ - if (base === '.') { - return patternDepth - patternBaseDepth; - } - return patternDepth - patternBaseDepth - 1; -} -exports.getNaiveDepth = getNaiveDepth; -function getMaxNaivePatternsDepth(patterns) { - return patterns.reduce((max, pattern) => { - const depth = getNaiveDepth(pattern); - return depth > max ? depth : max; - }, 0); -} -exports.getMaxNaivePatternsDepth = getMaxNaivePatternsDepth; -function makeRe(pattern, options) { - return micromatch.makeRe(pattern, options); -} -exports.makeRe = makeRe; -function convertPatternsToRe(patterns, options) { - return patterns.map((pattern) => makeRe(pattern, options)); -} -exports.convertPatternsToRe = convertPatternsToRe; -function matchAny(entry, patternsRe) { - const filepath = entry.replace(/^\.[\\\/]/, ''); - return patternsRe.some((patternRe) => patternRe.test(filepath)); -} -exports.matchAny = matchAny; + if (srcFiles.indexOf(file) < 0) { + const loc = (_path || _load_path()).default.join(dest, file); + possibleExtraneous.add(loc); + if ((yield lstat(loc)).isDirectory()) { + for (var _iterator5 = yield readdir(loc), _isArray5 = Array.isArray(_iterator5), _i5 = 0, _iterator5 = _isArray5 ? _iterator5 : _iterator5[Symbol.iterator]();;) { + var _ref7; -/***/ }), -/* 186 */ -/***/ (function(module, exports, __webpack_require__) { + if (_isArray5) { + if (_i5 >= _iterator5.length) break; + _ref7 = _iterator5[_i5++]; + } else { + _i5 = _iterator5.next(); + if (_i5.done) break; + _ref7 = _i5.value; + } -"use strict"; + const file = _ref7; + possibleExtraneous.add((_path || _load_path()).default.join(loc, file)); + } + } + } + } + } + } -var isGlob = __webpack_require__(187); -var pathPosixDirname = __webpack_require__(16).posix.dirname; -var isWin32 = __webpack_require__(11).platform() === 'win32'; + if (destStat && destStat.isSymbolicLink()) { + yield (0, (_fsNormalized || _load_fsNormalized()).unlink)(dest); + destStat = null; + } -var slash = '/'; -var backslash = /\\/g; -var enclosure = /[\{\[].*[\/]*.*[\}\]]$/; -var globby = /(^|[^\\])([\{\[]|\([^\)]+$)/; -var escaped = /\\([\*\?\|\[\]\(\)\{\}])/g; + if (srcStat.isSymbolicLink()) { + onFresh(); + const linkname = yield readlink(src); + actions.symlink.push({ + dest, + linkname + }); + onDone(); + } else if (srcStat.isDirectory()) { + if (!destStat) { + reporter.verbose(reporter.lang('verboseFileFolder', dest)); + yield mkdirp(dest); + } -module.exports = function globParent(str) { - // flip windows path separators - if (isWin32 && str.indexOf(slash) < 0) { - str = str.replace(backslash, slash); - } + const destParts = dest.split((_path || _load_path()).default.sep); + while (destParts.length) { + files.add(destParts.join((_path || _load_path()).default.sep).toLowerCase()); + destParts.pop(); + } - // special case for strings ending in enclosure containing path separator - if (enclosure.test(str)) { - str += slash; - } + // push all files to queue + invariant(srcFiles, 'src files not initialised'); + let remaining = srcFiles.length; + if (!remaining) { + onDone(); + } + for (var _iterator6 = srcFiles, _isArray6 = Array.isArray(_iterator6), _i6 = 0, _iterator6 = _isArray6 ? _iterator6 : _iterator6[Symbol.iterator]();;) { + var _ref8; - // preserves full path in case of trailing path separator - str += 'a'; + if (_isArray6) { + if (_i6 >= _iterator6.length) break; + _ref8 = _iterator6[_i6++]; + } else { + _i6 = _iterator6.next(); + if (_i6.done) break; + _ref8 = _i6.value; + } - // remove path parts that are globby - do { - str = pathPosixDirname(str); - } while (isGlob(str) || globby.test(str)); + const file = _ref8; - // remove escape chars and return result - return str.replace(escaped, '$1'); -}; + queue.push({ + dest: (_path || _load_path()).default.join(dest, file), + onFresh, + onDone: function (_onDone) { + function onDone() { + return _onDone.apply(this, arguments); + } + onDone.toString = function () { + return _onDone.toString(); + }; -/***/ }), -/* 187 */ -/***/ (function(module, exports, __webpack_require__) { + return onDone; + }(function () { + if (--remaining === 0) { + onDone(); + } + }), + src: (_path || _load_path()).default.join(src, file) + }); + } + } else if (srcStat.isFile()) { + onFresh(); + actions.file.push({ + src, + dest, + atime: srcStat.atime, + mtime: srcStat.mtime, + mode: srcStat.mode + }); + onDone(); + } else { + throw new Error(`unsure how to copy this: ${src}`); + } + }); -/*! - * is-glob - * - * Copyright (c) 2014-2017, Jon Schlinkert. - * Released under the MIT License. - */ + return function build(_x5) { + return _ref5.apply(this, arguments); + }; + })(); -var isExtglob = __webpack_require__(188); -var chars = { '{': '}', '(': ')', '[': ']'}; -var strictRegex = /\\(.)|(^!|\*|[\].+)]\?|\[[^\\\]]+\]|\{[^\\}]+\}|\(\?[:!=][^\\)]+\)|\([^|]+\|[^\\)]+\))/; -var relaxedRegex = /\\(.)|(^!|[*?{}()[\]]|\(\?)/; + const artifactFiles = new Set(events.artifactFiles || []); + const files = new Set(); -module.exports = function isGlob(str, options) { - if (typeof str !== 'string' || str === '') { - return false; - } + // initialise events + for (var _iterator = queue, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : _iterator[Symbol.iterator]();;) { + var _ref2; - if (isExtglob(str)) { - return true; - } + if (_isArray) { + if (_i >= _iterator.length) break; + _ref2 = _iterator[_i++]; + } else { + _i = _iterator.next(); + if (_i.done) break; + _ref2 = _i.value; + } - var regex = strictRegex; - var match; + const item = _ref2; - // optionally relax regex - if (options && options.strict === false) { - regex = relaxedRegex; - } + const onDone = item.onDone; + item.onDone = function () { + events.onProgress(item.dest); + if (onDone) { + onDone(); + } + }; + } + events.onStart(queue.length); - while ((match = regex.exec(str))) { - if (match[2]) return true; - var idx = match.index + match[0].length; + // start building actions + const actions = { + file: [], + symlink: [], + link: [] + }; - // if an open bracket/brace/paren is escaped, - // set the index to the next closing character - var open = match[1]; - var close = open ? chars[open] : null; - if (open && close) { - var n = str.indexOf(close, idx); - if (n !== -1) { - idx = n + 1; - } + // custom concurrency logic as we're always executing stacks of CONCURRENT_QUEUE_ITEMS queue items + // at a time due to the requirement to push items onto the queue + while (queue.length) { + const items = queue.splice(0, CONCURRENT_QUEUE_ITEMS); + yield Promise.all(items.map(build)); } - str = str.slice(idx); - } - return false; -}; + // simulate the existence of some files to prevent considering them extraneous + for (var _iterator2 = artifactFiles, _isArray2 = Array.isArray(_iterator2), _i2 = 0, _iterator2 = _isArray2 ? _iterator2 : _iterator2[Symbol.iterator]();;) { + var _ref3; + if (_isArray2) { + if (_i2 >= _iterator2.length) break; + _ref3 = _iterator2[_i2++]; + } else { + _i2 = _iterator2.next(); + if (_i2.done) break; + _ref3 = _i2.value; + } -/***/ }), -/* 188 */ -/***/ (function(module, exports) { + const file = _ref3; -/*! - * is-extglob - * - * Copyright (c) 2014-2016, Jon Schlinkert. - * Licensed under the MIT License. - */ + if (possibleExtraneous.has(file)) { + reporter.verbose(reporter.lang('verboseFilePhantomExtraneous', file)); + possibleExtraneous.delete(file); + } + } -module.exports = function isExtglob(str) { - if (typeof str !== 'string' || str === '') { - return false; - } + for (var _iterator3 = possibleExtraneous, _isArray3 = Array.isArray(_iterator3), _i3 = 0, _iterator3 = _isArray3 ? _iterator3 : _iterator3[Symbol.iterator]();;) { + var _ref4; - var match; - while ((match = /(\\).|([@?!+*]\(.*\))/g.exec(str))) { - if (match[2]) return true; - str = str.slice(match.index + match[0].length); - } + if (_isArray3) { + if (_i3 >= _iterator3.length) break; + _ref4 = _iterator3[_i3++]; + } else { + _i3 = _iterator3.next(); + if (_i3.done) break; + _ref4 = _i3.value; + } - return false; -}; + const loc = _ref4; + if (files.has(loc.toLowerCase())) { + possibleExtraneous.delete(loc); + } + } -/***/ }), -/* 189 */ -/***/ (function(module, exports, __webpack_require__) { + return actions; + }); -"use strict"; + return function buildActionsForCopy(_x, _x2, _x3, _x4) { + return _ref.apply(this, arguments); + }; +})(); +let buildActionsForHardlink = (() => { + var _ref9 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (queue, events, possibleExtraneous, reporter) { -const util = __webpack_require__(29); -const braces = __webpack_require__(190); -const picomatch = __webpack_require__(200); -const utils = __webpack_require__(203); -const isEmptyString = val => typeof val === 'string' && (val === '' || val === './'); + // + let build = (() => { + var _ref13 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (data) { + const src = data.src, + dest = data.dest; + + const onFresh = data.onFresh || noop; + const onDone = data.onDone || noop; + if (files.has(dest.toLowerCase())) { + // Fixes issue https://github.com/yarnpkg/yarn/issues/2734 + // When bulk hardlinking we have A -> B structure that we want to hardlink to A1 -> B1, + // package-linker passes that modules A1 and B1 need to be hardlinked, + // the recursive linking algorithm of A1 ends up scheduling files in B1 to be linked twice which will case + // an exception. + onDone(); + return; + } + files.add(dest.toLowerCase()); -/** - * Returns an array of strings that match one or more glob patterns. - * - * ```js - * const mm = require('micromatch'); - * // mm(list, patterns[, options]); - * - * console.log(mm(['a.js', 'a.txt'], ['*.js'])); - * //=> [ 'a.js' ] - * ``` - * @param {String|Array} list List of strings to match. - * @param {String|Array} patterns One or more glob patterns to use for matching. - * @param {Object} options See available [options](#options) - * @return {Array} Returns an array of matches - * @summary false - * @api public - */ + if (events.ignoreBasenames.indexOf((_path || _load_path()).default.basename(src)) >= 0) { + // ignored file + return; + } -const micromatch = (list, patterns, options) => { - patterns = [].concat(patterns); - list = [].concat(list); + const srcStat = yield lstat(src); + let srcFiles; - let omit = new Set(); - let keep = new Set(); - let items = new Set(); - let negatives = 0; + if (srcStat.isDirectory()) { + srcFiles = yield readdir(src); + } - let onResult = state => { - items.add(state.output); - if (options && options.onResult) { - options.onResult(state); - } - }; + const destExists = yield exists(dest); + if (destExists) { + const destStat = yield lstat(dest); - for (let i = 0; i < patterns.length; i++) { - let isMatch = picomatch(String(patterns[i]), { ...options, onResult }, true); - let negated = isMatch.state.negated || isMatch.state.negatedExtglob; - if (negated) negatives++; + const bothSymlinks = srcStat.isSymbolicLink() && destStat.isSymbolicLink(); + const bothFolders = srcStat.isDirectory() && destStat.isDirectory(); + const bothFiles = srcStat.isFile() && destStat.isFile(); - for (let item of list) { - let matched = isMatch(item, true); + if (srcStat.mode !== destStat.mode) { + try { + yield access(dest, srcStat.mode); + } catch (err) { + // EINVAL access errors sometimes happen which shouldn't because node shouldn't be giving + // us modes that aren't valid. investigate this, it's generally safe to proceed. + reporter.verbose(err); + } + } - let match = negated ? !matched.isMatch : matched.isMatch; - if (!match) continue; + if (bothFiles && artifactFiles.has(dest)) { + // this file gets changed during build, likely by a custom install script. Don't bother checking it. + onDone(); + reporter.verbose(reporter.lang('verboseFileSkipArtifact', src)); + return; + } - if (negated) { - omit.add(matched.output); - } else { - omit.delete(matched.output); - keep.add(matched.output); - } - } - } + // correct hardlink + if (bothFiles && srcStat.ino !== null && srcStat.ino === destStat.ino) { + onDone(); + reporter.verbose(reporter.lang('verboseFileSkip', src, dest, srcStat.ino)); + return; + } - let result = negatives === patterns.length ? [...items] : [...keep]; - let matches = result.filter(item => !omit.has(item)); + if (bothSymlinks) { + const srcReallink = yield readlink(src); + if (srcReallink === (yield readlink(dest))) { + // if both symlinks are the same then we can continue on + onDone(); + reporter.verbose(reporter.lang('verboseFileSkipSymlink', src, dest, srcReallink)); + return; + } + } - if (options && matches.length === 0) { - if (options.failglob === true) { - throw new Error(`No matches found for "${patterns.join(', ')}"`); - } + if (bothFolders) { + // mark files that aren't in this folder as possibly extraneous + const destFiles = yield readdir(dest); + invariant(srcFiles, 'src files not initialised'); + + for (var _iterator10 = destFiles, _isArray10 = Array.isArray(_iterator10), _i10 = 0, _iterator10 = _isArray10 ? _iterator10 : _iterator10[Symbol.iterator]();;) { + var _ref14; + + if (_isArray10) { + if (_i10 >= _iterator10.length) break; + _ref14 = _iterator10[_i10++]; + } else { + _i10 = _iterator10.next(); + if (_i10.done) break; + _ref14 = _i10.value; + } - if (options.nonull === true || options.nullglob === true) { - return options.unescape ? patterns.map(p => p.replace(/\\/g, '')) : patterns; - } - } + const file = _ref14; - return matches; -}; + if (srcFiles.indexOf(file) < 0) { + const loc = (_path || _load_path()).default.join(dest, file); + possibleExtraneous.add(loc); -/** - * Backwards compatibility - */ + if ((yield lstat(loc)).isDirectory()) { + for (var _iterator11 = yield readdir(loc), _isArray11 = Array.isArray(_iterator11), _i11 = 0, _iterator11 = _isArray11 ? _iterator11 : _iterator11[Symbol.iterator]();;) { + var _ref15; -micromatch.match = micromatch; + if (_isArray11) { + if (_i11 >= _iterator11.length) break; + _ref15 = _iterator11[_i11++]; + } else { + _i11 = _iterator11.next(); + if (_i11.done) break; + _ref15 = _i11.value; + } -/** - * Returns a matcher function from the given glob `pattern` and `options`. - * The returned function takes a string to match as its only argument and returns - * true if the string is a match. - * - * ```js - * const mm = require('micromatch'); - * // mm.matcher(pattern[, options]); - * - * const isMatch = mm.matcher('*.!(*a)'); - * console.log(isMatch('a.a')); //=> false - * console.log(isMatch('a.b')); //=> true - * ``` - * @param {String} `pattern` Glob pattern - * @param {Object} `options` - * @return {Function} Returns a matcher function. - * @api public - */ + const file = _ref15; -micromatch.matcher = (pattern, options) => picomatch(pattern, options); + possibleExtraneous.add((_path || _load_path()).default.join(loc, file)); + } + } + } + } + } + } -/** - * Returns true if **any** of the given glob `patterns` match the specified `string`. - * - * ```js - * const mm = require('micromatch'); - * // mm.isMatch(string, patterns[, options]); - * - * console.log(mm.isMatch('a.a', ['b.*', '*.a'])); //=> true - * console.log(mm.isMatch('a.a', 'b.*')); //=> false - * ``` - * @param {String} str The string to test. - * @param {String|Array} patterns One or more glob patterns to use for matching. - * @param {Object} [options] See available [options](#options). - * @return {Boolean} Returns true if any patterns match `str` - * @api public - */ + if (srcStat.isSymbolicLink()) { + onFresh(); + const linkname = yield readlink(src); + actions.symlink.push({ + dest, + linkname + }); + onDone(); + } else if (srcStat.isDirectory()) { + reporter.verbose(reporter.lang('verboseFileFolder', dest)); + yield mkdirp(dest); + + const destParts = dest.split((_path || _load_path()).default.sep); + while (destParts.length) { + files.add(destParts.join((_path || _load_path()).default.sep).toLowerCase()); + destParts.pop(); + } -micromatch.isMatch = (str, patterns, options) => picomatch(patterns, options)(str); + // push all files to queue + invariant(srcFiles, 'src files not initialised'); + let remaining = srcFiles.length; + if (!remaining) { + onDone(); + } + for (var _iterator12 = srcFiles, _isArray12 = Array.isArray(_iterator12), _i12 = 0, _iterator12 = _isArray12 ? _iterator12 : _iterator12[Symbol.iterator]();;) { + var _ref16; -/** - * Backwards compatibility - */ + if (_isArray12) { + if (_i12 >= _iterator12.length) break; + _ref16 = _iterator12[_i12++]; + } else { + _i12 = _iterator12.next(); + if (_i12.done) break; + _ref16 = _i12.value; + } -micromatch.any = micromatch.isMatch; + const file = _ref16; -/** - * Returns a list of strings that _**do not match any**_ of the given `patterns`. - * - * ```js - * const mm = require('micromatch'); - * // mm.not(list, patterns[, options]); - * - * console.log(mm.not(['a.a', 'b.b', 'c.c'], '*.a')); - * //=> ['b.b', 'c.c'] - * ``` - * @param {Array} `list` Array of strings to match. - * @param {String|Array} `patterns` One or more glob pattern to use for matching. - * @param {Object} `options` See available [options](#options) for changing how matches are performed - * @return {Array} Returns an array of strings that **do not match** the given patterns. - * @api public - */ + queue.push({ + onFresh, + src: (_path || _load_path()).default.join(src, file), + dest: (_path || _load_path()).default.join(dest, file), + onDone: function (_onDone2) { + function onDone() { + return _onDone2.apply(this, arguments); + } -micromatch.not = (list, patterns, options = {}) => { - patterns = [].concat(patterns).map(String); - let result = new Set(); - let items = []; + onDone.toString = function () { + return _onDone2.toString(); + }; - let onResult = state => { - if (options.onResult) options.onResult(state); - items.push(state.output); - }; + return onDone; + }(function () { + if (--remaining === 0) { + onDone(); + } + }) + }); + } + } else if (srcStat.isFile()) { + onFresh(); + actions.link.push({ + src, + dest, + removeDest: destExists + }); + onDone(); + } else { + throw new Error(`unsure how to copy this: ${src}`); + } + }); - let matches = micromatch(list, patterns, { ...options, onResult }); + return function build(_x10) { + return _ref13.apply(this, arguments); + }; + })(); - for (let item of items) { - if (!matches.includes(item)) { - result.add(item); - } - } - return [...result]; -}; + const artifactFiles = new Set(events.artifactFiles || []); + const files = new Set(); -/** - * Returns true if the given `string` contains the given pattern. Similar - * to [.isMatch](#isMatch) but the pattern can match any part of the string. - * - * ```js - * var mm = require('micromatch'); - * // mm.contains(string, pattern[, options]); - * - * console.log(mm.contains('aa/bb/cc', '*b')); - * //=> true - * console.log(mm.contains('aa/bb/cc', '*d')); - * //=> false - * ``` - * @param {String} `str` The string to match. - * @param {String|Array} `patterns` Glob pattern to use for matching. - * @param {Object} `options` See available [options](#options) for changing how matches are performed - * @return {Boolean} Returns true if the patter matches any part of `str`. - * @api public - */ + // initialise events + for (var _iterator7 = queue, _isArray7 = Array.isArray(_iterator7), _i7 = 0, _iterator7 = _isArray7 ? _iterator7 : _iterator7[Symbol.iterator]();;) { + var _ref10; -micromatch.contains = (str, pattern, options) => { - if (typeof str !== 'string') { - throw new TypeError(`Expected a string: "${util.inspect(str)}"`); - } + if (_isArray7) { + if (_i7 >= _iterator7.length) break; + _ref10 = _iterator7[_i7++]; + } else { + _i7 = _iterator7.next(); + if (_i7.done) break; + _ref10 = _i7.value; + } - if (Array.isArray(pattern)) { - return pattern.some(p => micromatch.contains(str, p, options)); - } + const item = _ref10; - if (typeof pattern === 'string') { - if (isEmptyString(str) || isEmptyString(pattern)) { - return false; + const onDone = item.onDone || noop; + item.onDone = function () { + events.onProgress(item.dest); + onDone(); + }; } + events.onStart(queue.length); - if (str.includes(pattern) || (str.startsWith('./') && str.slice(2).includes(pattern))) { - return true; + // start building actions + const actions = { + file: [], + symlink: [], + link: [] + }; + + // custom concurrency logic as we're always executing stacks of CONCURRENT_QUEUE_ITEMS queue items + // at a time due to the requirement to push items onto the queue + while (queue.length) { + const items = queue.splice(0, CONCURRENT_QUEUE_ITEMS); + yield Promise.all(items.map(build)); } - } - return micromatch.isMatch(str, pattern, { ...options, contains: true }); -}; + // simulate the existence of some files to prevent considering them extraneous + for (var _iterator8 = artifactFiles, _isArray8 = Array.isArray(_iterator8), _i8 = 0, _iterator8 = _isArray8 ? _iterator8 : _iterator8[Symbol.iterator]();;) { + var _ref11; -/** - * Filter the keys of the given object with the given `glob` pattern - * and `options`. Does not attempt to match nested keys. If you need this feature, - * use [glob-object][] instead. - * - * ```js - * const mm = require('micromatch'); - * // mm.matchKeys(object, patterns[, options]); - * - * const obj = { aa: 'a', ab: 'b', ac: 'c' }; - * console.log(mm.matchKeys(obj, '*b')); - * //=> { ab: 'b' } - * ``` - * @param {Object} `object` The object with keys to filter. - * @param {String|Array} `patterns` One or more glob patterns to use for matching. - * @param {Object} `options` See available [options](#options) for changing how matches are performed - * @return {Object} Returns an object with only keys that match the given patterns. - * @api public - */ + if (_isArray8) { + if (_i8 >= _iterator8.length) break; + _ref11 = _iterator8[_i8++]; + } else { + _i8 = _iterator8.next(); + if (_i8.done) break; + _ref11 = _i8.value; + } -micromatch.matchKeys = (obj, patterns, options) => { - if (!utils.isObject(obj)) { - throw new TypeError('Expected the first argument to be an object'); - } - let keys = micromatch(Object.keys(obj), patterns, options); - let res = {}; - for (let key of keys) res[key] = obj[key]; - return res; -}; + const file = _ref11; -/** - * Returns true if some of the strings in the given `list` match any of the given glob `patterns`. - * - * ```js - * const mm = require('micromatch'); - * // mm.some(list, patterns[, options]); - * - * console.log(mm.some(['foo.js', 'bar.js'], ['*.js', '!foo.js'])); - * // true - * console.log(mm.some(['foo.js'], ['*.js', '!foo.js'])); - * // false - * ``` - * @param {String|Array} `list` The string or array of strings to test. Returns as soon as the first match is found. - * @param {String|Array} `patterns` One or more glob patterns to use for matching. - * @param {Object} `options` See available [options](#options) for changing how matches are performed - * @return {Boolean} Returns true if any patterns match `str` - * @api public - */ + if (possibleExtraneous.has(file)) { + reporter.verbose(reporter.lang('verboseFilePhantomExtraneous', file)); + possibleExtraneous.delete(file); + } + } -micromatch.some = (list, patterns, options) => { - let items = [].concat(list); + for (var _iterator9 = possibleExtraneous, _isArray9 = Array.isArray(_iterator9), _i9 = 0, _iterator9 = _isArray9 ? _iterator9 : _iterator9[Symbol.iterator]();;) { + var _ref12; - for (let pattern of [].concat(patterns)) { - let isMatch = picomatch(String(pattern), options); - if (items.some(item => isMatch(item))) { - return true; + if (_isArray9) { + if (_i9 >= _iterator9.length) break; + _ref12 = _iterator9[_i9++]; + } else { + _i9 = _iterator9.next(); + if (_i9.done) break; + _ref12 = _i9.value; + } + + const loc = _ref12; + + if (files.has(loc.toLowerCase())) { + possibleExtraneous.delete(loc); + } } - } - return false; -}; -/** - * Returns true if every string in the given `list` matches - * any of the given glob `patterns`. - * - * ```js - * const mm = require('micromatch'); - * // mm.every(list, patterns[, options]); - * - * console.log(mm.every('foo.js', ['foo.js'])); - * // true - * console.log(mm.every(['foo.js', 'bar.js'], ['*.js'])); - * // true - * console.log(mm.every(['foo.js', 'bar.js'], ['*.js', '!foo.js'])); - * // false - * console.log(mm.every(['foo.js'], ['*.js', '!foo.js'])); - * // false - * ``` - * @param {String|Array} `list` The string or array of strings to test. - * @param {String|Array} `patterns` One or more glob patterns to use for matching. - * @param {Object} `options` See available [options](#options) for changing how matches are performed - * @return {Boolean} Returns true if any patterns match `str` - * @api public - */ + return actions; + }); -micromatch.every = (list, patterns, options) => { - let items = [].concat(list); + return function buildActionsForHardlink(_x6, _x7, _x8, _x9) { + return _ref9.apply(this, arguments); + }; +})(); - for (let pattern of [].concat(patterns)) { - let isMatch = picomatch(String(pattern), options); - if (!items.every(item => isMatch(item))) { - return false; - } - } - return true; -}; +let copyBulk = exports.copyBulk = (() => { + var _ref17 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (queue, reporter, _events) { + const events = { + onStart: _events && _events.onStart || noop, + onProgress: _events && _events.onProgress || noop, + possibleExtraneous: _events ? _events.possibleExtraneous : new Set(), + ignoreBasenames: _events && _events.ignoreBasenames || [], + artifactFiles: _events && _events.artifactFiles || [] + }; -/** - * Returns true if **all** of the given `patterns` match - * the specified string. - * - * ```js - * const mm = require('micromatch'); - * // mm.all(string, patterns[, options]); - * - * console.log(mm.all('foo.js', ['foo.js'])); - * // true - * - * console.log(mm.all('foo.js', ['*.js', '!foo.js'])); - * // false - * - * console.log(mm.all('foo.js', ['*.js', 'foo.js'])); - * // true - * - * console.log(mm.all('foo.js', ['*.js', 'f*', '*o*', '*o.js'])); - * // true - * ``` - * @param {String|Array} `str` The string to test. - * @param {String|Array} `patterns` One or more glob patterns to use for matching. - * @param {Object} `options` See available [options](#options) for changing how matches are performed - * @return {Boolean} Returns true if any patterns match `str` - * @api public - */ + const actions = yield buildActionsForCopy(queue, events, events.possibleExtraneous, reporter); + events.onStart(actions.file.length + actions.symlink.length + actions.link.length); -micromatch.all = (str, patterns, options) => { - if (typeof str !== 'string') { - throw new TypeError(`Expected a string: "${util.inspect(str)}"`); - } + const fileActions = actions.file; - return [].concat(patterns).every(p => picomatch(p, options)(str)); -}; + const currentlyWriting = new Map(); -/** - * Returns an array of matches captured by `pattern` in `string, or `null` if the pattern did not match. - * - * ```js - * const mm = require('micromatch'); - * // mm.capture(pattern, string[, options]); - * - * console.log(mm.capture('test/*.js', 'test/foo.js')); - * //=> ['foo'] - * console.log(mm.capture('test/*.js', 'foo/bar.css')); - * //=> null - * ``` - * @param {String} `glob` Glob pattern to use for matching. - * @param {String} `input` String to match - * @param {Object} `options` See available [options](#options) for changing how matches are performed - * @return {Boolean} Returns an array of captures if the input matches the glob pattern, otherwise `null`. - * @api public - */ + yield (_promise || _load_promise()).queue(fileActions, (() => { + var _ref18 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (data) { + let writePromise; + while (writePromise = currentlyWriting.get(data.dest)) { + yield writePromise; + } -micromatch.capture = (glob, input, options) => { - let posix = utils.isWindows(options); - let regex = picomatch.makeRe(String(glob), { ...options, capture: true }); - let match = regex.exec(posix ? utils.toPosixSlashes(input) : input); + reporter.verbose(reporter.lang('verboseFileCopy', data.src, data.dest)); + const copier = (0, (_fsNormalized || _load_fsNormalized()).copyFile)(data, function () { + return currentlyWriting.delete(data.dest); + }); + currentlyWriting.set(data.dest, copier); + events.onProgress(data.dest); + return copier; + }); - if (match) { - return match.slice(1).map(v => v === void 0 ? '' : v); - } -}; + return function (_x14) { + return _ref18.apply(this, arguments); + }; + })(), CONCURRENT_QUEUE_ITEMS); + + // we need to copy symlinks last as they could reference files we were copying + const symlinkActions = actions.symlink; + yield (_promise || _load_promise()).queue(symlinkActions, function (data) { + const linkname = (_path || _load_path()).default.resolve((_path || _load_path()).default.dirname(data.dest), data.linkname); + reporter.verbose(reporter.lang('verboseFileSymlink', data.dest, linkname)); + return symlink(linkname, data.dest); + }); + }); -/** - * Create a regular expression from the given glob `pattern`. - * - * ```js - * const mm = require('micromatch'); - * // mm.makeRe(pattern[, options]); - * - * console.log(mm.makeRe('*.js')); - * //=> /^(?:(\.[\\\/])?(?!\.)(?=.)[^\/]*?\.js)$/ - * ``` - * @param {String} `pattern` A glob pattern to convert to regex. - * @param {Object} `options` - * @return {RegExp} Returns a regex created from the given pattern. - * @api public - */ + return function copyBulk(_x11, _x12, _x13) { + return _ref17.apply(this, arguments); + }; +})(); -micromatch.makeRe = (...args) => picomatch.makeRe(...args); +let hardlinkBulk = exports.hardlinkBulk = (() => { + var _ref19 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (queue, reporter, _events) { + const events = { + onStart: _events && _events.onStart || noop, + onProgress: _events && _events.onProgress || noop, + possibleExtraneous: _events ? _events.possibleExtraneous : new Set(), + artifactFiles: _events && _events.artifactFiles || [], + ignoreBasenames: [] + }; -/** - * Scan a glob pattern to separate the pattern into segments. Used - * by the [split](#split) method. - * - * ```js - * const mm = require('micromatch'); - * const state = mm.scan(pattern[, options]); - * ``` - * @param {String} `pattern` - * @param {Object} `options` - * @return {Object} Returns an object with - * @api public - */ + const actions = yield buildActionsForHardlink(queue, events, events.possibleExtraneous, reporter); + events.onStart(actions.file.length + actions.symlink.length + actions.link.length); -micromatch.scan = (...args) => picomatch.scan(...args); + const fileActions = actions.link; -/** - * Parse a glob pattern to create the source string for a regular - * expression. - * - * ```js - * const mm = require('micromatch'); - * const state = mm(pattern[, options]); - * ``` - * @param {String} `glob` - * @param {Object} `options` - * @return {Object} Returns an object with useful properties and output to be used as regex source string. - * @api public - */ + yield (_promise || _load_promise()).queue(fileActions, (() => { + var _ref20 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (data) { + reporter.verbose(reporter.lang('verboseFileLink', data.src, data.dest)); + if (data.removeDest) { + yield (0, (_fsNormalized || _load_fsNormalized()).unlink)(data.dest); + } + yield link(data.src, data.dest); + }); -micromatch.parse = (patterns, options) => { - let res = []; - for (let pattern of [].concat(patterns || [])) { - for (let str of braces(String(pattern), options)) { - res.push(picomatch.parse(str, options)); + return function (_x18) { + return _ref20.apply(this, arguments); + }; + })(), CONCURRENT_QUEUE_ITEMS); + + // we need to copy symlinks last as they could reference files we were copying + const symlinkActions = actions.symlink; + yield (_promise || _load_promise()).queue(symlinkActions, function (data) { + const linkname = (_path || _load_path()).default.resolve((_path || _load_path()).default.dirname(data.dest), data.linkname); + reporter.verbose(reporter.lang('verboseFileSymlink', data.dest, linkname)); + return symlink(linkname, data.dest); + }); + }); + + return function hardlinkBulk(_x15, _x16, _x17) { + return _ref19.apply(this, arguments); + }; +})(); + +let readFileAny = exports.readFileAny = (() => { + var _ref21 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (files) { + for (var _iterator13 = files, _isArray13 = Array.isArray(_iterator13), _i13 = 0, _iterator13 = _isArray13 ? _iterator13 : _iterator13[Symbol.iterator]();;) { + var _ref22; + + if (_isArray13) { + if (_i13 >= _iterator13.length) break; + _ref22 = _iterator13[_i13++]; + } else { + _i13 = _iterator13.next(); + if (_i13.done) break; + _ref22 = _i13.value; + } + + const file = _ref22; + + if (yield exists(file)) { + return readFile(file); + } } - } - return res; -}; + return null; + }); -/** - * Process the given brace `pattern`. - * - * ```js - * const { braces } = require('micromatch'); - * console.log(braces('foo/{a,b,c}/bar')); - * //=> [ 'foo/(a|b|c)/bar' ] - * - * console.log(braces('foo/{a,b,c}/bar', { expand: true })); - * //=> [ 'foo/a/bar', 'foo/b/bar', 'foo/c/bar' ] - * ``` - * @param {String} `pattern` String with brace pattern to process. - * @param {Object} `options` Any [options](#options) to change how expansion is performed. See the [braces][] library for all available options. - * @return {Array} - * @api public - */ + return function readFileAny(_x19) { + return _ref21.apply(this, arguments); + }; +})(); -micromatch.braces = (pattern, options) => { - if (typeof pattern !== 'string') throw new TypeError('Expected a string'); - if ((options && options.nobrace === true) || !/\{.*\}/.test(pattern)) { - return [pattern]; - } - return braces(pattern, options); -}; +let readJson = exports.readJson = (() => { + var _ref23 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (loc) { + return (yield readJsonAndFile(loc)).object; + }); -/** - * Expand braces - */ + return function readJson(_x20) { + return _ref23.apply(this, arguments); + }; +})(); -micromatch.braceExpand = (pattern, options) => { - if (typeof pattern !== 'string') throw new TypeError('Expected a string'); - return micromatch.braces(pattern, { ...options, expand: true }); -}; +let readJsonAndFile = exports.readJsonAndFile = (() => { + var _ref24 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (loc) { + const file = yield readFile(loc); + try { + return { + object: (0, (_map || _load_map()).default)(JSON.parse(stripBOM(file))), + content: file + }; + } catch (err) { + err.message = `${loc}: ${err.message}`; + throw err; + } + }); -/** - * Expose micromatch - */ + return function readJsonAndFile(_x21) { + return _ref24.apply(this, arguments); + }; +})(); -module.exports = micromatch; +let find = exports.find = (() => { + var _ref25 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (filename, dir) { + const parts = dir.split((_path || _load_path()).default.sep); + while (parts.length) { + const loc = parts.concat(filename).join((_path || _load_path()).default.sep); -/***/ }), -/* 190 */ -/***/ (function(module, exports, __webpack_require__) { + if (yield exists(loc)) { + return loc; + } else { + parts.pop(); + } + } -"use strict"; + return false; + }); + + return function find(_x22, _x23) { + return _ref25.apply(this, arguments); + }; +})(); + +let symlink = exports.symlink = (() => { + var _ref26 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (src, dest) { + try { + const stats = yield lstat(dest); + if (stats.isSymbolicLink()) { + const resolved = yield realpath(dest); + if (resolved === src) { + return; + } + } + } catch (err) { + if (err.code !== 'ENOENT') { + throw err; + } + } + // We use rimraf for unlink which never throws an ENOENT on missing target + yield (0, (_fsNormalized || _load_fsNormalized()).unlink)(dest); + if (process.platform === 'win32') { + // use directory junctions if possible on win32, this requires absolute paths + yield fsSymlink(src, dest, 'junction'); + } else { + // use relative paths otherwise which will be retained if the directory is moved + let relative; + try { + relative = (_path || _load_path()).default.relative((_fs || _load_fs()).default.realpathSync((_path || _load_path()).default.dirname(dest)), (_fs || _load_fs()).default.realpathSync(src)); + } catch (err) { + if (err.code !== 'ENOENT') { + throw err; + } + relative = (_path || _load_path()).default.relative((_path || _load_path()).default.dirname(dest), src); + } + // When path.relative returns an empty string for the current directory, we should instead use + // '.', which is a valid fs.symlink target. + yield fsSymlink(relative || '.', dest); + } + }); -const stringify = __webpack_require__(191); -const compile = __webpack_require__(193); -const expand = __webpack_require__(197); -const parse = __webpack_require__(198); + return function symlink(_x24, _x25) { + return _ref26.apply(this, arguments); + }; +})(); -/** - * Expand the given pattern or create a regex-compatible string. - * - * ```js - * const braces = require('braces'); - * console.log(braces('{a,b,c}', { compile: true })); //=> ['(a|b|c)'] - * console.log(braces('{a,b,c}')); //=> ['a', 'b', 'c'] - * ``` - * @param {String} `str` - * @param {Object} `options` - * @return {String} - * @api public - */ +let walk = exports.walk = (() => { + var _ref27 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (dir, relativeDir, ignoreBasenames = new Set()) { + let files = []; -const braces = (input, options = {}) => { - let output = []; + let filenames = yield readdir(dir); + if (ignoreBasenames.size) { + filenames = filenames.filter(function (name) { + return !ignoreBasenames.has(name); + }); + } - if (Array.isArray(input)) { - for (let pattern of input) { - let result = braces.create(pattern, options); - if (Array.isArray(result)) { - output.push(...result); + for (var _iterator14 = filenames, _isArray14 = Array.isArray(_iterator14), _i14 = 0, _iterator14 = _isArray14 ? _iterator14 : _iterator14[Symbol.iterator]();;) { + var _ref28; + + if (_isArray14) { + if (_i14 >= _iterator14.length) break; + _ref28 = _iterator14[_i14++]; } else { - output.push(result); + _i14 = _iterator14.next(); + if (_i14.done) break; + _ref28 = _i14.value; } - } - } else { - output = [].concat(braces.create(input, options)); - } - - if (options && options.expand === true && options.nodupes === true) { - output = [...new Set(output)]; - } - return output; -}; -/** - * Parse the given `str` with the given `options`. - * - * ```js - * // braces.parse(pattern, [, options]); - * const ast = braces.parse('a/{b,c}/d'); - * console.log(ast); - * ``` - * @param {String} pattern Brace pattern to parse - * @param {Object} options - * @return {Object} Returns an AST - * @api public - */ + const name = _ref28; -braces.parse = (input, options = {}) => parse(input, options); + const relative = relativeDir ? (_path || _load_path()).default.join(relativeDir, name) : name; + const loc = (_path || _load_path()).default.join(dir, name); + const stat = yield lstat(loc); -/** - * Creates a braces string from an AST, or an AST node. - * - * ```js - * const braces = require('braces'); - * let ast = braces.parse('foo/{a,b}/bar'); - * console.log(stringify(ast.nodes[2])); //=> '{a,b}' - * ``` - * @param {String} `input` Brace pattern or AST. - * @param {Object} `options` - * @return {Array} Returns an array of expanded values. - * @api public - */ + files.push({ + relative, + basename: name, + absolute: loc, + mtime: +stat.mtime + }); -braces.stringify = (input, options = {}) => { - if (typeof input === 'string') { - return stringify(braces.parse(input, options), options); - } - return stringify(input, options); -}; + if (stat.isDirectory()) { + files = files.concat((yield walk(loc, relative, ignoreBasenames))); + } + } -/** - * Compiles a brace pattern into a regex-compatible, optimized string. - * This method is called by the main [braces](#braces) function by default. - * - * ```js - * const braces = require('braces'); - * console.log(braces.compile('a/{b,c}/d')); - * //=> ['a/(b|c)/d'] - * ``` - * @param {String} `input` Brace pattern or AST. - * @param {Object} `options` - * @return {Array} Returns an array of expanded values. - * @api public - */ + return files; + }); -braces.compile = (input, options = {}) => { - if (typeof input === 'string') { - input = braces.parse(input, options); - } - return compile(input, options); -}; + return function walk(_x26, _x27) { + return _ref27.apply(this, arguments); + }; +})(); -/** - * Expands a brace pattern into an array. This method is called by the - * main [braces](#braces) function when `options.expand` is true. Before - * using this method it's recommended that you read the [performance notes](#performance)) - * and advantages of using [.compile](#compile) instead. - * - * ```js - * const braces = require('braces'); - * console.log(braces.expand('a/{b,c}/d')); - * //=> ['a/b/d', 'a/c/d']; - * ``` - * @param {String} `pattern` Brace pattern - * @param {Object} `options` - * @return {Array} Returns an array of expanded values. - * @api public - */ +let getFileSizeOnDisk = exports.getFileSizeOnDisk = (() => { + var _ref29 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (loc) { + const stat = yield lstat(loc); + const size = stat.size, + blockSize = stat.blksize; -braces.expand = (input, options = {}) => { - if (typeof input === 'string') { - input = braces.parse(input, options); - } - let result = expand(input, options); + return Math.ceil(size / blockSize) * blockSize; + }); - // filter out empty strings if specified - if (options.noempty === true) { - result = result.filter(Boolean); - } + return function getFileSizeOnDisk(_x28) { + return _ref29.apply(this, arguments); + }; +})(); - // filter out duplicates if specified - if (options.nodupes === true) { - result = [...new Set(result)]; - } +let getEolFromFile = (() => { + var _ref30 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (path) { + if (!(yield exists(path))) { + return undefined; + } - return result; -}; + const buffer = yield readFileBuffer(path); -/** - * Processes a brace pattern and returns either an expanded array - * (if `options.expand` is true), a highly optimized regex-compatible string. - * This method is called by the main [braces](#braces) function. - * - * ```js - * const braces = require('braces'); - * console.log(braces.create('user-{200..300}/project-{a,b,c}-{1..10}')) - * //=> 'user-(20[0-9]|2[1-9][0-9]|300)/project-(a|b|c)-([1-9]|10)' - * ``` - * @param {String} `pattern` Brace pattern - * @param {Object} `options` - * @return {Array} Returns an array of expanded values. - * @api public - */ + for (let i = 0; i < buffer.length; ++i) { + if (buffer[i] === cr) { + return '\r\n'; + } + if (buffer[i] === lf) { + return '\n'; + } + } + return undefined; + }); -braces.create = (input, options = {}) => { - if (input === '' || input.length < 3) { - return [input]; - } + return function getEolFromFile(_x29) { + return _ref30.apply(this, arguments); + }; +})(); - return options.expand !== true - ? braces.compile(input, options) - : braces.expand(input, options); -}; +let writeFilePreservingEol = exports.writeFilePreservingEol = (() => { + var _ref31 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (path, data) { + const eol = (yield getEolFromFile(path)) || (_os || _load_os()).default.EOL; + if (eol !== '\n') { + data = data.replace(/\n/g, eol); + } + yield writeFile(path, data); + }); -/** - * Expose "braces" - */ + return function writeFilePreservingEol(_x30, _x31) { + return _ref31.apply(this, arguments); + }; +})(); -module.exports = braces; +let hardlinksWork = exports.hardlinksWork = (() => { + var _ref32 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (dir) { + const filename = 'test-file' + Math.random(); + const file = (_path || _load_path()).default.join(dir, filename); + const fileLink = (_path || _load_path()).default.join(dir, filename + '-link'); + try { + yield writeFile(file, 'test'); + yield link(file, fileLink); + } catch (err) { + return false; + } finally { + yield (0, (_fsNormalized || _load_fsNormalized()).unlink)(file); + yield (0, (_fsNormalized || _load_fsNormalized()).unlink)(fileLink); + } + return true; + }); + return function hardlinksWork(_x32) { + return _ref32.apply(this, arguments); + }; +})(); -/***/ }), -/* 191 */ -/***/ (function(module, exports, __webpack_require__) { +// not a strict polyfill for Node's fs.mkdtemp -"use strict"; +let makeTempDir = exports.makeTempDir = (() => { + var _ref33 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (prefix) { + const dir = (_path || _load_path()).default.join((_os || _load_os()).default.tmpdir(), `yarn-${prefix || ''}-${Date.now()}-${Math.random()}`); + yield (0, (_fsNormalized || _load_fsNormalized()).unlink)(dir); + yield mkdirp(dir); + return dir; + }); -const utils = __webpack_require__(192); + return function makeTempDir(_x33) { + return _ref33.apply(this, arguments); + }; +})(); -module.exports = (ast, options = {}) => { - let stringify = (node, parent = {}) => { - let invalidBlock = options.escapeInvalid && utils.isInvalidBrace(parent); - let invalidNode = node.invalid === true && options.escapeInvalid === true; - let output = ''; +let readFirstAvailableStream = exports.readFirstAvailableStream = (() => { + var _ref34 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (paths) { + for (var _iterator15 = paths, _isArray15 = Array.isArray(_iterator15), _i15 = 0, _iterator15 = _isArray15 ? _iterator15 : _iterator15[Symbol.iterator]();;) { + var _ref35; - if (node.value) { - if ((invalidBlock || invalidNode) && utils.isOpenOrClose(node)) { - return '\\' + node.value; + if (_isArray15) { + if (_i15 >= _iterator15.length) break; + _ref35 = _iterator15[_i15++]; + } else { + _i15 = _iterator15.next(); + if (_i15.done) break; + _ref35 = _i15.value; } - return node.value; - } - if (node.value) { - return node.value; - } + const path = _ref35; - if (node.nodes) { - for (let child of node.nodes) { - output += stringify(child); + try { + const fd = yield open(path, 'r'); + return (_fs || _load_fs()).default.createReadStream(path, { fd }); + } catch (err) { + // Try the next one } } - return output; + return null; + }); + + return function readFirstAvailableStream(_x34) { + return _ref34.apply(this, arguments); }; +})(); - return stringify(ast); -}; +let getFirstSuitableFolder = exports.getFirstSuitableFolder = (() => { + var _ref36 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (paths, mode = constants.W_OK | constants.X_OK) { + const result = { + skipped: [], + folder: null + }; + for (var _iterator16 = paths, _isArray16 = Array.isArray(_iterator16), _i16 = 0, _iterator16 = _isArray16 ? _iterator16 : _iterator16[Symbol.iterator]();;) { + var _ref37; + if (_isArray16) { + if (_i16 >= _iterator16.length) break; + _ref37 = _iterator16[_i16++]; + } else { + _i16 = _iterator16.next(); + if (_i16.done) break; + _ref37 = _i16.value; + } -/***/ }), -/* 192 */ -/***/ (function(module, exports, __webpack_require__) { + const folder = _ref37; -"use strict"; + try { + yield mkdirp(folder); + yield access(folder, mode); + result.folder = folder; -exports.isInteger = num => { - if (typeof num === 'number') { - return Number.isInteger(num); - } - if (typeof num === 'string' && num.trim() !== '') { - return Number.isInteger(Number(num)); - } - return false; -}; + return result; + } catch (error) { + result.skipped.push({ + error, + folder + }); + } + } + return result; + }); -/** - * Find a node of the given type - */ + return function getFirstSuitableFolder(_x35) { + return _ref36.apply(this, arguments); + }; +})(); -exports.find = (node, type) => node.nodes.find(node => node.type === type); +exports.copy = copy; +exports.readFile = readFile; +exports.readFileRaw = readFileRaw; +exports.normalizeOS = normalizeOS; -/** - * Find a node of the given type - */ +var _fs; -exports.exceedsLimit = (min, max, step = 1, limit) => { - if (limit === false) return false; - if (!exports.isInteger(min) || !exports.isInteger(max)) return false; - return ((Number(max) - Number(min)) / Number(step)) >= limit; -}; +function _load_fs() { + return _fs = _interopRequireDefault(__webpack_require__(3)); +} -/** - * Escape the given node with '\\' before node.value - */ +var _glob; -exports.escapeNode = (block, n = 0, type) => { - let node = block.nodes[n]; - if (!node) return; +function _load_glob() { + return _glob = _interopRequireDefault(__webpack_require__(75)); +} - if ((type && node.type === type) || node.type === 'open' || node.type === 'close') { - if (node.escaped !== true) { - node.value = '\\' + node.value; - node.escaped = true; - } - } -}; +var _os; -/** - * Returns true if the given brace node should be enclosed in literal braces - */ +function _load_os() { + return _os = _interopRequireDefault(__webpack_require__(36)); +} -exports.encloseBrace = node => { - if (node.type !== 'brace') return false; - if ((node.commas >> 0 + node.ranges >> 0) === 0) { - node.invalid = true; - return true; - } - return false; -}; +var _path; -/** - * Returns true if a brace node is invalid. - */ +function _load_path() { + return _path = _interopRequireDefault(__webpack_require__(0)); +} -exports.isInvalidBrace = block => { - if (block.type !== 'brace') return false; - if (block.invalid === true || block.dollar) return true; - if ((block.commas >> 0 + block.ranges >> 0) === 0) { - block.invalid = true; - return true; - } - if (block.open !== true || block.close !== true) { - block.invalid = true; - return true; - } - return false; -}; +var _blockingQueue; -/** - * Returns true if a node is an open or close node - */ +function _load_blockingQueue() { + return _blockingQueue = _interopRequireDefault(__webpack_require__(84)); +} -exports.isOpenOrClose = node => { - if (node.type === 'open' || node.type === 'close') { - return true; - } - return node.open === true || node.close === true; -}; +var _promise; -/** - * Reduce an array of text nodes. - */ +function _load_promise() { + return _promise = _interopRequireWildcard(__webpack_require__(40)); +} -exports.reduce = nodes => nodes.reduce((acc, node) => { - if (node.type === 'text') acc.push(node.value); - if (node.type === 'range') node.type = 'text'; - return acc; -}, []); +var _promise2; -/** - * Flatten an array - */ +function _load_promise2() { + return _promise2 = __webpack_require__(40); +} -exports.flatten = (...args) => { - const result = []; - const flat = arr => { - for (let i = 0; i < arr.length; i++) { - let ele = arr[i]; - Array.isArray(ele) ? flat(ele, result) : ele !== void 0 && result.push(ele); - } - return result; - }; - flat(args); - return result; -}; +var _map; +function _load_map() { + return _map = _interopRequireDefault(__webpack_require__(20)); +} -/***/ }), -/* 193 */ -/***/ (function(module, exports, __webpack_require__) { +var _fsNormalized; -"use strict"; +function _load_fsNormalized() { + return _fsNormalized = __webpack_require__(164); +} +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } -const fill = __webpack_require__(194); -const utils = __webpack_require__(192); +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -const compile = (ast, options = {}) => { - let walk = (node, parent = {}) => { - let invalidBlock = utils.isInvalidBrace(parent); - let invalidNode = node.invalid === true && options.escapeInvalid === true; - let invalid = invalidBlock === true || invalidNode === true; - let prefix = options.escapeInvalid === true ? '\\' : ''; - let output = ''; +const constants = exports.constants = typeof (_fs || _load_fs()).default.constants !== 'undefined' ? (_fs || _load_fs()).default.constants : { + R_OK: (_fs || _load_fs()).default.R_OK, + W_OK: (_fs || _load_fs()).default.W_OK, + X_OK: (_fs || _load_fs()).default.X_OK +}; - if (node.isOpen === true) { - return prefix + node.value; - } - if (node.isClose === true) { - return prefix + node.value; - } +const lockQueue = exports.lockQueue = new (_blockingQueue || _load_blockingQueue()).default('fs lock'); - if (node.type === 'open') { - return invalid ? (prefix + node.value) : '('; - } +const readFileBuffer = exports.readFileBuffer = (0, (_promise2 || _load_promise2()).promisify)((_fs || _load_fs()).default.readFile); +const open = exports.open = (0, (_promise2 || _load_promise2()).promisify)((_fs || _load_fs()).default.open); +const writeFile = exports.writeFile = (0, (_promise2 || _load_promise2()).promisify)((_fs || _load_fs()).default.writeFile); +const readlink = exports.readlink = (0, (_promise2 || _load_promise2()).promisify)((_fs || _load_fs()).default.readlink); +const realpath = exports.realpath = (0, (_promise2 || _load_promise2()).promisify)((_fs || _load_fs()).default.realpath); +const readdir = exports.readdir = (0, (_promise2 || _load_promise2()).promisify)((_fs || _load_fs()).default.readdir); +const rename = exports.rename = (0, (_promise2 || _load_promise2()).promisify)((_fs || _load_fs()).default.rename); +const access = exports.access = (0, (_promise2 || _load_promise2()).promisify)((_fs || _load_fs()).default.access); +const stat = exports.stat = (0, (_promise2 || _load_promise2()).promisify)((_fs || _load_fs()).default.stat); +const mkdirp = exports.mkdirp = (0, (_promise2 || _load_promise2()).promisify)(__webpack_require__(116)); +const exists = exports.exists = (0, (_promise2 || _load_promise2()).promisify)((_fs || _load_fs()).default.exists, true); +const lstat = exports.lstat = (0, (_promise2 || _load_promise2()).promisify)((_fs || _load_fs()).default.lstat); +const chmod = exports.chmod = (0, (_promise2 || _load_promise2()).promisify)((_fs || _load_fs()).default.chmod); +const link = exports.link = (0, (_promise2 || _load_promise2()).promisify)((_fs || _load_fs()).default.link); +const glob = exports.glob = (0, (_promise2 || _load_promise2()).promisify)((_glob || _load_glob()).default); +exports.unlink = (_fsNormalized || _load_fsNormalized()).unlink; - if (node.type === 'close') { - return invalid ? (prefix + node.value) : ')'; - } +// fs.copyFile uses the native file copying instructions on the system, performing much better +// than any JS-based solution and consumes fewer resources. Repeated testing to fine tune the +// concurrency level revealed 128 as the sweet spot on a quad-core, 16 CPU Intel system with SSD. - if (node.type === 'comma') { - return node.prev.type === 'comma' ? '' : (invalid ? node.value : '|'); - } +const CONCURRENT_QUEUE_ITEMS = (_fs || _load_fs()).default.copyFile ? 128 : 4; - if (node.value) { - return node.value; - } +const fsSymlink = (0, (_promise2 || _load_promise2()).promisify)((_fs || _load_fs()).default.symlink); +const invariant = __webpack_require__(7); +const stripBOM = __webpack_require__(122); - if (node.nodes && node.ranges > 0) { - let args = utils.reduce(node.nodes); - let range = fill(...args, { ...options, wrap: false, toRegex: true }); +const noop = () => {}; - if (range.length !== 0) { - return args.length > 1 && range.length > 1 ? `(${range})` : range; - } - } +function copy(src, dest, reporter) { + return copyBulk([{ src, dest }], reporter); +} - if (node.nodes) { - for (let child of node.nodes) { - output += walk(child, node); +function _readFile(loc, encoding) { + return new Promise((resolve, reject) => { + (_fs || _load_fs()).default.readFile(loc, encoding, function (err, content) { + if (err) { + reject(err); + } else { + resolve(content); } - } - return output; - }; + }); + }); +} - return walk(ast); -}; +function readFile(loc) { + return _readFile(loc, 'utf8').then(normalizeOS); +} -module.exports = compile; +function readFileRaw(loc) { + return _readFile(loc, 'binary'); +} + +function normalizeOS(body) { + return body.replace(/\r\n/g, '\n'); +} +const cr = '\r'.charCodeAt(0); +const lf = '\n'.charCodeAt(0); /***/ }), -/* 194 */ +/* 6 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -/*! - * fill-range - * - * Copyright (c) 2014-present, Jon Schlinkert. - * Licensed under the MIT License. - */ +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.getPathKey = getPathKey; +const os = __webpack_require__(36); +const path = __webpack_require__(0); +const userHome = __webpack_require__(45).default; -const util = __webpack_require__(29); -const toRegexRange = __webpack_require__(195); +var _require = __webpack_require__(171); -const isObject = val => val !== null && typeof val === 'object' && !Array.isArray(val); +const getCacheDir = _require.getCacheDir, + getConfigDir = _require.getConfigDir, + getDataDir = _require.getDataDir; -const transform = toNumber => { - return value => toNumber === true ? Number(value) : String(value); -}; +const isWebpackBundle = __webpack_require__(227); -const isValidValue = value => { - return typeof value === 'number' || (typeof value === 'string' && value !== ''); -}; +const DEPENDENCY_TYPES = exports.DEPENDENCY_TYPES = ['devDependencies', 'dependencies', 'optionalDependencies', 'peerDependencies']; +const RESOLUTIONS = exports.RESOLUTIONS = 'resolutions'; +const MANIFEST_FIELDS = exports.MANIFEST_FIELDS = [RESOLUTIONS, ...DEPENDENCY_TYPES]; -const isNumber = num => Number.isInteger(+num); +const SUPPORTED_NODE_VERSIONS = exports.SUPPORTED_NODE_VERSIONS = '^4.8.0 || ^5.7.0 || ^6.2.2 || >=8.0.0'; -const zeros = input => { - let value = `${input}`; - let index = -1; - if (value[0] === '-') value = value.slice(1); - if (value === '0') return false; - while (value[++index] === '0'); - return index > 0; -}; +const YARN_REGISTRY = exports.YARN_REGISTRY = 'https://registry.yarnpkg.com'; -const stringify = (start, end, options) => { - if (typeof start === 'string' || typeof end === 'string') { - return true; - } - return options.stringify === true; -}; +const YARN_DOCS = exports.YARN_DOCS = 'https://yarnpkg.com/en/docs/cli/'; +const YARN_INSTALLER_SH = exports.YARN_INSTALLER_SH = 'https://yarnpkg.com/install.sh'; +const YARN_INSTALLER_MSI = exports.YARN_INSTALLER_MSI = 'https://yarnpkg.com/latest.msi'; -const pad = (input, maxLength, toNumber) => { - if (maxLength > 0) { - let dash = input[0] === '-' ? '-' : ''; - if (dash) input = input.slice(1); - input = (dash + input.padStart(dash ? maxLength - 1 : maxLength, '0')); - } - if (toNumber === false) { - return String(input); - } - return input; -}; +const SELF_UPDATE_VERSION_URL = exports.SELF_UPDATE_VERSION_URL = 'https://yarnpkg.com/latest-version'; -const toMaxLen = (input, maxLength) => { - let negative = input[0] === '-' ? '-' : ''; - if (negative) { - input = input.slice(1); - maxLength--; - } - while (input.length < maxLength) input = '0' + input; - return negative ? ('-' + input) : input; -}; +// cache version, bump whenever we make backwards incompatible changes +const CACHE_VERSION = exports.CACHE_VERSION = 2; -const toSequence = (parts, options) => { - parts.negatives.sort((a, b) => a < b ? -1 : a > b ? 1 : 0); - parts.positives.sort((a, b) => a < b ? -1 : a > b ? 1 : 0); +// lockfile version, bump whenever we make backwards incompatible changes +const LOCKFILE_VERSION = exports.LOCKFILE_VERSION = 1; - let prefix = options.capture ? '' : '?:'; - let positives = ''; - let negatives = ''; - let result; +// max amount of network requests to perform concurrently +const NETWORK_CONCURRENCY = exports.NETWORK_CONCURRENCY = 8; - if (parts.positives.length) { - positives = parts.positives.join('|'); - } +// HTTP timeout used when downloading packages +const NETWORK_TIMEOUT = exports.NETWORK_TIMEOUT = 30 * 1000; // in milliseconds - if (parts.negatives.length) { - negatives = `-(${prefix}${parts.negatives.join('|')})`; +// max amount of child processes to execute concurrently +const CHILD_CONCURRENCY = exports.CHILD_CONCURRENCY = 5; + +const REQUIRED_PACKAGE_KEYS = exports.REQUIRED_PACKAGE_KEYS = ['name', 'version', '_uid']; + +function getPreferredCacheDirectories() { + const preferredCacheDirectories = [getCacheDir()]; + + if (process.getuid) { + // $FlowFixMe: process.getuid exists, dammit + preferredCacheDirectories.push(path.join(os.tmpdir(), `.yarn-cache-${process.getuid()}`)); } - if (positives && negatives) { - result = `${positives}|${negatives}`; + preferredCacheDirectories.push(path.join(os.tmpdir(), `.yarn-cache`)); + + return preferredCacheDirectories; +} + +const PREFERRED_MODULE_CACHE_DIRECTORIES = exports.PREFERRED_MODULE_CACHE_DIRECTORIES = getPreferredCacheDirectories(); +const CONFIG_DIRECTORY = exports.CONFIG_DIRECTORY = getConfigDir(); +const DATA_DIRECTORY = exports.DATA_DIRECTORY = getDataDir(); +const LINK_REGISTRY_DIRECTORY = exports.LINK_REGISTRY_DIRECTORY = path.join(DATA_DIRECTORY, 'link'); +const GLOBAL_MODULE_DIRECTORY = exports.GLOBAL_MODULE_DIRECTORY = path.join(DATA_DIRECTORY, 'global'); + +const NODE_BIN_PATH = exports.NODE_BIN_PATH = process.execPath; +const YARN_BIN_PATH = exports.YARN_BIN_PATH = getYarnBinPath(); + +// Webpack needs to be configured with node.__dirname/__filename = false +function getYarnBinPath() { + if (isWebpackBundle) { + return __filename; } else { - result = positives || negatives; + return path.join(__dirname, '..', 'bin', 'yarn.js'); } +} - if (options.wrap) { - return `(${prefix}${result})`; - } +const NODE_MODULES_FOLDER = exports.NODE_MODULES_FOLDER = 'node_modules'; +const NODE_PACKAGE_JSON = exports.NODE_PACKAGE_JSON = 'package.json'; - return result; -}; +const POSIX_GLOBAL_PREFIX = exports.POSIX_GLOBAL_PREFIX = `${process.env.DESTDIR || ''}/usr/local`; +const FALLBACK_GLOBAL_PREFIX = exports.FALLBACK_GLOBAL_PREFIX = path.join(userHome, '.yarn'); -const toRange = (a, b, isNumbers, options) => { - if (isNumbers) { - return toRegexRange(a, b, { wrap: false, ...options }); - } +const META_FOLDER = exports.META_FOLDER = '.yarn-meta'; +const INTEGRITY_FILENAME = exports.INTEGRITY_FILENAME = '.yarn-integrity'; +const LOCKFILE_FILENAME = exports.LOCKFILE_FILENAME = 'yarn.lock'; +const METADATA_FILENAME = exports.METADATA_FILENAME = '.yarn-metadata.json'; +const TARBALL_FILENAME = exports.TARBALL_FILENAME = '.yarn-tarball.tgz'; +const CLEAN_FILENAME = exports.CLEAN_FILENAME = '.yarnclean'; - let start = String.fromCharCode(a); - if (a === b) return start; +const NPM_LOCK_FILENAME = exports.NPM_LOCK_FILENAME = 'package-lock.json'; +const NPM_SHRINKWRAP_FILENAME = exports.NPM_SHRINKWRAP_FILENAME = 'npm-shrinkwrap.json'; - let stop = String.fromCharCode(b); - return `[${start}-${stop}]`; -}; +const DEFAULT_INDENT = exports.DEFAULT_INDENT = ' '; +const SINGLE_INSTANCE_PORT = exports.SINGLE_INSTANCE_PORT = 31997; +const SINGLE_INSTANCE_FILENAME = exports.SINGLE_INSTANCE_FILENAME = '.yarn-single-instance'; -const toRegex = (start, end, options) => { - if (Array.isArray(start)) { - let wrap = options.wrap === true; - let prefix = options.capture ? '' : '?:'; - return wrap ? `(${prefix}${start.join('|')})` : start.join('|'); - } - return toRegexRange(start, end, options); -}; +const ENV_PATH_KEY = exports.ENV_PATH_KEY = getPathKey(process.platform, process.env); -const rangeError = (...args) => { - return new RangeError('Invalid range arguments: ' + util.inspect(...args)); -}; +function getPathKey(platform, env) { + let pathKey = 'PATH'; -const invalidRange = (start, end, options) => { - if (options.strictRanges === true) throw rangeError([start, end]); - return []; -}; + // windows calls its path "Path" usually, but this is not guaranteed. + if (platform === 'win32') { + pathKey = 'Path'; -const invalidStep = (step, options) => { - if (options.strictRanges === true) { - throw new TypeError(`Expected step "${step}" to be a number`); + for (const key in env) { + if (key.toLowerCase() === 'path') { + pathKey = key; + } + } } - return []; + + return pathKey; +} + +const VERSION_COLOR_SCHEME = exports.VERSION_COLOR_SCHEME = { + major: 'red', + premajor: 'red', + minor: 'yellow', + preminor: 'yellow', + patch: 'green', + prepatch: 'green', + prerelease: 'red', + unchanged: 'white', + unknown: 'red' }; -const fillNumbers = (start, end, step = 1, options = {}) => { - let a = Number(start); - let b = Number(end); +/***/ }), +/* 7 */ +/***/ (function(module, exports, __webpack_require__) { - if (!Number.isInteger(a) || !Number.isInteger(b)) { - if (options.strictRanges === true) throw rangeError([start, end]); - return []; - } +"use strict"; +/** + * Copyright (c) 2013-present, Facebook, Inc. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ - // fix negative zero - if (a === 0) a = 0; - if (b === 0) b = 0; - let descending = a > b; - let startString = String(start); - let endString = String(end); - let stepString = String(step); - step = Math.max(Math.abs(step), 1); - let padded = zeros(startString) || zeros(endString) || zeros(stepString); - let maxLen = padded ? Math.max(startString.length, endString.length, stepString.length) : 0; - let toNumber = padded === false && stringify(start, end, options) === false; - let format = options.transform || transform(toNumber); +/** + * Use invariant() to assert state which your program assumes to be true. + * + * Provide sprintf-style format (only %s is supported) and arguments + * to provide information about what broke and what you were + * expecting. + * + * The invariant message will be stripped in production, but the invariant + * will remain to ensure logic does not differ in production. + */ + +var NODE_ENV = "none"; - if (options.toRegex && step === 1) { - return toRange(toMaxLen(start, maxLen), toMaxLen(end, maxLen), true, options); +var invariant = function(condition, format, a, b, c, d, e, f) { + if (NODE_ENV !== 'production') { + if (format === undefined) { + throw new Error('invariant requires an error message argument'); + } } - let parts = { negatives: [], positives: [] }; - let push = num => parts[num < 0 ? 'negatives' : 'positives'].push(Math.abs(num)); - let range = []; - let index = 0; - - while (descending ? a >= b : a <= b) { - if (options.toRegex === true && step > 1) { - push(a); + if (!condition) { + var error; + if (format === undefined) { + error = new Error( + 'Minified exception occurred; use the non-minified dev environment ' + + 'for the full error message and additional helpful warnings.' + ); } else { - range.push(pad(format(a, index), maxLen, toNumber)); + var args = [a, b, c, d, e, f]; + var argIndex = 0; + error = new Error( + format.replace(/%s/g, function() { return args[argIndex++]; }) + ); + error.name = 'Invariant Violation'; } - a = descending ? a - step : a + step; - index++; - } - if (options.toRegex === true) { - return step > 1 - ? toSequence(parts, options) - : toRegex(range, null, { wrap: false, ...options }); + error.framesToPop = 1; // we don't care about invariant's own frame + throw error; } - - return range; }; -const fillLetters = (start, end, step = 1, options = {}) => { - if ((!isNumber(start) && start.length > 1) || (!isNumber(end) && end.length > 1)) { - return invalidRange(start, end, options); - } +module.exports = invariant; - let format = options.transform || (val => String.fromCharCode(val)); - let a = `${start}`.charCodeAt(0); - let b = `${end}`.charCodeAt(0); +/***/ }), +/* 8 */, +/* 9 */ +/***/ (function(module, exports) { - let descending = a > b; - let min = Math.min(a, b); - let max = Math.max(a, b); +module.exports = __webpack_require__(582); - if (options.toRegex && step === 1) { - return toRange(min, max, false, options); - } +/***/ }), +/* 10 */, +/* 11 */ +/***/ (function(module, exports) { - let range = []; - let index = 0; +// https://github.com/zloirock/core-js/issues/86#issuecomment-115759028 +var global = module.exports = typeof window != 'undefined' && window.Math == Math + ? window : typeof self != 'undefined' && self.Math == Math ? self + // eslint-disable-next-line no-new-func + : Function('return this')(); +if (typeof __g == 'number') __g = global; // eslint-disable-line no-undef - while (descending ? a >= b : a <= b) { - range.push(format(a, index)); - a = descending ? a - step : a + step; - index++; - } - if (options.toRegex === true) { - return toRegex(range, null, { wrap: false, options }); - } +/***/ }), +/* 12 */ +/***/ (function(module, exports, __webpack_require__) { - return range; -}; +"use strict"; -const fill = (start, end, step, options = {}) => { - if (end == null && isValidValue(start)) { - return [start]; + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.sortAlpha = sortAlpha; +exports.entries = entries; +exports.removePrefix = removePrefix; +exports.removeSuffix = removeSuffix; +exports.addSuffix = addSuffix; +exports.hyphenate = hyphenate; +exports.camelCase = camelCase; +exports.compareSortedArrays = compareSortedArrays; +exports.sleep = sleep; +const _camelCase = __webpack_require__(176); + +function sortAlpha(a, b) { + // sort alphabetically in a deterministic way + const shortLen = Math.min(a.length, b.length); + for (let i = 0; i < shortLen; i++) { + const aChar = a.charCodeAt(i); + const bChar = b.charCodeAt(i); + if (aChar !== bChar) { + return aChar - bChar; + } } + return a.length - b.length; +} - if (!isValidValue(start) || !isValidValue(end)) { - return invalidRange(start, end, options); +function entries(obj) { + const entries = []; + if (obj) { + for (const key in obj) { + entries.push([key, obj[key]]); + } } + return entries; +} - if (typeof step === 'function') { - return fill(start, end, 1, { transform: step }); +function removePrefix(pattern, prefix) { + if (pattern.startsWith(prefix)) { + pattern = pattern.slice(prefix.length); } - if (isObject(step)) { - return fill(start, end, 0, step); + return pattern; +} + +function removeSuffix(pattern, suffix) { + if (pattern.endsWith(suffix)) { + return pattern.slice(0, -suffix.length); } - let opts = { ...options }; - if (opts.capture === true) opts.wrap = true; - step = step || opts.step || 1; + return pattern; +} - if (!isNumber(step)) { - if (step != null && !isObject(step)) return invalidStep(step, opts); - return fill(start, end, 1, step); +function addSuffix(pattern, suffix) { + if (!pattern.endsWith(suffix)) { + return pattern + suffix; } - if (isNumber(start) && isNumber(end)) { - return fillNumbers(start, end, step, opts); + return pattern; +} + +function hyphenate(str) { + return str.replace(/[A-Z]/g, match => { + return '-' + match.charAt(0).toLowerCase(); + }); +} + +function camelCase(str) { + if (/[A-Z]/.test(str)) { + return null; + } else { + return _camelCase(str); } +} - return fillLetters(start, end, Math.max(Math.abs(step), 1), opts); +function compareSortedArrays(array1, array2) { + if (array1.length !== array2.length) { + return false; + } + for (let i = 0, len = array1.length; i < len; i++) { + if (array1[i] !== array2[i]) { + return false; + } + } + return true; +} + +function sleep(ms) { + return new Promise(resolve => { + setTimeout(resolve, ms); + }); +} + +/***/ }), +/* 13 */ +/***/ (function(module, exports, __webpack_require__) { + +var store = __webpack_require__(107)('wks'); +var uid = __webpack_require__(111); +var Symbol = __webpack_require__(11).Symbol; +var USE_SYMBOL = typeof Symbol == 'function'; + +var $exports = module.exports = function (name) { + return store[name] || (store[name] = + USE_SYMBOL && Symbol[name] || (USE_SYMBOL ? Symbol : uid)('Symbol.' + name)); }; -module.exports = fill; +$exports.store = store; /***/ }), -/* 195 */ +/* 14 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -/*! - * to-regex-range - * - * Copyright (c) 2015-present, Jon Schlinkert. - * Released under the MIT License. - */ +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.stringify = exports.parse = undefined; + +var _asyncToGenerator2; -const isNumber = __webpack_require__(196); +function _load_asyncToGenerator() { + return _asyncToGenerator2 = _interopRequireDefault(__webpack_require__(1)); +} -const toRegexRange = (min, max, options) => { - if (isNumber(min) === false) { - throw new TypeError('toRegexRange: expected the first argument to be a number'); - } +var _parse; - if (max === void 0 || min === max) { - return String(min); - } +function _load_parse() { + return _parse = __webpack_require__(81); +} - if (isNumber(max) === false) { - throw new TypeError('toRegexRange: expected the second argument to be a number.'); +Object.defineProperty(exports, 'parse', { + enumerable: true, + get: function get() { + return _interopRequireDefault(_parse || _load_parse()).default; } +}); - let opts = { relaxZeros: true, ...options }; - if (typeof opts.strictZeros === 'boolean') { - opts.relaxZeros = opts.strictZeros === false; - } +var _stringify; - let relax = String(opts.relaxZeros); - let shorthand = String(opts.shorthand); - let capture = String(opts.capture); - let wrap = String(opts.wrap); - let cacheKey = min + ':' + max + '=' + relax + shorthand + capture + wrap; +function _load_stringify() { + return _stringify = __webpack_require__(150); +} - if (toRegexRange.cache.hasOwnProperty(cacheKey)) { - return toRegexRange.cache[cacheKey].result; +Object.defineProperty(exports, 'stringify', { + enumerable: true, + get: function get() { + return _interopRequireDefault(_stringify || _load_stringify()).default; } +}); +exports.implodeEntry = implodeEntry; +exports.explodeEntry = explodeEntry; - let a = Math.min(min, max); - let b = Math.max(min, max); +var _misc; - if (Math.abs(a - b) === 1) { - let result = min + '|' + max; - if (opts.capture) { - return `(${result})`; - } - if (opts.wrap === false) { - return result; - } - return `(?:${result})`; - } +function _load_misc() { + return _misc = __webpack_require__(12); +} - let isPadded = hasPadding(min) || hasPadding(max); - let state = { min, max, a, b }; - let positives = []; - let negatives = []; +var _normalizePattern; - if (isPadded) { - state.isPadded = isPadded; - state.maxLen = String(state.max).length; - } +function _load_normalizePattern() { + return _normalizePattern = __webpack_require__(29); +} - if (a < 0) { - let newMin = b < 0 ? Math.abs(b) : 1; - negatives = splitToPatterns(newMin, Math.abs(a), state, opts); - a = state.a = 0; - } +var _parse2; - if (b >= 0) { - positives = splitToPatterns(a, b, state, opts); - } +function _load_parse2() { + return _parse2 = _interopRequireDefault(__webpack_require__(81)); +} - state.negatives = negatives; - state.positives = positives; - state.result = collatePatterns(negatives, positives, opts); +var _constants; - if (opts.capture === true) { - state.result = `(${state.result})`; - } else if (opts.wrap !== false && (positives.length + negatives.length) > 1) { - state.result = `(?:${state.result})`; - } +function _load_constants() { + return _constants = __webpack_require__(6); +} - toRegexRange.cache[cacheKey] = state; - return state.result; -}; +var _fs; -function collatePatterns(neg, pos, options) { - let onlyNegative = filterPatterns(neg, pos, '-', false, options) || []; - let onlyPositive = filterPatterns(pos, neg, '', false, options) || []; - let intersected = filterPatterns(neg, pos, '-?', true, options) || []; - let subpatterns = onlyNegative.concat(intersected).concat(onlyPositive); - return subpatterns.join('|'); +function _load_fs() { + return _fs = _interopRequireWildcard(__webpack_require__(5)); } -function splitToRanges(min, max) { - let nines = 1; - let zeros = 1; +function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } - let stop = countNines(min, nines); - let stops = new Set([max]); +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - while (min <= stop && stop <= max) { - stops.add(stop); - nines += 1; - stop = countNines(min, nines); - } +const invariant = __webpack_require__(7); - stop = countZeros(max + 1, zeros) - 1; +const path = __webpack_require__(0); +const ssri = __webpack_require__(55); - while (min < stop && stop <= max) { - stops.add(stop); - zeros += 1; - stop = countZeros(max + 1, zeros) - 1; - } +function getName(pattern) { + return (0, (_normalizePattern || _load_normalizePattern()).normalizePattern)(pattern).name; +} - stops = [...stops]; - stops.sort(compare); - return stops; +function blankObjectUndefined(obj) { + return obj && Object.keys(obj).length ? obj : undefined; } -/** - * Convert a range to a regex pattern - * @param {Number} `start` - * @param {Number} `stop` - * @return {String} - */ +function keyForRemote(remote) { + return remote.resolved || (remote.reference && remote.hash ? `${remote.reference}#${remote.hash}` : null); +} -function rangeToPattern(start, stop, options) { - if (start === stop) { - return { pattern: start, count: [], digits: 0 }; +function serializeIntegrity(integrity) { + // We need this because `Integrity.toString()` does not use sorting to ensure a stable string output + // See https://git.io/vx2Hy + return integrity.toString().split(' ').sort().join(' '); +} + +function implodeEntry(pattern, obj) { + const inferredName = getName(pattern); + const integrity = obj.integrity ? serializeIntegrity(obj.integrity) : ''; + const imploded = { + name: inferredName === obj.name ? undefined : obj.name, + version: obj.version, + uid: obj.uid === obj.version ? undefined : obj.uid, + resolved: obj.resolved, + registry: obj.registry === 'npm' ? undefined : obj.registry, + dependencies: blankObjectUndefined(obj.dependencies), + optionalDependencies: blankObjectUndefined(obj.optionalDependencies), + permissions: blankObjectUndefined(obj.permissions), + prebuiltVariants: blankObjectUndefined(obj.prebuiltVariants) + }; + if (integrity) { + imploded.integrity = integrity; } + return imploded; +} - let zipped = zip(start, stop); - let digits = zipped.length; - let pattern = ''; - let count = 0; +function explodeEntry(pattern, obj) { + obj.optionalDependencies = obj.optionalDependencies || {}; + obj.dependencies = obj.dependencies || {}; + obj.uid = obj.uid || obj.version; + obj.permissions = obj.permissions || {}; + obj.registry = obj.registry || 'npm'; + obj.name = obj.name || getName(pattern); + const integrity = obj.integrity; + if (integrity && integrity.isIntegrity) { + obj.integrity = ssri.parse(integrity); + } + return obj; +} - for (let i = 0; i < digits; i++) { - let [startDigit, stopDigit] = zipped[i]; +class Lockfile { + constructor({ cache, source, parseResultType } = {}) { + this.source = source || ''; + this.cache = cache; + this.parseResultType = parseResultType; + } - if (startDigit === stopDigit) { - pattern += startDigit; + // source string if the `cache` was parsed - } else if (startDigit !== '0' || stopDigit !== '9') { - pattern += toCharacterClass(startDigit, stopDigit, options); - } else { - count++; + // if true, we're parsing an old yarn file and need to update integrity fields + hasEntriesExistWithoutIntegrity() { + if (!this.cache) { + return false; } - } - if (count) { - pattern += options.shorthand === true ? '\\d' : '[0-9]'; + for (const key in this.cache) { + // $FlowFixMe - `this.cache` is clearly defined at this point + if (!/^.*@(file:|http)/.test(key) && this.cache[key] && !this.cache[key].integrity) { + return true; + } + } + + return false; } - return { pattern, count: [count], digits }; -} + static fromDirectory(dir, reporter) { + return (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* () { + // read the manifest in this directory + const lockfileLoc = path.join(dir, (_constants || _load_constants()).LOCKFILE_FILENAME); -function splitToPatterns(min, max, tok, options) { - let ranges = splitToRanges(min, max); - let tokens = []; - let start = min; - let prev; + let lockfile; + let rawLockfile = ''; + let parseResult; - for (let i = 0; i < ranges.length; i++) { - let max = ranges[i]; - let obj = rangeToPattern(String(start), String(max), options); - let zeros = ''; + if (yield (_fs || _load_fs()).exists(lockfileLoc)) { + rawLockfile = yield (_fs || _load_fs()).readFile(lockfileLoc); + parseResult = (0, (_parse2 || _load_parse2()).default)(rawLockfile, lockfileLoc); - if (!tok.isPadded && prev && prev.pattern === obj.pattern) { - if (prev.count.length > 1) { - prev.count.pop(); + if (reporter) { + if (parseResult.type === 'merge') { + reporter.info(reporter.lang('lockfileMerged')); + } else if (parseResult.type === 'conflict') { + reporter.warn(reporter.lang('lockfileConflict')); + } + } + + lockfile = parseResult.object; + } else if (reporter) { + reporter.info(reporter.lang('noLockfileFound')); } - prev.count.push(obj.count[0]); - prev.string = prev.pattern + toQuantifier(prev.count); - start = max + 1; - continue; + return new Lockfile({ cache: lockfile, source: rawLockfile, parseResultType: parseResult && parseResult.type }); + })(); + } + + getLocked(pattern) { + const cache = this.cache; + if (!cache) { + return undefined; } - if (tok.isPadded) { - zeros = padZeros(max, tok, options); + const shrunk = pattern in cache && cache[pattern]; + + if (typeof shrunk === 'string') { + return this.getLocked(shrunk); + } else if (shrunk) { + explodeEntry(pattern, shrunk); + return shrunk; } - obj.string = zeros + obj.pattern + toQuantifier(obj.count); - tokens.push(obj); - start = max + 1; - prev = obj; + return undefined; } - return tokens; + removePattern(pattern) { + const cache = this.cache; + if (!cache) { + return; + } + delete cache[pattern]; + } + + getLockfile(patterns) { + const lockfile = {}; + const seen = new Map(); + + // order by name so that lockfile manifest is assigned to the first dependency with this manifest + // the others that have the same remoteKey will just refer to the first + // ordering allows for consistency in lockfile when it is serialized + const sortedPatternsKeys = Object.keys(patterns).sort((_misc || _load_misc()).sortAlpha); + + for (var _iterator = sortedPatternsKeys, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : _iterator[Symbol.iterator]();;) { + var _ref; + + if (_isArray) { + if (_i >= _iterator.length) break; + _ref = _iterator[_i++]; + } else { + _i = _iterator.next(); + if (_i.done) break; + _ref = _i.value; + } + + const pattern = _ref; + + const pkg = patterns[pattern]; + const remote = pkg._remote, + ref = pkg._reference; + + invariant(ref, 'Package is missing a reference'); + invariant(remote, 'Package is missing a remote'); + + const remoteKey = keyForRemote(remote); + const seenPattern = remoteKey && seen.get(remoteKey); + if (seenPattern) { + // no point in duplicating it + lockfile[pattern] = seenPattern; + + // if we're relying on our name being inferred and two of the patterns have + // different inferred names then we need to set it + if (!seenPattern.name && getName(pattern) !== pkg.name) { + seenPattern.name = pkg.name; + } + continue; + } + const obj = implodeEntry(pattern, { + name: pkg.name, + version: pkg.version, + uid: pkg._uid, + resolved: remote.resolved, + integrity: remote.integrity, + registry: remote.registry, + dependencies: pkg.dependencies, + peerDependencies: pkg.peerDependencies, + optionalDependencies: pkg.optionalDependencies, + permissions: ref.permissions, + prebuiltVariants: pkg.prebuiltVariants + }); + + lockfile[pattern] = obj; + + if (remoteKey) { + seen.set(remoteKey, obj); + } + } + + return lockfile; + } } +exports.default = Lockfile; -function filterPatterns(arr, comparison, prefix, intersection, options) { - let result = []; +/***/ }), +/* 15 */, +/* 16 */, +/* 17 */ +/***/ (function(module, exports) { - for (let ele of arr) { - let { string } = ele; +module.exports = __webpack_require__(27); - // only push if _both_ are negative... - if (!intersection && !contains(comparison, 'string', string)) { - result.push(prefix + string); +/***/ }), +/* 18 */, +/* 19 */, +/* 20 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = nullify; +function nullify(obj = {}) { + if (Array.isArray(obj)) { + for (var _iterator = obj, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : _iterator[Symbol.iterator]();;) { + var _ref; + + if (_isArray) { + if (_i >= _iterator.length) break; + _ref = _iterator[_i++]; + } else { + _i = _iterator.next(); + if (_i.done) break; + _ref = _i.value; + } + + const item = _ref; + + nullify(item); } + } else if (obj !== null && typeof obj === 'object' || typeof obj === 'function') { + Object.setPrototypeOf(obj, null); - // or _both_ are positive - if (intersection && contains(comparison, 'string', string)) { - result.push(prefix + string); + // for..in can only be applied to 'object', not 'function' + if (typeof obj === 'object') { + for (const key in obj) { + nullify(obj[key]); + } } } - return result; + + return obj; } +/***/ }), +/* 21 */, +/* 22 */ +/***/ (function(module, exports) { + +module.exports = __webpack_require__(30); + +/***/ }), +/* 23 */ +/***/ (function(module, exports) { + +var core = module.exports = { version: '2.5.7' }; +if (typeof __e == 'number') __e = core; // eslint-disable-line no-undef + + +/***/ }), +/* 24 */, +/* 25 */, +/* 26 */, +/* 27 */ +/***/ (function(module, exports, __webpack_require__) { + +var isObject = __webpack_require__(34); +module.exports = function (it) { + if (!isObject(it)) throw TypeError(it + ' is not an object!'); + return it; +}; + + +/***/ }), +/* 28 */, +/* 29 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.normalizePattern = normalizePattern; + /** - * Zip strings + * Explode and normalize a pattern into its name and range. */ -function zip(a, b) { - let arr = []; - for (let i = 0; i < a.length; i++) arr.push([a[i], b[i]]); - return arr; -} +function normalizePattern(pattern) { + let hasVersion = false; + let range = 'latest'; + let name = pattern; -function compare(a, b) { - return a > b ? 1 : b > a ? -1 : 0; -} + // if we're a scope then remove the @ and add it back later + let isScoped = false; + if (name[0] === '@') { + isScoped = true; + name = name.slice(1); + } -function contains(arr, key, val) { - return arr.some(ele => ele[key] === val); -} + // take first part as the name + const parts = name.split('@'); + if (parts.length > 1) { + name = parts.shift(); + range = parts.join('@'); -function countNines(min, len) { - return Number(String(min).slice(0, -len) + '9'.repeat(len)); -} + if (range) { + hasVersion = true; + } else { + range = '*'; + } + } -function countZeros(integer, zeros) { - return integer - (integer % Math.pow(10, zeros)); + // add back @ scope suffix + if (isScoped) { + name = `@${name}`; + } + + return { name, range, hasVersion }; } -function toQuantifier(digits) { - let [start = 0, stop = ''] = digits; - if (stop || start > 1) { - return `{${start + (stop ? ',' + stop : '')}}`; +/***/ }), +/* 30 */, +/* 31 */ +/***/ (function(module, exports, __webpack_require__) { + +var dP = __webpack_require__(50); +var createDesc = __webpack_require__(106); +module.exports = __webpack_require__(33) ? function (object, key, value) { + return dP.f(object, key, createDesc(1, value)); +} : function (object, key, value) { + object[key] = value; + return object; +}; + + +/***/ }), +/* 32 */ +/***/ (function(module, exports, __webpack_require__) { + +/* eslint-disable node/no-deprecated-api */ +var buffer = __webpack_require__(63) +var Buffer = buffer.Buffer + +// alternative to using Object.keys for old browsers +function copyProps (src, dst) { + for (var key in src) { + dst[key] = src[key] } - return ''; +} +if (Buffer.from && Buffer.alloc && Buffer.allocUnsafe && Buffer.allocUnsafeSlow) { + module.exports = buffer +} else { + // Copy properties from require('buffer') + copyProps(buffer, exports) + exports.Buffer = SafeBuffer } -function toCharacterClass(a, b, options) { - return `[${a}${(b - a === 1) ? '' : '-'}${b}]`; +function SafeBuffer (arg, encodingOrOffset, length) { + return Buffer(arg, encodingOrOffset, length) } -function hasPadding(str) { - return /^-?(0+)\d/.test(str); +// Copy static methods from Buffer +copyProps(Buffer, SafeBuffer) + +SafeBuffer.from = function (arg, encodingOrOffset, length) { + if (typeof arg === 'number') { + throw new TypeError('Argument must not be a number') + } + return Buffer(arg, encodingOrOffset, length) } -function padZeros(value, tok, options) { - if (!tok.isPadded) { - return value; +SafeBuffer.alloc = function (size, fill, encoding) { + if (typeof size !== 'number') { + throw new TypeError('Argument must be a number') + } + var buf = Buffer(size) + if (fill !== undefined) { + if (typeof encoding === 'string') { + buf.fill(fill, encoding) + } else { + buf.fill(fill) + } + } else { + buf.fill(0) } + return buf +} - let diff = Math.abs(tok.maxLen - String(value).length); - let relax = options.relaxZeros !== false; +SafeBuffer.allocUnsafe = function (size) { + if (typeof size !== 'number') { + throw new TypeError('Argument must be a number') + } + return Buffer(size) +} - switch (diff) { - case 0: - return ''; - case 1: - return relax ? '0?' : '0'; - case 2: - return relax ? '0{0,2}' : '00'; - default: { - return relax ? `0{0,${diff}}` : `0{${diff}}`; - } +SafeBuffer.allocUnsafeSlow = function (size) { + if (typeof size !== 'number') { + throw new TypeError('Argument must be a number') } + return buffer.SlowBuffer(size) } -/** - * Cache - */ -toRegexRange.cache = {}; -toRegexRange.clearCache = () => (toRegexRange.cache = {}); +/***/ }), +/* 33 */ +/***/ (function(module, exports, __webpack_require__) { + +// Thank's IE8 for his funny defineProperty +module.exports = !__webpack_require__(85)(function () { + return Object.defineProperty({}, 'a', { get: function () { return 7; } }).a != 7; +}); + + +/***/ }), +/* 34 */ +/***/ (function(module, exports) { + +module.exports = function (it) { + return typeof it === 'object' ? it !== null : typeof it === 'function'; +}; + + +/***/ }), +/* 35 */ +/***/ (function(module, exports) { -/** - * Expose `toRegexRange` - */ +module.exports = {}; -module.exports = toRegexRange; +/***/ }), +/* 36 */ +/***/ (function(module, exports) { + +module.exports = __webpack_require__(11); /***/ }), -/* 196 */ +/* 37 */, +/* 38 */, +/* 39 */, +/* 40 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -/*! - * is-number - * - * Copyright (c) 2014-present, Jon Schlinkert. - * Released under the MIT License. - */ +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.wait = wait; +exports.promisify = promisify; +exports.queue = queue; +function wait(delay) { + return new Promise(resolve => { + setTimeout(resolve, delay); + }); +} -module.exports = function(num) { - if (typeof num === 'number') { - return num - num === 0; - } - if (typeof num === 'string' && num.trim() !== '') { - return Number.isFinite ? Number.isFinite(+num) : isFinite(+num); - } - return false; -}; - +function promisify(fn, firstData) { + return function (...args) { + return new Promise(function (resolve, reject) { + args.push(function (err, ...result) { + let res = result; -/***/ }), -/* 197 */ -/***/ (function(module, exports, __webpack_require__) { + if (result.length <= 1) { + res = result[0]; + } -"use strict"; + if (firstData) { + res = err; + err = null; + } + if (err) { + reject(err); + } else { + resolve(res); + } + }); -const fill = __webpack_require__(194); -const stringify = __webpack_require__(191); -const utils = __webpack_require__(192); + fn.apply(null, args); + }); + }; +} -const append = (queue = '', stash = '', enclose = false) => { - let result = []; +function queue(arr, promiseProducer, concurrency = Infinity) { + concurrency = Math.min(concurrency, arr.length); - queue = [].concat(queue); - stash = [].concat(stash); + // clone + arr = arr.slice(); - if (!stash.length) return queue; - if (!queue.length) { - return enclose ? utils.flatten(stash).map(ele => `{${ele}}`) : stash; + const results = []; + let total = arr.length; + if (!total) { + return Promise.resolve(results); } - for (let item of queue) { - if (Array.isArray(item)) { - for (let value of item) { - result.push(append(value, stash, enclose)); - } - } else { - for (let ele of stash) { - if (enclose === true && typeof ele === 'string') ele = `{${ele}}`; - result.push(Array.isArray(ele) ? append(item, ele, enclose) : (item + ele)); - } + return new Promise((resolve, reject) => { + for (let i = 0; i < concurrency; i++) { + next(); } - } - return utils.flatten(result); -}; - -const expand = (ast, options = {}) => { - let rangeLimit = options.rangeLimit === void 0 ? 1000 : options.rangeLimit; - let walk = (node, parent = {}) => { - node.queue = []; + function next() { + const item = arr.shift(); + const promise = promiseProducer(item); - let p = parent; - let q = parent.queue; + promise.then(function (result) { + results.push(result); - while (p.type !== 'brace' && p.type !== 'root' && p.parent) { - p = p.parent; - q = p.queue; + total--; + if (total === 0) { + resolve(results); + } else { + if (arr.length) { + next(); + } + } + }, reject); } + }); +} - if (node.invalid || node.dollar) { - q.push(append(q.pop(), stringify(node, options))); - return; - } +/***/ }), +/* 41 */ +/***/ (function(module, exports, __webpack_require__) { - if (node.type === 'brace' && node.invalid !== true && node.nodes.length === 2) { - q.push(append(q.pop(), ['{}'])); - return; +var global = __webpack_require__(11); +var core = __webpack_require__(23); +var ctx = __webpack_require__(48); +var hide = __webpack_require__(31); +var has = __webpack_require__(49); +var PROTOTYPE = 'prototype'; + +var $export = function (type, name, source) { + var IS_FORCED = type & $export.F; + var IS_GLOBAL = type & $export.G; + var IS_STATIC = type & $export.S; + var IS_PROTO = type & $export.P; + var IS_BIND = type & $export.B; + var IS_WRAP = type & $export.W; + var exports = IS_GLOBAL ? core : core[name] || (core[name] = {}); + var expProto = exports[PROTOTYPE]; + var target = IS_GLOBAL ? global : IS_STATIC ? global[name] : (global[name] || {})[PROTOTYPE]; + var key, own, out; + if (IS_GLOBAL) source = name; + for (key in source) { + // contains in native + own = !IS_FORCED && target && target[key] !== undefined; + if (own && has(exports, key)) continue; + // export native or passed + out = own ? target[key] : source[key]; + // prevent global pollution for namespaces + exports[key] = IS_GLOBAL && typeof target[key] != 'function' ? source[key] + // bind timers to global for call from export context + : IS_BIND && own ? ctx(out, global) + // wrap global constructors for prevent change them in library + : IS_WRAP && target[key] == out ? (function (C) { + var F = function (a, b, c) { + if (this instanceof C) { + switch (arguments.length) { + case 0: return new C(); + case 1: return new C(a); + case 2: return new C(a, b); + } return new C(a, b, c); + } return C.apply(this, arguments); + }; + F[PROTOTYPE] = C[PROTOTYPE]; + return F; + // make static versions for prototype methods + })(out) : IS_PROTO && typeof out == 'function' ? ctx(Function.call, out) : out; + // export proto methods to core.%CONSTRUCTOR%.methods.%NAME% + if (IS_PROTO) { + (exports.virtual || (exports.virtual = {}))[key] = out; + // export proto methods to core.%CONSTRUCTOR%.prototype.%NAME% + if (type & $export.R && expProto && !expProto[key]) hide(expProto, key, out); } + } +}; +// type bitmap +$export.F = 1; // forced +$export.G = 2; // global +$export.S = 4; // static +$export.P = 8; // proto +$export.B = 16; // bind +$export.W = 32; // wrap +$export.U = 64; // safe +$export.R = 128; // real proto method for `library` +module.exports = $export; - if (node.nodes && node.ranges > 0) { - let args = utils.reduce(node.nodes); - if (utils.exceedsLimit(...args, options.step, rangeLimit)) { - throw new RangeError('expanded array length exceeds range limit. Use options.rangeLimit to increase or disable the limit.'); - } +/***/ }), +/* 42 */ +/***/ (function(module, exports, __webpack_require__) { - let range = fill(...args, options); - if (range.length === 0) { - range = stringify(node, options); - } +try { + var util = __webpack_require__(2); + if (typeof util.inherits !== 'function') throw ''; + module.exports = util.inherits; +} catch (e) { + module.exports = __webpack_require__(224); +} - q.push(append(q.pop(), range)); - node.nodes = []; - return; - } - let enclose = utils.encloseBrace(node); - let queue = node.queue; - let block = node; +/***/ }), +/* 43 */, +/* 44 */, +/* 45 */ +/***/ (function(module, exports, __webpack_require__) { - while (block.type !== 'brace' && block.type !== 'root' && block.parent) { - block = block.parent; - queue = block.queue; - } +"use strict"; - for (let i = 0; i < node.nodes.length; i++) { - let child = node.nodes[i]; - if (child.type === 'comma' && node.type === 'brace') { - if (i === 1) queue.push(''); - queue.push(''); - continue; - } +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.home = undefined; - if (child.type === 'close') { - q.push(append(q.pop(), queue, enclose)); - continue; - } +var _rootUser; - if (child.value && child.type !== 'open') { - queue.push(append(queue.pop(), child.value)); - continue; - } +function _load_rootUser() { + return _rootUser = _interopRequireDefault(__webpack_require__(169)); +} - if (child.nodes) { - walk(child, node); - } - } +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - return queue; - }; +const path = __webpack_require__(0); - return utils.flatten(walk(ast)); +const home = exports.home = __webpack_require__(36).homedir(); + +const userHomeDir = (_rootUser || _load_rootUser()).default ? path.resolve('/usr/local/share') : home; + +exports.default = userHomeDir; + +/***/ }), +/* 46 */ +/***/ (function(module, exports) { + +module.exports = function (it) { + if (typeof it != 'function') throw TypeError(it + ' is not a function!'); + return it; }; -module.exports = expand; + +/***/ }), +/* 47 */ +/***/ (function(module, exports) { + +var toString = {}.toString; + +module.exports = function (it) { + return toString.call(it).slice(8, -1); +}; /***/ }), -/* 198 */ +/* 48 */ /***/ (function(module, exports, __webpack_require__) { -"use strict"; +// optional / simple context binding +var aFunction = __webpack_require__(46); +module.exports = function (fn, that, length) { + aFunction(fn); + if (that === undefined) return fn; + switch (length) { + case 1: return function (a) { + return fn.call(that, a); + }; + case 2: return function (a, b) { + return fn.call(that, a, b); + }; + case 3: return function (a, b, c) { + return fn.call(that, a, b, c); + }; + } + return function (/* ...args */) { + return fn.apply(that, arguments); + }; +}; -const stringify = __webpack_require__(191); +/***/ }), +/* 49 */ +/***/ (function(module, exports) { -/** - * Constants - */ +var hasOwnProperty = {}.hasOwnProperty; +module.exports = function (it, key) { + return hasOwnProperty.call(it, key); +}; -const { - MAX_LENGTH, - CHAR_BACKSLASH, /* \ */ - CHAR_BACKTICK, /* ` */ - CHAR_COMMA, /* , */ - CHAR_DOT, /* . */ - CHAR_LEFT_PARENTHESES, /* ( */ - CHAR_RIGHT_PARENTHESES, /* ) */ - CHAR_LEFT_CURLY_BRACE, /* { */ - CHAR_RIGHT_CURLY_BRACE, /* } */ - CHAR_LEFT_SQUARE_BRACKET, /* [ */ - CHAR_RIGHT_SQUARE_BRACKET, /* ] */ - CHAR_DOUBLE_QUOTE, /* " */ - CHAR_SINGLE_QUOTE, /* ' */ - CHAR_NO_BREAK_SPACE, - CHAR_ZERO_WIDTH_NOBREAK_SPACE -} = __webpack_require__(199); -/** - * parse - */ +/***/ }), +/* 50 */ +/***/ (function(module, exports, __webpack_require__) { -const parse = (input, options = {}) => { - if (typeof input !== 'string') { - throw new TypeError('Expected a string'); - } +var anObject = __webpack_require__(27); +var IE8_DOM_DEFINE = __webpack_require__(184); +var toPrimitive = __webpack_require__(201); +var dP = Object.defineProperty; - let opts = options || {}; - let max = typeof opts.maxLength === 'number' ? Math.min(MAX_LENGTH, opts.maxLength) : MAX_LENGTH; - if (input.length > max) { - throw new SyntaxError(`Input length (${input.length}), exceeds max characters (${max})`); - } +exports.f = __webpack_require__(33) ? Object.defineProperty : function defineProperty(O, P, Attributes) { + anObject(O); + P = toPrimitive(P, true); + anObject(Attributes); + if (IE8_DOM_DEFINE) try { + return dP(O, P, Attributes); + } catch (e) { /* empty */ } + if ('get' in Attributes || 'set' in Attributes) throw TypeError('Accessors not supported!'); + if ('value' in Attributes) O[P] = Attributes.value; + return O; +}; - let ast = { type: 'root', input, nodes: [] }; - let stack = [ast]; - let block = ast; - let prev = ast; - let brackets = 0; - let length = input.length; - let index = 0; - let depth = 0; - let value; - let memo = {}; - /** - * Helpers - */ +/***/ }), +/* 51 */, +/* 52 */, +/* 53 */, +/* 54 */ +/***/ (function(module, exports) { - const advance = () => input[index++]; - const push = node => { - if (node.type === 'text' && prev.type === 'dot') { - prev.type = 'text'; - } +module.exports = __webpack_require__(379); - if (prev && prev.type === 'text' && node.type === 'text') { - prev.value += node.value; - return; - } +/***/ }), +/* 55 */ +/***/ (function(module, exports, __webpack_require__) { - block.nodes.push(node); - node.parent = block; - node.prev = prev; - prev = node; - return node; - }; +"use strict"; - push({ type: 'bos' }); - while (index < length) { - block = stack[stack.length - 1]; - value = advance(); +const Buffer = __webpack_require__(32).Buffer - /** - * Invalid chars - */ +const crypto = __webpack_require__(9) +const Transform = __webpack_require__(17).Transform - if (value === CHAR_ZERO_WIDTH_NOBREAK_SPACE || value === CHAR_NO_BREAK_SPACE) { - continue; - } +const SPEC_ALGORITHMS = ['sha256', 'sha384', 'sha512'] - /** - * Escaped chars - */ +const BASE64_REGEX = /^[a-z0-9+/]+(?:=?=?)$/i +const SRI_REGEX = /^([^-]+)-([^?]+)([?\S*]*)$/ +const STRICT_SRI_REGEX = /^([^-]+)-([A-Za-z0-9+/=]{44,88})(\?[\x21-\x7E]*)*$/ +const VCHAR_REGEX = /^[\x21-\x7E]+$/ - if (value === CHAR_BACKSLASH) { - push({ type: 'text', value: (options.keepEscaping ? value : '') + advance() }); - continue; +class Hash { + get isHash () { return true } + constructor (hash, opts) { + const strict = !!(opts && opts.strict) + this.source = hash.trim() + // 3.1. Integrity metadata (called "Hash" by ssri) + // https://w3c.github.io/webappsec-subresource-integrity/#integrity-metadata-description + const match = this.source.match( + strict + ? STRICT_SRI_REGEX + : SRI_REGEX + ) + if (!match) { return } + if (strict && !SPEC_ALGORITHMS.some(a => a === match[1])) { return } + this.algorithm = match[1] + this.digest = match[2] + + const rawOpts = match[3] + this.options = rawOpts ? rawOpts.slice(1).split('?') : [] + } + hexDigest () { + return this.digest && Buffer.from(this.digest, 'base64').toString('hex') + } + toJSON () { + return this.toString() + } + toString (opts) { + if (opts && opts.strict) { + // Strict mode enforces the standard as close to the foot of the + // letter as it can. + if (!( + // The spec has very restricted productions for algorithms. + // https://www.w3.org/TR/CSP2/#source-list-syntax + SPEC_ALGORITHMS.some(x => x === this.algorithm) && + // Usually, if someone insists on using a "different" base64, we + // leave it as-is, since there's multiple standards, and the + // specified is not a URL-safe variant. + // https://www.w3.org/TR/CSP2/#base64_value + this.digest.match(BASE64_REGEX) && + // Option syntax is strictly visual chars. + // https://w3c.github.io/webappsec-subresource-integrity/#grammardef-option-expression + // https://tools.ietf.org/html/rfc5234#appendix-B.1 + (this.options || []).every(opt => opt.match(VCHAR_REGEX)) + )) { + return '' + } + } + const options = this.options && this.options.length + ? `?${this.options.join('?')}` + : '' + return `${this.algorithm}-${this.digest}${options}` + } +} + +class Integrity { + get isIntegrity () { return true } + toJSON () { + return this.toString() + } + toString (opts) { + opts = opts || {} + let sep = opts.sep || ' ' + if (opts.strict) { + // Entries must be separated by whitespace, according to spec. + sep = sep.replace(/\S+/g, ' ') } + return Object.keys(this).map(k => { + return this[k].map(hash => { + return Hash.prototype.toString.call(hash, opts) + }).filter(x => x.length).join(sep) + }).filter(x => x.length).join(sep) + } + concat (integrity, opts) { + const other = typeof integrity === 'string' + ? integrity + : stringify(integrity, opts) + return parse(`${this.toString(opts)} ${other}`, opts) + } + hexDigest () { + return parse(this, {single: true}).hexDigest() + } + match (integrity, opts) { + const other = parse(integrity, opts) + const algo = other.pickAlgorithm(opts) + return ( + this[algo] && + other[algo] && + this[algo].find(hash => + other[algo].find(otherhash => + hash.digest === otherhash.digest + ) + ) + ) || false + } + pickAlgorithm (opts) { + const pickAlgorithm = (opts && opts.pickAlgorithm) || getPrioritizedHash + const keys = Object.keys(this) + if (!keys.length) { + throw new Error(`No algorithms available for ${ + JSON.stringify(this.toString()) + }`) + } + return keys.reduce((acc, algo) => { + return pickAlgorithm(acc, algo) || acc + }) + } +} - /** - * Right square bracket (literal): ']' - */ +module.exports.parse = parse +function parse (sri, opts) { + opts = opts || {} + if (typeof sri === 'string') { + return _parse(sri, opts) + } else if (sri.algorithm && sri.digest) { + const fullSri = new Integrity() + fullSri[sri.algorithm] = [sri] + return _parse(stringify(fullSri, opts), opts) + } else { + return _parse(stringify(sri, opts), opts) + } +} - if (value === CHAR_RIGHT_SQUARE_BRACKET) { - push({ type: 'text', value: '\\' + value }); - continue; +function _parse (integrity, opts) { + // 3.4.3. Parse metadata + // https://w3c.github.io/webappsec-subresource-integrity/#parse-metadata + if (opts.single) { + return new Hash(integrity, opts) + } + return integrity.trim().split(/\s+/).reduce((acc, string) => { + const hash = new Hash(string, opts) + if (hash.algorithm && hash.digest) { + const algo = hash.algorithm + if (!acc[algo]) { acc[algo] = [] } + acc[algo].push(hash) } + return acc + }, new Integrity()) +} - /** - * Left square bracket: '[' - */ - - if (value === CHAR_LEFT_SQUARE_BRACKET) { - brackets++; +module.exports.stringify = stringify +function stringify (obj, opts) { + if (obj.algorithm && obj.digest) { + return Hash.prototype.toString.call(obj, opts) + } else if (typeof obj === 'string') { + return stringify(parse(obj, opts), opts) + } else { + return Integrity.prototype.toString.call(obj, opts) + } +} - let closed = true; - let next; +module.exports.fromHex = fromHex +function fromHex (hexDigest, algorithm, opts) { + const optString = (opts && opts.options && opts.options.length) + ? `?${opts.options.join('?')}` + : '' + return parse( + `${algorithm}-${ + Buffer.from(hexDigest, 'hex').toString('base64') + }${optString}`, opts + ) +} - while (index < length && (next = advance())) { - value += next; +module.exports.fromData = fromData +function fromData (data, opts) { + opts = opts || {} + const algorithms = opts.algorithms || ['sha512'] + const optString = opts.options && opts.options.length + ? `?${opts.options.join('?')}` + : '' + return algorithms.reduce((acc, algo) => { + const digest = crypto.createHash(algo).update(data).digest('base64') + const hash = new Hash( + `${algo}-${digest}${optString}`, + opts + ) + if (hash.algorithm && hash.digest) { + const algo = hash.algorithm + if (!acc[algo]) { acc[algo] = [] } + acc[algo].push(hash) + } + return acc + }, new Integrity()) +} + +module.exports.fromStream = fromStream +function fromStream (stream, opts) { + opts = opts || {} + const P = opts.Promise || Promise + const istream = integrityStream(opts) + return new P((resolve, reject) => { + stream.pipe(istream) + stream.on('error', reject) + istream.on('error', reject) + let sri + istream.on('integrity', s => { sri = s }) + istream.on('end', () => resolve(sri)) + istream.on('data', () => {}) + }) +} - if (next === CHAR_LEFT_SQUARE_BRACKET) { - brackets++; - continue; +module.exports.checkData = checkData +function checkData (data, sri, opts) { + opts = opts || {} + sri = parse(sri, opts) + if (!Object.keys(sri).length) { + if (opts.error) { + throw Object.assign( + new Error('No valid integrity hashes to check against'), { + code: 'EINTEGRITY' } + ) + } else { + return false + } + } + const algorithm = sri.pickAlgorithm(opts) + const digest = crypto.createHash(algorithm).update(data).digest('base64') + const newSri = parse({algorithm, digest}) + const match = newSri.match(sri, opts) + if (match || !opts.error) { + return match + } else if (typeof opts.size === 'number' && (data.length !== opts.size)) { + const err = new Error(`data size mismatch when checking ${sri}.\n Wanted: ${opts.size}\n Found: ${data.length}`) + err.code = 'EBADSIZE' + err.found = data.length + err.expected = opts.size + err.sri = sri + throw err + } else { + const err = new Error(`Integrity checksum failed when using ${algorithm}: Wanted ${sri}, but got ${newSri}. (${data.length} bytes)`) + err.code = 'EINTEGRITY' + err.found = newSri + err.expected = sri + err.algorithm = algorithm + err.sri = sri + throw err + } +} - if (next === CHAR_BACKSLASH) { - value += advance(); - continue; - } +module.exports.checkStream = checkStream +function checkStream (stream, sri, opts) { + opts = opts || {} + const P = opts.Promise || Promise + const checker = integrityStream(Object.assign({}, opts, { + integrity: sri + })) + return new P((resolve, reject) => { + stream.pipe(checker) + stream.on('error', reject) + checker.on('error', reject) + let sri + checker.on('verified', s => { sri = s }) + checker.on('end', () => resolve(sri)) + checker.on('data', () => {}) + }) +} - if (next === CHAR_RIGHT_SQUARE_BRACKET) { - brackets--; +module.exports.integrityStream = integrityStream +function integrityStream (opts) { + opts = opts || {} + // For verification + const sri = opts.integrity && parse(opts.integrity, opts) + const goodSri = sri && Object.keys(sri).length + const algorithm = goodSri && sri.pickAlgorithm(opts) + const digests = goodSri && sri[algorithm] + // Calculating stream + const algorithms = Array.from( + new Set( + (opts.algorithms || ['sha512']) + .concat(algorithm ? [algorithm] : []) + ) + ) + const hashes = algorithms.map(crypto.createHash) + let streamSize = 0 + const stream = new Transform({ + transform (chunk, enc, cb) { + streamSize += chunk.length + hashes.forEach(h => h.update(chunk, enc)) + cb(null, chunk, enc) + } + }).on('end', () => { + const optString = (opts.options && opts.options.length) + ? `?${opts.options.join('?')}` + : '' + const newSri = parse(hashes.map((h, i) => { + return `${algorithms[i]}-${h.digest('base64')}${optString}` + }).join(' '), opts) + // Integrity verification mode + const match = goodSri && newSri.match(sri, opts) + if (typeof opts.size === 'number' && streamSize !== opts.size) { + const err = new Error(`stream size mismatch when checking ${sri}.\n Wanted: ${opts.size}\n Found: ${streamSize}`) + err.code = 'EBADSIZE' + err.found = streamSize + err.expected = opts.size + err.sri = sri + stream.emit('error', err) + } else if (opts.integrity && !match) { + const err = new Error(`${sri} integrity checksum failed when using ${algorithm}: wanted ${digests} but got ${newSri}. (${streamSize} bytes)`) + err.code = 'EINTEGRITY' + err.found = newSri + err.expected = digests + err.algorithm = algorithm + err.sri = sri + stream.emit('error', err) + } else { + stream.emit('size', streamSize) + stream.emit('integrity', newSri) + match && stream.emit('verified', match) + } + }) + return stream +} - if (brackets === 0) { - break; - } - } - } +module.exports.create = createIntegrity +function createIntegrity (opts) { + opts = opts || {} + const algorithms = opts.algorithms || ['sha512'] + const optString = opts.options && opts.options.length + ? `?${opts.options.join('?')}` + : '' - push({ type: 'text', value }); - continue; - } + const hashes = algorithms.map(crypto.createHash) - /** - * Parentheses - */ + return { + update: function (chunk, enc) { + hashes.forEach(h => h.update(chunk, enc)) + return this + }, + digest: function (enc) { + const integrity = algorithms.reduce((acc, algo) => { + const digest = hashes.shift().digest('base64') + const hash = new Hash( + `${algo}-${digest}${optString}`, + opts + ) + if (hash.algorithm && hash.digest) { + const algo = hash.algorithm + if (!acc[algo]) { acc[algo] = [] } + acc[algo].push(hash) + } + return acc + }, new Integrity()) - if (value === CHAR_LEFT_PARENTHESES) { - block = push({ type: 'paren', nodes: [] }); - stack.push(block); - push({ type: 'text', value }); - continue; + return integrity } + } +} - if (value === CHAR_RIGHT_PARENTHESES) { - if (block.type !== 'paren') { - push({ type: 'text', value }); - continue; - } - block = stack.pop(); - push({ type: 'text', value }); - block = stack[stack.length - 1]; - continue; - } +const NODE_HASHES = new Set(crypto.getHashes()) - /** - * Quotes: '|"|` - */ +// This is a Best Effort™ at a reasonable priority for hash algos +const DEFAULT_PRIORITY = [ + 'md5', 'whirlpool', 'sha1', 'sha224', 'sha256', 'sha384', 'sha512', + // TODO - it's unclear _which_ of these Node will actually use as its name + // for the algorithm, so we guesswork it based on the OpenSSL names. + 'sha3', + 'sha3-256', 'sha3-384', 'sha3-512', + 'sha3_256', 'sha3_384', 'sha3_512' +].filter(algo => NODE_HASHES.has(algo)) - if (value === CHAR_DOUBLE_QUOTE || value === CHAR_SINGLE_QUOTE || value === CHAR_BACKTICK) { - let open = value; - let next; +function getPrioritizedHash (algo1, algo2) { + return DEFAULT_PRIORITY.indexOf(algo1.toLowerCase()) >= DEFAULT_PRIORITY.indexOf(algo2.toLowerCase()) + ? algo1 + : algo2 +} - if (options.keepQuotes !== true) { - value = ''; - } - while (index < length && (next = advance())) { - if (next === CHAR_BACKSLASH) { - value += next + advance(); - continue; - } +/***/ }), +/* 56 */, +/* 57 */, +/* 58 */, +/* 59 */, +/* 60 */ +/***/ (function(module, exports, __webpack_require__) { - if (next === open) { - if (options.keepQuotes === true) value += next; - break; - } +module.exports = minimatch +minimatch.Minimatch = Minimatch - value += next; - } +var path = { sep: '/' } +try { + path = __webpack_require__(0) +} catch (er) {} - push({ type: 'text', value }); - continue; - } +var GLOBSTAR = minimatch.GLOBSTAR = Minimatch.GLOBSTAR = {} +var expand = __webpack_require__(175) - /** - * Left curly brace: '{' - */ +var plTypes = { + '!': { open: '(?:(?!(?:', close: '))[^/]*?)'}, + '?': { open: '(?:', close: ')?' }, + '+': { open: '(?:', close: ')+' }, + '*': { open: '(?:', close: ')*' }, + '@': { open: '(?:', close: ')' } +} - if (value === CHAR_LEFT_CURLY_BRACE) { - depth++; +// any single thing other than / +// don't need to escape / when using new RegExp() +var qmark = '[^/]' - let dollar = prev.value && prev.value.slice(-1) === '$' || block.dollar === true; - let brace = { - type: 'brace', - open: true, - close: false, - dollar, - depth, - commas: 0, - ranges: 0, - nodes: [] - }; +// * => any number of characters +var star = qmark + '*?' - block = push(brace); - stack.push(block); - push({ type: 'open', value }); - continue; - } +// ** when dots are allowed. Anything goes, except .. and . +// not (^ or / followed by one or two dots followed by $ or /), +// followed by anything, any number of times. +var twoStarDot = '(?:(?!(?:\\\/|^)(?:\\.{1,2})($|\\\/)).)*?' - /** - * Right curly brace: '}' - */ +// not a ^ or / followed by a dot, +// followed by anything, any number of times. +var twoStarNoDot = '(?:(?!(?:\\\/|^)\\.).)*?' - if (value === CHAR_RIGHT_CURLY_BRACE) { - if (block.type !== 'brace') { - push({ type: 'text', value }); - continue; - } +// characters that need to be escaped in RegExp. +var reSpecials = charSet('().*{}+?[]^$\\!') - let type = 'close'; - block = stack.pop(); - block.close = true; +// "abc" -> { a:true, b:true, c:true } +function charSet (s) { + return s.split('').reduce(function (set, c) { + set[c] = true + return set + }, {}) +} - push({ type, value }); - depth--; +// normalizes slashes. +var slashSplit = /\/+/ - block = stack[stack.length - 1]; - continue; - } +minimatch.filter = filter +function filter (pattern, options) { + options = options || {} + return function (p, i, list) { + return minimatch(p, pattern, options) + } +} - /** - * Comma: ',' - */ +function ext (a, b) { + a = a || {} + b = b || {} + var t = {} + Object.keys(b).forEach(function (k) { + t[k] = b[k] + }) + Object.keys(a).forEach(function (k) { + t[k] = a[k] + }) + return t +} - if (value === CHAR_COMMA && depth > 0) { - if (block.ranges > 0) { - block.ranges = 0; - let open = block.nodes.shift(); - block.nodes = [open, { type: 'text', value: stringify(block) }]; - } +minimatch.defaults = function (def) { + if (!def || !Object.keys(def).length) return minimatch - push({ type: 'comma', value }); - block.commas++; - continue; - } + var orig = minimatch - /** - * Dot: '.' - */ + var m = function minimatch (p, pattern, options) { + return orig.minimatch(p, pattern, ext(def, options)) + } - if (value === CHAR_DOT && depth > 0 && block.commas === 0) { - let siblings = block.nodes; + m.Minimatch = function Minimatch (pattern, options) { + return new orig.Minimatch(pattern, ext(def, options)) + } - if (depth === 0 || siblings.length === 0) { - push({ type: 'text', value }); - continue; - } + return m +} - if (prev.type === 'dot') { - block.range = []; - prev.value += value; - prev.type = 'range'; +Minimatch.defaults = function (def) { + if (!def || !Object.keys(def).length) return Minimatch + return minimatch.defaults(def).Minimatch +} - if (block.nodes.length !== 3 && block.nodes.length !== 5) { - block.invalid = true; - block.ranges = 0; - prev.type = 'text'; - continue; - } +function minimatch (p, pattern, options) { + if (typeof pattern !== 'string') { + throw new TypeError('glob pattern string required') + } - block.ranges++; - block.args = []; - continue; - } + if (!options) options = {} - if (prev.type === 'range') { - siblings.pop(); + // shortcut: comments match nothing. + if (!options.nocomment && pattern.charAt(0) === '#') { + return false + } - let before = siblings[siblings.length - 1]; - before.value += prev.value + value; - prev = before; - block.ranges--; - continue; - } + // "" only matches "" + if (pattern.trim() === '') return p === '' - push({ type: 'dot', value }); - continue; - } + return new Minimatch(pattern, options).match(p) +} - /** - * Text - */ +function Minimatch (pattern, options) { + if (!(this instanceof Minimatch)) { + return new Minimatch(pattern, options) + } - push({ type: 'text', value }); + if (typeof pattern !== 'string') { + throw new TypeError('glob pattern string required') } - // Mark imbalanced braces and brackets as invalid - do { - block = stack.pop(); + if (!options) options = {} + pattern = pattern.trim() - if (block.type !== 'root') { - block.nodes.forEach(node => { - if (!node.nodes) { - if (node.type === 'open') node.isOpen = true; - if (node.type === 'close') node.isClose = true; - if (!node.nodes) node.type = 'text'; - node.invalid = true; - } - }); + // windows support: need to use /, not \ + if (path.sep !== '/') { + pattern = pattern.split(path.sep).join('/') + } - // get the location of the block on parent.nodes (block's siblings) - let parent = stack[stack.length - 1]; - let index = parent.nodes.indexOf(block); - // replace the (invalid) block with it's nodes - parent.nodes.splice(index, 1, ...block.nodes); - } - } while (stack.length > 0); + this.options = options + this.set = [] + this.pattern = pattern + this.regexp = null + this.negate = false + this.comment = false + this.empty = false - push({ type: 'eos' }); - return ast; -}; + // make the set of regexps etc. + this.make() +} -module.exports = parse; +Minimatch.prototype.debug = function () {} +Minimatch.prototype.make = make +function make () { + // don't do it more than once. + if (this._made) return -/***/ }), -/* 199 */ -/***/ (function(module, exports, __webpack_require__) { + var pattern = this.pattern + var options = this.options -"use strict"; + // empty patterns and comments match nothing. + if (!options.nocomment && pattern.charAt(0) === '#') { + this.comment = true + return + } + if (!pattern) { + this.empty = true + return + } + // step 1: figure out negation, etc. + this.parseNegate() -module.exports = { - MAX_LENGTH: 1024 * 64, + // step 2: expand braces + var set = this.globSet = this.braceExpand() - // Digits - CHAR_0: '0', /* 0 */ - CHAR_9: '9', /* 9 */ + if (options.debug) this.debug = console.error - // Alphabet chars. - CHAR_UPPERCASE_A: 'A', /* A */ - CHAR_LOWERCASE_A: 'a', /* a */ - CHAR_UPPERCASE_Z: 'Z', /* Z */ - CHAR_LOWERCASE_Z: 'z', /* z */ + this.debug(this.pattern, set) - CHAR_LEFT_PARENTHESES: '(', /* ( */ - CHAR_RIGHT_PARENTHESES: ')', /* ) */ + // step 3: now we have a set, so turn each one into a series of path-portion + // matching patterns. + // These will be regexps, except in the case of "**", which is + // set to the GLOBSTAR object for globstar behavior, + // and will not contain any / characters + set = this.globParts = set.map(function (s) { + return s.split(slashSplit) + }) - CHAR_ASTERISK: '*', /* * */ + this.debug(this.pattern, set) - // Non-alphabetic chars. - CHAR_AMPERSAND: '&', /* & */ - CHAR_AT: '@', /* @ */ - CHAR_BACKSLASH: '\\', /* \ */ - CHAR_BACKTICK: '`', /* ` */ - CHAR_CARRIAGE_RETURN: '\r', /* \r */ - CHAR_CIRCUMFLEX_ACCENT: '^', /* ^ */ - CHAR_COLON: ':', /* : */ - CHAR_COMMA: ',', /* , */ - CHAR_DOLLAR: '$', /* . */ - CHAR_DOT: '.', /* . */ - CHAR_DOUBLE_QUOTE: '"', /* " */ - CHAR_EQUAL: '=', /* = */ - CHAR_EXCLAMATION_MARK: '!', /* ! */ - CHAR_FORM_FEED: '\f', /* \f */ - CHAR_FORWARD_SLASH: '/', /* / */ - CHAR_HASH: '#', /* # */ - CHAR_HYPHEN_MINUS: '-', /* - */ - CHAR_LEFT_ANGLE_BRACKET: '<', /* < */ - CHAR_LEFT_CURLY_BRACE: '{', /* { */ - CHAR_LEFT_SQUARE_BRACKET: '[', /* [ */ - CHAR_LINE_FEED: '\n', /* \n */ - CHAR_NO_BREAK_SPACE: '\u00A0', /* \u00A0 */ - CHAR_PERCENT: '%', /* % */ - CHAR_PLUS: '+', /* + */ - CHAR_QUESTION_MARK: '?', /* ? */ - CHAR_RIGHT_ANGLE_BRACKET: '>', /* > */ - CHAR_RIGHT_CURLY_BRACE: '}', /* } */ - CHAR_RIGHT_SQUARE_BRACKET: ']', /* ] */ - CHAR_SEMICOLON: ';', /* ; */ - CHAR_SINGLE_QUOTE: '\'', /* ' */ - CHAR_SPACE: ' ', /* */ - CHAR_TAB: '\t', /* \t */ - CHAR_UNDERSCORE: '_', /* _ */ - CHAR_VERTICAL_LINE: '|', /* | */ - CHAR_ZERO_WIDTH_NOBREAK_SPACE: '\uFEFF' /* \uFEFF */ -}; + // glob --> regexps + set = set.map(function (s, si, set) { + return s.map(this.parse, this) + }, this) + this.debug(this.pattern, set) -/***/ }), -/* 200 */ -/***/ (function(module, exports, __webpack_require__) { + // filter out everything that didn't compile properly. + set = set.filter(function (s) { + return s.indexOf(false) === -1 + }) -"use strict"; + this.debug(this.pattern, set) + this.set = set +} -module.exports = __webpack_require__(201); +Minimatch.prototype.parseNegate = parseNegate +function parseNegate () { + var pattern = this.pattern + var negate = false + var options = this.options + var negateOffset = 0 + if (options.nonegate) return -/***/ }), -/* 201 */ -/***/ (function(module, exports, __webpack_require__) { + for (var i = 0, l = pattern.length + ; i < l && pattern.charAt(i) === '!' + ; i++) { + negate = !negate + negateOffset++ + } -"use strict"; + if (negateOffset) this.pattern = pattern.substr(negateOffset) + this.negate = negate +} +// Brace expansion: +// a{b,c}d -> abd acd +// a{b,}c -> abc ac +// a{0..3}d -> a0d a1d a2d a3d +// a{b,c{d,e}f}g -> abg acdfg acefg +// a{b,c}d{e,f}g -> abdeg acdeg abdeg abdfg +// +// Invalid sets are not expanded. +// a{2..}b -> a{2..}b +// a{b}c -> a{b}c +minimatch.braceExpand = function (pattern, options) { + return braceExpand(pattern, options) +} -const path = __webpack_require__(16); -const scan = __webpack_require__(202); -const parse = __webpack_require__(205); -const utils = __webpack_require__(203); +Minimatch.prototype.braceExpand = braceExpand -/** - * Creates a matcher function from one or more glob patterns. The - * returned function takes a string to match as its first argument, - * and returns true if the string is a match. The returned matcher - * function also takes a boolean as the second argument that, when true, - * returns an object with additional information. - * - * ```js - * const picomatch = require('picomatch'); - * // picomatch(glob[, options]); - * - * const isMatch = picomatch('*.!(*a)'); - * console.log(isMatch('a.a')); //=> false - * console.log(isMatch('a.b')); //=> true - * ``` - * @name picomatch - * @param {String|Array} `globs` One or more glob patterns. - * @param {Object=} `options` - * @return {Function=} Returns a matcher function. - * @api public - */ +function braceExpand (pattern, options) { + if (!options) { + if (this instanceof Minimatch) { + options = this.options + } else { + options = {} + } + } -const picomatch = (glob, options, returnState = false) => { - if (Array.isArray(glob)) { - let fns = glob.map(input => picomatch(input, options, returnState)); - return str => { - for (let isMatch of fns) { - let state = isMatch(str); - if (state) return state; - } - return false; - }; + pattern = typeof pattern === 'undefined' + ? this.pattern : pattern + + if (typeof pattern === 'undefined') { + throw new TypeError('undefined pattern') } - if (typeof glob !== 'string' || glob === '') { - throw new TypeError('Expected pattern to be a non-empty string'); + if (options.nobrace || + !pattern.match(/\{.*\}/)) { + // shortcut. no need to expand. + return [pattern] } - let opts = options || {}; - let posix = utils.isWindows(options); - let regex = picomatch.makeRe(glob, options, false, true); - let state = regex.state; - delete regex.state; + return expand(pattern) +} - let isIgnored = () => false; - if (opts.ignore) { - let ignoreOpts = { ...options, ignore: null, onMatch: null, onResult: null }; - isIgnored = picomatch(opts.ignore, ignoreOpts, returnState); +// parse a component of the expanded set. +// At this point, no pattern may contain "/" in it +// so we're going to return a 2d array, where each entry is the full +// pattern, split on '/', and then turned into a regular expression. +// A regexp is made at the end which joins each array with an +// escaped /, and another full one which joins each regexp with |. +// +// Following the lead of Bash 4.1, note that "**" only has special meaning +// when it is the *only* thing in a path portion. Otherwise, any series +// of * is equivalent to a single *. Globstar behavior is enabled by +// default, and can be disabled by setting options.noglobstar. +Minimatch.prototype.parse = parse +var SUBPARSE = {} +function parse (pattern, isSub) { + if (pattern.length > 1024 * 64) { + throw new TypeError('pattern is too long') } - const matcher = (input, returnObject = false) => { - let { isMatch, match, output } = picomatch.test(input, regex, options, { glob, posix }); - let result = { glob, state, regex, posix, input, output, match, isMatch }; + var options = this.options - if (typeof opts.onResult === 'function') { - opts.onResult(result); - } + // shortcuts + if (!options.noglobstar && pattern === '**') return GLOBSTAR + if (pattern === '') return '' - if (isMatch === false) { - result.isMatch = false; - return returnObject ? result : false; - } + var re = '' + var hasMagic = !!options.nocase + var escaping = false + // ? => one single character + var patternListStack = [] + var negativeLists = [] + var stateChar + var inClass = false + var reClassStart = -1 + var classStart = -1 + // . and .. never match anything that doesn't start with ., + // even when options.dot is set. + var patternStart = pattern.charAt(0) === '.' ? '' // anything + // not (start or / followed by . or .. followed by / or end) + : options.dot ? '(?!(?:^|\\\/)\\.{1,2}(?:$|\\\/))' + : '(?!\\.)' + var self = this - if (isIgnored(input)) { - if (typeof opts.onIgnore === 'function') { - opts.onIgnore(result); + function clearStateChar () { + if (stateChar) { + // we had some state-tracking character + // that wasn't consumed by this pass. + switch (stateChar) { + case '*': + re += star + hasMagic = true + break + case '?': + re += qmark + hasMagic = true + break + default: + re += '\\' + stateChar + break } - result.isMatch = false; - return returnObject ? result : false; + self.debug('clearStateChar %j %j', stateChar, re) + stateChar = false } + } - if (typeof opts.onMatch === 'function') { - opts.onMatch(result); + for (var i = 0, len = pattern.length, c + ; (i < len) && (c = pattern.charAt(i)) + ; i++) { + this.debug('%s\t%s %s %j', pattern, i, re, c) + + // skip over any that are escaped. + if (escaping && reSpecials[c]) { + re += '\\' + c + escaping = false + continue } - return returnObject ? result : true; - }; - if (returnState) { - matcher.state = state; - } + switch (c) { + case '/': + // completely not allowed, even escaped. + // Should already be path-split by now. + return false - return matcher; -}; + case '\\': + clearStateChar() + escaping = true + continue -/** - * Test `input` with the given `regex`. This is used by the main - * `picomatch()` function to test the input string. - * - * ```js - * const picomatch = require('picomatch'); - * // picomatch.test(input, regex[, options]); - * - * console.log(picomatch.test('foo/bar', /^(?:([^/]*?)\/([^/]*?))$/)); - * // { isMatch: true, match: [ 'foo/', 'foo', 'bar' ], output: 'foo/bar' } - * ``` - * @param {String} `input` String to test. - * @param {RegExp} `regex` - * @return {Object} Returns an object with matching info. - * @api public - */ + // the various stateChar values + // for the "extglob" stuff. + case '?': + case '*': + case '+': + case '@': + case '!': + this.debug('%s\t%s %s %j <-- stateChar', pattern, i, re, c) -picomatch.test = (input, regex, options, { glob, posix } = {}) => { - if (typeof input !== 'string') { - throw new TypeError('Expected input to be a string'); - } + // all of those are literals inside a class, except that + // the glob [!a] means [^a] in regexp + if (inClass) { + this.debug(' in class') + if (c === '!' && i === classStart + 1) c = '^' + re += c + continue + } - if (input === '') { - return { isMatch: false, output: '' }; - } + // if we already have a stateChar, then it means + // that there was something like ** or +? in there. + // Handle the stateChar, then proceed with this one. + self.debug('call clearStateChar %j', stateChar) + clearStateChar() + stateChar = c + // if extglob is disabled, then +(asdf|foo) isn't a thing. + // just clear the statechar *now*, rather than even diving into + // the patternList stuff. + if (options.noext) clearStateChar() + continue - let opts = options || {}; - let format = opts.format || (posix ? utils.toPosixSlashes : null); - let match = input === glob; - let output = (match && format) ? format(input) : input; + case '(': + if (inClass) { + re += '(' + continue + } - if (match === false) { - output = format ? format(input) : input; - match = output === glob; - } + if (!stateChar) { + re += '\\(' + continue + } - if (match === false || opts.capture === true) { - if (opts.matchBase === true || opts.basename === true) { - match = picomatch.matchBase(input, regex, options, posix); - } else { - match = regex.exec(output); - } - } + patternListStack.push({ + type: stateChar, + start: i - 1, + reStart: re.length, + open: plTypes[stateChar].open, + close: plTypes[stateChar].close + }) + // negation is (?:(?!js)[^/]*) + re += stateChar === '!' ? '(?:(?!(?:' : '(?:' + this.debug('plType %j %j', stateChar, re) + stateChar = false + continue + + case ')': + if (inClass || !patternListStack.length) { + re += '\\)' + continue + } + + clearStateChar() + hasMagic = true + var pl = patternListStack.pop() + // negation is (?:(?!js)[^/]*) + // The others are (?:) + re += pl.close + if (pl.type === '!') { + negativeLists.push(pl) + } + pl.reEnd = re.length + continue + + case '|': + if (inClass || !patternListStack.length || escaping) { + re += '\\|' + escaping = false + continue + } + + clearStateChar() + re += '|' + continue - return { isMatch: !!match, match, output }; -}; + // these are mostly the same in regexp and glob + case '[': + // swallow any state-tracking char before the [ + clearStateChar() -/** - * Match the basename of a filepath. - * - * ```js - * const picomatch = require('picomatch'); - * // picomatch.matchBase(input, glob[, options]); - * console.log(picomatch.matchBase('foo/bar.js', '*.js'); // true - * ``` - * @param {String} `input` String to test. - * @param {RegExp|String} `glob` Glob pattern or regex created by [.makeRe](#makeRe). - * @return {Boolean} - * @api public - */ + if (inClass) { + re += '\\' + c + continue + } -picomatch.matchBase = (input, glob, options, posix = utils.isWindows(options)) => { - let regex = glob instanceof RegExp ? glob : picomatch.makeRe(glob, options); - return regex.test(path.basename(input)); -}; + inClass = true + classStart = i + reClassStart = re.length + re += c + continue -/** - * Returns true if **any** of the given glob `patterns` match the specified `string`. - * - * ```js - * const picomatch = require('picomatch'); - * // picomatch.isMatch(string, patterns[, options]); - * - * console.log(picomatch.isMatch('a.a', ['b.*', '*.a'])); //=> true - * console.log(picomatch.isMatch('a.a', 'b.*')); //=> false - * ``` - * @param {String|Array} str The string to test. - * @param {String|Array} patterns One or more glob patterns to use for matching. - * @param {Object} [options] See available [options](#options). - * @return {Boolean} Returns true if any patterns match `str` - * @api public - */ + case ']': + // a right bracket shall lose its special + // meaning and represent itself in + // a bracket expression if it occurs + // first in the list. -- POSIX.2 2.8.3.2 + if (i === classStart + 1 || !inClass) { + re += '\\' + c + escaping = false + continue + } -picomatch.isMatch = (str, patterns, options) => picomatch(patterns, options)(str); + // handle the case where we left a class open. + // "[z-a]" is valid, equivalent to "\[z-a\]" + if (inClass) { + // split where the last [ was, make sure we don't have + // an invalid re. if so, re-walk the contents of the + // would-be class to re-translate any characters that + // were passed through as-is + // TODO: It would probably be faster to determine this + // without a try/catch and a new RegExp, but it's tricky + // to do safely. For now, this is safe and works. + var cs = pattern.substring(classStart + 1, i) + try { + RegExp('[' + cs + ']') + } catch (er) { + // not a valid class! + var sp = this.parse(cs, SUBPARSE) + re = re.substr(0, reClassStart) + '\\[' + sp[0] + '\\]' + hasMagic = hasMagic || sp[1] + inClass = false + continue + } + } -/** - * Parse a glob pattern to create the source string for a regular - * expression. - * - * ```js - * const picomatch = require('picomatch'); - * const result = picomatch.parse(glob[, options]); - * ``` - * @param {String} `glob` - * @param {Object} `options` - * @return {Object} Returns an object with useful properties and output to be used as a regex source string. - * @api public - */ + // finish up the class. + hasMagic = true + inClass = false + re += c + continue -picomatch.parse = (glob, options) => parse(glob, options); + default: + // swallow any state char that wasn't consumed + clearStateChar() -/** - * Scan a glob pattern to separate the pattern into segments. - * - * ```js - * const picomatch = require('picomatch'); - * // picomatch.scan(input[, options]); - * - * const result = picomatch.scan('!./foo/*.js'); - * console.log(result); - * // { prefix: '!./', - * // input: '!./foo/*.js', - * // base: 'foo', - * // glob: '*.js', - * // negated: true, - * // isGlob: true } - * ``` - * @param {String} `input` Glob pattern to scan. - * @param {Object} `options` - * @return {Object} Returns an object with - * @api public - */ + if (escaping) { + // no need + escaping = false + } else if (reSpecials[c] + && !(c === '^' && inClass)) { + re += '\\' + } -picomatch.scan = (input, options) => scan(input, options); + re += c -/** - * Create a regular expression from a glob pattern. - * - * ```js - * const picomatch = require('picomatch'); - * // picomatch.makeRe(input[, options]); - * - * console.log(picomatch.makeRe('*.js')); - * //=> /^(?:(?!\.)(?=.)[^/]*?\.js)$/ - * ``` - * @param {String} `input` A glob pattern to convert to regex. - * @param {Object} `options` - * @return {RegExp} Returns a regex created from the given pattern. - * @api public - */ + } // switch + } // for -picomatch.makeRe = (input, options, returnOutput = false, returnState = false) => { - if (!input || typeof input !== 'string') { - throw new TypeError('Expected a non-empty string'); + // handle the case where we left a class open. + // "[abc" is valid, equivalent to "\[abc" + if (inClass) { + // split where the last [ was, and escape it + // this is a huge pita. We now have to re-walk + // the contents of the would-be class to re-translate + // any characters that were passed through as-is + cs = pattern.substr(classStart + 1) + sp = this.parse(cs, SUBPARSE) + re = re.substr(0, reClassStart) + '\\[' + sp[0] + hasMagic = hasMagic || sp[1] } - let opts = options || {}; - let prepend = opts.contains ? '' : '^'; - let append = opts.contains ? '' : '$'; - let state = { negated: false, fastpaths: true }; - let prefix = ''; - let output; + // handle the case where we had a +( thing at the *end* + // of the pattern. + // each pattern list stack adds 3 chars, and we need to go through + // and escape any | chars that were passed through as-is for the regexp. + // Go through and escape them, taking care not to double-escape any + // | chars that were already escaped. + for (pl = patternListStack.pop(); pl; pl = patternListStack.pop()) { + var tail = re.slice(pl.reStart + pl.open.length) + this.debug('setting tail', re, pl) + // maybe some even number of \, then maybe 1 \, followed by a | + tail = tail.replace(/((?:\\{2}){0,64})(\\?)\|/g, function (_, $1, $2) { + if (!$2) { + // the | isn't already escaped, so escape it. + $2 = '\\' + } - if (input.startsWith('./')) { - input = input.slice(2); - prefix = state.prefix = './'; + // need to escape all those slashes *again*, without escaping the + // one that we need for escaping the | character. As it works out, + // escaping an even number of slashes can be done by simply repeating + // it exactly after itself. That's why this trick works. + // + // I am sorry that you have to see this. + return $1 + $1 + $2 + '|' + }) + + this.debug('tail=%j\n %s', tail, tail, pl, re) + var t = pl.type === '*' ? star + : pl.type === '?' ? qmark + : '\\' + pl.type + + hasMagic = true + re = re.slice(0, pl.reStart) + t + '\\(' + tail } - if (opts.fastpaths !== false && (input[0] === '.' || input[0] === '*')) { - output = parse.fastpaths(input, options); + // handle trailing things that only matter at the very end. + clearStateChar() + if (escaping) { + // trailing \\ + re += '\\\\' } - if (output === void 0) { - state = picomatch.parse(input, options); - state.prefix = prefix + (state.prefix || ''); - output = state.output; + // only need to apply the nodot start if the re starts with + // something that could conceivably capture a dot + var addPatternStart = false + switch (re.charAt(0)) { + case '.': + case '[': + case '(': addPatternStart = true } - if (returnOutput === true) { - return output; + // Hack to work around lack of negative lookbehind in JS + // A pattern like: *.!(x).!(y|z) needs to ensure that a name + // like 'a.xyz.yz' doesn't match. So, the first negative + // lookahead, has to look ALL the way ahead, to the end of + // the pattern. + for (var n = negativeLists.length - 1; n > -1; n--) { + var nl = negativeLists[n] + + var nlBefore = re.slice(0, nl.reStart) + var nlFirst = re.slice(nl.reStart, nl.reEnd - 8) + var nlLast = re.slice(nl.reEnd - 8, nl.reEnd) + var nlAfter = re.slice(nl.reEnd) + + nlLast += nlAfter + + // Handle nested stuff like *(*.js|!(*.json)), where open parens + // mean that we should *not* include the ) in the bit that is considered + // "after" the negated section. + var openParensBefore = nlBefore.split('(').length - 1 + var cleanAfter = nlAfter + for (i = 0; i < openParensBefore; i++) { + cleanAfter = cleanAfter.replace(/\)[+*?]?/, '') + } + nlAfter = cleanAfter + + var dollar = '' + if (nlAfter === '' && isSub !== SUBPARSE) { + dollar = '$' + } + var newRe = nlBefore + nlFirst + nlAfter + dollar + nlLast + re = newRe } - let source = `${prepend}(?:${output})${append}`; - if (state && state.negated === true) { - source = `^(?!${source}).*$`; + // if the re is not "" at this point, then we need to make sure + // it doesn't match against an empty path part. + // Otherwise a/* will match a/, which it should not. + if (re !== '' && hasMagic) { + re = '(?=.)' + re } - let regex = picomatch.toRegex(source, options); - if (returnState === true) { - regex.state = state; + if (addPatternStart) { + re = patternStart + re } - return regex; -}; + // parsing just a piece of a larger pattern. + if (isSub === SUBPARSE) { + return [re, hasMagic] + } -/** - * Create a regular expression from the given regex source string. - * - * ```js - * const picomatch = require('picomatch'); - * // picomatch.toRegex(source[, options]); - * - * const { output } = picomatch.parse('*.js'); - * console.log(picomatch.toRegex(output)); - * //=> /^(?:(?!\.)(?=.)[^/]*?\.js)$/ - * ``` - * @param {String} `source` Regular expression source string. - * @param {Object} `options` - * @return {RegExp} - * @api public - */ + // skip the regexp for non-magical patterns + // unescape anything in it, though, so that it'll be + // an exact match against a file etc. + if (!hasMagic) { + return globUnescape(pattern) + } -picomatch.toRegex = (source, options) => { + var flags = options.nocase ? 'i' : '' try { - let opts = options || {}; - return new RegExp(source, opts.flags || (opts.nocase ? 'i' : '')); - } catch (err) { - if (options && options.debug === true) throw err; - return /$^/; + var regExp = new RegExp('^' + re + '$', flags) + } catch (er) { + // If it was an invalid regular expression, then it can't match + // anything. This trick looks for a character after the end of + // the string, which is of course impossible, except in multi-line + // mode, but it's not a /m regex. + return new RegExp('$.') } -}; - -/** - * Picomatch constants. - * @return {Object} - */ - -picomatch.constants = __webpack_require__(204); - -/** - * Expose "picomatch" - */ -module.exports = picomatch; + regExp._glob = pattern + regExp._src = re + return regExp +} -/***/ }), -/* 202 */ -/***/ (function(module, exports, __webpack_require__) { +minimatch.makeRe = function (pattern, options) { + return new Minimatch(pattern, options || {}).makeRe() +} -"use strict"; +Minimatch.prototype.makeRe = makeRe +function makeRe () { + if (this.regexp || this.regexp === false) return this.regexp + // at this point, this.set is a 2d array of partial + // pattern strings, or "**". + // + // It's better to use .match(). This function shouldn't + // be used, really, but it's pretty convenient sometimes, + // when you just want to work with a regex. + var set = this.set -const utils = __webpack_require__(203); + if (!set.length) { + this.regexp = false + return this.regexp + } + var options = this.options -const { - CHAR_ASTERISK, /* * */ - CHAR_AT, /* @ */ - CHAR_BACKWARD_SLASH, /* \ */ - CHAR_COMMA, /* , */ - CHAR_DOT, /* . */ - CHAR_EXCLAMATION_MARK, /* ! */ - CHAR_FORWARD_SLASH, /* / */ - CHAR_LEFT_CURLY_BRACE, /* { */ - CHAR_LEFT_PARENTHESES, /* ( */ - CHAR_LEFT_SQUARE_BRACKET, /* [ */ - CHAR_PLUS, /* + */ - CHAR_QUESTION_MARK, /* ? */ - CHAR_RIGHT_CURLY_BRACE, /* } */ - CHAR_RIGHT_PARENTHESES, /* ) */ - CHAR_RIGHT_SQUARE_BRACKET /* ] */ -} = __webpack_require__(204); + var twoStar = options.noglobstar ? star + : options.dot ? twoStarDot + : twoStarNoDot + var flags = options.nocase ? 'i' : '' -const isPathSeparator = code => { - return code === CHAR_FORWARD_SLASH || code === CHAR_BACKWARD_SLASH; -}; + var re = set.map(function (pattern) { + return pattern.map(function (p) { + return (p === GLOBSTAR) ? twoStar + : (typeof p === 'string') ? regExpEscape(p) + : p._src + }).join('\\\/') + }).join('|') -/** - * Quickly scans a glob pattern and returns an object with a handful of - * useful properties, like `isGlob`, `path` (the leading non-glob, if it exists), - * `glob` (the actual pattern), and `negated` (true if the path starts with `!`). - * - * ```js - * const pm = require('picomatch'); - * console.log(pm.scan('foo/bar/*.js')); - * { isGlob: true, input: 'foo/bar/*.js', base: 'foo/bar', glob: '*.js' } - * ``` - * @param {String} `str` - * @param {Object} `options` - * @return {Object} Returns an object with tokens and regex source string. - * @api public - */ + // must match entire pattern + // ending in a * or ** will make it less strict. + re = '^(?:' + re + ')$' -module.exports = (input, options) => { - let opts = options || {}; - let length = input.length - 1; - let index = -1; - let start = 0; - let lastIndex = 0; - let isGlob = false; - let backslashes = false; - let negated = false; - let braces = 0; - let prev; - let code; + // can match anything, as long as it's not this. + if (this.negate) re = '^(?!' + re + ').*$' - let braceEscaped = false; + try { + this.regexp = new RegExp(re, flags) + } catch (ex) { + this.regexp = false + } + return this.regexp +} - let eos = () => index >= length; - let advance = () => { - prev = code; - return input.charCodeAt(++index); - }; +minimatch.match = function (list, pattern, options) { + options = options || {} + var mm = new Minimatch(pattern, options) + list = list.filter(function (f) { + return mm.match(f) + }) + if (mm.options.nonull && !list.length) { + list.push(pattern) + } + return list +} - while (index < length) { - code = advance(); - let next; +Minimatch.prototype.match = match +function match (f, partial) { + this.debug('match', f, this.pattern) + // short-circuit in the case of busted things. + // comments, etc. + if (this.comment) return false + if (this.empty) return f === '' - if (code === CHAR_BACKWARD_SLASH) { - backslashes = true; - next = advance(); + if (f === '/' && partial) return true - if (next === CHAR_LEFT_CURLY_BRACE) { - braceEscaped = true; - } - continue; - } + var options = this.options - if (braceEscaped === true || code === CHAR_LEFT_CURLY_BRACE) { - braces++; + // windows: need to use /, not \ + if (path.sep !== '/') { + f = f.split(path.sep).join('/') + } - while (!eos() && (next = advance())) { - if (next === CHAR_BACKWARD_SLASH) { - backslashes = true; - next = advance(); - continue; - } + // treat the test path as a set of pathparts. + f = f.split(slashSplit) + this.debug(this.pattern, 'split', f) - if (next === CHAR_LEFT_CURLY_BRACE) { - braces++; - continue; - } + // just ONE of the pattern sets in this.set needs to match + // in order for it to be valid. If negating, then just one + // match means that we have failed. + // Either way, return on the first hit. - if (!braceEscaped && next === CHAR_DOT && (next = advance()) === CHAR_DOT) { - isGlob = true; - break; - } + var set = this.set + this.debug(this.pattern, 'set', set) - if (!braceEscaped && next === CHAR_COMMA) { - isGlob = true; - break; - } + // Find the basename of the path by looking for the last non-empty segment + var filename + var i + for (i = f.length - 1; i >= 0; i--) { + filename = f[i] + if (filename) break + } - if (next === CHAR_RIGHT_CURLY_BRACE) { - braces--; - if (braces === 0) { - braceEscaped = false; - break; - } - } - } + for (i = 0; i < set.length; i++) { + var pattern = set[i] + var file = f + if (options.matchBase && pattern.length === 1) { + file = [filename] + } + var hit = this.matchOne(file, pattern, partial) + if (hit) { + if (options.flipNegate) return true + return !this.negate } + } - if (code === CHAR_FORWARD_SLASH) { - if (prev === CHAR_DOT && index === (start + 1)) { - start += 2; - continue; - } + // didn't get any hits. this is success if it's a negative + // pattern, failure otherwise. + if (options.flipNegate) return false + return this.negate +} - lastIndex = index + 1; - continue; - } +// set partial to true to test if, for example, +// "/a/b" matches the start of "/*/b/*/d" +// Partial means, if you run out of file before you run +// out of pattern, then that's fine, as long as all +// the parts match. +Minimatch.prototype.matchOne = function (file, pattern, partial) { + var options = this.options - if (code === CHAR_ASTERISK) { - isGlob = true; - break; - } + this.debug('matchOne', + { 'this': this, file: file, pattern: pattern }) - if (code === CHAR_ASTERISK || code === CHAR_QUESTION_MARK) { - isGlob = true; - break; - } + this.debug('matchOne', file.length, pattern.length) - if (code === CHAR_LEFT_SQUARE_BRACKET) { - while (!eos() && (next = advance())) { - if (next === CHAR_BACKWARD_SLASH) { - backslashes = true; - next = advance(); - continue; - } + for (var fi = 0, + pi = 0, + fl = file.length, + pl = pattern.length + ; (fi < fl) && (pi < pl) + ; fi++, pi++) { + this.debug('matchOne loop') + var p = pattern[pi] + var f = file[fi] - if (next === CHAR_RIGHT_SQUARE_BRACKET) { - isGlob = true; - break; + this.debug(pattern, p, f) + + // should be impossible. + // some invalid regexp stuff in the set. + if (p === false) return false + + if (p === GLOBSTAR) { + this.debug('GLOBSTAR', [pattern, p, f]) + + // "**" + // a/**/b/**/c would match the following: + // a/b/x/y/z/c + // a/x/y/z/b/c + // a/b/x/b/x/c + // a/b/c + // To do this, take the rest of the pattern after + // the **, and see if it would match the file remainder. + // If so, return success. + // If not, the ** "swallows" a segment, and try again. + // This is recursively awful. + // + // a/**/b/**/c matching a/b/x/y/z/c + // - a matches a + // - doublestar + // - matchOne(b/x/y/z/c, b/**/c) + // - b matches b + // - doublestar + // - matchOne(x/y/z/c, c) -> no + // - matchOne(y/z/c, c) -> no + // - matchOne(z/c, c) -> no + // - matchOne(c, c) yes, hit + var fr = fi + var pr = pi + 1 + if (pr === pl) { + this.debug('** at the end') + // a ** at the end will just swallow the rest. + // We have found a match. + // however, it will not swallow /.x, unless + // options.dot is set. + // . and .. are *never* matched by **, for explosively + // exponential reasons. + for (; fi < fl; fi++) { + if (file[fi] === '.' || file[fi] === '..' || + (!options.dot && file[fi].charAt(0) === '.')) return false } + return true } - } - let isExtglobChar = code === CHAR_PLUS - || code === CHAR_AT - || code === CHAR_EXCLAMATION_MARK; + // ok, let's see if we can swallow whatever we can. + while (fr < fl) { + var swallowee = file[fr] - if (isExtglobChar && input.charCodeAt(index + 1) === CHAR_LEFT_PARENTHESES) { - isGlob = true; - break; - } + this.debug('\nglobstar while', file, fr, pattern, pr, swallowee) - if (code === CHAR_EXCLAMATION_MARK && index === start) { - negated = true; - start++; - continue; - } + // XXX remove this slice. Just pass the start index. + if (this.matchOne(file.slice(fr), pattern.slice(pr), partial)) { + this.debug('globstar found match!', fr, fl, swallowee) + // found a match. + return true + } else { + // can't swallow "." or ".." ever. + // can only swallow ".foo" when explicitly asked. + if (swallowee === '.' || swallowee === '..' || + (!options.dot && swallowee.charAt(0) === '.')) { + this.debug('dot detected!', file, fr, pattern, pr) + break + } - if (code === CHAR_LEFT_PARENTHESES) { - while (!eos() && (next = advance())) { - if (next === CHAR_BACKWARD_SLASH) { - backslashes = true; - next = advance(); - continue; + // ** swallows a segment, and continue. + this.debug('globstar swallow a segment, and continue') + fr++ } + } - if (next === CHAR_RIGHT_PARENTHESES) { - isGlob = true; - break; - } + // no match was found. + // However, in partial mode, we can't say this is necessarily over. + // If there's more *pattern* left, then + if (partial) { + // ran out of file + this.debug('\n>>> no match, partial?', file, fr, pattern, pr) + if (fr === fl) return true } + return false } - if (isGlob) { - break; + // something other than ** + // non-magic patterns just have to match exactly + // patterns with magic have been turned into regexps. + var hit + if (typeof p === 'string') { + if (options.nocase) { + hit = f.toLowerCase() === p.toLowerCase() + } else { + hit = f === p + } + this.debug('string match', p, f, hit) + } else { + hit = f.match(p) + this.debug('pattern match', p, f, hit) } - } - - let prefix = ''; - let orig = input; - let base = input; - let glob = ''; - if (start > 0) { - prefix = input.slice(0, start); - input = input.slice(start); - lastIndex -= start; + if (!hit) return false } - if (base && isGlob === true && lastIndex > 0) { - base = input.slice(0, lastIndex); - glob = input.slice(lastIndex); - } else if (isGlob === true) { - base = ''; - glob = input; - } else { - base = input; - } + // Note: ending in / means that we'll get a final "" + // at the end of the pattern. This can only match a + // corresponding "" at the end of the file. + // If the file ends in /, then it can only match a + // a pattern that ends in /, unless the pattern just + // doesn't have any more for it. But, a/b/ should *not* + // match "a/b/*", even though "" matches against the + // [^/]*? pattern, except in partial mode, where it might + // simply not be reached yet. + // However, a/b/ should still satisfy a/* - if (base && base !== '' && base !== '/' && base !== input) { - if (isPathSeparator(base.charCodeAt(base.length - 1))) { - base = base.slice(0, -1); - } + // now either we fell off the end of the pattern, or we're done. + if (fi === fl && pi === pl) { + // ran out of pattern and filename at the same time. + // an exact hit! + return true + } else if (fi === fl) { + // ran out of file, but still had pattern left. + // this is ok if we're doing the match as part of + // a glob fs traversal. + return partial + } else if (pi === pl) { + // ran out of pattern, still have file left. + // this is only acceptable if we're on the very last + // empty segment of a file with a trailing slash. + // a/* should match a/b/ + var emptyFileEnd = (fi === fl - 1) && (file[fi] === '') + return emptyFileEnd } - if (opts.unescape === true) { - if (glob) glob = utils.removeBackslashes(glob); + // should be unreachable. + throw new Error('wtf?') +} - if (base && backslashes === true) { - base = utils.removeBackslashes(base); - } - } +// replace stuff like \* with * +function globUnescape (s) { + return s.replace(/\\(.)/g, '$1') +} - return { prefix, input: orig, base, glob, negated, isGlob }; -}; +function regExpEscape (s) { + return s.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&') +} /***/ }), -/* 203 */ +/* 61 */ /***/ (function(module, exports, __webpack_require__) { -"use strict"; - +var wrappy = __webpack_require__(123) +module.exports = wrappy(once) +module.exports.strict = wrappy(onceStrict) -const path = __webpack_require__(16); -const win32 = process.platform === 'win32'; -const { - REGEX_SPECIAL_CHARS, - REGEX_SPECIAL_CHARS_GLOBAL, - REGEX_REMOVE_BACKSLASH -} = __webpack_require__(204); +once.proto = once(function () { + Object.defineProperty(Function.prototype, 'once', { + value: function () { + return once(this) + }, + configurable: true + }) -exports.isObject = val => val !== null && typeof val === 'object' && !Array.isArray(val); -exports.hasRegexChars = str => REGEX_SPECIAL_CHARS.test(str); -exports.isRegexChar = str => str.length === 1 && exports.hasRegexChars(str); -exports.escapeRegex = str => str.replace(REGEX_SPECIAL_CHARS_GLOBAL, '\\$1'); -exports.toPosixSlashes = str => str.replace(/\\/g, '/'); + Object.defineProperty(Function.prototype, 'onceStrict', { + value: function () { + return onceStrict(this) + }, + configurable: true + }) +}) -exports.removeBackslashes = str => { - return str.replace(REGEX_REMOVE_BACKSLASH, match => { - return match === '\\' ? '' : match; - }); +function once (fn) { + var f = function () { + if (f.called) return f.value + f.called = true + return f.value = fn.apply(this, arguments) + } + f.called = false + return f } -exports.supportsLookbehinds = () => { - let segs = process.version.slice(1).split('.'); - if (segs.length === 3 && +segs[0] >= 9 || (+segs[0] === 8 && +segs[1] >= 10)) { - return true; +function onceStrict (fn) { + var f = function () { + if (f.called) + throw new Error(f.onceError) + f.called = true + return f.value = fn.apply(this, arguments) } - return false; -}; + var name = fn.name || 'Function wrapped with `once`' + f.onceError = name + " shouldn't be called more than once" + f.called = false + return f +} -exports.isWindows = options => { - if (options && typeof options.windows === 'boolean') { - return options.windows; - } - return win32 === true || path.sep === '\\'; -}; -exports.escapeLast = (input, char, lastIdx) => { - let idx = input.lastIndexOf(char, lastIdx); - if (idx === -1) return input; - if (input[idx - 1] === '\\') return exports.escapeLast(input, char, idx - 1); - return input.slice(0, idx) + '\\' + input.slice(idx); +/***/ }), +/* 62 */, +/* 63 */ +/***/ (function(module, exports) { + +module.exports = __webpack_require__(585); + +/***/ }), +/* 64 */, +/* 65 */, +/* 66 */, +/* 67 */ +/***/ (function(module, exports) { + +// 7.2.1 RequireObjectCoercible(argument) +module.exports = function (it) { + if (it == undefined) throw TypeError("Can't call method on " + it); + return it; }; /***/ }), -/* 204 */ +/* 68 */ /***/ (function(module, exports, __webpack_require__) { -"use strict"; +var isObject = __webpack_require__(34); +var document = __webpack_require__(11).document; +// typeof document.createElement is 'object' in old IE +var is = isObject(document) && isObject(document.createElement); +module.exports = function (it) { + return is ? document.createElement(it) : {}; +}; -const path = __webpack_require__(16); -const WIN_SLASH = '\\\\/'; -const WIN_NO_SLASH = `[^${WIN_SLASH}]`; +/***/ }), +/* 69 */ +/***/ (function(module, exports) { -/** - * Posix glob regex - */ +module.exports = true; -const DOT_LITERAL = '\\.'; -const PLUS_LITERAL = '\\+'; -const QMARK_LITERAL = '\\?'; -const SLASH_LITERAL = '\\/'; -const ONE_CHAR = '(?=.)'; -const QMARK = '[^/]'; -const END_ANCHOR = `(?:${SLASH_LITERAL}|$)`; -const START_ANCHOR = `(?:^|${SLASH_LITERAL})`; -const DOTS_SLASH = `${DOT_LITERAL}{1,2}${END_ANCHOR}`; -const NO_DOT = `(?!${DOT_LITERAL})`; -const NO_DOTS = `(?!${START_ANCHOR}${DOTS_SLASH})`; -const NO_DOT_SLASH = `(?!${DOT_LITERAL}{0,1}${END_ANCHOR})`; -const NO_DOTS_SLASH = `(?!${DOTS_SLASH})`; -const QMARK_NO_DOT = `[^.${SLASH_LITERAL}]`; -const STAR = `${QMARK}*?`; -const POSIX_CHARS = { - DOT_LITERAL, - PLUS_LITERAL, - QMARK_LITERAL, - SLASH_LITERAL, - ONE_CHAR, - QMARK, - END_ANCHOR, - DOTS_SLASH, - NO_DOT, - NO_DOTS, - NO_DOT_SLASH, - NO_DOTS_SLASH, - QMARK_NO_DOT, - STAR, - START_ANCHOR +/***/ }), +/* 70 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +// 25.4.1.5 NewPromiseCapability(C) +var aFunction = __webpack_require__(46); + +function PromiseCapability(C) { + var resolve, reject; + this.promise = new C(function ($$resolve, $$reject) { + if (resolve !== undefined || reject !== undefined) throw TypeError('Bad Promise constructor'); + resolve = $$resolve; + reject = $$reject; + }); + this.resolve = aFunction(resolve); + this.reject = aFunction(reject); +} + +module.exports.f = function (C) { + return new PromiseCapability(C); }; -/** - * Windows glob regex - */ -const WINDOWS_CHARS = { - ...POSIX_CHARS, +/***/ }), +/* 71 */ +/***/ (function(module, exports, __webpack_require__) { - SLASH_LITERAL: `[${WIN_SLASH}]`, - QMARK: WIN_NO_SLASH, - STAR: `${WIN_NO_SLASH}*?`, - DOTS_SLASH: `${DOT_LITERAL}{1,2}(?:[${WIN_SLASH}]|$)`, - NO_DOT: `(?!${DOT_LITERAL})`, - NO_DOTS: `(?!(?:^|[${WIN_SLASH}])${DOT_LITERAL}{1,2}(?:[${WIN_SLASH}]|$))`, - NO_DOT_SLASH: `(?!${DOT_LITERAL}{0,1}(?:[${WIN_SLASH}]|$))`, - NO_DOTS_SLASH: `(?!${DOT_LITERAL}{1,2}(?:[${WIN_SLASH}]|$))`, - QMARK_NO_DOT: `[^.${WIN_SLASH}]`, - START_ANCHOR: `(?:^|[${WIN_SLASH}])`, - END_ANCHOR: `(?:[${WIN_SLASH}]|$)` +var def = __webpack_require__(50).f; +var has = __webpack_require__(49); +var TAG = __webpack_require__(13)('toStringTag'); + +module.exports = function (it, tag, stat) { + if (it && !has(it = stat ? it : it.prototype, TAG)) def(it, TAG, { configurable: true, value: tag }); }; -/** - * POSIX Bracket Regex - */ -const POSIX_REGEX_SOURCE = { - alnum: 'a-zA-Z0-9', - alpha: 'a-zA-Z', - ascii: '\\x00-\\x7F', - blank: ' \\t', - cntrl: '\\x00-\\x1F\\x7F', - digit: '0-9', - graph: '\\x21-\\x7E', - lower: 'a-z', - print: '\\x20-\\x7E ', - punct: '\\-!"#$%&\'()\\*+,./:;<=>?@[\\]^_`{|}~', - space: ' \\t\\r\\n\\v\\f', - upper: 'A-Z', - word: 'A-Za-z0-9_', - xdigit: 'A-Fa-f0-9' +/***/ }), +/* 72 */ +/***/ (function(module, exports, __webpack_require__) { + +var shared = __webpack_require__(107)('keys'); +var uid = __webpack_require__(111); +module.exports = function (key) { + return shared[key] || (shared[key] = uid(key)); }; -module.exports = { - MAX_LENGTH: 1024 * 64, - POSIX_REGEX_SOURCE, - // regular expressions - REGEX_BACKSLASH: /\\(?![*+?^${}(|)[\]])/g, - REGEX_NON_SPECIAL_CHAR: /^[^@![\].,$*+?^{}()|\\/]+/, - REGEX_SPECIAL_CHARS: /[-*+?.^${}(|)[\]]/, - REGEX_SPECIAL_CHARS_BACKREF: /(\\?)((\W)(\3*))/g, - REGEX_SPECIAL_CHARS_GLOBAL: /([-*+?.^${}(|)[\]])/g, - REGEX_REMOVE_BACKSLASH: /(?:\[.*?[^\\]\]|\\(?=.))/g, +/***/ }), +/* 73 */ +/***/ (function(module, exports) { - // Replace globs with equivalent patterns to reduce parsing time. - REPLACEMENTS: { - '***': '*', - '**/**': '**', - '**/**/**': '**' - }, +// 7.1.4 ToInteger +var ceil = Math.ceil; +var floor = Math.floor; +module.exports = function (it) { + return isNaN(it = +it) ? 0 : (it > 0 ? floor : ceil)(it); +}; - // Digits - CHAR_0: 48, /* 0 */ - CHAR_9: 57, /* 9 */ - // Alphabet chars. - CHAR_UPPERCASE_A: 65, /* A */ - CHAR_LOWERCASE_A: 97, /* a */ - CHAR_UPPERCASE_Z: 90, /* Z */ - CHAR_LOWERCASE_Z: 122, /* z */ +/***/ }), +/* 74 */ +/***/ (function(module, exports, __webpack_require__) { - CHAR_LEFT_PARENTHESES: 40, /* ( */ - CHAR_RIGHT_PARENTHESES: 41, /* ) */ +// to indexed object, toObject with fallback for non-array-like ES3 strings +var IObject = __webpack_require__(131); +var defined = __webpack_require__(67); +module.exports = function (it) { + return IObject(defined(it)); +}; - CHAR_ASTERISK: 42, /* * */ - // Non-alphabetic chars. - CHAR_AMPERSAND: 38, /* & */ - CHAR_AT: 64, /* @ */ - CHAR_BACKWARD_SLASH: 92, /* \ */ - CHAR_CARRIAGE_RETURN: 13, /* \r */ - CHAR_CIRCUMFLEX_ACCENT: 94, /* ^ */ - CHAR_COLON: 58, /* : */ - CHAR_COMMA: 44, /* , */ - CHAR_DOT: 46, /* . */ - CHAR_DOUBLE_QUOTE: 34, /* " */ - CHAR_EQUAL: 61, /* = */ - CHAR_EXCLAMATION_MARK: 33, /* ! */ - CHAR_FORM_FEED: 12, /* \f */ - CHAR_FORWARD_SLASH: 47, /* / */ - CHAR_GRAVE_ACCENT: 96, /* ` */ - CHAR_HASH: 35, /* # */ - CHAR_HYPHEN_MINUS: 45, /* - */ - CHAR_LEFT_ANGLE_BRACKET: 60, /* < */ - CHAR_LEFT_CURLY_BRACE: 123, /* { */ - CHAR_LEFT_SQUARE_BRACKET: 91, /* [ */ - CHAR_LINE_FEED: 10, /* \n */ - CHAR_NO_BREAK_SPACE: 160, /* \u00A0 */ - CHAR_PERCENT: 37, /* % */ - CHAR_PLUS: 43, /* + */ - CHAR_QUESTION_MARK: 63, /* ? */ - CHAR_RIGHT_ANGLE_BRACKET: 62, /* > */ - CHAR_RIGHT_CURLY_BRACE: 125, /* } */ - CHAR_RIGHT_SQUARE_BRACKET: 93, /* ] */ - CHAR_SEMICOLON: 59, /* ; */ - CHAR_SINGLE_QUOTE: 39, /* ' */ - CHAR_SPACE: 32, /* */ - CHAR_TAB: 9, /* \t */ - CHAR_UNDERSCORE: 95, /* _ */ - CHAR_VERTICAL_LINE: 124, /* | */ - CHAR_ZERO_WIDTH_NOBREAK_SPACE: 65279, /* \uFEFF */ +/***/ }), +/* 75 */ +/***/ (function(module, exports, __webpack_require__) { + +// Approach: +// +// 1. Get the minimatch set +// 2. For each pattern in the set, PROCESS(pattern, false) +// 3. Store matches per-set, then uniq them +// +// PROCESS(pattern, inGlobStar) +// Get the first [n] items from pattern that are all strings +// Join these together. This is PREFIX. +// If there is no more remaining, then stat(PREFIX) and +// add to matches if it succeeds. END. +// +// If inGlobStar and PREFIX is symlink and points to dir +// set ENTRIES = [] +// else readdir(PREFIX) as ENTRIES +// If fail, END +// +// with ENTRIES +// If pattern[n] is GLOBSTAR +// // handle the case where the globstar match is empty +// // by pruning it out, and testing the resulting pattern +// PROCESS(pattern[0..n] + pattern[n+1 .. $], false) +// // handle other cases. +// for ENTRY in ENTRIES (not dotfiles) +// // attach globstar + tail onto the entry +// // Mark that this entry is a globstar match +// PROCESS(pattern[0..n] + ENTRY + pattern[n .. $], true) +// +// else // not globstar +// for ENTRY in ENTRIES (not dotfiles, unless pattern[n] is dot) +// Test ENTRY against pattern[n] +// If fails, continue +// If passes, PROCESS(pattern[0..n] + item + pattern[n+1 .. $]) +// +// Caveat: +// Cache all stats and readdirs results to minimize syscall. Since all +// we ever care about is existence and directory-ness, we can just keep +// `true` for files, and [children,...] for directories, or `false` for +// things that don't exist. - SEP: path.sep, +module.exports = glob - /** - * Create EXTGLOB_CHARS - */ +var fs = __webpack_require__(3) +var rp = __webpack_require__(114) +var minimatch = __webpack_require__(60) +var Minimatch = minimatch.Minimatch +var inherits = __webpack_require__(42) +var EE = __webpack_require__(54).EventEmitter +var path = __webpack_require__(0) +var assert = __webpack_require__(22) +var isAbsolute = __webpack_require__(76) +var globSync = __webpack_require__(218) +var common = __webpack_require__(115) +var alphasort = common.alphasort +var alphasorti = common.alphasorti +var setopts = common.setopts +var ownProp = common.ownProp +var inflight = __webpack_require__(223) +var util = __webpack_require__(2) +var childrenIgnored = common.childrenIgnored +var isIgnored = common.isIgnored - extglobChars(chars) { - return { - '!': { type: 'negate', open: '(?:(?!(?:', close: `))${chars.STAR})` }, - '?': { type: 'qmark', open: '(?:', close: ')?' }, - '+': { type: 'plus', open: '(?:', close: ')+' }, - '*': { type: 'star', open: '(?:', close: ')*' }, - '@': { type: 'at', open: '(?:', close: ')' } - }; - }, +var once = __webpack_require__(61) - /** - * Create GLOB_CHARS - */ +function glob (pattern, options, cb) { + if (typeof options === 'function') cb = options, options = {} + if (!options) options = {} - globChars(win32) { - return win32 === true ? WINDOWS_CHARS : POSIX_CHARS; + if (options.sync) { + if (cb) + throw new TypeError('callback provided to sync glob') + return globSync(pattern, options) } -}; + return new Glob(pattern, options, cb) +} -/***/ }), -/* 205 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; +glob.sync = globSync +var GlobSync = glob.GlobSync = globSync.GlobSync +// old api surface +glob.glob = glob -const utils = __webpack_require__(203); -const constants = __webpack_require__(204); +function extend (origin, add) { + if (add === null || typeof add !== 'object') { + return origin + } -/** - * Constants - */ + var keys = Object.keys(add) + var i = keys.length + while (i--) { + origin[keys[i]] = add[keys[i]] + } + return origin +} -const { - MAX_LENGTH, - POSIX_REGEX_SOURCE, - REGEX_NON_SPECIAL_CHAR, - REGEX_SPECIAL_CHARS_BACKREF, - REPLACEMENTS -} = constants; +glob.hasMagic = function (pattern, options_) { + var options = extend({}, options_) + options.noprocess = true -/** - * Helpers - */ + var g = new Glob(pattern, options) + var set = g.minimatch.set -const expandRange = (args, options) => { - if (typeof options.expandRange === 'function') { - return options.expandRange(...args, options); - } + if (!pattern) + return false - args.sort(); - let value = `[${args.join('-')}]`; + if (set.length > 1) + return true - try { - /* eslint-disable no-new */ - new RegExp(value); - } catch (ex) { - return args.map(v => utils.escapeRegex(v)).join('..'); + for (var j = 0; j < set[0].length; j++) { + if (typeof set[0][j] !== 'string') + return true } - return value; -}; - -const negate = state => { - let count = 1; + return false +} - while (state.peek() === '!' && (state.peek(2) !== '(' || state.peek(3) === '?')) { - state.advance(); - state.start++; - count++; +glob.Glob = Glob +inherits(Glob, EE) +function Glob (pattern, options, cb) { + if (typeof options === 'function') { + cb = options + options = null } - if (count % 2 === 0) { - return false; + if (options && options.sync) { + if (cb) + throw new TypeError('callback provided to sync glob') + return new GlobSync(pattern, options) } - state.negated = true; - state.start++; - return true; -}; - -/** - * Create the message for a syntax error - */ - -const syntaxError = (type, char) => { - return `Missing ${type}: "${char}" - use "\\\\${char}" to match literal characters`; -}; + if (!(this instanceof Glob)) + return new Glob(pattern, options, cb) -/** - * Parse the given input string. - * @param {String} input - * @param {Object} options - * @return {Object} - */ + setopts(this, pattern, options) + this._didRealPath = false -const parse = (input, options) => { - if (typeof input !== 'string') { - throw new TypeError('Expected a string'); - } + // process each pattern in the minimatch set + var n = this.minimatch.set.length - input = REPLACEMENTS[input] || input; + // The matches are stored as {: true,...} so that + // duplicates are automagically pruned. + // Later, we do an Object.keys() on these. + // Keep them as a list so we can fill in when nonull is set. + this.matches = new Array(n) - let opts = { ...options }; - let max = typeof opts.maxLength === 'number' ? Math.min(MAX_LENGTH, opts.maxLength) : MAX_LENGTH; - let len = input.length; - if (len > max) { - throw new SyntaxError(`Input length: ${len}, exceeds maximum allowed length: ${max}`); + if (typeof cb === 'function') { + cb = once(cb) + this.on('error', cb) + this.on('end', function (matches) { + cb(null, matches) + }) } - let bos = { type: 'bos', value: '', output: opts.prepend || '' }; - let tokens = [bos]; - - let capture = opts.capture ? '' : '?:'; - let win32 = utils.isWindows(options); - - // create constants based on platform, for windows or posix - const PLATFORM_CHARS = constants.globChars(win32); - const EXTGLOB_CHARS = constants.extglobChars(PLATFORM_CHARS); + var self = this + this._processing = 0 - const { - DOT_LITERAL, - PLUS_LITERAL, - SLASH_LITERAL, - ONE_CHAR, - DOTS_SLASH, - NO_DOT, - NO_DOT_SLASH, - NO_DOTS_SLASH, - QMARK, - QMARK_NO_DOT, - STAR, - START_ANCHOR - } = PLATFORM_CHARS; + this._emitQueue = [] + this._processQueue = [] + this.paused = false - const globstar = (opts) => { - return `(${capture}(?:(?!${START_ANCHOR}${opts.dot ? DOTS_SLASH : DOT_LITERAL}).)*?)`; - }; + if (this.noprocess) + return this - let nodot = opts.dot ? '' : NO_DOT; - let star = opts.bash === true ? globstar(opts) : STAR; - let qmarkNoDot = opts.dot ? QMARK : QMARK_NO_DOT; + if (n === 0) + return done() - if (opts.capture) { - star = `(${star})`; + var sync = true + for (var i = 0; i < n; i ++) { + this._process(this.minimatch.set[i], i, false, done) } + sync = false - // minimatch options support - if (typeof opts.noext === 'boolean') { - opts.noextglob = opts.noext; + function done () { + --self._processing + if (self._processing <= 0) { + if (sync) { + process.nextTick(function () { + self._finish() + }) + } else { + self._finish() + } + } } +} - let state = { - index: -1, - start: 0, - consumed: '', - output: '', - backtrack: false, - brackets: 0, - braces: 0, - parens: 0, - quotes: 0, - tokens - }; +Glob.prototype._finish = function () { + assert(this instanceof Glob) + if (this.aborted) + return - let extglobs = []; - let stack = []; - let prev = bos; - let value; + if (this.realpath && !this._didRealpath) + return this._realpath() - /** - * Tokenizing helpers - */ + common.finish(this) + this.emit('end', this.found) +} - const eos = () => state.index === len - 1; - const peek = state.peek = (n = 1) => input[state.index + n]; - const advance = state.advance = () => input[++state.index]; - const append = token => { - state.output += token.output != null ? token.output : token.value; - state.consumed += token.value || ''; - }; +Glob.prototype._realpath = function () { + if (this._didRealpath) + return - const increment = type => { - state[type]++; - stack.push(type); - }; + this._didRealpath = true - const decrement = type => { - state[type]--; - stack.pop(); - }; + var n = this.matches.length + if (n === 0) + return this._finish() - /** - * Push tokens onto the tokens array. This helper speeds up - * tokenizing by 1) helping us avoid backtracking as much as possible, - * and 2) helping us avoid creating extra tokens when consecutive - * characters are plain text. This improves performance and simplifies - * lookbehinds. - */ + var self = this + for (var i = 0; i < this.matches.length; i++) + this._realpathSet(i, next) - const push = tok => { - if (prev.type === 'globstar') { - let isBrace = state.braces > 0 && (tok.type === 'comma' || tok.type === 'brace'); - let isExtglob = extglobs.length && (tok.type === 'pipe' || tok.type === 'paren'); - if (tok.type !== 'slash' && tok.type !== 'paren' && !isBrace && !isExtglob) { - state.output = state.output.slice(0, -prev.output.length); - prev.type = 'star'; - prev.value = '*'; - prev.output = star; - state.output += prev.output; - } - } + function next () { + if (--n === 0) + self._finish() + } +} - if (extglobs.length && tok.type !== 'paren' && !EXTGLOB_CHARS[tok.value]) { - extglobs[extglobs.length - 1].inner += tok.value; - } +Glob.prototype._realpathSet = function (index, cb) { + var matchset = this.matches[index] + if (!matchset) + return cb() - if (tok.value || tok.output) append(tok); - if (prev && prev.type === 'text' && tok.type === 'text') { - prev.value += tok.value; - return; - } + var found = Object.keys(matchset) + var self = this + var n = found.length - tok.prev = prev; - tokens.push(tok); - prev = tok; - }; + if (n === 0) + return cb() - const extglobOpen = (type, value) => { - let token = { ...EXTGLOB_CHARS[value], conditions: 1, inner: '' }; + var set = this.matches[index] = Object.create(null) + found.forEach(function (p, i) { + // If there's a problem with the stat, then it means that + // one or more of the links in the realpath couldn't be + // resolved. just return the abs value in that case. + p = self._makeAbs(p) + rp.realpath(p, self.realpathCache, function (er, real) { + if (!er) + set[real] = true + else if (er.syscall === 'stat') + set[p] = true + else + self.emit('error', er) // srsly wtf right here - token.prev = prev; - token.parens = state.parens; - token.output = state.output; - let output = (opts.capture ? '(' : '') + token.open; + if (--n === 0) { + self.matches[index] = set + cb() + } + }) + }) +} - push({ type, value, output: state.output ? '' : ONE_CHAR }); - push({ type: 'paren', extglob: true, value: advance(), output }); - increment('parens'); - extglobs.push(token); - }; +Glob.prototype._mark = function (p) { + return common.mark(this, p) +} - const extglobClose = token => { - let output = token.close + (opts.capture ? ')' : ''); +Glob.prototype._makeAbs = function (f) { + return common.makeAbs(this, f) +} - if (token.type === 'negate') { - let extglobStar = star; +Glob.prototype.abort = function () { + this.aborted = true + this.emit('abort') +} - if (token.inner && token.inner.length > 1 && token.inner.includes('/')) { - extglobStar = globstar(opts); - } +Glob.prototype.pause = function () { + if (!this.paused) { + this.paused = true + this.emit('pause') + } +} - if (extglobStar !== star || eos() || /^\)+$/.test(input.slice(state.index + 1))) { - output = token.close = ')$))' + extglobStar; +Glob.prototype.resume = function () { + if (this.paused) { + this.emit('resume') + this.paused = false + if (this._emitQueue.length) { + var eq = this._emitQueue.slice(0) + this._emitQueue.length = 0 + for (var i = 0; i < eq.length; i ++) { + var e = eq[i] + this._emitMatch(e[0], e[1]) } - - if (token.prev.type === 'bos' && eos()) { - state.negatedExtglob = true; + } + if (this._processQueue.length) { + var pq = this._processQueue.slice(0) + this._processQueue.length = 0 + for (var i = 0; i < pq.length; i ++) { + var p = pq[i] + this._processing-- + this._process(p[0], p[1], p[2], p[3]) } } + } +} - push({ type: 'paren', extglob: true, value, output }); - decrement('parens'); - }; +Glob.prototype._process = function (pattern, index, inGlobStar, cb) { + assert(this instanceof Glob) + assert(typeof cb === 'function') - if (opts.fastpaths !== false && !/(^[*!]|[/{[()\]}"])/.test(input)) { - let backslashes = false; + if (this.aborted) + return - let output = input.replace(REGEX_SPECIAL_CHARS_BACKREF, (m, esc, chars, first, rest, index) => { - if (first === '\\') { - backslashes = true; - return m; - } + this._processing++ + if (this.paused) { + this._processQueue.push([pattern, index, inGlobStar, cb]) + return + } - if (first === '?') { - if (esc) { - return esc + first + (rest ? QMARK.repeat(rest.length) : ''); - } - if (index === 0) { - return qmarkNoDot + (rest ? QMARK.repeat(rest.length) : ''); - } - return QMARK.repeat(chars.length); - } + //console.error('PROCESS %d', this._processing, pattern) - if (first === '.') { - return DOT_LITERAL.repeat(chars.length); - } + // Get the first [n] parts of pattern that are all strings. + var n = 0 + while (typeof pattern[n] === 'string') { + n ++ + } + // now n is the index of the first one that is *not* a string. - if (first === '*') { - if (esc) { - return esc + first + (rest ? star : ''); - } - return star; - } - return esc ? m : '\\' + m; - }); + // see if there's anything else + var prefix + switch (n) { + // if not, then this is rather simple + case pattern.length: + this._processSimple(pattern.join('/'), index, cb) + return - if (backslashes === true) { - if (opts.unescape === true) { - output = output.replace(/\\/g, ''); - } else { - output = output.replace(/\\+/g, m => { - return m.length % 2 === 0 ? '\\\\' : (m ? '\\' : ''); - }); - } - } + case 0: + // pattern *starts* with some non-trivial item. + // going to readdir(cwd), but not include the prefix in matches. + prefix = null + break - state.output = output; - return state; + default: + // pattern has some string bits in the front. + // whatever it starts with, whether that's 'absolute' like /foo/bar, + // or 'relative' like '../baz' + prefix = pattern.slice(0, n).join('/') + break } - /** - * Tokenize input until we reach end-of-string - */ - - while (!eos()) { - value = advance(); + var remain = pattern.slice(n) - if (value === '\u0000') { - continue; - } + // get the list of entries. + var read + if (prefix === null) + read = '.' + else if (isAbsolute(prefix) || isAbsolute(pattern.join('/'))) { + if (!prefix || !isAbsolute(prefix)) + prefix = '/' + prefix + read = prefix + } else + read = prefix - /** - * Escaped characters - */ + var abs = this._makeAbs(read) - if (value === '\\') { - let next = peek(); + //if ignored, skip _processing + if (childrenIgnored(this, read)) + return cb() - if (next === '/' && opts.bash !== true) { - continue; - } + var isGlobStar = remain[0] === minimatch.GLOBSTAR + if (isGlobStar) + this._processGlobStar(prefix, read, abs, remain, index, inGlobStar, cb) + else + this._processReaddir(prefix, read, abs, remain, index, inGlobStar, cb) +} - if (next === '.' || next === ';') { - continue; - } +Glob.prototype._processReaddir = function (prefix, read, abs, remain, index, inGlobStar, cb) { + var self = this + this._readdir(abs, inGlobStar, function (er, entries) { + return self._processReaddir2(prefix, read, abs, remain, index, inGlobStar, entries, cb) + }) +} - if (!next) { - value += '\\'; - push({ type: 'text', value }); - continue; - } +Glob.prototype._processReaddir2 = function (prefix, read, abs, remain, index, inGlobStar, entries, cb) { - // collapse slashes to reduce potential for exploits - let match = /^\\+/.exec(input.slice(state.index + 1)); - let slashes = 0; + // if the abs isn't a dir, then nothing can match! + if (!entries) + return cb() - if (match && match[0].length > 2) { - slashes = match[0].length; - state.index += slashes; - if (slashes % 2 !== 0) { - value += '\\'; - } - } + // It will only match dot entries if it starts with a dot, or if + // dot is set. Stuff like @(.foo|.bar) isn't allowed. + var pn = remain[0] + var negate = !!this.minimatch.negate + var rawGlob = pn._glob + var dotOk = this.dot || rawGlob.charAt(0) === '.' - if (opts.unescape === true) { - value = advance() || ''; + var matchedEntries = [] + for (var i = 0; i < entries.length; i++) { + var e = entries[i] + if (e.charAt(0) !== '.' || dotOk) { + var m + if (negate && !prefix) { + m = !e.match(pn) } else { - value += advance() || ''; - } - - if (state.brackets === 0) { - push({ type: 'text', value }); - continue; + m = e.match(pn) } + if (m) + matchedEntries.push(e) } + } - /** - * If we're inside a regex character class, continue - * until we reach the closing bracket. - */ - - if (state.brackets > 0 && (value !== ']' || prev.value === '[' || prev.value === '[^')) { - if (opts.posix !== false && value === ':') { - let inner = prev.value.slice(1); - if (inner.includes('[')) { - prev.posix = true; + //console.error('prd2', prefix, entries, remain[0]._glob, matchedEntries) - if (inner.includes(':')) { - let idx = prev.value.lastIndexOf('['); - let pre = prev.value.slice(0, idx); - let rest = prev.value.slice(idx + 2); - let posix = POSIX_REGEX_SOURCE[rest]; - if (posix) { - prev.value = pre + posix; - state.backtrack = true; - advance(); + var len = matchedEntries.length + // If there are no matched entries, then nothing matches. + if (len === 0) + return cb() - if (!bos.output && tokens.indexOf(prev) === 1) { - bos.output = ONE_CHAR; - } - continue; - } - } - } - } + // if this is the last remaining pattern bit, then no need for + // an additional stat *unless* the user has specified mark or + // stat explicitly. We know they exist, since readdir returned + // them. - if ((value === '[' && peek() !== ':') || (value === '-' && peek() === ']')) { - value = '\\' + value; - } + if (remain.length === 1 && !this.mark && !this.stat) { + if (!this.matches[index]) + this.matches[index] = Object.create(null) - if (value === ']' && (prev.value === '[' || prev.value === '[^')) { - value = '\\' + value; + for (var i = 0; i < len; i ++) { + var e = matchedEntries[i] + if (prefix) { + if (prefix !== '/') + e = prefix + '/' + e + else + e = prefix + e } - if (opts.posix === true && value === '!' && prev.value === '[') { - value = '^'; + if (e.charAt(0) === '/' && !this.nomount) { + e = path.join(this.root, e) } - - prev.value += value; - append({ value }); - continue; - } - - /** - * If we're inside a quoted string, continue - * until we reach the closing double quote. - */ - - if (state.quotes === 1 && value !== '"') { - value = utils.escapeRegex(value); - prev.value += value; - append({ value }); - continue; + this._emitMatch(index, e) } + // This was the last one, and no stats were needed + return cb() + } - /** - * Double quotes - */ - - if (value === '"') { - state.quotes = state.quotes === 1 ? 0 : 1; - if (opts.keepQuotes === true) { - push({ type: 'text', value }); - } - continue; + // now test all matched entries as stand-ins for that part + // of the pattern. + remain.shift() + for (var i = 0; i < len; i ++) { + var e = matchedEntries[i] + var newPattern + if (prefix) { + if (prefix !== '/') + e = prefix + '/' + e + else + e = prefix + e } + this._process([e].concat(remain), index, inGlobStar, cb) + } + cb() +} - /** - * Parentheses - */ +Glob.prototype._emitMatch = function (index, e) { + if (this.aborted) + return - if (value === '(') { - push({ type: 'paren', value }); - increment('parens'); - continue; - } + if (isIgnored(this, e)) + return - if (value === ')') { - if (state.parens === 0 && opts.strictBrackets === true) { - throw new SyntaxError(syntaxError('opening', '(')); - } + if (this.paused) { + this._emitQueue.push([index, e]) + return + } - let extglob = extglobs[extglobs.length - 1]; - if (extglob && state.parens === extglob.parens + 1) { - extglobClose(extglobs.pop()); - continue; - } + var abs = isAbsolute(e) ? e : this._makeAbs(e) - push({ type: 'paren', value, output: state.parens ? ')' : '\\)' }); - decrement('parens'); - continue; - } + if (this.mark) + e = this._mark(e) - /** - * Brackets - */ + if (this.absolute) + e = abs - if (value === '[') { - if (opts.nobracket === true || !input.slice(state.index + 1).includes(']')) { - if (opts.nobracket !== true && opts.strictBrackets === true) { - throw new SyntaxError(syntaxError('closing', ']')); - } + if (this.matches[index][e]) + return - value = '\\' + value; - } else { - increment('brackets'); - } + if (this.nodir) { + var c = this.cache[abs] + if (c === 'DIR' || Array.isArray(c)) + return + } - push({ type: 'bracket', value }); - continue; - } + this.matches[index][e] = true - if (value === ']') { - if (opts.nobracket === true || (prev && prev.type === 'bracket' && prev.value.length === 1)) { - push({ type: 'text', value, output: '\\' + value }); - continue; - } + var st = this.statCache[abs] + if (st) + this.emit('stat', e, st) - if (state.brackets === 0) { - if (opts.strictBrackets === true) { - throw new SyntaxError(syntaxError('opening', '[')); - } + this.emit('match', e) +} - push({ type: 'text', value, output: '\\' + value }); - continue; - } +Glob.prototype._readdirInGlobStar = function (abs, cb) { + if (this.aborted) + return - decrement('brackets'); + // follow all symlinked directories forever + // just proceed as if this is a non-globstar situation + if (this.follow) + return this._readdir(abs, false, cb) - let prevValue = prev.value.slice(1); - if (prev.posix !== true && prevValue[0] === '^' && !prevValue.includes('/')) { - value = '/' + value; - } + var lstatkey = 'lstat\0' + abs + var self = this + var lstatcb = inflight(lstatkey, lstatcb_) - prev.value += value; - append({ value }); + if (lstatcb) + fs.lstat(abs, lstatcb) - // when literal brackets are explicitly disabled - // assume we should match with a regex character class - if (opts.literalBrackets === false || utils.hasRegexChars(prevValue)) { - continue; - } + function lstatcb_ (er, lstat) { + if (er && er.code === 'ENOENT') + return cb() - let escaped = utils.escapeRegex(prev.value); - state.output = state.output.slice(0, -prev.value.length); + var isSym = lstat && lstat.isSymbolicLink() + self.symlinks[abs] = isSym - // when literal brackets are explicitly enabled - // assume we should escape the brackets to match literal characters - if (opts.literalBrackets === true) { - state.output += escaped; - prev.value = escaped; - continue; - } + // If it's not a symlink or a dir, then it's definitely a regular file. + // don't bother doing a readdir in that case. + if (!isSym && lstat && !lstat.isDirectory()) { + self.cache[abs] = 'FILE' + cb() + } else + self._readdir(abs, false, cb) + } +} - // when the user specifies nothing, try to match both - prev.value = `(${capture}${escaped}|${prev.value})`; - state.output += prev.value; - continue; - } +Glob.prototype._readdir = function (abs, inGlobStar, cb) { + if (this.aborted) + return - /** - * Braces - */ + cb = inflight('readdir\0'+abs+'\0'+inGlobStar, cb) + if (!cb) + return - if (value === '{' && opts.nobrace !== true) { - push({ type: 'brace', value, output: '(' }); - increment('braces'); - continue; - } + //console.error('RD %j %j', +inGlobStar, abs) + if (inGlobStar && !ownProp(this.symlinks, abs)) + return this._readdirInGlobStar(abs, cb) - if (value === '}') { - if (opts.nobrace === true || state.braces === 0) { - push({ type: 'text', value, output: '\\' + value }); - continue; - } + if (ownProp(this.cache, abs)) { + var c = this.cache[abs] + if (!c || c === 'FILE') + return cb() - let output = ')'; + if (Array.isArray(c)) + return cb(null, c) + } - if (state.dots === true) { - let arr = tokens.slice(); - let range = []; + var self = this + fs.readdir(abs, readdirCb(this, abs, cb)) +} - for (let i = arr.length - 1; i >= 0; i--) { - tokens.pop(); - if (arr[i].type === 'brace') { - break; - } - if (arr[i].type !== 'dots') { - range.unshift(arr[i].value); - } - } +function readdirCb (self, abs, cb) { + return function (er, entries) { + if (er) + self._readdirError(abs, er, cb) + else + self._readdirEntries(abs, entries, cb) + } +} - output = expandRange(range, opts); - state.backtrack = true; - } +Glob.prototype._readdirEntries = function (abs, entries, cb) { + if (this.aborted) + return - push({ type: 'brace', value, output }); - decrement('braces'); - continue; + // if we haven't asked to stat everything, then just + // assume that everything in there exists, so we can avoid + // having to stat it a second time. + if (!this.mark && !this.stat) { + for (var i = 0; i < entries.length; i ++) { + var e = entries[i] + if (abs === '/') + e = abs + e + else + e = abs + '/' + e + this.cache[e] = true } + } - /** - * Pipes - */ + this.cache[abs] = entries + return cb(null, entries) +} - if (value === '|') { - if (extglobs.length > 0) { - extglobs[extglobs.length - 1].conditions++; - } - push({ type: 'text', value }); - continue; - } +Glob.prototype._readdirError = function (f, er, cb) { + if (this.aborted) + return - /** - * Commas - */ + // handle errors, and cache the information + switch (er.code) { + case 'ENOTSUP': // https://github.com/isaacs/node-glob/issues/205 + case 'ENOTDIR': // totally normal. means it *does* exist. + var abs = this._makeAbs(f) + this.cache[abs] = 'FILE' + if (abs === this.cwdAbs) { + var error = new Error(er.code + ' invalid cwd ' + this.cwd) + error.path = this.cwd + error.code = er.code + this.emit('error', error) + this.abort() + } + break - if (value === ',') { - let output = value; + case 'ENOENT': // not terribly unusual + case 'ELOOP': + case 'ENAMETOOLONG': + case 'UNKNOWN': + this.cache[this._makeAbs(f)] = false + break - if (state.braces > 0 && stack[stack.length - 1] === 'braces') { - output = '|'; + default: // some unusual error. Treat as failure. + this.cache[this._makeAbs(f)] = false + if (this.strict) { + this.emit('error', er) + // If the error is handled, then we abort + // if not, we threw out of here + this.abort() } + if (!this.silent) + console.error('glob error', er) + break + } - push({ type: 'comma', value, output }); - continue; - } + return cb() +} - /** - * Slashes - */ +Glob.prototype._processGlobStar = function (prefix, read, abs, remain, index, inGlobStar, cb) { + var self = this + this._readdir(abs, inGlobStar, function (er, entries) { + self._processGlobStar2(prefix, read, abs, remain, index, inGlobStar, entries, cb) + }) +} - if (value === '/') { - // if the beginning of the glob is "./", advance the start - // to the current index, and don't add the "./" characters - // to the state. This greatly simplifies lookbehinds when - // checking for BOS characters like "!" and "." (not "./") - if (prev.type === 'dot' && state.index === 1) { - state.start = state.index + 1; - state.consumed = ''; - state.output = ''; - tokens.pop(); - prev = bos; // reset "prev" to the first token - continue; - } - push({ type: 'slash', value, output: SLASH_LITERAL }); - continue; - } +Glob.prototype._processGlobStar2 = function (prefix, read, abs, remain, index, inGlobStar, entries, cb) { + //console.error('pgs2', prefix, remain[0], entries) - /** - * Dots - */ + // no entries means not a dir, so it can never have matches + // foo.txt/** doesn't match foo.txt + if (!entries) + return cb() - if (value === '.') { - if (state.braces > 0 && prev.type === 'dot') { - if (prev.value === '.') prev.output = DOT_LITERAL; - prev.type = 'dots'; - prev.output += value; - prev.value += value; - state.dots = true; - continue; - } + // test without the globstar, and with every child both below + // and replacing the globstar. + var remainWithoutGlobStar = remain.slice(1) + var gspref = prefix ? [ prefix ] : [] + var noGlobStar = gspref.concat(remainWithoutGlobStar) - push({ type: 'dot', value, output: DOT_LITERAL }); - continue; - } + // the noGlobStar pattern exits the inGlobStar state + this._process(noGlobStar, index, false, cb) - /** - * Question marks - */ + var isSym = this.symlinks[abs] + var len = entries.length - if (value === '?') { - if (prev && prev.type === 'paren') { - let next = peek(); - let output = value; + // If it's a symlink, and we're in a globstar, then stop + if (isSym && inGlobStar) + return cb() - if (next === '<' && !utils.supportsLookbehinds()) { - throw new Error('Node.js v10 or higher is required for regex lookbehinds'); - } + for (var i = 0; i < len; i++) { + var e = entries[i] + if (e.charAt(0) === '.' && !this.dot) + continue - if (prev.value === '(' && !/[!=<:]/.test(next) || (next === '<' && !/[!=]/.test(peek(2)))) { - output = '\\' + value; - } + // these two cases enter the inGlobStar state + var instead = gspref.concat(entries[i], remainWithoutGlobStar) + this._process(instead, index, true, cb) - push({ type: 'text', value, output }); - continue; - } + var below = gspref.concat(entries[i], remain) + this._process(below, index, true, cb) + } - if (opts.noextglob !== true && peek() === '(' && peek(2) !== '?') { - extglobOpen('qmark', value); - continue; - } + cb() +} - if (opts.dot !== true && (prev.type === 'slash' || prev.type === 'bos')) { - push({ type: 'qmark', value, output: QMARK_NO_DOT }); - continue; - } +Glob.prototype._processSimple = function (prefix, index, cb) { + // XXX review this. Shouldn't it be doing the mounting etc + // before doing stat? kinda weird? + var self = this + this._stat(prefix, function (er, exists) { + self._processSimple2(prefix, index, er, exists, cb) + }) +} +Glob.prototype._processSimple2 = function (prefix, index, er, exists, cb) { - push({ type: 'qmark', value, output: QMARK }); - continue; - } + //console.error('ps2', prefix, exists) - /** - * Exclamation - */ + if (!this.matches[index]) + this.matches[index] = Object.create(null) - if (value === '!') { - if (opts.noextglob !== true && peek() === '(') { - if (peek(2) !== '?' || !/[!=<:]/.test(peek(3))) { - extglobOpen('negate', value); - continue; - } - } + // If it doesn't exist, then just mark the lack of results + if (!exists) + return cb() - if (opts.nonegate !== true && state.index === 0) { - negate(state); - continue; - } + if (prefix && isAbsolute(prefix) && !this.nomount) { + var trail = /[\/\\]$/.test(prefix) + if (prefix.charAt(0) === '/') { + prefix = path.join(this.root, prefix) + } else { + prefix = path.resolve(this.root, prefix) + if (trail) + prefix += '/' } + } - /** - * Plus - */ + if (process.platform === 'win32') + prefix = prefix.replace(/\\/g, '/') - if (value === '+') { - if (opts.noextglob !== true && peek() === '(' && peek(2) !== '?') { - extglobOpen('plus', value); - continue; - } + // Mark this as a match + this._emitMatch(index, prefix) + cb() +} - if (prev && (prev.type === 'bracket' || prev.type === 'paren' || prev.type === 'brace')) { - let output = prev.extglob === true ? '\\' + value : value; - push({ type: 'plus', value, output }); - continue; - } +// Returns either 'DIR', 'FILE', or false +Glob.prototype._stat = function (f, cb) { + var abs = this._makeAbs(f) + var needDir = f.slice(-1) === '/' - // use regex behavior inside parens - if (state.parens > 0 && opts.regex !== false) { - push({ type: 'plus', value }); - continue; - } + if (f.length > this.maxLength) + return cb() - push({ type: 'plus', value: PLUS_LITERAL }); - continue; - } + if (!this.stat && ownProp(this.cache, abs)) { + var c = this.cache[abs] - /** - * Plain text - */ + if (Array.isArray(c)) + c = 'DIR' - if (value === '@') { - if (opts.noextglob !== true && peek() === '(' && peek(2) !== '?') { - push({ type: 'at', value, output: '' }); - continue; - } + // It exists, but maybe not how we need it + if (!needDir || c === 'DIR') + return cb(null, c) - push({ type: 'text', value }); - continue; - } + if (needDir && c === 'FILE') + return cb() - /** - * Plain text - */ + // otherwise we have to stat, because maybe c=true + // if we know it exists, but not what it is. + } - if (value !== '*') { - if (value === '$' || value === '^') { - value = '\\' + value; - } + var exists + var stat = this.statCache[abs] + if (stat !== undefined) { + if (stat === false) + return cb(null, stat) + else { + var type = stat.isDirectory() ? 'DIR' : 'FILE' + if (needDir && type === 'FILE') + return cb() + else + return cb(null, type, stat) + } + } - let match = REGEX_NON_SPECIAL_CHAR.exec(input.slice(state.index + 1)); - if (match) { - value += match[0]; - state.index += match[0].length; - } + var self = this + var statcb = inflight('stat\0' + abs, lstatcb_) + if (statcb) + fs.lstat(abs, statcb) - push({ type: 'text', value }); - continue; + function lstatcb_ (er, lstat) { + if (lstat && lstat.isSymbolicLink()) { + // If it's a symlink, then treat it as the target, unless + // the target does not exist, then treat it as a file. + return fs.stat(abs, function (er, stat) { + if (er) + self._stat2(f, abs, null, lstat, cb) + else + self._stat2(f, abs, er, stat, cb) + }) + } else { + self._stat2(f, abs, er, lstat, cb) } + } +} - /** - * Stars - */ +Glob.prototype._stat2 = function (f, abs, er, stat, cb) { + if (er && (er.code === 'ENOENT' || er.code === 'ENOTDIR')) { + this.statCache[abs] = false + return cb() + } - if (prev && (prev.type === 'globstar' || prev.star === true)) { - prev.type = 'star'; - prev.star = true; - prev.value += value; - prev.output = star; - state.backtrack = true; - state.consumed += value; - continue; - } + var needDir = f.slice(-1) === '/' + this.statCache[abs] = stat - if (opts.noextglob !== true && peek() === '(' && peek(2) !== '?') { - extglobOpen('star', value); - continue; - } + if (abs.slice(-1) === '/' && stat && !stat.isDirectory()) + return cb(null, false, stat) - if (prev.type === 'star') { - if (opts.noglobstar === true) { - state.consumed += value; - continue; - } + var c = true + if (stat) + c = stat.isDirectory() ? 'DIR' : 'FILE' + this.cache[abs] = this.cache[abs] || c - let prior = prev.prev; - let before = prior.prev; - let isStart = prior.type === 'slash' || prior.type === 'bos'; - let afterStar = before && (before.type === 'star' || before.type === 'globstar'); + if (needDir && c === 'FILE') + return cb() - if (opts.bash === true && (!isStart || (!eos() && peek() !== '/'))) { - push({ type: 'star', value, output: '' }); - continue; - } + return cb(null, c, stat) +} - let isBrace = state.braces > 0 && (prior.type === 'comma' || prior.type === 'brace'); - let isExtglob = extglobs.length && (prior.type === 'pipe' || prior.type === 'paren'); - if (!isStart && prior.type !== 'paren' && !isBrace && !isExtglob) { - push({ type: 'star', value, output: '' }); - continue; - } - // strip consecutive `/**/` - while (input.slice(state.index + 1, state.index + 4) === '/**') { - let after = input[state.index + 4]; - if (after && after !== '/') { - break; - } - state.consumed += '/**'; - state.index += 3; - } +/***/ }), +/* 76 */ +/***/ (function(module, exports, __webpack_require__) { - if (prior.type === 'bos' && eos()) { - prev.type = 'globstar'; - prev.value += value; - prev.output = globstar(opts); - state.output = prev.output; - state.consumed += value; - continue; - } +"use strict"; - if (prior.type === 'slash' && prior.prev.type !== 'bos' && !afterStar && eos()) { - state.output = state.output.slice(0, -(prior.output + prev.output).length); - prior.output = '(?:' + prior.output; - prev.type = 'globstar'; - prev.output = globstar(opts) + '|$)'; - prev.value += value; +function posix(path) { + return path.charAt(0) === '/'; +} - state.output += prior.output + prev.output; - state.consumed += value; - continue; - } +function win32(path) { + // https://github.com/nodejs/node/blob/b3fcc245fb25539909ef1d5eaa01dbf92e168633/lib/path.js#L56 + var splitDeviceRe = /^([a-zA-Z]:|[\\\/]{2}[^\\\/]+[\\\/]+[^\\\/]+)?([\\\/])?([\s\S]*?)$/; + var result = splitDeviceRe.exec(path); + var device = result[1] || ''; + var isUnc = Boolean(device && device.charAt(1) !== ':'); - let next = peek(); - if (prior.type === 'slash' && prior.prev.type !== 'bos' && next === '/') { - let end = peek(2) !== void 0 ? '|$' : ''; + // UNC paths are always absolute + return Boolean(result[2] || isUnc); +} - state.output = state.output.slice(0, -(prior.output + prev.output).length); - prior.output = '(?:' + prior.output; +module.exports = process.platform === 'win32' ? win32 : posix; +module.exports.posix = posix; +module.exports.win32 = win32; - prev.type = 'globstar'; - prev.output = `${globstar(opts)}${SLASH_LITERAL}|${SLASH_LITERAL}${end})`; - prev.value += value; - state.output += prior.output + prev.output; - state.consumed += value + advance(); +/***/ }), +/* 77 */, +/* 78 */, +/* 79 */ +/***/ (function(module, exports) { - push({ type: 'slash', value, output: '' }); - continue; - } +module.exports = __webpack_require__(480); - if (prior.type === 'bos' && next === '/') { - prev.type = 'globstar'; - prev.value += value; - prev.output = `(?:^|${SLASH_LITERAL}|${globstar(opts)}${SLASH_LITERAL})`; - state.output = prev.output; - state.consumed += value + advance(); - push({ type: 'slash', value, output: '' }); - continue; - } +/***/ }), +/* 80 */, +/* 81 */ +/***/ (function(module, exports, __webpack_require__) { - // remove single star from output - state.output = state.output.slice(0, -prev.output.length); +"use strict"; - // reset previous token to globstar - prev.type = 'globstar'; - prev.output = globstar(opts); - prev.value += value; - // reset output with globstar - state.output += prev.output; - state.consumed += value; - continue; - } +Object.defineProperty(exports, "__esModule", { + value: true +}); - let token = { type: 'star', value, output: star }; +exports.default = function (str, fileLoc = 'lockfile') { + str = (0, (_stripBom || _load_stripBom()).default)(str); + return hasMergeConflicts(str) ? parseWithConflict(str, fileLoc) : { type: 'success', object: parse(str, fileLoc) }; +}; - if (opts.bash === true) { - token.output = '.*?'; - if (prev.type === 'bos' || prev.type === 'slash') { - token.output = nodot + token.output; - } - push(token); - continue; - } +var _util; - if (prev && (prev.type === 'bracket' || prev.type === 'paren') && opts.regex === true) { - token.output = value; - push(token); - continue; - } +function _load_util() { + return _util = _interopRequireDefault(__webpack_require__(2)); +} - if (state.index === state.start || prev.type === 'slash' || prev.type === 'dot') { - if (prev.type === 'dot') { - state.output += NO_DOT_SLASH; - prev.output += NO_DOT_SLASH; +var _invariant; - } else if (opts.dot === true) { - state.output += NO_DOTS_SLASH; - prev.output += NO_DOTS_SLASH; +function _load_invariant() { + return _invariant = _interopRequireDefault(__webpack_require__(7)); +} - } else { - state.output += nodot; - prev.output += nodot; - } +var _stripBom; - if (peek() !== '*') { - state.output += ONE_CHAR; - prev.output += ONE_CHAR; - } - } +function _load_stripBom() { + return _stripBom = _interopRequireDefault(__webpack_require__(122)); +} - push(token); - } +var _constants; - while (state.brackets > 0) { - if (opts.strictBrackets === true) throw new SyntaxError(syntaxError('closing', ']')); - state.output = utils.escapeLast(state.output, '['); - decrement('brackets'); - } +function _load_constants() { + return _constants = __webpack_require__(6); +} - while (state.parens > 0) { - if (opts.strictBrackets === true) throw new SyntaxError(syntaxError('closing', ')')); - state.output = utils.escapeLast(state.output, '('); - decrement('parens'); - } +var _errors; - while (state.braces > 0) { - if (opts.strictBrackets === true) throw new SyntaxError(syntaxError('closing', '}')); - state.output = utils.escapeLast(state.output, '{'); - decrement('braces'); - } +function _load_errors() { + return _errors = __webpack_require__(4); +} - if (opts.strictSlashes !== true && (prev.type === 'star' || prev.type === 'bracket')) { - push({ type: 'maybe_slash', value: '', output: `${SLASH_LITERAL}?` }); - } +var _map; - // rebuild the output if we had to backtrack at any point - if (state.backtrack === true) { - state.output = ''; +function _load_map() { + return _map = _interopRequireDefault(__webpack_require__(20)); +} - for (let token of state.tokens) { - state.output += token.output != null ? token.output : token.value; +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - if (token.suffix) { - state.output += token.suffix; - } - } - } +/* eslint quotes: 0 */ - return state; +const VERSION_REGEX = /^yarn lockfile v(\d+)$/; + +const TOKEN_TYPES = { + boolean: 'BOOLEAN', + string: 'STRING', + identifier: 'IDENTIFIER', + eof: 'EOF', + colon: 'COLON', + newline: 'NEWLINE', + comment: 'COMMENT', + indent: 'INDENT', + invalid: 'INVALID', + number: 'NUMBER', + comma: 'COMMA' }; -/** - * Fast paths for creating regular expressions for common glob patterns. - * This can significantly speed up processing and has very little downside - * impact when none of the fast paths match. - */ +const VALID_PROP_VALUE_TOKENS = [TOKEN_TYPES.boolean, TOKEN_TYPES.string, TOKEN_TYPES.number]; -parse.fastpaths = (input, options) => { - let opts = { ...options }; - let max = typeof opts.maxLength === 'number' ? Math.min(MAX_LENGTH, opts.maxLength) : MAX_LENGTH; - let len = input.length; - if (len > max) { - throw new SyntaxError(`Input length: ${len}, exceeds maximum allowed length: ${max}`); +function isValidPropValueToken(token) { + return VALID_PROP_VALUE_TOKENS.indexOf(token.type) >= 0; +} + +function* tokenise(input) { + let lastNewline = false; + let line = 1; + let col = 0; + + function buildToken(type, value) { + return { line, col, type, value }; } - input = REPLACEMENTS[input] || input; - let win32 = utils.isWindows(options); + while (input.length) { + let chop = 0; - // create constants based on platform, for windows or posix - const { - DOT_LITERAL, - SLASH_LITERAL, - ONE_CHAR, - DOTS_SLASH, - NO_DOT, - NO_DOTS, - NO_DOTS_SLASH, - STAR, - START_ANCHOR - } = constants.globChars(win32); + if (input[0] === '\n' || input[0] === '\r') { + chop++; + // If this is a \r\n line, ignore both chars but only add one new line + if (input[1] === '\n') { + chop++; + } + line++; + col = 0; + yield buildToken(TOKEN_TYPES.newline); + } else if (input[0] === '#') { + chop++; - let capture = opts.capture ? '' : '?:'; - let star = opts.bash === true ? '.*?' : STAR; - let nodot = opts.dot ? NO_DOTS : NO_DOT; - let slashDot = opts.dot ? NO_DOTS_SLASH : NO_DOT; + let val = ''; + while (input[chop] !== '\n') { + val += input[chop]; + chop++; + } + yield buildToken(TOKEN_TYPES.comment, val); + } else if (input[0] === ' ') { + if (lastNewline) { + let indent = ''; + for (let i = 0; input[i] === ' '; i++) { + indent += input[i]; + } - if (opts.capture) { - star = `(${star})`; - } + if (indent.length % 2) { + throw new TypeError('Invalid number of spaces'); + } else { + chop = indent.length; + yield buildToken(TOKEN_TYPES.indent, indent.length / 2); + } + } else { + chop++; + } + } else if (input[0] === '"') { + let val = ''; - const globstar = (opts) => { - return `(${capture}(?:(?!${START_ANCHOR}${opts.dot ? DOTS_SLASH : DOT_LITERAL}).)*?)`; - }; + for (let i = 0;; i++) { + const currentChar = input[i]; + val += currentChar; - const create = str => { - switch (str) { - case '*': - return `${nodot}${ONE_CHAR}${star}`; + if (i > 0 && currentChar === '"') { + const isEscaped = input[i - 1] === '\\' && input[i - 2] !== '\\'; + if (!isEscaped) { + break; + } + } + } - case '.*': - return `${DOT_LITERAL}${ONE_CHAR}${star}`; + chop = val.length; - case '*.*': - return `${nodot}${star}${DOT_LITERAL}${ONE_CHAR}${star}`; + try { + yield buildToken(TOKEN_TYPES.string, JSON.parse(val)); + } catch (err) { + if (err instanceof SyntaxError) { + yield buildToken(TOKEN_TYPES.invalid); + } else { + throw err; + } + } + } else if (/^[0-9]/.test(input)) { + let val = ''; + for (let i = 0; /^[0-9]$/.test(input[i]); i++) { + val += input[i]; + } + chop = val.length; + + yield buildToken(TOKEN_TYPES.number, +val); + } else if (/^true/.test(input)) { + yield buildToken(TOKEN_TYPES.boolean, true); + chop = 4; + } else if (/^false/.test(input)) { + yield buildToken(TOKEN_TYPES.boolean, false); + chop = 5; + } else if (input[0] === ':') { + yield buildToken(TOKEN_TYPES.colon); + chop++; + } else if (input[0] === ',') { + yield buildToken(TOKEN_TYPES.comma); + chop++; + } else if (/^[a-zA-Z\/-]/g.test(input)) { + let name = ''; + for (let i = 0; i < input.length; i++) { + const char = input[i]; + if (char === ':' || char === ' ' || char === '\n' || char === '\r' || char === ',') { + break; + } else { + name += char; + } + } + chop = name.length; - case '*/*': - return `${nodot}${star}${SLASH_LITERAL}${ONE_CHAR}${slashDot}${star}`; + yield buildToken(TOKEN_TYPES.string, name); + } else { + yield buildToken(TOKEN_TYPES.invalid); + } - case '**': - return nodot + globstar(opts); + if (!chop) { + // will trigger infinite recursion + yield buildToken(TOKEN_TYPES.invalid); + } - case '**/*': - return `(?:${nodot}${globstar(opts)}${SLASH_LITERAL})?${slashDot}${ONE_CHAR}${star}`; + col += chop; + lastNewline = input[0] === '\n' || input[0] === '\r' && input[1] === '\n'; + input = input.slice(chop); + } - case '**/*.*': - return `(?:${nodot}${globstar(opts)}${SLASH_LITERAL})?${slashDot}${star}${DOT_LITERAL}${ONE_CHAR}${star}`; + yield buildToken(TOKEN_TYPES.eof); +} - case '**/.*': - return `(?:${nodot}${globstar(opts)}${SLASH_LITERAL})?${DOT_LITERAL}${ONE_CHAR}${star}`; +class Parser { + constructor(input, fileLoc = 'lockfile') { + this.comments = []; + this.tokens = tokenise(input); + this.fileLoc = fileLoc; + } - default: { - let match = /^(.*?)\.(\w+)$/.exec(str); - if (!match) return; + onComment(token) { + const value = token.value; + (0, (_invariant || _load_invariant()).default)(typeof value === 'string', 'expected token value to be a string'); - let source = create(match[1], options); - if (!source) return; + const comment = value.trim(); - return source + DOT_LITERAL + match[2]; + const versionMatch = comment.match(VERSION_REGEX); + if (versionMatch) { + const version = +versionMatch[1]; + if (version > (_constants || _load_constants()).LOCKFILE_VERSION) { + throw new (_errors || _load_errors()).MessageError(`Can't install from a lockfile of version ${version} as you're on an old yarn version that only supports ` + `versions up to ${(_constants || _load_constants()).LOCKFILE_VERSION}. Run \`$ yarn self-update\` to upgrade to the latest version.`); } } - }; - let output = create(input); - if (output && opts.strictSlashes !== true) { - output += `${SLASH_LITERAL}?`; + this.comments.push(comment); } - return output; -}; - -module.exports = parse; - + next() { + const item = this.tokens.next(); + (0, (_invariant || _load_invariant()).default)(item, 'expected a token'); -/***/ }), -/* 206 */ -/***/ (function(module, exports, __webpack_require__) { + const done = item.done, + value = item.value; -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -const merge2 = __webpack_require__(177); -function merge(streams) { - const mergedStream = merge2(streams); - streams.forEach((stream) => { - stream.once('error', (err) => mergedStream.emit('error', err)); - }); - return mergedStream; -} -exports.merge = merge; + if (done || !value) { + throw new Error('No more tokens'); + } else if (value.type === TOKEN_TYPES.comment) { + this.onComment(value); + return this.next(); + } else { + return this.token = value; + } + } + unexpected(msg = 'Unexpected token') { + throw new SyntaxError(`${msg} ${this.token.line}:${this.token.col} in ${this.fileLoc}`); + } -/***/ }), -/* 207 */ -/***/ (function(module, exports, __webpack_require__) { + expect(tokType) { + if (this.token.type === tokType) { + this.next(); + } else { + this.unexpected(); + } + } -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -const stream_1 = __webpack_require__(208); -const provider_1 = __webpack_require__(235); -class ProviderAsync extends provider_1.default { - constructor() { - super(...arguments); - this._reader = new stream_1.default(this._settings); - } - read(task) { - const root = this._getRootDirectory(task); - const options = this._getReaderOptions(task); - const entries = []; - return new Promise((resolve, reject) => { - const stream = this.api(root, task, options); - stream.once('error', reject); - stream.on('data', (entry) => entries.push(options.transform(entry))); - stream.once('end', () => resolve(entries)); - }); - } - api(root, task, options) { - if (task.dynamic) { - return this._reader.dynamic(root, options); - } - return this._reader.static(task.patterns, options); - } -} -exports.default = ProviderAsync; + eat(tokType) { + if (this.token.type === tokType) { + this.next(); + return true; + } else { + return false; + } + } + parse(indent = 0) { + const obj = (0, (_map || _load_map()).default)(); -/***/ }), -/* 208 */ -/***/ (function(module, exports, __webpack_require__) { + while (true) { + const propToken = this.token; -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -const stream_1 = __webpack_require__(28); -const fsStat = __webpack_require__(209); -const fsWalk = __webpack_require__(214); -const reader_1 = __webpack_require__(234); -class ReaderStream extends reader_1.default { - constructor() { - super(...arguments); - this._walkStream = fsWalk.walkStream; - this._stat = fsStat.stat; - } - dynamic(root, options) { - return this._walkStream(root, options); - } - static(patterns, options) { - const filepaths = patterns.map(this._getFullEntryPath, this); - const stream = new stream_1.PassThrough({ objectMode: true }); - stream._write = (index, _enc, done) => { - return this._getEntry(filepaths[index], patterns[index], options) - .then((entry) => { - if (entry !== null && options.entryFilter(entry)) { - stream.push(entry); - } - if (index === filepaths.length - 1) { - stream.end(); - } - done(); - }) - .catch(done); - }; - for (let i = 0; i < filepaths.length; i++) { - stream.write(i); - } - return stream; - } - _getEntry(filepath, pattern, options) { - return this._getStat(filepath) - .then((stats) => this._makeEntry(stats, pattern)) - .catch((error) => { - if (options.errorFilter(error)) { - return null; - } - throw error; - }); - } - _getStat(filepath) { - return new Promise((resolve, reject) => { - this._stat(filepath, this._fsStatSettings, (error, stats) => { - error ? reject(error) : resolve(stats); - }); - }); - } -} -exports.default = ReaderStream; + if (propToken.type === TOKEN_TYPES.newline) { + const nextToken = this.next(); + if (!indent) { + // if we have 0 indentation then the next token doesn't matter + continue; + } + if (nextToken.type !== TOKEN_TYPES.indent) { + // if we have no indentation after a newline then we've gone down a level + break; + } -/***/ }), -/* 209 */ -/***/ (function(module, exports, __webpack_require__) { + if (nextToken.value === indent) { + // all is good, the indent is on our level + this.next(); + } else { + // the indentation is less than our level + break; + } + } else if (propToken.type === TOKEN_TYPES.indent) { + if (propToken.value === indent) { + this.next(); + } else { + break; + } + } else if (propToken.type === TOKEN_TYPES.eof) { + break; + } else if (propToken.type === TOKEN_TYPES.string) { + // property key + const key = propToken.value; + (0, (_invariant || _load_invariant()).default)(key, 'Expected a key'); -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -const async = __webpack_require__(210); -const sync = __webpack_require__(211); -const settings_1 = __webpack_require__(212); -exports.Settings = settings_1.default; -function stat(path, optionsOrSettingsOrCallback, callback) { - if (typeof optionsOrSettingsOrCallback === 'function') { - return async.read(path, getSettings(), optionsOrSettingsOrCallback); - } - async.read(path, getSettings(optionsOrSettingsOrCallback), callback); -} -exports.stat = stat; -function statSync(path, optionsOrSettings) { - const settings = getSettings(optionsOrSettings); - return sync.read(path, settings); -} -exports.statSync = statSync; -function getSettings(settingsOrOptions = {}) { - if (settingsOrOptions instanceof settings_1.default) { - return settingsOrOptions; - } - return new settings_1.default(settingsOrOptions); -} + const keys = [key]; + this.next(); + // support multiple keys + while (this.token.type === TOKEN_TYPES.comma) { + this.next(); // skip comma -/***/ }), -/* 210 */ -/***/ (function(module, exports, __webpack_require__) { + const keyToken = this.token; + if (keyToken.type !== TOKEN_TYPES.string) { + this.unexpected('Expected string'); + } -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -function read(path, settings, callback) { - settings.fs.lstat(path, (lstatError, lstat) => { - if (lstatError) { - return callFailureCallback(callback, lstatError); - } - if (!lstat.isSymbolicLink() || !settings.followSymbolicLink) { - return callSuccessCallback(callback, lstat); - } - settings.fs.stat(path, (statError, stat) => { - if (statError) { - if (settings.throwErrorOnBrokenSymbolicLink) { - return callFailureCallback(callback, statError); - } - return callSuccessCallback(callback, lstat); - } - if (settings.markSymbolicLink) { - stat.isSymbolicLink = () => true; - } - callSuccessCallback(callback, stat); - }); - }); -} -exports.read = read; -function callFailureCallback(callback, error) { - callback(error); -} -function callSuccessCallback(callback, result) { - callback(null, result); -} + const key = keyToken.value; + (0, (_invariant || _load_invariant()).default)(key, 'Expected a key'); + keys.push(key); + this.next(); + } + const valToken = this.token; -/***/ }), -/* 211 */ -/***/ (function(module, exports, __webpack_require__) { + if (valToken.type === TOKEN_TYPES.colon) { + // object + this.next(); -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -function read(path, settings) { - const lstat = settings.fs.lstatSync(path); - if (!lstat.isSymbolicLink() || !settings.followSymbolicLink) { - return lstat; - } - try { - const stat = settings.fs.statSync(path); - if (settings.markSymbolicLink) { - stat.isSymbolicLink = () => true; - } - return stat; - } - catch (error) { - if (!settings.throwErrorOnBrokenSymbolicLink) { - return lstat; - } - throw error; - } -} -exports.read = read; + // parse object + const val = this.parse(indent + 1); + for (var _iterator = keys, _isArray = Array.isArray(_iterator), _i = 0, _iterator = _isArray ? _iterator : _iterator[Symbol.iterator]();;) { + var _ref; -/***/ }), -/* 212 */ -/***/ (function(module, exports, __webpack_require__) { + if (_isArray) { + if (_i >= _iterator.length) break; + _ref = _iterator[_i++]; + } else { + _i = _iterator.next(); + if (_i.done) break; + _ref = _i.value; + } -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -const fs = __webpack_require__(213); -class Settings { - constructor(_options = {}) { - this._options = _options; - this.followSymbolicLink = this._getValue(this._options.followSymbolicLink, true); - this.fs = fs.createFileSystemAdapter(this._options.fs); - this.markSymbolicLink = this._getValue(this._options.markSymbolicLink, false); - this.throwErrorOnBrokenSymbolicLink = this._getValue(this._options.throwErrorOnBrokenSymbolicLink, true); - } - _getValue(option, value) { - return option === undefined ? value : option; - } -} -exports.default = Settings; + const key = _ref; + obj[key] = val; + } -/***/ }), -/* 213 */ -/***/ (function(module, exports, __webpack_require__) { + if (indent && this.token.type !== TOKEN_TYPES.indent) { + break; + } + } else if (isValidPropValueToken(valToken)) { + // plain value + for (var _iterator2 = keys, _isArray2 = Array.isArray(_iterator2), _i2 = 0, _iterator2 = _isArray2 ? _iterator2 : _iterator2[Symbol.iterator]();;) { + var _ref2; + + if (_isArray2) { + if (_i2 >= _iterator2.length) break; + _ref2 = _iterator2[_i2++]; + } else { + _i2 = _iterator2.next(); + if (_i2.done) break; + _ref2 = _i2.value; + } -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -const fs = __webpack_require__(23); -exports.FILE_SYSTEM_ADAPTER = { - lstat: fs.lstat, - stat: fs.stat, - lstatSync: fs.lstatSync, - statSync: fs.statSync -}; -function createFileSystemAdapter(fsMethods) { - if (!fsMethods) { - return exports.FILE_SYSTEM_ADAPTER; - } - return Object.assign({}, exports.FILE_SYSTEM_ADAPTER, fsMethods); -} -exports.createFileSystemAdapter = createFileSystemAdapter; + const key = _ref2; + obj[key] = valToken.value; + } -/***/ }), -/* 214 */ -/***/ (function(module, exports, __webpack_require__) { + this.next(); + } else { + this.unexpected('Invalid value type'); + } + } else { + this.unexpected(`Unknown token: ${(_util || _load_util()).default.inspect(propToken)}`); + } + } -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -const async_1 = __webpack_require__(215); -const stream_1 = __webpack_require__(230); -const sync_1 = __webpack_require__(231); -const settings_1 = __webpack_require__(233); -exports.Settings = settings_1.default; -function walk(dir, optionsOrSettingsOrCallback, callback) { - if (typeof optionsOrSettingsOrCallback === 'function') { - return new async_1.default(dir, getSettings()).read(optionsOrSettingsOrCallback); - } - new async_1.default(dir, getSettings(optionsOrSettingsOrCallback)).read(callback); -} -exports.walk = walk; -function walkSync(dir, optionsOrSettings) { - const settings = getSettings(optionsOrSettings); - const provider = new sync_1.default(dir, settings); - return provider.read(); -} -exports.walkSync = walkSync; -function walkStream(dir, optionsOrSettings) { - const settings = getSettings(optionsOrSettings); - const provider = new stream_1.default(dir, settings); - return provider.read(); -} -exports.walkStream = walkStream; -function getSettings(settingsOrOptions = {}) { - if (settingsOrOptions instanceof settings_1.default) { - return settingsOrOptions; - } - return new settings_1.default(settingsOrOptions); -} + return obj; + } +} +const MERGE_CONFLICT_ANCESTOR = '|||||||'; +const MERGE_CONFLICT_END = '>>>>>>>'; +const MERGE_CONFLICT_SEP = '======='; +const MERGE_CONFLICT_START = '<<<<<<<'; -/***/ }), -/* 215 */ -/***/ (function(module, exports, __webpack_require__) { +/** + * Extract the two versions of the lockfile from a merge conflict. + */ +function extractConflictVariants(str) { + const variants = [[], []]; + const lines = str.split(/\r?\n/g); + let skip = false; -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -const async_1 = __webpack_require__(216); -class AsyncProvider { - constructor(_root, _settings) { - this._root = _root; - this._settings = _settings; - this._reader = new async_1.default(this._root, this._settings); - this._storage = new Set(); - } - read(callback) { - this._reader.onError((error) => { - callFailureCallback(callback, error); - }); - this._reader.onEntry((entry) => { - this._storage.add(entry); - }); - this._reader.onEnd(() => { - callSuccessCallback(callback, Array.from(this._storage)); - }); - this._reader.read(); - } -} -exports.default = AsyncProvider; -function callFailureCallback(callback, error) { - callback(error); -} -function callSuccessCallback(callback, entries) { - callback(null, entries); -} + while (lines.length) { + const line = lines.shift(); + if (line.startsWith(MERGE_CONFLICT_START)) { + // get the first variant + while (lines.length) { + const conflictLine = lines.shift(); + if (conflictLine === MERGE_CONFLICT_SEP) { + skip = false; + break; + } else if (skip || conflictLine.startsWith(MERGE_CONFLICT_ANCESTOR)) { + skip = true; + continue; + } else { + variants[0].push(conflictLine); + } + } + // get the second variant + while (lines.length) { + const conflictLine = lines.shift(); + if (conflictLine.startsWith(MERGE_CONFLICT_END)) { + break; + } else { + variants[1].push(conflictLine); + } + } + } else { + variants[0].push(line); + variants[1].push(line); + } + } -/***/ }), -/* 216 */ -/***/ (function(module, exports, __webpack_require__) { + return [variants[0].join('\n'), variants[1].join('\n')]; +} -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -const events_1 = __webpack_require__(46); -const fsScandir = __webpack_require__(217); -const fastq = __webpack_require__(226); -const common = __webpack_require__(228); -const reader_1 = __webpack_require__(229); -class AsyncReader extends reader_1.default { - constructor(_root, _settings) { - super(_root, _settings); - this._settings = _settings; - this._scandir = fsScandir.scandir; - this._emitter = new events_1.EventEmitter(); - this._queue = fastq(this._worker.bind(this), this._settings.concurrency); - this._isFatalError = false; - this._isDestroyed = false; - this._queue.drain = () => { - if (!this._isFatalError) { - this._emitter.emit('end'); - } - }; - } - read() { - this._isFatalError = false; - this._isDestroyed = false; - setImmediate(() => { - this._pushToQueue(this._root, this._settings.basePath); - }); - return this._emitter; - } - destroy() { - if (this._isDestroyed) { - throw new Error('The reader is already destroyed'); - } - this._isDestroyed = true; - this._queue.killAndDrain(); - } - onEntry(callback) { - this._emitter.on('entry', callback); - } - onError(callback) { - this._emitter.once('error', callback); - } - onEnd(callback) { - this._emitter.once('end', callback); - } - _pushToQueue(dir, base) { - const queueItem = { dir, base }; - this._queue.push(queueItem, (error) => { - if (error) { - this._handleError(error); - } - }); - } - _worker(item, done) { - this._scandir(item.dir, this._settings.fsScandirSettings, (error, entries) => { - if (error) { - return done(error, undefined); - } - for (const entry of entries) { - this._handleEntry(entry, item.base); - } - done(null, undefined); - }); - } - _handleError(error) { - if (!common.isFatalError(this._settings, error)) { - return; - } - this._isFatalError = true; - this._isDestroyed = true; - this._emitter.emit('error', error); - } - _handleEntry(entry, base) { - if (this._isDestroyed || this._isFatalError) { - return; - } - const fullpath = entry.path; - if (base !== undefined) { - entry.path = common.joinPathSegments(base, entry.name, this._settings.pathSegmentSeparator); - } - if (common.isAppliedFilter(this._settings.entryFilter, entry)) { - this._emitEntry(entry); - } - if (entry.dirent.isDirectory() && common.isAppliedFilter(this._settings.deepFilter, entry)) { - this._pushToQueue(fullpath, entry.path); - } - } - _emitEntry(entry) { - this._emitter.emit('entry', entry); - } -} -exports.default = AsyncReader; +/** + * Check if a lockfile has merge conflicts. + */ +function hasMergeConflicts(str) { + return str.includes(MERGE_CONFLICT_START) && str.includes(MERGE_CONFLICT_SEP) && str.includes(MERGE_CONFLICT_END); +} + +/** + * Parse the lockfile. + */ +function parse(str, fileLoc) { + const parser = new Parser(str, fileLoc); + parser.next(); + return parser.parse(); +} +/** + * Parse and merge the two variants in a conflicted lockfile. + */ +function parseWithConflict(str, fileLoc) { + const variants = extractConflictVariants(str); + try { + return { type: 'merge', object: Object.assign({}, parse(variants[0], fileLoc), parse(variants[1], fileLoc)) }; + } catch (err) { + if (err instanceof SyntaxError) { + return { type: 'conflict', object: {} }; + } else { + throw err; + } + } +} /***/ }), -/* 217 */ +/* 82 */, +/* 83 */, +/* 84 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -const async = __webpack_require__(218); -const sync = __webpack_require__(223); -const settings_1 = __webpack_require__(224); -exports.Settings = settings_1.default; -function scandir(path, optionsOrSettingsOrCallback, callback) { - if (typeof optionsOrSettingsOrCallback === 'function') { - return async.read(path, getSettings(), optionsOrSettingsOrCallback); - } - async.read(path, getSettings(optionsOrSettingsOrCallback), callback); -} -exports.scandir = scandir; -function scandirSync(path, optionsOrSettings) { - const settings = getSettings(optionsOrSettings); - return sync.read(path, settings); -} -exports.scandirSync = scandirSync; -function getSettings(settingsOrOptions = {}) { - if (settingsOrOptions instanceof settings_1.default) { - return settingsOrOptions; - } - return new settings_1.default(settingsOrOptions); -} -/***/ }), -/* 218 */ -/***/ (function(module, exports, __webpack_require__) { +Object.defineProperty(exports, "__esModule", { + value: true +}); -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -const fsStat = __webpack_require__(209); -const rpl = __webpack_require__(219); -const constants_1 = __webpack_require__(220); -const utils = __webpack_require__(221); -function read(dir, settings, callback) { - if (!settings.stats && constants_1.IS_SUPPORT_READDIR_WITH_FILE_TYPES) { - return readdirWithFileTypes(dir, settings, callback); - } - return readdir(dir, settings, callback); -} -exports.read = read; -function readdirWithFileTypes(dir, settings, callback) { - settings.fs.readdir(dir, { withFileTypes: true }, (readdirError, dirents) => { - if (readdirError) { - return callFailureCallback(callback, readdirError); - } - const entries = dirents.map((dirent) => ({ - dirent, - name: dirent.name, - path: `${dir}${settings.pathSegmentSeparator}${dirent.name}` - })); - if (!settings.followSymbolicLinks) { - return callSuccessCallback(callback, entries); - } - const tasks = entries.map((entry) => makeRplTaskEntry(entry, settings)); - rpl(tasks, (rplError, rplEntries) => { - if (rplError) { - return callFailureCallback(callback, rplError); - } - callSuccessCallback(callback, rplEntries); - }); - }); -} -exports.readdirWithFileTypes = readdirWithFileTypes; -function makeRplTaskEntry(entry, settings) { - return (done) => { - if (!entry.dirent.isSymbolicLink()) { - return done(null, entry); - } - settings.fs.stat(entry.path, (statError, stats) => { - if (statError) { - if (settings.throwErrorOnBrokenSymbolicLink) { - return done(statError); - } - return done(null, entry); - } - entry.dirent = utils.fs.createDirentFromStats(entry.name, stats); - return done(null, entry); - }); - }; -} -function readdir(dir, settings, callback) { - settings.fs.readdir(dir, (readdirError, names) => { - if (readdirError) { - return callFailureCallback(callback, readdirError); - } - const filepaths = names.map((name) => `${dir}${settings.pathSegmentSeparator}${name}`); - const tasks = filepaths.map((filepath) => { - return (done) => fsStat.stat(filepath, settings.fsStatSettings, done); - }); - rpl(tasks, (rplError, results) => { - if (rplError) { - return callFailureCallback(callback, rplError); - } - const entries = []; - for (let index = 0; index < names.length; index++) { - const name = names[index]; - const stats = results[index]; - const entry = { - name, - path: filepaths[index], - dirent: utils.fs.createDirentFromStats(name, stats) - }; - if (settings.stats) { - entry.stats = stats; - } - entries.push(entry); - } - callSuccessCallback(callback, entries); - }); - }); -} -exports.readdir = readdir; -function callFailureCallback(callback, error) { - callback(error); -} -function callSuccessCallback(callback, result) { - callback(null, result); -} +var _map; + +function _load_map() { + return _map = _interopRequireDefault(__webpack_require__(20)); +} +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -/***/ }), -/* 219 */ -/***/ (function(module, exports) { +const debug = __webpack_require__(212)('yarn'); -module.exports = runParallel +class BlockingQueue { + constructor(alias, maxConcurrency = Infinity) { + this.concurrencyQueue = []; + this.maxConcurrency = maxConcurrency; + this.runningCount = 0; + this.warnedStuck = false; + this.alias = alias; + this.first = true; -function runParallel (tasks, cb) { - var results, pending, keys - var isSync = true + this.running = (0, (_map || _load_map()).default)(); + this.queue = (0, (_map || _load_map()).default)(); - if (Array.isArray(tasks)) { - results = [] - pending = tasks.length - } else { - keys = Object.keys(tasks) - results = {} - pending = keys.length + this.stuckTick = this.stuckTick.bind(this); } - function done (err) { - function end () { - if (cb) cb(err, results) - cb = null + stillActive() { + if (this.stuckTimer) { + clearTimeout(this.stuckTimer); } - if (isSync) process.nextTick(end) - else end() + + this.stuckTimer = setTimeout(this.stuckTick, 5000); + + // We need to check the existence of unref because of https://github.com/facebook/jest/issues/4559 + // $FlowFixMe: Node's setInterval returns a Timeout, not a Number + this.stuckTimer.unref && this.stuckTimer.unref(); } - function each (i, err, result) { - results[i] = result - if (--pending === 0 || err) { - done(err) + stuckTick() { + if (this.runningCount === 1) { + this.warnedStuck = true; + debug(`The ${JSON.stringify(this.alias)} blocking queue may be stuck. 5 seconds ` + `without any activity with 1 worker: ${Object.keys(this.running)[0]}`); } } - if (!pending) { - // empty - done(null) - } else if (keys) { - // object - keys.forEach(function (key) { - tasks[key](function (err, result) { each(key, err, result) }) - }) - } else { - // array - tasks.forEach(function (task, i) { - task(function (err, result) { each(i, err, result) }) - }) + push(key, factory) { + if (this.first) { + this.first = false; + } else { + this.stillActive(); + } + + return new Promise((resolve, reject) => { + // we're already running so push ourselves to the queue + const queue = this.queue[key] = this.queue[key] || []; + queue.push({ factory, resolve, reject }); + + if (!this.running[key]) { + this.shift(key); + } + }); } - isSync = false + shift(key) { + if (this.running[key]) { + delete this.running[key]; + this.runningCount--; + + if (this.stuckTimer) { + clearTimeout(this.stuckTimer); + this.stuckTimer = null; + } + + if (this.warnedStuck) { + this.warnedStuck = false; + debug(`${JSON.stringify(this.alias)} blocking queue finally resolved. Nothing to worry about.`); + } + } + + const queue = this.queue[key]; + if (!queue) { + return; + } + + var _queue$shift = queue.shift(); + + const resolve = _queue$shift.resolve, + reject = _queue$shift.reject, + factory = _queue$shift.factory; + + if (!queue.length) { + delete this.queue[key]; + } + + const next = () => { + this.shift(key); + this.shiftConcurrencyQueue(); + }; + + const run = () => { + this.running[key] = true; + this.runningCount++; + + factory().then(function (val) { + resolve(val); + next(); + return null; + }).catch(function (err) { + reject(err); + next(); + }); + }; + + this.maybePushConcurrencyQueue(run); + } + + maybePushConcurrencyQueue(run) { + if (this.runningCount < this.maxConcurrency) { + run(); + } else { + this.concurrencyQueue.push(run); + } + } + + shiftConcurrencyQueue() { + if (this.runningCount < this.maxConcurrency) { + const fn = this.concurrencyQueue.shift(); + if (fn) { + fn(); + } + } + } } +exports.default = BlockingQueue; + +/***/ }), +/* 85 */ +/***/ (function(module, exports) { + +module.exports = function (exec) { + try { + return !!exec(); + } catch (e) { + return true; + } +}; /***/ }), -/* 220 */ +/* 86 */, +/* 87 */, +/* 88 */, +/* 89 */, +/* 90 */, +/* 91 */, +/* 92 */, +/* 93 */, +/* 94 */, +/* 95 */, +/* 96 */, +/* 97 */, +/* 98 */, +/* 99 */, +/* 100 */ /***/ (function(module, exports, __webpack_require__) { -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -const NODE_PROCESS_VERSION_PARTS = process.versions.node.split('.'); -const MAJOR_VERSION = parseInt(NODE_PROCESS_VERSION_PARTS[0], 10); -const MINOR_VERSION = parseInt(NODE_PROCESS_VERSION_PARTS[1], 10); -/** - * IS `true` for Node.js 10.10 and greater. - */ -exports.IS_SUPPORT_READDIR_WITH_FILE_TYPES = MAJOR_VERSION > 10 || (MAJOR_VERSION === 10 && MINOR_VERSION >= 10); +// getting tag from 19.1.3.6 Object.prototype.toString() +var cof = __webpack_require__(47); +var TAG = __webpack_require__(13)('toStringTag'); +// ES3 wrong here +var ARG = cof(function () { return arguments; }()) == 'Arguments'; + +// fallback for IE11 Script Access Denied error +var tryGet = function (it, key) { + try { + return it[key]; + } catch (e) { /* empty */ } +}; + +module.exports = function (it) { + var O, T, B; + return it === undefined ? 'Undefined' : it === null ? 'Null' + // @@toStringTag case + : typeof (T = tryGet(O = Object(it), TAG)) == 'string' ? T + // builtinTag case + : ARG ? cof(O) + // ES3 arguments fallback + : (B = cof(O)) == 'Object' && typeof O.callee == 'function' ? 'Arguments' : B; +}; /***/ }), -/* 221 */ -/***/ (function(module, exports, __webpack_require__) { +/* 101 */ +/***/ (function(module, exports) { -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -const fs = __webpack_require__(222); -exports.fs = fs; +// IE 8- don't enum bug keys +module.exports = ( + 'constructor,hasOwnProperty,isPrototypeOf,propertyIsEnumerable,toLocaleString,toString,valueOf' +).split(','); /***/ }), -/* 222 */ +/* 102 */ /***/ (function(module, exports, __webpack_require__) { -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -class DirentFromStats { - constructor(name, stats) { - this.name = name; - this.isBlockDevice = stats.isBlockDevice.bind(stats); - this.isCharacterDevice = stats.isCharacterDevice.bind(stats); - this.isDirectory = stats.isDirectory.bind(stats); - this.isFIFO = stats.isFIFO.bind(stats); - this.isFile = stats.isFile.bind(stats); - this.isSocket = stats.isSocket.bind(stats); - this.isSymbolicLink = stats.isSymbolicLink.bind(stats); - } -} -function createDirentFromStats(name, stats) { - return new DirentFromStats(name, stats); -} -exports.createDirentFromStats = createDirentFromStats; +var document = __webpack_require__(11).document; +module.exports = document && document.documentElement; /***/ }), -/* 223 */ +/* 103 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -const fsStat = __webpack_require__(209); -const constants_1 = __webpack_require__(220); -const utils = __webpack_require__(221); -function read(dir, settings) { - if (!settings.stats && constants_1.IS_SUPPORT_READDIR_WITH_FILE_TYPES) { - return readdirWithFileTypes(dir, settings); - } - return readdir(dir, settings); -} -exports.read = read; -function readdirWithFileTypes(dir, settings) { - const dirents = settings.fs.readdirSync(dir, { withFileTypes: true }); - return dirents.map((dirent) => { - const entry = { - dirent, - name: dirent.name, - path: `${dir}${settings.pathSegmentSeparator}${dirent.name}` - }; - if (entry.dirent.isSymbolicLink() && settings.followSymbolicLinks) { - try { - const stats = settings.fs.statSync(entry.path); - entry.dirent = utils.fs.createDirentFromStats(entry.name, stats); - } - catch (error) { - if (settings.throwErrorOnBrokenSymbolicLink) { - throw error; - } - } - } - return entry; - }); -} -exports.readdirWithFileTypes = readdirWithFileTypes; -function readdir(dir, settings) { - const names = settings.fs.readdirSync(dir); - return names.map((name) => { - const entryPath = `${dir}${settings.pathSegmentSeparator}${name}`; - const stats = fsStat.statSync(entryPath, settings.fsStatSettings); - const entry = { - name, - path: entryPath, - dirent: utils.fs.createDirentFromStats(name, stats) - }; - if (settings.stats) { - entry.stats = stats; - } - return entry; - }); -} -exports.readdir = readdir; + +var LIBRARY = __webpack_require__(69); +var $export = __webpack_require__(41); +var redefine = __webpack_require__(197); +var hide = __webpack_require__(31); +var Iterators = __webpack_require__(35); +var $iterCreate = __webpack_require__(188); +var setToStringTag = __webpack_require__(71); +var getPrototypeOf = __webpack_require__(194); +var ITERATOR = __webpack_require__(13)('iterator'); +var BUGGY = !([].keys && 'next' in [].keys()); // Safari has buggy iterators w/o `next` +var FF_ITERATOR = '@@iterator'; +var KEYS = 'keys'; +var VALUES = 'values'; + +var returnThis = function () { return this; }; + +module.exports = function (Base, NAME, Constructor, next, DEFAULT, IS_SET, FORCED) { + $iterCreate(Constructor, NAME, next); + var getMethod = function (kind) { + if (!BUGGY && kind in proto) return proto[kind]; + switch (kind) { + case KEYS: return function keys() { return new Constructor(this, kind); }; + case VALUES: return function values() { return new Constructor(this, kind); }; + } return function entries() { return new Constructor(this, kind); }; + }; + var TAG = NAME + ' Iterator'; + var DEF_VALUES = DEFAULT == VALUES; + var VALUES_BUG = false; + var proto = Base.prototype; + var $native = proto[ITERATOR] || proto[FF_ITERATOR] || DEFAULT && proto[DEFAULT]; + var $default = $native || getMethod(DEFAULT); + var $entries = DEFAULT ? !DEF_VALUES ? $default : getMethod('entries') : undefined; + var $anyNative = NAME == 'Array' ? proto.entries || $native : $native; + var methods, key, IteratorPrototype; + // Fix native + if ($anyNative) { + IteratorPrototype = getPrototypeOf($anyNative.call(new Base())); + if (IteratorPrototype !== Object.prototype && IteratorPrototype.next) { + // Set @@toStringTag to native iterators + setToStringTag(IteratorPrototype, TAG, true); + // fix for some old engines + if (!LIBRARY && typeof IteratorPrototype[ITERATOR] != 'function') hide(IteratorPrototype, ITERATOR, returnThis); + } + } + // fix Array#{values, @@iterator}.name in V8 / FF + if (DEF_VALUES && $native && $native.name !== VALUES) { + VALUES_BUG = true; + $default = function values() { return $native.call(this); }; + } + // Define iterator + if ((!LIBRARY || FORCED) && (BUGGY || VALUES_BUG || !proto[ITERATOR])) { + hide(proto, ITERATOR, $default); + } + // Plug for library + Iterators[NAME] = $default; + Iterators[TAG] = returnThis; + if (DEFAULT) { + methods = { + values: DEF_VALUES ? $default : getMethod(VALUES), + keys: IS_SET ? $default : getMethod(KEYS), + entries: $entries + }; + if (FORCED) for (key in methods) { + if (!(key in proto)) redefine(proto, key, methods[key]); + } else $export($export.P + $export.F * (BUGGY || VALUES_BUG), NAME, methods); + } + return methods; +}; /***/ }), -/* 224 */ +/* 104 */ +/***/ (function(module, exports) { + +module.exports = function (exec) { + try { + return { e: false, v: exec() }; + } catch (e) { + return { e: true, v: e }; + } +}; + + +/***/ }), +/* 105 */ /***/ (function(module, exports, __webpack_require__) { -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -const path = __webpack_require__(16); -const fsStat = __webpack_require__(209); -const fs = __webpack_require__(225); -class Settings { - constructor(_options = {}) { - this._options = _options; - this.followSymbolicLinks = this._getValue(this._options.followSymbolicLinks, false); - this.fs = fs.createFileSystemAdapter(this._options.fs); - this.pathSegmentSeparator = this._getValue(this._options.pathSegmentSeparator, path.sep); - this.stats = this._getValue(this._options.stats, false); - this.throwErrorOnBrokenSymbolicLink = this._getValue(this._options.throwErrorOnBrokenSymbolicLink, true); - this.fsStatSettings = new fsStat.Settings({ - followSymbolicLink: this.followSymbolicLinks, - fs: this.fs, - throwErrorOnBrokenSymbolicLink: this.throwErrorOnBrokenSymbolicLink - }); - } - _getValue(option, value) { - return option === undefined ? value : option; - } -} -exports.default = Settings; +var anObject = __webpack_require__(27); +var isObject = __webpack_require__(34); +var newPromiseCapability = __webpack_require__(70); + +module.exports = function (C, x) { + anObject(C); + if (isObject(x) && x.constructor === C) return x; + var promiseCapability = newPromiseCapability.f(C); + var resolve = promiseCapability.resolve; + resolve(x); + return promiseCapability.promise; +}; /***/ }), -/* 225 */ +/* 106 */ +/***/ (function(module, exports) { + +module.exports = function (bitmap, value) { + return { + enumerable: !(bitmap & 1), + configurable: !(bitmap & 2), + writable: !(bitmap & 4), + value: value + }; +}; + + +/***/ }), +/* 107 */ /***/ (function(module, exports, __webpack_require__) { -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -const fs = __webpack_require__(23); -exports.FILE_SYSTEM_ADAPTER = { - lstat: fs.lstat, - stat: fs.stat, - lstatSync: fs.lstatSync, - statSync: fs.statSync, - readdir: fs.readdir, - readdirSync: fs.readdirSync -}; -function createFileSystemAdapter(fsMethods) { - if (!fsMethods) { - return exports.FILE_SYSTEM_ADAPTER; - } - return Object.assign({}, exports.FILE_SYSTEM_ADAPTER, fsMethods); -} -exports.createFileSystemAdapter = createFileSystemAdapter; +var core = __webpack_require__(23); +var global = __webpack_require__(11); +var SHARED = '__core-js_shared__'; +var store = global[SHARED] || (global[SHARED] = {}); + +(module.exports = function (key, value) { + return store[key] || (store[key] = value !== undefined ? value : {}); +})('versions', []).push({ + version: core.version, + mode: __webpack_require__(69) ? 'pure' : 'global', + copyright: '© 2018 Denis Pushkarev (zloirock.ru)' +}); /***/ }), -/* 226 */ +/* 108 */ /***/ (function(module, exports, __webpack_require__) { -"use strict"; +// 7.3.20 SpeciesConstructor(O, defaultConstructor) +var anObject = __webpack_require__(27); +var aFunction = __webpack_require__(46); +var SPECIES = __webpack_require__(13)('species'); +module.exports = function (O, D) { + var C = anObject(O).constructor; + var S; + return C === undefined || (S = anObject(C)[SPECIES]) == undefined ? D : aFunction(S); +}; -var reusify = __webpack_require__(227) +/***/ }), +/* 109 */ +/***/ (function(module, exports, __webpack_require__) { -function fastqueue (context, worker, concurrency) { - if (typeof context === 'function') { - concurrency = worker - worker = context - context = null +var ctx = __webpack_require__(48); +var invoke = __webpack_require__(185); +var html = __webpack_require__(102); +var cel = __webpack_require__(68); +var global = __webpack_require__(11); +var process = global.process; +var setTask = global.setImmediate; +var clearTask = global.clearImmediate; +var MessageChannel = global.MessageChannel; +var Dispatch = global.Dispatch; +var counter = 0; +var queue = {}; +var ONREADYSTATECHANGE = 'onreadystatechange'; +var defer, channel, port; +var run = function () { + var id = +this; + // eslint-disable-next-line no-prototype-builtins + if (queue.hasOwnProperty(id)) { + var fn = queue[id]; + delete queue[id]; + fn(); + } +}; +var listener = function (event) { + run.call(event.data); +}; +// Node.js 0.9+ & IE10+ has setImmediate, otherwise: +if (!setTask || !clearTask) { + setTask = function setImmediate(fn) { + var args = []; + var i = 1; + while (arguments.length > i) args.push(arguments[i++]); + queue[++counter] = function () { + // eslint-disable-next-line no-new-func + invoke(typeof fn == 'function' ? fn : Function(fn), args); + }; + defer(counter); + return counter; + }; + clearTask = function clearImmediate(id) { + delete queue[id]; + }; + // Node.js 0.8- + if (__webpack_require__(47)(process) == 'process') { + defer = function (id) { + process.nextTick(ctx(run, id, 1)); + }; + // Sphere (JS game engine) Dispatch API + } else if (Dispatch && Dispatch.now) { + defer = function (id) { + Dispatch.now(ctx(run, id, 1)); + }; + // Browsers with MessageChannel, includes WebWorkers + } else if (MessageChannel) { + channel = new MessageChannel(); + port = channel.port2; + channel.port1.onmessage = listener; + defer = ctx(port.postMessage, port, 1); + // Browsers with postMessage, skip WebWorkers + // IE8 has postMessage, but it's sync & typeof its postMessage is 'object' + } else if (global.addEventListener && typeof postMessage == 'function' && !global.importScripts) { + defer = function (id) { + global.postMessage(id + '', '*'); + }; + global.addEventListener('message', listener, false); + // IE8- + } else if (ONREADYSTATECHANGE in cel('script')) { + defer = function (id) { + html.appendChild(cel('script'))[ONREADYSTATECHANGE] = function () { + html.removeChild(this); + run.call(id); + }; + }; + // Rest old browsers + } else { + defer = function (id) { + setTimeout(ctx(run, id, 1), 0); + }; } +} +module.exports = { + set: setTask, + clear: clearTask +}; - var cache = reusify(Task) - var queueHead = null - var queueTail = null - var _running = 0 - var self = { - push: push, - drain: noop, - saturated: noop, - pause: pause, - paused: false, - concurrency: concurrency, - running: running, - resume: resume, - idle: idle, - length: length, - unshift: unshift, - empty: noop, - kill: kill, - killAndDrain: killAndDrain - } +/***/ }), +/* 110 */ +/***/ (function(module, exports, __webpack_require__) { - return self +// 7.1.15 ToLength +var toInteger = __webpack_require__(73); +var min = Math.min; +module.exports = function (it) { + return it > 0 ? min(toInteger(it), 0x1fffffffffffff) : 0; // pow(2, 53) - 1 == 9007199254740991 +}; - function running () { - return _running - } - function pause () { - self.paused = true - } +/***/ }), +/* 111 */ +/***/ (function(module, exports) { - function length () { - var current = queueHead - var counter = 0 +var id = 0; +var px = Math.random(); +module.exports = function (key) { + return 'Symbol('.concat(key === undefined ? '' : key, ')_', (++id + px).toString(36)); +}; - while (current) { - current = current.next - counter++ - } - return counter - } +/***/ }), +/* 112 */ +/***/ (function(module, exports, __webpack_require__) { - function resume () { - if (!self.paused) return - self.paused = false - for (var i = 0; i < self.concurrency; i++) { - _running++ - release() - } - } - function idle () { - return _running === 0 && self.length() === 0 - } +/** + * This is the common logic for both the Node.js and web browser + * implementations of `debug()`. + * + * Expose `debug()` as the module. + */ - function push (value, done) { - var current = cache.get() +exports = module.exports = createDebug.debug = createDebug['default'] = createDebug; +exports.coerce = coerce; +exports.disable = disable; +exports.enable = enable; +exports.enabled = enabled; +exports.humanize = __webpack_require__(229); - current.context = context - current.release = release - current.value = value - current.callback = done || noop +/** + * Active `debug` instances. + */ +exports.instances = []; - if (_running === self.concurrency || self.paused) { - if (queueTail) { - queueTail.next = current - queueTail = current - } else { - queueHead = current - queueTail = current - self.saturated() - } - } else { - _running++ - worker.call(context, current.value, current.worked) - } +/** + * 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 = {}; + +/** + * 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 } - function unshift (value, done) { - var current = cache.get() + return exports.colors[Math.abs(hash) % exports.colors.length]; +} - current.context = context - current.release = release - current.value = value - current.callback = done || noop +/** + * Create a debugger with the given `namespace`. + * + * @param {String} namespace + * @return {Function} + * @api public + */ - if (_running === self.concurrency || self.paused) { - if (queueHead) { - current.next = queueHead - queueHead = current - } else { - queueHead = current - queueTail = current - self.saturated() - } - } else { - _running++ - worker.call(context, current.value, current.worked) +function createDebug(namespace) { + + var prevTime; + + 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]; } - } - function release (holder) { - if (holder) { - cache.release(holder) + args[0] = exports.coerce(args[0]); + + if ('string' !== typeof args[0]) { + // anything else let's inspect with %O + args.unshift('%O'); } - var next = queueHead - if (next) { - if (!self.paused) { - if (queueTail === queueHead) { - queueTail = null - } - queueHead = next.next - next.next = null - worker.call(context, next.value, next.worked) - if (queueTail === null) { - self.empty() - } - } else { - _running-- + + // 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--; } - } else if (--_running === 0) { - self.drain() - } + 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); + debug.destroy = destroy; + + // env-specific initialization logic for debug instances + if ('function' === typeof exports.init) { + exports.init(debug); + } + + exports.instances.push(debug); + + return debug; +} + +function destroy () { + var index = exports.instances.indexOf(this); + if (index !== -1) { + exports.instances.splice(index, 1); + return true; + } else { + return false; } +} - function kill () { - queueHead = null - queueTail = null - self.drain = noop +/** + * 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 i; + var split = (typeof namespaces === 'string' ? namespaces : '').split(/[\s,]+/); + var len = split.length; + + for (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 + '$')); + } } - function killAndDrain () { - queueHead = null - queueTail = null - self.drain() - self.drain = noop + for (i = 0; i < exports.instances.length; i++) { + var instance = exports.instances[i]; + instance.enabled = exports.enabled(instance.namespace); } } -function noop () {} +/** + * Disable debug output. + * + * @api public + */ -function Task () { - this.value = null - this.callback = noop - this.next = null - this.release = noop - this.context = null +function disable() { + exports.enable(''); +} - var self = this +/** + * Returns true if the given mode name is enabled, false otherwise. + * + * @param {String} name + * @return {Boolean} + * @api public + */ - this.worked = function worked (err, result) { - var callback = self.callback - self.value = null - self.callback = noop - callback.call(self.context, err, result) - self.release(self) +function enabled(name) { + if (name[name.length - 1] === '*') { + return true; + } + 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; } -module.exports = fastqueue +/** + * Coerce `val`. + * + * @param {Mixed} val + * @return {Mixed} + * @api private + */ + +function coerce(val) { + if (val instanceof Error) return val.stack || val.message; + return val; +} /***/ }), -/* 227 */ +/* 113 */, +/* 114 */ /***/ (function(module, exports, __webpack_require__) { -"use strict"; - +module.exports = realpath +realpath.realpath = realpath +realpath.sync = realpathSync +realpath.realpathSync = realpathSync +realpath.monkeypatch = monkeypatch +realpath.unmonkeypatch = unmonkeypatch -function reusify (Constructor) { - var head = new Constructor() - var tail = head +var fs = __webpack_require__(3) +var origRealpath = fs.realpath +var origRealpathSync = fs.realpathSync - function get () { - var current = head +var version = process.version +var ok = /^v[0-5]\./.test(version) +var old = __webpack_require__(217) - if (current.next) { - head = current.next - } else { - head = new Constructor() - tail = head - } +function newError (er) { + return er && er.syscall === 'realpath' && ( + er.code === 'ELOOP' || + er.code === 'ENOMEM' || + er.code === 'ENAMETOOLONG' + ) +} - current.next = null +function realpath (p, cache, cb) { + if (ok) { + return origRealpath(p, cache, cb) + } - return current + if (typeof cache === 'function') { + cb = cache + cache = null } + origRealpath(p, cache, function (er, result) { + if (newError(er)) { + old.realpath(p, cache, cb) + } else { + cb(er, result) + } + }) +} - function release (obj) { - tail.next = obj - tail = obj +function realpathSync (p, cache) { + if (ok) { + return origRealpathSync(p, cache) } - return { - get: get, - release: release + try { + return origRealpathSync(p, cache) + } catch (er) { + if (newError(er)) { + return old.realpathSync(p, cache) + } else { + throw er + } } } -module.exports = reusify - - -/***/ }), -/* 228 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -function isFatalError(settings, error) { - if (settings.errorFilter === null) { - return true; - } - return !settings.errorFilter(error); -} -exports.isFatalError = isFatalError; -function isAppliedFilter(filter, value) { - return filter === null || filter(value); -} -exports.isAppliedFilter = isAppliedFilter; -function replacePathSegmentSeparator(filepath, separator) { - return filepath.split(/[\\\/]/).join(separator); -} -exports.replacePathSegmentSeparator = replacePathSegmentSeparator; -function joinPathSegments(a, b, separator) { - if (a === '') { - return b; - } - return a + separator + b; -} -exports.joinPathSegments = joinPathSegments; - - -/***/ }), -/* 229 */ -/***/ (function(module, exports, __webpack_require__) { +function monkeypatch () { + fs.realpath = realpath + fs.realpathSync = realpathSync +} -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -const common = __webpack_require__(228); -class Reader { - constructor(_root, _settings) { - this._root = _root; - this._settings = _settings; - this._root = common.replacePathSegmentSeparator(_root, _settings.pathSegmentSeparator); - } -} -exports.default = Reader; +function unmonkeypatch () { + fs.realpath = origRealpath + fs.realpathSync = origRealpathSync +} /***/ }), -/* 230 */ +/* 115 */ /***/ (function(module, exports, __webpack_require__) { -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -const stream_1 = __webpack_require__(28); -const async_1 = __webpack_require__(216); -class StreamProvider { - constructor(_root, _settings) { - this._root = _root; - this._settings = _settings; - this._reader = new async_1.default(this._root, this._settings); - this._stream = new stream_1.Readable({ - objectMode: true, - read: () => { }, - destroy: this._reader.destroy.bind(this._reader) - }); - } - read() { - this._reader.onError((error) => { - this._stream.emit('error', error); - }); - this._reader.onEntry((entry) => { - this._stream.push(entry); - }); - this._reader.onEnd(() => { - this._stream.push(null); - }); - this._reader.read(); - return this._stream; - } -} -exports.default = StreamProvider; - +exports.alphasort = alphasort +exports.alphasorti = alphasorti +exports.setopts = setopts +exports.ownProp = ownProp +exports.makeAbs = makeAbs +exports.finish = finish +exports.mark = mark +exports.isIgnored = isIgnored +exports.childrenIgnored = childrenIgnored -/***/ }), -/* 231 */ -/***/ (function(module, exports, __webpack_require__) { +function ownProp (obj, field) { + return Object.prototype.hasOwnProperty.call(obj, field) +} -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -const sync_1 = __webpack_require__(232); -class SyncProvider { - constructor(_root, _settings) { - this._root = _root; - this._settings = _settings; - this._reader = new sync_1.default(this._root, this._settings); - } - read() { - return this._reader.read(); - } -} -exports.default = SyncProvider; +var path = __webpack_require__(0) +var minimatch = __webpack_require__(60) +var isAbsolute = __webpack_require__(76) +var Minimatch = minimatch.Minimatch +function alphasorti (a, b) { + return a.toLowerCase().localeCompare(b.toLowerCase()) +} -/***/ }), -/* 232 */ -/***/ (function(module, exports, __webpack_require__) { +function alphasort (a, b) { + return a.localeCompare(b) +} -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -const fsScandir = __webpack_require__(217); -const common = __webpack_require__(228); -const reader_1 = __webpack_require__(229); -class SyncReader extends reader_1.default { - constructor() { - super(...arguments); - this._scandir = fsScandir.scandirSync; - this._storage = new Set(); - this._queue = new Set(); - } - read() { - this._pushToQueue(this._root, this._settings.basePath); - this._handleQueue(); - return Array.from(this._storage); - } - _pushToQueue(dir, base) { - this._queue.add({ dir, base }); - } - _handleQueue() { - for (const item of this._queue.values()) { - this._handleDirectory(item.dir, item.base); - } - } - _handleDirectory(dir, base) { - try { - const entries = this._scandir(dir, this._settings.fsScandirSettings); - for (const entry of entries) { - this._handleEntry(entry, base); - } - } - catch (error) { - this._handleError(error); - } - } - _handleError(error) { - if (!common.isFatalError(this._settings, error)) { - return; - } - throw error; - } - _handleEntry(entry, base) { - const fullpath = entry.path; - if (base !== undefined) { - entry.path = common.joinPathSegments(base, entry.name, this._settings.pathSegmentSeparator); - } - if (common.isAppliedFilter(this._settings.entryFilter, entry)) { - this._pushToStorage(entry); - } - if (entry.dirent.isDirectory() && common.isAppliedFilter(this._settings.deepFilter, entry)) { - this._pushToQueue(fullpath, entry.path); - } - } - _pushToStorage(entry) { - this._storage.add(entry); - } -} -exports.default = SyncReader; +function setupIgnores (self, options) { + self.ignore = options.ignore || [] + if (!Array.isArray(self.ignore)) + self.ignore = [self.ignore] -/***/ }), -/* 233 */ -/***/ (function(module, exports, __webpack_require__) { + if (self.ignore.length) { + self.ignore = self.ignore.map(ignoreMap) + } +} -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -const path = __webpack_require__(16); -const fsScandir = __webpack_require__(217); -class Settings { - constructor(_options = {}) { - this._options = _options; - this.basePath = this._getValue(this._options.basePath, undefined); - this.concurrency = this._getValue(this._options.concurrency, Infinity); - this.deepFilter = this._getValue(this._options.deepFilter, null); - this.entryFilter = this._getValue(this._options.entryFilter, null); - this.errorFilter = this._getValue(this._options.errorFilter, null); - this.pathSegmentSeparator = this._getValue(this._options.pathSegmentSeparator, path.sep); - this.fsScandirSettings = new fsScandir.Settings({ - followSymbolicLinks: this._options.followSymbolicLinks, - fs: this._options.fs, - pathSegmentSeparator: this._options.pathSegmentSeparator, - stats: this._options.stats, - throwErrorOnBrokenSymbolicLink: this._options.throwErrorOnBrokenSymbolicLink - }); - } - _getValue(option, value) { - return option === undefined ? value : option; - } -} -exports.default = Settings; +// ignore patterns are always in dot:true mode. +function ignoreMap (pattern) { + var gmatcher = null + if (pattern.slice(-3) === '/**') { + var gpattern = pattern.replace(/(\/\*\*)+$/, '') + gmatcher = new Minimatch(gpattern, { dot: true }) + } + return { + matcher: new Minimatch(pattern, { dot: true }), + gmatcher: gmatcher + } +} -/***/ }), -/* 234 */ -/***/ (function(module, exports, __webpack_require__) { +function setopts (self, pattern, options) { + if (!options) + options = {} -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -const path = __webpack_require__(16); -const fsStat = __webpack_require__(209); -const utils = __webpack_require__(180); -class Reader { - constructor(_settings) { - this._settings = _settings; - this._fsStatSettings = new fsStat.Settings({ - followSymbolicLink: this._settings.followSymbolicLinks, - fs: this._settings.fs, - throwErrorOnBrokenSymbolicLink: this._settings.followSymbolicLinks - }); - } - _getFullEntryPath(filepath) { - return path.resolve(this._settings.cwd, filepath); - } - _makeEntry(stats, pattern) { - const entry = { - name: pattern, - path: pattern, - dirent: utils.fs.createDirentFromStats(pattern, stats) - }; - if (this._settings.stats) { - entry.stats = stats; - } - return entry; - } - _isFatalError(error) { - return !utils.errno.isEnoentCodeError(error) && !this._settings.suppressErrors; - } -} -exports.default = Reader; + // base-matching: just use globstar for that. + if (options.matchBase && -1 === pattern.indexOf("/")) { + if (options.noglobstar) { + throw new Error("base matching requires globstar") + } + pattern = "**/" + pattern + } + self.silent = !!options.silent + self.pattern = pattern + self.strict = options.strict !== false + self.realpath = !!options.realpath + self.realpathCache = options.realpathCache || Object.create(null) + self.follow = !!options.follow + self.dot = !!options.dot + self.mark = !!options.mark + self.nodir = !!options.nodir + if (self.nodir) + self.mark = true + self.sync = !!options.sync + self.nounique = !!options.nounique + self.nonull = !!options.nonull + self.nosort = !!options.nosort + self.nocase = !!options.nocase + self.stat = !!options.stat + self.noprocess = !!options.noprocess + self.absolute = !!options.absolute -/***/ }), -/* 235 */ -/***/ (function(module, exports, __webpack_require__) { + self.maxLength = options.maxLength || Infinity + self.cache = options.cache || Object.create(null) + self.statCache = options.statCache || Object.create(null) + self.symlinks = options.symlinks || Object.create(null) -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -const path = __webpack_require__(16); -const deep_1 = __webpack_require__(236); -const entry_1 = __webpack_require__(237); -const error_1 = __webpack_require__(238); -const entry_2 = __webpack_require__(239); -class Provider { - constructor(_settings) { - this._settings = _settings; - this.errorFilter = new error_1.default(this._settings); - this.entryFilter = new entry_1.default(this._settings, this._getMicromatchOptions()); - this.deepFilter = new deep_1.default(this._settings, this._getMicromatchOptions()); - this.entryTransformer = new entry_2.default(this._settings); - } - _getRootDirectory(task) { - return path.resolve(this._settings.cwd, task.base); - } - _getReaderOptions(task) { - const basePath = task.base === '.' ? '' : task.base; - return { - basePath, - pathSegmentSeparator: '/', - concurrency: this._settings.concurrency, - deepFilter: this.deepFilter.getFilter(basePath, task.positive, task.negative), - entryFilter: this.entryFilter.getFilter(task.positive, task.negative), - errorFilter: this.errorFilter.getFilter(), - followSymbolicLinks: this._settings.followSymbolicLinks, - fs: this._settings.fs, - stats: this._settings.stats, - throwErrorOnBrokenSymbolicLink: this._settings.throwErrorOnBrokenSymbolicLink, - transform: this.entryTransformer.getTransformer() - }; - } - _getMicromatchOptions() { - return { - dot: this._settings.dot, - matchBase: this._settings.baseNameMatch, - nobrace: !this._settings.braceExpansion, - nocase: !this._settings.caseSensitiveMatch, - noext: !this._settings.extglob, - noglobstar: !this._settings.globstar, - posix: true, - strictSlashes: false - }; - } -} -exports.default = Provider; + setupIgnores(self, options) + self.changedCwd = false + var cwd = process.cwd() + if (!ownProp(options, "cwd")) + self.cwd = cwd + else { + self.cwd = path.resolve(options.cwd) + self.changedCwd = self.cwd !== cwd + } -/***/ }), -/* 236 */ -/***/ (function(module, exports, __webpack_require__) { + self.root = options.root || path.resolve(self.cwd, "/") + self.root = path.resolve(self.root) + if (process.platform === "win32") + self.root = self.root.replace(/\\/g, "/") -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -const utils = __webpack_require__(180); -class DeepFilter { - constructor(_settings, _micromatchOptions) { - this._settings = _settings; - this._micromatchOptions = _micromatchOptions; - } - getFilter(basePath, positive, negative) { - const maxPatternDepth = this._getMaxPatternDepth(positive); - const negativeRe = this._getNegativePatternsRe(negative); - return (entry) => this._filter(basePath, entry, negativeRe, maxPatternDepth); - } - _getMaxPatternDepth(patterns) { - const globstar = patterns.some(utils.pattern.hasGlobStar); - return globstar ? Infinity : utils.pattern.getMaxNaivePatternsDepth(patterns); - } - _getNegativePatternsRe(patterns) { - const affectDepthOfReadingPatterns = patterns.filter(utils.pattern.isAffectDepthOfReadingPattern); - return utils.pattern.convertPatternsToRe(affectDepthOfReadingPatterns, this._micromatchOptions); - } - _filter(basePath, entry, negativeRe, maxPatternDepth) { - const depth = this._getEntryDepth(basePath, entry.path); - if (this._isSkippedByDeep(depth)) { - return false; - } - if (this._isSkippedByMaxPatternDepth(depth, maxPatternDepth)) { - return false; - } - if (this._isSkippedSymbolicLink(entry)) { - return false; - } - if (this._isSkippedDotDirectory(entry)) { - return false; - } - return this._isSkippedByNegativePatterns(entry, negativeRe); - } - _getEntryDepth(basePath, entryPath) { - const basePathDepth = basePath.split('/').length; - const entryPathDepth = entryPath.split('/').length; - return entryPathDepth - (basePath === '' ? 0 : basePathDepth); - } - _isSkippedByDeep(entryDepth) { - return entryDepth >= this._settings.deep; - } - _isSkippedByMaxPatternDepth(entryDepth, maxPatternDepth) { - return !this._settings.baseNameMatch && maxPatternDepth !== Infinity && entryDepth > maxPatternDepth; - } - _isSkippedSymbolicLink(entry) { - return !this._settings.followSymbolicLinks && entry.dirent.isSymbolicLink(); - } - _isSkippedDotDirectory(entry) { - return !this._settings.dot && entry.name.startsWith('.'); - } - _isSkippedByNegativePatterns(entry, negativeRe) { - return !utils.pattern.matchAny(entry.path, negativeRe); - } -} -exports.default = DeepFilter; + // TODO: is an absolute `cwd` supposed to be resolved against `root`? + // e.g. { cwd: '/test', root: __dirname } === path.join(__dirname, '/test') + self.cwdAbs = isAbsolute(self.cwd) ? self.cwd : makeAbs(self, self.cwd) + if (process.platform === "win32") + self.cwdAbs = self.cwdAbs.replace(/\\/g, "/") + self.nomount = !!options.nomount + // disable comments and negation in Minimatch. + // Note that they are not supported in Glob itself anyway. + options.nonegate = true + options.nocomment = true -/***/ }), -/* 237 */ -/***/ (function(module, exports, __webpack_require__) { + self.minimatch = new Minimatch(pattern, options) + self.options = self.minimatch.options +} -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -const utils = __webpack_require__(180); -class EntryFilter { - constructor(_settings, _micromatchOptions) { - this._settings = _settings; - this._micromatchOptions = _micromatchOptions; - this.index = new Map(); - } - getFilter(positive, negative) { - const positiveRe = utils.pattern.convertPatternsToRe(positive, this._micromatchOptions); - const negativeRe = utils.pattern.convertPatternsToRe(negative, this._micromatchOptions); - return (entry) => this._filter(entry, positiveRe, negativeRe); - } - _filter(entry, positiveRe, negativeRe) { - if (this._settings.unique) { - if (this._isDuplicateEntry(entry)) { - return false; - } - this._createIndexRecord(entry); - } - if (this._onlyFileFilter(entry) || this._onlyDirectoryFilter(entry)) { - return false; - } - if (this._isSkippedByAbsoluteNegativePatterns(entry, negativeRe)) { - return false; - } - const filepath = this._settings.baseNameMatch ? entry.name : entry.path; - return this._isMatchToPatterns(filepath, positiveRe) && !this._isMatchToPatterns(entry.path, negativeRe); - } - _isDuplicateEntry(entry) { - return this.index.has(entry.path); - } - _createIndexRecord(entry) { - this.index.set(entry.path, undefined); - } - _onlyFileFilter(entry) { - return this._settings.onlyFiles && !entry.dirent.isFile(); - } - _onlyDirectoryFilter(entry) { - return this._settings.onlyDirectories && !entry.dirent.isDirectory(); - } - _isSkippedByAbsoluteNegativePatterns(entry, negativeRe) { - if (!this._settings.absolute) { - return false; - } - const fullpath = utils.path.makeAbsolute(this._settings.cwd, entry.path); - return this._isMatchToPatterns(fullpath, negativeRe); - } - _isMatchToPatterns(filepath, patternsRe) { - return utils.pattern.matchAny(filepath, patternsRe); - } -} -exports.default = EntryFilter; +function finish (self) { + var nou = self.nounique + var all = nou ? [] : Object.create(null) + for (var i = 0, l = self.matches.length; i < l; i ++) { + var matches = self.matches[i] + if (!matches || Object.keys(matches).length === 0) { + if (self.nonull) { + // do like the shell, and spit out the literal glob + var literal = self.minimatch.globSet[i] + if (nou) + all.push(literal) + else + all[literal] = true + } + } else { + // had matches + var m = Object.keys(matches) + if (nou) + all.push.apply(all, m) + else + m.forEach(function (m) { + all[m] = true + }) + } + } -/***/ }), -/* 238 */ -/***/ (function(module, exports, __webpack_require__) { + if (!nou) + all = Object.keys(all) -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -const utils = __webpack_require__(180); -class ErrorFilter { - constructor(_settings) { - this._settings = _settings; - } - getFilter() { - return (error) => this._isNonFatalError(error); - } - _isNonFatalError(error) { - return utils.errno.isEnoentCodeError(error) || this._settings.suppressErrors; - } -} -exports.default = ErrorFilter; + if (!self.nosort) + all = all.sort(self.nocase ? alphasorti : alphasort) + // at *some* point we statted all of these + if (self.mark) { + for (var i = 0; i < all.length; i++) { + all[i] = self._mark(all[i]) + } + if (self.nodir) { + all = all.filter(function (e) { + var notDir = !(/\/$/.test(e)) + var c = self.cache[e] || self.cache[makeAbs(self, e)] + if (notDir && c) + notDir = c !== 'DIR' && !Array.isArray(c) + return notDir + }) + } + } -/***/ }), -/* 239 */ -/***/ (function(module, exports, __webpack_require__) { + if (self.ignore.length) + all = all.filter(function(m) { + return !isIgnored(self, m) + }) -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -const utils = __webpack_require__(180); -class EntryTransformer { - constructor(_settings) { - this._settings = _settings; - } - getTransformer() { - return (entry) => this._transform(entry); - } - _transform(entry) { - let filepath = entry.path; - if (this._settings.absolute) { - filepath = utils.path.makeAbsolute(this._settings.cwd, filepath); - filepath = utils.path.unixify(filepath); - } - if (this._settings.markDirectories && entry.dirent.isDirectory()) { - filepath += '/'; - } - if (!this._settings.objectMode) { - return filepath; - } - return Object.assign({}, entry, { path: filepath }); - } -} -exports.default = EntryTransformer; + self.found = all +} +function mark (self, p) { + var abs = makeAbs(self, p) + var c = self.cache[abs] + var m = p + if (c) { + var isDir = c === 'DIR' || Array.isArray(c) + var slash = p.slice(-1) === '/' -/***/ }), -/* 240 */ -/***/ (function(module, exports, __webpack_require__) { + if (isDir && !slash) + m += '/' + else if (!isDir && slash) + m = m.slice(0, -1) -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -const stream_1 = __webpack_require__(28); -const stream_2 = __webpack_require__(208); -const provider_1 = __webpack_require__(235); -class ProviderStream extends provider_1.default { - constructor() { - super(...arguments); - this._reader = new stream_2.default(this._settings); - } - read(task) { - const root = this._getRootDirectory(task); - const options = this._getReaderOptions(task); - const source = this.api(root, task, options); - const dest = new stream_1.Readable({ objectMode: true, read: () => { } }); - source - .once('error', (error) => dest.emit('error', error)) - .on('data', (entry) => dest.emit('data', options.transform(entry))) - .once('end', () => dest.emit('end')); - return dest; - } - api(root, task, options) { - if (task.dynamic) { - return this._reader.dynamic(root, options); - } - return this._reader.static(task.patterns, options); - } -} -exports.default = ProviderStream; + if (m !== p) { + var mabs = makeAbs(self, m) + self.statCache[mabs] = self.statCache[abs] + self.cache[mabs] = self.cache[abs] + } + } + return m +} -/***/ }), -/* 241 */ -/***/ (function(module, exports, __webpack_require__) { +// lotta situps... +function makeAbs (self, f) { + var abs = f + if (f.charAt(0) === '/') { + abs = path.join(self.root, f) + } else if (isAbsolute(f) || f === '') { + abs = f + } else if (self.changedCwd) { + abs = path.resolve(self.cwd, f) + } else { + abs = path.resolve(f) + } -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -const sync_1 = __webpack_require__(242); -const provider_1 = __webpack_require__(235); -class ProviderSync extends provider_1.default { - constructor() { - super(...arguments); - this._reader = new sync_1.default(this._settings); - } - read(task) { - const root = this._getRootDirectory(task); - const options = this._getReaderOptions(task); - const entries = this.api(root, task, options); - return entries.map(options.transform); - } - api(root, task, options) { - if (task.dynamic) { - return this._reader.dynamic(root, options); - } - return this._reader.static(task.patterns, options); - } -} -exports.default = ProviderSync; + if (process.platform === 'win32') + abs = abs.replace(/\\/g, '/') + return abs +} -/***/ }), -/* 242 */ -/***/ (function(module, exports, __webpack_require__) { -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -const fsStat = __webpack_require__(209); -const fsWalk = __webpack_require__(214); -const reader_1 = __webpack_require__(234); -class ReaderSync extends reader_1.default { - constructor() { - super(...arguments); - this._walkSync = fsWalk.walkSync; - this._statSync = fsStat.statSync; - } - dynamic(root, options) { - return this._walkSync(root, options); - } - static(patterns, options) { - const entries = []; - for (const pattern of patterns) { - const filepath = this._getFullEntryPath(pattern); - const entry = this._getEntry(filepath, pattern, options); - if (entry === null || !options.entryFilter(entry)) { - continue; - } - entries.push(entry); - } - return entries; - } - _getEntry(filepath, pattern, options) { - try { - const stats = this._getStat(filepath); - return this._makeEntry(stats, pattern); - } - catch (error) { - if (options.errorFilter(error)) { - return null; - } - throw error; - } - } - _getStat(filepath) { - return this._statSync(filepath, this._fsStatSettings); - } -} -exports.default = ReaderSync; +// Return true, if pattern ends with globstar '**', for the accompanying parent directory. +// Ex:- If node_modules/** is the pattern, add 'node_modules' to ignore list along with it's contents +function isIgnored (self, path) { + if (!self.ignore.length) + return false + return self.ignore.some(function(item) { + return item.matcher.match(path) || !!(item.gmatcher && item.gmatcher.match(path)) + }) +} -/***/ }), -/* 243 */ -/***/ (function(module, exports, __webpack_require__) { +function childrenIgnored (self, path) { + if (!self.ignore.length) + return false -"use strict"; - -Object.defineProperty(exports, "__esModule", { value: true }); -const fs = __webpack_require__(23); -const os = __webpack_require__(11); -const CPU_COUNT = os.cpus().length; -exports.DEFAULT_FILE_SYSTEM_ADAPTER = { - lstat: fs.lstat, - lstatSync: fs.lstatSync, - stat: fs.stat, - statSync: fs.statSync, - readdir: fs.readdir, - readdirSync: fs.readdirSync -}; -// tslint:enable no-redundant-jsdoc -class Settings { - constructor(_options = {}) { - this._options = _options; - this.absolute = this._getValue(this._options.absolute, false); - this.baseNameMatch = this._getValue(this._options.baseNameMatch, false); - this.braceExpansion = this._getValue(this._options.braceExpansion, true); - this.caseSensitiveMatch = this._getValue(this._options.caseSensitiveMatch, true); - this.concurrency = this._getValue(this._options.concurrency, CPU_COUNT); - this.cwd = this._getValue(this._options.cwd, process.cwd()); - this.deep = this._getValue(this._options.deep, Infinity); - this.dot = this._getValue(this._options.dot, false); - this.extglob = this._getValue(this._options.extglob, true); - this.followSymbolicLinks = this._getValue(this._options.followSymbolicLinks, true); - this.fs = this._getFileSystemMethods(this._options.fs); - this.globstar = this._getValue(this._options.globstar, true); - this.ignore = this._getValue(this._options.ignore, []); - this.markDirectories = this._getValue(this._options.markDirectories, false); - this.objectMode = this._getValue(this._options.objectMode, false); - this.onlyDirectories = this._getValue(this._options.onlyDirectories, false); - this.onlyFiles = this._getValue(this._options.onlyFiles, true); - this.stats = this._getValue(this._options.stats, false); - this.suppressErrors = this._getValue(this._options.suppressErrors, false); - this.throwErrorOnBrokenSymbolicLink = this._getValue(this._options.throwErrorOnBrokenSymbolicLink, false); - this.unique = this._getValue(this._options.unique, true); - if (this.onlyDirectories) { - this.onlyFiles = false; - } - if (this.stats) { - this.objectMode = true; - } - } - _getValue(option, value) { - return option === undefined ? value : option; - } - _getFileSystemMethods(methods = {}) { - return Object.assign({}, exports.DEFAULT_FILE_SYSTEM_ADAPTER, methods); - } -} -exports.default = Settings; + return self.ignore.some(function(item) { + return !!(item.gmatcher && item.gmatcher.match(path)) + }) +} /***/ }), -/* 244 */ +/* 116 */ /***/ (function(module, exports, __webpack_require__) { -"use strict"; - -const path = __webpack_require__(16); -const pathType = __webpack_require__(245); - -const getExtensions = extensions => extensions.length > 1 ? `{${extensions.join(',')}}` : extensions[0]; - -const getPath = (filepath, cwd) => { - const pth = filepath[0] === '!' ? filepath.slice(1) : filepath; - return path.isAbsolute(pth) ? pth : path.join(cwd, pth); -}; +var path = __webpack_require__(0); +var fs = __webpack_require__(3); +var _0777 = parseInt('0777', 8); -const addExtensions = (file, extensions) => { - if (path.extname(file)) { - return `**/${file}`; - } +module.exports = mkdirP.mkdirp = mkdirP.mkdirP = mkdirP; - return `**/${file}.${getExtensions(extensions)}`; -}; +function mkdirP (p, opts, f, made) { + if (typeof opts === 'function') { + f = opts; + opts = {}; + } + else if (!opts || typeof opts !== 'object') { + opts = { mode: opts }; + } + + var mode = opts.mode; + var xfs = opts.fs || fs; + + if (mode === undefined) { + mode = _0777 & (~process.umask()); + } + if (!made) made = null; + + var cb = f || function () {}; + p = path.resolve(p); + + xfs.mkdir(p, mode, function (er) { + if (!er) { + made = made || p; + return cb(null, made); + } + switch (er.code) { + case 'ENOENT': + mkdirP(path.dirname(p), opts, function (er, made) { + if (er) cb(er, made); + else mkdirP(p, opts, cb, made); + }); + break; -const getGlob = (directory, options) => { - if (options.files && !Array.isArray(options.files)) { - throw new TypeError(`Expected \`files\` to be of type \`Array\` but received type \`${typeof options.files}\``); - } + // In the case of any other error, just see if there's a dir + // there already. If so, then hooray! If not, then something + // is borked. + default: + xfs.stat(p, function (er2, stat) { + // if the stat fails, then that's super weird. + // let the original error be the failure reason. + if (er2 || !stat.isDirectory()) cb(er, made) + else cb(null, made); + }); + break; + } + }); +} - if (options.extensions && !Array.isArray(options.extensions)) { - throw new TypeError(`Expected \`extensions\` to be of type \`Array\` but received type \`${typeof options.extensions}\``); - } +mkdirP.sync = function sync (p, opts, made) { + if (!opts || typeof opts !== 'object') { + opts = { mode: opts }; + } + + var mode = opts.mode; + var xfs = opts.fs || fs; + + if (mode === undefined) { + mode = _0777 & (~process.umask()); + } + if (!made) made = null; - if (options.files && options.extensions) { - return options.files.map(x => path.posix.join(directory, addExtensions(x, options.extensions))); - } + p = path.resolve(p); - if (options.files) { - return options.files.map(x => path.posix.join(directory, `**/${x}`)); - } + try { + xfs.mkdirSync(p, mode); + made = made || p; + } + catch (err0) { + switch (err0.code) { + case 'ENOENT' : + made = sync(path.dirname(p), opts, made); + sync(p, opts, made); + break; - if (options.extensions) { - return [path.posix.join(directory, `**/*.${getExtensions(options.extensions)}`)]; - } + // In the case of any other error, just see if there's a dir + // there already. If so, then hooray! If not, then something + // is borked. + default: + var stat; + try { + stat = xfs.statSync(p); + } + catch (err1) { + throw err0; + } + if (!stat.isDirectory()) throw err0; + break; + } + } - return [path.posix.join(directory, '**')]; + return made; }; -module.exports = async (input, options) => { - options = { - cwd: process.cwd(), - ...options - }; - - if (typeof options.cwd !== 'string') { - throw new TypeError(`Expected \`cwd\` to be of type \`string\` but received type \`${typeof options.cwd}\``); - } - - const globs = await Promise.all([].concat(input).map(async x => { - const isDirectory = await pathType.isDirectory(getPath(x, options.cwd)); - return isDirectory ? getGlob(x, options) : x; - })); - return [].concat.apply([], globs); // eslint-disable-line prefer-spread -}; +/***/ }), +/* 117 */, +/* 118 */, +/* 119 */, +/* 120 */, +/* 121 */, +/* 122 */ +/***/ (function(module, exports, __webpack_require__) { -module.exports.sync = (input, options) => { - options = { - cwd: process.cwd(), - ...options - }; +"use strict"; - if (typeof options.cwd !== 'string') { - throw new TypeError(`Expected \`cwd\` to be of type \`string\` but received type \`${typeof options.cwd}\``); +module.exports = x => { + if (typeof x !== 'string') { + throw new TypeError('Expected a string, got ' + typeof x); } - const globs = [].concat(input).map(x => pathType.isDirectorySync(getPath(x, options.cwd)) ? getGlob(x, options) : x); + // Catches EFBBBF (UTF-8 BOM) because the buffer-to-string + // conversion translates it to FEFF (UTF-16 BOM) + if (x.charCodeAt(0) === 0xFEFF) { + return x.slice(1); + } - return [].concat.apply([], globs); // eslint-disable-line prefer-spread + return x; }; /***/ }), -/* 245 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - -const {promisify} = __webpack_require__(29); -const fs = __webpack_require__(23); - -async function isType(fsStatType, statsMethodName, filePath) { - if (typeof filePath !== 'string') { - throw new TypeError(`Expected a string, got ${typeof filePath}`); - } +/* 123 */ +/***/ (function(module, exports) { - try { - const stats = await promisify(fs[fsStatType])(filePath); - return stats[statsMethodName](); - } catch (error) { - if (error.code === 'ENOENT') { - return false; - } +// Returns a wrapper function that returns a wrapped callback +// The wrapper function should do some stuff, and return a +// presumably different callback function. +// This makes sure that own properties are retained, so that +// decorations and such are not lost along the way. +module.exports = wrappy +function wrappy (fn, cb) { + if (fn && cb) return wrappy(fn)(cb) - throw error; - } -} + if (typeof fn !== 'function') + throw new TypeError('need wrapper function') -function isTypeSync(fsStatType, statsMethodName, filePath) { - if (typeof filePath !== 'string') { - throw new TypeError(`Expected a string, got ${typeof filePath}`); - } + Object.keys(fn).forEach(function (k) { + wrapper[k] = fn[k] + }) - try { - return fs[fsStatType](filePath)[statsMethodName](); - } catch (error) { - if (error.code === 'ENOENT') { - return false; - } + return wrapper - throw error; - } + function wrapper() { + var args = new Array(arguments.length) + for (var i = 0; i < args.length; i++) { + args[i] = arguments[i] + } + var ret = fn.apply(this, args) + var cb = args[args.length-1] + if (typeof ret === 'function' && ret !== cb) { + Object.keys(cb).forEach(function (k) { + ret[k] = cb[k] + }) + } + return ret + } } -exports.isFile = isType.bind(null, 'stat', 'isFile'); -exports.isDirectory = isType.bind(null, 'stat', 'isDirectory'); -exports.isSymlink = isType.bind(null, 'lstat', 'isSymbolicLink'); -exports.isFileSync = isTypeSync.bind(null, 'statSync', 'isFile'); -exports.isDirectorySync = isTypeSync.bind(null, 'statSync', 'isDirectory'); -exports.isSymlinkSync = isTypeSync.bind(null, 'lstatSync', 'isSymbolicLink'); - /***/ }), -/* 246 */ +/* 124 */, +/* 125 */, +/* 126 */, +/* 127 */, +/* 128 */, +/* 129 */, +/* 130 */, +/* 131 */ /***/ (function(module, exports, __webpack_require__) { -"use strict"; - -const {promisify} = __webpack_require__(29); -const fs = __webpack_require__(23); -const path = __webpack_require__(16); -const fastGlob = __webpack_require__(178); -const gitIgnore = __webpack_require__(247); -const slash = __webpack_require__(248); +// fallback for non-array-like ES3 and non-enumerable old V8 strings +var cof = __webpack_require__(47); +// eslint-disable-next-line no-prototype-builtins +module.exports = Object('z').propertyIsEnumerable(0) ? Object : function (it) { + return cof(it) == 'String' ? it.split('') : Object(it); +}; -const DEFAULT_IGNORE = [ - '**/node_modules/**', - '**/flow-typed/**', - '**/coverage/**', - '**/.git' -]; -const readFileP = promisify(fs.readFile); +/***/ }), +/* 132 */ +/***/ (function(module, exports, __webpack_require__) { -const mapGitIgnorePatternTo = base => ignore => { - if (ignore.startsWith('!')) { - return '!' + path.posix.join(base, ignore.slice(1)); - } +// 19.1.2.14 / 15.2.3.14 Object.keys(O) +var $keys = __webpack_require__(195); +var enumBugKeys = __webpack_require__(101); - return path.posix.join(base, ignore); +module.exports = Object.keys || function keys(O) { + return $keys(O, enumBugKeys); }; -const parseGitIgnore = (content, options) => { - const base = slash(path.relative(options.cwd, path.dirname(options.fileName))); - return content - .split(/\r?\n/) - .filter(Boolean) - .filter(line => !line.startsWith('#')) - .map(mapGitIgnorePatternTo(base)); -}; +/***/ }), +/* 133 */ +/***/ (function(module, exports, __webpack_require__) { -const reduceIgnore = files => { - return files.reduce((ignores, file) => { - ignores.add(parseGitIgnore(file.content, { - cwd: file.cwd, - fileName: file.filePath - })); - return ignores; - }, gitIgnore()); +// 7.1.13 ToObject(argument) +var defined = __webpack_require__(67); +module.exports = function (it) { + return Object(defined(it)); }; -const ensureAbsolutePathForCwd = (cwd, p) => { - if (path.isAbsolute(p)) { - if (p.startsWith(cwd)) { - return p; - } - throw new Error(`Path ${p} is not in cwd ${cwd}`); - } +/***/ }), +/* 134 */, +/* 135 */, +/* 136 */, +/* 137 */, +/* 138 */, +/* 139 */, +/* 140 */, +/* 141 */, +/* 142 */, +/* 143 */, +/* 144 */, +/* 145 */ +/***/ (function(module, exports) { - return path.join(cwd, p); -}; +module.exports = {"name":"yarn","installationMethod":"unknown","version":"1.10.0-0","license":"BSD-2-Clause","preferGlobal":true,"description":"📦🐈 Fast, reliable, and secure dependency management.","dependencies":{"@zkochan/cmd-shim":"^2.2.4","babel-runtime":"^6.26.0","bytes":"^3.0.0","camelcase":"^4.0.0","chalk":"^2.1.0","commander":"^2.9.0","death":"^1.0.0","debug":"^3.0.0","deep-equal":"^1.0.1","detect-indent":"^5.0.0","dnscache":"^1.0.1","glob":"^7.1.1","gunzip-maybe":"^1.4.0","hash-for-dep":"^1.2.3","imports-loader":"^0.8.0","ini":"^1.3.4","inquirer":"^3.0.1","invariant":"^2.2.0","is-builtin-module":"^2.0.0","is-ci":"^1.0.10","is-webpack-bundle":"^1.0.0","leven":"^2.0.0","loud-rejection":"^1.2.0","micromatch":"^2.3.11","mkdirp":"^0.5.1","node-emoji":"^1.6.1","normalize-url":"^2.0.0","npm-logical-tree":"^1.2.1","object-path":"^0.11.2","proper-lockfile":"^2.0.0","puka":"^1.0.0","read":"^1.0.7","request":"^2.87.0","request-capture-har":"^1.2.2","rimraf":"^2.5.0","semver":"^5.1.0","ssri":"^5.3.0","strip-ansi":"^4.0.0","strip-bom":"^3.0.0","tar-fs":"^1.16.0","tar-stream":"^1.6.1","uuid":"^3.0.1","v8-compile-cache":"^2.0.0","validate-npm-package-license":"^3.0.3","yn":"^2.0.0"},"devDependencies":{"babel-core":"^6.26.0","babel-eslint":"^7.2.3","babel-loader":"^6.2.5","babel-plugin-array-includes":"^2.0.3","babel-plugin-transform-builtin-extend":"^1.1.2","babel-plugin-transform-inline-imports-commonjs":"^1.0.0","babel-plugin-transform-runtime":"^6.4.3","babel-preset-env":"^1.6.0","babel-preset-flow":"^6.23.0","babel-preset-stage-0":"^6.0.0","babylon":"^6.5.0","commitizen":"^2.9.6","cz-conventional-changelog":"^2.0.0","eslint":"^4.3.0","eslint-config-fb-strict":"^22.0.0","eslint-plugin-babel":"^5.0.0","eslint-plugin-flowtype":"^2.35.0","eslint-plugin-jasmine":"^2.6.2","eslint-plugin-jest":"^21.0.0","eslint-plugin-jsx-a11y":"^6.0.2","eslint-plugin-prefer-object-spread":"^1.2.1","eslint-plugin-prettier":"^2.1.2","eslint-plugin-react":"^7.1.0","eslint-plugin-relay":"^0.0.24","eslint-plugin-yarn-internal":"file:scripts/eslint-rules","execa":"^0.10.0","flow-bin":"^0.66.0","git-release-notes":"^3.0.0","gulp":"^3.9.0","gulp-babel":"^7.0.0","gulp-if":"^2.0.1","gulp-newer":"^1.0.0","gulp-plumber":"^1.0.1","gulp-sourcemaps":"^2.2.0","gulp-util":"^3.0.7","gulp-watch":"^5.0.0","jest":"^22.4.4","jsinspect":"^0.12.6","minimatch":"^3.0.4","mock-stdin":"^0.3.0","prettier":"^1.5.2","temp":"^0.8.3","webpack":"^2.1.0-beta.25","yargs":"^6.3.0"},"resolutions":{"sshpk":"^1.14.2"},"engines":{"node":">=4.0.0"},"repository":"yarnpkg/yarn","bin":{"yarn":"./bin/yarn.js","yarnpkg":"./bin/yarn.js"},"scripts":{"build":"gulp build","build-bundle":"node ./scripts/build-webpack.js","build-chocolatey":"powershell ./scripts/build-chocolatey.ps1","build-deb":"./scripts/build-deb.sh","build-dist":"bash ./scripts/build-dist.sh","build-win-installer":"scripts\\build-windows-installer.bat","changelog":"git-release-notes $(git describe --tags --abbrev=0 $(git describe --tags --abbrev=0)^)..$(git describe --tags --abbrev=0) scripts/changelog.md","dupe-check":"yarn jsinspect ./src","lint":"eslint . && flow check","pkg-tests":"yarn --cwd packages/pkg-tests jest yarn.test.js","prettier":"eslint src __tests__ --fix","release-branch":"./scripts/release-branch.sh","test":"yarn lint && yarn test-only","test-only":"node --max_old_space_size=4096 node_modules/jest/bin/jest.js --verbose","test-only-debug":"node --inspect-brk --max_old_space_size=4096 node_modules/jest/bin/jest.js --runInBand --verbose","test-coverage":"node --max_old_space_size=4096 node_modules/jest/bin/jest.js --coverage --verbose","watch":"gulp watch","commit":"git-cz"},"jest":{"collectCoverageFrom":["src/**/*.js"],"testEnvironment":"node","modulePathIgnorePatterns":["__tests__/fixtures/","packages/pkg-tests/pkg-tests-fixtures","dist/"],"testPathIgnorePatterns":["__tests__/(fixtures|__mocks__)/","updates/","_(temp|mock|install|init|helpers).js$","packages/pkg-tests"]},"config":{"commitizen":{"path":"./node_modules/cz-conventional-changelog"}}} -const getIsIgnoredPredecate = (ignores, cwd) => { - return p => ignores.ignores(slash(path.relative(cwd, ensureAbsolutePathForCwd(cwd, p)))); -}; +/***/ }), +/* 146 */, +/* 147 */, +/* 148 */, +/* 149 */, +/* 150 */ +/***/ (function(module, exports, __webpack_require__) { -const getFile = async (file, cwd) => { - const filePath = path.join(cwd, file); - const content = await readFileP(filePath, 'utf8'); +"use strict"; - return { - cwd, - filePath, - content - }; -}; -const getFileSync = (file, cwd) => { - const filePath = path.join(cwd, file); - const content = fs.readFileSync(filePath, 'utf8'); +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.default = stringify; - return { - cwd, - filePath, - content - }; -}; +var _misc; -const normalizeOptions = ({ - ignore = [], - cwd = process.cwd() -} = {}) => { - return {ignore, cwd}; -}; +function _load_misc() { + return _misc = __webpack_require__(12); +} -module.exports = async options => { - options = normalizeOptions(options); +var _constants; - const paths = await fastGlob('**/.gitignore', { - ignore: DEFAULT_IGNORE.concat(options.ignore), - cwd: options.cwd - }); +function _load_constants() { + return _constants = __webpack_require__(6); +} - const files = await Promise.all(paths.map(file => getFile(file, options.cwd))); - const ignores = reduceIgnore(files); +var _package; - return getIsIgnoredPredecate(ignores, options.cwd); -}; +function _load_package() { + return _package = __webpack_require__(145); +} -module.exports.sync = options => { - options = normalizeOptions(options); +const NODE_VERSION = process.version; - const paths = fastGlob.sync('**/.gitignore', { - ignore: DEFAULT_IGNORE.concat(options.ignore), - cwd: options.cwd - }); +function shouldWrapKey(str) { + return str.indexOf('true') === 0 || str.indexOf('false') === 0 || /[:\s\n\\",\[\]]/g.test(str) || /^[0-9]/g.test(str) || !/^[a-zA-Z]/g.test(str); +} - const files = paths.map(file => getFileSync(file, options.cwd)); - const ignores = reduceIgnore(files); +function maybeWrap(str) { + if (typeof str === 'boolean' || typeof str === 'number' || shouldWrapKey(str)) { + return JSON.stringify(str); + } else { + return str; + } +} - return getIsIgnoredPredecate(ignores, options.cwd); +const priorities = { + name: 1, + version: 2, + uid: 3, + resolved: 4, + integrity: 5, + registry: 6, + dependencies: 7 }; - -/***/ }), -/* 247 */ -/***/ (function(module, exports) { - -// A simple implementation of make-array -function makeArray (subject) { - return Array.isArray(subject) - ? subject - : [subject] +function priorityThenAlphaSort(a, b) { + if (priorities[a] || priorities[b]) { + return (priorities[a] || 100) > (priorities[b] || 100) ? 1 : -1; + } else { + return (0, (_misc || _load_misc()).sortAlpha)(a, b); + } } -const REGEX_TEST_BLANK_LINE = /^\s+$/ -const REGEX_REPLACE_LEADING_EXCAPED_EXCLAMATION = /^\\!/ -const REGEX_REPLACE_LEADING_EXCAPED_HASH = /^\\#/ -const REGEX_SPLITALL_CRLF = /\r?\n/g -// /foo, -// ./foo, -// ../foo, -// . -// .. -const REGEX_TEST_INVALID_PATH = /^\.*\/|^\.+$/ - -const SLASH = '/' -const KEY_IGNORE = typeof Symbol !== 'undefined' - ? Symbol.for('node-ignore') - /* istanbul ignore next */ - : 'node-ignore' +function _stringify(obj, options) { + if (typeof obj !== 'object') { + throw new TypeError(); + } -const define = (object, key, value) => - Object.defineProperty(object, key, {value}) + const indent = options.indent; + const lines = []; -const REGEX_REGEXP_RANGE = /([0-z])-([0-z])/g + // Sorting order needs to be consistent between runs, we run native sort by name because there are no + // problems with it being unstable because there are no to keys the same + // However priorities can be duplicated and native sort can shuffle things from run to run + const keys = Object.keys(obj).sort(priorityThenAlphaSort); -// Sanitize the range of a regular expression -// The cases are complicated, see test cases for details -const sanitizeRange = range => range.replace( - REGEX_REGEXP_RANGE, - (match, from, to) => from.charCodeAt(0) <= to.charCodeAt(0) - ? match - // Invalid range (out of order) which is ok for gitignore rules but - // fatal for JavaScript regular expression, so eliminate it. - : '' -) + let addedKeys = []; -// > If the pattern ends with a slash, -// > it is removed for the purpose of the following description, -// > but it would only find a match with a directory. -// > In other words, foo/ will match a directory foo and paths underneath it, -// > but will not match a regular file or a symbolic link foo -// > (this is consistent with the way how pathspec works in general in Git). -// '`foo/`' will not match regular file '`foo`' or symbolic link '`foo`' -// -> ignore-rules will not deal with it, because it costs extra `fs.stat` call -// you could use option `mark: true` with `glob` + for (let i = 0; i < keys.length; i++) { + const key = keys[i]; + const val = obj[key]; + if (val == null || addedKeys.indexOf(key) >= 0) { + continue; + } -// '`foo/`' should not continue with the '`..`' -const DEFAULT_REPLACER_PREFIX = [ + const valKeys = [key]; - // > Trailing spaces are ignored unless they are quoted with backslash ("\") - [ - // (a\ ) -> (a ) - // (a ) -> (a) - // (a \ ) -> (a ) - /\\?\s+$/, - match => match.indexOf('\\') === 0 - ? ' ' - : '' - ], + // get all keys that have the same value equality, we only want this for objects + if (typeof val === 'object') { + for (let j = i + 1; j < keys.length; j++) { + const key = keys[j]; + if (val === obj[key]) { + valKeys.push(key); + } + } + } - // replace (\ ) with ' ' - [ - /\\\s/g, - () => ' ' - ], + const keyLine = valKeys.sort((_misc || _load_misc()).sortAlpha).map(maybeWrap).join(', '); - // Escape metacharacters - // which is written down by users but means special for regular expressions. + if (typeof val === 'string' || typeof val === 'boolean' || typeof val === 'number') { + lines.push(`${keyLine} ${maybeWrap(val)}`); + } else if (typeof val === 'object') { + lines.push(`${keyLine}:\n${_stringify(val, { indent: indent + ' ' })}` + (options.topLevel ? '\n' : '')); + } else { + throw new TypeError(); + } - // > There are 12 characters with special meanings: - // > - the backslash \, - // > - the caret ^, - // > - the dollar sign $, - // > - the period or dot ., - // > - the vertical bar or pipe symbol |, - // > - the question mark ?, - // > - the asterisk or star *, - // > - the plus sign +, - // > - the opening parenthesis (, - // > - the closing parenthesis ), - // > - and the opening square bracket [, - // > - the opening curly brace {, - // > These special characters are often called "metacharacters". - [ - /[\\^$.|*+(){]/g, - match => `\\${match}` - ], + addedKeys = addedKeys.concat(valKeys); + } - [ - // > [abc] matches any character inside the brackets - // > (in this case a, b, or c); - /\[([^\]/]*)($|\])/g, - (match, p1, p2) => p2 === ']' - ? `[${sanitizeRange(p1)}]` - : `\\${match}` - ], + return indent + lines.join(`\n${indent}`); +} - [ - // > a question mark (?) matches a single character - /(?!\\)\?/g, - () => '[^/]' - ], +function stringify(obj, noHeader, enableVersions) { + const val = _stringify(obj, { + indent: '', + topLevel: true + }); + if (noHeader) { + return val; + } - // leading slash - [ + const lines = []; + lines.push('# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.'); + lines.push(`# yarn lockfile v${(_constants || _load_constants()).LOCKFILE_VERSION}`); + if (enableVersions) { + lines.push(`# yarn v${(_package || _load_package()).version}`); + lines.push(`# node ${NODE_VERSION}`); + } + lines.push('\n'); + lines.push(val); - // > A leading slash matches the beginning of the pathname. - // > For example, "/*.c" matches "cat-file.c" but not "mozilla-sha1/sha1.c". - // A leading slash matches the beginning of the pathname - /^\//, - () => '^' - ], + return lines.join('\n'); +} - // replace special metacharacter slash after the leading slash - [ - /\//g, - () => '\\/' - ], +/***/ }), +/* 151 */, +/* 152 */, +/* 153 */, +/* 154 */, +/* 155 */, +/* 156 */, +/* 157 */, +/* 158 */, +/* 159 */, +/* 160 */, +/* 161 */, +/* 162 */, +/* 163 */, +/* 164 */ +/***/ (function(module, exports, __webpack_require__) { - [ - // > A leading "**" followed by a slash means match in all directories. - // > For example, "**/foo" matches file or directory "foo" anywhere, - // > the same as pattern "foo". - // > "**/foo/bar" matches file or directory "bar" anywhere that is directly - // > under directory "foo". - // Notice that the '*'s have been replaced as '\\*' - /^\^*\\\*\\\*\\\//, +"use strict"; - // '**/foo' <-> 'foo' - () => '^(?:.*\\/)?' - ] -] -const DEFAULT_REPLACER_SUFFIX = [ - // starting - [ - // there will be no leading '/' - // (which has been replaced by section "leading slash") - // If starts with '**', adding a '^' to the regular expression also works - /^(?=[^^])/, - function startingReplacer () { - return !/\/(?!$)/.test(this) - // > If the pattern does not contain a slash /, - // > Git treats it as a shell glob pattern - // Actually, if there is only a trailing slash, - // git also treats it as a shell glob pattern - ? '(?:^|\\/)' +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.fileDatesEqual = exports.copyFile = exports.unlink = undefined; - // > Otherwise, Git treats the pattern as a shell glob suitable for - // > consumption by fnmatch(3) - : '^' - } - ], +var _asyncToGenerator2; - // two globstars - [ - // Use lookahead assertions so that we could match more than one `'/**'` - /\\\/\\\*\\\*(?=\\\/|$)/g, +function _load_asyncToGenerator() { + return _asyncToGenerator2 = _interopRequireDefault(__webpack_require__(1)); +} - // Zero, one or several directories - // should not use '*', or it will be replaced by the next replacer +// We want to preserve file timestamps when copying a file, since yarn uses them to decide if a file has +// changed compared to the cache. +// There are some OS specific cases here: +// * On linux, fs.copyFile does not preserve timestamps, but does on OSX and Win. +// * On windows, you must open a file with write permissions to call `fs.futimes`. +// * On OSX you can open with read permissions and still call `fs.futimes`. +let fixTimes = (() => { + var _ref3 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (fd, dest, data) { + const doOpen = fd === undefined; + let openfd = fd ? fd : -1; - // Check if it is not the last `'/**'` - (_, index, str) => index + 6 < str.length + if (disableTimestampCorrection === undefined) { + // if timestamps match already, no correction is needed. + // the need to correct timestamps varies based on OS and node versions. + const destStat = yield lstat(dest); + disableTimestampCorrection = fileDatesEqual(destStat.mtime, data.mtime); + } - // case: /**/ - // > A slash followed by two consecutive asterisks then a slash matches - // > zero or more directories. - // > For example, "a/**/b" matches "a/b", "a/x/b", "a/x/y/b" and so on. - // '/**/' - ? '(?:\\/[^\\/]+)*' + if (disableTimestampCorrection) { + return; + } - // case: /** - // > A trailing `"/**"` matches everything inside. + if (doOpen) { + try { + openfd = yield open(dest, 'a', data.mode); + } catch (er) { + // file is likely read-only + try { + openfd = yield open(dest, 'r', data.mode); + } catch (err) { + // We can't even open this file for reading. + return; + } + } + } - // #21: everything inside but it should not include the current folder - : '\\/.+' - ], + try { + if (openfd) { + yield futimes(openfd, data.atime, data.mtime); + } + } catch (er) { + // If `futimes` throws an exception, we probably have a case of a read-only file on Windows. + // In this case we can just return. The incorrect timestamp will just cause that file to be recopied + // on subsequent installs, which will effect yarn performance but not break anything. + } finally { + if (doOpen && openfd) { + yield close(openfd); + } + } + }); - // intermediate wildcards - [ - // Never replace escaped '*' - // ignore rule '\*' will match the path '*' + return function fixTimes(_x7, _x8, _x9) { + return _ref3.apply(this, arguments); + }; +})(); - // 'abc.*/' -> go - // 'abc.*' -> skip this rule - /(^|[^\\]+)\\\*(?=.+)/g, +// Compare file timestamps. +// Some versions of Node on windows zero the milliseconds when utime is used. - // '*.js' matches '.js' - // '*.js' doesn't match 'abc' - (_, p1) => `${p1}[^\\/]*` - ], - // trailing wildcard - [ - /(\^|\\\/)?\\\*$/, - (_, p1) => { - const prefix = p1 - // '\^': - // '/*' does not match '' - // '/*' does not match everything +var _fs; - // '\\\/': - // 'abc/*' does not match 'abc/' - ? `${p1}[^/]+` +function _load_fs() { + return _fs = _interopRequireDefault(__webpack_require__(3)); +} - // 'a*' matches 'a' - // 'a*' matches 'aa' - : '[^/]*' +var _promise; - return `${prefix}(?=$|\\/$)` - } - ], +function _load_promise() { + return _promise = __webpack_require__(40); +} - [ - // unescape - /\\\\\\/g, - () => '\\' - ] -] +function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } -const POSITIVE_REPLACERS = [ - ...DEFAULT_REPLACER_PREFIX, +// This module serves as a wrapper for file operations that are inconsistant across node and OS versions. - // 'f' - // matches - // - /f(end) - // - /f/ - // - (start)f(end) - // - (start)f/ - // doesn't match - // - oof - // - foo - // pseudo: - // -> (^|/)f(/|$) +let disableTimestampCorrection = undefined; // OS dependent. will be detected on first file copy. - // ending - [ - // 'js' will not match 'js.' - // 'ab' will not match 'abc' - /(?:[^*/])$/, +const readFileBuffer = (0, (_promise || _load_promise()).promisify)((_fs || _load_fs()).default.readFile); +const close = (0, (_promise || _load_promise()).promisify)((_fs || _load_fs()).default.close); +const lstat = (0, (_promise || _load_promise()).promisify)((_fs || _load_fs()).default.lstat); +const open = (0, (_promise || _load_promise()).promisify)((_fs || _load_fs()).default.open); +const futimes = (0, (_promise || _load_promise()).promisify)((_fs || _load_fs()).default.futimes); - // 'js*' will not match 'a.js' - // 'js/' will not match 'a.js' - // 'js' will match 'a.js' and 'a.js/' - match => `${match}(?=$|\\/)` - ], +const write = (0, (_promise || _load_promise()).promisify)((_fs || _load_fs()).default.write); - ...DEFAULT_REPLACER_SUFFIX -] +const unlink = exports.unlink = (0, (_promise || _load_promise()).promisify)(__webpack_require__(233)); -const NEGATIVE_REPLACERS = [ - ...DEFAULT_REPLACER_PREFIX, +/** + * Unlinks the destination to force a recreation. This is needed on case-insensitive file systems + * to force the correct naming when the filename has changed only in character-casing. (Jest -> jest). + */ +const copyFile = exports.copyFile = (() => { + var _ref = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (data, cleanup) { + try { + yield unlink(data.dest); + yield copyFilePoly(data.src, data.dest, 0, data); + } finally { + if (cleanup) { + cleanup(); + } + } + }); - // #24, #38 - // The MISSING rule of [gitignore docs](https://git-scm.com/docs/gitignore) - // A negative pattern without a trailing wildcard should not - // re-include the things inside that directory. + return function copyFile(_x, _x2) { + return _ref.apply(this, arguments); + }; +})(); - // eg: - // ['node_modules/*', '!node_modules'] - // should ignore `node_modules/a.js` - [ - /(?:[^*])$/, - match => `${match}(?=$|\\/$)` - ], +// Node 8.5.0 introduced `fs.copyFile` which is much faster, so use that when available. +// Otherwise we fall back to reading and writing files as buffers. +const copyFilePoly = (src, dest, flags, data) => { + if ((_fs || _load_fs()).default.copyFile) { + return new Promise((resolve, reject) => (_fs || _load_fs()).default.copyFile(src, dest, flags, err => { + if (err) { + reject(err); + } else { + fixTimes(undefined, dest, data).then(() => resolve()).catch(ex => reject(ex)); + } + })); + } else { + return copyWithBuffer(src, dest, flags, data); + } +}; - ...DEFAULT_REPLACER_SUFFIX -] +const copyWithBuffer = (() => { + var _ref2 = (0, (_asyncToGenerator2 || _load_asyncToGenerator()).default)(function* (src, dest, flags, data) { + // Use open -> write -> futimes -> close sequence to avoid opening the file twice: + // one with writeFile and one with utimes + const fd = yield open(dest, 'w', data.mode); + try { + const buffer = yield readFileBuffer(src); + yield write(fd, buffer, 0, buffer.length); + yield fixTimes(fd, dest, data); + } finally { + yield close(fd); + } + }); -// A simple cache, because an ignore rule only has only one certain meaning -const regexCache = Object.create(null) + return function copyWithBuffer(_x3, _x4, _x5, _x6) { + return _ref2.apply(this, arguments); + }; +})();const fileDatesEqual = exports.fileDatesEqual = (a, b) => { + const aTime = a.getTime(); + const bTime = b.getTime(); -// @param {pattern} -const makeRegex = (pattern, negative, ignorecase) => { - const r = regexCache[pattern] - if (r) { - return r + if (process.platform !== 'win32') { + return aTime === bTime; } - const replacers = negative - ? NEGATIVE_REPLACERS - : POSITIVE_REPLACERS + // See https://github.com/nodejs/node/pull/12607 + // Submillisecond times from stat and utimes are truncated on Windows, + // causing a file with mtime 8.0079998 and 8.0081144 to become 8.007 and 8.008 + // and making it impossible to update these files to their correct timestamps. + if (Math.abs(aTime - bTime) <= 1) { + return true; + } - const source = replacers.reduce( - (prev, current) => prev.replace(current[0], current[1].bind(pattern)), - pattern - ) + const aTimeSec = Math.floor(aTime / 1000); + const bTimeSec = Math.floor(bTime / 1000); - return regexCache[pattern] = ignorecase - ? new RegExp(source, 'i') - : new RegExp(source) -} + // See https://github.com/nodejs/node/issues/2069 + // Some versions of Node on windows zero the milliseconds when utime is used + // So if any of the time has a milliseconds part of zero we suspect that the + // bug is present and compare only seconds. + if (aTime - aTimeSec * 1000 === 0 || bTime - bTimeSec * 1000 === 0) { + return aTimeSec === bTimeSec; + } -const isString = subject => typeof subject === 'string' + return aTime === bTime; +}; -// > A blank line matches no files, so it can serve as a separator for readability. -const checkPattern = pattern => pattern - && isString(pattern) - && !REGEX_TEST_BLANK_LINE.test(pattern) +/***/ }), +/* 165 */, +/* 166 */, +/* 167 */, +/* 168 */, +/* 169 */ +/***/ (function(module, exports, __webpack_require__) { - // > A line starting with # serves as a comment. - && pattern.indexOf('#') !== 0 +"use strict"; -const splitPattern = pattern => pattern.split(REGEX_SPLITALL_CRLF) -class IgnoreRule { - constructor ( - origin, - pattern, - negative, - regex - ) { - this.origin = origin - this.pattern = pattern - this.negative = negative - this.regex = regex +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.isFakeRoot = isFakeRoot; +exports.isRootUser = isRootUser; +function getUid() { + if (process.platform !== 'win32' && process.getuid) { + return process.getuid(); } + return null; } -const createRule = (pattern, ignorecase) => { - const origin = pattern - let negative = false +exports.default = isRootUser(getUid()) && !isFakeRoot(); +function isFakeRoot() { + return Boolean(process.env.FAKEROOTKEY); +} - // > An optional prefix "!" which negates the pattern; - if (pattern.indexOf('!') === 0) { - negative = true - pattern = pattern.substr(1) - } +function isRootUser(uid) { + return uid === 0; +} - pattern = pattern - // > Put a backslash ("\") in front of the first "!" for patterns that - // > begin with a literal "!", for example, `"\!important!.txt"`. - .replace(REGEX_REPLACE_LEADING_EXCAPED_EXCLAMATION, '!') - // > Put a backslash ("\") in front of the first hash for patterns that - // > begin with a hash. - .replace(REGEX_REPLACE_LEADING_EXCAPED_HASH, '#') +/***/ }), +/* 170 */, +/* 171 */ +/***/ (function(module, exports, __webpack_require__) { - const regex = makeRegex(pattern, negative, ignorecase) +"use strict"; - return new IgnoreRule( - origin, - pattern, - negative, - regex - ) -} -const throwError = (message, Ctor) => { - throw new Ctor(message) -} +Object.defineProperty(exports, "__esModule", { + value: true +}); +exports.getDataDir = getDataDir; +exports.getCacheDir = getCacheDir; +exports.getConfigDir = getConfigDir; +const path = __webpack_require__(0); +const userHome = __webpack_require__(45).default; -const checkPath = (path, originalPath, doThrow) => { - if (!isString(path)) { - return doThrow( - `path must be a string, but got \`${originalPath}\``, - TypeError - ) +const FALLBACK_CONFIG_DIR = path.join(userHome, '.config', 'yarn'); +const FALLBACK_CACHE_DIR = path.join(userHome, '.cache', 'yarn'); + +function getDataDir() { + if (process.platform === 'win32') { + const WIN32_APPDATA_DIR = getLocalAppDataDir(); + return WIN32_APPDATA_DIR == null ? FALLBACK_CONFIG_DIR : path.join(WIN32_APPDATA_DIR, 'Data'); + } else if (process.env.XDG_DATA_HOME) { + return path.join(process.env.XDG_DATA_HOME, 'yarn'); + } else { + // This could arguably be ~/Library/Application Support/Yarn on Macs, + // but that feels unintuitive for a cli tool + + // Instead, use our prior fallback. Some day this could be + // path.join(userHome, '.local', 'share', 'yarn') + // or return path.join(WIN32_APPDATA_DIR, 'Data') on win32 + return FALLBACK_CONFIG_DIR; } +} - // We don't know if we should ignore '', so throw - if (!path) { - return doThrow(`path must not be empty`, TypeError) +function getCacheDir() { + if (process.platform === 'win32') { + // process.env.TEMP also exists, but most apps put caches here + return path.join(getLocalAppDataDir() || path.join(userHome, 'AppData', 'Local', 'Yarn'), 'Cache'); + } else if (process.env.XDG_CACHE_HOME) { + return path.join(process.env.XDG_CACHE_HOME, 'yarn'); + } else if (process.platform === 'darwin') { + return path.join(userHome, 'Library', 'Caches', 'Yarn'); + } else { + return FALLBACK_CACHE_DIR; } +} - // Check if it is a relative path - if (checkPath.isNotRelative(path)) { - const r = '`path.relative()`d' - return doThrow( - `path should be a ${r} string, but got "${originalPath}"`, - RangeError - ) +function getConfigDir() { + if (process.platform === 'win32') { + // Use our prior fallback. Some day this could be + // return path.join(WIN32_APPDATA_DIR, 'Config') + const WIN32_APPDATA_DIR = getLocalAppDataDir(); + return WIN32_APPDATA_DIR == null ? FALLBACK_CONFIG_DIR : path.join(WIN32_APPDATA_DIR, 'Config'); + } else if (process.env.XDG_CONFIG_HOME) { + return path.join(process.env.XDG_CONFIG_HOME, 'yarn'); + } else { + return FALLBACK_CONFIG_DIR; } +} - return true +function getLocalAppDataDir() { + return process.env.LOCALAPPDATA ? path.join(process.env.LOCALAPPDATA, 'Yarn') : null; } -const isNotRelative = path => REGEX_TEST_INVALID_PATH.test(path) +/***/ }), +/* 172 */, +/* 173 */ +/***/ (function(module, exports, __webpack_require__) { -checkPath.isNotRelative = isNotRelative -checkPath.convert = p => p +module.exports = { "default": __webpack_require__(179), __esModule: true }; -class Ignore { - constructor ({ - ignorecase = true - } = {}) { - this._rules = [] - this._ignorecase = ignorecase - define(this, KEY_IGNORE, true) - this._initCache() - } +/***/ }), +/* 174 */ +/***/ (function(module, exports, __webpack_require__) { - _initCache () { - this._ignoreCache = Object.create(null) - this._testCache = Object.create(null) - } +"use strict"; - _addPattern (pattern) { - // #32 - if (pattern && pattern[KEY_IGNORE]) { - this._rules = this._rules.concat(pattern._rules) - this._added = true - return +module.exports = balanced; +function balanced(a, b, str) { + if (a instanceof RegExp) a = maybeMatch(a, str); + if (b instanceof RegExp) b = maybeMatch(b, str); + + var r = range(a, b, str); + + return r && { + start: r[0], + end: r[1], + pre: str.slice(0, r[0]), + body: str.slice(r[0] + a.length, r[1]), + post: str.slice(r[1] + b.length) + }; +} + +function maybeMatch(reg, str) { + var m = str.match(reg); + return m ? m[0] : null; +} + +balanced.range = range; +function range(a, b, str) { + var begs, beg, left, right, result; + var ai = str.indexOf(a); + var bi = str.indexOf(b, ai + 1); + var i = ai; + + if (ai >= 0 && bi > 0) { + begs = []; + left = str.length; + + while (i >= 0 && !result) { + if (i == ai) { + begs.push(i); + ai = str.indexOf(a, i + 1); + } else if (begs.length == 1) { + result = [ begs.pop(), bi ]; + } else { + beg = begs.pop(); + if (beg < left) { + left = beg; + right = bi; + } + + bi = str.indexOf(b, i + 1); + } + + i = ai < bi && ai >= 0 ? ai : bi; } - if (checkPattern(pattern)) { - const rule = createRule(pattern, this._ignorecase) - this._added = true - this._rules.push(rule) + if (begs.length) { + result = [ left, right ]; } } - // @param {Array | string | Ignore} pattern - add (pattern) { - this._added = false + return result; +} - makeArray( - isString(pattern) - ? splitPattern(pattern) - : pattern - ).forEach(this._addPattern, this) - // Some rules have just added to the ignore, - // making the behavior changed. - if (this._added) { - this._initCache() - } +/***/ }), +/* 175 */ +/***/ (function(module, exports, __webpack_require__) { - return this - } +var concatMap = __webpack_require__(178); +var balanced = __webpack_require__(174); - // legacy - addPattern (pattern) { - return this.add(pattern) - } +module.exports = expandTop; - // | ignored : unignored - // negative | 0:0 | 0:1 | 1:0 | 1:1 - // -------- | ------- | ------- | ------- | -------- - // 0 | TEST | TEST | SKIP | X - // 1 | TESTIF | SKIP | TEST | X +var escSlash = '\0SLASH'+Math.random()+'\0'; +var escOpen = '\0OPEN'+Math.random()+'\0'; +var escClose = '\0CLOSE'+Math.random()+'\0'; +var escComma = '\0COMMA'+Math.random()+'\0'; +var escPeriod = '\0PERIOD'+Math.random()+'\0'; - // - SKIP: always skip - // - TEST: always test - // - TESTIF: only test if checkUnignored - // - X: that never happen +function numeric(str) { + return parseInt(str, 10) == str + ? parseInt(str, 10) + : str.charCodeAt(0); +} - // @param {boolean} whether should check if the path is unignored, - // setting `checkUnignored` to `false` could reduce additional - // path matching. +function escapeBraces(str) { + return str.split('\\\\').join(escSlash) + .split('\\{').join(escOpen) + .split('\\}').join(escClose) + .split('\\,').join(escComma) + .split('\\.').join(escPeriod); +} - // @returns {TestResult} true if a file is ignored - _testOne (path, checkUnignored) { - let ignored = false - let unignored = false +function unescapeBraces(str) { + return str.split(escSlash).join('\\') + .split(escOpen).join('{') + .split(escClose).join('}') + .split(escComma).join(',') + .split(escPeriod).join('.'); +} - this._rules.forEach(rule => { - const {negative} = rule - if ( - unignored === negative && ignored !== unignored - || negative && !ignored && !unignored && !checkUnignored - ) { - return - } - const matched = rule.regex.test(path) +// Basically just str.split(","), but handling cases +// where we have nested braced sections, which should be +// treated as individual members, like {a,{b,c},d} +function parseCommaParts(str) { + if (!str) + return ['']; - if (matched) { - ignored = !negative - unignored = negative - } - }) + var parts = []; + var m = balanced('{', '}', str); - return { - ignored, - unignored - } + if (!m) + return str.split(','); + + var pre = m.pre; + var body = m.body; + var post = m.post; + var p = pre.split(','); + + p[p.length-1] += '{' + body + '}'; + var postParts = parseCommaParts(post); + if (post.length) { + p[p.length-1] += postParts.shift(); + p.push.apply(p, postParts); } - // @returns {TestResult} - _test (originalPath, cache, checkUnignored, slices) { - const path = originalPath - // Supports nullable path - && checkPath.convert(originalPath) + parts.push.apply(parts, p); - checkPath(path, originalPath, throwError) + return parts; +} - return this._t(path, cache, checkUnignored, slices) +function expandTop(str) { + if (!str) + return []; + + // I don't know why Bash 4.3 does this, but it does. + // Anything starting with {} will have the first two bytes preserved + // but *only* at the top level, so {},a}b will not expand to anything, + // but a{},b}c will be expanded to [a}c,abc]. + // One could argue that this is a bug in Bash, but since the goal of + // this module is to match Bash's rules, we escape a leading {} + if (str.substr(0, 2) === '{}') { + str = '\\{\\}' + str.substr(2); } - _t (path, cache, checkUnignored, slices) { - if (path in cache) { - return cache[path] - } + return expand(escapeBraces(str), true).map(unescapeBraces); +} - if (!slices) { - // path/to/a.js - // ['path', 'to', 'a.js'] - slices = path.split(SLASH) - } +function identity(e) { + return e; +} - slices.pop() +function embrace(str) { + return '{' + str + '}'; +} +function isPadded(el) { + return /^-?0\d/.test(el); +} - // If the path has no parent directory, just test it - if (!slices.length) { - return cache[path] = this._testOne(path, checkUnignored) - } +function lte(i, y) { + return i <= y; +} +function gte(i, y) { + return i >= y; +} - const parent = this._t( - slices.join(SLASH) + SLASH, - cache, - checkUnignored, - slices - ) +function expand(str, isTop) { + var expansions = []; - // If the path contains a parent directory, check the parent first - return cache[path] = parent.ignored - // > It is not possible to re-include a file if a parent directory of - // > that file is excluded. - ? parent - : this._testOne(path, checkUnignored) + var m = balanced('{', '}', str); + if (!m || /\$$/.test(m.pre)) return [str]; + + var isNumericSequence = /^-?\d+\.\.-?\d+(?:\.\.-?\d+)?$/.test(m.body); + var isAlphaSequence = /^[a-zA-Z]\.\.[a-zA-Z](?:\.\.-?\d+)?$/.test(m.body); + var isSequence = isNumericSequence || isAlphaSequence; + var isOptions = m.body.indexOf(',') >= 0; + if (!isSequence && !isOptions) { + // {a},b} + if (m.post.match(/,.*\}/)) { + str = m.pre + '{' + m.body + escClose + m.post; + return expand(str); + } + return [str]; } - ignores (path) { - return this._test(path, this._ignoreCache, false).ignored + var n; + if (isSequence) { + n = m.body.split(/\.\./); + } else { + n = parseCommaParts(m.body); + if (n.length === 1) { + // x{{a,b}}y ==> x{a}y x{b}y + n = expand(n[0], false).map(embrace); + if (n.length === 1) { + var post = m.post.length + ? expand(m.post, false) + : ['']; + return post.map(function(p) { + return m.pre + n[0] + p; + }); + } + } } - createFilter () { - return path => !this.ignores(path) + // at this point, n is the parts, and we know it's not a comma set + // with a single entry. + + // no need to expand pre, since it is guaranteed to be free of brace-sets + var pre = m.pre; + var post = m.post.length + ? expand(m.post, false) + : ['']; + + var N; + + if (isSequence) { + var x = numeric(n[0]); + var y = numeric(n[1]); + var width = Math.max(n[0].length, n[1].length) + var incr = n.length == 3 + ? Math.abs(numeric(n[2])) + : 1; + var test = lte; + var reverse = y < x; + if (reverse) { + incr *= -1; + test = gte; + } + var pad = n.some(isPadded); + + N = []; + + for (var i = x; test(i, y); i += incr) { + var c; + if (isAlphaSequence) { + c = String.fromCharCode(i); + if (c === '\\') + c = ''; + } else { + c = String(i); + if (pad) { + var need = width - c.length; + if (need > 0) { + var z = new Array(need + 1).join('0'); + if (i < 0) + c = '-' + z + c.slice(1); + else + c = z + c; + } + } + } + N.push(c); + } + } else { + N = concatMap(n, function(el) { return expand(el, false) }); } - filter (paths) { - return makeArray(paths).filter(this.createFilter()) + for (var j = 0; j < N.length; j++) { + for (var k = 0; k < post.length; k++) { + var expansion = pre + N[j] + post[k]; + if (!isTop || isSequence || expansion) + expansions.push(expansion); + } } - // @returns {TestResult} - test (path) { - return this._test(path, this._testCache, true) - } + return expansions; } -const factory = options => new Ignore(options) -const returnFalse = () => false -const isPathValid = path => - checkPath(path && checkPath.convert(path), path, returnFalse) +/***/ }), +/* 176 */ +/***/ (function(module, exports, __webpack_require__) { -factory.isPathValid = isPathValid +"use strict"; -// Fixes typescript -factory.default = factory -module.exports = factory +function preserveCamelCase(str) { + let isLastCharLower = false; + let isLastCharUpper = false; + let isLastLastCharUpper = false; -// Windows -// -------------------------------------------------------------- -/* istanbul ignore if */ -if ( - // Detect `process` so that it can run in browsers. - typeof process !== 'undefined' - && ( - process.env && process.env.IGNORE_TEST_WIN32 - || process.platform === 'win32' - ) -) { - /* eslint no-control-regex: "off" */ - const makePosix = str => /^\\\\\?\\/.test(str) - || /["<>|\u0000-\u001F]+/u.test(str) - ? str - : str.replace(/\\/g, '/') + for (let i = 0; i < str.length; i++) { + const c = str[i]; - checkPath.convert = makePosix + if (isLastCharLower && /[a-zA-Z]/.test(c) && c.toUpperCase() === c) { + str = str.substr(0, i) + '-' + str.substr(i); + isLastCharLower = false; + isLastLastCharUpper = isLastCharUpper; + isLastCharUpper = true; + i++; + } else if (isLastCharUpper && isLastLastCharUpper && /[a-zA-Z]/.test(c) && c.toLowerCase() === c) { + str = str.substr(0, i - 1) + '-' + str.substr(i - 1); + isLastLastCharUpper = isLastCharUpper; + isLastCharUpper = false; + isLastCharLower = true; + } else { + isLastCharLower = c.toLowerCase() === c; + isLastLastCharUpper = isLastCharUpper; + isLastCharUpper = c.toUpperCase() === c; + } + } - // 'C:\\foo' <- 'C:\\foo' has been converted to 'C:/' - // 'd:\\foo' - const REGIX_IS_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i - checkPath.isNotRelative = path => - REGIX_IS_WINDOWS_PATH_ABSOLUTE.test(path) - || isNotRelative(path) + return str; } +module.exports = function (str) { + if (arguments.length > 1) { + str = Array.from(arguments) + .map(x => x.trim()) + .filter(x => x.length) + .join('-'); + } else { + str = str.trim(); + } -/***/ }), -/* 248 */ -/***/ (function(module, exports, __webpack_require__) { + if (str.length === 0) { + return ''; + } -"use strict"; + if (str.length === 1) { + return str.toLowerCase(); + } -module.exports = path => { - const isExtendedLengthPath = /^\\\\\?\\/.test(path); - const hasNonAscii = /[^\u0000-\u0080]+/.test(path); // eslint-disable-line no-control-regex + if (/^[a-z0-9]+$/.test(str)) { + return str; + } - if (isExtendedLengthPath || hasNonAscii) { - return path; + const hasUpperCase = str !== str.toLowerCase(); + + if (hasUpperCase) { + str = preserveCamelCase(str); } - return path.replace(/\\/g, '/'); + return str + .replace(/^[_.\- ]+/, '') + .toLowerCase() + .replace(/[_.\- ]+(\w|$)/g, (m, p1) => p1.toUpperCase()); }; /***/ }), -/* 249 */ +/* 177 */, +/* 178 */ +/***/ (function(module, exports) { + +module.exports = function (xs, fn) { + var res = []; + for (var i = 0; i < xs.length; i++) { + var x = fn(xs[i], i); + if (isArray(x)) res.push.apply(res, x); + else res.push(x); + } + return res; +}; + +var isArray = Array.isArray || function (xs) { + return Object.prototype.toString.call(xs) === '[object Array]'; +}; + + +/***/ }), +/* 179 */ /***/ (function(module, exports, __webpack_require__) { -"use strict"; +__webpack_require__(205); +__webpack_require__(207); +__webpack_require__(210); +__webpack_require__(206); +__webpack_require__(208); +__webpack_require__(209); +module.exports = __webpack_require__(23).Promise; -const {Transform} = __webpack_require__(28); -class ObjectTransform extends Transform { - constructor() { - super({ - objectMode: true - }); - } -} +/***/ }), +/* 180 */ +/***/ (function(module, exports) { -class FilterStream extends ObjectTransform { - constructor(filter) { - super(); - this._filter = filter; - } +module.exports = function () { /* empty */ }; - _transform(data, encoding, callback) { - if (this._filter(data)) { - this.push(data); - } - callback(); - } -} +/***/ }), +/* 181 */ +/***/ (function(module, exports) { -class UniqueStream extends ObjectTransform { - constructor() { - super(); - this._pushed = new Set(); - } +module.exports = function (it, Constructor, name, forbiddenField) { + if (!(it instanceof Constructor) || (forbiddenField !== undefined && forbiddenField in it)) { + throw TypeError(name + ': incorrect invocation!'); + } return it; +}; - _transform(data, encoding, callback) { - if (!this._pushed.has(data)) { - this.push(data); - this._pushed.add(data); - } - callback(); - } -} +/***/ }), +/* 182 */ +/***/ (function(module, exports, __webpack_require__) { -module.exports = { - FilterStream, - UniqueStream +// false -> Array#indexOf +// true -> Array#includes +var toIObject = __webpack_require__(74); +var toLength = __webpack_require__(110); +var toAbsoluteIndex = __webpack_require__(200); +module.exports = function (IS_INCLUDES) { + return function ($this, el, fromIndex) { + var O = toIObject($this); + var length = toLength(O.length); + var index = toAbsoluteIndex(fromIndex, length); + var value; + // Array#includes uses SameValueZero equality algorithm + // eslint-disable-next-line no-self-compare + if (IS_INCLUDES && el != el) while (length > index) { + value = O[index++]; + // eslint-disable-next-line no-self-compare + if (value != value) return true; + // Array#indexOf ignores holes, Array#includes - not + } else for (;length > index; index++) if (IS_INCLUDES || index in O) { + if (O[index] === el) return IS_INCLUDES || index || 0; + } return !IS_INCLUDES && -1; + }; }; /***/ }), -/* 250 */ +/* 183 */ /***/ (function(module, exports, __webpack_require__) { -var fs = __webpack_require__(23) -var polyfills = __webpack_require__(251) -var legacy = __webpack_require__(252) -var clone = __webpack_require__(253) +var ctx = __webpack_require__(48); +var call = __webpack_require__(187); +var isArrayIter = __webpack_require__(186); +var anObject = __webpack_require__(27); +var toLength = __webpack_require__(110); +var getIterFn = __webpack_require__(203); +var BREAK = {}; +var RETURN = {}; +var exports = module.exports = function (iterable, entries, fn, that, ITERATOR) { + var iterFn = ITERATOR ? function () { return iterable; } : getIterFn(iterable); + var f = ctx(fn, that, entries ? 2 : 1); + var index = 0; + var length, step, iterator, result; + if (typeof iterFn != 'function') throw TypeError(iterable + ' is not iterable!'); + // fast case for arrays with default iterator + if (isArrayIter(iterFn)) for (length = toLength(iterable.length); length > index; index++) { + result = entries ? f(anObject(step = iterable[index])[0], step[1]) : f(iterable[index]); + if (result === BREAK || result === RETURN) return result; + } else for (iterator = iterFn.call(iterable); !(step = iterator.next()).done;) { + result = call(iterator, f, step.value, entries); + if (result === BREAK || result === RETURN) return result; + } +}; +exports.BREAK = BREAK; +exports.RETURN = RETURN; -var util = __webpack_require__(29) -/* istanbul ignore next - node 0.x polyfill */ -var gracefulQueue -var previousSymbol +/***/ }), +/* 184 */ +/***/ (function(module, exports, __webpack_require__) { -/* istanbul ignore else - node 0.x polyfill */ -if (typeof Symbol === 'function' && typeof Symbol.for === 'function') { - gracefulQueue = Symbol.for('graceful-fs.queue') - // This is used in testing by future versions - previousSymbol = Symbol.for('graceful-fs.previous') -} else { - gracefulQueue = '___graceful-fs.queue' - previousSymbol = '___graceful-fs.previous' -} +module.exports = !__webpack_require__(33) && !__webpack_require__(85)(function () { + return Object.defineProperty(__webpack_require__(68)('div'), 'a', { get: function () { return 7; } }).a != 7; +}); -function noop () {} -var debug = noop -if (util.debuglog) - debug = util.debuglog('gfs4') -else if (/\bgfs4\b/i.test(process.env.NODE_DEBUG || '')) - debug = function() { - var m = util.format.apply(util, arguments) - m = 'GFS4: ' + m.split(/\n/).join('\nGFS4: ') - console.error(m) - } +/***/ }), +/* 185 */ +/***/ (function(module, exports) { -// Once time initialization -if (!global[gracefulQueue]) { - // This queue can be shared by multiple loaded instances - var queue = [] - Object.defineProperty(global, gracefulQueue, { - get: function() { - return queue - } - }) +// fast apply, http://jsperf.lnkit.com/fast-apply/5 +module.exports = function (fn, args, that) { + var un = that === undefined; + switch (args.length) { + case 0: return un ? fn() + : fn.call(that); + case 1: return un ? fn(args[0]) + : fn.call(that, args[0]); + case 2: return un ? fn(args[0], args[1]) + : fn.call(that, args[0], args[1]); + case 3: return un ? fn(args[0], args[1], args[2]) + : fn.call(that, args[0], args[1], args[2]); + case 4: return un ? fn(args[0], args[1], args[2], args[3]) + : fn.call(that, args[0], args[1], args[2], args[3]); + } return fn.apply(that, args); +}; - // Patch fs.close/closeSync to shared queue version, because we need - // to retry() whenever a close happens *anywhere* in the program. - // This is essential when multiple graceful-fs instances are - // in play at the same time. - fs.close = (function (fs$close) { - function close (fd, cb) { - return fs$close.call(fs, fd, function (err) { - // This function uses the graceful-fs shared queue - if (!err) { - retry() - } - if (typeof cb === 'function') - cb.apply(this, arguments) - }) - } +/***/ }), +/* 186 */ +/***/ (function(module, exports, __webpack_require__) { - Object.defineProperty(close, previousSymbol, { - value: fs$close - }) - return close - })(fs.close) +// check on default Array iterator +var Iterators = __webpack_require__(35); +var ITERATOR = __webpack_require__(13)('iterator'); +var ArrayProto = Array.prototype; - fs.closeSync = (function (fs$closeSync) { - function closeSync (fd) { - // This function uses the graceful-fs shared queue - fs$closeSync.apply(fs, arguments) - retry() - } +module.exports = function (it) { + return it !== undefined && (Iterators.Array === it || ArrayProto[ITERATOR] === it); +}; - Object.defineProperty(closeSync, previousSymbol, { - value: fs$closeSync - }) - return closeSync - })(fs.closeSync) - if (/\bgfs4\b/i.test(process.env.NODE_DEBUG || '')) { - process.on('exit', function() { - debug(global[gracefulQueue]) - __webpack_require__(30).equal(global[gracefulQueue].length, 0) - }) +/***/ }), +/* 187 */ +/***/ (function(module, exports, __webpack_require__) { + +// call something on iterator step with safe closing on error +var anObject = __webpack_require__(27); +module.exports = function (iterator, fn, value, entries) { + try { + return entries ? fn(anObject(value)[0], value[1]) : fn(value); + // 7.4.6 IteratorClose(iterator, completion) + } catch (e) { + var ret = iterator['return']; + if (ret !== undefined) anObject(ret.call(iterator)); + throw e; } -} +}; -module.exports = patch(clone(fs)) -if (process.env.TEST_GRACEFUL_FS_GLOBAL_PATCH && !fs.__patched) { - module.exports = patch(fs) - fs.__patched = true; -} -function patch (fs) { - // Everything that references the open() function needs to be in here - polyfills(fs) - fs.gracefulify = patch +/***/ }), +/* 188 */ +/***/ (function(module, exports, __webpack_require__) { - fs.createReadStream = createReadStream - fs.createWriteStream = createWriteStream - var fs$readFile = fs.readFile - fs.readFile = readFile - function readFile (path, options, cb) { - if (typeof options === 'function') - cb = options, options = null +"use strict"; - return go$readFile(path, options, cb) +var create = __webpack_require__(192); +var descriptor = __webpack_require__(106); +var setToStringTag = __webpack_require__(71); +var IteratorPrototype = {}; - function go$readFile (path, options, cb) { - return fs$readFile(path, options, function (err) { - if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) - enqueue([go$readFile, [path, options, cb]]) - else { - if (typeof cb === 'function') - cb.apply(this, arguments) - retry() - } - }) - } - } +// 25.1.2.1.1 %IteratorPrototype%[@@iterator]() +__webpack_require__(31)(IteratorPrototype, __webpack_require__(13)('iterator'), function () { return this; }); - var fs$writeFile = fs.writeFile - fs.writeFile = writeFile - function writeFile (path, data, options, cb) { - if (typeof options === 'function') - cb = options, options = null +module.exports = function (Constructor, NAME, next) { + Constructor.prototype = create(IteratorPrototype, { next: descriptor(1, next) }); + setToStringTag(Constructor, NAME + ' Iterator'); +}; - return go$writeFile(path, data, options, cb) - function go$writeFile (path, data, options, cb) { - return fs$writeFile(path, data, options, function (err) { - if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) - enqueue([go$writeFile, [path, data, options, cb]]) - else { - if (typeof cb === 'function') - cb.apply(this, arguments) - retry() - } - }) - } - } +/***/ }), +/* 189 */ +/***/ (function(module, exports, __webpack_require__) { - var fs$appendFile = fs.appendFile - if (fs$appendFile) - fs.appendFile = appendFile - function appendFile (path, data, options, cb) { - if (typeof options === 'function') - cb = options, options = null +var ITERATOR = __webpack_require__(13)('iterator'); +var SAFE_CLOSING = false; - return go$appendFile(path, data, options, cb) +try { + var riter = [7][ITERATOR](); + riter['return'] = function () { SAFE_CLOSING = true; }; + // eslint-disable-next-line no-throw-literal + Array.from(riter, function () { throw 2; }); +} catch (e) { /* empty */ } + +module.exports = function (exec, skipClosing) { + if (!skipClosing && !SAFE_CLOSING) return false; + var safe = false; + try { + var arr = [7]; + var iter = arr[ITERATOR](); + iter.next = function () { return { done: safe = true }; }; + arr[ITERATOR] = function () { return iter; }; + exec(arr); + } catch (e) { /* empty */ } + return safe; +}; - function go$appendFile (path, data, options, cb) { - return fs$appendFile(path, data, options, function (err) { - if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) - enqueue([go$appendFile, [path, data, options, cb]]) - else { - if (typeof cb === 'function') - cb.apply(this, arguments) - retry() - } - }) - } - } - var fs$readdir = fs.readdir - fs.readdir = readdir - function readdir (path, options, cb) { - var args = [path] - if (typeof options !== 'function') { - args.push(options) - } else { - cb = options - } - args.push(go$readdir$cb) +/***/ }), +/* 190 */ +/***/ (function(module, exports) { - return go$readdir(args) +module.exports = function (done, value) { + return { value: value, done: !!done }; +}; - function go$readdir$cb (err, files) { - if (files && files.sort) - files.sort() - if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) - enqueue([go$readdir, [args]]) +/***/ }), +/* 191 */ +/***/ (function(module, exports, __webpack_require__) { - else { - if (typeof cb === 'function') - cb.apply(this, arguments) - retry() +var global = __webpack_require__(11); +var macrotask = __webpack_require__(109).set; +var Observer = global.MutationObserver || global.WebKitMutationObserver; +var process = global.process; +var Promise = global.Promise; +var isNode = __webpack_require__(47)(process) == 'process'; + +module.exports = function () { + var head, last, notify; + + var flush = function () { + var parent, fn; + if (isNode && (parent = process.domain)) parent.exit(); + while (head) { + fn = head.fn; + head = head.next; + try { + fn(); + } catch (e) { + if (head) notify(); + else last = undefined; + throw e; } - } - } + } last = undefined; + if (parent) parent.enter(); + }; - function go$readdir (args) { - return fs$readdir.apply(fs, args) + // Node.js + if (isNode) { + notify = function () { + process.nextTick(flush); + }; + // browsers with MutationObserver, except iOS Safari - https://github.com/zloirock/core-js/issues/339 + } else if (Observer && !(global.navigator && global.navigator.standalone)) { + var toggle = true; + var node = document.createTextNode(''); + new Observer(flush).observe(node, { characterData: true }); // eslint-disable-line no-new + notify = function () { + node.data = toggle = !toggle; + }; + // environments with maybe non-completely correct, but existent Promise + } else if (Promise && Promise.resolve) { + // Promise.resolve without an argument throws an error in LG WebOS 2 + var promise = Promise.resolve(undefined); + notify = function () { + promise.then(flush); + }; + // for other environments - macrotask based on: + // - setImmediate + // - MessageChannel + // - window.postMessag + // - onreadystatechange + // - setTimeout + } else { + notify = function () { + // strange IE + webpack dev server bug - use .call(global) + macrotask.call(global, flush); + }; } - if (process.version.substr(0, 4) === 'v0.8') { - var legStreams = legacy(fs) - ReadStream = legStreams.ReadStream - WriteStream = legStreams.WriteStream - } + return function (fn) { + var task = { fn: fn, next: undefined }; + if (last) last.next = task; + if (!head) { + head = task; + notify(); + } last = task; + }; +}; - var fs$ReadStream = fs.ReadStream - if (fs$ReadStream) { - ReadStream.prototype = Object.create(fs$ReadStream.prototype) - ReadStream.prototype.open = ReadStream$open - } - var fs$WriteStream = fs.WriteStream - if (fs$WriteStream) { - WriteStream.prototype = Object.create(fs$WriteStream.prototype) - WriteStream.prototype.open = WriteStream$open - } +/***/ }), +/* 192 */ +/***/ (function(module, exports, __webpack_require__) { - Object.defineProperty(fs, 'ReadStream', { - get: function () { - return ReadStream - }, - set: function (val) { - ReadStream = val - }, - enumerable: true, - configurable: true - }) - Object.defineProperty(fs, 'WriteStream', { - get: function () { - return WriteStream - }, - set: function (val) { - WriteStream = val - }, - enumerable: true, - configurable: true - }) +// 19.1.2.2 / 15.2.3.5 Object.create(O [, Properties]) +var anObject = __webpack_require__(27); +var dPs = __webpack_require__(193); +var enumBugKeys = __webpack_require__(101); +var IE_PROTO = __webpack_require__(72)('IE_PROTO'); +var Empty = function () { /* empty */ }; +var PROTOTYPE = 'prototype'; + +// Create object with fake `null` prototype: use iframe Object with cleared prototype +var createDict = function () { + // Thrash, waste and sodomy: IE GC bug + var iframe = __webpack_require__(68)('iframe'); + var i = enumBugKeys.length; + var lt = '<'; + var gt = '>'; + var iframeDocument; + iframe.style.display = 'none'; + __webpack_require__(102).appendChild(iframe); + iframe.src = 'javascript:'; // eslint-disable-line no-script-url + // createDict = iframe.contentWindow.Object; + // html.removeChild(iframe); + iframeDocument = iframe.contentWindow.document; + iframeDocument.open(); + iframeDocument.write(lt + 'script' + gt + 'document.F=Object' + lt + '/script' + gt); + iframeDocument.close(); + createDict = iframeDocument.F; + while (i--) delete createDict[PROTOTYPE][enumBugKeys[i]]; + return createDict(); +}; + +module.exports = Object.create || function create(O, Properties) { + var result; + if (O !== null) { + Empty[PROTOTYPE] = anObject(O); + result = new Empty(); + Empty[PROTOTYPE] = null; + // add "__proto__" for Object.getPrototypeOf polyfill + result[IE_PROTO] = O; + } else result = createDict(); + return Properties === undefined ? result : dPs(result, Properties); +}; - // legacy names - Object.defineProperty(fs, 'FileReadStream', { - get: function () { - return ReadStream - }, - set: function (val) { - ReadStream = val - }, - enumerable: true, - configurable: true - }) - Object.defineProperty(fs, 'FileWriteStream', { - get: function () { - return WriteStream - }, - set: function (val) { - WriteStream = val - }, - enumerable: true, - configurable: true - }) - function ReadStream (path, options) { - if (this instanceof ReadStream) - return fs$ReadStream.apply(this, arguments), this - else - return ReadStream.apply(Object.create(ReadStream.prototype), arguments) - } +/***/ }), +/* 193 */ +/***/ (function(module, exports, __webpack_require__) { - function ReadStream$open () { - var that = this - open(that.path, that.flags, that.mode, function (err, fd) { - if (err) { - if (that.autoClose) - that.destroy() +var dP = __webpack_require__(50); +var anObject = __webpack_require__(27); +var getKeys = __webpack_require__(132); - that.emit('error', err) - } else { - that.fd = fd - that.emit('open', fd) - that.read() - } - }) - } +module.exports = __webpack_require__(33) ? Object.defineProperties : function defineProperties(O, Properties) { + anObject(O); + var keys = getKeys(Properties); + var length = keys.length; + var i = 0; + var P; + while (length > i) dP.f(O, P = keys[i++], Properties[P]); + return O; +}; - function WriteStream (path, options) { - if (this instanceof WriteStream) - return fs$WriteStream.apply(this, arguments), this - else - return WriteStream.apply(Object.create(WriteStream.prototype), arguments) - } - function WriteStream$open () { - var that = this - open(that.path, that.flags, that.mode, function (err, fd) { - if (err) { - that.destroy() - that.emit('error', err) - } else { - that.fd = fd - that.emit('open', fd) - } - }) - } +/***/ }), +/* 194 */ +/***/ (function(module, exports, __webpack_require__) { - function createReadStream (path, options) { - return new fs.ReadStream(path, options) - } +// 19.1.2.9 / 15.2.3.2 Object.getPrototypeOf(O) +var has = __webpack_require__(49); +var toObject = __webpack_require__(133); +var IE_PROTO = __webpack_require__(72)('IE_PROTO'); +var ObjectProto = Object.prototype; - function createWriteStream (path, options) { - return new fs.WriteStream(path, options) - } +module.exports = Object.getPrototypeOf || function (O) { + O = toObject(O); + if (has(O, IE_PROTO)) return O[IE_PROTO]; + if (typeof O.constructor == 'function' && O instanceof O.constructor) { + return O.constructor.prototype; + } return O instanceof Object ? ObjectProto : null; +}; - var fs$open = fs.open - fs.open = open - function open (path, flags, mode, cb) { - if (typeof mode === 'function') - cb = mode, mode = null - return go$open(path, flags, mode, cb) +/***/ }), +/* 195 */ +/***/ (function(module, exports, __webpack_require__) { - function go$open (path, flags, mode, cb) { - return fs$open(path, flags, mode, function (err, fd) { - if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) - enqueue([go$open, [path, flags, mode, cb]]) - else { - if (typeof cb === 'function') - cb.apply(this, arguments) - retry() - } - }) - } +var has = __webpack_require__(49); +var toIObject = __webpack_require__(74); +var arrayIndexOf = __webpack_require__(182)(false); +var IE_PROTO = __webpack_require__(72)('IE_PROTO'); + +module.exports = function (object, names) { + var O = toIObject(object); + var i = 0; + var result = []; + var key; + for (key in O) if (key != IE_PROTO) has(O, key) && result.push(key); + // Don't enum bug & hidden keys + while (names.length > i) if (has(O, key = names[i++])) { + ~arrayIndexOf(result, key) || result.push(key); } + return result; +}; + + +/***/ }), +/* 196 */ +/***/ (function(module, exports, __webpack_require__) { + +var hide = __webpack_require__(31); +module.exports = function (target, src, safe) { + for (var key in src) { + if (safe && target[key]) target[key] = src[key]; + else hide(target, key, src[key]); + } return target; +}; + + +/***/ }), +/* 197 */ +/***/ (function(module, exports, __webpack_require__) { + +module.exports = __webpack_require__(31); + + +/***/ }), +/* 198 */ +/***/ (function(module, exports, __webpack_require__) { - return fs -} +"use strict"; -function enqueue (elem) { - debug('ENQUEUE', elem[0].name, elem[1]) - global[gracefulQueue].push(elem) -} +var global = __webpack_require__(11); +var core = __webpack_require__(23); +var dP = __webpack_require__(50); +var DESCRIPTORS = __webpack_require__(33); +var SPECIES = __webpack_require__(13)('species'); -function retry () { - var elem = global[gracefulQueue].shift() - if (elem) { - debug('RETRY', elem[0].name, elem[1]) - elem[0].apply(null, elem[1]) - } -} +module.exports = function (KEY) { + var C = typeof core[KEY] == 'function' ? core[KEY] : global[KEY]; + if (DESCRIPTORS && C && !C[SPECIES]) dP.f(C, SPECIES, { + configurable: true, + get: function () { return this; } + }); +}; /***/ }), -/* 251 */ +/* 199 */ /***/ (function(module, exports, __webpack_require__) { -var constants = __webpack_require__(26) +var toInteger = __webpack_require__(73); +var defined = __webpack_require__(67); +// true -> String#at +// false -> String#codePointAt +module.exports = function (TO_STRING) { + return function (that, pos) { + var s = String(defined(that)); + var i = toInteger(pos); + var l = s.length; + var a, b; + if (i < 0 || i >= l) return TO_STRING ? '' : undefined; + a = s.charCodeAt(i); + return a < 0xd800 || a > 0xdbff || i + 1 === l || (b = s.charCodeAt(i + 1)) < 0xdc00 || b > 0xdfff + ? TO_STRING ? s.charAt(i) : a + : TO_STRING ? s.slice(i, i + 2) : (a - 0xd800 << 10) + (b - 0xdc00) + 0x10000; + }; +}; -var origCwd = process.cwd -var cwd = null -var platform = process.env.GRACEFUL_FS_PLATFORM || process.platform +/***/ }), +/* 200 */ +/***/ (function(module, exports, __webpack_require__) { -process.cwd = function() { - if (!cwd) - cwd = origCwd.call(process) - return cwd -} -try { - process.cwd() -} catch (er) {} +var toInteger = __webpack_require__(73); +var max = Math.max; +var min = Math.min; +module.exports = function (index, length) { + index = toInteger(index); + return index < 0 ? max(index + length, 0) : min(index, length); +}; -var chdir = process.chdir -process.chdir = function(d) { - cwd = null - chdir.call(process, d) -} -module.exports = patch +/***/ }), +/* 201 */ +/***/ (function(module, exports, __webpack_require__) { -function patch (fs) { - // (re-)implement some things that are known busted or missing. +// 7.1.1 ToPrimitive(input [, PreferredType]) +var isObject = __webpack_require__(34); +// instead of the ES6 spec version, we didn't implement @@toPrimitive case +// and the second argument - flag - preferred type is a string +module.exports = function (it, S) { + if (!isObject(it)) return it; + var fn, val; + if (S && typeof (fn = it.toString) == 'function' && !isObject(val = fn.call(it))) return val; + if (typeof (fn = it.valueOf) == 'function' && !isObject(val = fn.call(it))) return val; + if (!S && typeof (fn = it.toString) == 'function' && !isObject(val = fn.call(it))) return val; + throw TypeError("Can't convert object to primitive value"); +}; - // lchmod, broken prior to 0.6.2 - // back-port the fix here. - if (constants.hasOwnProperty('O_SYMLINK') && - process.version.match(/^v0\.6\.[0-2]|^v0\.5\./)) { - patchLchmod(fs) - } - // lutimes implementation, or no-op - if (!fs.lutimes) { - patchLutimes(fs) - } +/***/ }), +/* 202 */ +/***/ (function(module, exports, __webpack_require__) { - // https://github.com/isaacs/node-graceful-fs/issues/4 - // Chown should not fail on einval or eperm if non-root. - // It should not fail on enosys ever, as this just indicates - // that a fs doesn't support the intended operation. +var global = __webpack_require__(11); +var navigator = global.navigator; - fs.chown = chownFix(fs.chown) - fs.fchown = chownFix(fs.fchown) - fs.lchown = chownFix(fs.lchown) +module.exports = navigator && navigator.userAgent || ''; - fs.chmod = chmodFix(fs.chmod) - fs.fchmod = chmodFix(fs.fchmod) - fs.lchmod = chmodFix(fs.lchmod) - fs.chownSync = chownFixSync(fs.chownSync) - fs.fchownSync = chownFixSync(fs.fchownSync) - fs.lchownSync = chownFixSync(fs.lchownSync) +/***/ }), +/* 203 */ +/***/ (function(module, exports, __webpack_require__) { - fs.chmodSync = chmodFixSync(fs.chmodSync) - fs.fchmodSync = chmodFixSync(fs.fchmodSync) - fs.lchmodSync = chmodFixSync(fs.lchmodSync) +var classof = __webpack_require__(100); +var ITERATOR = __webpack_require__(13)('iterator'); +var Iterators = __webpack_require__(35); +module.exports = __webpack_require__(23).getIteratorMethod = function (it) { + if (it != undefined) return it[ITERATOR] + || it['@@iterator'] + || Iterators[classof(it)]; +}; - fs.stat = statFix(fs.stat) - fs.fstat = statFix(fs.fstat) - fs.lstat = statFix(fs.lstat) - fs.statSync = statFixSync(fs.statSync) - fs.fstatSync = statFixSync(fs.fstatSync) - fs.lstatSync = statFixSync(fs.lstatSync) +/***/ }), +/* 204 */ +/***/ (function(module, exports, __webpack_require__) { - // if lchmod/lchown do not exist, then make them no-ops - if (!fs.lchmod) { - fs.lchmod = function (path, mode, cb) { - if (cb) process.nextTick(cb) - } - fs.lchmodSync = function () {} - } - if (!fs.lchown) { - fs.lchown = function (path, uid, gid, cb) { - if (cb) process.nextTick(cb) - } - fs.lchownSync = function () {} - } +"use strict"; - // on Windows, A/V software can lock the directory, causing this - // to fail with an EACCES or EPERM if the directory contains newly - // created files. Try again on failure, for up to 60 seconds. +var addToUnscopables = __webpack_require__(180); +var step = __webpack_require__(190); +var Iterators = __webpack_require__(35); +var toIObject = __webpack_require__(74); - // Set the timeout this long because some Windows Anti-Virus, such as Parity - // bit9, may lock files for up to a minute, causing npm package install - // failures. Also, take care to yield the scheduler. Windows scheduling gives - // CPU to a busy looping process, which can cause the program causing the lock - // contention to be starved of CPU by node, so the contention doesn't resolve. - if (platform === "win32") { - fs.rename = (function (fs$rename) { return function (from, to, cb) { - var start = Date.now() - var backoff = 0; - fs$rename(from, to, function CB (er) { - if (er - && (er.code === "EACCES" || er.code === "EPERM") - && Date.now() - start < 60000) { - setTimeout(function() { - fs.stat(to, function (stater, st) { - if (stater && stater.code === "ENOENT") - fs$rename(from, to, CB); - else - cb(er) - }) - }, backoff) - if (backoff < 100) - backoff += 10; - return; - } - if (cb) cb(er) - }) - }})(fs.rename) +// 22.1.3.4 Array.prototype.entries() +// 22.1.3.13 Array.prototype.keys() +// 22.1.3.29 Array.prototype.values() +// 22.1.3.30 Array.prototype[@@iterator]() +module.exports = __webpack_require__(103)(Array, 'Array', function (iterated, kind) { + this._t = toIObject(iterated); // target + this._i = 0; // next index + this._k = kind; // kind +// 22.1.5.2.1 %ArrayIteratorPrototype%.next() +}, function () { + var O = this._t; + var kind = this._k; + var index = this._i++; + if (!O || index >= O.length) { + this._t = undefined; + return step(1); } + if (kind == 'keys') return step(0, index); + if (kind == 'values') return step(0, O[index]); + return step(0, [index, O[index]]); +}, 'values'); - // if read() returns EAGAIN, then just try it again. - fs.read = (function (fs$read) { - function read (fd, buffer, offset, length, position, callback_) { - var callback - if (callback_ && typeof callback_ === 'function') { - var eagCounter = 0 - callback = function (er, _, __) { - if (er && er.code === 'EAGAIN' && eagCounter < 10) { - eagCounter ++ - return fs$read.call(fs, fd, buffer, offset, length, position, callback) - } - callback_.apply(this, arguments) - } - } - return fs$read.call(fs, fd, buffer, offset, length, position, callback) - } +// argumentsList[@@iterator] is %ArrayProto_values% (9.4.4.6, 9.4.4.7) +Iterators.Arguments = Iterators.Array; - // This ensures `util.promisify` works as it does for native `fs.read`. - read.__proto__ = fs$read - return read - })(fs.read) +addToUnscopables('keys'); +addToUnscopables('values'); +addToUnscopables('entries'); - fs.readSync = (function (fs$readSync) { return function (fd, buffer, offset, length, position) { - var eagCounter = 0 - while (true) { - try { - return fs$readSync.call(fs, fd, buffer, offset, length, position) - } catch (er) { - if (er.code === 'EAGAIN' && eagCounter < 10) { - eagCounter ++ - continue - } - throw er - } - } - }})(fs.readSync) - function patchLchmod (fs) { - fs.lchmod = function (path, mode, callback) { - fs.open( path - , constants.O_WRONLY | constants.O_SYMLINK - , mode - , function (err, fd) { - if (err) { - if (callback) callback(err) - return - } - // prefer to return the chmod error, if one occurs, - // but still try to close, and report closing errors if they occur. - fs.fchmod(fd, mode, function (err) { - fs.close(fd, function(err2) { - if (callback) callback(err || err2) - }) - }) - }) - } +/***/ }), +/* 205 */ +/***/ (function(module, exports) { - fs.lchmodSync = function (path, mode) { - var fd = fs.openSync(path, constants.O_WRONLY | constants.O_SYMLINK, mode) - // prefer to return the chmod error, if one occurs, - // but still try to close, and report closing errors if they occur. - var threw = true - var ret + +/***/ }), +/* 206 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +var LIBRARY = __webpack_require__(69); +var global = __webpack_require__(11); +var ctx = __webpack_require__(48); +var classof = __webpack_require__(100); +var $export = __webpack_require__(41); +var isObject = __webpack_require__(34); +var aFunction = __webpack_require__(46); +var anInstance = __webpack_require__(181); +var forOf = __webpack_require__(183); +var speciesConstructor = __webpack_require__(108); +var task = __webpack_require__(109).set; +var microtask = __webpack_require__(191)(); +var newPromiseCapabilityModule = __webpack_require__(70); +var perform = __webpack_require__(104); +var userAgent = __webpack_require__(202); +var promiseResolve = __webpack_require__(105); +var PROMISE = 'Promise'; +var TypeError = global.TypeError; +var process = global.process; +var versions = process && process.versions; +var v8 = versions && versions.v8 || ''; +var $Promise = global[PROMISE]; +var isNode = classof(process) == 'process'; +var empty = function () { /* empty */ }; +var Internal, newGenericPromiseCapability, OwnPromiseCapability, Wrapper; +var newPromiseCapability = newGenericPromiseCapability = newPromiseCapabilityModule.f; + +var USE_NATIVE = !!function () { + try { + // correct subclassing with @@species support + var promise = $Promise.resolve(1); + var FakePromise = (promise.constructor = {})[__webpack_require__(13)('species')] = function (exec) { + exec(empty, empty); + }; + // unhandled rejections tracking support, NodeJS Promise without it fails @@species test + return (isNode || typeof PromiseRejectionEvent == 'function') + && promise.then(empty) instanceof FakePromise + // v8 6.6 (Node 10 and Chrome 66) have a bug with resolving custom thenables + // https://bugs.chromium.org/p/chromium/issues/detail?id=830565 + // we can't detect it synchronously, so just check versions + && v8.indexOf('6.6') !== 0 + && userAgent.indexOf('Chrome/66') === -1; + } catch (e) { /* empty */ } +}(); + +// helpers +var isThenable = function (it) { + var then; + return isObject(it) && typeof (then = it.then) == 'function' ? then : false; +}; +var notify = function (promise, isReject) { + if (promise._n) return; + promise._n = true; + var chain = promise._c; + microtask(function () { + var value = promise._v; + var ok = promise._s == 1; + var i = 0; + var run = function (reaction) { + var handler = ok ? reaction.ok : reaction.fail; + var resolve = reaction.resolve; + var reject = reaction.reject; + var domain = reaction.domain; + var result, then, exited; try { - ret = fs.fchmodSync(fd, mode) - threw = false - } finally { - if (threw) { - try { - fs.closeSync(fd) - } catch (er) {} - } else { - fs.closeSync(fd) + if (handler) { + if (!ok) { + if (promise._h == 2) onHandleUnhandled(promise); + promise._h = 1; + } + if (handler === true) result = value; + else { + if (domain) domain.enter(); + result = handler(value); // may throw + if (domain) { + domain.exit(); + exited = true; + } + } + if (result === reaction.promise) { + reject(TypeError('Promise-chain cycle')); + } else if (then = isThenable(result)) { + then.call(result, resolve, reject); + } else resolve(result); + } else reject(value); + } catch (e) { + if (domain && !exited) domain.exit(); + reject(e); + } + }; + while (chain.length > i) run(chain[i++]); // variable length - can't use forEach + promise._c = []; + promise._n = false; + if (isReject && !promise._h) onUnhandled(promise); + }); +}; +var onUnhandled = function (promise) { + task.call(global, function () { + var value = promise._v; + var unhandled = isUnhandled(promise); + var result, handler, console; + if (unhandled) { + result = perform(function () { + if (isNode) { + process.emit('unhandledRejection', value, promise); + } else if (handler = global.onunhandledrejection) { + handler({ promise: promise, reason: value }); + } else if ((console = global.console) && console.error) { + console.error('Unhandled promise rejection', value); } - } - return ret + }); + // Browsers should not trigger `rejectionHandled` event if it was handled here, NodeJS - should + promise._h = isNode || isUnhandled(promise) ? 2 : 1; + } promise._a = undefined; + if (unhandled && result.e) throw result.v; + }); +}; +var isUnhandled = function (promise) { + return promise._h !== 1 && (promise._a || promise._c).length === 0; +}; +var onHandleUnhandled = function (promise) { + task.call(global, function () { + var handler; + if (isNode) { + process.emit('rejectionHandled', promise); + } else if (handler = global.onrejectionhandled) { + handler({ promise: promise, reason: promise._v }); } - } - - function patchLutimes (fs) { - if (constants.hasOwnProperty("O_SYMLINK")) { - fs.lutimes = function (path, at, mt, cb) { - fs.open(path, constants.O_SYMLINK, function (er, fd) { - if (er) { - if (cb) cb(er) - return - } - fs.futimes(fd, at, mt, function (er) { - fs.close(fd, function (er2) { - if (cb) cb(er || er2) - }) - }) - }) - } - - fs.lutimesSync = function (path, at, mt) { - var fd = fs.openSync(path, constants.O_SYMLINK) - var ret - var threw = true + }); +}; +var $reject = function (value) { + var promise = this; + if (promise._d) return; + promise._d = true; + promise = promise._w || promise; // unwrap + promise._v = value; + promise._s = 2; + if (!promise._a) promise._a = promise._c.slice(); + notify(promise, true); +}; +var $resolve = function (value) { + var promise = this; + var then; + if (promise._d) return; + promise._d = true; + promise = promise._w || promise; // unwrap + try { + if (promise === value) throw TypeError("Promise can't be resolved itself"); + if (then = isThenable(value)) { + microtask(function () { + var wrapper = { _w: promise, _d: false }; // wrap try { - ret = fs.futimesSync(fd, at, mt) - threw = false - } finally { - if (threw) { - try { - fs.closeSync(fd) - } catch (er) {} - } else { - fs.closeSync(fd) - } + then.call(value, ctx($resolve, wrapper, 1), ctx($reject, wrapper, 1)); + } catch (e) { + $reject.call(wrapper, e); } - return ret - } - + }); } else { - fs.lutimes = function (_a, _b, _c, cb) { if (cb) process.nextTick(cb) } - fs.lutimesSync = function () {} + promise._v = value; + promise._s = 1; + notify(promise, false); } + } catch (e) { + $reject.call({ _w: promise, _d: false }, e); // wrap } +}; - function chmodFix (orig) { - if (!orig) return orig - return function (target, mode, cb) { - return orig.call(fs, target, mode, function (er) { - if (chownErOk(er)) er = null - if (cb) cb.apply(this, arguments) - }) +// constructor polyfill +if (!USE_NATIVE) { + // 25.4.3.1 Promise(executor) + $Promise = function Promise(executor) { + anInstance(this, $Promise, PROMISE, '_h'); + aFunction(executor); + Internal.call(this); + try { + executor(ctx($resolve, this, 1), ctx($reject, this, 1)); + } catch (err) { + $reject.call(this, err); } - } - - function chmodFixSync (orig) { - if (!orig) return orig - return function (target, mode) { - try { - return orig.call(fs, target, mode) - } catch (er) { - if (!chownErOk(er)) throw er - } + }; + // eslint-disable-next-line no-unused-vars + Internal = function Promise(executor) { + this._c = []; // <- awaiting reactions + this._a = undefined; // <- checked in isUnhandled reactions + this._s = 0; // <- state + this._d = false; // <- done + this._v = undefined; // <- value + this._h = 0; // <- rejection state, 0 - default, 1 - handled, 2 - unhandled + this._n = false; // <- notify + }; + Internal.prototype = __webpack_require__(196)($Promise.prototype, { + // 25.4.5.3 Promise.prototype.then(onFulfilled, onRejected) + then: function then(onFulfilled, onRejected) { + var reaction = newPromiseCapability(speciesConstructor(this, $Promise)); + reaction.ok = typeof onFulfilled == 'function' ? onFulfilled : true; + reaction.fail = typeof onRejected == 'function' && onRejected; + reaction.domain = isNode ? process.domain : undefined; + this._c.push(reaction); + if (this._a) this._a.push(reaction); + if (this._s) notify(this, false); + return reaction.promise; + }, + // 25.4.5.1 Promise.prototype.catch(onRejected) + 'catch': function (onRejected) { + return this.then(undefined, onRejected); } - } + }); + OwnPromiseCapability = function () { + var promise = new Internal(); + this.promise = promise; + this.resolve = ctx($resolve, promise, 1); + this.reject = ctx($reject, promise, 1); + }; + newPromiseCapabilityModule.f = newPromiseCapability = function (C) { + return C === $Promise || C === Wrapper + ? new OwnPromiseCapability(C) + : newGenericPromiseCapability(C); + }; +} +$export($export.G + $export.W + $export.F * !USE_NATIVE, { Promise: $Promise }); +__webpack_require__(71)($Promise, PROMISE); +__webpack_require__(198)(PROMISE); +Wrapper = __webpack_require__(23)[PROMISE]; - function chownFix (orig) { - if (!orig) return orig - return function (target, uid, gid, cb) { - return orig.call(fs, target, uid, gid, function (er) { - if (chownErOk(er)) er = null - if (cb) cb.apply(this, arguments) - }) - } +// statics +$export($export.S + $export.F * !USE_NATIVE, PROMISE, { + // 25.4.4.5 Promise.reject(r) + reject: function reject(r) { + var capability = newPromiseCapability(this); + var $$reject = capability.reject; + $$reject(r); + return capability.promise; } - - function chownFixSync (orig) { - if (!orig) return orig - return function (target, uid, gid) { - try { - return orig.call(fs, target, uid, gid) - } catch (er) { - if (!chownErOk(er)) throw er - } - } +}); +$export($export.S + $export.F * (LIBRARY || !USE_NATIVE), PROMISE, { + // 25.4.4.6 Promise.resolve(x) + resolve: function resolve(x) { + return promiseResolve(LIBRARY && this === Wrapper ? $Promise : this, x); } - - function statFix (orig) { - if (!orig) return orig - // Older versions of Node erroneously returned signed integers for - // uid + gid. - return function (target, options, cb) { - if (typeof options === 'function') { - cb = options - options = null - } - function callback (er, stats) { - if (stats) { - if (stats.uid < 0) stats.uid += 0x100000000 - if (stats.gid < 0) stats.gid += 0x100000000 - } - if (cb) cb.apply(this, arguments) - } - return options ? orig.call(fs, target, options, callback) - : orig.call(fs, target, callback) - } +}); +$export($export.S + $export.F * !(USE_NATIVE && __webpack_require__(189)(function (iter) { + $Promise.all(iter)['catch'](empty); +})), PROMISE, { + // 25.4.4.1 Promise.all(iterable) + all: function all(iterable) { + var C = this; + var capability = newPromiseCapability(C); + var resolve = capability.resolve; + var reject = capability.reject; + var result = perform(function () { + var values = []; + var index = 0; + var remaining = 1; + forOf(iterable, false, function (promise) { + var $index = index++; + var alreadyCalled = false; + values.push(undefined); + remaining++; + C.resolve(promise).then(function (value) { + if (alreadyCalled) return; + alreadyCalled = true; + values[$index] = value; + --remaining || resolve(values); + }, reject); + }); + --remaining || resolve(values); + }); + if (result.e) reject(result.v); + return capability.promise; + }, + // 25.4.4.4 Promise.race(iterable) + race: function race(iterable) { + var C = this; + var capability = newPromiseCapability(C); + var reject = capability.reject; + var result = perform(function () { + forOf(iterable, false, function (promise) { + C.resolve(promise).then(capability.resolve, reject); + }); + }); + if (result.e) reject(result.v); + return capability.promise; } +}); - function statFixSync (orig) { - if (!orig) return orig - // Older versions of Node erroneously returned signed integers for - // uid + gid. - return function (target, options) { - var stats = options ? orig.call(fs, target, options) - : orig.call(fs, target) - if (stats.uid < 0) stats.uid += 0x100000000 - if (stats.gid < 0) stats.gid += 0x100000000 - return stats; - } - } - // ENOSYS means that the fs doesn't support the op. Just ignore - // that, because it doesn't matter. - // - // if there's no getuid, or if getuid() is something other - // than 0, and the error is EINVAL or EPERM, then just ignore - // it. - // - // This specific case is a silent failure in cp, install, tar, - // and most other unix tools that manage permissions. - // - // When running as root, or if other types of errors are - // encountered, then it's strict. - function chownErOk (er) { - if (!er) - return true +/***/ }), +/* 207 */ +/***/ (function(module, exports, __webpack_require__) { - if (er.code === "ENOSYS") - return true +"use strict"; - var nonroot = !process.getuid || process.getuid() !== 0 - if (nonroot) { - if (er.code === "EINVAL" || er.code === "EPERM") - return true - } +var $at = __webpack_require__(199)(true); - return false - } -} +// 21.1.3.27 String.prototype[@@iterator]() +__webpack_require__(103)(String, 'String', function (iterated) { + this._t = String(iterated); // target + this._i = 0; // next index +// 21.1.5.2.1 %StringIteratorPrototype%.next() +}, function () { + var O = this._t; + var index = this._i; + var point; + if (index >= O.length) return { value: undefined, done: true }; + point = $at(O, index); + this._i += point.length; + return { value: point, done: false }; +}); /***/ }), -/* 252 */ +/* 208 */ /***/ (function(module, exports, __webpack_require__) { -var Stream = __webpack_require__(28).Stream +"use strict"; +// https://github.com/tc39/proposal-promise-finally + +var $export = __webpack_require__(41); +var core = __webpack_require__(23); +var global = __webpack_require__(11); +var speciesConstructor = __webpack_require__(108); +var promiseResolve = __webpack_require__(105); + +$export($export.P + $export.R, 'Promise', { 'finally': function (onFinally) { + var C = speciesConstructor(this, core.Promise || global.Promise); + var isFunction = typeof onFinally == 'function'; + return this.then( + isFunction ? function (x) { + return promiseResolve(C, onFinally()).then(function () { return x; }); + } : onFinally, + isFunction ? function (e) { + return promiseResolve(C, onFinally()).then(function () { throw e; }); + } : onFinally + ); +} }); -module.exports = legacy -function legacy (fs) { - return { - ReadStream: ReadStream, - WriteStream: WriteStream - } +/***/ }), +/* 209 */ +/***/ (function(module, exports, __webpack_require__) { - function ReadStream (path, options) { - if (!(this instanceof ReadStream)) return new ReadStream(path, options); +"use strict"; - Stream.call(this); +// https://github.com/tc39/proposal-promise-try +var $export = __webpack_require__(41); +var newPromiseCapability = __webpack_require__(70); +var perform = __webpack_require__(104); - var self = this; +$export($export.S, 'Promise', { 'try': function (callbackfn) { + var promiseCapability = newPromiseCapability.f(this); + var result = perform(callbackfn); + (result.e ? promiseCapability.reject : promiseCapability.resolve)(result.v); + return promiseCapability.promise; +} }); - this.path = path; - this.fd = null; - this.readable = true; - this.paused = false; - this.flags = 'r'; - this.mode = 438; /*=0666*/ - this.bufferSize = 64 * 1024; +/***/ }), +/* 210 */ +/***/ (function(module, exports, __webpack_require__) { - options = options || {}; +__webpack_require__(204); +var global = __webpack_require__(11); +var hide = __webpack_require__(31); +var Iterators = __webpack_require__(35); +var TO_STRING_TAG = __webpack_require__(13)('toStringTag'); - // Mixin options into this - var keys = Object.keys(options); - for (var index = 0, length = keys.length; index < length; index++) { - var key = keys[index]; - this[key] = options[key]; - } +var DOMIterables = ('CSSRuleList,CSSStyleDeclaration,CSSValueList,ClientRectList,DOMRectList,DOMStringList,' + + 'DOMTokenList,DataTransferItemList,FileList,HTMLAllCollection,HTMLCollection,HTMLFormElement,HTMLSelectElement,' + + 'MediaList,MimeTypeArray,NamedNodeMap,NodeList,PaintRequestList,Plugin,PluginArray,SVGLengthList,SVGNumberList,' + + 'SVGPathSegList,SVGPointList,SVGStringList,SVGTransformList,SourceBufferList,StyleSheetList,TextTrackCueList,' + + 'TextTrackList,TouchList').split(','); - if (this.encoding) this.setEncoding(this.encoding); +for (var i = 0; i < DOMIterables.length; i++) { + var NAME = DOMIterables[i]; + var Collection = global[NAME]; + var proto = Collection && Collection.prototype; + if (proto && !proto[TO_STRING_TAG]) hide(proto, TO_STRING_TAG, NAME); + Iterators[NAME] = Iterators.Array; +} - if (this.start !== undefined) { - if ('number' !== typeof this.start) { - throw TypeError('start must be a Number'); - } - if (this.end === undefined) { - this.end = Infinity; - } else if ('number' !== typeof this.end) { - throw TypeError('end must be a Number'); - } - if (this.start > this.end) { - throw new Error('start must be <= end'); - } +/***/ }), +/* 211 */ +/***/ (function(module, exports, __webpack_require__) { - this.pos = this.start; - } +/** + * This is the web browser implementation of `debug()`. + * + * Expose `debug()` as the module. + */ - if (this.fd !== null) { - process.nextTick(function() { - self._read(); - }); - return; - } +exports = module.exports = __webpack_require__(112); +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(); - fs.open(this.path, this.flags, this.mode, function (err, fd) { - if (err) { - self.emit('error', err); - self.readable = false; - return; - } +/** + * Colors. + */ - self.fd = fd; - self.emit('open', fd); - self._read(); - }) +exports.colors = [ + '#0000CC', '#0000FF', '#0033CC', '#0033FF', '#0066CC', '#0066FF', '#0099CC', + '#0099FF', '#00CC00', '#00CC33', '#00CC66', '#00CC99', '#00CCCC', '#00CCFF', + '#3300CC', '#3300FF', '#3333CC', '#3333FF', '#3366CC', '#3366FF', '#3399CC', + '#3399FF', '#33CC00', '#33CC33', '#33CC66', '#33CC99', '#33CCCC', '#33CCFF', + '#6600CC', '#6600FF', '#6633CC', '#6633FF', '#66CC00', '#66CC33', '#9900CC', + '#9900FF', '#9933CC', '#9933FF', '#99CC00', '#99CC33', '#CC0000', '#CC0033', + '#CC0066', '#CC0099', '#CC00CC', '#CC00FF', '#CC3300', '#CC3333', '#CC3366', + '#CC3399', '#CC33CC', '#CC33FF', '#CC6600', '#CC6633', '#CC9900', '#CC9933', + '#CCCC00', '#CCCC33', '#FF0000', '#FF0033', '#FF0066', '#FF0099', '#FF00CC', + '#FF00FF', '#FF3300', '#FF3333', '#FF3366', '#FF3399', '#FF33CC', '#FF33FF', + '#FF6600', '#FF6633', '#FF9900', '#FF9933', '#FFCC00', '#FFCC33' +]; + +/** + * 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; + } + + // Internet Explorer and Edge do not support colors. + if (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/(edge|trident)\/(\d+)/)) { + return false; + } + + // 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; } +}; - function WriteStream (path, options) { - if (!(this instanceof WriteStream)) return new WriteStream(path, options); - Stream.call(this); +/** + * Colorize log arguments if enabled. + * + * @api public + */ - this.path = path; - this.fd = null; - this.writable = true; +function formatArgs(args) { + var useColors = this.useColors; - this.flags = 'w'; - this.encoding = 'binary'; - this.mode = 438; /*=0666*/ - this.bytesWritten = 0; + args[0] = (useColors ? '%c' : '') + + this.namespace + + (useColors ? ' %c' : ' ') + + args[0] + + (useColors ? '%c ' : ' ') + + '+' + exports.humanize(this.diff); - options = options || {}; + if (!useColors) return; - // Mixin options into this - var keys = Object.keys(options); - for (var index = 0, length = keys.length; index < length; index++) { - var key = keys[index]; - this[key] = options[key]; + 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; } + }); - if (this.start !== undefined) { - if ('number' !== typeof this.start) { - throw TypeError('start must be a Number'); - } - if (this.start < 0) { - throw new Error('start must be >= zero'); - } + args.splice(lastC, 0, c); +} - this.pos = this.start; - } +/** + * Invokes `console.log()` when available. + * No-op when `console.log` is not a "function". + * + * @api public + */ - this.busy = false; - this._queue = []; +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); +} - if (this.fd === null) { - this._open = fs.open; - this._queue.push([this._open, this.path, this.flags, this.mode, undefined]); - this.flush(); +/** + * 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 + */ -/***/ }), -/* 253 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; +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; + } -module.exports = clone + return r; +} -function clone (obj) { - if (obj === null || typeof obj !== 'object') - return obj +/** + * Enable namespaces listed in `localStorage.debug` initially. + */ - if (obj instanceof Object) - var copy = { __proto__: obj.__proto__ } - else - var copy = Object.create(null) +exports.enable(load()); - Object.getOwnPropertyNames(obj).forEach(function (key) { - Object.defineProperty(copy, key, Object.getOwnPropertyDescriptor(obj, key)) - }) +/** + * 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 + */ - return copy +function localstorage() { + try { + return window.localStorage; + } catch (e) {} } /***/ }), -/* 254 */ +/* 212 */ /***/ (function(module, exports, __webpack_require__) { -"use strict"; - -const path = __webpack_require__(16); - -module.exports = path_ => { - let cwd = process.cwd(); - - path_ = path.resolve(path_); - - if (process.platform === 'win32') { - cwd = cwd.toLowerCase(); - path_ = path_.toLowerCase(); - } +/** + * Detect Electron renderer process, which is node, but we should + * treat as a browser. + */ - return path_ === cwd; -}; +if (typeof process === 'undefined' || process.type === 'renderer') { + module.exports = __webpack_require__(211); +} else { + module.exports = __webpack_require__(213); +} /***/ }), -/* 255 */ +/* 213 */ /***/ (function(module, exports, __webpack_require__) { -"use strict"; - -const path = __webpack_require__(16); - -module.exports = (childPath, parentPath) => { - childPath = path.resolve(childPath); - parentPath = path.resolve(parentPath); - - if (process.platform === 'win32') { - childPath = childPath.toLowerCase(); - parentPath = parentPath.toLowerCase(); - } +/** + * Module dependencies. + */ - if (childPath === parentPath) { - return false; - } +var tty = __webpack_require__(79); +var util = __webpack_require__(2); - childPath += path.sep; - parentPath += path.sep; +/** + * This is the Node.js implementation of `debug()`. + * + * Expose `debug()` as the module. + */ - return childPath.startsWith(parentPath); -}; +exports = module.exports = __webpack_require__(112); +exports.init = init; +exports.log = log; +exports.formatArgs = formatArgs; +exports.save = save; +exports.load = load; +exports.useColors = useColors; +/** + * Colors. + */ -/***/ }), -/* 256 */ -/***/ (function(module, exports, __webpack_require__) { +exports.colors = [ 6, 2, 3, 4, 5, 1 ]; -const assert = __webpack_require__(30) -const path = __webpack_require__(16) -const fs = __webpack_require__(23) -let glob = undefined try { - glob = __webpack_require__(37) -} catch (_err) { - // treat glob as optional. -} - -const defaultGlobOpts = { - nosort: true, - silent: true -} - -// for EMFILE handling -let timeout = 0 - -const isWindows = (process.platform === "win32") - -const defaults = options => { - const methods = [ - 'unlink', - 'chmod', - 'stat', - 'lstat', - 'rmdir', - 'readdir' - ] - methods.forEach(m => { - options[m] = options[m] || fs[m] - m = m + 'Sync' - options[m] = options[m] || fs[m] - }) - - options.maxBusyTries = options.maxBusyTries || 3 - options.emfileWait = options.emfileWait || 1000 - if (options.glob === false) { - options.disableGlob = true - } - if (options.disableGlob !== true && glob === undefined) { - throw Error('glob dependency not found, set `options.disableGlob = true` if intentional') + var supportsColor = __webpack_require__(239); + if (supportsColor && supportsColor.level >= 2) { + exports.colors = [ + 20, 21, 26, 27, 32, 33, 38, 39, 40, 41, 42, 43, 44, 45, 56, 57, 62, 63, 68, + 69, 74, 75, 76, 77, 78, 79, 80, 81, 92, 93, 98, 99, 112, 113, 128, 129, 134, + 135, 148, 149, 160, 161, 162, 163, 164, 165, 166, 167, 168, 169, 170, 171, + 172, 173, 178, 179, 184, 185, 196, 197, 198, 199, 200, 201, 202, 203, 204, + 205, 206, 207, 208, 209, 214, 215, 220, 221 + ]; } - options.disableGlob = options.disableGlob || false - options.glob = options.glob || defaultGlobOpts +} catch (err) { + // swallow - we only care if `supports-color` is available; it doesn't have to be. } -const rimraf = (p, options, cb) => { - if (typeof options === 'function') { - cb = options - options = {} - } - - assert(p, 'rimraf: missing path') - assert.equal(typeof p, 'string', 'rimraf: path should be a string') - assert.equal(typeof cb, 'function', 'rimraf: callback function required') - assert(options, 'rimraf: invalid options argument provided') - assert.equal(typeof options, 'object', 'rimraf: options should be object') +/** + * Build up the default `inspectOpts` object from the environment variables. + * + * $ DEBUG_COLORS=no DEBUG_DEPTH=10 DEBUG_SHOW_HIDDEN=enabled node script.js + */ - defaults(options) +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() }); - let busyTries = 0 - let errState = null - let n = 0 + // 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); - const next = (er) => { - errState = errState || er - if (--n === 0) - cb(errState) - } + obj[prop] = val; + return obj; +}, {}); - const afterGlob = (er, results) => { - if (er) - return cb(er) +/** + * Is stdout a TTY? Colored output is enabled when `true`. + */ - n = results.length - if (n === 0) - return cb() +function useColors() { + return 'colors' in exports.inspectOpts + ? Boolean(exports.inspectOpts.colors) + : tty.isatty(process.stderr.fd); +} - results.forEach(p => { - const CB = (er) => { - if (er) { - if ((er.code === "EBUSY" || er.code === "ENOTEMPTY" || er.code === "EPERM") && - busyTries < options.maxBusyTries) { - busyTries ++ - // try again, with the same exact callback as this one. - return setTimeout(() => rimraf_(p, options, CB), busyTries * 100) - } +/** + * Map %o to `util.inspect()`, all on a single line. + */ - // this one won't happen if graceful-fs is used. - if (er.code === "EMFILE" && timeout < options.emfileWait) { - return setTimeout(() => rimraf_(p, options, CB), timeout ++) - } +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(' '); +}; - // already gone - if (er.code === "ENOENT") er = null - } +/** + * Map %o to `util.inspect()`, allowing multiple lines if needed. + */ - timeout = 0 - next(er) - } - rimraf_(p, options, CB) - }) - } +exports.formatters.O = function(v) { + this.inspectOpts.colors = this.useColors; + return util.inspect(v, this.inspectOpts); +}; - if (options.disableGlob || !glob.hasMagic(p)) - return afterGlob(null, [p]) +/** + * Adds ANSI color escape codes if enabled. + * + * @api public + */ - options.lstat(p, (er, stat) => { - if (!er) - return afterGlob(null, [p]) +function formatArgs(args) { + var name = this.namespace; + var useColors = this.useColors; - glob(p, options.glob, afterGlob) - }) + if (useColors) { + var c = this.color; + var colorCode = '\u001b[3' + (c < 8 ? c : '8;5;' + c); + var prefix = ' ' + colorCode + ';1m' + name + ' ' + '\u001b[0m'; + args[0] = prefix + args[0].split('\n').join('\n' + prefix); + args.push(colorCode + 'm+' + exports.humanize(this.diff) + '\u001b[0m'); + } else { + args[0] = getDate() + name + ' ' + args[0]; + } } -// Two possible strategies. -// 1. Assume it's a file. unlink it, then do the dir stuff on EPERM or EISDIR -// 2. Assume it's a directory. readdir, then do the file stuff on ENOTDIR -// -// Both result in an extra syscall when you guess wrong. However, there -// are likely far more normal files in the world than directories. This -// is based on the assumption that a the average number of files per -// directory is >= 1. -// -// If anyone ever complains about this, then I guess the strategy could -// be made configurable somehow. But until then, YAGNI. -const rimraf_ = (p, options, cb) => { - assert(p) - assert(options) - assert(typeof cb === 'function') +function getDate() { + if (exports.inspectOpts.hideDate) { + return ''; + } else { + return new Date().toISOString() + ' '; + } +} - // sunos lets the root user unlink directories, which is... weird. - // so we have to lstat here and make sure it's not a dir. - options.lstat(p, (er, st) => { - if (er && er.code === "ENOENT") - return cb(null) +/** + * Invokes `util.format()` with the specified arguments and writes to stderr. + */ - // Windows can EPERM on stat. Life is suffering. - if (er && er.code === "EPERM" && isWindows) - fixWinEPERM(p, options, er, cb) +function log() { + return process.stderr.write(util.format.apply(util, arguments) + '\n'); +} - if (st && st.isDirectory()) - return rmdir(p, options, er, cb) +/** + * Save `namespaces`. + * + * @param {String} namespaces + * @api private + */ - options.unlink(p, er => { - if (er) { - if (er.code === "ENOENT") - return cb(null) - if (er.code === "EPERM") - return (isWindows) - ? fixWinEPERM(p, options, er, cb) - : rmdir(p, options, er, cb) - if (er.code === "EISDIR") - return rmdir(p, options, er, cb) - } - return cb(er) - }) - }) +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; + } } -const fixWinEPERM = (p, options, er, cb) => { - assert(p) - assert(options) - assert(typeof cb === 'function') - if (er) - assert(er instanceof Error) +/** + * Load `namespaces`. + * + * @return {String} returns the previously persisted debug modes + * @api private + */ - options.chmod(p, 0o666, er2 => { - if (er2) - cb(er2.code === "ENOENT" ? null : er) - else - options.stat(p, (er3, stats) => { - if (er3) - cb(er3.code === "ENOENT" ? null : er) - else if (stats.isDirectory()) - rmdir(p, options, er, cb) - else - options.unlink(p, cb) - }) - }) +function load() { + return process.env.DEBUG; } -const fixWinEPERMSync = (p, options, er) => { - assert(p) - assert(options) - if (er) - assert(er instanceof Error) +/** + * Init logic for `debug` instances. + * + * Create a new `inspectOpts` object in case `useColors` is set + * differently for a particular `debug` instance. + */ - try { - options.chmodSync(p, 0o666) - } catch (er2) { - if (er2.code === "ENOENT") - return - else - throw er - } +function init (debug) { + debug.inspectOpts = {}; - let stats - try { - stats = options.statSync(p) - } catch (er3) { - if (er3.code === "ENOENT") - return - else - throw er + var keys = Object.keys(exports.inspectOpts); + for (var i = 0; i < keys.length; i++) { + debug.inspectOpts[keys[i]] = exports.inspectOpts[keys[i]]; } - - if (stats.isDirectory()) - rmdirSync(p, options, er) - else - options.unlinkSync(p) } -const rmdir = (p, options, originalEr, cb) => { - assert(p) - assert(options) - if (originalEr) - assert(originalEr instanceof Error) - assert(typeof cb === 'function') - - // try to rmdir first, and only readdir on ENOTEMPTY or EEXIST (SunOS) - // if we guessed wrong, and it's not a directory, then - // raise the original error. - options.rmdir(p, er => { - if (er && (er.code === "ENOTEMPTY" || er.code === "EEXIST" || er.code === "EPERM")) - rmkids(p, options, cb) - else if (er && er.code === "ENOTDIR") - cb(originalEr) - else - cb(er) - }) -} +/** + * Enable namespaces listed in `process.env.DEBUG` initially. + */ -const rmkids = (p, options, cb) => { - assert(p) - assert(options) - assert(typeof cb === 'function') +exports.enable(load()); - options.readdir(p, (er, files) => { - if (er) - return cb(er) - let n = files.length - if (n === 0) - return options.rmdir(p, cb) - let errState - files.forEach(f => { - rimraf(path.join(p, f), options, er => { - if (errState) - return - if (er) - return cb(errState = er) - if (--n === 0) - options.rmdir(p, cb) - }) - }) - }) -} -// this looks simpler, and is strictly *faster*, but will -// tie up the JavaScript thread and fail on excessively -// deep directory trees. -const rimrafSync = (p, options) => { - options = options || {} - defaults(options) +/***/ }), +/* 214 */, +/* 215 */, +/* 216 */, +/* 217 */ +/***/ (function(module, exports, __webpack_require__) { - assert(p, 'rimraf: missing path') - assert.equal(typeof p, 'string', 'rimraf: path should be a string') - assert(options, 'rimraf: missing options') - assert.equal(typeof options, 'object', 'rimraf: options should be object') +// Copyright Joyent, Inc. and other Node contributors. +// +// Permission is hereby granted, free of charge, to any person obtaining a +// copy of this software and associated documentation files (the +// "Software"), to deal in the Software without restriction, including +// without limitation the rights to use, copy, modify, merge, publish, +// distribute, sublicense, and/or sell copies of the Software, and to permit +// persons to whom the Software is furnished to do so, subject to the +// following conditions: +// +// The above copyright notice and this permission notice shall be included +// in all copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS +// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN +// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, +// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR +// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE +// USE OR OTHER DEALINGS IN THE SOFTWARE. - let results +var pathModule = __webpack_require__(0); +var isWindows = process.platform === 'win32'; +var fs = __webpack_require__(3); - if (options.disableGlob || !glob.hasMagic(p)) { - results = [p] - } else { - try { - options.lstatSync(p) - results = [p] - } catch (er) { - results = glob.sync(p, options.glob) - } - } +// JavaScript implementation of realpath, ported from node pre-v6 - if (!results.length) - return +var DEBUG = process.env.NODE_DEBUG && /fs/.test(process.env.NODE_DEBUG); - for (let i = 0; i < results.length; i++) { - const p = results[i] +function rethrow() { + // Only enable in debug mode. A backtrace uses ~1000 bytes of heap space and + // is fairly slow to generate. + var callback; + if (DEBUG) { + var backtrace = new Error; + callback = debugCallback; + } else + callback = missingCallback; - let st - try { - st = options.lstatSync(p) - } catch (er) { - if (er.code === "ENOENT") - return + return callback; - // Windows can EPERM on stat. Life is suffering. - if (er.code === "EPERM" && isWindows) - fixWinEPERMSync(p, options, er) + function debugCallback(err) { + if (err) { + backtrace.message = err.message; + err = backtrace; + missingCallback(err); } + } - try { - // sunos lets the root user unlink directories, which is... weird. - if (st && st.isDirectory()) - rmdirSync(p, options, null) - else - options.unlinkSync(p) - } catch (er) { - if (er.code === "ENOENT") - return - if (er.code === "EPERM") - return isWindows ? fixWinEPERMSync(p, options, er) : rmdirSync(p, options, er) - if (er.code !== "EISDIR") - throw er - - rmdirSync(p, options, er) + function missingCallback(err) { + if (err) { + if (process.throwDeprecation) + throw err; // Forgot a callback but don't know where? Use NODE_DEBUG=fs + else if (!process.noDeprecation) { + var msg = 'fs: missing callback ' + (err.stack || err.message); + if (process.traceDeprecation) + console.trace(msg); + else + console.error(msg); + } } } } -const rmdirSync = (p, options, originalEr) => { - assert(p) - assert(options) - if (originalEr) - assert(originalEr instanceof Error) - - try { - options.rmdirSync(p) - } catch (er) { - if (er.code === "ENOENT") - return - if (er.code === "ENOTDIR") - throw originalEr - if (er.code === "ENOTEMPTY" || er.code === "EEXIST" || er.code === "EPERM") - rmkidsSync(p, options) - } +function maybeCallback(cb) { + return typeof cb === 'function' ? cb : rethrow(); } -const rmkidsSync = (p, options) => { - assert(p) - assert(options) - options.readdirSync(p).forEach(f => rimrafSync(path.join(p, f), options)) +var normalize = pathModule.normalize; - // We only end up here once we got ENOTEMPTY at least once, and - // at this point, we are guaranteed to have removed all the kids. - // So, we know that it won't be ENOENT or ENOTDIR or anything else. - // try really hard to delete stuff on windows, because it has a - // PROFOUNDLY annoying habit of not closing handles promptly when - // files are deleted, resulting in spurious ENOTEMPTY errors. - const retries = isWindows ? 100 : 1 - let i = 0 - do { - let threw = true - try { - const ret = options.rmdirSync(p, options) - threw = false - return ret - } finally { - if (++i < retries && threw) - continue - } - } while (true) +// Regexp that finds the next partion of a (partial) path +// result is [base_with_slash, base], e.g. ['somedir/', 'somedir'] +if (isWindows) { + var nextPartRe = /(.*?)(?:[\/\\]+|$)/g; +} else { + var nextPartRe = /(.*?)(?:[\/]+|$)/g; } -module.exports = rimraf -rimraf.sync = rimrafSync +// Regex to find the device root, including trailing slash. E.g. 'c:\\'. +if (isWindows) { + var splitRootRe = /^(?:[a-zA-Z]:|[\\\/]{2}[^\\\/]+[\\\/][^\\\/]+)?[\\\/]*/; +} else { + var splitRootRe = /^[\/]*/; +} +exports.realpathSync = function realpathSync(p, cache) { + // make p is absolute + p = pathModule.resolve(p); -/***/ }), -/* 257 */ -/***/ (function(module, exports, __webpack_require__) { + if (cache && Object.prototype.hasOwnProperty.call(cache, p)) { + return cache[p]; + } -"use strict"; + var original = p, + seenLinks = {}, + knownHard = {}; -const AggregateError = __webpack_require__(258); + // current character position in p + var pos; + // the partial path so far, including a trailing slash if any + var current; + // the partial path without a trailing slash (except when pointing at a root) + var base; + // the partial path scanned in the previous round, with slash + var previous; -module.exports = async ( - iterable, - mapper, - { - concurrency = Infinity, - stopOnError = true - } = {} -) => { - return new Promise((resolve, reject) => { - if (typeof mapper !== 'function') { - throw new TypeError('Mapper function is required'); - } + start(); - if (!(typeof concurrency === 'number' && concurrency >= 1)) { - throw new TypeError(`Expected \`concurrency\` to be a number from 1 and up, got \`${concurrency}\` (${typeof concurrency})`); - } + function start() { + // Skip over roots + var m = splitRootRe.exec(p); + pos = m[0].length; + current = m[0]; + base = m[0]; + previous = ''; - const ret = []; - const errors = []; - const iterator = iterable[Symbol.iterator](); - let isRejected = false; - let isIterableDone = false; - let resolvingCount = 0; - let currentIndex = 0; + // On windows, check that the root exists. On unix there is no need. + if (isWindows && !knownHard[base]) { + fs.lstatSync(base); + knownHard[base] = true; + } + } - const next = () => { - if (isRejected) { - return; - } + // walk down the path, swapping out linked pathparts for their real + // values + // NB: p.length changes. + while (pos < p.length) { + // find the next part + nextPartRe.lastIndex = pos; + var result = nextPartRe.exec(p); + previous = current; + current += result[0]; + base = previous + result[1]; + pos = nextPartRe.lastIndex; - const nextItem = iterator.next(); - const i = currentIndex; - currentIndex++; + // continue if not a symlink + if (knownHard[base] || (cache && cache[base] === base)) { + continue; + } - if (nextItem.done) { - isIterableDone = true; + var resolvedLink; + if (cache && Object.prototype.hasOwnProperty.call(cache, base)) { + // some known symbolic link. no need to stat again. + resolvedLink = cache[base]; + } else { + var stat = fs.lstatSync(base); + if (!stat.isSymbolicLink()) { + knownHard[base] = true; + if (cache) cache[base] = base; + continue; + } - if (resolvingCount === 0) { - if (!stopOnError && errors.length !== 0) { - reject(new AggregateError(errors)); - } else { - resolve(ret); - } - } + // read the link if it wasn't read before + // dev/ino always return 0 on windows, so skip the check. + var linkTarget = null; + if (!isWindows) { + var id = stat.dev.toString(32) + ':' + stat.ino.toString(32); + if (seenLinks.hasOwnProperty(id)) { + linkTarget = seenLinks[id]; + } + } + if (linkTarget === null) { + fs.statSync(base); + linkTarget = fs.readlinkSync(base); + } + resolvedLink = pathModule.resolve(previous, linkTarget); + // track this, if given a cache. + if (cache) cache[base] = resolvedLink; + if (!isWindows) seenLinks[id] = linkTarget; + } - return; - } + // resolve the link, then start over + p = pathModule.resolve(resolvedLink, p.slice(pos)); + start(); + } - resolvingCount++; + if (cache) cache[original] = p; - (async () => { - try { - const element = await nextItem.value; - ret[i] = await mapper(element, i); - resolvingCount--; - next(); - } catch (error) { - if (stopOnError) { - isRejected = true; - reject(error); - } else { - errors.push(error); - resolvingCount--; - next(); - } - } - })(); - }; + return p; +}; - for (let i = 0; i < concurrency; i++) { - next(); - if (isIterableDone) { - break; - } - } - }); -}; +exports.realpath = function realpath(p, cache, cb) { + if (typeof cb !== 'function') { + cb = maybeCallback(cache); + cache = null; + } + // make p is absolute + p = pathModule.resolve(p); -/***/ }), -/* 258 */ -/***/ (function(module, exports, __webpack_require__) { + if (cache && Object.prototype.hasOwnProperty.call(cache, p)) { + return process.nextTick(cb.bind(null, null, cache[p])); + } -"use strict"; + var original = p, + seenLinks = {}, + knownHard = {}; -const indentString = __webpack_require__(259); -const cleanStack = __webpack_require__(260); + // current character position in p + var pos; + // the partial path so far, including a trailing slash if any + var current; + // the partial path without a trailing slash (except when pointing at a root) + var base; + // the partial path scanned in the previous round, with slash + var previous; -const cleanInternalStack = stack => stack.replace(/\s+at .*aggregate-error\/index.js:\d+:\d+\)?/g, ''); + start(); -class AggregateError extends Error { - constructor(errors) { - if (!Array.isArray(errors)) { - throw new TypeError(`Expected input to be an Array, got ${typeof errors}`); - } + function start() { + // Skip over roots + var m = splitRootRe.exec(p); + pos = m[0].length; + current = m[0]; + base = m[0]; + previous = ''; - errors = [...errors].map(error => { - if (error instanceof Error) { - return error; - } + // On windows, check that the root exists. On unix there is no need. + if (isWindows && !knownHard[base]) { + fs.lstat(base, function(err) { + if (err) return cb(err); + knownHard[base] = true; + LOOP(); + }); + } else { + process.nextTick(LOOP); + } + } - if (error !== null && typeof error === 'object') { - // Handle plain error objects with message property and/or possibly other metadata - return Object.assign(new Error(error.message), error); - } + // walk down the path, swapping out linked pathparts for their real + // values + function LOOP() { + // stop if scanned past end of path + if (pos >= p.length) { + if (cache) cache[original] = p; + return cb(null, p); + } - return new Error(error); - }); + // find the next part + nextPartRe.lastIndex = pos; + var result = nextPartRe.exec(p); + previous = current; + current += result[0]; + base = previous + result[1]; + pos = nextPartRe.lastIndex; - let message = errors - .map(error => { - // The `stack` property is not standardized, so we can't assume it exists - return typeof error.stack === 'string' ? cleanInternalStack(cleanStack(error.stack)) : String(error); - }) - .join('\n'); - message = '\n' + indentString(message, 4); - super(message); + // continue if not a symlink + if (knownHard[base] || (cache && cache[base] === base)) { + return process.nextTick(LOOP); + } - this.name = 'AggregateError'; + if (cache && Object.prototype.hasOwnProperty.call(cache, base)) { + // known symbolic link. no need to stat again. + return gotResolvedLink(cache[base]); + } - Object.defineProperty(this, '_errors', {value: errors}); - } + return fs.lstat(base, gotStat); + } - * [Symbol.iterator]() { - for (const error of this._errors) { - yield error; - } - } -} + function gotStat(err, stat) { + if (err) return cb(err); -module.exports = AggregateError; + // if not a symlink, skip to the next path part + if (!stat.isSymbolicLink()) { + knownHard[base] = true; + if (cache) cache[base] = base; + return process.nextTick(LOOP); + } + // stat & read the link if not read before + // call gotTarget as soon as the link target is known + // dev/ino always return 0 on windows, so skip the check. + if (!isWindows) { + var id = stat.dev.toString(32) + ':' + stat.ino.toString(32); + if (seenLinks.hasOwnProperty(id)) { + return gotTarget(null, seenLinks[id], base); + } + } + fs.stat(base, function(err) { + if (err) return cb(err); -/***/ }), -/* 259 */ -/***/ (function(module, exports, __webpack_require__) { + fs.readlink(base, function(err, target) { + if (!isWindows) seenLinks[id] = target; + gotTarget(err, target); + }); + }); + } -"use strict"; + function gotTarget(err, target, base) { + if (err) return cb(err); -module.exports = (str, count, opts) => { - // Support older versions: use the third parameter as options.indent - // TODO: Remove the workaround in the next major version - const options = typeof opts === 'object' ? Object.assign({indent: ' '}, opts) : {indent: opts || ' '}; - count = count === undefined ? 1 : count; + var resolvedLink = pathModule.resolve(previous, target); + if (cache) cache[base] = resolvedLink; + gotResolvedLink(resolvedLink); + } - if (typeof str !== 'string') { - throw new TypeError(`Expected \`input\` to be a \`string\`, got \`${typeof str}\``); - } + function gotResolvedLink(resolvedLink) { + // resolve the link, then start over + p = pathModule.resolve(resolvedLink, p.slice(pos)); + start(); + } +}; - if (typeof count !== 'number') { - throw new TypeError(`Expected \`count\` to be a \`number\`, got \`${typeof count}\``); - } - if (typeof options.indent !== 'string') { - throw new TypeError(`Expected \`options.indent\` to be a \`string\`, got \`${typeof options.indent}\``); - } +/***/ }), +/* 218 */ +/***/ (function(module, exports, __webpack_require__) { - if (count === 0) { - return str; - } +module.exports = globSync +globSync.GlobSync = GlobSync - const regex = options.includeEmptyLines ? /^/mg : /^(?!\s*$)/mg; - return str.replace(regex, options.indent.repeat(count)); -} -; +var fs = __webpack_require__(3) +var rp = __webpack_require__(114) +var minimatch = __webpack_require__(60) +var Minimatch = minimatch.Minimatch +var Glob = __webpack_require__(75).Glob +var util = __webpack_require__(2) +var path = __webpack_require__(0) +var assert = __webpack_require__(22) +var isAbsolute = __webpack_require__(76) +var common = __webpack_require__(115) +var alphasort = common.alphasort +var alphasorti = common.alphasorti +var setopts = common.setopts +var ownProp = common.ownProp +var childrenIgnored = common.childrenIgnored +var isIgnored = common.isIgnored +function globSync (pattern, options) { + if (typeof options === 'function' || arguments.length === 3) + throw new TypeError('callback provided to sync glob\n'+ + 'See: https://github.com/isaacs/node-glob/issues/167') -/***/ }), -/* 260 */ -/***/ (function(module, exports, __webpack_require__) { + return new GlobSync(pattern, options).found +} -"use strict"; +function GlobSync (pattern, options) { + if (!pattern) + throw new Error('must provide pattern') -const os = __webpack_require__(11); + if (typeof options === 'function' || arguments.length === 3) + throw new TypeError('callback provided to sync glob\n'+ + 'See: https://github.com/isaacs/node-glob/issues/167') -const extractPathRegex = /\s+at.*(?:\(|\s)(.*)\)?/; -const pathRegex = /^(?:(?:(?:node|(?:internal\/[\w/]*|.*node_modules\/(?:babel-polyfill|pirates)\/.*)?\w+)\.js:\d+:\d+)|native)/; -const homeDir = os.homedir(); + if (!(this instanceof GlobSync)) + return new GlobSync(pattern, options) -module.exports = (stack, options) => { - options = Object.assign({pretty: false}, options); + setopts(this, pattern, options) - return stack.replace(/\\/g, '/') - .split('\n') - .filter(line => { - const pathMatches = line.match(extractPathRegex); - if (pathMatches === null || !pathMatches[1]) { - return true; - } + if (this.noprocess) + return this - const match = pathMatches[1]; + var n = this.minimatch.set.length + this.matches = new Array(n) + for (var i = 0; i < n; i ++) { + this._process(this.minimatch.set[i], i, false) + } + this._finish() +} - // Electron - if ( - match.includes('.app/Contents/Resources/electron.asar') || - match.includes('.app/Contents/Resources/default_app.asar') - ) { - return false; - } +GlobSync.prototype._finish = function () { + assert(this instanceof GlobSync) + if (this.realpath) { + var self = this + this.matches.forEach(function (matchset, index) { + var set = self.matches[index] = Object.create(null) + for (var p in matchset) { + try { + p = self._makeAbs(p) + var real = rp.realpathSync(p, self.realpathCache) + set[real] = true + } catch (er) { + if (er.syscall === 'stat') + set[self._makeAbs(p)] = true + else + throw er + } + } + }) + } + common.finish(this) +} - return !pathRegex.test(match); - }) - .filter(line => line.trim() !== '') - .map(line => { - if (options.pretty) { - return line.replace(extractPathRegex, (m, p1) => m.replace(p1, p1.replace(homeDir, '~'))); - } - return line; - }) - .join('\n'); -}; +GlobSync.prototype._process = function (pattern, index, inGlobStar) { + assert(this instanceof GlobSync) + // Get the first [n] parts of pattern that are all strings. + var n = 0 + while (typeof pattern[n] === 'string') { + n ++ + } + // now n is the index of the first one that is *not* a string. -/***/ }), -/* 261 */ -/***/ (function(module, exports, __webpack_require__) { + // See if there's anything else + var prefix + switch (n) { + // if not, then this is rather simple + case pattern.length: + this._processSimple(pattern.join('/'), index) + return -"use strict"; + case 0: + // pattern *starts* with some non-trivial item. + // going to readdir(cwd), but not include the prefix in matches. + prefix = null + break -const chalk = __webpack_require__(262); -const cliCursor = __webpack_require__(266); -const cliSpinners = __webpack_require__(270); -const logSymbols = __webpack_require__(158); + default: + // pattern has some string bits in the front. + // whatever it starts with, whether that's 'absolute' like /foo/bar, + // or 'relative' like '../baz' + prefix = pattern.slice(0, n).join('/') + break + } -class Ora { - constructor(options) { - if (typeof options === 'string') { - options = { - text: options - }; - } + var remain = pattern.slice(n) - this.options = Object.assign({ - text: '', - color: 'cyan', - stream: process.stderr - }, options); + // get the list of entries. + var read + if (prefix === null) + read = '.' + else if (isAbsolute(prefix) || isAbsolute(pattern.join('/'))) { + if (!prefix || !isAbsolute(prefix)) + prefix = '/' + prefix + read = prefix + } else + read = prefix - const sp = this.options.spinner; - this.spinner = typeof sp === 'object' ? sp : (process.platform === 'win32' ? cliSpinners.line : (cliSpinners[sp] || cliSpinners.dots)); // eslint-disable-line no-nested-ternary + var abs = this._makeAbs(read) - if (this.spinner.frames === undefined) { - throw new Error('Spinner must define `frames`'); - } + //if ignored, skip processing + if (childrenIgnored(this, read)) + return - this.text = this.options.text; - this.color = this.options.color; - this.interval = this.options.interval || this.spinner.interval || 100; - this.stream = this.options.stream; - this.id = null; - this.frameIndex = 0; - this.enabled = typeof this.options.enabled === 'boolean' ? this.options.enabled : ((this.stream && this.stream.isTTY) && !process.env.CI); - } - frame() { - const frames = this.spinner.frames; - let frame = frames[this.frameIndex]; + var isGlobStar = remain[0] === minimatch.GLOBSTAR + if (isGlobStar) + this._processGlobStar(prefix, read, abs, remain, index, inGlobStar) + else + this._processReaddir(prefix, read, abs, remain, index, inGlobStar) +} - if (this.color) { - frame = chalk[this.color](frame); - } - this.frameIndex = ++this.frameIndex % frames.length; +GlobSync.prototype._processReaddir = function (prefix, read, abs, remain, index, inGlobStar) { + var entries = this._readdir(abs, inGlobStar) - return frame + ' ' + this.text; - } - clear() { - if (!this.enabled) { - return this; - } + // if the abs isn't a dir, then nothing can match! + if (!entries) + return - this.stream.clearLine(); - this.stream.cursorTo(0); + // It will only match dot entries if it starts with a dot, or if + // dot is set. Stuff like @(.foo|.bar) isn't allowed. + var pn = remain[0] + var negate = !!this.minimatch.negate + var rawGlob = pn._glob + var dotOk = this.dot || rawGlob.charAt(0) === '.' - return this; - } - render() { - this.clear(); - this.stream.write(this.frame()); + var matchedEntries = [] + for (var i = 0; i < entries.length; i++) { + var e = entries[i] + if (e.charAt(0) !== '.' || dotOk) { + var m + if (negate && !prefix) { + m = !e.match(pn) + } else { + m = e.match(pn) + } + if (m) + matchedEntries.push(e) + } + } - return this; - } - start(text) { - if (text) { - this.text = text; - } + var len = matchedEntries.length + // If there are no matched entries, then nothing matches. + if (len === 0) + return - if (!this.enabled || this.id) { - return this; - } + // if this is the last remaining pattern bit, then no need for + // an additional stat *unless* the user has specified mark or + // stat explicitly. We know they exist, since readdir returned + // them. - cliCursor.hide(this.stream); - this.render(); - this.id = setInterval(this.render.bind(this), this.interval); + if (remain.length === 1 && !this.mark && !this.stat) { + if (!this.matches[index]) + this.matches[index] = Object.create(null) - return this; - } - stop() { - if (!this.enabled) { - return this; - } + for (var i = 0; i < len; i ++) { + var e = matchedEntries[i] + if (prefix) { + if (prefix.slice(-1) !== '/') + e = prefix + '/' + e + else + e = prefix + e + } - clearInterval(this.id); - this.id = null; - this.frameIndex = 0; - this.clear(); - cliCursor.show(this.stream); + if (e.charAt(0) === '/' && !this.nomount) { + e = path.join(this.root, e) + } + this._emitMatch(index, e) + } + // This was the last one, and no stats were needed + return + } - return this; - } - succeed(text) { - return this.stopAndPersist({symbol: logSymbols.success, text}); - } - fail(text) { - return this.stopAndPersist({symbol: logSymbols.error, text}); - } - warn(text) { - return this.stopAndPersist({symbol: logSymbols.warning, text}); - } - info(text) { - return this.stopAndPersist({symbol: logSymbols.info, text}); - } - stopAndPersist(options) { - if (!this.enabled) { - return this; - } + // now test all matched entries as stand-ins for that part + // of the pattern. + remain.shift() + for (var i = 0; i < len; i ++) { + var e = matchedEntries[i] + var newPattern + if (prefix) + newPattern = [prefix, e] + else + newPattern = [e] + this._process(newPattern.concat(remain), index, inGlobStar) + } +} - // Legacy argument - // TODO: Deprecate sometime in the future - if (typeof options === 'string') { - options = { - symbol: options - }; - } - options = options || {}; +GlobSync.prototype._emitMatch = function (index, e) { + if (isIgnored(this, e)) + return - this.stop(); - this.stream.write(`${options.symbol || ' '} ${options.text || this.text}\n`); + var abs = this._makeAbs(e) - return this; - } -} + if (this.mark) + e = this._mark(e) -module.exports = function (opts) { - return new Ora(opts); -}; + if (this.absolute) { + e = abs + } -module.exports.promise = (action, options) => { - if (typeof action.then !== 'function') { - throw new TypeError('Parameter `action` must be a Promise'); - } + if (this.matches[index][e]) + return - const spinner = new Ora(options); - spinner.start(); + if (this.nodir) { + var c = this.cache[abs] + if (c === 'DIR' || Array.isArray(c)) + return + } - action.then( - () => { - spinner.succeed(); - }, - () => { - spinner.fail(); - } - ); + this.matches[index][e] = true - return spinner; -}; + if (this.stat) + this._stat(e) +} -/***/ }), -/* 262 */ -/***/ (function(module, exports, __webpack_require__) { +GlobSync.prototype._readdirInGlobStar = function (abs) { + // follow all symlinked directories forever + // just proceed as if this is a non-globstar situation + if (this.follow) + return this._readdir(abs, false) -"use strict"; + var entries + var lstat + var stat + try { + lstat = fs.lstatSync(abs) + } catch (er) { + if (er.code === 'ENOENT') { + // lstat failed, doesn't exist + return null + } + } -const escapeStringRegexp = __webpack_require__(3); -const ansiStyles = __webpack_require__(263); -const stdoutColor = __webpack_require__(264).stdout; + var isSym = lstat && lstat.isSymbolicLink() + this.symlinks[abs] = isSym -const template = __webpack_require__(265); + // If it's not a symlink or a dir, then it's definitely a regular file. + // don't bother doing a readdir in that case. + if (!isSym && lstat && !lstat.isDirectory()) + this.cache[abs] = 'FILE' + else + entries = this._readdir(abs, false) -const isSimpleWindowsTerm = process.platform === 'win32' && !(process.env.TERM || '').toLowerCase().startsWith('xterm'); + return entries +} -// `supportsColor.level` → `ansiStyles.color[name]` mapping -const levelMapping = ['ansi', 'ansi', 'ansi256', 'ansi16m']; +GlobSync.prototype._readdir = function (abs, inGlobStar) { + var entries -// `color-convert` models to exclude from the Chalk API due to conflicts and such -const skipModels = new Set(['gray']); + if (inGlobStar && !ownProp(this.symlinks, abs)) + return this._readdirInGlobStar(abs) -const styles = Object.create(null); + if (ownProp(this.cache, abs)) { + var c = this.cache[abs] + if (!c || c === 'FILE') + return null -function applyOptions(obj, options) { - options = options || {}; + if (Array.isArray(c)) + return c + } - // Detect level if not set manually - const scLevel = stdoutColor ? stdoutColor.level : 0; - obj.level = options.level === undefined ? scLevel : options.level; - obj.enabled = 'enabled' in options ? options.enabled : obj.level > 0; + try { + return this._readdirEntries(abs, fs.readdirSync(abs)) + } catch (er) { + this._readdirError(abs, er) + return null + } } -function Chalk(options) { - // We check for this.template here since calling `chalk.constructor()` - // by itself will have a `this` of a previously constructed chalk object - if (!this || !(this instanceof Chalk) || this.template) { - const chalk = {}; - applyOptions(chalk, options); - - chalk.template = function () { - const args = [].slice.call(arguments); - return chalkTag.apply(null, [chalk.template].concat(args)); - }; +GlobSync.prototype._readdirEntries = function (abs, entries) { + // if we haven't asked to stat everything, then just + // assume that everything in there exists, so we can avoid + // having to stat it a second time. + if (!this.mark && !this.stat) { + for (var i = 0; i < entries.length; i ++) { + var e = entries[i] + if (abs === '/') + e = abs + e + else + e = abs + '/' + e + this.cache[e] = true + } + } - Object.setPrototypeOf(chalk, Chalk.prototype); - Object.setPrototypeOf(chalk.template, chalk); + this.cache[abs] = entries - chalk.template.constructor = Chalk; + // mark and cache dir-ness + return entries +} - return chalk.template; - } +GlobSync.prototype._readdirError = function (f, er) { + // handle errors, and cache the information + switch (er.code) { + case 'ENOTSUP': // https://github.com/isaacs/node-glob/issues/205 + case 'ENOTDIR': // totally normal. means it *does* exist. + var abs = this._makeAbs(f) + this.cache[abs] = 'FILE' + if (abs === this.cwdAbs) { + var error = new Error(er.code + ' invalid cwd ' + this.cwd) + error.path = this.cwd + error.code = er.code + throw error + } + break - applyOptions(this, options); -} + case 'ENOENT': // not terribly unusual + case 'ELOOP': + case 'ENAMETOOLONG': + case 'UNKNOWN': + this.cache[this._makeAbs(f)] = false + break -// Use bright blue on Windows as the normal blue color is illegible -if (isSimpleWindowsTerm) { - ansiStyles.blue.open = '\u001B[94m'; + default: // some unusual error. Treat as failure. + this.cache[this._makeAbs(f)] = false + if (this.strict) + throw er + if (!this.silent) + console.error('glob error', er) + break + } } -for (const key of Object.keys(ansiStyles)) { - ansiStyles[key].closeRe = new RegExp(escapeStringRegexp(ansiStyles[key].close), 'g'); +GlobSync.prototype._processGlobStar = function (prefix, read, abs, remain, index, inGlobStar) { - styles[key] = { - get() { - const codes = ansiStyles[key]; - return build.call(this, this._styles ? this._styles.concat(codes) : [codes], this._empty, key); - } - }; -} + var entries = this._readdir(abs, inGlobStar) -styles.visible = { - get() { - return build.call(this, this._styles || [], true, 'visible'); - } -}; + // no entries means not a dir, so it can never have matches + // foo.txt/** doesn't match foo.txt + if (!entries) + return -ansiStyles.color.closeRe = new RegExp(escapeStringRegexp(ansiStyles.color.close), 'g'); -for (const model of Object.keys(ansiStyles.color.ansi)) { - if (skipModels.has(model)) { - continue; - } + // test without the globstar, and with every child both below + // and replacing the globstar. + var remainWithoutGlobStar = remain.slice(1) + var gspref = prefix ? [ prefix ] : [] + var noGlobStar = gspref.concat(remainWithoutGlobStar) - styles[model] = { - get() { - const level = this.level; - return function () { - const open = ansiStyles.color[levelMapping[level]][model].apply(null, arguments); - const codes = { - open, - close: ansiStyles.color.close, - closeRe: ansiStyles.color.closeRe - }; - return build.call(this, this._styles ? this._styles.concat(codes) : [codes], this._empty, model); - }; - } - }; -} + // the noGlobStar pattern exits the inGlobStar state + this._process(noGlobStar, index, false) -ansiStyles.bgColor.closeRe = new RegExp(escapeStringRegexp(ansiStyles.bgColor.close), 'g'); -for (const model of Object.keys(ansiStyles.bgColor.ansi)) { - if (skipModels.has(model)) { - continue; - } + var len = entries.length + var isSym = this.symlinks[abs] - const bgModel = 'bg' + model[0].toUpperCase() + model.slice(1); - styles[bgModel] = { - get() { - const level = this.level; - return function () { - const open = ansiStyles.bgColor[levelMapping[level]][model].apply(null, arguments); - const codes = { - open, - close: ansiStyles.bgColor.close, - closeRe: ansiStyles.bgColor.closeRe - }; - return build.call(this, this._styles ? this._styles.concat(codes) : [codes], this._empty, model); - }; - } - }; -} + // If it's a symlink, and we're in a globstar, then stop + if (isSym && inGlobStar) + return -const proto = Object.defineProperties(() => {}, styles); + for (var i = 0; i < len; i++) { + var e = entries[i] + if (e.charAt(0) === '.' && !this.dot) + continue -function build(_styles, _empty, key) { - const builder = function () { - return applyStyle.apply(builder, arguments); - }; + // these two cases enter the inGlobStar state + var instead = gspref.concat(entries[i], remainWithoutGlobStar) + this._process(instead, index, true) - builder._styles = _styles; - builder._empty = _empty; + var below = gspref.concat(entries[i], remain) + this._process(below, index, true) + } +} - const self = this; +GlobSync.prototype._processSimple = function (prefix, index) { + // XXX review this. Shouldn't it be doing the mounting etc + // before doing stat? kinda weird? + var exists = this._stat(prefix) - Object.defineProperty(builder, 'level', { - enumerable: true, - get() { - return self.level; - }, - set(level) { - self.level = level; - } - }); + if (!this.matches[index]) + this.matches[index] = Object.create(null) - Object.defineProperty(builder, 'enabled', { - enumerable: true, - get() { - return self.enabled; - }, - set(enabled) { - self.enabled = enabled; - } - }); + // If it doesn't exist, then just mark the lack of results + if (!exists) + return - // See below for fix regarding invisible grey/dim combination on Windows - builder.hasGrey = this.hasGrey || key === 'gray' || key === 'grey'; + if (prefix && isAbsolute(prefix) && !this.nomount) { + var trail = /[\/\\]$/.test(prefix) + if (prefix.charAt(0) === '/') { + prefix = path.join(this.root, prefix) + } else { + prefix = path.resolve(this.root, prefix) + if (trail) + prefix += '/' + } + } - // `__proto__` is used because we must return a function, but there is - // no way to create a function with a different prototype - builder.__proto__ = proto; // eslint-disable-line no-proto + if (process.platform === 'win32') + prefix = prefix.replace(/\\/g, '/') - return builder; + // Mark this as a match + this._emitMatch(index, prefix) } -function applyStyle() { - // Support varags, but simply cast to string in case there's only one arg - const args = arguments; - const argsLen = args.length; - let str = String(arguments[0]); +// Returns either 'DIR', 'FILE', or false +GlobSync.prototype._stat = function (f) { + var abs = this._makeAbs(f) + var needDir = f.slice(-1) === '/' - if (argsLen === 0) { - return ''; - } + if (f.length > this.maxLength) + return false - if (argsLen > 1) { - // Don't slice `arguments`, it prevents V8 optimizations - for (let a = 1; a < argsLen; a++) { - str += ' ' + args[a]; - } - } + if (!this.stat && ownProp(this.cache, abs)) { + var c = this.cache[abs] - if (!this.enabled || this.level <= 0 || !str) { - return this._empty ? '' : str; - } + if (Array.isArray(c)) + c = 'DIR' - // Turns out that on Windows dimmed gray text becomes invisible in cmd.exe, - // see https://github.com/chalk/chalk/issues/58 - // If we're on Windows and we're dealing with a gray color, temporarily make 'dim' a noop. - const originalDim = ansiStyles.dim.open; - if (isSimpleWindowsTerm && this.hasGrey) { - ansiStyles.dim.open = ''; - } + // It exists, but maybe not how we need it + if (!needDir || c === 'DIR') + return c - for (const code of this._styles.slice().reverse()) { - // Replace any instances already present with a re-opening code - // otherwise only the part of the string until said closing code - // will be colored, and the rest will simply be 'plain'. - str = code.open + str.replace(code.closeRe, code.open) + code.close; + if (needDir && c === 'FILE') + return false - // Close the styling before a linebreak and reopen - // after next line to fix a bleed issue on macOS - // https://github.com/chalk/chalk/pull/92 - str = str.replace(/\r?\n/g, `${code.close}$&${code.open}`); - } + // otherwise we have to stat, because maybe c=true + // if we know it exists, but not what it is. + } - // Reset the original `dim` if we changed it to work around the Windows dimmed gray issue - ansiStyles.dim.open = originalDim; + var exists + var stat = this.statCache[abs] + if (!stat) { + var lstat + try { + lstat = fs.lstatSync(abs) + } catch (er) { + if (er && (er.code === 'ENOENT' || er.code === 'ENOTDIR')) { + this.statCache[abs] = false + return false + } + } - return str; -} + if (lstat && lstat.isSymbolicLink()) { + try { + stat = fs.statSync(abs) + } catch (er) { + stat = lstat + } + } else { + stat = lstat + } + } -function chalkTag(chalk, strings) { - if (!Array.isArray(strings)) { - // If chalk() was called by itself or with a string, - // return the string itself as a string. - return [].slice.call(arguments, 1).join(' '); - } + this.statCache[abs] = stat - const args = [].slice.call(arguments, 2); - const parts = [strings.raw[0]]; + var c = true + if (stat) + c = stat.isDirectory() ? 'DIR' : 'FILE' - for (let i = 1; i < strings.length; i++) { - parts.push(String(args[i - 1]).replace(/[{}\\]/g, '\\$&')); - parts.push(String(strings.raw[i])); - } + this.cache[abs] = this.cache[abs] || c - return template(chalk, parts.join('')); + if (needDir && c === 'FILE') + return false + + return c } -Object.defineProperties(Chalk.prototype, styles); +GlobSync.prototype._mark = function (p) { + return common.mark(this, p) +} -module.exports = Chalk(); // eslint-disable-line new-cap -module.exports.supportsColor = stdoutColor; -module.exports.default = module.exports; // For TypeScript +GlobSync.prototype._makeAbs = function (f) { + return common.makeAbs(this, f) +} /***/ }), -/* 263 */ +/* 219 */, +/* 220 */, +/* 221 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -/* WEBPACK VAR INJECTION */(function(module) { -const colorConvert = __webpack_require__(6); -const wrapAnsi16 = (fn, offset) => function () { - const code = fn.apply(colorConvert, arguments); - return `\u001B[${code + offset}m`; -}; +module.exports = function (flag, argv) { + argv = argv || process.argv; -const wrapAnsi256 = (fn, offset) => function () { - const code = fn.apply(colorConvert, arguments); - return `\u001B[${38 + offset};5;${code}m`; -}; + var terminatorPos = argv.indexOf('--'); + var prefix = /^--/.test(flag) ? '' : '--'; + var pos = argv.indexOf(prefix + flag); -const wrapAnsi16m = (fn, offset) => function () { - const rgb = fn.apply(colorConvert, arguments); - return `\u001B[${38 + offset};2;${rgb[0]};${rgb[1]};${rgb[2]}m`; + return pos !== -1 && (terminatorPos !== -1 ? pos < terminatorPos : true); }; -function assembleStyles() { - const codes = new Map(); - const styles = { - modifier: { - reset: [0, 0], - // 21 isn't widely supported and 22 does the same thing - bold: [1, 22], - dim: [2, 22], - italic: [3, 23], - underline: [4, 24], - inverse: [7, 27], - hidden: [8, 28], - strikethrough: [9, 29] - }, - color: { - black: [30, 39], - red: [31, 39], - green: [32, 39], - yellow: [33, 39], - blue: [34, 39], - magenta: [35, 39], - cyan: [36, 39], - white: [37, 39], - gray: [90, 39], - // Bright color - redBright: [91, 39], - greenBright: [92, 39], - yellowBright: [93, 39], - blueBright: [94, 39], - magentaBright: [95, 39], - cyanBright: [96, 39], - whiteBright: [97, 39] - }, - bgColor: { - bgBlack: [40, 49], - bgRed: [41, 49], - bgGreen: [42, 49], - bgYellow: [43, 49], - bgBlue: [44, 49], - bgMagenta: [45, 49], - bgCyan: [46, 49], - bgWhite: [47, 49], +/***/ }), +/* 222 */, +/* 223 */ +/***/ (function(module, exports, __webpack_require__) { - // Bright color - bgBlackBright: [100, 49], - bgRedBright: [101, 49], - bgGreenBright: [102, 49], - bgYellowBright: [103, 49], - bgBlueBright: [104, 49], - bgMagentaBright: [105, 49], - bgCyanBright: [106, 49], - bgWhiteBright: [107, 49] - } - }; +var wrappy = __webpack_require__(123) +var reqs = Object.create(null) +var once = __webpack_require__(61) - // Fix humans - styles.color.grey = styles.color.gray; +module.exports = wrappy(inflight) - for (const groupName of Object.keys(styles)) { - const group = styles[groupName]; +function inflight (key, cb) { + if (reqs[key]) { + reqs[key].push(cb) + return null + } else { + reqs[key] = [cb] + return makeres(key) + } +} - for (const styleName of Object.keys(group)) { - const style = group[styleName]; +function makeres (key) { + return once(function RES () { + var cbs = reqs[key] + var len = cbs.length + var args = slice(arguments) - styles[styleName] = { - open: `\u001B[${style[0]}m`, - close: `\u001B[${style[1]}m` - }; + // XXX It's somewhat ambiguous whether a new callback added in this + // pass should be queued for later execution if something in the + // list of callbacks throws, or if it should just be discarded. + // However, it's such an edge case that it hardly matters, and either + // choice is likely as surprising as the other. + // As it happens, we do go ahead and schedule it for later execution. + try { + for (var i = 0; i < len; i++) { + cbs[i].apply(null, args) + } + } finally { + if (cbs.length > len) { + // added more in the interim. + // de-zalgo, just in case, but don't call again. + cbs.splice(0, len) + process.nextTick(function () { + RES.apply(null, args) + }) + } else { + delete reqs[key] + } + } + }) +} - group[styleName] = styles[styleName]; +function slice (args) { + var length = args.length + var array = [] - codes.set(style[0], style[1]); - } + for (var i = 0; i < length; i++) array[i] = args[i] + return array +} - Object.defineProperty(styles, groupName, { - value: group, - enumerable: false - }); - Object.defineProperty(styles, 'codes', { - value: codes, - enumerable: false - }); - } +/***/ }), +/* 224 */ +/***/ (function(module, exports) { - const ansi2ansi = n => n; - const rgb2rgb = (r, g, b) => [r, g, b]; +if (typeof Object.create === 'function') { + // implementation from standard node.js 'util' module + module.exports = function inherits(ctor, superCtor) { + ctor.super_ = superCtor + ctor.prototype = Object.create(superCtor.prototype, { + constructor: { + value: ctor, + enumerable: false, + writable: true, + configurable: true + } + }); + }; +} else { + // old school shim for old browsers + module.exports = function inherits(ctor, superCtor) { + ctor.super_ = superCtor + var TempCtor = function () {} + TempCtor.prototype = superCtor.prototype + ctor.prototype = new TempCtor() + ctor.prototype.constructor = ctor + } +} - styles.color.close = '\u001B[39m'; - styles.bgColor.close = '\u001B[49m'; - styles.color.ansi = { - ansi: wrapAnsi16(ansi2ansi, 0) - }; - styles.color.ansi256 = { - ansi256: wrapAnsi256(ansi2ansi, 0) - }; - styles.color.ansi16m = { - rgb: wrapAnsi16m(rgb2rgb, 0) - }; +/***/ }), +/* 225 */, +/* 226 */, +/* 227 */ +/***/ (function(module, exports, __webpack_require__) { - styles.bgColor.ansi = { - ansi: wrapAnsi16(ansi2ansi, 10) - }; - styles.bgColor.ansi256 = { - ansi256: wrapAnsi256(ansi2ansi, 10) - }; - styles.bgColor.ansi16m = { - rgb: wrapAnsi16m(rgb2rgb, 10) - }; +// @flow - for (let key of Object.keys(colorConvert)) { - if (typeof colorConvert[key] !== 'object') { - continue; - } +/*:: +declare var __webpack_require__: mixed; +*/ - const suite = colorConvert[key]; +module.exports = typeof __webpack_require__ !== "undefined"; - if (key === 'ansi16') { - key = 'ansi'; - } - if ('ansi16' in suite) { - styles.color.ansi[key] = wrapAnsi16(suite.ansi16, 0); - styles.bgColor.ansi[key] = wrapAnsi16(suite.ansi16, 10); - } +/***/ }), +/* 228 */, +/* 229 */ +/***/ (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 + */ - if ('ansi256' in suite) { - styles.color.ansi256[key] = wrapAnsi256(suite.ansi256, 0); - styles.bgColor.ansi256[key] = wrapAnsi256(suite.ansi256, 10); - } +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'; +} - if ('rgb' in suite) { - styles.color.ansi16m[key] = wrapAnsi16m(suite.rgb, 0); - styles.bgColor.ansi16m[key] = wrapAnsi16m(suite.rgb, 10); - } - } +/** + * Long format for `ms`. + * + * @param {Number} ms + * @return {String} + * @api private + */ - return styles; +function fmtLong(ms) { + return plural(ms, d, 'day') || + plural(ms, h, 'hour') || + plural(ms, m, 'minute') || + plural(ms, s, 'second') || + ms + ' ms'; } -// Make the export immutable -Object.defineProperty(module, 'exports', { - enumerable: true, - get: assembleStyles -}); +/** + * 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'; +} -/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(5)(module))) /***/ }), -/* 264 */ +/* 230 */, +/* 231 */, +/* 232 */, +/* 233 */ /***/ (function(module, exports, __webpack_require__) { -"use strict"; - -const os = __webpack_require__(11); -const hasFlag = __webpack_require__(12); +module.exports = rimraf +rimraf.sync = rimrafSync -const env = process.env; +var assert = __webpack_require__(22) +var path = __webpack_require__(0) +var fs = __webpack_require__(3) +var glob = __webpack_require__(75) +var _0666 = parseInt('666', 8) -let forceColor; -if (hasFlag('no-color') || - hasFlag('no-colors') || - hasFlag('color=false')) { - forceColor = false; -} else if (hasFlag('color') || - hasFlag('colors') || - hasFlag('color=true') || - hasFlag('color=always')) { - forceColor = true; -} -if ('FORCE_COLOR' in env) { - forceColor = env.FORCE_COLOR.length === 0 || parseInt(env.FORCE_COLOR, 10) !== 0; +var defaultGlobOpts = { + nosort: true, + silent: true } -function translateLevel(level) { - if (level === 0) { - return false; - } +// for EMFILE handling +var timeout = 0 - return { - level, - hasBasic: true, - has256: level >= 2, - has16m: level >= 3 - }; +var isWindows = (process.platform === "win32") + +function defaults (options) { + var methods = [ + 'unlink', + 'chmod', + 'stat', + 'lstat', + 'rmdir', + 'readdir' + ] + methods.forEach(function(m) { + options[m] = options[m] || fs[m] + m = m + 'Sync' + options[m] = options[m] || fs[m] + }) + + options.maxBusyTries = options.maxBusyTries || 3 + options.emfileWait = options.emfileWait || 1000 + if (options.glob === false) { + options.disableGlob = true + } + options.disableGlob = options.disableGlob || false + options.glob = options.glob || defaultGlobOpts } -function supportsColor(stream) { - if (forceColor === false) { - return 0; - } +function rimraf (p, options, cb) { + if (typeof options === 'function') { + cb = options + options = {} + } - if (hasFlag('color=16m') || - hasFlag('color=full') || - hasFlag('color=truecolor')) { - return 3; - } + assert(p, 'rimraf: missing path') + assert.equal(typeof p, 'string', 'rimraf: path should be a string') + assert.equal(typeof cb, 'function', 'rimraf: callback function required') + assert(options, 'rimraf: invalid options argument provided') + assert.equal(typeof options, 'object', 'rimraf: options should be object') - if (hasFlag('color=256')) { - return 2; - } + defaults(options) - if (stream && !stream.isTTY && forceColor !== true) { - // VS code debugger doesn't have isTTY set - if (env.VSCODE_PID) { - return 1; - } - return 0; - } + var busyTries = 0 + var errState = null + var n = 0 - const min = forceColor ? 1 : 0; + if (options.disableGlob || !glob.hasMagic(p)) + return afterGlob(null, [p]) - if (process.platform === 'win32') { - // Node.js 7.5.0 is the first version of Node.js to include a patch to - // libuv that enables 256 color output on Windows. Anything earlier and it - // won't work. However, here we target Node.js 8 at minimum as it is an LTS - // release, and Node.js 7 is not. Windows 10 build 10586 is the first Windows - // release that supports 256 colors. Windows 10 build 14931 is the first release - // that supports 16m/TrueColor. - const osRelease = os.release().split('.'); - if ( - Number(process.versions.node.split('.')[0]) >= 8 && - Number(osRelease[0]) >= 10 && - Number(osRelease[2]) >= 10586 - ) { - return Number(osRelease[2]) >= 14931 ? 3 : 2; - } + options.lstat(p, function (er, stat) { + if (!er) + return afterGlob(null, [p]) - return 1; - } + glob(p, options.glob, afterGlob) + }) - if ('CI' in env) { - if (['TRAVIS', 'CIRCLECI', 'APPVEYOR', 'GITLAB_CI'].some(sign => sign in env) || env.CI_NAME === 'codeship') { - return 1; - } + function next (er) { + errState = errState || er + if (--n === 0) + cb(errState) + } - return min; - } + function afterGlob (er, results) { + if (er) + return cb(er) - if ('TEAMCITY_VERSION' in env) { - return /^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(env.TEAMCITY_VERSION) ? 1 : 0; - } + n = results.length + if (n === 0) + return cb() - if (env.COLORTERM === 'truecolor') { - return 3; - } + results.forEach(function (p) { + rimraf_(p, options, function CB (er) { + if (er) { + if ((er.code === "EBUSY" || er.code === "ENOTEMPTY" || er.code === "EPERM") && + busyTries < options.maxBusyTries) { + busyTries ++ + var time = busyTries * 100 + // try again, with the same exact callback as this one. + return setTimeout(function () { + rimraf_(p, options, CB) + }, time) + } - if ('TERM_PROGRAM' in env) { - const version = parseInt((env.TERM_PROGRAM_VERSION || '').split('.')[0], 10); + // this one won't happen if graceful-fs is used. + if (er.code === "EMFILE" && timeout < options.emfileWait) { + return setTimeout(function () { + rimraf_(p, options, CB) + }, timeout ++) + } - switch (env.TERM_PROGRAM) { - case 'iTerm.app': - return version >= 3 ? 3 : 2; - case 'Apple_Terminal': - return 2; - // No default - } - } + // already gone + if (er.code === "ENOENT") er = null + } - if (/-256(color)?$/i.test(env.TERM)) { - return 2; - } + timeout = 0 + next(er) + }) + }) + } +} - if (/^screen|^xterm|^vt100|^rxvt|color|ansi|cygwin|linux/i.test(env.TERM)) { - return 1; - } +// Two possible strategies. +// 1. Assume it's a file. unlink it, then do the dir stuff on EPERM or EISDIR +// 2. Assume it's a directory. readdir, then do the file stuff on ENOTDIR +// +// Both result in an extra syscall when you guess wrong. However, there +// are likely far more normal files in the world than directories. This +// is based on the assumption that a the average number of files per +// directory is >= 1. +// +// If anyone ever complains about this, then I guess the strategy could +// be made configurable somehow. But until then, YAGNI. +function rimraf_ (p, options, cb) { + assert(p) + assert(options) + assert(typeof cb === 'function') - if ('COLORTERM' in env) { - return 1; - } + // sunos lets the root user unlink directories, which is... weird. + // so we have to lstat here and make sure it's not a dir. + options.lstat(p, function (er, st) { + if (er && er.code === "ENOENT") + return cb(null) - if (env.TERM === 'dumb') { - return min; - } + // Windows can EPERM on stat. Life is suffering. + if (er && er.code === "EPERM" && isWindows) + fixWinEPERM(p, options, er, cb) - return min; -} + if (st && st.isDirectory()) + return rmdir(p, options, er, cb) -function getSupportLevel(stream) { - const level = supportsColor(stream); - return translateLevel(level); + options.unlink(p, function (er) { + if (er) { + if (er.code === "ENOENT") + return cb(null) + if (er.code === "EPERM") + return (isWindows) + ? fixWinEPERM(p, options, er, cb) + : rmdir(p, options, er, cb) + if (er.code === "EISDIR") + return rmdir(p, options, er, cb) + } + return cb(er) + }) + }) } -module.exports = { - supportsColor: getSupportLevel, - stdout: getSupportLevel(process.stdout), - stderr: getSupportLevel(process.stderr) -}; +function fixWinEPERM (p, options, er, cb) { + assert(p) + assert(options) + assert(typeof cb === 'function') + if (er) + assert(er instanceof Error) + options.chmod(p, _0666, function (er2) { + if (er2) + cb(er2.code === "ENOENT" ? null : er) + else + options.stat(p, function(er3, stats) { + if (er3) + cb(er3.code === "ENOENT" ? null : er) + else if (stats.isDirectory()) + rmdir(p, options, er, cb) + else + options.unlink(p, cb) + }) + }) +} -/***/ }), -/* 265 */ -/***/ (function(module, exports, __webpack_require__) { +function fixWinEPERMSync (p, options, er) { + assert(p) + assert(options) + if (er) + assert(er instanceof Error) -"use strict"; + try { + options.chmodSync(p, _0666) + } catch (er2) { + if (er2.code === "ENOENT") + return + else + throw er + } -const TEMPLATE_REGEX = /(?:\\(u[a-f\d]{4}|x[a-f\d]{2}|.))|(?:\{(~)?(\w+(?:\([^)]*\))?(?:\.\w+(?:\([^)]*\))?)*)(?:[ \t]|(?=\r?\n)))|(\})|((?:.|[\r\n\f])+?)/gi; -const STYLE_REGEX = /(?:^|\.)(\w+)(?:\(([^)]*)\))?/g; -const STRING_REGEX = /^(['"])((?:\\.|(?!\1)[^\\])*)\1$/; -const ESCAPE_REGEX = /\\(u[a-f\d]{4}|x[a-f\d]{2}|.)|([^\\])/gi; + try { + var stats = options.statSync(p) + } catch (er3) { + if (er3.code === "ENOENT") + return + else + throw er + } -const ESCAPES = new Map([ - ['n', '\n'], - ['r', '\r'], - ['t', '\t'], - ['b', '\b'], - ['f', '\f'], - ['v', '\v'], - ['0', '\0'], - ['\\', '\\'], - ['e', '\u001B'], - ['a', '\u0007'] -]); + if (stats.isDirectory()) + rmdirSync(p, options, er) + else + options.unlinkSync(p) +} -function unescape(c) { - if ((c[0] === 'u' && c.length === 5) || (c[0] === 'x' && c.length === 3)) { - return String.fromCharCode(parseInt(c.slice(1), 16)); - } +function rmdir (p, options, originalEr, cb) { + assert(p) + assert(options) + if (originalEr) + assert(originalEr instanceof Error) + assert(typeof cb === 'function') - return ESCAPES.get(c) || c; + // try to rmdir first, and only readdir on ENOTEMPTY or EEXIST (SunOS) + // if we guessed wrong, and it's not a directory, then + // raise the original error. + options.rmdir(p, function (er) { + if (er && (er.code === "ENOTEMPTY" || er.code === "EEXIST" || er.code === "EPERM")) + rmkids(p, options, cb) + else if (er && er.code === "ENOTDIR") + cb(originalEr) + else + cb(er) + }) } -function parseArguments(name, args) { - const results = []; - const chunks = args.trim().split(/\s*,\s*/g); - let matches; - - for (const chunk of chunks) { - if (!isNaN(chunk)) { - results.push(Number(chunk)); - } else if ((matches = chunk.match(STRING_REGEX))) { - results.push(matches[2].replace(ESCAPE_REGEX, (m, escape, chr) => escape ? unescape(escape) : chr)); - } else { - throw new Error(`Invalid Chalk template style argument: ${chunk} (in style '${name}')`); - } - } +function rmkids(p, options, cb) { + assert(p) + assert(options) + assert(typeof cb === 'function') - return results; + options.readdir(p, function (er, files) { + if (er) + return cb(er) + var n = files.length + if (n === 0) + return options.rmdir(p, cb) + var errState + files.forEach(function (f) { + rimraf(path.join(p, f), options, function (er) { + if (errState) + return + if (er) + return cb(errState = er) + if (--n === 0) + options.rmdir(p, cb) + }) + }) + }) } -function parseStyle(style) { - STYLE_REGEX.lastIndex = 0; +// this looks simpler, and is strictly *faster*, but will +// tie up the JavaScript thread and fail on excessively +// deep directory trees. +function rimrafSync (p, options) { + options = options || {} + defaults(options) - const results = []; - let matches; + assert(p, 'rimraf: missing path') + assert.equal(typeof p, 'string', 'rimraf: path should be a string') + assert(options, 'rimraf: missing options') + assert.equal(typeof options, 'object', 'rimraf: options should be object') - while ((matches = STYLE_REGEX.exec(style)) !== null) { - const name = matches[1]; + var results - if (matches[2]) { - const args = parseArguments(name, matches[2]); - results.push([name].concat(args)); - } else { - results.push([name]); - } - } + if (options.disableGlob || !glob.hasMagic(p)) { + results = [p] + } else { + try { + options.lstatSync(p) + results = [p] + } catch (er) { + results = glob.sync(p, options.glob) + } + } - return results; -} + if (!results.length) + return -function buildStyle(chalk, styles) { - const enabled = {}; + for (var i = 0; i < results.length; i++) { + var p = results[i] - for (const layer of styles) { - for (const style of layer.styles) { - enabled[style[0]] = layer.inverse ? null : style.slice(1); - } - } + try { + var st = options.lstatSync(p) + } catch (er) { + if (er.code === "ENOENT") + return - let current = chalk; - for (const styleName of Object.keys(enabled)) { - if (Array.isArray(enabled[styleName])) { - if (!(styleName in current)) { - throw new Error(`Unknown Chalk style: ${styleName}`); - } + // Windows can EPERM on stat. Life is suffering. + if (er.code === "EPERM" && isWindows) + fixWinEPERMSync(p, options, er) + } - if (enabled[styleName].length > 0) { - current = current[styleName].apply(current, enabled[styleName]); - } else { - current = current[styleName]; - } - } - } + try { + // sunos lets the root user unlink directories, which is... weird. + if (st && st.isDirectory()) + rmdirSync(p, options, null) + else + options.unlinkSync(p) + } catch (er) { + if (er.code === "ENOENT") + return + if (er.code === "EPERM") + return isWindows ? fixWinEPERMSync(p, options, er) : rmdirSync(p, options, er) + if (er.code !== "EISDIR") + throw er - return current; + rmdirSync(p, options, er) + } + } } -module.exports = (chalk, tmp) => { - const styles = []; - const chunks = []; - let chunk = []; - - // eslint-disable-next-line max-params - tmp.replace(TEMPLATE_REGEX, (m, escapeChar, inverse, style, close, chr) => { - if (escapeChar) { - chunk.push(unescape(escapeChar)); - } else if (style) { - const str = chunk.join(''); - chunk = []; - chunks.push(styles.length === 0 ? str : buildStyle(chalk, styles)(str)); - styles.push({inverse, styles: parseStyle(style)}); - } else if (close) { - if (styles.length === 0) { - throw new Error('Found extraneous } in Chalk template literal'); - } - - chunks.push(buildStyle(chalk, styles)(chunk.join(''))); - chunk = []; - styles.pop(); - } else { - chunk.push(chr); - } - }); +function rmdirSync (p, options, originalEr) { + assert(p) + assert(options) + if (originalEr) + assert(originalEr instanceof Error) - chunks.push(chunk.join('')); + try { + options.rmdirSync(p) + } catch (er) { + if (er.code === "ENOENT") + return + if (er.code === "ENOTDIR") + throw originalEr + if (er.code === "ENOTEMPTY" || er.code === "EEXIST" || er.code === "EPERM") + rmkidsSync(p, options) + } +} - if (styles.length > 0) { - const errMsg = `Chalk template literal is missing ${styles.length} closing bracket${styles.length === 1 ? '' : 's'} (\`}\`)`; - throw new Error(errMsg); - } +function rmkidsSync (p, options) { + assert(p) + assert(options) + options.readdirSync(p).forEach(function (f) { + rimrafSync(path.join(p, f), options) + }) - return chunks.join(''); -}; + // We only end up here once we got ENOTEMPTY at least once, and + // at this point, we are guaranteed to have removed all the kids. + // So, we know that it won't be ENOENT or ENOTDIR or anything else. + // try really hard to delete stuff on windows, because it has a + // PROFOUNDLY annoying habit of not closing handles promptly when + // files are deleted, resulting in spurious ENOTEMPTY errors. + var retries = isWindows ? 100 : 1 + var i = 0 + do { + var threw = true + try { + var ret = options.rmdirSync(p, options) + threw = false + return ret + } finally { + if (++i < retries && threw) + continue + } + } while (true) +} /***/ }), -/* 266 */ +/* 234 */, +/* 235 */, +/* 236 */, +/* 237 */, +/* 238 */, +/* 239 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const restoreCursor = __webpack_require__(267); - -let hidden = false; - -exports.show = stream => { - const s = stream || process.stderr; +var hasFlag = __webpack_require__(221); - if (!s.isTTY) { - return; +var support = function (level) { + if (level === 0) { + return false; } - hidden = false; - s.write('\u001b[?25h'); + return { + level: level, + hasBasic: true, + has256: level >= 2, + has16m: level >= 3 + }; }; -exports.hide = stream => { - const s = stream || process.stderr; - - if (!s.isTTY) { - return; +var supportLevel = (function () { + if (hasFlag('no-color') || + hasFlag('no-colors') || + hasFlag('color=false')) { + return 0; } - restoreCursor(); - hidden = true; - s.write('\u001b[?25l'); -}; - -exports.toggle = (force, stream) => { - if (force !== undefined) { - hidden = force; + if (hasFlag('color=16m') || + hasFlag('color=full') || + hasFlag('color=truecolor')) { + return 3; } - if (hidden) { - exports.show(stream); - } else { - exports.hide(stream); + if (hasFlag('color=256')) { + return 2; } -}; - - -/***/ }), -/* 267 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - -const onetime = __webpack_require__(268); -const signalExit = __webpack_require__(110); -module.exports = onetime(() => { - signalExit(() => { - process.stderr.write('\u001b[?25h'); - }, {alwaysLast: true}); -}); - - -/***/ }), -/* 268 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - -const mimicFn = __webpack_require__(269); - -module.exports = (fn, opts) => { - // TODO: Remove this in v3 - if (opts === true) { - throw new TypeError('The second argument is now an options object'); + if (hasFlag('color') || + hasFlag('colors') || + hasFlag('color=true') || + hasFlag('color=always')) { + return 1; } - if (typeof fn !== 'function') { - throw new TypeError('Expected a function'); + if (process.stdout && !process.stdout.isTTY) { + return 0; } - opts = opts || {}; - - let ret; - let called = false; - const fnName = fn.displayName || fn.name || ''; - - const onetime = function () { - if (called) { - if (opts.throw === true) { - throw new Error(`Function \`${fnName}\` can only be called once`); - } + if (process.platform === 'win32') { + return 1; + } - return ret; + if ('CI' in process.env) { + if ('TRAVIS' in process.env || process.env.CI === 'Travis') { + return 1; } - called = true; - ret = fn.apply(this, arguments); - fn = null; - - return ret; - }; - - mimicFn(onetime, fn); - - return onetime; -}; + return 0; + } + if ('TEAMCITY_VERSION' in process.env) { + return process.env.TEAMCITY_VERSION.match(/^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/) === null ? 0 : 1; + } -/***/ }), -/* 269 */ -/***/ (function(module, exports, __webpack_require__) { + if (/^(screen|xterm)-256(?:color)?/.test(process.env.TERM)) { + return 2; + } -"use strict"; + if (/^screen|^xterm|^vt100|color|ansi|cygwin|linux/i.test(process.env.TERM)) { + return 1; + } -module.exports = (to, from) => { - // TODO: use `Reflect.ownKeys()` when targeting Node.js 6 - for (const prop of Object.getOwnPropertyNames(from).concat(Object.getOwnPropertySymbols(from))) { - Object.defineProperty(to, prop, Object.getOwnPropertyDescriptor(from, prop)); + if ('COLORTERM' in process.env) { + return 1; } - return to; -}; + if (process.env.TERM === 'dumb') { + return 0; + } + return 0; +})(); -/***/ }), -/* 270 */ -/***/ (function(module, exports, __webpack_require__) { +if (supportLevel === 0 && 'FORCE_COLOR' in process.env) { + supportLevel = 1; +} -"use strict"; +module.exports = process && support(supportLevel); -module.exports = __webpack_require__(271); +/***/ }) +/******/ ]); /***/ }), -/* 271 */ -/***/ (function(module) { +/* 585 */ +/***/ (function(module, exports) { -module.exports = JSON.parse("{\"dots\":{\"interval\":80,\"frames\":[\"⠋\",\"⠙\",\"⠹\",\"⠸\",\"⠼\",\"⠴\",\"⠦\",\"⠧\",\"⠇\",\"⠏\"]},\"dots2\":{\"interval\":80,\"frames\":[\"⣾\",\"⣽\",\"⣻\",\"⢿\",\"⡿\",\"⣟\",\"⣯\",\"⣷\"]},\"dots3\":{\"interval\":80,\"frames\":[\"⠋\",\"⠙\",\"⠚\",\"⠞\",\"⠖\",\"⠦\",\"⠴\",\"⠲\",\"⠳\",\"⠓\"]},\"dots4\":{\"interval\":80,\"frames\":[\"⠄\",\"⠆\",\"⠇\",\"⠋\",\"⠙\",\"⠸\",\"⠰\",\"⠠\",\"⠰\",\"⠸\",\"⠙\",\"⠋\",\"⠇\",\"⠆\"]},\"dots5\":{\"interval\":80,\"frames\":[\"⠋\",\"⠙\",\"⠚\",\"⠒\",\"⠂\",\"⠂\",\"⠒\",\"⠲\",\"⠴\",\"⠦\",\"⠖\",\"⠒\",\"⠐\",\"⠐\",\"⠒\",\"⠓\",\"⠋\"]},\"dots6\":{\"interval\":80,\"frames\":[\"⠁\",\"⠉\",\"⠙\",\"⠚\",\"⠒\",\"⠂\",\"⠂\",\"⠒\",\"⠲\",\"⠴\",\"⠤\",\"⠄\",\"⠄\",\"⠤\",\"⠴\",\"⠲\",\"⠒\",\"⠂\",\"⠂\",\"⠒\",\"⠚\",\"⠙\",\"⠉\",\"⠁\"]},\"dots7\":{\"interval\":80,\"frames\":[\"⠈\",\"⠉\",\"⠋\",\"⠓\",\"⠒\",\"⠐\",\"⠐\",\"⠒\",\"⠖\",\"⠦\",\"⠤\",\"⠠\",\"⠠\",\"⠤\",\"⠦\",\"⠖\",\"⠒\",\"⠐\",\"⠐\",\"⠒\",\"⠓\",\"⠋\",\"⠉\",\"⠈\"]},\"dots8\":{\"interval\":80,\"frames\":[\"⠁\",\"⠁\",\"⠉\",\"⠙\",\"⠚\",\"⠒\",\"⠂\",\"⠂\",\"⠒\",\"⠲\",\"⠴\",\"⠤\",\"⠄\",\"⠄\",\"⠤\",\"⠠\",\"⠠\",\"⠤\",\"⠦\",\"⠖\",\"⠒\",\"⠐\",\"⠐\",\"⠒\",\"⠓\",\"⠋\",\"⠉\",\"⠈\",\"⠈\"]},\"dots9\":{\"interval\":80,\"frames\":[\"⢹\",\"⢺\",\"⢼\",\"⣸\",\"⣇\",\"⡧\",\"⡗\",\"⡏\"]},\"dots10\":{\"interval\":80,\"frames\":[\"⢄\",\"⢂\",\"⢁\",\"⡁\",\"⡈\",\"⡐\",\"⡠\"]},\"dots11\":{\"interval\":100,\"frames\":[\"⠁\",\"⠂\",\"⠄\",\"⡀\",\"⢀\",\"⠠\",\"⠐\",\"⠈\"]},\"dots12\":{\"interval\":80,\"frames\":[\"⢀⠀\",\"⡀⠀\",\"⠄⠀\",\"⢂⠀\",\"⡂⠀\",\"⠅⠀\",\"⢃⠀\",\"⡃⠀\",\"⠍⠀\",\"⢋⠀\",\"⡋⠀\",\"⠍⠁\",\"⢋⠁\",\"⡋⠁\",\"⠍⠉\",\"⠋⠉\",\"⠋⠉\",\"⠉⠙\",\"⠉⠙\",\"⠉⠩\",\"⠈⢙\",\"⠈⡙\",\"⢈⠩\",\"⡀⢙\",\"⠄⡙\",\"⢂⠩\",\"⡂⢘\",\"⠅⡘\",\"⢃⠨\",\"⡃⢐\",\"⠍⡐\",\"⢋⠠\",\"⡋⢀\",\"⠍⡁\",\"⢋⠁\",\"⡋⠁\",\"⠍⠉\",\"⠋⠉\",\"⠋⠉\",\"⠉⠙\",\"⠉⠙\",\"⠉⠩\",\"⠈⢙\",\"⠈⡙\",\"⠈⠩\",\"⠀⢙\",\"⠀⡙\",\"⠀⠩\",\"⠀⢘\",\"⠀⡘\",\"⠀⠨\",\"⠀⢐\",\"⠀⡐\",\"⠀⠠\",\"⠀⢀\",\"⠀⡀\"]},\"line\":{\"interval\":130,\"frames\":[\"-\",\"\\\\\",\"|\",\"/\"]},\"line2\":{\"interval\":100,\"frames\":[\"⠂\",\"-\",\"–\",\"—\",\"–\",\"-\"]},\"pipe\":{\"interval\":100,\"frames\":[\"┤\",\"┘\",\"┴\",\"└\",\"├\",\"┌\",\"┬\",\"┐\"]},\"simpleDots\":{\"interval\":400,\"frames\":[\". \",\".. \",\"...\",\" \"]},\"simpleDotsScrolling\":{\"interval\":200,\"frames\":[\". \",\".. \",\"...\",\" ..\",\" .\",\" \"]},\"star\":{\"interval\":70,\"frames\":[\"✶\",\"✸\",\"✹\",\"✺\",\"✹\",\"✷\"]},\"star2\":{\"interval\":80,\"frames\":[\"+\",\"x\",\"*\"]},\"flip\":{\"interval\":70,\"frames\":[\"_\",\"_\",\"_\",\"-\",\"`\",\"`\",\"'\",\"´\",\"-\",\"_\",\"_\",\"_\"]},\"hamburger\":{\"interval\":100,\"frames\":[\"☱\",\"☲\",\"☴\"]},\"growVertical\":{\"interval\":120,\"frames\":[\"▁\",\"▃\",\"▄\",\"▅\",\"▆\",\"▇\",\"▆\",\"▅\",\"▄\",\"▃\"]},\"growHorizontal\":{\"interval\":120,\"frames\":[\"▏\",\"▎\",\"▍\",\"▌\",\"▋\",\"▊\",\"▉\",\"▊\",\"▋\",\"▌\",\"▍\",\"▎\"]},\"balloon\":{\"interval\":140,\"frames\":[\" \",\".\",\"o\",\"O\",\"@\",\"*\",\" \"]},\"balloon2\":{\"interval\":120,\"frames\":[\".\",\"o\",\"O\",\"°\",\"O\",\"o\",\".\"]},\"noise\":{\"interval\":100,\"frames\":[\"▓\",\"▒\",\"░\"]},\"bounce\":{\"interval\":120,\"frames\":[\"⠁\",\"⠂\",\"⠄\",\"⠂\"]},\"boxBounce\":{\"interval\":120,\"frames\":[\"▖\",\"▘\",\"▝\",\"▗\"]},\"boxBounce2\":{\"interval\":100,\"frames\":[\"▌\",\"▀\",\"▐\",\"▄\"]},\"triangle\":{\"interval\":50,\"frames\":[\"◢\",\"◣\",\"◤\",\"◥\"]},\"arc\":{\"interval\":100,\"frames\":[\"◜\",\"◠\",\"◝\",\"◞\",\"◡\",\"◟\"]},\"circle\":{\"interval\":120,\"frames\":[\"◡\",\"⊙\",\"◠\"]},\"squareCorners\":{\"interval\":180,\"frames\":[\"◰\",\"◳\",\"◲\",\"◱\"]},\"circleQuarters\":{\"interval\":120,\"frames\":[\"◴\",\"◷\",\"◶\",\"◵\"]},\"circleHalves\":{\"interval\":50,\"frames\":[\"◐\",\"◓\",\"◑\",\"◒\"]},\"squish\":{\"interval\":100,\"frames\":[\"╫\",\"╪\"]},\"toggle\":{\"interval\":250,\"frames\":[\"⊶\",\"⊷\"]},\"toggle2\":{\"interval\":80,\"frames\":[\"▫\",\"▪\"]},\"toggle3\":{\"interval\":120,\"frames\":[\"□\",\"■\"]},\"toggle4\":{\"interval\":100,\"frames\":[\"■\",\"□\",\"▪\",\"▫\"]},\"toggle5\":{\"interval\":100,\"frames\":[\"▮\",\"▯\"]},\"toggle6\":{\"interval\":300,\"frames\":[\"ဝ\",\"၀\"]},\"toggle7\":{\"interval\":80,\"frames\":[\"⦾\",\"⦿\"]},\"toggle8\":{\"interval\":100,\"frames\":[\"◍\",\"◌\"]},\"toggle9\":{\"interval\":100,\"frames\":[\"◉\",\"◎\"]},\"toggle10\":{\"interval\":100,\"frames\":[\"㊂\",\"㊀\",\"㊁\"]},\"toggle11\":{\"interval\":50,\"frames\":[\"⧇\",\"⧆\"]},\"toggle12\":{\"interval\":120,\"frames\":[\"☗\",\"☖\"]},\"toggle13\":{\"interval\":80,\"frames\":[\"=\",\"*\",\"-\"]},\"arrow\":{\"interval\":100,\"frames\":[\"←\",\"↖\",\"↑\",\"↗\",\"→\",\"↘\",\"↓\",\"↙\"]},\"arrow2\":{\"interval\":80,\"frames\":[\"⬆️ \",\"↗️ \",\"➡️ \",\"↘️ \",\"⬇️ \",\"↙️ \",\"⬅️ \",\"↖️ \"]},\"arrow3\":{\"interval\":120,\"frames\":[\"▹▹▹▹▹\",\"▸▹▹▹▹\",\"▹▸▹▹▹\",\"▹▹▸▹▹\",\"▹▹▹▸▹\",\"▹▹▹▹▸\"]},\"bouncingBar\":{\"interval\":80,\"frames\":[\"[ ]\",\"[= ]\",\"[== ]\",\"[=== ]\",\"[ ===]\",\"[ ==]\",\"[ =]\",\"[ ]\",\"[ =]\",\"[ ==]\",\"[ ===]\",\"[====]\",\"[=== ]\",\"[== ]\",\"[= ]\"]},\"bouncingBall\":{\"interval\":80,\"frames\":[\"( ● )\",\"( ● )\",\"( ● )\",\"( ● )\",\"( ●)\",\"( ● )\",\"( ● )\",\"( ● )\",\"( ● )\",\"(● )\"]},\"smiley\":{\"interval\":200,\"frames\":[\"😄 \",\"😝 \"]},\"monkey\":{\"interval\":300,\"frames\":[\"🙈 \",\"🙈 \",\"🙉 \",\"🙊 \"]},\"hearts\":{\"interval\":100,\"frames\":[\"💛 \",\"💙 \",\"💜 \",\"💚 \",\"❤️ \"]},\"clock\":{\"interval\":100,\"frames\":[\"🕐 \",\"🕑 \",\"🕒 \",\"🕓 \",\"🕔 \",\"🕕 \",\"🕖 \",\"🕗 \",\"🕘 \",\"🕙 \",\"🕚 \"]},\"earth\":{\"interval\":180,\"frames\":[\"🌍 \",\"🌎 \",\"🌏 \"]},\"moon\":{\"interval\":80,\"frames\":[\"🌑 \",\"🌒 \",\"🌓 \",\"🌔 \",\"🌕 \",\"🌖 \",\"🌗 \",\"🌘 \"]},\"runner\":{\"interval\":140,\"frames\":[\"🚶 \",\"🏃 \"]},\"pong\":{\"interval\":80,\"frames\":[\"▐⠂ ▌\",\"▐⠈ ▌\",\"▐ ⠂ ▌\",\"▐ ⠠ ▌\",\"▐ ⡀ ▌\",\"▐ ⠠ ▌\",\"▐ ⠂ ▌\",\"▐ ⠈ ▌\",\"▐ ⠂ ▌\",\"▐ ⠠ ▌\",\"▐ ⡀ ▌\",\"▐ ⠠ ▌\",\"▐ ⠂ ▌\",\"▐ ⠈ ▌\",\"▐ ⠂▌\",\"▐ ⠠▌\",\"▐ ⡀▌\",\"▐ ⠠ ▌\",\"▐ ⠂ ▌\",\"▐ ⠈ ▌\",\"▐ ⠂ ▌\",\"▐ ⠠ ▌\",\"▐ ⡀ ▌\",\"▐ ⠠ ▌\",\"▐ ⠂ ▌\",\"▐ ⠈ ▌\",\"▐ ⠂ ▌\",\"▐ ⠠ ▌\",\"▐ ⡀ ▌\",\"▐⠠ ▌\"]},\"shark\":{\"interval\":120,\"frames\":[\"▐|\\\\____________▌\",\"▐_|\\\\___________▌\",\"▐__|\\\\__________▌\",\"▐___|\\\\_________▌\",\"▐____|\\\\________▌\",\"▐_____|\\\\_______▌\",\"▐______|\\\\______▌\",\"▐_______|\\\\_____▌\",\"▐________|\\\\____▌\",\"▐_________|\\\\___▌\",\"▐__________|\\\\__▌\",\"▐___________|\\\\_▌\",\"▐____________|\\\\▌\",\"▐____________/|▌\",\"▐___________/|_▌\",\"▐__________/|__▌\",\"▐_________/|___▌\",\"▐________/|____▌\",\"▐_______/|_____▌\",\"▐______/|______▌\",\"▐_____/|_______▌\",\"▐____/|________▌\",\"▐___/|_________▌\",\"▐__/|__________▌\",\"▐_/|___________▌\",\"▐/|____________▌\"]},\"dqpb\":{\"interval\":100,\"frames\":[\"d\",\"q\",\"p\",\"b\"]},\"weather\":{\"interval\":100,\"frames\":[\"☀️ \",\"☀️ \",\"☀️ \",\"🌤 \",\"⛅️ \",\"🌥 \",\"☁️ \",\"🌧 \",\"🌨 \",\"🌧 \",\"🌨 \",\"🌧 \",\"🌨 \",\"⛈ \",\"🌨 \",\"🌧 \",\"🌨 \",\"☁️ \",\"🌥 \",\"⛅️ \",\"🌤 \",\"☀️ \",\"☀️ \"]},\"christmas\":{\"interval\":400,\"frames\":[\"🌲\",\"🎄\"]}}"); +module.exports = require("buffer"); /***/ }), -/* 272 */ +/* 586 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "RunCommand", function() { return RunCommand; }); -/* harmony import */ var chalk__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2); -/* harmony import */ var chalk__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(chalk__WEBPACK_IMPORTED_MODULE_0__); -/* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(34); -/* harmony import */ var _utils_parallelize__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(35); -/* harmony import */ var _utils_projects__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(36); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "BootstrapCacheFile", function() { return BootstrapCacheFile; }); +/* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(23); +/* harmony import */ var fs__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(fs__WEBPACK_IMPORTED_MODULE_0__); +/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(16); +/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_1__); +function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } + /* * Licensed to Elasticsearch B.V. under one or more contributor * license agreements. See the NOTICE file distributed with @@ -33080,138 +68888,87 @@ __webpack_require__.r(__webpack_exports__); */ +class BootstrapCacheFile { + constructor(kbn, project, checksums) { + _defineProperty(this, "path", void 0); + _defineProperty(this, "expectedValue", void 0); -const RunCommand = { - description: 'Run script defined in package.json in each package that contains that script.', - name: 'run', - - async run(projects, projectGraph, { - extraArgs - }) { - const batchedProjects = Object(_utils_projects__WEBPACK_IMPORTED_MODULE_3__["topologicallyBatchProjects"])(projects, projectGraph); + this.path = path__WEBPACK_IMPORTED_MODULE_1___default.a.resolve(project.targetLocation, '.bootstrap-cache'); - if (extraArgs.length === 0) { - _utils_log__WEBPACK_IMPORTED_MODULE_1__["log"].write(chalk__WEBPACK_IMPORTED_MODULE_0___default.a.red.bold('\nNo script specified')); - process.exit(1); + if (!checksums) { + return; } - const scriptName = extraArgs[0]; - const scriptArgs = extraArgs.slice(1); - _utils_log__WEBPACK_IMPORTED_MODULE_1__["log"].write(chalk__WEBPACK_IMPORTED_MODULE_0___default.a.bold(`\nRunning script [${chalk__WEBPACK_IMPORTED_MODULE_0___default.a.green(scriptName)}] in batched topological order\n`)); - await Object(_utils_parallelize__WEBPACK_IMPORTED_MODULE_2__["parallelizeBatches"])(batchedProjects, async pkg => { - if (pkg.hasScript(scriptName)) { - await pkg.runScriptStreaming(scriptName, scriptArgs); - } - }); - } - -}; - -/***/ }), -/* 273 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { - -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "WatchCommand", function() { return WatchCommand; }); -/* harmony import */ var chalk__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2); -/* harmony import */ var chalk__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(chalk__WEBPACK_IMPORTED_MODULE_0__); -/* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(34); -/* harmony import */ var _utils_parallelize__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(35); -/* harmony import */ var _utils_projects__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(36); -/* harmony import */ var _utils_watch__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(274); -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - - - - + const projectAndDepCacheKeys = Array.from(kbn.getProjectAndDeps(project.name).values()) // sort deps by name so that the key is stable + .sort((a, b) => a.name.localeCompare(b.name)) // get the cacheKey for each project, return undefined if the cache key couldn't be determined + .map(p => { + const cacheKey = checksums.get(p.name); + if (cacheKey) { + return `${p.name}:${cacheKey}`; + } + }); // if any of the relevant cache keys are undefined then the projectCacheKey must be too -/** - * Name of the script in the package/project package.json file to run during `kbn watch`. - */ -const watchScriptName = 'kbn:watch'; -/** - * Name of the Kibana project. - */ + this.expectedValue = projectAndDepCacheKeys.some(k => !k) ? undefined : [`# this is only human readable for debugging, please don't try to parse this`, ...projectAndDepCacheKeys].join('\n'); + } -const kibanaProjectName = 'kibana'; -/** - * Command that traverses through list of available projects/packages that have `kbn:watch` script in their - * package.json files, groups them into topology aware batches and then processes theses batches one by one - * running `kbn:watch` scripts in parallel within the same batch. - * - * Command internally relies on the fact that most of the build systems that are triggered by `kbn:watch` - * will emit special "marker" once build/watch process is ready that we can use as completion condition for - * the `kbn:watch` script and eventually for the entire batch. Currently we support completion "markers" for - * `webpack` and `tsc` only, for the rest we rely on predefined timeouts. - */ + isValid() { + if (!this.expectedValue) { + return false; + } -const WatchCommand = { - description: 'Runs `kbn:watch` script for every project.', - name: 'watch', + try { + return fs__WEBPACK_IMPORTED_MODULE_0___default.a.readFileSync(this.path, 'utf8') === this.expectedValue; + } catch (error) { + if (error.code === 'ENOENT') { + return false; + } - async run(projects, projectGraph) { - const projectsToWatch = new Map(); + throw error; + } + } - for (const project of projects.values()) { - // We can't watch project that doesn't have `kbn:watch` script. - if (project.hasScript(watchScriptName)) { - projectsToWatch.set(project.name, project); + delete() { + try { + fs__WEBPACK_IMPORTED_MODULE_0___default.a.unlinkSync(this.path); + } catch (error) { + if (error.code !== 'ENOENT') { + throw error; } } + } - if (projectsToWatch.size === 0) { - _utils_log__WEBPACK_IMPORTED_MODULE_1__["log"].write(chalk__WEBPACK_IMPORTED_MODULE_0___default.a.red(`\nThere are no projects to watch found. Make sure that projects define 'kbn:watch' script in 'package.json'.\n`)); + write() { + if (!this.expectedValue) { return; } - const projectNames = Array.from(projectsToWatch.keys()); - _utils_log__WEBPACK_IMPORTED_MODULE_1__["log"].write(chalk__WEBPACK_IMPORTED_MODULE_0___default.a.bold(chalk__WEBPACK_IMPORTED_MODULE_0___default.a.green(`Running ${watchScriptName} scripts for [${projectNames.join(', ')}].`))); // Kibana should always be run the last, so we don't rely on automatic - // topological batching and push it to the last one-entry batch manually. - - const shouldWatchKibanaProject = projectsToWatch.delete(kibanaProjectName); - const batchedProjects = Object(_utils_projects__WEBPACK_IMPORTED_MODULE_3__["topologicallyBatchProjects"])(projectsToWatch, projectGraph); - - if (shouldWatchKibanaProject) { - batchedProjects.push([projects.get(kibanaProjectName)]); - } - - await Object(_utils_parallelize__WEBPACK_IMPORTED_MODULE_2__["parallelizeBatches"])(batchedProjects, async pkg => { - const completionHint = await Object(_utils_watch__WEBPACK_IMPORTED_MODULE_4__["waitUntilWatchIsReady"])(pkg.runScriptStreaming(watchScriptName).stdout); - _utils_log__WEBPACK_IMPORTED_MODULE_1__["log"].write(chalk__WEBPACK_IMPORTED_MODULE_0___default.a.bold(`[${chalk__WEBPACK_IMPORTED_MODULE_0___default.a.green(pkg.name)}] Initial build completed (${completionHint}).`)); + fs__WEBPACK_IMPORTED_MODULE_0___default.a.mkdirSync(path__WEBPACK_IMPORTED_MODULE_1___default.a.dirname(this.path), { + recursive: true }); + fs__WEBPACK_IMPORTED_MODULE_0___default.a.writeFileSync(this.path, this.expectedValue); } -}; +} /***/ }), -/* 274 */ +/* 587 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "waitUntilWatchIsReady", function() { return waitUntilWatchIsReady; }); -/* harmony import */ var rxjs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(275); -/* harmony import */ var rxjs_operators__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(377); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "CleanCommand", function() { return CleanCommand; }); +/* harmony import */ var chalk__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2); +/* harmony import */ var chalk__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(chalk__WEBPACK_IMPORTED_MODULE_0__); +/* harmony import */ var del__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(588); +/* harmony import */ var del__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(del__WEBPACK_IMPORTED_MODULE_1__); +/* harmony import */ var ora__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(675); +/* harmony import */ var ora__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(ora__WEBPACK_IMPORTED_MODULE_2__); +/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(16); +/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_3___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_3__); +/* harmony import */ var _utils_fs__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(20); +/* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(34); /* * Licensed to Elasticsearch B.V. under one or more contributor * license agreements. See the NOTICE file distributed with @@ -33232,11721 +68989,10044 @@ __webpack_require__.r(__webpack_exports__); */ -/** - * Number of milliseconds we wait before we fall back to the default watch handler. - */ - -const defaultHandlerDelay = 3000; -/** - * If default watch handler is used, then it's the number of milliseconds we wait for - * any build output before we consider watch task ready. - */ - -const defaultHandlerReadinessTimeout = 2000; -/** - * Describes configurable watch options. - */ - -function getWatchHandlers(buildOutput$, { - handlerDelay = defaultHandlerDelay, - handlerReadinessTimeout = defaultHandlerReadinessTimeout -}) { - const typescriptHandler = buildOutput$.pipe(Object(rxjs_operators__WEBPACK_IMPORTED_MODULE_1__["first"])(data => data.includes('$ tsc')), Object(rxjs_operators__WEBPACK_IMPORTED_MODULE_1__["map"])(() => buildOutput$.pipe(Object(rxjs_operators__WEBPACK_IMPORTED_MODULE_1__["first"])(data => data.includes('Compilation complete.')), Object(rxjs_operators__WEBPACK_IMPORTED_MODULE_1__["mapTo"])('tsc')))); - const webpackHandler = buildOutput$.pipe(Object(rxjs_operators__WEBPACK_IMPORTED_MODULE_1__["first"])(data => data.includes('$ webpack')), Object(rxjs_operators__WEBPACK_IMPORTED_MODULE_1__["map"])(() => buildOutput$.pipe(Object(rxjs_operators__WEBPACK_IMPORTED_MODULE_1__["first"])(data => data.includes('Chunk Names')), Object(rxjs_operators__WEBPACK_IMPORTED_MODULE_1__["mapTo"])('webpack')))); - const defaultHandler = rxjs__WEBPACK_IMPORTED_MODULE_0__["of"](undefined).pipe(Object(rxjs_operators__WEBPACK_IMPORTED_MODULE_1__["delay"])(handlerReadinessTimeout), Object(rxjs_operators__WEBPACK_IMPORTED_MODULE_1__["map"])(() => buildOutput$.pipe(Object(rxjs_operators__WEBPACK_IMPORTED_MODULE_1__["timeout"])(handlerDelay), Object(rxjs_operators__WEBPACK_IMPORTED_MODULE_1__["catchError"])(() => rxjs__WEBPACK_IMPORTED_MODULE_0__["of"]('timeout'))))); - return [typescriptHandler, webpackHandler, defaultHandler]; -} - -function waitUntilWatchIsReady(stream, opts = {}) { - const buildOutput$ = new rxjs__WEBPACK_IMPORTED_MODULE_0__["Subject"](); - - const onDataListener = data => buildOutput$.next(data.toString('utf-8')); - - const onEndListener = () => buildOutput$.complete(); - - const onErrorListener = e => buildOutput$.error(e); - - stream.once('end', onEndListener); - stream.once('error', onErrorListener); - stream.on('data', onDataListener); - return rxjs__WEBPACK_IMPORTED_MODULE_0__["race"](getWatchHandlers(buildOutput$, opts)).pipe(Object(rxjs_operators__WEBPACK_IMPORTED_MODULE_1__["mergeMap"])(whenReady => whenReady), Object(rxjs_operators__WEBPACK_IMPORTED_MODULE_1__["finalize"])(() => { - stream.removeListener('data', onDataListener); - stream.removeListener('end', onEndListener); - stream.removeListener('error', onErrorListener); - buildOutput$.complete(); - })).toPromise(); -} - -/***/ }), -/* 275 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { - -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony import */ var _internal_Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "Observable", function() { return _internal_Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"]; }); -/* harmony import */ var _internal_observable_ConnectableObservable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(293); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "ConnectableObservable", function() { return _internal_observable_ConnectableObservable__WEBPACK_IMPORTED_MODULE_1__["ConnectableObservable"]; }); -/* harmony import */ var _internal_operators_groupBy__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(298); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "GroupedObservable", function() { return _internal_operators_groupBy__WEBPACK_IMPORTED_MODULE_2__["GroupedObservable"]; }); -/* harmony import */ var _internal_symbol_observable__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(290); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "observable", function() { return _internal_symbol_observable__WEBPACK_IMPORTED_MODULE_3__["observable"]; }); -/* harmony import */ var _internal_Subject__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(294); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "Subject", function() { return _internal_Subject__WEBPACK_IMPORTED_MODULE_4__["Subject"]; }); +const CleanCommand = { + description: 'Remove the node_modules and target directories from all projects.', + name: 'clean', -/* harmony import */ var _internal_BehaviorSubject__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(299); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "BehaviorSubject", function() { return _internal_BehaviorSubject__WEBPACK_IMPORTED_MODULE_5__["BehaviorSubject"]; }); + async run(projects) { + const toDelete = []; -/* harmony import */ var _internal_ReplaySubject__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(300); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "ReplaySubject", function() { return _internal_ReplaySubject__WEBPACK_IMPORTED_MODULE_6__["ReplaySubject"]; }); + for (const project of projects.values()) { + if (await Object(_utils_fs__WEBPACK_IMPORTED_MODULE_4__["isDirectory"])(project.nodeModulesLocation)) { + toDelete.push({ + cwd: project.path, + pattern: Object(path__WEBPACK_IMPORTED_MODULE_3__["relative"])(project.path, project.nodeModulesLocation) + }); + } -/* harmony import */ var _internal_AsyncSubject__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(317); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "AsyncSubject", function() { return _internal_AsyncSubject__WEBPACK_IMPORTED_MODULE_7__["AsyncSubject"]; }); + if (await Object(_utils_fs__WEBPACK_IMPORTED_MODULE_4__["isDirectory"])(project.targetLocation)) { + toDelete.push({ + cwd: project.path, + pattern: Object(path__WEBPACK_IMPORTED_MODULE_3__["relative"])(project.path, project.targetLocation) + }); + } -/* harmony import */ var _internal_scheduler_asap__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(318); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "asapScheduler", function() { return _internal_scheduler_asap__WEBPACK_IMPORTED_MODULE_8__["asap"]; }); + const { + extraPatterns + } = project.getCleanConfig(); -/* harmony import */ var _internal_scheduler_async__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(322); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "asyncScheduler", function() { return _internal_scheduler_async__WEBPACK_IMPORTED_MODULE_9__["async"]; }); + if (extraPatterns) { + toDelete.push({ + cwd: project.path, + pattern: extraPatterns + }); + } + } -/* harmony import */ var _internal_scheduler_queue__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(301); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "queueScheduler", function() { return _internal_scheduler_queue__WEBPACK_IMPORTED_MODULE_10__["queue"]; }); + if (toDelete.length === 0) { + _utils_log__WEBPACK_IMPORTED_MODULE_5__["log"].write(chalk__WEBPACK_IMPORTED_MODULE_0___default.a.bold.green('\n\nNothing to delete')); + } else { + _utils_log__WEBPACK_IMPORTED_MODULE_5__["log"].write(chalk__WEBPACK_IMPORTED_MODULE_0___default.a.bold.red('\n\nDeleting:\n')); + /** + * In order to avoid patterns like `/build` in packages from accidentally + * impacting files outside the package we use `process.chdir()` to change + * the cwd to the package and execute `del()` without the `force` option + * so it will check that each file being deleted is within the package. + * + * `del()` does support a `cwd` option, but it's only for resolving the + * patterns and does not impact the cwd check. + */ -/* harmony import */ var _internal_scheduler_animationFrame__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(323); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "animationFrameScheduler", function() { return _internal_scheduler_animationFrame__WEBPACK_IMPORTED_MODULE_11__["animationFrame"]; }); + const originalCwd = process.cwd(); -/* harmony import */ var _internal_scheduler_VirtualTimeScheduler__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(326); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "VirtualTimeScheduler", function() { return _internal_scheduler_VirtualTimeScheduler__WEBPACK_IMPORTED_MODULE_12__["VirtualTimeScheduler"]; }); + try { + for (const _ref of toDelete) { + const { + pattern, + cwd + } = _ref; + process.chdir(cwd); + const promise = del__WEBPACK_IMPORTED_MODULE_1___default()(pattern); + ora__WEBPACK_IMPORTED_MODULE_2___default.a.promise(promise, Object(path__WEBPACK_IMPORTED_MODULE_3__["relative"])(originalCwd, Object(path__WEBPACK_IMPORTED_MODULE_3__["join"])(cwd, String(pattern)))); + await promise; + } + } finally { + process.chdir(originalCwd); + } + } + } -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "VirtualAction", function() { return _internal_scheduler_VirtualTimeScheduler__WEBPACK_IMPORTED_MODULE_12__["VirtualAction"]; }); +}; -/* harmony import */ var _internal_Scheduler__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(307); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "Scheduler", function() { return _internal_Scheduler__WEBPACK_IMPORTED_MODULE_13__["Scheduler"]; }); +/***/ }), +/* 588 */ +/***/ (function(module, exports, __webpack_require__) { -/* harmony import */ var _internal_Subscription__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(284); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "Subscription", function() { return _internal_Subscription__WEBPACK_IMPORTED_MODULE_14__["Subscription"]; }); +"use strict"; -/* harmony import */ var _internal_Subscriber__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(278); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "Subscriber", function() { return _internal_Subscriber__WEBPACK_IMPORTED_MODULE_15__["Subscriber"]; }); +const {promisify} = __webpack_require__(29); +const path = __webpack_require__(16); +const globby = __webpack_require__(589); +const isGlob = __webpack_require__(601); +const slash = __webpack_require__(662); +const gracefulFs = __webpack_require__(664); +const isPathCwd = __webpack_require__(668); +const isPathInside = __webpack_require__(669); +const rimraf = __webpack_require__(670); +const pMap = __webpack_require__(671); -/* harmony import */ var _internal_Notification__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(309); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "Notification", function() { return _internal_Notification__WEBPACK_IMPORTED_MODULE_16__["Notification"]; }); +const rimrafP = promisify(rimraf); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "NotificationKind", function() { return _internal_Notification__WEBPACK_IMPORTED_MODULE_16__["NotificationKind"]; }); +const rimrafOptions = { + glob: false, + unlink: gracefulFs.unlink, + unlinkSync: gracefulFs.unlinkSync, + chmod: gracefulFs.chmod, + chmodSync: gracefulFs.chmodSync, + stat: gracefulFs.stat, + statSync: gracefulFs.statSync, + lstat: gracefulFs.lstat, + lstatSync: gracefulFs.lstatSync, + rmdir: gracefulFs.rmdir, + rmdirSync: gracefulFs.rmdirSync, + readdir: gracefulFs.readdir, + readdirSync: gracefulFs.readdirSync +}; -/* harmony import */ var _internal_util_pipe__WEBPACK_IMPORTED_MODULE_17__ = __webpack_require__(291); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "pipe", function() { return _internal_util_pipe__WEBPACK_IMPORTED_MODULE_17__["pipe"]; }); +function safeCheck(file, cwd) { + if (isPathCwd(file)) { + throw new Error('Cannot delete the current working directory. Can be overridden with the `force` option.'); + } -/* harmony import */ var _internal_util_noop__WEBPACK_IMPORTED_MODULE_18__ = __webpack_require__(292); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "noop", function() { return _internal_util_noop__WEBPACK_IMPORTED_MODULE_18__["noop"]; }); + if (!isPathInside(file, cwd)) { + throw new Error('Cannot delete files/directories outside the current working directory. Can be overridden with the `force` option.'); + } +} -/* harmony import */ var _internal_util_identity__WEBPACK_IMPORTED_MODULE_19__ = __webpack_require__(327); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "identity", function() { return _internal_util_identity__WEBPACK_IMPORTED_MODULE_19__["identity"]; }); +function normalizePatterns(patterns) { + patterns = Array.isArray(patterns) ? patterns : [patterns]; -/* harmony import */ var _internal_util_isObservable__WEBPACK_IMPORTED_MODULE_20__ = __webpack_require__(328); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "isObservable", function() { return _internal_util_isObservable__WEBPACK_IMPORTED_MODULE_20__["isObservable"]; }); + patterns = patterns.map(pattern => { + if (process.platform === 'win32' && isGlob(pattern) === false) { + return slash(pattern); + } -/* harmony import */ var _internal_util_ArgumentOutOfRangeError__WEBPACK_IMPORTED_MODULE_21__ = __webpack_require__(329); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "ArgumentOutOfRangeError", function() { return _internal_util_ArgumentOutOfRangeError__WEBPACK_IMPORTED_MODULE_21__["ArgumentOutOfRangeError"]; }); + return pattern; + }); -/* harmony import */ var _internal_util_EmptyError__WEBPACK_IMPORTED_MODULE_22__ = __webpack_require__(330); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "EmptyError", function() { return _internal_util_EmptyError__WEBPACK_IMPORTED_MODULE_22__["EmptyError"]; }); + return patterns; +} -/* harmony import */ var _internal_util_ObjectUnsubscribedError__WEBPACK_IMPORTED_MODULE_23__ = __webpack_require__(295); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "ObjectUnsubscribedError", function() { return _internal_util_ObjectUnsubscribedError__WEBPACK_IMPORTED_MODULE_23__["ObjectUnsubscribedError"]; }); +module.exports = async (patterns, {force, dryRun, cwd = process.cwd(), ...options} = {}) => { + options = { + expandDirectories: false, + onlyFiles: false, + followSymbolicLinks: false, + cwd, + ...options + }; -/* harmony import */ var _internal_util_UnsubscriptionError__WEBPACK_IMPORTED_MODULE_24__ = __webpack_require__(287); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "UnsubscriptionError", function() { return _internal_util_UnsubscriptionError__WEBPACK_IMPORTED_MODULE_24__["UnsubscriptionError"]; }); + patterns = normalizePatterns(patterns); -/* harmony import */ var _internal_util_TimeoutError__WEBPACK_IMPORTED_MODULE_25__ = __webpack_require__(331); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "TimeoutError", function() { return _internal_util_TimeoutError__WEBPACK_IMPORTED_MODULE_25__["TimeoutError"]; }); + const files = (await globby(patterns, options)) + .sort((a, b) => b.localeCompare(a)); -/* harmony import */ var _internal_observable_bindCallback__WEBPACK_IMPORTED_MODULE_26__ = __webpack_require__(332); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "bindCallback", function() { return _internal_observable_bindCallback__WEBPACK_IMPORTED_MODULE_26__["bindCallback"]; }); + const mapper = async file => { + file = path.resolve(cwd, file); -/* harmony import */ var _internal_observable_bindNodeCallback__WEBPACK_IMPORTED_MODULE_27__ = __webpack_require__(334); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "bindNodeCallback", function() { return _internal_observable_bindNodeCallback__WEBPACK_IMPORTED_MODULE_27__["bindNodeCallback"]; }); + if (!force) { + safeCheck(file, cwd); + } -/* harmony import */ var _internal_observable_combineLatest__WEBPACK_IMPORTED_MODULE_28__ = __webpack_require__(335); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "combineLatest", function() { return _internal_observable_combineLatest__WEBPACK_IMPORTED_MODULE_28__["combineLatest"]; }); + if (!dryRun) { + await rimrafP(file, rimrafOptions); + } -/* harmony import */ var _internal_observable_concat__WEBPACK_IMPORTED_MODULE_29__ = __webpack_require__(346); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "concat", function() { return _internal_observable_concat__WEBPACK_IMPORTED_MODULE_29__["concat"]; }); + return file; + }; -/* harmony import */ var _internal_observable_defer__WEBPACK_IMPORTED_MODULE_30__ = __webpack_require__(357); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "defer", function() { return _internal_observable_defer__WEBPACK_IMPORTED_MODULE_30__["defer"]; }); + const removedFiles = await pMap(files, mapper, options); -/* harmony import */ var _internal_observable_empty__WEBPACK_IMPORTED_MODULE_31__ = __webpack_require__(310); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "empty", function() { return _internal_observable_empty__WEBPACK_IMPORTED_MODULE_31__["empty"]; }); + removedFiles.sort((a, b) => a.localeCompare(b)); -/* harmony import */ var _internal_observable_forkJoin__WEBPACK_IMPORTED_MODULE_32__ = __webpack_require__(358); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "forkJoin", function() { return _internal_observable_forkJoin__WEBPACK_IMPORTED_MODULE_32__["forkJoin"]; }); + return removedFiles; +}; -/* harmony import */ var _internal_observable_from__WEBPACK_IMPORTED_MODULE_33__ = __webpack_require__(350); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "from", function() { return _internal_observable_from__WEBPACK_IMPORTED_MODULE_33__["from"]; }); +module.exports.sync = (patterns, {force, dryRun, cwd = process.cwd(), ...options} = {}) => { + options = { + expandDirectories: false, + onlyFiles: false, + followSymbolicLinks: false, + cwd, + ...options + }; -/* harmony import */ var _internal_observable_fromEvent__WEBPACK_IMPORTED_MODULE_34__ = __webpack_require__(359); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "fromEvent", function() { return _internal_observable_fromEvent__WEBPACK_IMPORTED_MODULE_34__["fromEvent"]; }); + patterns = normalizePatterns(patterns); -/* harmony import */ var _internal_observable_fromEventPattern__WEBPACK_IMPORTED_MODULE_35__ = __webpack_require__(360); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "fromEventPattern", function() { return _internal_observable_fromEventPattern__WEBPACK_IMPORTED_MODULE_35__["fromEventPattern"]; }); + const files = globby.sync(patterns, options) + .sort((a, b) => b.localeCompare(a)); -/* harmony import */ var _internal_observable_generate__WEBPACK_IMPORTED_MODULE_36__ = __webpack_require__(361); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "generate", function() { return _internal_observable_generate__WEBPACK_IMPORTED_MODULE_36__["generate"]; }); + const removedFiles = files.map(file => { + file = path.resolve(cwd, file); -/* harmony import */ var _internal_observable_iif__WEBPACK_IMPORTED_MODULE_37__ = __webpack_require__(362); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "iif", function() { return _internal_observable_iif__WEBPACK_IMPORTED_MODULE_37__["iif"]; }); + if (!force) { + safeCheck(file, cwd); + } -/* harmony import */ var _internal_observable_interval__WEBPACK_IMPORTED_MODULE_38__ = __webpack_require__(363); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "interval", function() { return _internal_observable_interval__WEBPACK_IMPORTED_MODULE_38__["interval"]; }); + if (!dryRun) { + rimraf.sync(file, rimrafOptions); + } -/* harmony import */ var _internal_observable_merge__WEBPACK_IMPORTED_MODULE_39__ = __webpack_require__(365); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "merge", function() { return _internal_observable_merge__WEBPACK_IMPORTED_MODULE_39__["merge"]; }); + return file; + }); -/* harmony import */ var _internal_observable_never__WEBPACK_IMPORTED_MODULE_40__ = __webpack_require__(366); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "never", function() { return _internal_observable_never__WEBPACK_IMPORTED_MODULE_40__["never"]; }); + removedFiles.sort((a, b) => a.localeCompare(b)); -/* harmony import */ var _internal_observable_of__WEBPACK_IMPORTED_MODULE_41__ = __webpack_require__(311); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "of", function() { return _internal_observable_of__WEBPACK_IMPORTED_MODULE_41__["of"]; }); + return removedFiles; +}; -/* harmony import */ var _internal_observable_onErrorResumeNext__WEBPACK_IMPORTED_MODULE_42__ = __webpack_require__(367); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "onErrorResumeNext", function() { return _internal_observable_onErrorResumeNext__WEBPACK_IMPORTED_MODULE_42__["onErrorResumeNext"]; }); -/* harmony import */ var _internal_observable_pairs__WEBPACK_IMPORTED_MODULE_43__ = __webpack_require__(368); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "pairs", function() { return _internal_observable_pairs__WEBPACK_IMPORTED_MODULE_43__["pairs"]; }); +/***/ }), +/* 589 */ +/***/ (function(module, exports, __webpack_require__) { -/* harmony import */ var _internal_observable_partition__WEBPACK_IMPORTED_MODULE_44__ = __webpack_require__(369); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "partition", function() { return _internal_observable_partition__WEBPACK_IMPORTED_MODULE_44__["partition"]; }); +"use strict"; -/* harmony import */ var _internal_observable_race__WEBPACK_IMPORTED_MODULE_45__ = __webpack_require__(372); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "race", function() { return _internal_observable_race__WEBPACK_IMPORTED_MODULE_45__["race"]; }); +const fs = __webpack_require__(23); +const arrayUnion = __webpack_require__(590); +const merge2 = __webpack_require__(591); +const glob = __webpack_require__(502); +const fastGlob = __webpack_require__(592); +const dirGlob = __webpack_require__(658); +const gitignore = __webpack_require__(660); +const {FilterStream, UniqueStream} = __webpack_require__(663); -/* harmony import */ var _internal_observable_range__WEBPACK_IMPORTED_MODULE_46__ = __webpack_require__(373); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "range", function() { return _internal_observable_range__WEBPACK_IMPORTED_MODULE_46__["range"]; }); +const DEFAULT_FILTER = () => false; -/* harmony import */ var _internal_observable_throwError__WEBPACK_IMPORTED_MODULE_47__ = __webpack_require__(316); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "throwError", function() { return _internal_observable_throwError__WEBPACK_IMPORTED_MODULE_47__["throwError"]; }); +const isNegative = pattern => pattern[0] === '!'; -/* harmony import */ var _internal_observable_timer__WEBPACK_IMPORTED_MODULE_48__ = __webpack_require__(374); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "timer", function() { return _internal_observable_timer__WEBPACK_IMPORTED_MODULE_48__["timer"]; }); +const assertPatternsInput = patterns => { + if (!patterns.every(pattern => typeof pattern === 'string')) { + throw new TypeError('Patterns must be a string or an array of strings'); + } +}; -/* harmony import */ var _internal_observable_using__WEBPACK_IMPORTED_MODULE_49__ = __webpack_require__(375); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "using", function() { return _internal_observable_using__WEBPACK_IMPORTED_MODULE_49__["using"]; }); +const checkCwdOption = (options = {}) => { + if (!options.cwd) { + return; + } -/* harmony import */ var _internal_observable_zip__WEBPACK_IMPORTED_MODULE_50__ = __webpack_require__(376); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "zip", function() { return _internal_observable_zip__WEBPACK_IMPORTED_MODULE_50__["zip"]; }); + let stat; + try { + stat = fs.statSync(options.cwd); + } catch (_) { + return; + } -/* harmony import */ var _internal_scheduled_scheduled__WEBPACK_IMPORTED_MODULE_51__ = __webpack_require__(351); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "scheduled", function() { return _internal_scheduled_scheduled__WEBPACK_IMPORTED_MODULE_51__["scheduled"]; }); + if (!stat.isDirectory()) { + throw new Error('The `cwd` option must be a path to a directory'); + } +}; -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "EMPTY", function() { return _internal_observable_empty__WEBPACK_IMPORTED_MODULE_31__["EMPTY"]; }); +const getPathString = p => p.stats instanceof fs.Stats ? p.path : p; -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "NEVER", function() { return _internal_observable_never__WEBPACK_IMPORTED_MODULE_40__["NEVER"]; }); +const generateGlobTasks = (patterns, taskOptions) => { + patterns = arrayUnion([].concat(patterns)); + assertPatternsInput(patterns); + checkCwdOption(taskOptions); -/* harmony import */ var _internal_config__WEBPACK_IMPORTED_MODULE_52__ = __webpack_require__(282); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "config", function() { return _internal_config__WEBPACK_IMPORTED_MODULE_52__["config"]; }); + const globTasks = []; -/** PURE_IMPORTS_START PURE_IMPORTS_END */ + taskOptions = { + ignore: [], + expandDirectories: true, + ...taskOptions + }; + for (const [index, pattern] of patterns.entries()) { + if (isNegative(pattern)) { + continue; + } + const ignore = patterns + .slice(index) + .filter(isNegative) + .map(pattern => pattern.slice(1)); + const options = { + ...taskOptions, + ignore: taskOptions.ignore.concat(ignore) + }; + globTasks.push({pattern, options}); + } + return globTasks; +}; +const globDirs = (task, fn) => { + let options = {}; + if (task.options.cwd) { + options.cwd = task.options.cwd; + } + if (Array.isArray(task.options.expandDirectories)) { + options = { + ...options, + files: task.options.expandDirectories + }; + } else if (typeof task.options.expandDirectories === 'object') { + options = { + ...options, + ...task.options.expandDirectories + }; + } + return fn(task.pattern, options); +}; +const getPattern = (task, fn) => task.options.expandDirectories ? globDirs(task, fn) : [task.pattern]; +const getFilterSync = options => { + return options && options.gitignore ? + gitignore.sync({cwd: options.cwd, ignore: options.ignore}) : + DEFAULT_FILTER; +}; +const globToTask = task => glob => { + const {options} = task; + if (options.ignore && Array.isArray(options.ignore) && options.expandDirectories) { + options.ignore = dirGlob.sync(options.ignore); + } + return { + pattern: glob, + options + }; +}; +module.exports = async (patterns, options) => { + const globTasks = generateGlobTasks(patterns, options); + const getFilter = async () => { + return options && options.gitignore ? + gitignore({cwd: options.cwd, ignore: options.ignore}) : + DEFAULT_FILTER; + }; + const getTasks = async () => { + const tasks = await Promise.all(globTasks.map(async task => { + const globs = await getPattern(task, dirGlob); + return Promise.all(globs.map(globToTask(task))); + })); + return arrayUnion(...tasks); + }; + const [filter, tasks] = await Promise.all([getFilter(), getTasks()]); + const paths = await Promise.all(tasks.map(task => fastGlob(task.pattern, task.options))); + return arrayUnion(...paths).filter(path_ => !filter(getPathString(path_))); +}; +module.exports.sync = (patterns, options) => { + const globTasks = generateGlobTasks(patterns, options); + const tasks = globTasks.reduce((tasks, task) => { + const newTask = getPattern(task, dirGlob.sync).map(globToTask(task)); + return tasks.concat(newTask); + }, []); + const filter = getFilterSync(options); + return tasks.reduce( + (matches, task) => arrayUnion(matches, fastGlob.sync(task.pattern, task.options)), + [] + ).filter(path_ => !filter(path_)); +}; +module.exports.stream = (patterns, options) => { + const globTasks = generateGlobTasks(patterns, options); + const tasks = globTasks.reduce((tasks, task) => { + const newTask = getPattern(task, dirGlob.sync).map(globToTask(task)); + return tasks.concat(newTask); + }, []); + const filter = getFilterSync(options); + const filterStream = new FilterStream(p => !filter(p)); + const uniqueStream = new UniqueStream(); + return merge2(tasks.map(task => fastGlob.stream(task.pattern, task.options))) + .pipe(filterStream) + .pipe(uniqueStream); +}; +module.exports.generateGlobTasks = generateGlobTasks; +module.exports.hasMagic = (patterns, options) => [] + .concat(patterns) + .some(pattern => glob.hasMagic(pattern, options)); +module.exports.gitignore = gitignore; +/***/ }), +/* 590 */ +/***/ (function(module, exports, __webpack_require__) { +"use strict"; +module.exports = (...arguments_) => { + return [...new Set([].concat(...arguments_))]; +}; +/***/ }), +/* 591 */ +/***/ (function(module, exports, __webpack_require__) { +"use strict"; +/* + * merge2 + * https://github.com/teambition/merge2 + * + * Copyright (c) 2014-2016 Teambition + * Licensed under the MIT license. + */ +const Stream = __webpack_require__(27) +const PassThrough = Stream.PassThrough +const slice = Array.prototype.slice +module.exports = merge2 +function merge2 () { + const streamsQueue = [] + let merging = false + const args = slice.call(arguments) + let options = args[args.length - 1] + if (options && !Array.isArray(options) && options.pipe == null) args.pop() + else options = {} + const doEnd = options.end !== false + if (options.objectMode == null) options.objectMode = true + if (options.highWaterMark == null) options.highWaterMark = 64 * 1024 + const mergedStream = PassThrough(options) + function addStream () { + for (let i = 0, len = arguments.length; i < len; i++) { + streamsQueue.push(pauseStreams(arguments[i], options)) + } + mergeStream() + return this + } + function mergeStream () { + if (merging) return + merging = true + let streams = streamsQueue.shift() + if (!streams) { + process.nextTick(endStream) + return + } + if (!Array.isArray(streams)) streams = [streams] + let pipesCount = streams.length + 1 + function next () { + if (--pipesCount > 0) return + merging = false + mergeStream() + } + function pipe (stream) { + function onend () { + stream.removeListener('merge2UnpipeEnd', onend) + stream.removeListener('end', onend) + next() + } + // skip ended stream + if (stream._readableState.endEmitted) return next() + stream.on('merge2UnpipeEnd', onend) + stream.on('end', onend) + stream.pipe(mergedStream, { end: false }) + // compatible for old stream + stream.resume() + } + for (let i = 0; i < streams.length; i++) pipe(streams[i]) + next() + } + function endStream () { + merging = false + // emit 'queueDrain' when all streams merged. + mergedStream.emit('queueDrain') + return doEnd && mergedStream.end() + } + mergedStream.setMaxListeners(0) + mergedStream.add = addStream + mergedStream.on('unpipe', function (stream) { + stream.emit('merge2UnpipeEnd') + }) + if (args.length) addStream.apply(null, args) + return mergedStream +} -//# sourceMappingURL=index.js.map +// check and pause streams for pipe. +function pauseStreams (streams, options) { + if (!Array.isArray(streams)) { + // Backwards-compat with old-style streams + if (!streams._readableState && streams.pipe) streams = streams.pipe(PassThrough(options)) + if (!streams._readableState || !streams.pause || !streams.pipe) { + throw new Error('Only readable stream can be merged.') + } + streams.pause() + } else { + for (let i = 0, len = streams.length; i < len; i++) streams[i] = pauseStreams(streams[i], options) + } + return streams +} /***/ }), -/* 276 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/* 592 */ +/***/ (function(module, exports, __webpack_require__) { "use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Observable", function() { return Observable; }); -/* harmony import */ var _util_canReportError__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(277); -/* harmony import */ var _util_toSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(289); -/* harmony import */ var _symbol_observable__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(290); -/* harmony import */ var _util_pipe__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(291); -/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(282); -/** PURE_IMPORTS_START _util_canReportError,_util_toSubscriber,_symbol_observable,_util_pipe,_config PURE_IMPORTS_END */ - - - + +const taskManager = __webpack_require__(593); +const async_1 = __webpack_require__(621); +const stream_1 = __webpack_require__(654); +const sync_1 = __webpack_require__(655); +const settings_1 = __webpack_require__(657); +const utils = __webpack_require__(594); +function FastGlob(source, options) { + try { + assertPatternsInput(source); + } + catch (error) { + return Promise.reject(error); + } + const works = getWorks(source, async_1.default, options); + return Promise.all(works).then(utils.array.flatten); +} +(function (FastGlob) { + function sync(source, options) { + assertPatternsInput(source); + const works = getWorks(source, sync_1.default, options); + return utils.array.flatten(works); + } + FastGlob.sync = sync; + function stream(source, options) { + assertPatternsInput(source); + const works = getWorks(source, stream_1.default, options); + /** + * The stream returned by the provider cannot work with an asynchronous iterator. + * To support asynchronous iterators, regardless of the number of tasks, we always multiplex streams. + * This affects performance (+25%). I don't see best solution right now. + */ + return utils.stream.merge(works); + } + FastGlob.stream = stream; + function generateTasks(source, options) { + assertPatternsInput(source); + const patterns = [].concat(source); + const settings = new settings_1.default(options); + return taskManager.generate(patterns, settings); + } + FastGlob.generateTasks = generateTasks; +})(FastGlob || (FastGlob = {})); +function getWorks(source, _Provider, options) { + const patterns = [].concat(source); + const settings = new settings_1.default(options); + const tasks = taskManager.generate(patterns, settings); + const provider = new _Provider(settings); + return tasks.map(provider.read, provider); +} +function assertPatternsInput(source) { + if ([].concat(source).every(isString)) { + return; + } + throw new TypeError('Patterns must be a string or an array of strings'); +} +function isString(source) { + /* tslint:disable-next-line strict-type-predicates */ + return typeof source === 'string'; +} +module.exports = FastGlob; -var Observable = /*@__PURE__*/ (function () { - function Observable(subscribe) { - this._isScalar = false; - if (subscribe) { - this._subscribe = subscribe; - } - } - Observable.prototype.lift = function (operator) { - var observable = new Observable(); - observable.source = this; - observable.operator = operator; - return observable; - }; - Observable.prototype.subscribe = function (observerOrNext, error, complete) { - var operator = this.operator; - var sink = Object(_util_toSubscriber__WEBPACK_IMPORTED_MODULE_1__["toSubscriber"])(observerOrNext, error, complete); - if (operator) { - sink.add(operator.call(sink, this.source)); - } - else { - sink.add(this.source || (_config__WEBPACK_IMPORTED_MODULE_4__["config"].useDeprecatedSynchronousErrorHandling && !sink.syncErrorThrowable) ? - this._subscribe(sink) : - this._trySubscribe(sink)); - } - if (_config__WEBPACK_IMPORTED_MODULE_4__["config"].useDeprecatedSynchronousErrorHandling) { - if (sink.syncErrorThrowable) { - sink.syncErrorThrowable = false; - if (sink.syncErrorThrown) { - throw sink.syncErrorValue; - } - } - } - return sink; - }; - Observable.prototype._trySubscribe = function (sink) { - try { - return this._subscribe(sink); - } - catch (err) { - if (_config__WEBPACK_IMPORTED_MODULE_4__["config"].useDeprecatedSynchronousErrorHandling) { - sink.syncErrorThrown = true; - sink.syncErrorValue = err; - } - if (Object(_util_canReportError__WEBPACK_IMPORTED_MODULE_0__["canReportError"])(sink)) { - sink.error(err); - } - else { - console.warn(err); - } - } - }; - Observable.prototype.forEach = function (next, promiseCtor) { - var _this = this; - promiseCtor = getPromiseCtor(promiseCtor); - return new promiseCtor(function (resolve, reject) { - var subscription; - subscription = _this.subscribe(function (value) { - try { - next(value); - } - catch (err) { - reject(err); - if (subscription) { - subscription.unsubscribe(); - } - } - }, reject, resolve); - }); - }; - Observable.prototype._subscribe = function (subscriber) { - var source = this.source; - return source && source.subscribe(subscriber); - }; - Observable.prototype[_symbol_observable__WEBPACK_IMPORTED_MODULE_2__["observable"]] = function () { - return this; - }; - Observable.prototype.pipe = function () { - var operations = []; - for (var _i = 0; _i < arguments.length; _i++) { - operations[_i] = arguments[_i]; - } - if (operations.length === 0) { - return this; - } - return Object(_util_pipe__WEBPACK_IMPORTED_MODULE_3__["pipeFromArray"])(operations)(this); - }; - Observable.prototype.toPromise = function (promiseCtor) { - var _this = this; - promiseCtor = getPromiseCtor(promiseCtor); - return new promiseCtor(function (resolve, reject) { - var value; - _this.subscribe(function (x) { return value = x; }, function (err) { return reject(err); }, function () { return resolve(value); }); - }); - }; - Observable.create = function (subscribe) { - return new Observable(subscribe); - }; - return Observable; -}()); +/***/ }), +/* 593 */ +/***/ (function(module, exports, __webpack_require__) { -function getPromiseCtor(promiseCtor) { - if (!promiseCtor) { - promiseCtor = _config__WEBPACK_IMPORTED_MODULE_4__["config"].Promise || Promise; - } - if (!promiseCtor) { - throw new Error('no Promise impl found'); - } - return promiseCtor; -} -//# sourceMappingURL=Observable.js.map +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const utils = __webpack_require__(594); +function generate(patterns, settings) { + const positivePatterns = getPositivePatterns(patterns); + const negativePatterns = getNegativePatternsAsPositive(patterns, settings.ignore); + /** + * When the `caseSensitiveMatch` option is disabled, all patterns must be marked as dynamic, because we cannot check + * filepath directly (without read directory). + */ + const staticPatterns = !settings.caseSensitiveMatch ? [] : positivePatterns.filter(utils.pattern.isStaticPattern); + const dynamicPatterns = !settings.caseSensitiveMatch ? positivePatterns : positivePatterns.filter(utils.pattern.isDynamicPattern); + const staticTasks = convertPatternsToTasks(staticPatterns, negativePatterns, /* dynamic */ false); + const dynamicTasks = convertPatternsToTasks(dynamicPatterns, negativePatterns, /* dynamic */ true); + return staticTasks.concat(dynamicTasks); +} +exports.generate = generate; +function convertPatternsToTasks(positive, negative, dynamic) { + const positivePatternsGroup = groupPatternsByBaseDirectory(positive); + // When we have a global group – there is no reason to divide the patterns into independent tasks. + // In this case, the global task covers the rest. + if ('.' in positivePatternsGroup) { + const task = convertPatternGroupToTask('.', positive, negative, dynamic); + return [task]; + } + return convertPatternGroupsToTasks(positivePatternsGroup, negative, dynamic); +} +exports.convertPatternsToTasks = convertPatternsToTasks; +function getPositivePatterns(patterns) { + return utils.pattern.getPositivePatterns(patterns); +} +exports.getPositivePatterns = getPositivePatterns; +function getNegativePatternsAsPositive(patterns, ignore) { + const negative = utils.pattern.getNegativePatterns(patterns).concat(ignore); + const positive = negative.map(utils.pattern.convertToPositivePattern); + return positive; +} +exports.getNegativePatternsAsPositive = getNegativePatternsAsPositive; +function groupPatternsByBaseDirectory(patterns) { + return patterns.reduce((collection, pattern) => { + const base = utils.pattern.getBaseDirectory(pattern); + if (base in collection) { + collection[base].push(pattern); + } + else { + collection[base] = [pattern]; + } + return collection; + }, {}); +} +exports.groupPatternsByBaseDirectory = groupPatternsByBaseDirectory; +function convertPatternGroupsToTasks(positive, negative, dynamic) { + return Object.keys(positive).map((base) => { + return convertPatternGroupToTask(base, positive[base], negative, dynamic); + }); +} +exports.convertPatternGroupsToTasks = convertPatternGroupsToTasks; +function convertPatternGroupToTask(base, positive, negative, dynamic) { + return { + dynamic, + positive, + negative, + base, + patterns: [].concat(positive, negative.map(utils.pattern.convertToNegativePattern)) + }; +} +exports.convertPatternGroupToTask = convertPatternGroupToTask; /***/ }), -/* 277 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/* 594 */ +/***/ (function(module, exports, __webpack_require__) { "use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "canReportError", function() { return canReportError; }); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(278); -/** PURE_IMPORTS_START _Subscriber PURE_IMPORTS_END */ - -function canReportError(observer) { - while (observer) { - var _a = observer, closed_1 = _a.closed, destination = _a.destination, isStopped = _a.isStopped; - if (closed_1 || isStopped) { - return false; - } - else if (destination && destination instanceof _Subscriber__WEBPACK_IMPORTED_MODULE_0__["Subscriber"]) { - observer = destination; - } - else { - observer = null; - } - } - return true; -} -//# sourceMappingURL=canReportError.js.map + +Object.defineProperty(exports, "__esModule", { value: true }); +const array = __webpack_require__(595); +exports.array = array; +const errno = __webpack_require__(596); +exports.errno = errno; +const fs = __webpack_require__(597); +exports.fs = fs; +const path = __webpack_require__(598); +exports.path = path; +const pattern = __webpack_require__(599); +exports.pattern = pattern; +const stream = __webpack_require__(620); +exports.stream = stream; /***/ }), -/* 278 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/* 595 */ +/***/ (function(module, exports, __webpack_require__) { "use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Subscriber", function() { return Subscriber; }); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "SafeSubscriber", function() { return SafeSubscriber; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _util_isFunction__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(280); -/* harmony import */ var _Observer__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(281); -/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(284); -/* harmony import */ var _internal_symbol_rxSubscriber__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(288); -/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(282); -/* harmony import */ var _util_hostReportError__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(283); -/** PURE_IMPORTS_START tslib,_util_isFunction,_Observer,_Subscription,_internal_symbol_rxSubscriber,_config,_util_hostReportError PURE_IMPORTS_END */ - - - + +Object.defineProperty(exports, "__esModule", { value: true }); +function flatten(items) { + return items.reduce((collection, item) => [].concat(collection, item), []); +} +exports.flatten = flatten; +/***/ }), +/* 596 */ +/***/ (function(module, exports, __webpack_require__) { +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +function isEnoentCodeError(error) { + return error.code === 'ENOENT'; +} +exports.isEnoentCodeError = isEnoentCodeError; -var Subscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](Subscriber, _super); - function Subscriber(destinationOrNext, error, complete) { - var _this = _super.call(this) || this; - _this.syncErrorValue = null; - _this.syncErrorThrown = false; - _this.syncErrorThrowable = false; - _this.isStopped = false; - switch (arguments.length) { - case 0: - _this.destination = _Observer__WEBPACK_IMPORTED_MODULE_2__["empty"]; - break; - case 1: - if (!destinationOrNext) { - _this.destination = _Observer__WEBPACK_IMPORTED_MODULE_2__["empty"]; - break; - } - if (typeof destinationOrNext === 'object') { - if (destinationOrNext instanceof Subscriber) { - _this.syncErrorThrowable = destinationOrNext.syncErrorThrowable; - _this.destination = destinationOrNext; - destinationOrNext.add(_this); - } - else { - _this.syncErrorThrowable = true; - _this.destination = new SafeSubscriber(_this, destinationOrNext); - } - break; - } - default: - _this.syncErrorThrowable = true; - _this.destination = new SafeSubscriber(_this, destinationOrNext, error, complete); - break; - } - return _this; - } - Subscriber.prototype[_internal_symbol_rxSubscriber__WEBPACK_IMPORTED_MODULE_4__["rxSubscriber"]] = function () { return this; }; - Subscriber.create = function (next, error, complete) { - var subscriber = new Subscriber(next, error, complete); - subscriber.syncErrorThrowable = false; - return subscriber; - }; - Subscriber.prototype.next = function (value) { - if (!this.isStopped) { - this._next(value); - } - }; - Subscriber.prototype.error = function (err) { - if (!this.isStopped) { - this.isStopped = true; - this._error(err); - } - }; - Subscriber.prototype.complete = function () { - if (!this.isStopped) { - this.isStopped = true; - this._complete(); - } - }; - Subscriber.prototype.unsubscribe = function () { - if (this.closed) { - return; - } - this.isStopped = true; - _super.prototype.unsubscribe.call(this); - }; - Subscriber.prototype._next = function (value) { - this.destination.next(value); - }; - Subscriber.prototype._error = function (err) { - this.destination.error(err); - this.unsubscribe(); - }; - Subscriber.prototype._complete = function () { - this.destination.complete(); - this.unsubscribe(); - }; - Subscriber.prototype._unsubscribeAndRecycle = function () { - var _parentOrParents = this._parentOrParents; - this._parentOrParents = null; - this.unsubscribe(); - this.closed = false; - this.isStopped = false; - this._parentOrParents = _parentOrParents; - return this; - }; - return Subscriber; -}(_Subscription__WEBPACK_IMPORTED_MODULE_3__["Subscription"])); -var SafeSubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](SafeSubscriber, _super); - function SafeSubscriber(_parentSubscriber, observerOrNext, error, complete) { - var _this = _super.call(this) || this; - _this._parentSubscriber = _parentSubscriber; - var next; - var context = _this; - if (Object(_util_isFunction__WEBPACK_IMPORTED_MODULE_1__["isFunction"])(observerOrNext)) { - next = observerOrNext; - } - else if (observerOrNext) { - next = observerOrNext.next; - error = observerOrNext.error; - complete = observerOrNext.complete; - if (observerOrNext !== _Observer__WEBPACK_IMPORTED_MODULE_2__["empty"]) { - context = Object.create(observerOrNext); - if (Object(_util_isFunction__WEBPACK_IMPORTED_MODULE_1__["isFunction"])(context.unsubscribe)) { - _this.add(context.unsubscribe.bind(context)); - } - context.unsubscribe = _this.unsubscribe.bind(_this); - } - } - _this._context = context; - _this._next = next; - _this._error = error; - _this._complete = complete; - return _this; - } - SafeSubscriber.prototype.next = function (value) { - if (!this.isStopped && this._next) { - var _parentSubscriber = this._parentSubscriber; - if (!_config__WEBPACK_IMPORTED_MODULE_5__["config"].useDeprecatedSynchronousErrorHandling || !_parentSubscriber.syncErrorThrowable) { - this.__tryOrUnsub(this._next, value); - } - else if (this.__tryOrSetError(_parentSubscriber, this._next, value)) { - this.unsubscribe(); - } - } - }; - SafeSubscriber.prototype.error = function (err) { - if (!this.isStopped) { - var _parentSubscriber = this._parentSubscriber; - var useDeprecatedSynchronousErrorHandling = _config__WEBPACK_IMPORTED_MODULE_5__["config"].useDeprecatedSynchronousErrorHandling; - if (this._error) { - if (!useDeprecatedSynchronousErrorHandling || !_parentSubscriber.syncErrorThrowable) { - this.__tryOrUnsub(this._error, err); - this.unsubscribe(); - } - else { - this.__tryOrSetError(_parentSubscriber, this._error, err); - this.unsubscribe(); - } - } - else if (!_parentSubscriber.syncErrorThrowable) { - this.unsubscribe(); - if (useDeprecatedSynchronousErrorHandling) { - throw err; - } - Object(_util_hostReportError__WEBPACK_IMPORTED_MODULE_6__["hostReportError"])(err); - } - else { - if (useDeprecatedSynchronousErrorHandling) { - _parentSubscriber.syncErrorValue = err; - _parentSubscriber.syncErrorThrown = true; - } - else { - Object(_util_hostReportError__WEBPACK_IMPORTED_MODULE_6__["hostReportError"])(err); - } - this.unsubscribe(); - } - } - }; - SafeSubscriber.prototype.complete = function () { - var _this = this; - if (!this.isStopped) { - var _parentSubscriber = this._parentSubscriber; - if (this._complete) { - var wrappedComplete = function () { return _this._complete.call(_this._context); }; - if (!_config__WEBPACK_IMPORTED_MODULE_5__["config"].useDeprecatedSynchronousErrorHandling || !_parentSubscriber.syncErrorThrowable) { - this.__tryOrUnsub(wrappedComplete); - this.unsubscribe(); - } - else { - this.__tryOrSetError(_parentSubscriber, wrappedComplete); - this.unsubscribe(); - } - } - else { - this.unsubscribe(); - } - } - }; - SafeSubscriber.prototype.__tryOrUnsub = function (fn, value) { - try { - fn.call(this._context, value); - } - catch (err) { - this.unsubscribe(); - if (_config__WEBPACK_IMPORTED_MODULE_5__["config"].useDeprecatedSynchronousErrorHandling) { - throw err; - } - else { - Object(_util_hostReportError__WEBPACK_IMPORTED_MODULE_6__["hostReportError"])(err); - } - } - }; - SafeSubscriber.prototype.__tryOrSetError = function (parent, fn, value) { - if (!_config__WEBPACK_IMPORTED_MODULE_5__["config"].useDeprecatedSynchronousErrorHandling) { - throw new Error('bad call'); - } - try { - fn.call(this._context, value); - } - catch (err) { - if (_config__WEBPACK_IMPORTED_MODULE_5__["config"].useDeprecatedSynchronousErrorHandling) { - parent.syncErrorValue = err; - parent.syncErrorThrown = true; - return true; - } - else { - Object(_util_hostReportError__WEBPACK_IMPORTED_MODULE_6__["hostReportError"])(err); - return true; - } - } - return false; - }; - SafeSubscriber.prototype._unsubscribe = function () { - var _parentSubscriber = this._parentSubscriber; - this._context = null; - this._parentSubscriber = null; - _parentSubscriber.unsubscribe(); - }; - return SafeSubscriber; -}(Subscriber)); +/***/ }), +/* 597 */ +/***/ (function(module, exports, __webpack_require__) { -//# sourceMappingURL=Subscriber.js.map +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +class DirentFromStats { + constructor(name, stats) { + this.name = name; + this.isBlockDevice = stats.isBlockDevice.bind(stats); + this.isCharacterDevice = stats.isCharacterDevice.bind(stats); + this.isDirectory = stats.isDirectory.bind(stats); + this.isFIFO = stats.isFIFO.bind(stats); + this.isFile = stats.isFile.bind(stats); + this.isSocket = stats.isSocket.bind(stats); + this.isSymbolicLink = stats.isSymbolicLink.bind(stats); + } +} +function createDirentFromStats(name, stats) { + return new DirentFromStats(name, stats); +} +exports.createDirentFromStats = createDirentFromStats; /***/ }), -/* 279 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/* 598 */ +/***/ (function(module, exports, __webpack_require__) { "use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__extends", function() { return __extends; }); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__assign", function() { return __assign; }); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__rest", function() { return __rest; }); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__decorate", function() { return __decorate; }); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__param", function() { return __param; }); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__metadata", function() { return __metadata; }); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__awaiter", function() { return __awaiter; }); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__generator", function() { return __generator; }); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__exportStar", function() { return __exportStar; }); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__values", function() { return __values; }); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__read", function() { return __read; }); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__spread", function() { return __spread; }); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__await", function() { return __await; }); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__asyncGenerator", function() { return __asyncGenerator; }); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__asyncDelegator", function() { return __asyncDelegator; }); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__asyncValues", function() { return __asyncValues; }); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__makeTemplateObject", function() { return __makeTemplateObject; }); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__importStar", function() { return __importStar; }); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "__importDefault", function() { return __importDefault; }); -/*! ***************************************************************************** -Copyright (c) Microsoft Corporation. All rights reserved. -Licensed under the Apache License, Version 2.0 (the "License"); you may not use -this file except in compliance with the License. You may obtain a copy of the -License at http://www.apache.org/licenses/LICENSE-2.0 - -THIS CODE IS PROVIDED ON AN *AS IS* BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -KIND, EITHER EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION ANY IMPLIED -WARRANTIES OR CONDITIONS OF TITLE, FITNESS FOR A PARTICULAR PURPOSE, -MERCHANTABLITY OR NON-INFRINGEMENT. - -See the Apache Version 2.0 License for specific language governing permissions -and limitations under the License. -***************************************************************************** */ -/* global Reflect, Promise */ - -var extendStatics = function(d, b) { - extendStatics = Object.setPrototypeOf || - ({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) || - function (d, b) { for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p]; }; - return extendStatics(d, b); -}; -function __extends(d, b) { - extendStatics(d, b); - function __() { this.constructor = d; } - d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); +Object.defineProperty(exports, "__esModule", { value: true }); +const path = __webpack_require__(16); +/** + * Designed to work only with simple paths: `dir\\file`. + */ +function unixify(filepath) { + return filepath.replace(/\\/g, '/'); } - -var __assign = function() { - __assign = Object.assign || function __assign(t) { - for (var s, i = 1, n = arguments.length; i < n; i++) { - s = arguments[i]; - for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p)) t[p] = s[p]; - } - return t; - } - return __assign.apply(this, arguments); +exports.unixify = unixify; +function makeAbsolute(cwd, filepath) { + return path.resolve(cwd, filepath); } +exports.makeAbsolute = makeAbsolute; + + +/***/ }), +/* 599 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; -function __rest(s, e) { - var t = {}; - for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p) && e.indexOf(p) < 0) - t[p] = s[p]; - if (s != null && typeof Object.getOwnPropertySymbols === "function") - for (var i = 0, p = Object.getOwnPropertySymbols(s); i < p.length; i++) if (e.indexOf(p[i]) < 0) - t[p[i]] = s[p[i]]; - return t; +Object.defineProperty(exports, "__esModule", { value: true }); +const path = __webpack_require__(16); +const globParent = __webpack_require__(600); +const isGlob = __webpack_require__(601); +const micromatch = __webpack_require__(603); +const GLOBSTAR = '**'; +function isStaticPattern(pattern) { + return !isDynamicPattern(pattern); } - -function __decorate(decorators, target, key, desc) { - var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d; - if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc); - else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r; - return c > 3 && r && Object.defineProperty(target, key, r), r; +exports.isStaticPattern = isStaticPattern; +function isDynamicPattern(pattern) { + return isGlob(pattern, { strict: false }); } - -function __param(paramIndex, decorator) { - return function (target, key) { decorator(target, key, paramIndex); } +exports.isDynamicPattern = isDynamicPattern; +function convertToPositivePattern(pattern) { + return isNegativePattern(pattern) ? pattern.slice(1) : pattern; } - -function __metadata(metadataKey, metadataValue) { - if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(metadataKey, metadataValue); +exports.convertToPositivePattern = convertToPositivePattern; +function convertToNegativePattern(pattern) { + return '!' + pattern; } - -function __awaiter(thisArg, _arguments, P, generator) { - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); +exports.convertToNegativePattern = convertToNegativePattern; +function isNegativePattern(pattern) { + return pattern.startsWith('!') && pattern[1] !== '('; } - -function __generator(thisArg, body) { - var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g; - return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g; - function verb(n) { return function (v) { return step([n, v]); }; } - function step(op) { - if (f) throw new TypeError("Generator is already executing."); - while (_) try { - if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t; - if (y = 0, t) op = [op[0] & 2, t.value]; - switch (op[0]) { - case 0: case 1: t = op; break; - case 4: _.label++; return { value: op[1], done: false }; - case 5: _.label++; y = op[1]; op = [0]; continue; - case 7: op = _.ops.pop(); _.trys.pop(); continue; - default: - if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; } - if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; } - if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; } - if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; } - if (t[2]) _.ops.pop(); - _.trys.pop(); continue; - } - op = body.call(thisArg, _); - } catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; } - if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true }; - } +exports.isNegativePattern = isNegativePattern; +function isPositivePattern(pattern) { + return !isNegativePattern(pattern); } - -function __exportStar(m, exports) { - for (var p in m) if (!exports.hasOwnProperty(p)) exports[p] = m[p]; +exports.isPositivePattern = isPositivePattern; +function getNegativePatterns(patterns) { + return patterns.filter(isNegativePattern); } - -function __values(o) { - var m = typeof Symbol === "function" && o[Symbol.iterator], i = 0; - if (m) return m.call(o); - return { - next: function () { - if (o && i >= o.length) o = void 0; - return { value: o && o[i++], done: !o }; - } - }; +exports.getNegativePatterns = getNegativePatterns; +function getPositivePatterns(patterns) { + return patterns.filter(isPositivePattern); } - -function __read(o, n) { - var m = typeof Symbol === "function" && o[Symbol.iterator]; - if (!m) return o; - var i = m.call(o), r, ar = [], e; - try { - while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value); - } - catch (error) { e = { error: error }; } - finally { - try { - if (r && !r.done && (m = i["return"])) m.call(i); - } - finally { if (e) throw e.error; } - } - return ar; +exports.getPositivePatterns = getPositivePatterns; +function getBaseDirectory(pattern) { + return globParent(pattern); } - -function __spread() { - for (var ar = [], i = 0; i < arguments.length; i++) - ar = ar.concat(__read(arguments[i])); - return ar; +exports.getBaseDirectory = getBaseDirectory; +function hasGlobStar(pattern) { + return pattern.indexOf(GLOBSTAR) !== -1; } - -function __await(v) { - return this instanceof __await ? (this.v = v, this) : new __await(v); +exports.hasGlobStar = hasGlobStar; +function endsWithSlashGlobStar(pattern) { + return pattern.endsWith('/' + GLOBSTAR); } - -function __asyncGenerator(thisArg, _arguments, generator) { - if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); - var g = generator.apply(thisArg, _arguments || []), i, q = []; - return i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i; - function verb(n) { if (g[n]) i[n] = function (v) { return new Promise(function (a, b) { q.push([n, v, a, b]) > 1 || resume(n, v); }); }; } - function resume(n, v) { try { step(g[n](v)); } catch (e) { settle(q[0][3], e); } } - function step(r) { r.value instanceof __await ? Promise.resolve(r.value.v).then(fulfill, reject) : settle(q[0][2], r); } - function fulfill(value) { resume("next", value); } - function reject(value) { resume("throw", value); } - function settle(f, v) { if (f(v), q.shift(), q.length) resume(q[0][0], q[0][1]); } +exports.endsWithSlashGlobStar = endsWithSlashGlobStar; +function isAffectDepthOfReadingPattern(pattern) { + const basename = path.basename(pattern); + return endsWithSlashGlobStar(pattern) || isStaticPattern(basename); } - -function __asyncDelegator(o) { - var i, p; - return i = {}, verb("next"), verb("throw", function (e) { throw e; }), verb("return"), i[Symbol.iterator] = function () { return this; }, i; - function verb(n, f) { i[n] = o[n] ? function (v) { return (p = !p) ? { value: __await(o[n](v)), done: n === "return" } : f ? f(v) : v; } : f; } +exports.isAffectDepthOfReadingPattern = isAffectDepthOfReadingPattern; +function getNaiveDepth(pattern) { + const base = getBaseDirectory(pattern); + const patternDepth = pattern.split('/').length; + const patternBaseDepth = base.split('/').length; + /** + * This is a hack for pattern that has no base directory. + * + * This is related to the `*\something\*` pattern. + */ + if (base === '.') { + return patternDepth - patternBaseDepth; + } + return patternDepth - patternBaseDepth - 1; } - -function __asyncValues(o) { - if (!Symbol.asyncIterator) throw new TypeError("Symbol.asyncIterator is not defined."); - var m = o[Symbol.asyncIterator], i; - return m ? m.call(o) : (o = typeof __values === "function" ? __values(o) : o[Symbol.iterator](), i = {}, verb("next"), verb("throw"), verb("return"), i[Symbol.asyncIterator] = function () { return this; }, i); - function verb(n) { i[n] = o[n] && function (v) { return new Promise(function (resolve, reject) { v = o[n](v), settle(resolve, reject, v.done, v.value); }); }; } - function settle(resolve, reject, d, v) { Promise.resolve(v).then(function(v) { resolve({ value: v, done: d }); }, reject); } +exports.getNaiveDepth = getNaiveDepth; +function getMaxNaivePatternsDepth(patterns) { + return patterns.reduce((max, pattern) => { + const depth = getNaiveDepth(pattern); + return depth > max ? depth : max; + }, 0); } - -function __makeTemplateObject(cooked, raw) { - if (Object.defineProperty) { Object.defineProperty(cooked, "raw", { value: raw }); } else { cooked.raw = raw; } - return cooked; -}; - -function __importStar(mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k in mod) if (Object.hasOwnProperty.call(mod, k)) result[k] = mod[k]; - result.default = mod; - return result; +exports.getMaxNaivePatternsDepth = getMaxNaivePatternsDepth; +function makeRe(pattern, options) { + return micromatch.makeRe(pattern, options); } - -function __importDefault(mod) { - return (mod && mod.__esModule) ? mod : { default: mod }; +exports.makeRe = makeRe; +function convertPatternsToRe(patterns, options) { + return patterns.map((pattern) => makeRe(pattern, options)); +} +exports.convertPatternsToRe = convertPatternsToRe; +function matchAny(entry, patternsRe) { + const filepath = entry.replace(/^\.[\\\/]/, ''); + return patternsRe.some((patternRe) => patternRe.test(filepath)); } +exports.matchAny = matchAny; /***/ }), -/* 280 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/* 600 */ +/***/ (function(module, exports, __webpack_require__) { "use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "isFunction", function() { return isFunction; }); -/** PURE_IMPORTS_START PURE_IMPORTS_END */ -function isFunction(x) { - return typeof x === 'function'; -} -//# sourceMappingURL=isFunction.js.map + + +var isGlob = __webpack_require__(601); +var pathPosixDirname = __webpack_require__(16).posix.dirname; +var isWin32 = __webpack_require__(11).platform() === 'win32'; + +var slash = '/'; +var backslash = /\\/g; +var enclosure = /[\{\[].*[\/]*.*[\}\]]$/; +var globby = /(^|[^\\])([\{\[]|\([^\)]+$)/; +var escaped = /\\([\*\?\|\[\]\(\)\{\}])/g; + +module.exports = function globParent(str) { + // flip windows path separators + if (isWin32 && str.indexOf(slash) < 0) { + str = str.replace(backslash, slash); + } + + // special case for strings ending in enclosure containing path separator + if (enclosure.test(str)) { + str += slash; + } + + // preserves full path in case of trailing path separator + str += 'a'; + + // remove path parts that are globby + do { + str = pathPosixDirname(str); + } while (isGlob(str) || globby.test(str)); + + // remove escape chars and return result + return str.replace(escaped, '$1'); +}; /***/ }), -/* 281 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/* 601 */ +/***/ (function(module, exports, __webpack_require__) { + +/*! + * is-glob + * + * Copyright (c) 2014-2017, Jon Schlinkert. + * Released under the MIT License. + */ + +var isExtglob = __webpack_require__(602); +var chars = { '{': '}', '(': ')', '[': ']'}; +var strictRegex = /\\(.)|(^!|\*|[\].+)]\?|\[[^\\\]]+\]|\{[^\\}]+\}|\(\?[:!=][^\\)]+\)|\([^|]+\|[^\\)]+\))/; +var relaxedRegex = /\\(.)|(^!|[*?{}()[\]]|\(\?)/; + +module.exports = function isGlob(str, options) { + if (typeof str !== 'string' || str === '') { + return false; + } + + if (isExtglob(str)) { + return true; + } + + var regex = strictRegex; + var match; + + // optionally relax regex + if (options && options.strict === false) { + regex = relaxedRegex; + } + + while ((match = regex.exec(str))) { + if (match[2]) return true; + var idx = match.index + match[0].length; + + // if an open bracket/brace/paren is escaped, + // set the index to the next closing character + var open = match[1]; + var close = open ? chars[open] : null; + if (open && close) { + var n = str.indexOf(close, idx); + if (n !== -1) { + idx = n + 1; + } + } + + str = str.slice(idx); + } + return false; +}; + + +/***/ }), +/* 602 */ +/***/ (function(module, exports) { + +/*! + * is-extglob + * + * Copyright (c) 2014-2016, Jon Schlinkert. + * Licensed under the MIT License. + */ + +module.exports = function isExtglob(str) { + if (typeof str !== 'string' || str === '') { + return false; + } + + var match; + while ((match = /(\\).|([@?!+*]\(.*\))/g.exec(str))) { + if (match[2]) return true; + str = str.slice(match.index + match[0].length); + } + + return false; +}; + + +/***/ }), +/* 603 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +const util = __webpack_require__(29); +const braces = __webpack_require__(604); +const picomatch = __webpack_require__(614); +const utils = __webpack_require__(617); +const isEmptyString = val => typeof val === 'string' && (val === '' || val === './'); + +/** + * Returns an array of strings that match one or more glob patterns. + * + * ```js + * const mm = require('micromatch'); + * // mm(list, patterns[, options]); + * + * console.log(mm(['a.js', 'a.txt'], ['*.js'])); + * //=> [ 'a.js' ] + * ``` + * @param {String|Array} list List of strings to match. + * @param {String|Array} patterns One or more glob patterns to use for matching. + * @param {Object} options See available [options](#options) + * @return {Array} Returns an array of matches + * @summary false + * @api public + */ + +const micromatch = (list, patterns, options) => { + patterns = [].concat(patterns); + list = [].concat(list); + + let omit = new Set(); + let keep = new Set(); + let items = new Set(); + let negatives = 0; + + let onResult = state => { + items.add(state.output); + if (options && options.onResult) { + options.onResult(state); + } + }; + + for (let i = 0; i < patterns.length; i++) { + let isMatch = picomatch(String(patterns[i]), { ...options, onResult }, true); + let negated = isMatch.state.negated || isMatch.state.negatedExtglob; + if (negated) negatives++; + + for (let item of list) { + let matched = isMatch(item, true); + + let match = negated ? !matched.isMatch : matched.isMatch; + if (!match) continue; + + if (negated) { + omit.add(matched.output); + } else { + omit.delete(matched.output); + keep.add(matched.output); + } + } + } + + let result = negatives === patterns.length ? [...items] : [...keep]; + let matches = result.filter(item => !omit.has(item)); + + if (options && matches.length === 0) { + if (options.failglob === true) { + throw new Error(`No matches found for "${patterns.join(', ')}"`); + } + + if (options.nonull === true || options.nullglob === true) { + return options.unescape ? patterns.map(p => p.replace(/\\/g, '')) : patterns; + } + } + + return matches; +}; + +/** + * Backwards compatibility + */ + +micromatch.match = micromatch; + +/** + * Returns a matcher function from the given glob `pattern` and `options`. + * The returned function takes a string to match as its only argument and returns + * true if the string is a match. + * + * ```js + * const mm = require('micromatch'); + * // mm.matcher(pattern[, options]); + * + * const isMatch = mm.matcher('*.!(*a)'); + * console.log(isMatch('a.a')); //=> false + * console.log(isMatch('a.b')); //=> true + * ``` + * @param {String} `pattern` Glob pattern + * @param {Object} `options` + * @return {Function} Returns a matcher function. + * @api public + */ + +micromatch.matcher = (pattern, options) => picomatch(pattern, options); + +/** + * Returns true if **any** of the given glob `patterns` match the specified `string`. + * + * ```js + * const mm = require('micromatch'); + * // mm.isMatch(string, patterns[, options]); + * + * console.log(mm.isMatch('a.a', ['b.*', '*.a'])); //=> true + * console.log(mm.isMatch('a.a', 'b.*')); //=> false + * ``` + * @param {String} str The string to test. + * @param {String|Array} patterns One or more glob patterns to use for matching. + * @param {Object} [options] See available [options](#options). + * @return {Boolean} Returns true if any patterns match `str` + * @api public + */ + +micromatch.isMatch = (str, patterns, options) => picomatch(patterns, options)(str); + +/** + * Backwards compatibility + */ + +micromatch.any = micromatch.isMatch; + +/** + * Returns a list of strings that _**do not match any**_ of the given `patterns`. + * + * ```js + * const mm = require('micromatch'); + * // mm.not(list, patterns[, options]); + * + * console.log(mm.not(['a.a', 'b.b', 'c.c'], '*.a')); + * //=> ['b.b', 'c.c'] + * ``` + * @param {Array} `list` Array of strings to match. + * @param {String|Array} `patterns` One or more glob pattern to use for matching. + * @param {Object} `options` See available [options](#options) for changing how matches are performed + * @return {Array} Returns an array of strings that **do not match** the given patterns. + * @api public + */ + +micromatch.not = (list, patterns, options = {}) => { + patterns = [].concat(patterns).map(String); + let result = new Set(); + let items = []; + + let onResult = state => { + if (options.onResult) options.onResult(state); + items.push(state.output); + }; + + let matches = micromatch(list, patterns, { ...options, onResult }); + + for (let item of items) { + if (!matches.includes(item)) { + result.add(item); + } + } + return [...result]; +}; + +/** + * Returns true if the given `string` contains the given pattern. Similar + * to [.isMatch](#isMatch) but the pattern can match any part of the string. + * + * ```js + * var mm = require('micromatch'); + * // mm.contains(string, pattern[, options]); + * + * console.log(mm.contains('aa/bb/cc', '*b')); + * //=> true + * console.log(mm.contains('aa/bb/cc', '*d')); + * //=> false + * ``` + * @param {String} `str` The string to match. + * @param {String|Array} `patterns` Glob pattern to use for matching. + * @param {Object} `options` See available [options](#options) for changing how matches are performed + * @return {Boolean} Returns true if the patter matches any part of `str`. + * @api public + */ + +micromatch.contains = (str, pattern, options) => { + if (typeof str !== 'string') { + throw new TypeError(`Expected a string: "${util.inspect(str)}"`); + } + + if (Array.isArray(pattern)) { + return pattern.some(p => micromatch.contains(str, p, options)); + } + + if (typeof pattern === 'string') { + if (isEmptyString(str) || isEmptyString(pattern)) { + return false; + } + + if (str.includes(pattern) || (str.startsWith('./') && str.slice(2).includes(pattern))) { + return true; + } + } + + return micromatch.isMatch(str, pattern, { ...options, contains: true }); +}; + +/** + * Filter the keys of the given object with the given `glob` pattern + * and `options`. Does not attempt to match nested keys. If you need this feature, + * use [glob-object][] instead. + * + * ```js + * const mm = require('micromatch'); + * // mm.matchKeys(object, patterns[, options]); + * + * const obj = { aa: 'a', ab: 'b', ac: 'c' }; + * console.log(mm.matchKeys(obj, '*b')); + * //=> { ab: 'b' } + * ``` + * @param {Object} `object` The object with keys to filter. + * @param {String|Array} `patterns` One or more glob patterns to use for matching. + * @param {Object} `options` See available [options](#options) for changing how matches are performed + * @return {Object} Returns an object with only keys that match the given patterns. + * @api public + */ + +micromatch.matchKeys = (obj, patterns, options) => { + if (!utils.isObject(obj)) { + throw new TypeError('Expected the first argument to be an object'); + } + let keys = micromatch(Object.keys(obj), patterns, options); + let res = {}; + for (let key of keys) res[key] = obj[key]; + return res; +}; + +/** + * Returns true if some of the strings in the given `list` match any of the given glob `patterns`. + * + * ```js + * const mm = require('micromatch'); + * // mm.some(list, patterns[, options]); + * + * console.log(mm.some(['foo.js', 'bar.js'], ['*.js', '!foo.js'])); + * // true + * console.log(mm.some(['foo.js'], ['*.js', '!foo.js'])); + * // false + * ``` + * @param {String|Array} `list` The string or array of strings to test. Returns as soon as the first match is found. + * @param {String|Array} `patterns` One or more glob patterns to use for matching. + * @param {Object} `options` See available [options](#options) for changing how matches are performed + * @return {Boolean} Returns true if any patterns match `str` + * @api public + */ + +micromatch.some = (list, patterns, options) => { + let items = [].concat(list); + + for (let pattern of [].concat(patterns)) { + let isMatch = picomatch(String(pattern), options); + if (items.some(item => isMatch(item))) { + return true; + } + } + return false; +}; + +/** + * Returns true if every string in the given `list` matches + * any of the given glob `patterns`. + * + * ```js + * const mm = require('micromatch'); + * // mm.every(list, patterns[, options]); + * + * console.log(mm.every('foo.js', ['foo.js'])); + * // true + * console.log(mm.every(['foo.js', 'bar.js'], ['*.js'])); + * // true + * console.log(mm.every(['foo.js', 'bar.js'], ['*.js', '!foo.js'])); + * // false + * console.log(mm.every(['foo.js'], ['*.js', '!foo.js'])); + * // false + * ``` + * @param {String|Array} `list` The string or array of strings to test. + * @param {String|Array} `patterns` One or more glob patterns to use for matching. + * @param {Object} `options` See available [options](#options) for changing how matches are performed + * @return {Boolean} Returns true if any patterns match `str` + * @api public + */ + +micromatch.every = (list, patterns, options) => { + let items = [].concat(list); + + for (let pattern of [].concat(patterns)) { + let isMatch = picomatch(String(pattern), options); + if (!items.every(item => isMatch(item))) { + return false; + } + } + return true; +}; -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "empty", function() { return empty; }); -/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(282); -/* harmony import */ var _util_hostReportError__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(283); -/** PURE_IMPORTS_START _config,_util_hostReportError PURE_IMPORTS_END */ +/** + * Returns true if **all** of the given `patterns` match + * the specified string. + * + * ```js + * const mm = require('micromatch'); + * // mm.all(string, patterns[, options]); + * + * console.log(mm.all('foo.js', ['foo.js'])); + * // true + * + * console.log(mm.all('foo.js', ['*.js', '!foo.js'])); + * // false + * + * console.log(mm.all('foo.js', ['*.js', 'foo.js'])); + * // true + * + * console.log(mm.all('foo.js', ['*.js', 'f*', '*o*', '*o.js'])); + * // true + * ``` + * @param {String|Array} `str` The string to test. + * @param {String|Array} `patterns` One or more glob patterns to use for matching. + * @param {Object} `options` See available [options](#options) for changing how matches are performed + * @return {Boolean} Returns true if any patterns match `str` + * @api public + */ +micromatch.all = (str, patterns, options) => { + if (typeof str !== 'string') { + throw new TypeError(`Expected a string: "${util.inspect(str)}"`); + } -var empty = { - closed: true, - next: function (value) { }, - error: function (err) { - if (_config__WEBPACK_IMPORTED_MODULE_0__["config"].useDeprecatedSynchronousErrorHandling) { - throw err; - } - else { - Object(_util_hostReportError__WEBPACK_IMPORTED_MODULE_1__["hostReportError"])(err); - } - }, - complete: function () { } + return [].concat(patterns).every(p => picomatch(p, options)(str)); }; -//# sourceMappingURL=Observer.js.map +/** + * Returns an array of matches captured by `pattern` in `string, or `null` if the pattern did not match. + * + * ```js + * const mm = require('micromatch'); + * // mm.capture(pattern, string[, options]); + * + * console.log(mm.capture('test/*.js', 'test/foo.js')); + * //=> ['foo'] + * console.log(mm.capture('test/*.js', 'foo/bar.css')); + * //=> null + * ``` + * @param {String} `glob` Glob pattern to use for matching. + * @param {String} `input` String to match + * @param {Object} `options` See available [options](#options) for changing how matches are performed + * @return {Boolean} Returns an array of captures if the input matches the glob pattern, otherwise `null`. + * @api public + */ -/***/ }), -/* 282 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +micromatch.capture = (glob, input, options) => { + let posix = utils.isWindows(options); + let regex = picomatch.makeRe(String(glob), { ...options, capture: true }); + let match = regex.exec(posix ? utils.toPosixSlashes(input) : input); -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "config", function() { return config; }); -/** PURE_IMPORTS_START PURE_IMPORTS_END */ -var _enable_super_gross_mode_that_will_cause_bad_things = false; -var config = { - Promise: undefined, - set useDeprecatedSynchronousErrorHandling(value) { - if (value) { - var error = /*@__PURE__*/ new Error(); - /*@__PURE__*/ console.warn('DEPRECATED! RxJS was set to use deprecated synchronous error handling behavior by code at: \n' + error.stack); - } - else if (_enable_super_gross_mode_that_will_cause_bad_things) { - /*@__PURE__*/ console.log('RxJS: Back to a better error behavior. Thank you. <3'); - } - _enable_super_gross_mode_that_will_cause_bad_things = value; - }, - get useDeprecatedSynchronousErrorHandling() { - return _enable_super_gross_mode_that_will_cause_bad_things; - }, + if (match) { + return match.slice(1).map(v => v === void 0 ? '' : v); + } }; -//# sourceMappingURL=config.js.map +/** + * Create a regular expression from the given glob `pattern`. + * + * ```js + * const mm = require('micromatch'); + * // mm.makeRe(pattern[, options]); + * + * console.log(mm.makeRe('*.js')); + * //=> /^(?:(\.[\\\/])?(?!\.)(?=.)[^\/]*?\.js)$/ + * ``` + * @param {String} `pattern` A glob pattern to convert to regex. + * @param {Object} `options` + * @return {RegExp} Returns a regex created from the given pattern. + * @api public + */ -/***/ }), -/* 283 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +micromatch.makeRe = (...args) => picomatch.makeRe(...args); -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "hostReportError", function() { return hostReportError; }); -/** PURE_IMPORTS_START PURE_IMPORTS_END */ -function hostReportError(err) { - setTimeout(function () { throw err; }, 0); -} -//# sourceMappingURL=hostReportError.js.map +/** + * Scan a glob pattern to separate the pattern into segments. Used + * by the [split](#split) method. + * + * ```js + * const mm = require('micromatch'); + * const state = mm.scan(pattern[, options]); + * ``` + * @param {String} `pattern` + * @param {Object} `options` + * @return {Object} Returns an object with + * @api public + */ +micromatch.scan = (...args) => picomatch.scan(...args); -/***/ }), -/* 284 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/** + * Parse a glob pattern to create the source string for a regular + * expression. + * + * ```js + * const mm = require('micromatch'); + * const state = mm(pattern[, options]); + * ``` + * @param {String} `glob` + * @param {Object} `options` + * @return {Object} Returns an object with useful properties and output to be used as regex source string. + * @api public + */ -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Subscription", function() { return Subscription; }); -/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(285); -/* harmony import */ var _util_isObject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(286); -/* harmony import */ var _util_isFunction__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(280); -/* harmony import */ var _util_UnsubscriptionError__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(287); -/** PURE_IMPORTS_START _util_isArray,_util_isObject,_util_isFunction,_util_UnsubscriptionError PURE_IMPORTS_END */ +micromatch.parse = (patterns, options) => { + let res = []; + for (let pattern of [].concat(patterns || [])) { + for (let str of braces(String(pattern), options)) { + res.push(picomatch.parse(str, options)); + } + } + return res; +}; +/** + * Process the given brace `pattern`. + * + * ```js + * const { braces } = require('micromatch'); + * console.log(braces('foo/{a,b,c}/bar')); + * //=> [ 'foo/(a|b|c)/bar' ] + * + * console.log(braces('foo/{a,b,c}/bar', { expand: true })); + * //=> [ 'foo/a/bar', 'foo/b/bar', 'foo/c/bar' ] + * ``` + * @param {String} `pattern` String with brace pattern to process. + * @param {Object} `options` Any [options](#options) to change how expansion is performed. See the [braces][] library for all available options. + * @return {Array} + * @api public + */ +micromatch.braces = (pattern, options) => { + if (typeof pattern !== 'string') throw new TypeError('Expected a string'); + if ((options && options.nobrace === true) || !/\{.*\}/.test(pattern)) { + return [pattern]; + } + return braces(pattern, options); +}; +/** + * Expand braces + */ -var Subscription = /*@__PURE__*/ (function () { - function Subscription(unsubscribe) { - this.closed = false; - this._parentOrParents = null; - this._subscriptions = null; - if (unsubscribe) { - this._unsubscribe = unsubscribe; - } - } - Subscription.prototype.unsubscribe = function () { - var errors; - if (this.closed) { - return; - } - var _a = this, _parentOrParents = _a._parentOrParents, _unsubscribe = _a._unsubscribe, _subscriptions = _a._subscriptions; - this.closed = true; - this._parentOrParents = null; - this._subscriptions = null; - if (_parentOrParents instanceof Subscription) { - _parentOrParents.remove(this); - } - else if (_parentOrParents !== null) { - for (var index = 0; index < _parentOrParents.length; ++index) { - var parent_1 = _parentOrParents[index]; - parent_1.remove(this); - } - } - if (Object(_util_isFunction__WEBPACK_IMPORTED_MODULE_2__["isFunction"])(_unsubscribe)) { - try { - _unsubscribe.call(this); - } - catch (e) { - errors = e instanceof _util_UnsubscriptionError__WEBPACK_IMPORTED_MODULE_3__["UnsubscriptionError"] ? flattenUnsubscriptionErrors(e.errors) : [e]; - } - } - if (Object(_util_isArray__WEBPACK_IMPORTED_MODULE_0__["isArray"])(_subscriptions)) { - var index = -1; - var len = _subscriptions.length; - while (++index < len) { - var sub = _subscriptions[index]; - if (Object(_util_isObject__WEBPACK_IMPORTED_MODULE_1__["isObject"])(sub)) { - try { - sub.unsubscribe(); - } - catch (e) { - errors = errors || []; - if (e instanceof _util_UnsubscriptionError__WEBPACK_IMPORTED_MODULE_3__["UnsubscriptionError"]) { - errors = errors.concat(flattenUnsubscriptionErrors(e.errors)); - } - else { - errors.push(e); - } - } - } - } - } - if (errors) { - throw new _util_UnsubscriptionError__WEBPACK_IMPORTED_MODULE_3__["UnsubscriptionError"](errors); - } - }; - Subscription.prototype.add = function (teardown) { - var subscription = teardown; - if (!teardown) { - return Subscription.EMPTY; - } - switch (typeof teardown) { - case 'function': - subscription = new Subscription(teardown); - case 'object': - if (subscription === this || subscription.closed || typeof subscription.unsubscribe !== 'function') { - return subscription; - } - else if (this.closed) { - subscription.unsubscribe(); - return subscription; - } - else if (!(subscription instanceof Subscription)) { - var tmp = subscription; - subscription = new Subscription(); - subscription._subscriptions = [tmp]; - } - break; - default: { - throw new Error('unrecognized teardown ' + teardown + ' added to Subscription.'); - } - } - var _parentOrParents = subscription._parentOrParents; - if (_parentOrParents === null) { - subscription._parentOrParents = this; - } - else if (_parentOrParents instanceof Subscription) { - if (_parentOrParents === this) { - return subscription; - } - subscription._parentOrParents = [_parentOrParents, this]; - } - else if (_parentOrParents.indexOf(this) === -1) { - _parentOrParents.push(this); - } - else { - return subscription; - } - var subscriptions = this._subscriptions; - if (subscriptions === null) { - this._subscriptions = [subscription]; - } - else { - subscriptions.push(subscription); - } - return subscription; - }; - Subscription.prototype.remove = function (subscription) { - var subscriptions = this._subscriptions; - if (subscriptions) { - var subscriptionIndex = subscriptions.indexOf(subscription); - if (subscriptionIndex !== -1) { - subscriptions.splice(subscriptionIndex, 1); - } - } - }; - Subscription.EMPTY = (function (empty) { - empty.closed = true; - return empty; - }(new Subscription())); - return Subscription; -}()); +micromatch.braceExpand = (pattern, options) => { + if (typeof pattern !== 'string') throw new TypeError('Expected a string'); + return micromatch.braces(pattern, { ...options, expand: true }); +}; -function flattenUnsubscriptionErrors(errors) { - return errors.reduce(function (errs, err) { return errs.concat((err instanceof _util_UnsubscriptionError__WEBPACK_IMPORTED_MODULE_3__["UnsubscriptionError"]) ? err.errors : err); }, []); -} -//# sourceMappingURL=Subscription.js.map +/** + * Expose micromatch + */ + +module.exports = micromatch; /***/ }), -/* 285 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/* 604 */ +/***/ (function(module, exports, __webpack_require__) { "use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "isArray", function() { return isArray; }); -/** PURE_IMPORTS_START PURE_IMPORTS_END */ -var isArray = /*@__PURE__*/ (function () { return Array.isArray || (function (x) { return x && typeof x.length === 'number'; }); })(); -//# sourceMappingURL=isArray.js.map - -/***/ }), -/* 286 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "isObject", function() { return isObject; }); -/** PURE_IMPORTS_START PURE_IMPORTS_END */ -function isObject(x) { - return x !== null && typeof x === 'object'; -} -//# sourceMappingURL=isObject.js.map +const stringify = __webpack_require__(605); +const compile = __webpack_require__(607); +const expand = __webpack_require__(611); +const parse = __webpack_require__(612); +/** + * Expand the given pattern or create a regex-compatible string. + * + * ```js + * const braces = require('braces'); + * console.log(braces('{a,b,c}', { compile: true })); //=> ['(a|b|c)'] + * console.log(braces('{a,b,c}')); //=> ['a', 'b', 'c'] + * ``` + * @param {String} `str` + * @param {Object} `options` + * @return {String} + * @api public + */ -/***/ }), -/* 287 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +const braces = (input, options = {}) => { + let output = []; -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "UnsubscriptionError", function() { return UnsubscriptionError; }); -/** PURE_IMPORTS_START PURE_IMPORTS_END */ -var UnsubscriptionErrorImpl = /*@__PURE__*/ (function () { - function UnsubscriptionErrorImpl(errors) { - Error.call(this); - this.message = errors ? - errors.length + " errors occurred during unsubscription:\n" + errors.map(function (err, i) { return i + 1 + ") " + err.toString(); }).join('\n ') : ''; - this.name = 'UnsubscriptionError'; - this.errors = errors; - return this; + if (Array.isArray(input)) { + for (let pattern of input) { + let result = braces.create(pattern, options); + if (Array.isArray(result)) { + output.push(...result); + } else { + output.push(result); + } } - UnsubscriptionErrorImpl.prototype = /*@__PURE__*/ Object.create(Error.prototype); - return UnsubscriptionErrorImpl; -})(); -var UnsubscriptionError = UnsubscriptionErrorImpl; -//# sourceMappingURL=UnsubscriptionError.js.map - + } else { + output = [].concat(braces.create(input, options)); + } -/***/ }), -/* 288 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + if (options && options.expand === true && options.nodupes === true) { + output = [...new Set(output)]; + } + return output; +}; -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "rxSubscriber", function() { return rxSubscriber; }); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "$$rxSubscriber", function() { return $$rxSubscriber; }); -/** PURE_IMPORTS_START PURE_IMPORTS_END */ -var rxSubscriber = /*@__PURE__*/ (function () { - return typeof Symbol === 'function' - ? /*@__PURE__*/ Symbol('rxSubscriber') - : '@@rxSubscriber_' + /*@__PURE__*/ Math.random(); -})(); -var $$rxSubscriber = rxSubscriber; -//# sourceMappingURL=rxSubscriber.js.map +/** + * Parse the given `str` with the given `options`. + * + * ```js + * // braces.parse(pattern, [, options]); + * const ast = braces.parse('a/{b,c}/d'); + * console.log(ast); + * ``` + * @param {String} pattern Brace pattern to parse + * @param {Object} options + * @return {Object} Returns an AST + * @api public + */ +braces.parse = (input, options = {}) => parse(input, options); -/***/ }), -/* 289 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/** + * Creates a braces string from an AST, or an AST node. + * + * ```js + * const braces = require('braces'); + * let ast = braces.parse('foo/{a,b}/bar'); + * console.log(stringify(ast.nodes[2])); //=> '{a,b}' + * ``` + * @param {String} `input` Brace pattern or AST. + * @param {Object} `options` + * @return {Array} Returns an array of expanded values. + * @api public + */ -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "toSubscriber", function() { return toSubscriber; }); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(278); -/* harmony import */ var _symbol_rxSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(288); -/* harmony import */ var _Observer__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(281); -/** PURE_IMPORTS_START _Subscriber,_symbol_rxSubscriber,_Observer PURE_IMPORTS_END */ +braces.stringify = (input, options = {}) => { + if (typeof input === 'string') { + return stringify(braces.parse(input, options), options); + } + return stringify(input, options); +}; +/** + * Compiles a brace pattern into a regex-compatible, optimized string. + * This method is called by the main [braces](#braces) function by default. + * + * ```js + * const braces = require('braces'); + * console.log(braces.compile('a/{b,c}/d')); + * //=> ['a/(b|c)/d'] + * ``` + * @param {String} `input` Brace pattern or AST. + * @param {Object} `options` + * @return {Array} Returns an array of expanded values. + * @api public + */ +braces.compile = (input, options = {}) => { + if (typeof input === 'string') { + input = braces.parse(input, options); + } + return compile(input, options); +}; -function toSubscriber(nextOrObserver, error, complete) { - if (nextOrObserver) { - if (nextOrObserver instanceof _Subscriber__WEBPACK_IMPORTED_MODULE_0__["Subscriber"]) { - return nextOrObserver; - } - if (nextOrObserver[_symbol_rxSubscriber__WEBPACK_IMPORTED_MODULE_1__["rxSubscriber"]]) { - return nextOrObserver[_symbol_rxSubscriber__WEBPACK_IMPORTED_MODULE_1__["rxSubscriber"]](); - } - } - if (!nextOrObserver && !error && !complete) { - return new _Subscriber__WEBPACK_IMPORTED_MODULE_0__["Subscriber"](_Observer__WEBPACK_IMPORTED_MODULE_2__["empty"]); - } - return new _Subscriber__WEBPACK_IMPORTED_MODULE_0__["Subscriber"](nextOrObserver, error, complete); -} -//# sourceMappingURL=toSubscriber.js.map +/** + * Expands a brace pattern into an array. This method is called by the + * main [braces](#braces) function when `options.expand` is true. Before + * using this method it's recommended that you read the [performance notes](#performance)) + * and advantages of using [.compile](#compile) instead. + * + * ```js + * const braces = require('braces'); + * console.log(braces.expand('a/{b,c}/d')); + * //=> ['a/b/d', 'a/c/d']; + * ``` + * @param {String} `pattern` Brace pattern + * @param {Object} `options` + * @return {Array} Returns an array of expanded values. + * @api public + */ +braces.expand = (input, options = {}) => { + if (typeof input === 'string') { + input = braces.parse(input, options); + } -/***/ }), -/* 290 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + let result = expand(input, options); -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "observable", function() { return observable; }); -/** PURE_IMPORTS_START PURE_IMPORTS_END */ -var observable = /*@__PURE__*/ (function () { return typeof Symbol === 'function' && Symbol.observable || '@@observable'; })(); -//# sourceMappingURL=observable.js.map + // filter out empty strings if specified + if (options.noempty === true) { + result = result.filter(Boolean); + } + // filter out duplicates if specified + if (options.nodupes === true) { + result = [...new Set(result)]; + } -/***/ }), -/* 291 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + return result; +}; -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "pipe", function() { return pipe; }); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "pipeFromArray", function() { return pipeFromArray; }); -/* harmony import */ var _noop__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(292); -/** PURE_IMPORTS_START _noop PURE_IMPORTS_END */ +/** + * Processes a brace pattern and returns either an expanded array + * (if `options.expand` is true), a highly optimized regex-compatible string. + * This method is called by the main [braces](#braces) function. + * + * ```js + * const braces = require('braces'); + * console.log(braces.create('user-{200..300}/project-{a,b,c}-{1..10}')) + * //=> 'user-(20[0-9]|2[1-9][0-9]|300)/project-(a|b|c)-([1-9]|10)' + * ``` + * @param {String} `pattern` Brace pattern + * @param {Object} `options` + * @return {Array} Returns an array of expanded values. + * @api public + */ -function pipe() { - var fns = []; - for (var _i = 0; _i < arguments.length; _i++) { - fns[_i] = arguments[_i]; - } - return pipeFromArray(fns); -} -function pipeFromArray(fns) { - if (!fns) { - return _noop__WEBPACK_IMPORTED_MODULE_0__["noop"]; - } - if (fns.length === 1) { - return fns[0]; - } - return function piped(input) { - return fns.reduce(function (prev, fn) { return fn(prev); }, input); - }; -} -//# sourceMappingURL=pipe.js.map +braces.create = (input, options = {}) => { + if (input === '' || input.length < 3) { + return [input]; + } + return options.expand !== true + ? braces.compile(input, options) + : braces.expand(input, options); +}; -/***/ }), -/* 292 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/** + * Expose "braces" + */ -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "noop", function() { return noop; }); -/** PURE_IMPORTS_START PURE_IMPORTS_END */ -function noop() { } -//# sourceMappingURL=noop.js.map +module.exports = braces; /***/ }), -/* 293 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/* 605 */ +/***/ (function(module, exports, __webpack_require__) { "use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ConnectableObservable", function() { return ConnectableObservable; }); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "connectableObservableDescriptor", function() { return connectableObservableDescriptor; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(294); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(276); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(278); -/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(284); -/* harmony import */ var _operators_refCount__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(297); -/** PURE_IMPORTS_START tslib,_Subject,_Observable,_Subscriber,_Subscription,_operators_refCount PURE_IMPORTS_END */ - - +const utils = __webpack_require__(606); +module.exports = (ast, options = {}) => { + let stringify = (node, parent = {}) => { + let invalidBlock = options.escapeInvalid && utils.isInvalidBrace(parent); + let invalidNode = node.invalid === true && options.escapeInvalid === true; + let output = ''; -var ConnectableObservable = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](ConnectableObservable, _super); - function ConnectableObservable(source, subjectFactory) { - var _this = _super.call(this) || this; - _this.source = source; - _this.subjectFactory = subjectFactory; - _this._refCount = 0; - _this._isComplete = false; - return _this; + if (node.value) { + if ((invalidBlock || invalidNode) && utils.isOpenOrClose(node)) { + return '\\' + node.value; + } + return node.value; } - ConnectableObservable.prototype._subscribe = function (subscriber) { - return this.getSubject().subscribe(subscriber); - }; - ConnectableObservable.prototype.getSubject = function () { - var subject = this._subject; - if (!subject || subject.isStopped) { - this._subject = this.subjectFactory(); - } - return this._subject; - }; - ConnectableObservable.prototype.connect = function () { - var connection = this._connection; - if (!connection) { - this._isComplete = false; - connection = this._connection = new _Subscription__WEBPACK_IMPORTED_MODULE_4__["Subscription"](); - connection.add(this.source - .subscribe(new ConnectableSubscriber(this.getSubject(), this))); - if (connection.closed) { - this._connection = null; - connection = _Subscription__WEBPACK_IMPORTED_MODULE_4__["Subscription"].EMPTY; - } - } - return connection; - }; - ConnectableObservable.prototype.refCount = function () { - return Object(_operators_refCount__WEBPACK_IMPORTED_MODULE_5__["refCount"])()(this); - }; - return ConnectableObservable; -}(_Observable__WEBPACK_IMPORTED_MODULE_2__["Observable"])); -var connectableObservableDescriptor = /*@__PURE__*/ (function () { - var connectableProto = ConnectableObservable.prototype; - return { - operator: { value: null }, - _refCount: { value: 0, writable: true }, - _subject: { value: null, writable: true }, - _connection: { value: null, writable: true }, - _subscribe: { value: connectableProto._subscribe }, - _isComplete: { value: connectableProto._isComplete, writable: true }, - getSubject: { value: connectableProto.getSubject }, - connect: { value: connectableProto.connect }, - refCount: { value: connectableProto.refCount } - }; -})(); -var ConnectableSubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](ConnectableSubscriber, _super); - function ConnectableSubscriber(destination, connectable) { - var _this = _super.call(this, destination) || this; - _this.connectable = connectable; - return _this; - } - ConnectableSubscriber.prototype._error = function (err) { - this._unsubscribe(); - _super.prototype._error.call(this, err); - }; - ConnectableSubscriber.prototype._complete = function () { - this.connectable._isComplete = true; - this._unsubscribe(); - _super.prototype._complete.call(this); - }; - ConnectableSubscriber.prototype._unsubscribe = function () { - var connectable = this.connectable; - if (connectable) { - this.connectable = null; - var connection = connectable._connection; - connectable._refCount = 0; - connectable._subject = null; - connectable._connection = null; - if (connection) { - connection.unsubscribe(); - } - } - }; - return ConnectableSubscriber; -}(_Subject__WEBPACK_IMPORTED_MODULE_1__["SubjectSubscriber"])); -var RefCountOperator = /*@__PURE__*/ (function () { - function RefCountOperator(connectable) { - this.connectable = connectable; + if (node.value) { + return node.value; } - RefCountOperator.prototype.call = function (subscriber, source) { - var connectable = this.connectable; - connectable._refCount++; - var refCounter = new RefCountSubscriber(subscriber, connectable); - var subscription = source.subscribe(refCounter); - if (!refCounter.closed) { - refCounter.connection = connectable.connect(); - } - return subscription; - }; - return RefCountOperator; -}()); -var RefCountSubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](RefCountSubscriber, _super); - function RefCountSubscriber(destination, connectable) { - var _this = _super.call(this, destination) || this; - _this.connectable = connectable; - return _this; + + if (node.nodes) { + for (let child of node.nodes) { + output += stringify(child); + } } - RefCountSubscriber.prototype._unsubscribe = function () { - var connectable = this.connectable; - if (!connectable) { - this.connection = null; - return; - } - this.connectable = null; - var refCount = connectable._refCount; - if (refCount <= 0) { - this.connection = null; - return; - } - connectable._refCount = refCount - 1; - if (refCount > 1) { - this.connection = null; - return; - } - var connection = this.connection; - var sharedConnection = connectable._connection; - this.connection = null; - if (sharedConnection && (!connection || sharedConnection === connection)) { - sharedConnection.unsubscribe(); - } - }; - return RefCountSubscriber; -}(_Subscriber__WEBPACK_IMPORTED_MODULE_3__["Subscriber"])); -//# sourceMappingURL=ConnectableObservable.js.map + return output; + }; + + return stringify(ast); +}; + /***/ }), -/* 294 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/* 606 */ +/***/ (function(module, exports, __webpack_require__) { "use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "SubjectSubscriber", function() { return SubjectSubscriber; }); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Subject", function() { return Subject; }); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "AnonymousSubject", function() { return AnonymousSubject; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(276); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(278); -/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(284); -/* harmony import */ var _util_ObjectUnsubscribedError__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(295); -/* harmony import */ var _SubjectSubscription__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(296); -/* harmony import */ var _internal_symbol_rxSubscriber__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(288); -/** PURE_IMPORTS_START tslib,_Observable,_Subscriber,_Subscription,_util_ObjectUnsubscribedError,_SubjectSubscription,_internal_symbol_rxSubscriber PURE_IMPORTS_END */ +exports.isInteger = num => { + if (typeof num === 'number') { + return Number.isInteger(num); + } + if (typeof num === 'string' && num.trim() !== '') { + return Number.isInteger(Number(num)); + } + return false; +}; +/** + * Find a node of the given type + */ +exports.find = (node, type) => node.nodes.find(node => node.type === type); +/** + * Find a node of the given type + */ +exports.exceedsLimit = (min, max, step = 1, limit) => { + if (limit === false) return false; + if (!exports.isInteger(min) || !exports.isInteger(max)) return false; + return ((Number(max) - Number(min)) / Number(step)) >= limit; +}; -var SubjectSubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](SubjectSubscriber, _super); - function SubjectSubscriber(destination) { - var _this = _super.call(this, destination) || this; - _this.destination = destination; - return _this; - } - return SubjectSubscriber; -}(_Subscriber__WEBPACK_IMPORTED_MODULE_2__["Subscriber"])); +/** + * Escape the given node with '\\' before node.value + */ -var Subject = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](Subject, _super); - function Subject() { - var _this = _super.call(this) || this; - _this.observers = []; - _this.closed = false; - _this.isStopped = false; - _this.hasError = false; - _this.thrownError = null; - return _this; - } - Subject.prototype[_internal_symbol_rxSubscriber__WEBPACK_IMPORTED_MODULE_6__["rxSubscriber"]] = function () { - return new SubjectSubscriber(this); - }; - Subject.prototype.lift = function (operator) { - var subject = new AnonymousSubject(this, this); - subject.operator = operator; - return subject; - }; - Subject.prototype.next = function (value) { - if (this.closed) { - throw new _util_ObjectUnsubscribedError__WEBPACK_IMPORTED_MODULE_4__["ObjectUnsubscribedError"](); - } - if (!this.isStopped) { - var observers = this.observers; - var len = observers.length; - var copy = observers.slice(); - for (var i = 0; i < len; i++) { - copy[i].next(value); - } - } - }; - Subject.prototype.error = function (err) { - if (this.closed) { - throw new _util_ObjectUnsubscribedError__WEBPACK_IMPORTED_MODULE_4__["ObjectUnsubscribedError"](); - } - this.hasError = true; - this.thrownError = err; - this.isStopped = true; - var observers = this.observers; - var len = observers.length; - var copy = observers.slice(); - for (var i = 0; i < len; i++) { - copy[i].error(err); - } - this.observers.length = 0; - }; - Subject.prototype.complete = function () { - if (this.closed) { - throw new _util_ObjectUnsubscribedError__WEBPACK_IMPORTED_MODULE_4__["ObjectUnsubscribedError"](); - } - this.isStopped = true; - var observers = this.observers; - var len = observers.length; - var copy = observers.slice(); - for (var i = 0; i < len; i++) { - copy[i].complete(); - } - this.observers.length = 0; - }; - Subject.prototype.unsubscribe = function () { - this.isStopped = true; - this.closed = true; - this.observers = null; - }; - Subject.prototype._trySubscribe = function (subscriber) { - if (this.closed) { - throw new _util_ObjectUnsubscribedError__WEBPACK_IMPORTED_MODULE_4__["ObjectUnsubscribedError"](); - } - else { - return _super.prototype._trySubscribe.call(this, subscriber); - } - }; - Subject.prototype._subscribe = function (subscriber) { - if (this.closed) { - throw new _util_ObjectUnsubscribedError__WEBPACK_IMPORTED_MODULE_4__["ObjectUnsubscribedError"](); - } - else if (this.hasError) { - subscriber.error(this.thrownError); - return _Subscription__WEBPACK_IMPORTED_MODULE_3__["Subscription"].EMPTY; - } - else if (this.isStopped) { - subscriber.complete(); - return _Subscription__WEBPACK_IMPORTED_MODULE_3__["Subscription"].EMPTY; - } - else { - this.observers.push(subscriber); - return new _SubjectSubscription__WEBPACK_IMPORTED_MODULE_5__["SubjectSubscription"](this, subscriber); - } - }; - Subject.prototype.asObservable = function () { - var observable = new _Observable__WEBPACK_IMPORTED_MODULE_1__["Observable"](); - observable.source = this; - return observable; - }; - Subject.create = function (destination, source) { - return new AnonymousSubject(destination, source); - }; - return Subject; -}(_Observable__WEBPACK_IMPORTED_MODULE_1__["Observable"])); +exports.escapeNode = (block, n = 0, type) => { + let node = block.nodes[n]; + if (!node) return; -var AnonymousSubject = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](AnonymousSubject, _super); - function AnonymousSubject(destination, source) { - var _this = _super.call(this) || this; - _this.destination = destination; - _this.source = source; - return _this; + if ((type && node.type === type) || node.type === 'open' || node.type === 'close') { + if (node.escaped !== true) { + node.value = '\\' + node.value; + node.escaped = true; } - AnonymousSubject.prototype.next = function (value) { - var destination = this.destination; - if (destination && destination.next) { - destination.next(value); - } - }; - AnonymousSubject.prototype.error = function (err) { - var destination = this.destination; - if (destination && destination.error) { - this.destination.error(err); - } - }; - AnonymousSubject.prototype.complete = function () { - var destination = this.destination; - if (destination && destination.complete) { - this.destination.complete(); - } - }; - AnonymousSubject.prototype._subscribe = function (subscriber) { - var source = this.source; - if (source) { - return this.source.subscribe(subscriber); - } - else { - return _Subscription__WEBPACK_IMPORTED_MODULE_3__["Subscription"].EMPTY; - } - }; - return AnonymousSubject; -}(Subject)); + } +}; -//# sourceMappingURL=Subject.js.map +/** + * Returns true if the given brace node should be enclosed in literal braces + */ + +exports.encloseBrace = node => { + if (node.type !== 'brace') return false; + if ((node.commas >> 0 + node.ranges >> 0) === 0) { + node.invalid = true; + return true; + } + return false; +}; +/** + * Returns true if a brace node is invalid. + */ -/***/ }), -/* 295 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +exports.isInvalidBrace = block => { + if (block.type !== 'brace') return false; + if (block.invalid === true || block.dollar) return true; + if ((block.commas >> 0 + block.ranges >> 0) === 0) { + block.invalid = true; + return true; + } + if (block.open !== true || block.close !== true) { + block.invalid = true; + return true; + } + return false; +}; -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ObjectUnsubscribedError", function() { return ObjectUnsubscribedError; }); -/** PURE_IMPORTS_START PURE_IMPORTS_END */ -var ObjectUnsubscribedErrorImpl = /*@__PURE__*/ (function () { - function ObjectUnsubscribedErrorImpl() { - Error.call(this); - this.message = 'object unsubscribed'; - this.name = 'ObjectUnsubscribedError'; - return this; +/** + * Returns true if a node is an open or close node + */ + +exports.isOpenOrClose = node => { + if (node.type === 'open' || node.type === 'close') { + return true; + } + return node.open === true || node.close === true; +}; + +/** + * Reduce an array of text nodes. + */ + +exports.reduce = nodes => nodes.reduce((acc, node) => { + if (node.type === 'text') acc.push(node.value); + if (node.type === 'range') node.type = 'text'; + return acc; +}, []); + +/** + * Flatten an array + */ + +exports.flatten = (...args) => { + const result = []; + const flat = arr => { + for (let i = 0; i < arr.length; i++) { + let ele = arr[i]; + Array.isArray(ele) ? flat(ele, result) : ele !== void 0 && result.push(ele); } - ObjectUnsubscribedErrorImpl.prototype = /*@__PURE__*/ Object.create(Error.prototype); - return ObjectUnsubscribedErrorImpl; -})(); -var ObjectUnsubscribedError = ObjectUnsubscribedErrorImpl; -//# sourceMappingURL=ObjectUnsubscribedError.js.map + return result; + }; + flat(args); + return result; +}; /***/ }), -/* 296 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/* 607 */ +/***/ (function(module, exports, __webpack_require__) { "use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "SubjectSubscription", function() { return SubjectSubscription; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(284); -/** PURE_IMPORTS_START tslib,_Subscription PURE_IMPORTS_END */ -var SubjectSubscription = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](SubjectSubscription, _super); - function SubjectSubscription(subject, subscriber) { - var _this = _super.call(this) || this; - _this.subject = subject; - _this.subscriber = subscriber; - _this.closed = false; - return _this; +const fill = __webpack_require__(608); +const utils = __webpack_require__(606); + +const compile = (ast, options = {}) => { + let walk = (node, parent = {}) => { + let invalidBlock = utils.isInvalidBrace(parent); + let invalidNode = node.invalid === true && options.escapeInvalid === true; + let invalid = invalidBlock === true || invalidNode === true; + let prefix = options.escapeInvalid === true ? '\\' : ''; + let output = ''; + + if (node.isOpen === true) { + return prefix + node.value; + } + if (node.isClose === true) { + return prefix + node.value; } - SubjectSubscription.prototype.unsubscribe = function () { - if (this.closed) { - return; - } - this.closed = true; - var subject = this.subject; - var observers = subject.observers; - this.subject = null; - if (!observers || observers.length === 0 || subject.isStopped || subject.closed) { - return; - } - var subscriberIndex = observers.indexOf(this.subscriber); - if (subscriberIndex !== -1) { - observers.splice(subscriberIndex, 1); - } - }; - return SubjectSubscription; -}(_Subscription__WEBPACK_IMPORTED_MODULE_1__["Subscription"])); -//# sourceMappingURL=SubjectSubscription.js.map + if (node.type === 'open') { + return invalid ? (prefix + node.value) : '('; + } + if (node.type === 'close') { + return invalid ? (prefix + node.value) : ')'; + } -/***/ }), -/* 297 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + if (node.type === 'comma') { + return node.prev.type === 'comma' ? '' : (invalid ? node.value : '|'); + } -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "refCount", function() { return refCount; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); -/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ + if (node.value) { + return node.value; + } + if (node.nodes && node.ranges > 0) { + let args = utils.reduce(node.nodes); + let range = fill(...args, { ...options, wrap: false, toRegex: true }); -function refCount() { - return function refCountOperatorFunction(source) { - return source.lift(new RefCountOperator(source)); - }; -} -var RefCountOperator = /*@__PURE__*/ (function () { - function RefCountOperator(connectable) { - this.connectable = connectable; + if (range.length !== 0) { + return args.length > 1 && range.length > 1 ? `(${range})` : range; + } } - RefCountOperator.prototype.call = function (subscriber, source) { - var connectable = this.connectable; - connectable._refCount++; - var refCounter = new RefCountSubscriber(subscriber, connectable); - var subscription = source.subscribe(refCounter); - if (!refCounter.closed) { - refCounter.connection = connectable.connect(); - } - return subscription; - }; - return RefCountOperator; -}()); -var RefCountSubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](RefCountSubscriber, _super); - function RefCountSubscriber(destination, connectable) { - var _this = _super.call(this, destination) || this; - _this.connectable = connectable; - return _this; + + if (node.nodes) { + for (let child of node.nodes) { + output += walk(child, node); + } } - RefCountSubscriber.prototype._unsubscribe = function () { - var connectable = this.connectable; - if (!connectable) { - this.connection = null; - return; - } - this.connectable = null; - var refCount = connectable._refCount; - if (refCount <= 0) { - this.connection = null; - return; - } - connectable._refCount = refCount - 1; - if (refCount > 1) { - this.connection = null; - return; - } - var connection = this.connection; - var sharedConnection = connectable._connection; - this.connection = null; - if (sharedConnection && (!connection || sharedConnection === connection)) { - sharedConnection.unsubscribe(); - } - }; - return RefCountSubscriber; -}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); -//# sourceMappingURL=refCount.js.map + return output; + }; + + return walk(ast); +}; + +module.exports = compile; /***/ }), -/* 298 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/* 608 */ +/***/ (function(module, exports, __webpack_require__) { "use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "groupBy", function() { return groupBy; }); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "GroupedObservable", function() { return GroupedObservable; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); -/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(284); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(276); -/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(294); -/** PURE_IMPORTS_START tslib,_Subscriber,_Subscription,_Observable,_Subject PURE_IMPORTS_END */ +/*! + * fill-range + * + * Copyright (c) 2014-present, Jon Schlinkert. + * Licensed under the MIT License. + */ +const util = __webpack_require__(29); +const toRegexRange = __webpack_require__(609); +const isObject = val => val !== null && typeof val === 'object' && !Array.isArray(val); -function groupBy(keySelector, elementSelector, durationSelector, subjectSelector) { - return function (source) { - return source.lift(new GroupByOperator(keySelector, elementSelector, durationSelector, subjectSelector)); - }; -} -var GroupByOperator = /*@__PURE__*/ (function () { - function GroupByOperator(keySelector, elementSelector, durationSelector, subjectSelector) { - this.keySelector = keySelector; - this.elementSelector = elementSelector; - this.durationSelector = durationSelector; - this.subjectSelector = subjectSelector; - } - GroupByOperator.prototype.call = function (subscriber, source) { - return source.subscribe(new GroupBySubscriber(subscriber, this.keySelector, this.elementSelector, this.durationSelector, this.subjectSelector)); - }; - return GroupByOperator; -}()); -var GroupBySubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](GroupBySubscriber, _super); - function GroupBySubscriber(destination, keySelector, elementSelector, durationSelector, subjectSelector) { - var _this = _super.call(this, destination) || this; - _this.keySelector = keySelector; - _this.elementSelector = elementSelector; - _this.durationSelector = durationSelector; - _this.subjectSelector = subjectSelector; - _this.groups = null; - _this.attemptedToUnsubscribe = false; - _this.count = 0; - return _this; - } - GroupBySubscriber.prototype._next = function (value) { - var key; - try { - key = this.keySelector(value); - } - catch (err) { - this.error(err); - return; - } - this._group(value, key); - }; - GroupBySubscriber.prototype._group = function (value, key) { - var groups = this.groups; - if (!groups) { - groups = this.groups = new Map(); - } - var group = groups.get(key); - var element; - if (this.elementSelector) { - try { - element = this.elementSelector(value); - } - catch (err) { - this.error(err); - } - } - else { - element = value; - } - if (!group) { - group = (this.subjectSelector ? this.subjectSelector() : new _Subject__WEBPACK_IMPORTED_MODULE_4__["Subject"]()); - groups.set(key, group); - var groupedObservable = new GroupedObservable(key, group, this); - this.destination.next(groupedObservable); - if (this.durationSelector) { - var duration = void 0; - try { - duration = this.durationSelector(new GroupedObservable(key, group)); - } - catch (err) { - this.error(err); - return; - } - this.add(duration.subscribe(new GroupDurationSubscriber(key, group, this))); - } - } - if (!group.closed) { - group.next(element); - } - }; - GroupBySubscriber.prototype._error = function (err) { - var groups = this.groups; - if (groups) { - groups.forEach(function (group, key) { - group.error(err); - }); - groups.clear(); - } - this.destination.error(err); - }; - GroupBySubscriber.prototype._complete = function () { - var groups = this.groups; - if (groups) { - groups.forEach(function (group, key) { - group.complete(); - }); - groups.clear(); - } - this.destination.complete(); - }; - GroupBySubscriber.prototype.removeGroup = function (key) { - this.groups.delete(key); - }; - GroupBySubscriber.prototype.unsubscribe = function () { - if (!this.closed) { - this.attemptedToUnsubscribe = true; - if (this.count === 0) { - _super.prototype.unsubscribe.call(this); - } - } - }; - return GroupBySubscriber; -}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); -var GroupDurationSubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](GroupDurationSubscriber, _super); - function GroupDurationSubscriber(key, group, parent) { - var _this = _super.call(this, group) || this; - _this.key = key; - _this.group = group; - _this.parent = parent; - return _this; - } - GroupDurationSubscriber.prototype._next = function (value) { - this.complete(); - }; - GroupDurationSubscriber.prototype._unsubscribe = function () { - var _a = this, parent = _a.parent, key = _a.key; - this.key = this.parent = null; - if (parent) { - parent.removeGroup(key); - } - }; - return GroupDurationSubscriber; -}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); -var GroupedObservable = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](GroupedObservable, _super); - function GroupedObservable(key, groupSubject, refCountSubscription) { - var _this = _super.call(this) || this; - _this.key = key; - _this.groupSubject = groupSubject; - _this.refCountSubscription = refCountSubscription; - return _this; - } - GroupedObservable.prototype._subscribe = function (subscriber) { - var subscription = new _Subscription__WEBPACK_IMPORTED_MODULE_2__["Subscription"](); - var _a = this, refCountSubscription = _a.refCountSubscription, groupSubject = _a.groupSubject; - if (refCountSubscription && !refCountSubscription.closed) { - subscription.add(new InnerRefCountSubscription(refCountSubscription)); - } - subscription.add(groupSubject.subscribe(subscriber)); - return subscription; - }; - return GroupedObservable; -}(_Observable__WEBPACK_IMPORTED_MODULE_3__["Observable"])); +const transform = toNumber => { + return value => toNumber === true ? Number(value) : String(value); +}; -var InnerRefCountSubscription = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](InnerRefCountSubscription, _super); - function InnerRefCountSubscription(parent) { - var _this = _super.call(this) || this; - _this.parent = parent; - parent.count++; - return _this; - } - InnerRefCountSubscription.prototype.unsubscribe = function () { - var parent = this.parent; - if (!parent.closed && !this.closed) { - _super.prototype.unsubscribe.call(this); - parent.count -= 1; - if (parent.count === 0 && parent.attemptedToUnsubscribe) { - parent.unsubscribe(); - } - } - }; - return InnerRefCountSubscription; -}(_Subscription__WEBPACK_IMPORTED_MODULE_2__["Subscription"])); -//# sourceMappingURL=groupBy.js.map +const isValidValue = value => { + return typeof value === 'number' || (typeof value === 'string' && value !== ''); +}; +const isNumber = num => Number.isInteger(+num); -/***/ }), -/* 299 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +const zeros = input => { + let value = `${input}`; + let index = -1; + if (value[0] === '-') value = value.slice(1); + if (value === '0') return false; + while (value[++index] === '0'); + return index > 0; +}; -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "BehaviorSubject", function() { return BehaviorSubject; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(294); -/* harmony import */ var _util_ObjectUnsubscribedError__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(295); -/** PURE_IMPORTS_START tslib,_Subject,_util_ObjectUnsubscribedError PURE_IMPORTS_END */ +const stringify = (start, end, options) => { + if (typeof start === 'string' || typeof end === 'string') { + return true; + } + return options.stringify === true; +}; + +const pad = (input, maxLength, toNumber) => { + if (maxLength > 0) { + let dash = input[0] === '-' ? '-' : ''; + if (dash) input = input.slice(1); + input = (dash + input.padStart(dash ? maxLength - 1 : maxLength, '0')); + } + if (toNumber === false) { + return String(input); + } + return input; +}; + +const toMaxLen = (input, maxLength) => { + let negative = input[0] === '-' ? '-' : ''; + if (negative) { + input = input.slice(1); + maxLength--; + } + while (input.length < maxLength) input = '0' + input; + return negative ? ('-' + input) : input; +}; +const toSequence = (parts, options) => { + parts.negatives.sort((a, b) => a < b ? -1 : a > b ? 1 : 0); + parts.positives.sort((a, b) => a < b ? -1 : a > b ? 1 : 0); + let prefix = options.capture ? '' : '?:'; + let positives = ''; + let negatives = ''; + let result; -var BehaviorSubject = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](BehaviorSubject, _super); - function BehaviorSubject(_value) { - var _this = _super.call(this) || this; - _this._value = _value; - return _this; - } - Object.defineProperty(BehaviorSubject.prototype, "value", { - get: function () { - return this.getValue(); - }, - enumerable: true, - configurable: true - }); - BehaviorSubject.prototype._subscribe = function (subscriber) { - var subscription = _super.prototype._subscribe.call(this, subscriber); - if (subscription && !subscription.closed) { - subscriber.next(this._value); - } - return subscription; - }; - BehaviorSubject.prototype.getValue = function () { - if (this.hasError) { - throw this.thrownError; - } - else if (this.closed) { - throw new _util_ObjectUnsubscribedError__WEBPACK_IMPORTED_MODULE_2__["ObjectUnsubscribedError"](); - } - else { - return this._value; - } - }; - BehaviorSubject.prototype.next = function (value) { - _super.prototype.next.call(this, this._value = value); - }; - return BehaviorSubject; -}(_Subject__WEBPACK_IMPORTED_MODULE_1__["Subject"])); + if (parts.positives.length) { + positives = parts.positives.join('|'); + } -//# sourceMappingURL=BehaviorSubject.js.map + if (parts.negatives.length) { + negatives = `-(${prefix}${parts.negatives.join('|')})`; + } + if (positives && negatives) { + result = `${positives}|${negatives}`; + } else { + result = positives || negatives; + } -/***/ }), -/* 300 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + if (options.wrap) { + return `(${prefix}${result})`; + } -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ReplaySubject", function() { return ReplaySubject; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(294); -/* harmony import */ var _scheduler_queue__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(301); -/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(284); -/* harmony import */ var _operators_observeOn__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(308); -/* harmony import */ var _util_ObjectUnsubscribedError__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(295); -/* harmony import */ var _SubjectSubscription__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(296); -/** PURE_IMPORTS_START tslib,_Subject,_scheduler_queue,_Subscription,_operators_observeOn,_util_ObjectUnsubscribedError,_SubjectSubscription PURE_IMPORTS_END */ + return result; +}; + +const toRange = (a, b, isNumbers, options) => { + if (isNumbers) { + return toRegexRange(a, b, { wrap: false, ...options }); + } + + let start = String.fromCharCode(a); + if (a === b) return start; + + let stop = String.fromCharCode(b); + return `[${start}-${stop}]`; +}; + +const toRegex = (start, end, options) => { + if (Array.isArray(start)) { + let wrap = options.wrap === true; + let prefix = options.capture ? '' : '?:'; + return wrap ? `(${prefix}${start.join('|')})` : start.join('|'); + } + return toRegexRange(start, end, options); +}; + +const rangeError = (...args) => { + return new RangeError('Invalid range arguments: ' + util.inspect(...args)); +}; + +const invalidRange = (start, end, options) => { + if (options.strictRanges === true) throw rangeError([start, end]); + return []; +}; + +const invalidStep = (step, options) => { + if (options.strictRanges === true) { + throw new TypeError(`Expected step "${step}" to be a number`); + } + return []; +}; + +const fillNumbers = (start, end, step = 1, options = {}) => { + let a = Number(start); + let b = Number(end); + + if (!Number.isInteger(a) || !Number.isInteger(b)) { + if (options.strictRanges === true) throw rangeError([start, end]); + return []; + } + + // fix negative zero + if (a === 0) a = 0; + if (b === 0) b = 0; + let descending = a > b; + let startString = String(start); + let endString = String(end); + let stepString = String(step); + step = Math.max(Math.abs(step), 1); + let padded = zeros(startString) || zeros(endString) || zeros(stepString); + let maxLen = padded ? Math.max(startString.length, endString.length, stepString.length) : 0; + let toNumber = padded === false && stringify(start, end, options) === false; + let format = options.transform || transform(toNumber); + if (options.toRegex && step === 1) { + return toRange(toMaxLen(start, maxLen), toMaxLen(end, maxLen), true, options); + } + let parts = { negatives: [], positives: [] }; + let push = num => parts[num < 0 ? 'negatives' : 'positives'].push(Math.abs(num)); + let range = []; + let index = 0; + while (descending ? a >= b : a <= b) { + if (options.toRegex === true && step > 1) { + push(a); + } else { + range.push(pad(format(a, index), maxLen, toNumber)); + } + a = descending ? a - step : a + step; + index++; + } + if (options.toRegex === true) { + return step > 1 + ? toSequence(parts, options) + : toRegex(range, null, { wrap: false, ...options }); + } -var ReplaySubject = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](ReplaySubject, _super); - function ReplaySubject(bufferSize, windowTime, scheduler) { - if (bufferSize === void 0) { - bufferSize = Number.POSITIVE_INFINITY; - } - if (windowTime === void 0) { - windowTime = Number.POSITIVE_INFINITY; - } - var _this = _super.call(this) || this; - _this.scheduler = scheduler; - _this._events = []; - _this._infiniteTimeWindow = false; - _this._bufferSize = bufferSize < 1 ? 1 : bufferSize; - _this._windowTime = windowTime < 1 ? 1 : windowTime; - if (windowTime === Number.POSITIVE_INFINITY) { - _this._infiniteTimeWindow = true; - _this.next = _this.nextInfiniteTimeWindow; - } - else { - _this.next = _this.nextTimeWindow; - } - return _this; - } - ReplaySubject.prototype.nextInfiniteTimeWindow = function (value) { - var _events = this._events; - _events.push(value); - if (_events.length > this._bufferSize) { - _events.shift(); - } - _super.prototype.next.call(this, value); - }; - ReplaySubject.prototype.nextTimeWindow = function (value) { - this._events.push(new ReplayEvent(this._getNow(), value)); - this._trimBufferThenGetEvents(); - _super.prototype.next.call(this, value); - }; - ReplaySubject.prototype._subscribe = function (subscriber) { - var _infiniteTimeWindow = this._infiniteTimeWindow; - var _events = _infiniteTimeWindow ? this._events : this._trimBufferThenGetEvents(); - var scheduler = this.scheduler; - var len = _events.length; - var subscription; - if (this.closed) { - throw new _util_ObjectUnsubscribedError__WEBPACK_IMPORTED_MODULE_5__["ObjectUnsubscribedError"](); - } - else if (this.isStopped || this.hasError) { - subscription = _Subscription__WEBPACK_IMPORTED_MODULE_3__["Subscription"].EMPTY; - } - else { - this.observers.push(subscriber); - subscription = new _SubjectSubscription__WEBPACK_IMPORTED_MODULE_6__["SubjectSubscription"](this, subscriber); - } - if (scheduler) { - subscriber.add(subscriber = new _operators_observeOn__WEBPACK_IMPORTED_MODULE_4__["ObserveOnSubscriber"](subscriber, scheduler)); - } - if (_infiniteTimeWindow) { - for (var i = 0; i < len && !subscriber.closed; i++) { - subscriber.next(_events[i]); - } - } - else { - for (var i = 0; i < len && !subscriber.closed; i++) { - subscriber.next(_events[i].value); - } - } - if (this.hasError) { - subscriber.error(this.thrownError); - } - else if (this.isStopped) { - subscriber.complete(); - } - return subscription; - }; - ReplaySubject.prototype._getNow = function () { - return (this.scheduler || _scheduler_queue__WEBPACK_IMPORTED_MODULE_2__["queue"]).now(); - }; - ReplaySubject.prototype._trimBufferThenGetEvents = function () { - var now = this._getNow(); - var _bufferSize = this._bufferSize; - var _windowTime = this._windowTime; - var _events = this._events; - var eventsCount = _events.length; - var spliceCount = 0; - while (spliceCount < eventsCount) { - if ((now - _events[spliceCount].time) < _windowTime) { - break; - } - spliceCount++; - } - if (eventsCount > _bufferSize) { - spliceCount = Math.max(spliceCount, eventsCount - _bufferSize); - } - if (spliceCount > 0) { - _events.splice(0, spliceCount); - } - return _events; - }; - return ReplaySubject; -}(_Subject__WEBPACK_IMPORTED_MODULE_1__["Subject"])); + return range; +}; -var ReplayEvent = /*@__PURE__*/ (function () { - function ReplayEvent(time, value) { - this.time = time; - this.value = value; - } - return ReplayEvent; -}()); -//# sourceMappingURL=ReplaySubject.js.map +const fillLetters = (start, end, step = 1, options = {}) => { + if ((!isNumber(start) && start.length > 1) || (!isNumber(end) && end.length > 1)) { + return invalidRange(start, end, options); + } -/***/ }), -/* 301 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + let format = options.transform || (val => String.fromCharCode(val)); + let a = `${start}`.charCodeAt(0); + let b = `${end}`.charCodeAt(0); -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "queue", function() { return queue; }); -/* harmony import */ var _QueueAction__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(302); -/* harmony import */ var _QueueScheduler__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(305); -/** PURE_IMPORTS_START _QueueAction,_QueueScheduler PURE_IMPORTS_END */ + let descending = a > b; + let min = Math.min(a, b); + let max = Math.max(a, b); + if (options.toRegex && step === 1) { + return toRange(min, max, false, options); + } -var queue = /*@__PURE__*/ new _QueueScheduler__WEBPACK_IMPORTED_MODULE_1__["QueueScheduler"](_QueueAction__WEBPACK_IMPORTED_MODULE_0__["QueueAction"]); -//# sourceMappingURL=queue.js.map + let range = []; + let index = 0; + while (descending ? a >= b : a <= b) { + range.push(format(a, index)); + a = descending ? a - step : a + step; + index++; + } -/***/ }), -/* 302 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + if (options.toRegex === true) { + return toRegex(range, null, { wrap: false, options }); + } -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "QueueAction", function() { return QueueAction; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _AsyncAction__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(303); -/** PURE_IMPORTS_START tslib,_AsyncAction PURE_IMPORTS_END */ + return range; +}; +const fill = (start, end, step, options = {}) => { + if (end == null && isValidValue(start)) { + return [start]; + } -var QueueAction = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](QueueAction, _super); - function QueueAction(scheduler, work) { - var _this = _super.call(this, scheduler, work) || this; - _this.scheduler = scheduler; - _this.work = work; - return _this; - } - QueueAction.prototype.schedule = function (state, delay) { - if (delay === void 0) { - delay = 0; - } - if (delay > 0) { - return _super.prototype.schedule.call(this, state, delay); - } - this.delay = delay; - this.state = state; - this.scheduler.flush(this); - return this; - }; - QueueAction.prototype.execute = function (state, delay) { - return (delay > 0 || this.closed) ? - _super.prototype.execute.call(this, state, delay) : - this._execute(state, delay); - }; - QueueAction.prototype.requestAsyncId = function (scheduler, id, delay) { - if (delay === void 0) { - delay = 0; - } - if ((delay !== null && delay > 0) || (delay === null && this.delay > 0)) { - return _super.prototype.requestAsyncId.call(this, scheduler, id, delay); - } - return scheduler.flush(this); - }; - return QueueAction; -}(_AsyncAction__WEBPACK_IMPORTED_MODULE_1__["AsyncAction"])); + if (!isValidValue(start) || !isValidValue(end)) { + return invalidRange(start, end, options); + } -//# sourceMappingURL=QueueAction.js.map + if (typeof step === 'function') { + return fill(start, end, 1, { transform: step }); + } + if (isObject(step)) { + return fill(start, end, 0, step); + } -/***/ }), -/* 303 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + let opts = { ...options }; + if (opts.capture === true) opts.wrap = true; + step = step || opts.step || 1; -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "AsyncAction", function() { return AsyncAction; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _Action__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(304); -/** PURE_IMPORTS_START tslib,_Action PURE_IMPORTS_END */ + if (!isNumber(step)) { + if (step != null && !isObject(step)) return invalidStep(step, opts); + return fill(start, end, 1, step); + } + if (isNumber(start) && isNumber(end)) { + return fillNumbers(start, end, step, opts); + } -var AsyncAction = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](AsyncAction, _super); - function AsyncAction(scheduler, work) { - var _this = _super.call(this, scheduler, work) || this; - _this.scheduler = scheduler; - _this.work = work; - _this.pending = false; - return _this; - } - AsyncAction.prototype.schedule = function (state, delay) { - if (delay === void 0) { - delay = 0; - } - if (this.closed) { - return this; - } - this.state = state; - var id = this.id; - var scheduler = this.scheduler; - if (id != null) { - this.id = this.recycleAsyncId(scheduler, id, delay); - } - this.pending = true; - this.delay = delay; - this.id = this.id || this.requestAsyncId(scheduler, this.id, delay); - return this; - }; - AsyncAction.prototype.requestAsyncId = function (scheduler, id, delay) { - if (delay === void 0) { - delay = 0; - } - return setInterval(scheduler.flush.bind(scheduler, this), delay); - }; - AsyncAction.prototype.recycleAsyncId = function (scheduler, id, delay) { - if (delay === void 0) { - delay = 0; - } - if (delay !== null && this.delay === delay && this.pending === false) { - return id; - } - clearInterval(id); - return undefined; - }; - AsyncAction.prototype.execute = function (state, delay) { - if (this.closed) { - return new Error('executing a cancelled action'); - } - this.pending = false; - var error = this._execute(state, delay); - if (error) { - return error; - } - else if (this.pending === false && this.id != null) { - this.id = this.recycleAsyncId(this.scheduler, this.id, null); - } - }; - AsyncAction.prototype._execute = function (state, delay) { - var errored = false; - var errorValue = undefined; - try { - this.work(state); - } - catch (e) { - errored = true; - errorValue = !!e && e || new Error(e); - } - if (errored) { - this.unsubscribe(); - return errorValue; - } - }; - AsyncAction.prototype._unsubscribe = function () { - var id = this.id; - var scheduler = this.scheduler; - var actions = scheduler.actions; - var index = actions.indexOf(this); - this.work = null; - this.state = null; - this.pending = false; - this.scheduler = null; - if (index !== -1) { - actions.splice(index, 1); - } - if (id != null) { - this.id = this.recycleAsyncId(scheduler, id, null); - } - this.delay = null; - }; - return AsyncAction; -}(_Action__WEBPACK_IMPORTED_MODULE_1__["Action"])); + return fillLetters(start, end, Math.max(Math.abs(step), 1), opts); +}; -//# sourceMappingURL=AsyncAction.js.map +module.exports = fill; /***/ }), -/* 304 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/* 609 */ +/***/ (function(module, exports, __webpack_require__) { "use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Action", function() { return Action; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(284); -/** PURE_IMPORTS_START tslib,_Subscription PURE_IMPORTS_END */ - - -var Action = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](Action, _super); - function Action(scheduler, work) { - return _super.call(this) || this; - } - Action.prototype.schedule = function (state, delay) { - if (delay === void 0) { - delay = 0; - } - return this; - }; - return Action; -}(_Subscription__WEBPACK_IMPORTED_MODULE_1__["Subscription"])); - -//# sourceMappingURL=Action.js.map +/*! + * to-regex-range + * + * Copyright (c) 2015-present, Jon Schlinkert. + * Released under the MIT License. + */ -/***/ }), -/* 305 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "QueueScheduler", function() { return QueueScheduler; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _AsyncScheduler__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(306); -/** PURE_IMPORTS_START tslib,_AsyncScheduler PURE_IMPORTS_END */ +const isNumber = __webpack_require__(610); +const toRegexRange = (min, max, options) => { + if (isNumber(min) === false) { + throw new TypeError('toRegexRange: expected the first argument to be a number'); + } -var QueueScheduler = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](QueueScheduler, _super); - function QueueScheduler() { - return _super !== null && _super.apply(this, arguments) || this; - } - return QueueScheduler; -}(_AsyncScheduler__WEBPACK_IMPORTED_MODULE_1__["AsyncScheduler"])); + if (max === void 0 || min === max) { + return String(min); + } -//# sourceMappingURL=QueueScheduler.js.map + if (isNumber(max) === false) { + throw new TypeError('toRegexRange: expected the second argument to be a number.'); + } + let opts = { relaxZeros: true, ...options }; + if (typeof opts.strictZeros === 'boolean') { + opts.relaxZeros = opts.strictZeros === false; + } -/***/ }), -/* 306 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + let relax = String(opts.relaxZeros); + let shorthand = String(opts.shorthand); + let capture = String(opts.capture); + let wrap = String(opts.wrap); + let cacheKey = min + ':' + max + '=' + relax + shorthand + capture + wrap; -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "AsyncScheduler", function() { return AsyncScheduler; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _Scheduler__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(307); -/** PURE_IMPORTS_START tslib,_Scheduler PURE_IMPORTS_END */ + if (toRegexRange.cache.hasOwnProperty(cacheKey)) { + return toRegexRange.cache[cacheKey].result; + } + let a = Math.min(min, max); + let b = Math.max(min, max); -var AsyncScheduler = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](AsyncScheduler, _super); - function AsyncScheduler(SchedulerAction, now) { - if (now === void 0) { - now = _Scheduler__WEBPACK_IMPORTED_MODULE_1__["Scheduler"].now; - } - var _this = _super.call(this, SchedulerAction, function () { - if (AsyncScheduler.delegate && AsyncScheduler.delegate !== _this) { - return AsyncScheduler.delegate.now(); - } - else { - return now(); - } - }) || this; - _this.actions = []; - _this.active = false; - _this.scheduled = undefined; - return _this; + if (Math.abs(a - b) === 1) { + let result = min + '|' + max; + if (opts.capture) { + return `(${result})`; } - AsyncScheduler.prototype.schedule = function (work, delay, state) { - if (delay === void 0) { - delay = 0; - } - if (AsyncScheduler.delegate && AsyncScheduler.delegate !== this) { - return AsyncScheduler.delegate.schedule(work, delay, state); - } - else { - return _super.prototype.schedule.call(this, work, delay, state); - } - }; - AsyncScheduler.prototype.flush = function (action) { - var actions = this.actions; - if (this.active) { - actions.push(action); - return; - } - var error; - this.active = true; - do { - if (error = action.execute(action.state, action.delay)) { - break; - } - } while (action = actions.shift()); - this.active = false; - if (error) { - while (action = actions.shift()) { - action.unsubscribe(); - } - throw error; - } - }; - return AsyncScheduler; -}(_Scheduler__WEBPACK_IMPORTED_MODULE_1__["Scheduler"])); - -//# sourceMappingURL=AsyncScheduler.js.map + if (opts.wrap === false) { + return result; + } + return `(?:${result})`; + } + let isPadded = hasPadding(min) || hasPadding(max); + let state = { min, max, a, b }; + let positives = []; + let negatives = []; -/***/ }), -/* 307 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + if (isPadded) { + state.isPadded = isPadded; + state.maxLen = String(state.max).length; + } -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Scheduler", function() { return Scheduler; }); -var Scheduler = /*@__PURE__*/ (function () { - function Scheduler(SchedulerAction, now) { - if (now === void 0) { - now = Scheduler.now; - } - this.SchedulerAction = SchedulerAction; - this.now = now; - } - Scheduler.prototype.schedule = function (work, delay, state) { - if (delay === void 0) { - delay = 0; - } - return new this.SchedulerAction(this, work).schedule(state, delay); - }; - Scheduler.now = function () { return Date.now(); }; - return Scheduler; -}()); + if (a < 0) { + let newMin = b < 0 ? Math.abs(b) : 1; + negatives = splitToPatterns(newMin, Math.abs(a), state, opts); + a = state.a = 0; + } -//# sourceMappingURL=Scheduler.js.map + if (b >= 0) { + positives = splitToPatterns(a, b, state, opts); + } + state.negatives = negatives; + state.positives = positives; + state.result = collatePatterns(negatives, positives, opts); -/***/ }), -/* 308 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + if (opts.capture === true) { + state.result = `(${state.result})`; + } else if (opts.wrap !== false && (positives.length + negatives.length) > 1) { + state.result = `(?:${state.result})`; + } -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "observeOn", function() { return observeOn; }); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ObserveOnOperator", function() { return ObserveOnOperator; }); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ObserveOnSubscriber", function() { return ObserveOnSubscriber; }); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ObserveOnMessage", function() { return ObserveOnMessage; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); -/* harmony import */ var _Notification__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(309); -/** PURE_IMPORTS_START tslib,_Subscriber,_Notification PURE_IMPORTS_END */ + toRegexRange.cache[cacheKey] = state; + return state.result; +}; +function collatePatterns(neg, pos, options) { + let onlyNegative = filterPatterns(neg, pos, '-', false, options) || []; + let onlyPositive = filterPatterns(pos, neg, '', false, options) || []; + let intersected = filterPatterns(neg, pos, '-?', true, options) || []; + let subpatterns = onlyNegative.concat(intersected).concat(onlyPositive); + return subpatterns.join('|'); +} +function splitToRanges(min, max) { + let nines = 1; + let zeros = 1; -function observeOn(scheduler, delay) { - if (delay === void 0) { - delay = 0; - } - return function observeOnOperatorFunction(source) { - return source.lift(new ObserveOnOperator(scheduler, delay)); - }; -} -var ObserveOnOperator = /*@__PURE__*/ (function () { - function ObserveOnOperator(scheduler, delay) { - if (delay === void 0) { - delay = 0; - } - this.scheduler = scheduler; - this.delay = delay; - } - ObserveOnOperator.prototype.call = function (subscriber, source) { - return source.subscribe(new ObserveOnSubscriber(subscriber, this.scheduler, this.delay)); - }; - return ObserveOnOperator; -}()); + let stop = countNines(min, nines); + let stops = new Set([max]); -var ObserveOnSubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](ObserveOnSubscriber, _super); - function ObserveOnSubscriber(destination, scheduler, delay) { - if (delay === void 0) { - delay = 0; - } - var _this = _super.call(this, destination) || this; - _this.scheduler = scheduler; - _this.delay = delay; - return _this; - } - ObserveOnSubscriber.dispatch = function (arg) { - var notification = arg.notification, destination = arg.destination; - notification.observe(destination); - this.unsubscribe(); - }; - ObserveOnSubscriber.prototype.scheduleMessage = function (notification) { - var destination = this.destination; - destination.add(this.scheduler.schedule(ObserveOnSubscriber.dispatch, this.delay, new ObserveOnMessage(notification, this.destination))); - }; - ObserveOnSubscriber.prototype._next = function (value) { - this.scheduleMessage(_Notification__WEBPACK_IMPORTED_MODULE_2__["Notification"].createNext(value)); - }; - ObserveOnSubscriber.prototype._error = function (err) { - this.scheduleMessage(_Notification__WEBPACK_IMPORTED_MODULE_2__["Notification"].createError(err)); - this.unsubscribe(); - }; - ObserveOnSubscriber.prototype._complete = function () { - this.scheduleMessage(_Notification__WEBPACK_IMPORTED_MODULE_2__["Notification"].createComplete()); - this.unsubscribe(); - }; - return ObserveOnSubscriber; -}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); + while (min <= stop && stop <= max) { + stops.add(stop); + nines += 1; + stop = countNines(min, nines); + } -var ObserveOnMessage = /*@__PURE__*/ (function () { - function ObserveOnMessage(notification, destination) { - this.notification = notification; - this.destination = destination; - } - return ObserveOnMessage; -}()); + stop = countZeros(max + 1, zeros) - 1; -//# sourceMappingURL=observeOn.js.map + while (min < stop && stop <= max) { + stops.add(stop); + zeros += 1; + stop = countZeros(max + 1, zeros) - 1; + } + stops = [...stops]; + stops.sort(compare); + return stops; +} -/***/ }), -/* 309 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/** + * Convert a range to a regex pattern + * @param {Number} `start` + * @param {Number} `stop` + * @return {String} + */ -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "NotificationKind", function() { return NotificationKind; }); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Notification", function() { return Notification; }); -/* harmony import */ var _observable_empty__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(310); -/* harmony import */ var _observable_of__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(311); -/* harmony import */ var _observable_throwError__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(316); -/** PURE_IMPORTS_START _observable_empty,_observable_of,_observable_throwError PURE_IMPORTS_END */ +function rangeToPattern(start, stop, options) { + if (start === stop) { + return { pattern: start, count: [], digits: 0 }; + } + let zipped = zip(start, stop); + let digits = zipped.length; + let pattern = ''; + let count = 0; + for (let i = 0; i < digits; i++) { + let [startDigit, stopDigit] = zipped[i]; -var NotificationKind; -/*@__PURE__*/ (function (NotificationKind) { - NotificationKind["NEXT"] = "N"; - NotificationKind["ERROR"] = "E"; - NotificationKind["COMPLETE"] = "C"; -})(NotificationKind || (NotificationKind = {})); -var Notification = /*@__PURE__*/ (function () { - function Notification(kind, value, error) { - this.kind = kind; - this.value = value; - this.error = error; - this.hasValue = kind === 'N'; + if (startDigit === stopDigit) { + pattern += startDigit; + + } else if (startDigit !== '0' || stopDigit !== '9') { + pattern += toCharacterClass(startDigit, stopDigit, options); + + } else { + count++; } - Notification.prototype.observe = function (observer) { - switch (this.kind) { - case 'N': - return observer.next && observer.next(this.value); - case 'E': - return observer.error && observer.error(this.error); - case 'C': - return observer.complete && observer.complete(); - } - }; - Notification.prototype.do = function (next, error, complete) { - var kind = this.kind; - switch (kind) { - case 'N': - return next && next(this.value); - case 'E': - return error && error(this.error); - case 'C': - return complete && complete(); - } - }; - Notification.prototype.accept = function (nextOrObserver, error, complete) { - if (nextOrObserver && typeof nextOrObserver.next === 'function') { - return this.observe(nextOrObserver); - } - else { - return this.do(nextOrObserver, error, complete); - } - }; - Notification.prototype.toObservable = function () { - var kind = this.kind; - switch (kind) { - case 'N': - return Object(_observable_of__WEBPACK_IMPORTED_MODULE_1__["of"])(this.value); - case 'E': - return Object(_observable_throwError__WEBPACK_IMPORTED_MODULE_2__["throwError"])(this.error); - case 'C': - return Object(_observable_empty__WEBPACK_IMPORTED_MODULE_0__["empty"])(); - } - throw new Error('unexpected notification kind value'); - }; - Notification.createNext = function (value) { - if (typeof value !== 'undefined') { - return new Notification('N', value); - } - return Notification.undefinedValueNotification; - }; - Notification.createError = function (err) { - return new Notification('E', undefined, err); - }; - Notification.createComplete = function () { - return Notification.completeNotification; - }; - Notification.completeNotification = new Notification('C'); - Notification.undefinedValueNotification = new Notification('N', undefined); - return Notification; -}()); + } -//# sourceMappingURL=Notification.js.map + if (count) { + pattern += options.shorthand === true ? '\\d' : '[0-9]'; + } + return { pattern, count: [count], digits }; +} -/***/ }), -/* 310 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +function splitToPatterns(min, max, tok, options) { + let ranges = splitToRanges(min, max); + let tokens = []; + let start = min; + let prev; -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "EMPTY", function() { return EMPTY; }); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "empty", function() { return empty; }); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); -/** PURE_IMPORTS_START _Observable PURE_IMPORTS_END */ + for (let i = 0; i < ranges.length; i++) { + let max = ranges[i]; + let obj = rangeToPattern(String(start), String(max), options); + let zeros = ''; -var EMPTY = /*@__PURE__*/ new _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"](function (subscriber) { return subscriber.complete(); }); -function empty(scheduler) { - return scheduler ? emptyScheduled(scheduler) : EMPTY; -} -function emptyScheduled(scheduler) { - return new _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"](function (subscriber) { return scheduler.schedule(function () { return subscriber.complete(); }); }); -} -//# sourceMappingURL=empty.js.map + if (!tok.isPadded && prev && prev.pattern === obj.pattern) { + if (prev.count.length > 1) { + prev.count.pop(); + } + + prev.count.push(obj.count[0]); + prev.string = prev.pattern + toQuantifier(prev.count); + start = max + 1; + continue; + } + if (tok.isPadded) { + zeros = padZeros(max, tok, options); + } -/***/ }), -/* 311 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + obj.string = zeros + obj.pattern + toQuantifier(obj.count); + tokens.push(obj); + start = max + 1; + prev = obj; + } -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "of", function() { return of; }); -/* harmony import */ var _util_isScheduler__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(312); -/* harmony import */ var _fromArray__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(313); -/* harmony import */ var _scheduled_scheduleArray__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(315); -/** PURE_IMPORTS_START _util_isScheduler,_fromArray,_scheduled_scheduleArray PURE_IMPORTS_END */ + return tokens; +} +function filterPatterns(arr, comparison, prefix, intersection, options) { + let result = []; + for (let ele of arr) { + let { string } = ele; -function of() { - var args = []; - for (var _i = 0; _i < arguments.length; _i++) { - args[_i] = arguments[_i]; - } - var scheduler = args[args.length - 1]; - if (Object(_util_isScheduler__WEBPACK_IMPORTED_MODULE_0__["isScheduler"])(scheduler)) { - args.pop(); - return Object(_scheduled_scheduleArray__WEBPACK_IMPORTED_MODULE_2__["scheduleArray"])(args, scheduler); + // only push if _both_ are negative... + if (!intersection && !contains(comparison, 'string', string)) { + result.push(prefix + string); } - else { - return Object(_fromArray__WEBPACK_IMPORTED_MODULE_1__["fromArray"])(args); + + // or _both_ are positive + if (intersection && contains(comparison, 'string', string)) { + result.push(prefix + string); } + } + return result; } -//# sourceMappingURL=of.js.map - -/***/ }), -/* 312 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/** + * Zip strings + */ -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "isScheduler", function() { return isScheduler; }); -/** PURE_IMPORTS_START PURE_IMPORTS_END */ -function isScheduler(value) { - return value && typeof value.schedule === 'function'; +function zip(a, b) { + let arr = []; + for (let i = 0; i < a.length; i++) arr.push([a[i], b[i]]); + return arr; } -//# sourceMappingURL=isScheduler.js.map +function compare(a, b) { + return a > b ? 1 : b > a ? -1 : 0; +} -/***/ }), -/* 313 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +function contains(arr, key, val) { + return arr.some(ele => ele[key] === val); +} -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "fromArray", function() { return fromArray; }); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); -/* harmony import */ var _util_subscribeToArray__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(314); -/* harmony import */ var _scheduled_scheduleArray__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(315); -/** PURE_IMPORTS_START _Observable,_util_subscribeToArray,_scheduled_scheduleArray PURE_IMPORTS_END */ +function countNines(min, len) { + return Number(String(min).slice(0, -len) + '9'.repeat(len)); +} +function countZeros(integer, zeros) { + return integer - (integer % Math.pow(10, zeros)); +} +function toQuantifier(digits) { + let [start = 0, stop = ''] = digits; + if (stop || start > 1) { + return `{${start + (stop ? ',' + stop : '')}}`; + } + return ''; +} -function fromArray(input, scheduler) { - if (!scheduler) { - return new _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"](Object(_util_subscribeToArray__WEBPACK_IMPORTED_MODULE_1__["subscribeToArray"])(input)); - } - else { - return Object(_scheduled_scheduleArray__WEBPACK_IMPORTED_MODULE_2__["scheduleArray"])(input, scheduler); - } +function toCharacterClass(a, b, options) { + return `[${a}${(b - a === 1) ? '' : '-'}${b}]`; } -//# sourceMappingURL=fromArray.js.map +function hasPadding(str) { + return /^-?(0+)\d/.test(str); +} -/***/ }), -/* 314 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +function padZeros(value, tok, options) { + if (!tok.isPadded) { + return value; + } -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "subscribeToArray", function() { return subscribeToArray; }); -/** PURE_IMPORTS_START PURE_IMPORTS_END */ -var subscribeToArray = function (array) { - return function (subscriber) { - for (var i = 0, len = array.length; i < len && !subscriber.closed; i++) { - subscriber.next(array[i]); - } - subscriber.complete(); - }; -}; -//# sourceMappingURL=subscribeToArray.js.map + let diff = Math.abs(tok.maxLen - String(value).length); + let relax = options.relaxZeros !== false; + switch (diff) { + case 0: + return ''; + case 1: + return relax ? '0?' : '0'; + case 2: + return relax ? '0{0,2}' : '00'; + default: { + return relax ? `0{0,${diff}}` : `0{${diff}}`; + } + } +} -/***/ }), -/* 315 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/** + * Cache + */ -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "scheduleArray", function() { return scheduleArray; }); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); -/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(284); -/** PURE_IMPORTS_START _Observable,_Subscription PURE_IMPORTS_END */ +toRegexRange.cache = {}; +toRegexRange.clearCache = () => (toRegexRange.cache = {}); +/** + * Expose `toRegexRange` + */ -function scheduleArray(input, scheduler) { - return new _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"](function (subscriber) { - var sub = new _Subscription__WEBPACK_IMPORTED_MODULE_1__["Subscription"](); - var i = 0; - sub.add(scheduler.schedule(function () { - if (i === input.length) { - subscriber.complete(); - return; - } - subscriber.next(input[i++]); - if (!subscriber.closed) { - sub.add(this.schedule()); - } - })); - return sub; - }); -} -//# sourceMappingURL=scheduleArray.js.map +module.exports = toRegexRange; /***/ }), -/* 316 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/* 610 */ +/***/ (function(module, exports, __webpack_require__) { "use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "throwError", function() { return throwError; }); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); -/** PURE_IMPORTS_START _Observable PURE_IMPORTS_END */ +/*! + * is-number + * + * Copyright (c) 2014-present, Jon Schlinkert. + * Released under the MIT License. + */ -function throwError(error, scheduler) { - if (!scheduler) { - return new _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"](function (subscriber) { return subscriber.error(error); }); - } - else { - return new _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"](function (subscriber) { return scheduler.schedule(dispatch, 0, { error: error, subscriber: subscriber }); }); - } -} -function dispatch(_a) { - var error = _a.error, subscriber = _a.subscriber; - subscriber.error(error); -} -//# sourceMappingURL=throwError.js.map + + +module.exports = function(num) { + if (typeof num === 'number') { + return num - num === 0; + } + if (typeof num === 'string' && num.trim() !== '') { + return Number.isFinite ? Number.isFinite(+num) : isFinite(+num); + } + return false; +}; /***/ }), -/* 317 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/* 611 */ +/***/ (function(module, exports, __webpack_require__) { "use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "AsyncSubject", function() { return AsyncSubject; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(294); -/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(284); -/** PURE_IMPORTS_START tslib,_Subject,_Subscription PURE_IMPORTS_END */ +const fill = __webpack_require__(608); +const stringify = __webpack_require__(605); +const utils = __webpack_require__(606); -var AsyncSubject = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](AsyncSubject, _super); - function AsyncSubject() { - var _this = _super !== null && _super.apply(this, arguments) || this; - _this.value = null; - _this.hasNext = false; - _this.hasCompleted = false; - return _this; - } - AsyncSubject.prototype._subscribe = function (subscriber) { - if (this.hasError) { - subscriber.error(this.thrownError); - return _Subscription__WEBPACK_IMPORTED_MODULE_2__["Subscription"].EMPTY; - } - else if (this.hasCompleted && this.hasNext) { - subscriber.next(this.value); - subscriber.complete(); - return _Subscription__WEBPACK_IMPORTED_MODULE_2__["Subscription"].EMPTY; - } - return _super.prototype._subscribe.call(this, subscriber); - }; - AsyncSubject.prototype.next = function (value) { - if (!this.hasCompleted) { - this.value = value; - this.hasNext = true; - } - }; - AsyncSubject.prototype.error = function (error) { - if (!this.hasCompleted) { - _super.prototype.error.call(this, error); - } - }; - AsyncSubject.prototype.complete = function () { - this.hasCompleted = true; - if (this.hasNext) { - _super.prototype.next.call(this, this.value); - } - _super.prototype.complete.call(this); - }; - return AsyncSubject; -}(_Subject__WEBPACK_IMPORTED_MODULE_1__["Subject"])); - -//# sourceMappingURL=AsyncSubject.js.map +const append = (queue = '', stash = '', enclose = false) => { + let result = []; + queue = [].concat(queue); + stash = [].concat(stash); -/***/ }), -/* 318 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + if (!stash.length) return queue; + if (!queue.length) { + return enclose ? utils.flatten(stash).map(ele => `{${ele}}`) : stash; + } -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "asap", function() { return asap; }); -/* harmony import */ var _AsapAction__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(319); -/* harmony import */ var _AsapScheduler__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(321); -/** PURE_IMPORTS_START _AsapAction,_AsapScheduler PURE_IMPORTS_END */ + for (let item of queue) { + if (Array.isArray(item)) { + for (let value of item) { + result.push(append(value, stash, enclose)); + } + } else { + for (let ele of stash) { + if (enclose === true && typeof ele === 'string') ele = `{${ele}}`; + result.push(Array.isArray(ele) ? append(item, ele, enclose) : (item + ele)); + } + } + } + return utils.flatten(result); +}; +const expand = (ast, options = {}) => { + let rangeLimit = options.rangeLimit === void 0 ? 1000 : options.rangeLimit; -var asap = /*@__PURE__*/ new _AsapScheduler__WEBPACK_IMPORTED_MODULE_1__["AsapScheduler"](_AsapAction__WEBPACK_IMPORTED_MODULE_0__["AsapAction"]); -//# sourceMappingURL=asap.js.map + let walk = (node, parent = {}) => { + node.queue = []; + let p = parent; + let q = parent.queue; -/***/ }), -/* 319 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + while (p.type !== 'brace' && p.type !== 'root' && p.parent) { + p = p.parent; + q = p.queue; + } -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "AsapAction", function() { return AsapAction; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _util_Immediate__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(320); -/* harmony import */ var _AsyncAction__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(303); -/** PURE_IMPORTS_START tslib,_util_Immediate,_AsyncAction PURE_IMPORTS_END */ + if (node.invalid || node.dollar) { + q.push(append(q.pop(), stringify(node, options))); + return; + } + if (node.type === 'brace' && node.invalid !== true && node.nodes.length === 2) { + q.push(append(q.pop(), ['{}'])); + return; + } + if (node.nodes && node.ranges > 0) { + let args = utils.reduce(node.nodes); -var AsapAction = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](AsapAction, _super); - function AsapAction(scheduler, work) { - var _this = _super.call(this, scheduler, work) || this; - _this.scheduler = scheduler; - _this.work = work; - return _this; - } - AsapAction.prototype.requestAsyncId = function (scheduler, id, delay) { - if (delay === void 0) { - delay = 0; - } - if (delay !== null && delay > 0) { - return _super.prototype.requestAsyncId.call(this, scheduler, id, delay); - } - scheduler.actions.push(this); - return scheduler.scheduled || (scheduler.scheduled = _util_Immediate__WEBPACK_IMPORTED_MODULE_1__["Immediate"].setImmediate(scheduler.flush.bind(scheduler, null))); - }; - AsapAction.prototype.recycleAsyncId = function (scheduler, id, delay) { - if (delay === void 0) { - delay = 0; - } - if ((delay !== null && delay > 0) || (delay === null && this.delay > 0)) { - return _super.prototype.recycleAsyncId.call(this, scheduler, id, delay); - } - if (scheduler.actions.length === 0) { - _util_Immediate__WEBPACK_IMPORTED_MODULE_1__["Immediate"].clearImmediate(id); - scheduler.scheduled = undefined; - } - return undefined; - }; - return AsapAction; -}(_AsyncAction__WEBPACK_IMPORTED_MODULE_2__["AsyncAction"])); + if (utils.exceedsLimit(...args, options.step, rangeLimit)) { + throw new RangeError('expanded array length exceeds range limit. Use options.rangeLimit to increase or disable the limit.'); + } -//# sourceMappingURL=AsapAction.js.map + let range = fill(...args, options); + if (range.length === 0) { + range = stringify(node, options); + } + q.push(append(q.pop(), range)); + node.nodes = []; + return; + } -/***/ }), -/* 320 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + let enclose = utils.encloseBrace(node); + let queue = node.queue; + let block = node; -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Immediate", function() { return Immediate; }); -/** PURE_IMPORTS_START PURE_IMPORTS_END */ -var nextHandle = 1; -var tasksByHandle = {}; -function runIfPresent(handle) { - var cb = tasksByHandle[handle]; - if (cb) { - cb(); + while (block.type !== 'brace' && block.type !== 'root' && block.parent) { + block = block.parent; + queue = block.queue; } -} -var Immediate = { - setImmediate: function (cb) { - var handle = nextHandle++; - tasksByHandle[handle] = cb; - Promise.resolve().then(function () { return runIfPresent(handle); }); - return handle; - }, - clearImmediate: function (handle) { - delete tasksByHandle[handle]; - }, -}; -//# sourceMappingURL=Immediate.js.map + for (let i = 0; i < node.nodes.length; i++) { + let child = node.nodes[i]; -/***/ }), -/* 321 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + if (child.type === 'comma' && node.type === 'brace') { + if (i === 1) queue.push(''); + queue.push(''); + continue; + } -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "AsapScheduler", function() { return AsapScheduler; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _AsyncScheduler__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(306); -/** PURE_IMPORTS_START tslib,_AsyncScheduler PURE_IMPORTS_END */ + if (child.type === 'close') { + q.push(append(q.pop(), queue, enclose)); + continue; + } + if (child.value && child.type !== 'open') { + queue.push(append(queue.pop(), child.value)); + continue; + } -var AsapScheduler = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](AsapScheduler, _super); - function AsapScheduler() { - return _super !== null && _super.apply(this, arguments) || this; + if (child.nodes) { + walk(child, node); + } } - AsapScheduler.prototype.flush = function (action) { - this.active = true; - this.scheduled = undefined; - var actions = this.actions; - var error; - var index = -1; - var count = actions.length; - action = action || actions.shift(); - do { - if (error = action.execute(action.state, action.delay)) { - break; - } - } while (++index < count && (action = actions.shift())); - this.active = false; - if (error) { - while (++index < count && (action = actions.shift())) { - action.unsubscribe(); - } - throw error; - } - }; - return AsapScheduler; -}(_AsyncScheduler__WEBPACK_IMPORTED_MODULE_1__["AsyncScheduler"])); -//# sourceMappingURL=AsapScheduler.js.map + return queue; + }; + + return utils.flatten(walk(ast)); +}; + +module.exports = expand; /***/ }), -/* 322 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/* 612 */ +/***/ (function(module, exports, __webpack_require__) { "use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "async", function() { return async; }); -/* harmony import */ var _AsyncAction__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(303); -/* harmony import */ var _AsyncScheduler__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(306); -/** PURE_IMPORTS_START _AsyncAction,_AsyncScheduler PURE_IMPORTS_END */ - -var async = /*@__PURE__*/ new _AsyncScheduler__WEBPACK_IMPORTED_MODULE_1__["AsyncScheduler"](_AsyncAction__WEBPACK_IMPORTED_MODULE_0__["AsyncAction"]); -//# sourceMappingURL=async.js.map +const stringify = __webpack_require__(605); -/***/ }), -/* 323 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/** + * Constants + */ -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "animationFrame", function() { return animationFrame; }); -/* harmony import */ var _AnimationFrameAction__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(324); -/* harmony import */ var _AnimationFrameScheduler__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(325); -/** PURE_IMPORTS_START _AnimationFrameAction,_AnimationFrameScheduler PURE_IMPORTS_END */ +const { + MAX_LENGTH, + CHAR_BACKSLASH, /* \ */ + CHAR_BACKTICK, /* ` */ + CHAR_COMMA, /* , */ + CHAR_DOT, /* . */ + CHAR_LEFT_PARENTHESES, /* ( */ + CHAR_RIGHT_PARENTHESES, /* ) */ + CHAR_LEFT_CURLY_BRACE, /* { */ + CHAR_RIGHT_CURLY_BRACE, /* } */ + CHAR_LEFT_SQUARE_BRACKET, /* [ */ + CHAR_RIGHT_SQUARE_BRACKET, /* ] */ + CHAR_DOUBLE_QUOTE, /* " */ + CHAR_SINGLE_QUOTE, /* ' */ + CHAR_NO_BREAK_SPACE, + CHAR_ZERO_WIDTH_NOBREAK_SPACE +} = __webpack_require__(613); +/** + * parse + */ -var animationFrame = /*@__PURE__*/ new _AnimationFrameScheduler__WEBPACK_IMPORTED_MODULE_1__["AnimationFrameScheduler"](_AnimationFrameAction__WEBPACK_IMPORTED_MODULE_0__["AnimationFrameAction"]); -//# sourceMappingURL=animationFrame.js.map +const parse = (input, options = {}) => { + if (typeof input !== 'string') { + throw new TypeError('Expected a string'); + } + let opts = options || {}; + let max = typeof opts.maxLength === 'number' ? Math.min(MAX_LENGTH, opts.maxLength) : MAX_LENGTH; + if (input.length > max) { + throw new SyntaxError(`Input length (${input.length}), exceeds max characters (${max})`); + } -/***/ }), -/* 324 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + let ast = { type: 'root', input, nodes: [] }; + let stack = [ast]; + let block = ast; + let prev = ast; + let brackets = 0; + let length = input.length; + let index = 0; + let depth = 0; + let value; + let memo = {}; -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "AnimationFrameAction", function() { return AnimationFrameAction; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _AsyncAction__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(303); -/** PURE_IMPORTS_START tslib,_AsyncAction PURE_IMPORTS_END */ + /** + * Helpers + */ + const advance = () => input[index++]; + const push = node => { + if (node.type === 'text' && prev.type === 'dot') { + prev.type = 'text'; + } -var AnimationFrameAction = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](AnimationFrameAction, _super); - function AnimationFrameAction(scheduler, work) { - var _this = _super.call(this, scheduler, work) || this; - _this.scheduler = scheduler; - _this.work = work; - return _this; + if (prev && prev.type === 'text' && node.type === 'text') { + prev.value += node.value; + return; } - AnimationFrameAction.prototype.requestAsyncId = function (scheduler, id, delay) { - if (delay === void 0) { - delay = 0; - } - if (delay !== null && delay > 0) { - return _super.prototype.requestAsyncId.call(this, scheduler, id, delay); - } - scheduler.actions.push(this); - return scheduler.scheduled || (scheduler.scheduled = requestAnimationFrame(function () { return scheduler.flush(null); })); - }; - AnimationFrameAction.prototype.recycleAsyncId = function (scheduler, id, delay) { - if (delay === void 0) { - delay = 0; - } - if ((delay !== null && delay > 0) || (delay === null && this.delay > 0)) { - return _super.prototype.recycleAsyncId.call(this, scheduler, id, delay); - } - if (scheduler.actions.length === 0) { - cancelAnimationFrame(id); - scheduler.scheduled = undefined; - } - return undefined; - }; - return AnimationFrameAction; -}(_AsyncAction__WEBPACK_IMPORTED_MODULE_1__["AsyncAction"])); -//# sourceMappingURL=AnimationFrameAction.js.map + block.nodes.push(node); + node.parent = block; + node.prev = prev; + prev = node; + return node; + }; + push({ type: 'bos' }); -/***/ }), -/* 325 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + while (index < length) { + block = stack[stack.length - 1]; + value = advance(); -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "AnimationFrameScheduler", function() { return AnimationFrameScheduler; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _AsyncScheduler__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(306); -/** PURE_IMPORTS_START tslib,_AsyncScheduler PURE_IMPORTS_END */ + /** + * Invalid chars + */ + + if (value === CHAR_ZERO_WIDTH_NOBREAK_SPACE || value === CHAR_NO_BREAK_SPACE) { + continue; + } + /** + * Escaped chars + */ -var AnimationFrameScheduler = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](AnimationFrameScheduler, _super); - function AnimationFrameScheduler() { - return _super !== null && _super.apply(this, arguments) || this; + if (value === CHAR_BACKSLASH) { + push({ type: 'text', value: (options.keepEscaping ? value : '') + advance() }); + continue; } - AnimationFrameScheduler.prototype.flush = function (action) { - this.active = true; - this.scheduled = undefined; - var actions = this.actions; - var error; - var index = -1; - var count = actions.length; - action = action || actions.shift(); - do { - if (error = action.execute(action.state, action.delay)) { - break; - } - } while (++index < count && (action = actions.shift())); - this.active = false; - if (error) { - while (++index < count && (action = actions.shift())) { - action.unsubscribe(); - } - throw error; - } - }; - return AnimationFrameScheduler; -}(_AsyncScheduler__WEBPACK_IMPORTED_MODULE_1__["AsyncScheduler"])); -//# sourceMappingURL=AnimationFrameScheduler.js.map + /** + * Right square bracket (literal): ']' + */ + if (value === CHAR_RIGHT_SQUARE_BRACKET) { + push({ type: 'text', value: '\\' + value }); + continue; + } -/***/ }), -/* 326 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + /** + * Left square bracket: '[' + */ -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "VirtualTimeScheduler", function() { return VirtualTimeScheduler; }); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "VirtualAction", function() { return VirtualAction; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _AsyncAction__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(303); -/* harmony import */ var _AsyncScheduler__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(306); -/** PURE_IMPORTS_START tslib,_AsyncAction,_AsyncScheduler PURE_IMPORTS_END */ + if (value === CHAR_LEFT_SQUARE_BRACKET) { + brackets++; + let closed = true; + let next; + while (index < length && (next = advance())) { + value += next; -var VirtualTimeScheduler = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](VirtualTimeScheduler, _super); - function VirtualTimeScheduler(SchedulerAction, maxFrames) { - if (SchedulerAction === void 0) { - SchedulerAction = VirtualAction; - } - if (maxFrames === void 0) { - maxFrames = Number.POSITIVE_INFINITY; - } - var _this = _super.call(this, SchedulerAction, function () { return _this.frame; }) || this; - _this.maxFrames = maxFrames; - _this.frame = 0; - _this.index = -1; - return _this; - } - VirtualTimeScheduler.prototype.flush = function () { - var _a = this, actions = _a.actions, maxFrames = _a.maxFrames; - var error, action; - while ((action = actions[0]) && action.delay <= maxFrames) { - actions.shift(); - this.frame = action.delay; - if (error = action.execute(action.state, action.delay)) { - break; - } + if (next === CHAR_LEFT_SQUARE_BRACKET) { + brackets++; + continue; } - if (error) { - while (action = actions.shift()) { - action.unsubscribe(); - } - throw error; + + if (next === CHAR_BACKSLASH) { + value += advance(); + continue; } - }; - VirtualTimeScheduler.frameTimeFactor = 10; - return VirtualTimeScheduler; -}(_AsyncScheduler__WEBPACK_IMPORTED_MODULE_2__["AsyncScheduler"])); -var VirtualAction = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](VirtualAction, _super); - function VirtualAction(scheduler, work, index) { - if (index === void 0) { - index = scheduler.index += 1; + if (next === CHAR_RIGHT_SQUARE_BRACKET) { + brackets--; + + if (brackets === 0) { + break; + } } - var _this = _super.call(this, scheduler, work) || this; - _this.scheduler = scheduler; - _this.work = work; - _this.index = index; - _this.active = true; - _this.index = scheduler.index = index; - return _this; + } + + push({ type: 'text', value }); + continue; + } + + /** + * Parentheses + */ + + if (value === CHAR_LEFT_PARENTHESES) { + block = push({ type: 'paren', nodes: [] }); + stack.push(block); + push({ type: 'text', value }); + continue; } - VirtualAction.prototype.schedule = function (state, delay) { - if (delay === void 0) { - delay = 0; - } - if (!this.id) { - return _super.prototype.schedule.call(this, state, delay); - } - this.active = false; - var action = new VirtualAction(this.scheduler, this.work); - this.add(action); - return action.schedule(state, delay); - }; - VirtualAction.prototype.requestAsyncId = function (scheduler, id, delay) { - if (delay === void 0) { - delay = 0; - } - this.delay = scheduler.frame + delay; - var actions = scheduler.actions; - actions.push(this); - actions.sort(VirtualAction.sortActions); - return true; - }; - VirtualAction.prototype.recycleAsyncId = function (scheduler, id, delay) { - if (delay === void 0) { - delay = 0; - } - return undefined; - }; - VirtualAction.prototype._execute = function (state, delay) { - if (this.active === true) { - return _super.prototype._execute.call(this, state, delay); - } - }; - VirtualAction.sortActions = function (a, b) { - if (a.delay === b.delay) { - if (a.index === b.index) { - return 0; - } - else if (a.index > b.index) { - return 1; - } - else { - return -1; - } - } - else if (a.delay > b.delay) { - return 1; - } - else { - return -1; - } - }; - return VirtualAction; -}(_AsyncAction__WEBPACK_IMPORTED_MODULE_1__["AsyncAction"])); -//# sourceMappingURL=VirtualTimeScheduler.js.map + if (value === CHAR_RIGHT_PARENTHESES) { + if (block.type !== 'paren') { + push({ type: 'text', value }); + continue; + } + block = stack.pop(); + push({ type: 'text', value }); + block = stack[stack.length - 1]; + continue; + } + /** + * Quotes: '|"|` + */ -/***/ }), -/* 327 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + if (value === CHAR_DOUBLE_QUOTE || value === CHAR_SINGLE_QUOTE || value === CHAR_BACKTICK) { + let open = value; + let next; -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "identity", function() { return identity; }); -/** PURE_IMPORTS_START PURE_IMPORTS_END */ -function identity(x) { - return x; -} -//# sourceMappingURL=identity.js.map + if (options.keepQuotes !== true) { + value = ''; + } + while (index < length && (next = advance())) { + if (next === CHAR_BACKSLASH) { + value += next + advance(); + continue; + } -/***/ }), -/* 328 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + if (next === open) { + if (options.keepQuotes === true) value += next; + break; + } -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "isObservable", function() { return isObservable; }); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); -/** PURE_IMPORTS_START _Observable PURE_IMPORTS_END */ + value += next; + } -function isObservable(obj) { - return !!obj && (obj instanceof _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"] || (typeof obj.lift === 'function' && typeof obj.subscribe === 'function')); -} -//# sourceMappingURL=isObservable.js.map + push({ type: 'text', value }); + continue; + } + /** + * Left curly brace: '{' + */ -/***/ }), -/* 329 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + if (value === CHAR_LEFT_CURLY_BRACE) { + depth++; -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ArgumentOutOfRangeError", function() { return ArgumentOutOfRangeError; }); -/** PURE_IMPORTS_START PURE_IMPORTS_END */ -var ArgumentOutOfRangeErrorImpl = /*@__PURE__*/ (function () { - function ArgumentOutOfRangeErrorImpl() { - Error.call(this); - this.message = 'argument out of range'; - this.name = 'ArgumentOutOfRangeError'; - return this; + let dollar = prev.value && prev.value.slice(-1) === '$' || block.dollar === true; + let brace = { + type: 'brace', + open: true, + close: false, + dollar, + depth, + commas: 0, + ranges: 0, + nodes: [] + }; + + block = push(brace); + stack.push(block); + push({ type: 'open', value }); + continue; } - ArgumentOutOfRangeErrorImpl.prototype = /*@__PURE__*/ Object.create(Error.prototype); - return ArgumentOutOfRangeErrorImpl; -})(); -var ArgumentOutOfRangeError = ArgumentOutOfRangeErrorImpl; -//# sourceMappingURL=ArgumentOutOfRangeError.js.map + /** + * Right curly brace: '}' + */ -/***/ }), -/* 330 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + if (value === CHAR_RIGHT_CURLY_BRACE) { + if (block.type !== 'brace') { + push({ type: 'text', value }); + continue; + } -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "EmptyError", function() { return EmptyError; }); -/** PURE_IMPORTS_START PURE_IMPORTS_END */ -var EmptyErrorImpl = /*@__PURE__*/ (function () { - function EmptyErrorImpl() { - Error.call(this); - this.message = 'no elements in sequence'; - this.name = 'EmptyError'; - return this; + let type = 'close'; + block = stack.pop(); + block.close = true; + + push({ type, value }); + depth--; + + block = stack[stack.length - 1]; + continue; } - EmptyErrorImpl.prototype = /*@__PURE__*/ Object.create(Error.prototype); - return EmptyErrorImpl; -})(); -var EmptyError = EmptyErrorImpl; -//# sourceMappingURL=EmptyError.js.map + /** + * Comma: ',' + */ -/***/ }), -/* 331 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + if (value === CHAR_COMMA && depth > 0) { + if (block.ranges > 0) { + block.ranges = 0; + let open = block.nodes.shift(); + block.nodes = [open, { type: 'text', value: stringify(block) }]; + } -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "TimeoutError", function() { return TimeoutError; }); -/** PURE_IMPORTS_START PURE_IMPORTS_END */ -var TimeoutErrorImpl = /*@__PURE__*/ (function () { - function TimeoutErrorImpl() { - Error.call(this); - this.message = 'Timeout has occurred'; - this.name = 'TimeoutError'; - return this; + push({ type: 'comma', value }); + block.commas++; + continue; } - TimeoutErrorImpl.prototype = /*@__PURE__*/ Object.create(Error.prototype); - return TimeoutErrorImpl; -})(); -var TimeoutError = TimeoutErrorImpl; -//# sourceMappingURL=TimeoutError.js.map + /** + * Dot: '.' + */ -/***/ }), -/* 332 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + if (value === CHAR_DOT && depth > 0 && block.commas === 0) { + let siblings = block.nodes; -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "bindCallback", function() { return bindCallback; }); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); -/* harmony import */ var _AsyncSubject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(317); -/* harmony import */ var _operators_map__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(333); -/* harmony import */ var _util_canReportError__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(277); -/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(285); -/* harmony import */ var _util_isScheduler__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(312); -/** PURE_IMPORTS_START _Observable,_AsyncSubject,_operators_map,_util_canReportError,_util_isArray,_util_isScheduler PURE_IMPORTS_END */ + if (depth === 0 || siblings.length === 0) { + push({ type: 'text', value }); + continue; + } + if (prev.type === 'dot') { + block.range = []; + prev.value += value; + prev.type = 'range'; + if (block.nodes.length !== 3 && block.nodes.length !== 5) { + block.invalid = true; + block.ranges = 0; + prev.type = 'text'; + continue; + } + block.ranges++; + block.args = []; + continue; + } + if (prev.type === 'range') { + siblings.pop(); + let before = siblings[siblings.length - 1]; + before.value += prev.value + value; + prev = before; + block.ranges--; + continue; + } -function bindCallback(callbackFunc, resultSelector, scheduler) { - if (resultSelector) { - if (Object(_util_isScheduler__WEBPACK_IMPORTED_MODULE_5__["isScheduler"])(resultSelector)) { - scheduler = resultSelector; - } - else { - return function () { - var args = []; - for (var _i = 0; _i < arguments.length; _i++) { - args[_i] = arguments[_i]; - } - return bindCallback(callbackFunc, scheduler).apply(void 0, args).pipe(Object(_operators_map__WEBPACK_IMPORTED_MODULE_2__["map"])(function (args) { return Object(_util_isArray__WEBPACK_IMPORTED_MODULE_4__["isArray"])(args) ? resultSelector.apply(void 0, args) : resultSelector(args); })); - }; - } - } - return function () { - var args = []; - for (var _i = 0; _i < arguments.length; _i++) { - args[_i] = arguments[_i]; - } - var context = this; - var subject; - var params = { - context: context, - subject: subject, - callbackFunc: callbackFunc, - scheduler: scheduler, - }; - return new _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"](function (subscriber) { - if (!scheduler) { - if (!subject) { - subject = new _AsyncSubject__WEBPACK_IMPORTED_MODULE_1__["AsyncSubject"](); - var handler = function () { - var innerArgs = []; - for (var _i = 0; _i < arguments.length; _i++) { - innerArgs[_i] = arguments[_i]; - } - subject.next(innerArgs.length <= 1 ? innerArgs[0] : innerArgs); - subject.complete(); - }; - try { - callbackFunc.apply(context, args.concat([handler])); - } - catch (err) { - if (Object(_util_canReportError__WEBPACK_IMPORTED_MODULE_3__["canReportError"])(subject)) { - subject.error(err); - } - else { - console.warn(err); - } - } - } - return subject.subscribe(subscriber); - } - else { - var state = { - args: args, subscriber: subscriber, params: params, - }; - return scheduler.schedule(dispatch, 0, state); - } - }); - }; -} -function dispatch(state) { - var _this = this; - var self = this; - var args = state.args, subscriber = state.subscriber, params = state.params; - var callbackFunc = params.callbackFunc, context = params.context, scheduler = params.scheduler; - var subject = params.subject; - if (!subject) { - subject = params.subject = new _AsyncSubject__WEBPACK_IMPORTED_MODULE_1__["AsyncSubject"](); - var handler = function () { - var innerArgs = []; - for (var _i = 0; _i < arguments.length; _i++) { - innerArgs[_i] = arguments[_i]; - } - var value = innerArgs.length <= 1 ? innerArgs[0] : innerArgs; - _this.add(scheduler.schedule(dispatchNext, 0, { value: value, subject: subject })); - }; - try { - callbackFunc.apply(context, args.concat([handler])); - } - catch (err) { - subject.error(err); - } + push({ type: 'dot', value }); + continue; } - this.add(subject.subscribe(subscriber)); -} -function dispatchNext(state) { - var value = state.value, subject = state.subject; - subject.next(value); - subject.complete(); -} -function dispatchError(state) { - var err = state.err, subject = state.subject; - subject.error(err); -} -//# sourceMappingURL=bindCallback.js.map - -/***/ }), -/* 333 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + /** + * Text + */ -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "map", function() { return map; }); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "MapOperator", function() { return MapOperator; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); -/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ + push({ type: 'text', value }); + } + // Mark imbalanced braces and brackets as invalid + do { + block = stack.pop(); -function map(project, thisArg) { - return function mapOperation(source) { - if (typeof project !== 'function') { - throw new TypeError('argument is not a function. Are you looking for `mapTo()`?'); + if (block.type !== 'root') { + block.nodes.forEach(node => { + if (!node.nodes) { + if (node.type === 'open') node.isOpen = true; + if (node.type === 'close') node.isClose = true; + if (!node.nodes) node.type = 'text'; + node.invalid = true; } - return source.lift(new MapOperator(project, thisArg)); - }; -} -var MapOperator = /*@__PURE__*/ (function () { - function MapOperator(project, thisArg) { - this.project = project; - this.thisArg = thisArg; - } - MapOperator.prototype.call = function (subscriber, source) { - return source.subscribe(new MapSubscriber(subscriber, this.project, this.thisArg)); - }; - return MapOperator; -}()); + }); -var MapSubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](MapSubscriber, _super); - function MapSubscriber(destination, project, thisArg) { - var _this = _super.call(this, destination) || this; - _this.project = project; - _this.count = 0; - _this.thisArg = thisArg || _this; - return _this; + // get the location of the block on parent.nodes (block's siblings) + let parent = stack[stack.length - 1]; + let index = parent.nodes.indexOf(block); + // replace the (invalid) block with it's nodes + parent.nodes.splice(index, 1, ...block.nodes); } - MapSubscriber.prototype._next = function (value) { - var result; - try { - result = this.project.call(this.thisArg, value, this.count++); - } - catch (err) { - this.destination.error(err); - return; - } - this.destination.next(result); - }; - return MapSubscriber; -}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); -//# sourceMappingURL=map.js.map + } while (stack.length > 0); + + push({ type: 'eos' }); + return ast; +}; + +module.exports = parse; /***/ }), -/* 334 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/* 613 */ +/***/ (function(module, exports, __webpack_require__) { "use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "bindNodeCallback", function() { return bindNodeCallback; }); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); -/* harmony import */ var _AsyncSubject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(317); -/* harmony import */ var _operators_map__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(333); -/* harmony import */ var _util_canReportError__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(277); -/* harmony import */ var _util_isScheduler__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(312); -/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(285); -/** PURE_IMPORTS_START _Observable,_AsyncSubject,_operators_map,_util_canReportError,_util_isScheduler,_util_isArray PURE_IMPORTS_END */ +module.exports = { + MAX_LENGTH: 1024 * 64, + + // Digits + CHAR_0: '0', /* 0 */ + CHAR_9: '9', /* 9 */ + // Alphabet chars. + CHAR_UPPERCASE_A: 'A', /* A */ + CHAR_LOWERCASE_A: 'a', /* a */ + CHAR_UPPERCASE_Z: 'Z', /* Z */ + CHAR_LOWERCASE_Z: 'z', /* z */ + CHAR_LEFT_PARENTHESES: '(', /* ( */ + CHAR_RIGHT_PARENTHESES: ')', /* ) */ + CHAR_ASTERISK: '*', /* * */ -function bindNodeCallback(callbackFunc, resultSelector, scheduler) { - if (resultSelector) { - if (Object(_util_isScheduler__WEBPACK_IMPORTED_MODULE_4__["isScheduler"])(resultSelector)) { - scheduler = resultSelector; - } - else { - return function () { - var args = []; - for (var _i = 0; _i < arguments.length; _i++) { - args[_i] = arguments[_i]; - } - return bindNodeCallback(callbackFunc, scheduler).apply(void 0, args).pipe(Object(_operators_map__WEBPACK_IMPORTED_MODULE_2__["map"])(function (args) { return Object(_util_isArray__WEBPACK_IMPORTED_MODULE_5__["isArray"])(args) ? resultSelector.apply(void 0, args) : resultSelector(args); })); - }; - } - } - return function () { - var args = []; - for (var _i = 0; _i < arguments.length; _i++) { - args[_i] = arguments[_i]; - } - var params = { - subject: undefined, - args: args, - callbackFunc: callbackFunc, - scheduler: scheduler, - context: this, - }; - return new _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"](function (subscriber) { - var context = params.context; - var subject = params.subject; - if (!scheduler) { - if (!subject) { - subject = params.subject = new _AsyncSubject__WEBPACK_IMPORTED_MODULE_1__["AsyncSubject"](); - var handler = function () { - var innerArgs = []; - for (var _i = 0; _i < arguments.length; _i++) { - innerArgs[_i] = arguments[_i]; - } - var err = innerArgs.shift(); - if (err) { - subject.error(err); - return; - } - subject.next(innerArgs.length <= 1 ? innerArgs[0] : innerArgs); - subject.complete(); - }; - try { - callbackFunc.apply(context, args.concat([handler])); - } - catch (err) { - if (Object(_util_canReportError__WEBPACK_IMPORTED_MODULE_3__["canReportError"])(subject)) { - subject.error(err); - } - else { - console.warn(err); - } - } - } - return subject.subscribe(subscriber); - } - else { - return scheduler.schedule(dispatch, 0, { params: params, subscriber: subscriber, context: context }); - } - }); - }; -} -function dispatch(state) { - var _this = this; - var params = state.params, subscriber = state.subscriber, context = state.context; - var callbackFunc = params.callbackFunc, args = params.args, scheduler = params.scheduler; - var subject = params.subject; - if (!subject) { - subject = params.subject = new _AsyncSubject__WEBPACK_IMPORTED_MODULE_1__["AsyncSubject"](); - var handler = function () { - var innerArgs = []; - for (var _i = 0; _i < arguments.length; _i++) { - innerArgs[_i] = arguments[_i]; - } - var err = innerArgs.shift(); - if (err) { - _this.add(scheduler.schedule(dispatchError, 0, { err: err, subject: subject })); - } - else { - var value = innerArgs.length <= 1 ? innerArgs[0] : innerArgs; - _this.add(scheduler.schedule(dispatchNext, 0, { value: value, subject: subject })); - } - }; - try { - callbackFunc.apply(context, args.concat([handler])); - } - catch (err) { - this.add(scheduler.schedule(dispatchError, 0, { err: err, subject: subject })); - } - } - this.add(subject.subscribe(subscriber)); -} -function dispatchNext(arg) { - var value = arg.value, subject = arg.subject; - subject.next(value); - subject.complete(); -} -function dispatchError(arg) { - var err = arg.err, subject = arg.subject; - subject.error(err); -} -//# sourceMappingURL=bindNodeCallback.js.map + // Non-alphabetic chars. + CHAR_AMPERSAND: '&', /* & */ + CHAR_AT: '@', /* @ */ + CHAR_BACKSLASH: '\\', /* \ */ + CHAR_BACKTICK: '`', /* ` */ + CHAR_CARRIAGE_RETURN: '\r', /* \r */ + CHAR_CIRCUMFLEX_ACCENT: '^', /* ^ */ + CHAR_COLON: ':', /* : */ + CHAR_COMMA: ',', /* , */ + CHAR_DOLLAR: '$', /* . */ + CHAR_DOT: '.', /* . */ + CHAR_DOUBLE_QUOTE: '"', /* " */ + CHAR_EQUAL: '=', /* = */ + CHAR_EXCLAMATION_MARK: '!', /* ! */ + CHAR_FORM_FEED: '\f', /* \f */ + CHAR_FORWARD_SLASH: '/', /* / */ + CHAR_HASH: '#', /* # */ + CHAR_HYPHEN_MINUS: '-', /* - */ + CHAR_LEFT_ANGLE_BRACKET: '<', /* < */ + CHAR_LEFT_CURLY_BRACE: '{', /* { */ + CHAR_LEFT_SQUARE_BRACKET: '[', /* [ */ + CHAR_LINE_FEED: '\n', /* \n */ + CHAR_NO_BREAK_SPACE: '\u00A0', /* \u00A0 */ + CHAR_PERCENT: '%', /* % */ + CHAR_PLUS: '+', /* + */ + CHAR_QUESTION_MARK: '?', /* ? */ + CHAR_RIGHT_ANGLE_BRACKET: '>', /* > */ + CHAR_RIGHT_CURLY_BRACE: '}', /* } */ + CHAR_RIGHT_SQUARE_BRACKET: ']', /* ] */ + CHAR_SEMICOLON: ';', /* ; */ + CHAR_SINGLE_QUOTE: '\'', /* ' */ + CHAR_SPACE: ' ', /* */ + CHAR_TAB: '\t', /* \t */ + CHAR_UNDERSCORE: '_', /* _ */ + CHAR_VERTICAL_LINE: '|', /* | */ + CHAR_ZERO_WIDTH_NOBREAK_SPACE: '\uFEFF' /* \uFEFF */ +}; /***/ }), -/* 335 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/* 614 */ +/***/ (function(module, exports, __webpack_require__) { "use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "combineLatest", function() { return combineLatest; }); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "CombineLatestOperator", function() { return CombineLatestOperator; }); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "CombineLatestSubscriber", function() { return CombineLatestSubscriber; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _util_isScheduler__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(312); -/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(285); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(336); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(337); -/* harmony import */ var _fromArray__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(313); -/** PURE_IMPORTS_START tslib,_util_isScheduler,_util_isArray,_OuterSubscriber,_util_subscribeToResult,_fromArray PURE_IMPORTS_END */ +module.exports = __webpack_require__(615); +/***/ }), +/* 615 */ +/***/ (function(module, exports, __webpack_require__) { +"use strict"; -var NONE = {}; -function combineLatest() { - var observables = []; - for (var _i = 0; _i < arguments.length; _i++) { - observables[_i] = arguments[_i]; - } - var resultSelector = null; - var scheduler = null; - if (Object(_util_isScheduler__WEBPACK_IMPORTED_MODULE_1__["isScheduler"])(observables[observables.length - 1])) { - scheduler = observables.pop(); - } - if (typeof observables[observables.length - 1] === 'function') { - resultSelector = observables.pop(); - } - if (observables.length === 1 && Object(_util_isArray__WEBPACK_IMPORTED_MODULE_2__["isArray"])(observables[0])) { - observables = observables[0]; - } - return Object(_fromArray__WEBPACK_IMPORTED_MODULE_5__["fromArray"])(observables, scheduler).lift(new CombineLatestOperator(resultSelector)); -} -var CombineLatestOperator = /*@__PURE__*/ (function () { - function CombineLatestOperator(resultSelector) { - this.resultSelector = resultSelector; - } - CombineLatestOperator.prototype.call = function (subscriber, source) { - return source.subscribe(new CombineLatestSubscriber(subscriber, this.resultSelector)); - }; - return CombineLatestOperator; -}()); -var CombineLatestSubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](CombineLatestSubscriber, _super); - function CombineLatestSubscriber(destination, resultSelector) { - var _this = _super.call(this, destination) || this; - _this.resultSelector = resultSelector; - _this.active = 0; - _this.values = []; - _this.observables = []; - return _this; - } - CombineLatestSubscriber.prototype._next = function (observable) { - this.values.push(NONE); - this.observables.push(observable); - }; - CombineLatestSubscriber.prototype._complete = function () { - var observables = this.observables; - var len = observables.length; - if (len === 0) { - this.destination.complete(); - } - else { - this.active = len; - this.toRespond = len; - for (var i = 0; i < len; i++) { - var observable = observables[i]; - this.add(Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_4__["subscribeToResult"])(this, observable, observable, i)); - } - } - }; - CombineLatestSubscriber.prototype.notifyComplete = function (unused) { - if ((this.active -= 1) === 0) { - this.destination.complete(); - } - }; - CombineLatestSubscriber.prototype.notifyNext = function (outerValue, innerValue, outerIndex, innerIndex, innerSub) { - var values = this.values; - var oldVal = values[outerIndex]; - var toRespond = !this.toRespond - ? 0 - : oldVal === NONE ? --this.toRespond : this.toRespond; - values[outerIndex] = innerValue; - if (toRespond === 0) { - if (this.resultSelector) { - this._tryResultSelector(values); - } - else { - this.destination.next(values.slice()); - } - } - }; - CombineLatestSubscriber.prototype._tryResultSelector = function (values) { - var result; - try { - result = this.resultSelector.apply(this, values); - } - catch (err) { - this.destination.error(err); - return; - } - this.destination.next(result); +const path = __webpack_require__(16); +const scan = __webpack_require__(616); +const parse = __webpack_require__(619); +const utils = __webpack_require__(617); + +/** + * Creates a matcher function from one or more glob patterns. The + * returned function takes a string to match as its first argument, + * and returns true if the string is a match. The returned matcher + * function also takes a boolean as the second argument that, when true, + * returns an object with additional information. + * + * ```js + * const picomatch = require('picomatch'); + * // picomatch(glob[, options]); + * + * const isMatch = picomatch('*.!(*a)'); + * console.log(isMatch('a.a')); //=> false + * console.log(isMatch('a.b')); //=> true + * ``` + * @name picomatch + * @param {String|Array} `globs` One or more glob patterns. + * @param {Object=} `options` + * @return {Function=} Returns a matcher function. + * @api public + */ + +const picomatch = (glob, options, returnState = false) => { + if (Array.isArray(glob)) { + let fns = glob.map(input => picomatch(input, options, returnState)); + return str => { + for (let isMatch of fns) { + let state = isMatch(str); + if (state) return state; + } + return false; }; - return CombineLatestSubscriber; -}(_OuterSubscriber__WEBPACK_IMPORTED_MODULE_3__["OuterSubscriber"])); + } -//# sourceMappingURL=combineLatest.js.map + if (typeof glob !== 'string' || glob === '') { + throw new TypeError('Expected pattern to be a non-empty string'); + } + let opts = options || {}; + let posix = utils.isWindows(options); + let regex = picomatch.makeRe(glob, options, false, true); + let state = regex.state; + delete regex.state; -/***/ }), -/* 336 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + let isIgnored = () => false; + if (opts.ignore) { + let ignoreOpts = { ...options, ignore: null, onMatch: null, onResult: null }; + isIgnored = picomatch(opts.ignore, ignoreOpts, returnState); + } -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "OuterSubscriber", function() { return OuterSubscriber; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); -/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ + const matcher = (input, returnObject = false) => { + let { isMatch, match, output } = picomatch.test(input, regex, options, { glob, posix }); + let result = { glob, state, regex, posix, input, output, match, isMatch }; + if (typeof opts.onResult === 'function') { + opts.onResult(result); + } -var OuterSubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](OuterSubscriber, _super); - function OuterSubscriber() { - return _super !== null && _super.apply(this, arguments) || this; + if (isMatch === false) { + result.isMatch = false; + return returnObject ? result : false; } - OuterSubscriber.prototype.notifyNext = function (outerValue, innerValue, outerIndex, innerIndex, innerSub) { - this.destination.next(innerValue); - }; - OuterSubscriber.prototype.notifyError = function (error, innerSub) { - this.destination.error(error); - }; - OuterSubscriber.prototype.notifyComplete = function (innerSub) { - this.destination.complete(); - }; - return OuterSubscriber; -}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); -//# sourceMappingURL=OuterSubscriber.js.map + if (isIgnored(input)) { + if (typeof opts.onIgnore === 'function') { + opts.onIgnore(result); + } + result.isMatch = false; + return returnObject ? result : false; + } + if (typeof opts.onMatch === 'function') { + opts.onMatch(result); + } + return returnObject ? result : true; + }; -/***/ }), -/* 337 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + if (returnState) { + matcher.state = state; + } -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "subscribeToResult", function() { return subscribeToResult; }); -/* harmony import */ var _InnerSubscriber__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(338); -/* harmony import */ var _subscribeTo__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(339); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(276); -/** PURE_IMPORTS_START _InnerSubscriber,_subscribeTo,_Observable PURE_IMPORTS_END */ + return matcher; +}; +/** + * Test `input` with the given `regex`. This is used by the main + * `picomatch()` function to test the input string. + * + * ```js + * const picomatch = require('picomatch'); + * // picomatch.test(input, regex[, options]); + * + * console.log(picomatch.test('foo/bar', /^(?:([^/]*?)\/([^/]*?))$/)); + * // { isMatch: true, match: [ 'foo/', 'foo', 'bar' ], output: 'foo/bar' } + * ``` + * @param {String} `input` String to test. + * @param {RegExp} `regex` + * @return {Object} Returns an object with matching info. + * @api public + */ +picomatch.test = (input, regex, options, { glob, posix } = {}) => { + if (typeof input !== 'string') { + throw new TypeError('Expected input to be a string'); + } -function subscribeToResult(outerSubscriber, result, outerValue, outerIndex, destination) { - if (destination === void 0) { - destination = new _InnerSubscriber__WEBPACK_IMPORTED_MODULE_0__["InnerSubscriber"](outerSubscriber, outerValue, outerIndex); - } - if (destination.closed) { - return undefined; - } - if (result instanceof _Observable__WEBPACK_IMPORTED_MODULE_2__["Observable"]) { - return result.subscribe(destination); - } - return Object(_subscribeTo__WEBPACK_IMPORTED_MODULE_1__["subscribeTo"])(result)(destination); -} -//# sourceMappingURL=subscribeToResult.js.map + if (input === '') { + return { isMatch: false, output: '' }; + } + let opts = options || {}; + let format = opts.format || (posix ? utils.toPosixSlashes : null); + let match = input === glob; + let output = (match && format) ? format(input) : input; -/***/ }), -/* 338 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + if (match === false) { + output = format ? format(input) : input; + match = output === glob; + } -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "InnerSubscriber", function() { return InnerSubscriber; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); -/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ + if (match === false || opts.capture === true) { + if (opts.matchBase === true || opts.basename === true) { + match = picomatch.matchBase(input, regex, options, posix); + } else { + match = regex.exec(output); + } + } + return { isMatch: !!match, match, output }; +}; -var InnerSubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](InnerSubscriber, _super); - function InnerSubscriber(parent, outerValue, outerIndex) { - var _this = _super.call(this) || this; - _this.parent = parent; - _this.outerValue = outerValue; - _this.outerIndex = outerIndex; - _this.index = 0; - return _this; - } - InnerSubscriber.prototype._next = function (value) { - this.parent.notifyNext(this.outerValue, value, this.outerIndex, this.index++, this); - }; - InnerSubscriber.prototype._error = function (error) { - this.parent.notifyError(error, this); - this.unsubscribe(); - }; - InnerSubscriber.prototype._complete = function () { - this.parent.notifyComplete(this); - this.unsubscribe(); - }; - return InnerSubscriber; -}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); +/** + * Match the basename of a filepath. + * + * ```js + * const picomatch = require('picomatch'); + * // picomatch.matchBase(input, glob[, options]); + * console.log(picomatch.matchBase('foo/bar.js', '*.js'); // true + * ``` + * @param {String} `input` String to test. + * @param {RegExp|String} `glob` Glob pattern or regex created by [.makeRe](#makeRe). + * @return {Boolean} + * @api public + */ -//# sourceMappingURL=InnerSubscriber.js.map +picomatch.matchBase = (input, glob, options, posix = utils.isWindows(options)) => { + let regex = glob instanceof RegExp ? glob : picomatch.makeRe(glob, options); + return regex.test(path.basename(input)); +}; +/** + * Returns true if **any** of the given glob `patterns` match the specified `string`. + * + * ```js + * const picomatch = require('picomatch'); + * // picomatch.isMatch(string, patterns[, options]); + * + * console.log(picomatch.isMatch('a.a', ['b.*', '*.a'])); //=> true + * console.log(picomatch.isMatch('a.a', 'b.*')); //=> false + * ``` + * @param {String|Array} str The string to test. + * @param {String|Array} patterns One or more glob patterns to use for matching. + * @param {Object} [options] See available [options](#options). + * @return {Boolean} Returns true if any patterns match `str` + * @api public + */ -/***/ }), -/* 339 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +picomatch.isMatch = (str, patterns, options) => picomatch(patterns, options)(str); -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "subscribeTo", function() { return subscribeTo; }); -/* harmony import */ var _subscribeToArray__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(314); -/* harmony import */ var _subscribeToPromise__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(340); -/* harmony import */ var _subscribeToIterable__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(341); -/* harmony import */ var _subscribeToObservable__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(343); -/* harmony import */ var _isArrayLike__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(344); -/* harmony import */ var _isPromise__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(345); -/* harmony import */ var _isObject__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(286); -/* harmony import */ var _symbol_iterator__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(342); -/* harmony import */ var _symbol_observable__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(290); -/** PURE_IMPORTS_START _subscribeToArray,_subscribeToPromise,_subscribeToIterable,_subscribeToObservable,_isArrayLike,_isPromise,_isObject,_symbol_iterator,_symbol_observable PURE_IMPORTS_END */ +/** + * Parse a glob pattern to create the source string for a regular + * expression. + * + * ```js + * const picomatch = require('picomatch'); + * const result = picomatch.parse(glob[, options]); + * ``` + * @param {String} `glob` + * @param {Object} `options` + * @return {Object} Returns an object with useful properties and output to be used as a regex source string. + * @api public + */ + +picomatch.parse = (glob, options) => parse(glob, options); + +/** + * Scan a glob pattern to separate the pattern into segments. + * + * ```js + * const picomatch = require('picomatch'); + * // picomatch.scan(input[, options]); + * + * const result = picomatch.scan('!./foo/*.js'); + * console.log(result); + * // { prefix: '!./', + * // input: '!./foo/*.js', + * // base: 'foo', + * // glob: '*.js', + * // negated: true, + * // isGlob: true } + * ``` + * @param {String} `input` Glob pattern to scan. + * @param {Object} `options` + * @return {Object} Returns an object with + * @api public + */ +picomatch.scan = (input, options) => scan(input, options); +/** + * Create a regular expression from a glob pattern. + * + * ```js + * const picomatch = require('picomatch'); + * // picomatch.makeRe(input[, options]); + * + * console.log(picomatch.makeRe('*.js')); + * //=> /^(?:(?!\.)(?=.)[^/]*?\.js)$/ + * ``` + * @param {String} `input` A glob pattern to convert to regex. + * @param {Object} `options` + * @return {RegExp} Returns a regex created from the given pattern. + * @api public + */ +picomatch.makeRe = (input, options, returnOutput = false, returnState = false) => { + if (!input || typeof input !== 'string') { + throw new TypeError('Expected a non-empty string'); + } + let opts = options || {}; + let prepend = opts.contains ? '' : '^'; + let append = opts.contains ? '' : '$'; + let state = { negated: false, fastpaths: true }; + let prefix = ''; + let output; + if (input.startsWith('./')) { + input = input.slice(2); + prefix = state.prefix = './'; + } + if (opts.fastpaths !== false && (input[0] === '.' || input[0] === '*')) { + output = parse.fastpaths(input, options); + } + if (output === void 0) { + state = picomatch.parse(input, options); + state.prefix = prefix + (state.prefix || ''); + output = state.output; + } + if (returnOutput === true) { + return output; + } -var subscribeTo = function (result) { - if (!!result && typeof result[_symbol_observable__WEBPACK_IMPORTED_MODULE_8__["observable"]] === 'function') { - return Object(_subscribeToObservable__WEBPACK_IMPORTED_MODULE_3__["subscribeToObservable"])(result); - } - else if (Object(_isArrayLike__WEBPACK_IMPORTED_MODULE_4__["isArrayLike"])(result)) { - return Object(_subscribeToArray__WEBPACK_IMPORTED_MODULE_0__["subscribeToArray"])(result); - } - else if (Object(_isPromise__WEBPACK_IMPORTED_MODULE_5__["isPromise"])(result)) { - return Object(_subscribeToPromise__WEBPACK_IMPORTED_MODULE_1__["subscribeToPromise"])(result); - } - else if (!!result && typeof result[_symbol_iterator__WEBPACK_IMPORTED_MODULE_7__["iterator"]] === 'function') { - return Object(_subscribeToIterable__WEBPACK_IMPORTED_MODULE_2__["subscribeToIterable"])(result); - } - else { - var value = Object(_isObject__WEBPACK_IMPORTED_MODULE_6__["isObject"])(result) ? 'an invalid object' : "'" + result + "'"; - var msg = "You provided " + value + " where a stream was expected." - + ' You can provide an Observable, Promise, Array, or Iterable.'; - throw new TypeError(msg); - } -}; -//# sourceMappingURL=subscribeTo.js.map + let source = `${prepend}(?:${output})${append}`; + if (state && state.negated === true) { + source = `^(?!${source}).*$`; + } + let regex = picomatch.toRegex(source, options); + if (returnState === true) { + regex.state = state; + } -/***/ }), -/* 340 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + return regex; +}; -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "subscribeToPromise", function() { return subscribeToPromise; }); -/* harmony import */ var _hostReportError__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(283); -/** PURE_IMPORTS_START _hostReportError PURE_IMPORTS_END */ +/** + * Create a regular expression from the given regex source string. + * + * ```js + * const picomatch = require('picomatch'); + * // picomatch.toRegex(source[, options]); + * + * const { output } = picomatch.parse('*.js'); + * console.log(picomatch.toRegex(output)); + * //=> /^(?:(?!\.)(?=.)[^/]*?\.js)$/ + * ``` + * @param {String} `source` Regular expression source string. + * @param {Object} `options` + * @return {RegExp} + * @api public + */ -var subscribeToPromise = function (promise) { - return function (subscriber) { - promise.then(function (value) { - if (!subscriber.closed) { - subscriber.next(value); - subscriber.complete(); - } - }, function (err) { return subscriber.error(err); }) - .then(null, _hostReportError__WEBPACK_IMPORTED_MODULE_0__["hostReportError"]); - return subscriber; - }; +picomatch.toRegex = (source, options) => { + try { + let opts = options || {}; + return new RegExp(source, opts.flags || (opts.nocase ? 'i' : '')); + } catch (err) { + if (options && options.debug === true) throw err; + return /$^/; + } }; -//# sourceMappingURL=subscribeToPromise.js.map + +/** + * Picomatch constants. + * @return {Object} + */ + +picomatch.constants = __webpack_require__(618); + +/** + * Expose "picomatch" + */ + +module.exports = picomatch; /***/ }), -/* 341 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/* 616 */ +/***/ (function(module, exports, __webpack_require__) { "use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "subscribeToIterable", function() { return subscribeToIterable; }); -/* harmony import */ var _symbol_iterator__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(342); -/** PURE_IMPORTS_START _symbol_iterator PURE_IMPORTS_END */ -var subscribeToIterable = function (iterable) { - return function (subscriber) { - var iterator = iterable[_symbol_iterator__WEBPACK_IMPORTED_MODULE_0__["iterator"]](); - do { - var item = iterator.next(); - if (item.done) { - subscriber.complete(); - break; - } - subscriber.next(item.value); - if (subscriber.closed) { - break; - } - } while (true); - if (typeof iterator.return === 'function') { - subscriber.add(function () { - if (iterator.return) { - iterator.return(); - } - }); - } - return subscriber; - }; + +const utils = __webpack_require__(617); + +const { + CHAR_ASTERISK, /* * */ + CHAR_AT, /* @ */ + CHAR_BACKWARD_SLASH, /* \ */ + CHAR_COMMA, /* , */ + CHAR_DOT, /* . */ + CHAR_EXCLAMATION_MARK, /* ! */ + CHAR_FORWARD_SLASH, /* / */ + CHAR_LEFT_CURLY_BRACE, /* { */ + CHAR_LEFT_PARENTHESES, /* ( */ + CHAR_LEFT_SQUARE_BRACKET, /* [ */ + CHAR_PLUS, /* + */ + CHAR_QUESTION_MARK, /* ? */ + CHAR_RIGHT_CURLY_BRACE, /* } */ + CHAR_RIGHT_PARENTHESES, /* ) */ + CHAR_RIGHT_SQUARE_BRACKET /* ] */ +} = __webpack_require__(618); + +const isPathSeparator = code => { + return code === CHAR_FORWARD_SLASH || code === CHAR_BACKWARD_SLASH; }; -//# sourceMappingURL=subscribeToIterable.js.map +/** + * Quickly scans a glob pattern and returns an object with a handful of + * useful properties, like `isGlob`, `path` (the leading non-glob, if it exists), + * `glob` (the actual pattern), and `negated` (true if the path starts with `!`). + * + * ```js + * const pm = require('picomatch'); + * console.log(pm.scan('foo/bar/*.js')); + * { isGlob: true, input: 'foo/bar/*.js', base: 'foo/bar', glob: '*.js' } + * ``` + * @param {String} `str` + * @param {Object} `options` + * @return {Object} Returns an object with tokens and regex source string. + * @api public + */ -/***/ }), -/* 342 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +module.exports = (input, options) => { + let opts = options || {}; + let length = input.length - 1; + let index = -1; + let start = 0; + let lastIndex = 0; + let isGlob = false; + let backslashes = false; + let negated = false; + let braces = 0; + let prev; + let code; -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "getSymbolIterator", function() { return getSymbolIterator; }); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "iterator", function() { return iterator; }); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "$$iterator", function() { return $$iterator; }); -/** PURE_IMPORTS_START PURE_IMPORTS_END */ -function getSymbolIterator() { - if (typeof Symbol !== 'function' || !Symbol.iterator) { - return '@@iterator'; - } - return Symbol.iterator; -} -var iterator = /*@__PURE__*/ getSymbolIterator(); -var $$iterator = iterator; -//# sourceMappingURL=iterator.js.map + let braceEscaped = false; + let eos = () => index >= length; + let advance = () => { + prev = code; + return input.charCodeAt(++index); + }; -/***/ }), -/* 343 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + while (index < length) { + code = advance(); + let next; -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "subscribeToObservable", function() { return subscribeToObservable; }); -/* harmony import */ var _symbol_observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(290); -/** PURE_IMPORTS_START _symbol_observable PURE_IMPORTS_END */ + if (code === CHAR_BACKWARD_SLASH) { + backslashes = true; + next = advance(); -var subscribeToObservable = function (obj) { - return function (subscriber) { - var obs = obj[_symbol_observable__WEBPACK_IMPORTED_MODULE_0__["observable"]](); - if (typeof obs.subscribe !== 'function') { - throw new TypeError('Provided object does not correctly implement Symbol.observable'); + if (next === CHAR_LEFT_CURLY_BRACE) { + braceEscaped = true; + } + continue; + } + + if (braceEscaped === true || code === CHAR_LEFT_CURLY_BRACE) { + braces++; + + while (!eos() && (next = advance())) { + if (next === CHAR_BACKWARD_SLASH) { + backslashes = true; + next = advance(); + continue; } - else { - return obs.subscribe(subscriber); + + if (next === CHAR_LEFT_CURLY_BRACE) { + braces++; + continue; } - }; -}; -//# sourceMappingURL=subscribeToObservable.js.map + if (!braceEscaped && next === CHAR_DOT && (next = advance()) === CHAR_DOT) { + isGlob = true; + break; + } -/***/ }), -/* 344 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + if (!braceEscaped && next === CHAR_COMMA) { + isGlob = true; + break; + } -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "isArrayLike", function() { return isArrayLike; }); -/** PURE_IMPORTS_START PURE_IMPORTS_END */ -var isArrayLike = (function (x) { return x && typeof x.length === 'number' && typeof x !== 'function'; }); -//# sourceMappingURL=isArrayLike.js.map + if (next === CHAR_RIGHT_CURLY_BRACE) { + braces--; + if (braces === 0) { + braceEscaped = false; + break; + } + } + } + } + if (code === CHAR_FORWARD_SLASH) { + if (prev === CHAR_DOT && index === (start + 1)) { + start += 2; + continue; + } -/***/ }), -/* 345 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + lastIndex = index + 1; + continue; + } -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "isPromise", function() { return isPromise; }); -/** PURE_IMPORTS_START PURE_IMPORTS_END */ -function isPromise(value) { - return !!value && typeof value.subscribe !== 'function' && typeof value.then === 'function'; -} -//# sourceMappingURL=isPromise.js.map + if (code === CHAR_ASTERISK) { + isGlob = true; + break; + } + if (code === CHAR_ASTERISK || code === CHAR_QUESTION_MARK) { + isGlob = true; + break; + } -/***/ }), -/* 346 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + if (code === CHAR_LEFT_SQUARE_BRACKET) { + while (!eos() && (next = advance())) { + if (next === CHAR_BACKWARD_SLASH) { + backslashes = true; + next = advance(); + continue; + } -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "concat", function() { return concat; }); -/* harmony import */ var _of__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(311); -/* harmony import */ var _operators_concatAll__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(347); -/** PURE_IMPORTS_START _of,_operators_concatAll PURE_IMPORTS_END */ + if (next === CHAR_RIGHT_SQUARE_BRACKET) { + isGlob = true; + break; + } + } + } + let isExtglobChar = code === CHAR_PLUS + || code === CHAR_AT + || code === CHAR_EXCLAMATION_MARK; -function concat() { - var observables = []; - for (var _i = 0; _i < arguments.length; _i++) { - observables[_i] = arguments[_i]; + if (isExtglobChar && input.charCodeAt(index + 1) === CHAR_LEFT_PARENTHESES) { + isGlob = true; + break; } - return Object(_operators_concatAll__WEBPACK_IMPORTED_MODULE_1__["concatAll"])()(_of__WEBPACK_IMPORTED_MODULE_0__["of"].apply(void 0, observables)); -} -//# sourceMappingURL=concat.js.map + if (code === CHAR_EXCLAMATION_MARK && index === start) { + negated = true; + start++; + continue; + } -/***/ }), -/* 347 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + if (code === CHAR_LEFT_PARENTHESES) { + while (!eos() && (next = advance())) { + if (next === CHAR_BACKWARD_SLASH) { + backslashes = true; + next = advance(); + continue; + } -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "concatAll", function() { return concatAll; }); -/* harmony import */ var _mergeAll__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(348); -/** PURE_IMPORTS_START _mergeAll PURE_IMPORTS_END */ + if (next === CHAR_RIGHT_PARENTHESES) { + isGlob = true; + break; + } + } + } -function concatAll() { - return Object(_mergeAll__WEBPACK_IMPORTED_MODULE_0__["mergeAll"])(1); -} -//# sourceMappingURL=concatAll.js.map + if (isGlob) { + break; + } + } + let prefix = ''; + let orig = input; + let base = input; + let glob = ''; -/***/ }), -/* 348 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + if (start > 0) { + prefix = input.slice(0, start); + input = input.slice(start); + lastIndex -= start; + } -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "mergeAll", function() { return mergeAll; }); -/* harmony import */ var _mergeMap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(349); -/* harmony import */ var _util_identity__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(327); -/** PURE_IMPORTS_START _mergeMap,_util_identity PURE_IMPORTS_END */ + if (base && isGlob === true && lastIndex > 0) { + base = input.slice(0, lastIndex); + glob = input.slice(lastIndex); + } else if (isGlob === true) { + base = ''; + glob = input; + } else { + base = input; + } + + if (base && base !== '' && base !== '/' && base !== input) { + if (isPathSeparator(base.charCodeAt(base.length - 1))) { + base = base.slice(0, -1); + } + } + if (opts.unescape === true) { + if (glob) glob = utils.removeBackslashes(glob); -function mergeAll(concurrent) { - if (concurrent === void 0) { - concurrent = Number.POSITIVE_INFINITY; + if (base && backslashes === true) { + base = utils.removeBackslashes(base); } - return Object(_mergeMap__WEBPACK_IMPORTED_MODULE_0__["mergeMap"])(_util_identity__WEBPACK_IMPORTED_MODULE_1__["identity"], concurrent); -} -//# sourceMappingURL=mergeAll.js.map + } + + return { prefix, input: orig, base, glob, negated, isGlob }; +}; /***/ }), -/* 349 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/* 617 */ +/***/ (function(module, exports, __webpack_require__) { "use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "mergeMap", function() { return mergeMap; }); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "MergeMapOperator", function() { return MergeMapOperator; }); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "MergeMapSubscriber", function() { return MergeMapSubscriber; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(337); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(336); -/* harmony import */ var _InnerSubscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(338); -/* harmony import */ var _map__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(333); -/* harmony import */ var _observable_from__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(350); -/** PURE_IMPORTS_START tslib,_util_subscribeToResult,_OuterSubscriber,_InnerSubscriber,_map,_observable_from PURE_IMPORTS_END */ - - +const path = __webpack_require__(16); +const win32 = process.platform === 'win32'; +const { + REGEX_SPECIAL_CHARS, + REGEX_SPECIAL_CHARS_GLOBAL, + REGEX_REMOVE_BACKSLASH +} = __webpack_require__(618); +exports.isObject = val => val !== null && typeof val === 'object' && !Array.isArray(val); +exports.hasRegexChars = str => REGEX_SPECIAL_CHARS.test(str); +exports.isRegexChar = str => str.length === 1 && exports.hasRegexChars(str); +exports.escapeRegex = str => str.replace(REGEX_SPECIAL_CHARS_GLOBAL, '\\$1'); +exports.toPosixSlashes = str => str.replace(/\\/g, '/'); -function mergeMap(project, resultSelector, concurrent) { - if (concurrent === void 0) { - concurrent = Number.POSITIVE_INFINITY; - } - if (typeof resultSelector === 'function') { - return function (source) { return source.pipe(mergeMap(function (a, i) { return Object(_observable_from__WEBPACK_IMPORTED_MODULE_5__["from"])(project(a, i)).pipe(Object(_map__WEBPACK_IMPORTED_MODULE_4__["map"])(function (b, ii) { return resultSelector(a, b, i, ii); })); }, concurrent)); }; - } - else if (typeof resultSelector === 'number') { - concurrent = resultSelector; - } - return function (source) { return source.lift(new MergeMapOperator(project, concurrent)); }; +exports.removeBackslashes = str => { + return str.replace(REGEX_REMOVE_BACKSLASH, match => { + return match === '\\' ? '' : match; + }); } -var MergeMapOperator = /*@__PURE__*/ (function () { - function MergeMapOperator(project, concurrent) { - if (concurrent === void 0) { - concurrent = Number.POSITIVE_INFINITY; - } - this.project = project; - this.concurrent = concurrent; - } - MergeMapOperator.prototype.call = function (observer, source) { - return source.subscribe(new MergeMapSubscriber(observer, this.project, this.concurrent)); - }; - return MergeMapOperator; -}()); -var MergeMapSubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](MergeMapSubscriber, _super); - function MergeMapSubscriber(destination, project, concurrent) { - if (concurrent === void 0) { - concurrent = Number.POSITIVE_INFINITY; - } - var _this = _super.call(this, destination) || this; - _this.project = project; - _this.concurrent = concurrent; - _this.hasCompleted = false; - _this.buffer = []; - _this.active = 0; - _this.index = 0; - return _this; - } - MergeMapSubscriber.prototype._next = function (value) { - if (this.active < this.concurrent) { - this._tryNext(value); - } - else { - this.buffer.push(value); - } - }; - MergeMapSubscriber.prototype._tryNext = function (value) { - var result; - var index = this.index++; - try { - result = this.project(value, index); - } - catch (err) { - this.destination.error(err); - return; - } - this.active++; - this._innerSub(result, value, index); - }; - MergeMapSubscriber.prototype._innerSub = function (ish, value, index) { - var innerSubscriber = new _InnerSubscriber__WEBPACK_IMPORTED_MODULE_3__["InnerSubscriber"](this, undefined, undefined); - var destination = this.destination; - destination.add(innerSubscriber); - Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_1__["subscribeToResult"])(this, ish, value, index, innerSubscriber); - }; - MergeMapSubscriber.prototype._complete = function () { - this.hasCompleted = true; - if (this.active === 0 && this.buffer.length === 0) { - this.destination.complete(); - } - this.unsubscribe(); - }; - MergeMapSubscriber.prototype.notifyNext = function (outerValue, innerValue, outerIndex, innerIndex, innerSub) { - this.destination.next(innerValue); - }; - MergeMapSubscriber.prototype.notifyComplete = function (innerSub) { - var buffer = this.buffer; - this.remove(innerSub); - this.active--; - if (buffer.length > 0) { - this._next(buffer.shift()); - } - else if (this.active === 0 && this.hasCompleted) { - this.destination.complete(); - } - }; - return MergeMapSubscriber; -}(_OuterSubscriber__WEBPACK_IMPORTED_MODULE_2__["OuterSubscriber"])); +exports.supportsLookbehinds = () => { + let segs = process.version.slice(1).split('.'); + if (segs.length === 3 && +segs[0] >= 9 || (+segs[0] === 8 && +segs[1] >= 10)) { + return true; + } + return false; +}; -//# sourceMappingURL=mergeMap.js.map +exports.isWindows = options => { + if (options && typeof options.windows === 'boolean') { + return options.windows; + } + return win32 === true || path.sep === '\\'; +}; + +exports.escapeLast = (input, char, lastIdx) => { + let idx = input.lastIndexOf(char, lastIdx); + if (idx === -1) return input; + if (input[idx - 1] === '\\') return exports.escapeLast(input, char, idx - 1); + return input.slice(0, idx) + '\\' + input.slice(idx); +}; /***/ }), -/* 350 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/* 618 */ +/***/ (function(module, exports, __webpack_require__) { "use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "from", function() { return from; }); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); -/* harmony import */ var _util_subscribeTo__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(339); -/* harmony import */ var _scheduled_scheduled__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(351); -/** PURE_IMPORTS_START _Observable,_util_subscribeTo,_scheduled_scheduled PURE_IMPORTS_END */ +const path = __webpack_require__(16); +const WIN_SLASH = '\\\\/'; +const WIN_NO_SLASH = `[^${WIN_SLASH}]`; -function from(input, scheduler) { - if (!scheduler) { - if (input instanceof _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"]) { - return input; - } - return new _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"](Object(_util_subscribeTo__WEBPACK_IMPORTED_MODULE_1__["subscribeTo"])(input)); - } - else { - return Object(_scheduled_scheduled__WEBPACK_IMPORTED_MODULE_2__["scheduled"])(input, scheduler); - } -} -//# sourceMappingURL=from.js.map +/** + * Posix glob regex + */ + +const DOT_LITERAL = '\\.'; +const PLUS_LITERAL = '\\+'; +const QMARK_LITERAL = '\\?'; +const SLASH_LITERAL = '\\/'; +const ONE_CHAR = '(?=.)'; +const QMARK = '[^/]'; +const END_ANCHOR = `(?:${SLASH_LITERAL}|$)`; +const START_ANCHOR = `(?:^|${SLASH_LITERAL})`; +const DOTS_SLASH = `${DOT_LITERAL}{1,2}${END_ANCHOR}`; +const NO_DOT = `(?!${DOT_LITERAL})`; +const NO_DOTS = `(?!${START_ANCHOR}${DOTS_SLASH})`; +const NO_DOT_SLASH = `(?!${DOT_LITERAL}{0,1}${END_ANCHOR})`; +const NO_DOTS_SLASH = `(?!${DOTS_SLASH})`; +const QMARK_NO_DOT = `[^.${SLASH_LITERAL}]`; +const STAR = `${QMARK}*?`; + +const POSIX_CHARS = { + DOT_LITERAL, + PLUS_LITERAL, + QMARK_LITERAL, + SLASH_LITERAL, + ONE_CHAR, + QMARK, + END_ANCHOR, + DOTS_SLASH, + NO_DOT, + NO_DOTS, + NO_DOT_SLASH, + NO_DOTS_SLASH, + QMARK_NO_DOT, + STAR, + START_ANCHOR +}; +/** + * Windows glob regex + */ -/***/ }), -/* 351 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +const WINDOWS_CHARS = { + ...POSIX_CHARS, -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "scheduled", function() { return scheduled; }); -/* harmony import */ var _scheduleObservable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(352); -/* harmony import */ var _schedulePromise__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(353); -/* harmony import */ var _scheduleArray__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(315); -/* harmony import */ var _scheduleIterable__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(354); -/* harmony import */ var _util_isInteropObservable__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(355); -/* harmony import */ var _util_isPromise__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(345); -/* harmony import */ var _util_isArrayLike__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(344); -/* harmony import */ var _util_isIterable__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(356); -/** PURE_IMPORTS_START _scheduleObservable,_schedulePromise,_scheduleArray,_scheduleIterable,_util_isInteropObservable,_util_isPromise,_util_isArrayLike,_util_isIterable PURE_IMPORTS_END */ + SLASH_LITERAL: `[${WIN_SLASH}]`, + QMARK: WIN_NO_SLASH, + STAR: `${WIN_NO_SLASH}*?`, + DOTS_SLASH: `${DOT_LITERAL}{1,2}(?:[${WIN_SLASH}]|$)`, + NO_DOT: `(?!${DOT_LITERAL})`, + NO_DOTS: `(?!(?:^|[${WIN_SLASH}])${DOT_LITERAL}{1,2}(?:[${WIN_SLASH}]|$))`, + NO_DOT_SLASH: `(?!${DOT_LITERAL}{0,1}(?:[${WIN_SLASH}]|$))`, + NO_DOTS_SLASH: `(?!${DOT_LITERAL}{1,2}(?:[${WIN_SLASH}]|$))`, + QMARK_NO_DOT: `[^.${WIN_SLASH}]`, + START_ANCHOR: `(?:^|[${WIN_SLASH}])`, + END_ANCHOR: `(?:[${WIN_SLASH}]|$)` +}; +/** + * POSIX Bracket Regex + */ +const POSIX_REGEX_SOURCE = { + alnum: 'a-zA-Z0-9', + alpha: 'a-zA-Z', + ascii: '\\x00-\\x7F', + blank: ' \\t', + cntrl: '\\x00-\\x1F\\x7F', + digit: '0-9', + graph: '\\x21-\\x7E', + lower: 'a-z', + print: '\\x20-\\x7E ', + punct: '\\-!"#$%&\'()\\*+,./:;<=>?@[\\]^_`{|}~', + space: ' \\t\\r\\n\\v\\f', + upper: 'A-Z', + word: 'A-Za-z0-9_', + xdigit: 'A-Fa-f0-9' +}; +module.exports = { + MAX_LENGTH: 1024 * 64, + POSIX_REGEX_SOURCE, + // regular expressions + REGEX_BACKSLASH: /\\(?![*+?^${}(|)[\]])/g, + REGEX_NON_SPECIAL_CHAR: /^[^@![\].,$*+?^{}()|\\/]+/, + REGEX_SPECIAL_CHARS: /[-*+?.^${}(|)[\]]/, + REGEX_SPECIAL_CHARS_BACKREF: /(\\?)((\W)(\3*))/g, + REGEX_SPECIAL_CHARS_GLOBAL: /([-*+?.^${}(|)[\]])/g, + REGEX_REMOVE_BACKSLASH: /(?:\[.*?[^\\]\]|\\(?=.))/g, + // Replace globs with equivalent patterns to reduce parsing time. + REPLACEMENTS: { + '***': '*', + '**/**': '**', + '**/**/**': '**' + }, + // Digits + CHAR_0: 48, /* 0 */ + CHAR_9: 57, /* 9 */ + // Alphabet chars. + CHAR_UPPERCASE_A: 65, /* A */ + CHAR_LOWERCASE_A: 97, /* a */ + CHAR_UPPERCASE_Z: 90, /* Z */ + CHAR_LOWERCASE_Z: 122, /* z */ -function scheduled(input, scheduler) { - if (input != null) { - if (Object(_util_isInteropObservable__WEBPACK_IMPORTED_MODULE_4__["isInteropObservable"])(input)) { - return Object(_scheduleObservable__WEBPACK_IMPORTED_MODULE_0__["scheduleObservable"])(input, scheduler); - } - else if (Object(_util_isPromise__WEBPACK_IMPORTED_MODULE_5__["isPromise"])(input)) { - return Object(_schedulePromise__WEBPACK_IMPORTED_MODULE_1__["schedulePromise"])(input, scheduler); - } - else if (Object(_util_isArrayLike__WEBPACK_IMPORTED_MODULE_6__["isArrayLike"])(input)) { - return Object(_scheduleArray__WEBPACK_IMPORTED_MODULE_2__["scheduleArray"])(input, scheduler); - } - else if (Object(_util_isIterable__WEBPACK_IMPORTED_MODULE_7__["isIterable"])(input) || typeof input === 'string') { - return Object(_scheduleIterable__WEBPACK_IMPORTED_MODULE_3__["scheduleIterable"])(input, scheduler); - } - } - throw new TypeError((input !== null && typeof input || input) + ' is not observable'); -} -//# sourceMappingURL=scheduled.js.map + CHAR_LEFT_PARENTHESES: 40, /* ( */ + CHAR_RIGHT_PARENTHESES: 41, /* ) */ + CHAR_ASTERISK: 42, /* * */ -/***/ }), -/* 352 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + // Non-alphabetic chars. + CHAR_AMPERSAND: 38, /* & */ + CHAR_AT: 64, /* @ */ + CHAR_BACKWARD_SLASH: 92, /* \ */ + CHAR_CARRIAGE_RETURN: 13, /* \r */ + CHAR_CIRCUMFLEX_ACCENT: 94, /* ^ */ + CHAR_COLON: 58, /* : */ + CHAR_COMMA: 44, /* , */ + CHAR_DOT: 46, /* . */ + CHAR_DOUBLE_QUOTE: 34, /* " */ + CHAR_EQUAL: 61, /* = */ + CHAR_EXCLAMATION_MARK: 33, /* ! */ + CHAR_FORM_FEED: 12, /* \f */ + CHAR_FORWARD_SLASH: 47, /* / */ + CHAR_GRAVE_ACCENT: 96, /* ` */ + CHAR_HASH: 35, /* # */ + CHAR_HYPHEN_MINUS: 45, /* - */ + CHAR_LEFT_ANGLE_BRACKET: 60, /* < */ + CHAR_LEFT_CURLY_BRACE: 123, /* { */ + CHAR_LEFT_SQUARE_BRACKET: 91, /* [ */ + CHAR_LINE_FEED: 10, /* \n */ + CHAR_NO_BREAK_SPACE: 160, /* \u00A0 */ + CHAR_PERCENT: 37, /* % */ + CHAR_PLUS: 43, /* + */ + CHAR_QUESTION_MARK: 63, /* ? */ + CHAR_RIGHT_ANGLE_BRACKET: 62, /* > */ + CHAR_RIGHT_CURLY_BRACE: 125, /* } */ + CHAR_RIGHT_SQUARE_BRACKET: 93, /* ] */ + CHAR_SEMICOLON: 59, /* ; */ + CHAR_SINGLE_QUOTE: 39, /* ' */ + CHAR_SPACE: 32, /* */ + CHAR_TAB: 9, /* \t */ + CHAR_UNDERSCORE: 95, /* _ */ + CHAR_VERTICAL_LINE: 124, /* | */ + CHAR_ZERO_WIDTH_NOBREAK_SPACE: 65279, /* \uFEFF */ -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "scheduleObservable", function() { return scheduleObservable; }); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); -/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(284); -/* harmony import */ var _symbol_observable__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(290); -/** PURE_IMPORTS_START _Observable,_Subscription,_symbol_observable PURE_IMPORTS_END */ + SEP: path.sep, + /** + * Create EXTGLOB_CHARS + */ + extglobChars(chars) { + return { + '!': { type: 'negate', open: '(?:(?!(?:', close: `))${chars.STAR})` }, + '?': { type: 'qmark', open: '(?:', close: ')?' }, + '+': { type: 'plus', open: '(?:', close: ')+' }, + '*': { type: 'star', open: '(?:', close: ')*' }, + '@': { type: 'at', open: '(?:', close: ')' } + }; + }, -function scheduleObservable(input, scheduler) { - return new _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"](function (subscriber) { - var sub = new _Subscription__WEBPACK_IMPORTED_MODULE_1__["Subscription"](); - sub.add(scheduler.schedule(function () { - var observable = input[_symbol_observable__WEBPACK_IMPORTED_MODULE_2__["observable"]](); - sub.add(observable.subscribe({ - next: function (value) { sub.add(scheduler.schedule(function () { return subscriber.next(value); })); }, - error: function (err) { sub.add(scheduler.schedule(function () { return subscriber.error(err); })); }, - complete: function () { sub.add(scheduler.schedule(function () { return subscriber.complete(); })); }, - })); - })); - return sub; - }); -} -//# sourceMappingURL=scheduleObservable.js.map + /** + * Create GLOB_CHARS + */ + + globChars(win32) { + return win32 === true ? WINDOWS_CHARS : POSIX_CHARS; + } +}; /***/ }), -/* 353 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/* 619 */ +/***/ (function(module, exports, __webpack_require__) { "use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "schedulePromise", function() { return schedulePromise; }); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); -/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(284); -/** PURE_IMPORTS_START _Observable,_Subscription PURE_IMPORTS_END */ -function schedulePromise(input, scheduler) { - return new _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"](function (subscriber) { - var sub = new _Subscription__WEBPACK_IMPORTED_MODULE_1__["Subscription"](); - sub.add(scheduler.schedule(function () { - return input.then(function (value) { - sub.add(scheduler.schedule(function () { - subscriber.next(value); - sub.add(scheduler.schedule(function () { return subscriber.complete(); })); - })); - }, function (err) { - sub.add(scheduler.schedule(function () { return subscriber.error(err); })); - }); - })); - return sub; - }); -} -//# sourceMappingURL=schedulePromise.js.map +const utils = __webpack_require__(617); +const constants = __webpack_require__(618); +/** + * Constants + */ -/***/ }), -/* 354 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +const { + MAX_LENGTH, + POSIX_REGEX_SOURCE, + REGEX_NON_SPECIAL_CHAR, + REGEX_SPECIAL_CHARS_BACKREF, + REPLACEMENTS +} = constants; -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "scheduleIterable", function() { return scheduleIterable; }); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); -/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(284); -/* harmony import */ var _symbol_iterator__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(342); -/** PURE_IMPORTS_START _Observable,_Subscription,_symbol_iterator PURE_IMPORTS_END */ +/** + * Helpers + */ +const expandRange = (args, options) => { + if (typeof options.expandRange === 'function') { + return options.expandRange(...args, options); + } + args.sort(); + let value = `[${args.join('-')}]`; -function scheduleIterable(input, scheduler) { - if (!input) { - throw new Error('Iterable cannot be null'); - } - return new _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"](function (subscriber) { - var sub = new _Subscription__WEBPACK_IMPORTED_MODULE_1__["Subscription"](); - var iterator; - sub.add(function () { - if (iterator && typeof iterator.return === 'function') { - iterator.return(); - } - }); - sub.add(scheduler.schedule(function () { - iterator = input[_symbol_iterator__WEBPACK_IMPORTED_MODULE_2__["iterator"]](); - sub.add(scheduler.schedule(function () { - if (subscriber.closed) { - return; - } - var value; - var done; - try { - var result = iterator.next(); - value = result.value; - done = result.done; - } - catch (err) { - subscriber.error(err); - return; - } - if (done) { - subscriber.complete(); - } - else { - subscriber.next(value); - this.schedule(); - } - })); - })); - return sub; - }); -} -//# sourceMappingURL=scheduleIterable.js.map + try { + /* eslint-disable no-new */ + new RegExp(value); + } catch (ex) { + return args.map(v => utils.escapeRegex(v)).join('..'); + } + return value; +}; -/***/ }), -/* 355 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +const negate = state => { + let count = 1; -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "isInteropObservable", function() { return isInteropObservable; }); -/* harmony import */ var _symbol_observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(290); -/** PURE_IMPORTS_START _symbol_observable PURE_IMPORTS_END */ + while (state.peek() === '!' && (state.peek(2) !== '(' || state.peek(3) === '?')) { + state.advance(); + state.start++; + count++; + } -function isInteropObservable(input) { - return input && typeof input[_symbol_observable__WEBPACK_IMPORTED_MODULE_0__["observable"]] === 'function'; -} -//# sourceMappingURL=isInteropObservable.js.map + if (count % 2 === 0) { + return false; + } + state.negated = true; + state.start++; + return true; +}; -/***/ }), -/* 356 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/** + * Create the message for a syntax error + */ -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "isIterable", function() { return isIterable; }); -/* harmony import */ var _symbol_iterator__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(342); -/** PURE_IMPORTS_START _symbol_iterator PURE_IMPORTS_END */ +const syntaxError = (type, char) => { + return `Missing ${type}: "${char}" - use "\\\\${char}" to match literal characters`; +}; -function isIterable(input) { - return input && typeof input[_symbol_iterator__WEBPACK_IMPORTED_MODULE_0__["iterator"]] === 'function'; -} -//# sourceMappingURL=isIterable.js.map +/** + * Parse the given input string. + * @param {String} input + * @param {Object} options + * @return {Object} + */ + +const parse = (input, options) => { + if (typeof input !== 'string') { + throw new TypeError('Expected a string'); + } + input = REPLACEMENTS[input] || input; -/***/ }), -/* 357 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + let opts = { ...options }; + let max = typeof opts.maxLength === 'number' ? Math.min(MAX_LENGTH, opts.maxLength) : MAX_LENGTH; + let len = input.length; + if (len > max) { + throw new SyntaxError(`Input length: ${len}, exceeds maximum allowed length: ${max}`); + } -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "defer", function() { return defer; }); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); -/* harmony import */ var _from__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(350); -/* harmony import */ var _empty__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(310); -/** PURE_IMPORTS_START _Observable,_from,_empty PURE_IMPORTS_END */ + let bos = { type: 'bos', value: '', output: opts.prepend || '' }; + let tokens = [bos]; + let capture = opts.capture ? '' : '?:'; + let win32 = utils.isWindows(options); + // create constants based on platform, for windows or posix + const PLATFORM_CHARS = constants.globChars(win32); + const EXTGLOB_CHARS = constants.extglobChars(PLATFORM_CHARS); -function defer(observableFactory) { - return new _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"](function (subscriber) { - var input; - try { - input = observableFactory(); - } - catch (err) { - subscriber.error(err); - return undefined; - } - var source = input ? Object(_from__WEBPACK_IMPORTED_MODULE_1__["from"])(input) : Object(_empty__WEBPACK_IMPORTED_MODULE_2__["empty"])(); - return source.subscribe(subscriber); - }); -} -//# sourceMappingURL=defer.js.map + const { + DOT_LITERAL, + PLUS_LITERAL, + SLASH_LITERAL, + ONE_CHAR, + DOTS_SLASH, + NO_DOT, + NO_DOT_SLASH, + NO_DOTS_SLASH, + QMARK, + QMARK_NO_DOT, + STAR, + START_ANCHOR + } = PLATFORM_CHARS; + const globstar = (opts) => { + return `(${capture}(?:(?!${START_ANCHOR}${opts.dot ? DOTS_SLASH : DOT_LITERAL}).)*?)`; + }; -/***/ }), -/* 358 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + let nodot = opts.dot ? '' : NO_DOT; + let star = opts.bash === true ? globstar(opts) : STAR; + let qmarkNoDot = opts.dot ? QMARK : QMARK_NO_DOT; -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "forkJoin", function() { return forkJoin; }); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); -/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(285); -/* harmony import */ var _operators_map__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(333); -/* harmony import */ var _util_isObject__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(286); -/* harmony import */ var _from__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(350); -/** PURE_IMPORTS_START _Observable,_util_isArray,_operators_map,_util_isObject,_from PURE_IMPORTS_END */ + if (opts.capture) { + star = `(${star})`; + } + // minimatch options support + if (typeof opts.noext === 'boolean') { + opts.noextglob = opts.noext; + } + let state = { + index: -1, + start: 0, + consumed: '', + output: '', + backtrack: false, + brackets: 0, + braces: 0, + parens: 0, + quotes: 0, + tokens + }; + let extglobs = []; + let stack = []; + let prev = bos; + let value; + /** + * Tokenizing helpers + */ -function forkJoin() { - var sources = []; - for (var _i = 0; _i < arguments.length; _i++) { - sources[_i] = arguments[_i]; - } - if (sources.length === 1) { - var first_1 = sources[0]; - if (Object(_util_isArray__WEBPACK_IMPORTED_MODULE_1__["isArray"])(first_1)) { - return forkJoinInternal(first_1, null); - } - if (Object(_util_isObject__WEBPACK_IMPORTED_MODULE_3__["isObject"])(first_1) && Object.getPrototypeOf(first_1) === Object.prototype) { - var keys = Object.keys(first_1); - return forkJoinInternal(keys.map(function (key) { return first_1[key]; }), keys); - } - } - if (typeof sources[sources.length - 1] === 'function') { - var resultSelector_1 = sources.pop(); - sources = (sources.length === 1 && Object(_util_isArray__WEBPACK_IMPORTED_MODULE_1__["isArray"])(sources[0])) ? sources[0] : sources; - return forkJoinInternal(sources, null).pipe(Object(_operators_map__WEBPACK_IMPORTED_MODULE_2__["map"])(function (args) { return resultSelector_1.apply(void 0, args); })); - } - return forkJoinInternal(sources, null); -} -function forkJoinInternal(sources, keys) { - return new _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"](function (subscriber) { - var len = sources.length; - if (len === 0) { - subscriber.complete(); - return; - } - var values = new Array(len); - var completed = 0; - var emitted = 0; - var _loop_1 = function (i) { - var source = Object(_from__WEBPACK_IMPORTED_MODULE_4__["from"])(sources[i]); - var hasValue = false; - subscriber.add(source.subscribe({ - next: function (value) { - if (!hasValue) { - hasValue = true; - emitted++; - } - values[i] = value; - }, - error: function (err) { return subscriber.error(err); }, - complete: function () { - completed++; - if (completed === len || !hasValue) { - if (emitted === len) { - subscriber.next(keys ? - keys.reduce(function (result, key, i) { return (result[key] = values[i], result); }, {}) : - values); - } - subscriber.complete(); - } - } - })); - }; - for (var i = 0; i < len; i++) { - _loop_1(i); - } - }); -} -//# sourceMappingURL=forkJoin.js.map + const eos = () => state.index === len - 1; + const peek = state.peek = (n = 1) => input[state.index + n]; + const advance = state.advance = () => input[++state.index]; + const append = token => { + state.output += token.output != null ? token.output : token.value; + state.consumed += token.value || ''; + }; + const increment = type => { + state[type]++; + stack.push(type); + }; -/***/ }), -/* 359 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + const decrement = type => { + state[type]--; + stack.pop(); + }; -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "fromEvent", function() { return fromEvent; }); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); -/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(285); -/* harmony import */ var _util_isFunction__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(280); -/* harmony import */ var _operators_map__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(333); -/** PURE_IMPORTS_START _Observable,_util_isArray,_util_isFunction,_operators_map PURE_IMPORTS_END */ + /** + * Push tokens onto the tokens array. This helper speeds up + * tokenizing by 1) helping us avoid backtracking as much as possible, + * and 2) helping us avoid creating extra tokens when consecutive + * characters are plain text. This improves performance and simplifies + * lookbehinds. + */ + + const push = tok => { + if (prev.type === 'globstar') { + let isBrace = state.braces > 0 && (tok.type === 'comma' || tok.type === 'brace'); + let isExtglob = extglobs.length && (tok.type === 'pipe' || tok.type === 'paren'); + if (tok.type !== 'slash' && tok.type !== 'paren' && !isBrace && !isExtglob) { + state.output = state.output.slice(0, -prev.output.length); + prev.type = 'star'; + prev.value = '*'; + prev.output = star; + state.output += prev.output; + } + } + if (extglobs.length && tok.type !== 'paren' && !EXTGLOB_CHARS[tok.value]) { + extglobs[extglobs.length - 1].inner += tok.value; + } + if (tok.value || tok.output) append(tok); + if (prev && prev.type === 'text' && tok.type === 'text') { + prev.value += tok.value; + return; + } + tok.prev = prev; + tokens.push(tok); + prev = tok; + }; -var toString = /*@__PURE__*/ (function () { return Object.prototype.toString; })(); -function fromEvent(target, eventName, options, resultSelector) { - if (Object(_util_isFunction__WEBPACK_IMPORTED_MODULE_2__["isFunction"])(options)) { - resultSelector = options; - options = undefined; - } - if (resultSelector) { - return fromEvent(target, eventName, options).pipe(Object(_operators_map__WEBPACK_IMPORTED_MODULE_3__["map"])(function (args) { return Object(_util_isArray__WEBPACK_IMPORTED_MODULE_1__["isArray"])(args) ? resultSelector.apply(void 0, args) : resultSelector(args); })); - } - return new _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"](function (subscriber) { - function handler(e) { - if (arguments.length > 1) { - subscriber.next(Array.prototype.slice.call(arguments)); - } - else { - subscriber.next(e); - } - } - setupSubscription(target, eventName, handler, subscriber, options); - }); -} -function setupSubscription(sourceObj, eventName, handler, subscriber, options) { - var unsubscribe; - if (isEventTarget(sourceObj)) { - var source_1 = sourceObj; - sourceObj.addEventListener(eventName, handler, options); - unsubscribe = function () { return source_1.removeEventListener(eventName, handler, options); }; - } - else if (isJQueryStyleEventEmitter(sourceObj)) { - var source_2 = sourceObj; - sourceObj.on(eventName, handler); - unsubscribe = function () { return source_2.off(eventName, handler); }; - } - else if (isNodeStyleEventEmitter(sourceObj)) { - var source_3 = sourceObj; - sourceObj.addListener(eventName, handler); - unsubscribe = function () { return source_3.removeListener(eventName, handler); }; - } - else if (sourceObj && sourceObj.length) { - for (var i = 0, len = sourceObj.length; i < len; i++) { - setupSubscription(sourceObj[i], eventName, handler, subscriber, options); - } - } - else { - throw new TypeError('Invalid event target'); - } - subscriber.add(unsubscribe); -} -function isNodeStyleEventEmitter(sourceObj) { - return sourceObj && typeof sourceObj.addListener === 'function' && typeof sourceObj.removeListener === 'function'; -} -function isJQueryStyleEventEmitter(sourceObj) { - return sourceObj && typeof sourceObj.on === 'function' && typeof sourceObj.off === 'function'; -} -function isEventTarget(sourceObj) { - return sourceObj && typeof sourceObj.addEventListener === 'function' && typeof sourceObj.removeEventListener === 'function'; -} -//# sourceMappingURL=fromEvent.js.map + const extglobOpen = (type, value) => { + let token = { ...EXTGLOB_CHARS[value], conditions: 1, inner: '' }; + token.prev = prev; + token.parens = state.parens; + token.output = state.output; + let output = (opts.capture ? '(' : '') + token.open; -/***/ }), -/* 360 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + push({ type, value, output: state.output ? '' : ONE_CHAR }); + push({ type: 'paren', extglob: true, value: advance(), output }); + increment('parens'); + extglobs.push(token); + }; -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "fromEventPattern", function() { return fromEventPattern; }); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); -/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(285); -/* harmony import */ var _util_isFunction__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(280); -/* harmony import */ var _operators_map__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(333); -/** PURE_IMPORTS_START _Observable,_util_isArray,_util_isFunction,_operators_map PURE_IMPORTS_END */ + const extglobClose = token => { + let output = token.close + (opts.capture ? ')' : ''); + if (token.type === 'negate') { + let extglobStar = star; + if (token.inner && token.inner.length > 1 && token.inner.includes('/')) { + extglobStar = globstar(opts); + } + if (extglobStar !== star || eos() || /^\)+$/.test(input.slice(state.index + 1))) { + output = token.close = ')$))' + extglobStar; + } -function fromEventPattern(addHandler, removeHandler, resultSelector) { - if (resultSelector) { - return fromEventPattern(addHandler, removeHandler).pipe(Object(_operators_map__WEBPACK_IMPORTED_MODULE_3__["map"])(function (args) { return Object(_util_isArray__WEBPACK_IMPORTED_MODULE_1__["isArray"])(args) ? resultSelector.apply(void 0, args) : resultSelector(args); })); + if (token.prev.type === 'bos' && eos()) { + state.negatedExtglob = true; + } } - return new _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"](function (subscriber) { - var handler = function () { - var e = []; - for (var _i = 0; _i < arguments.length; _i++) { - e[_i] = arguments[_i]; - } - return subscriber.next(e.length === 1 ? e[0] : e); - }; - var retValue; - try { - retValue = addHandler(handler); - } - catch (err) { - subscriber.error(err); - return undefined; - } - if (!Object(_util_isFunction__WEBPACK_IMPORTED_MODULE_2__["isFunction"])(removeHandler)) { - return undefined; - } - return function () { return removeHandler(handler, retValue); }; - }); -} -//# sourceMappingURL=fromEventPattern.js.map + push({ type: 'paren', extglob: true, value, output }); + decrement('parens'); + }; -/***/ }), -/* 361 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + if (opts.fastpaths !== false && !/(^[*!]|[/{[()\]}"])/.test(input)) { + let backslashes = false; -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "generate", function() { return generate; }); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); -/* harmony import */ var _util_identity__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(327); -/* harmony import */ var _util_isScheduler__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(312); -/** PURE_IMPORTS_START _Observable,_util_identity,_util_isScheduler PURE_IMPORTS_END */ + let output = input.replace(REGEX_SPECIAL_CHARS_BACKREF, (m, esc, chars, first, rest, index) => { + if (first === '\\') { + backslashes = true; + return m; + } + if (first === '?') { + if (esc) { + return esc + first + (rest ? QMARK.repeat(rest.length) : ''); + } + if (index === 0) { + return qmarkNoDot + (rest ? QMARK.repeat(rest.length) : ''); + } + return QMARK.repeat(chars.length); + } + if (first === '.') { + return DOT_LITERAL.repeat(chars.length); + } -function generate(initialStateOrOptions, condition, iterate, resultSelectorOrObservable, scheduler) { - var resultSelector; - var initialState; - if (arguments.length == 1) { - var options = initialStateOrOptions; - initialState = options.initialState; - condition = options.condition; - iterate = options.iterate; - resultSelector = options.resultSelector || _util_identity__WEBPACK_IMPORTED_MODULE_1__["identity"]; - scheduler = options.scheduler; - } - else if (resultSelectorOrObservable === undefined || Object(_util_isScheduler__WEBPACK_IMPORTED_MODULE_2__["isScheduler"])(resultSelectorOrObservable)) { - initialState = initialStateOrOptions; - resultSelector = _util_identity__WEBPACK_IMPORTED_MODULE_1__["identity"]; - scheduler = resultSelectorOrObservable; - } - else { - initialState = initialStateOrOptions; - resultSelector = resultSelectorOrObservable; - } - return new _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"](function (subscriber) { - var state = initialState; - if (scheduler) { - return scheduler.schedule(dispatch, 0, { - subscriber: subscriber, - iterate: iterate, - condition: condition, - resultSelector: resultSelector, - state: state - }); + if (first === '*') { + if (esc) { + return esc + first + (rest ? star : ''); } - do { - if (condition) { - var conditionResult = void 0; - try { - conditionResult = condition(state); - } - catch (err) { - subscriber.error(err); - return undefined; - } - if (!conditionResult) { - subscriber.complete(); - break; - } - } - var value = void 0; - try { - value = resultSelector(state); - } - catch (err) { - subscriber.error(err); - return undefined; - } - subscriber.next(value); - if (subscriber.closed) { - break; - } - try { - state = iterate(state); - } - catch (err) { - subscriber.error(err); - return undefined; - } - } while (true); - return undefined; + return star; + } + return esc ? m : '\\' + m; }); -} -function dispatch(state) { - var subscriber = state.subscriber, condition = state.condition; - if (subscriber.closed) { - return undefined; - } - if (state.needIterate) { - try { - state.state = state.iterate(state.state); - } - catch (err) { - subscriber.error(err); - return undefined; - } - } - else { - state.needIterate = true; - } - if (condition) { - var conditionResult = void 0; - try { - conditionResult = condition(state.state); - } - catch (err) { - subscriber.error(err); - return undefined; - } - if (!conditionResult) { - subscriber.complete(); - return undefined; - } - if (subscriber.closed) { - return undefined; - } - } - var value; - try { - value = state.resultSelector(state.state); - } - catch (err) { - subscriber.error(err); - return undefined; - } - if (subscriber.closed) { - return undefined; - } - subscriber.next(value); - if (subscriber.closed) { - return undefined; - } - return this.schedule(state); -} -//# sourceMappingURL=generate.js.map + if (backslashes === true) { + if (opts.unescape === true) { + output = output.replace(/\\/g, ''); + } else { + output = output.replace(/\\+/g, m => { + return m.length % 2 === 0 ? '\\\\' : (m ? '\\' : ''); + }); + } + } -/***/ }), -/* 362 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + state.output = output; + return state; + } -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "iif", function() { return iif; }); -/* harmony import */ var _defer__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(357); -/* harmony import */ var _empty__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(310); -/** PURE_IMPORTS_START _defer,_empty PURE_IMPORTS_END */ + /** + * Tokenize input until we reach end-of-string + */ + while (!eos()) { + value = advance(); -function iif(condition, trueResult, falseResult) { - if (trueResult === void 0) { - trueResult = _empty__WEBPACK_IMPORTED_MODULE_1__["EMPTY"]; - } - if (falseResult === void 0) { - falseResult = _empty__WEBPACK_IMPORTED_MODULE_1__["EMPTY"]; + if (value === '\u0000') { + continue; } - return Object(_defer__WEBPACK_IMPORTED_MODULE_0__["defer"])(function () { return condition() ? trueResult : falseResult; }); -} -//# sourceMappingURL=iif.js.map + /** + * Escaped characters + */ -/***/ }), -/* 363 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + if (value === '\\') { + let next = peek(); -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "interval", function() { return interval; }); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); -/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(322); -/* harmony import */ var _util_isNumeric__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(364); -/** PURE_IMPORTS_START _Observable,_scheduler_async,_util_isNumeric PURE_IMPORTS_END */ + if (next === '/' && opts.bash !== true) { + continue; + } + if (next === '.' || next === ';') { + continue; + } + if (!next) { + value += '\\'; + push({ type: 'text', value }); + continue; + } -function interval(period, scheduler) { - if (period === void 0) { - period = 0; - } - if (scheduler === void 0) { - scheduler = _scheduler_async__WEBPACK_IMPORTED_MODULE_1__["async"]; - } - if (!Object(_util_isNumeric__WEBPACK_IMPORTED_MODULE_2__["isNumeric"])(period) || period < 0) { - period = 0; - } - if (!scheduler || typeof scheduler.schedule !== 'function') { - scheduler = _scheduler_async__WEBPACK_IMPORTED_MODULE_1__["async"]; - } - return new _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"](function (subscriber) { - subscriber.add(scheduler.schedule(dispatch, period, { subscriber: subscriber, counter: 0, period: period })); - return subscriber; - }); -} -function dispatch(state) { - var subscriber = state.subscriber, counter = state.counter, period = state.period; - subscriber.next(counter); - this.schedule({ subscriber: subscriber, counter: counter + 1, period: period }, period); -} -//# sourceMappingURL=interval.js.map + // collapse slashes to reduce potential for exploits + let match = /^\\+/.exec(input.slice(state.index + 1)); + let slashes = 0; + if (match && match[0].length > 2) { + slashes = match[0].length; + state.index += slashes; + if (slashes % 2 !== 0) { + value += '\\'; + } + } -/***/ }), -/* 364 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + if (opts.unescape === true) { + value = advance() || ''; + } else { + value += advance() || ''; + } -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "isNumeric", function() { return isNumeric; }); -/* harmony import */ var _isArray__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(285); -/** PURE_IMPORTS_START _isArray PURE_IMPORTS_END */ + if (state.brackets === 0) { + push({ type: 'text', value }); + continue; + } + } -function isNumeric(val) { - return !Object(_isArray__WEBPACK_IMPORTED_MODULE_0__["isArray"])(val) && (val - parseFloat(val) + 1) >= 0; -} -//# sourceMappingURL=isNumeric.js.map + /** + * If we're inside a regex character class, continue + * until we reach the closing bracket. + */ + if (state.brackets > 0 && (value !== ']' || prev.value === '[' || prev.value === '[^')) { + if (opts.posix !== false && value === ':') { + let inner = prev.value.slice(1); + if (inner.includes('[')) { + prev.posix = true; -/***/ }), -/* 365 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + if (inner.includes(':')) { + let idx = prev.value.lastIndexOf('['); + let pre = prev.value.slice(0, idx); + let rest = prev.value.slice(idx + 2); + let posix = POSIX_REGEX_SOURCE[rest]; + if (posix) { + prev.value = pre + posix; + state.backtrack = true; + advance(); -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "merge", function() { return merge; }); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); -/* harmony import */ var _util_isScheduler__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(312); -/* harmony import */ var _operators_mergeAll__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(348); -/* harmony import */ var _fromArray__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(313); -/** PURE_IMPORTS_START _Observable,_util_isScheduler,_operators_mergeAll,_fromArray PURE_IMPORTS_END */ + if (!bos.output && tokens.indexOf(prev) === 1) { + bos.output = ONE_CHAR; + } + continue; + } + } + } + } + if ((value === '[' && peek() !== ':') || (value === '-' && peek() === ']')) { + value = '\\' + value; + } + if (value === ']' && (prev.value === '[' || prev.value === '[^')) { + value = '\\' + value; + } + if (opts.posix === true && value === '!' && prev.value === '[') { + value = '^'; + } -function merge() { - var observables = []; - for (var _i = 0; _i < arguments.length; _i++) { - observables[_i] = arguments[_i]; + prev.value += value; + append({ value }); + continue; } - var concurrent = Number.POSITIVE_INFINITY; - var scheduler = null; - var last = observables[observables.length - 1]; - if (Object(_util_isScheduler__WEBPACK_IMPORTED_MODULE_1__["isScheduler"])(last)) { - scheduler = observables.pop(); - if (observables.length > 1 && typeof observables[observables.length - 1] === 'number') { - concurrent = observables.pop(); - } + + /** + * If we're inside a quoted string, continue + * until we reach the closing double quote. + */ + + if (state.quotes === 1 && value !== '"') { + value = utils.escapeRegex(value); + prev.value += value; + append({ value }); + continue; } - else if (typeof last === 'number') { - concurrent = observables.pop(); + + /** + * Double quotes + */ + + if (value === '"') { + state.quotes = state.quotes === 1 ? 0 : 1; + if (opts.keepQuotes === true) { + push({ type: 'text', value }); + } + continue; } - if (scheduler === null && observables.length === 1 && observables[0] instanceof _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"]) { - return observables[0]; + + /** + * Parentheses + */ + + if (value === '(') { + push({ type: 'paren', value }); + increment('parens'); + continue; } - return Object(_operators_mergeAll__WEBPACK_IMPORTED_MODULE_2__["mergeAll"])(concurrent)(Object(_fromArray__WEBPACK_IMPORTED_MODULE_3__["fromArray"])(observables, scheduler)); -} -//# sourceMappingURL=merge.js.map + if (value === ')') { + if (state.parens === 0 && opts.strictBrackets === true) { + throw new SyntaxError(syntaxError('opening', '(')); + } + + let extglob = extglobs[extglobs.length - 1]; + if (extglob && state.parens === extglob.parens + 1) { + extglobClose(extglobs.pop()); + continue; + } + + push({ type: 'paren', value, output: state.parens ? ')' : '\\)' }); + decrement('parens'); + continue; + } + + /** + * Brackets + */ + + if (value === '[') { + if (opts.nobracket === true || !input.slice(state.index + 1).includes(']')) { + if (opts.nobracket !== true && opts.strictBrackets === true) { + throw new SyntaxError(syntaxError('closing', ']')); + } -/***/ }), -/* 366 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + value = '\\' + value; + } else { + increment('brackets'); + } -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "NEVER", function() { return NEVER; }); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "never", function() { return never; }); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); -/* harmony import */ var _util_noop__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(292); -/** PURE_IMPORTS_START _Observable,_util_noop PURE_IMPORTS_END */ + push({ type: 'bracket', value }); + continue; + } + if (value === ']') { + if (opts.nobracket === true || (prev && prev.type === 'bracket' && prev.value.length === 1)) { + push({ type: 'text', value, output: '\\' + value }); + continue; + } -var NEVER = /*@__PURE__*/ new _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"](_util_noop__WEBPACK_IMPORTED_MODULE_1__["noop"]); -function never() { - return NEVER; -} -//# sourceMappingURL=never.js.map + if (state.brackets === 0) { + if (opts.strictBrackets === true) { + throw new SyntaxError(syntaxError('opening', '[')); + } + push({ type: 'text', value, output: '\\' + value }); + continue; + } -/***/ }), -/* 367 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + decrement('brackets'); -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "onErrorResumeNext", function() { return onErrorResumeNext; }); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); -/* harmony import */ var _from__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(350); -/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(285); -/* harmony import */ var _empty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(310); -/** PURE_IMPORTS_START _Observable,_from,_util_isArray,_empty PURE_IMPORTS_END */ + let prevValue = prev.value.slice(1); + if (prev.posix !== true && prevValue[0] === '^' && !prevValue.includes('/')) { + value = '/' + value; + } + prev.value += value; + append({ value }); + // when literal brackets are explicitly disabled + // assume we should match with a regex character class + if (opts.literalBrackets === false || utils.hasRegexChars(prevValue)) { + continue; + } + let escaped = utils.escapeRegex(prev.value); + state.output = state.output.slice(0, -prev.value.length); -function onErrorResumeNext() { - var sources = []; - for (var _i = 0; _i < arguments.length; _i++) { - sources[_i] = arguments[_i]; - } - if (sources.length === 0) { - return _empty__WEBPACK_IMPORTED_MODULE_3__["EMPTY"]; - } - var first = sources[0], remainder = sources.slice(1); - if (sources.length === 1 && Object(_util_isArray__WEBPACK_IMPORTED_MODULE_2__["isArray"])(first)) { - return onErrorResumeNext.apply(void 0, first); - } - return new _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"](function (subscriber) { - var subNext = function () { return subscriber.add(onErrorResumeNext.apply(void 0, remainder).subscribe(subscriber)); }; - return Object(_from__WEBPACK_IMPORTED_MODULE_1__["from"])(first).subscribe({ - next: function (value) { subscriber.next(value); }, - error: subNext, - complete: subNext, - }); - }); -} -//# sourceMappingURL=onErrorResumeNext.js.map + // when literal brackets are explicitly enabled + // assume we should escape the brackets to match literal characters + if (opts.literalBrackets === true) { + state.output += escaped; + prev.value = escaped; + continue; + } + // when the user specifies nothing, try to match both + prev.value = `(${capture}${escaped}|${prev.value})`; + state.output += prev.value; + continue; + } -/***/ }), -/* 368 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + /** + * Braces + */ -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "pairs", function() { return pairs; }); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "dispatch", function() { return dispatch; }); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); -/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(284); -/** PURE_IMPORTS_START _Observable,_Subscription PURE_IMPORTS_END */ + if (value === '{' && opts.nobrace !== true) { + push({ type: 'brace', value, output: '(' }); + increment('braces'); + continue; + } + if (value === '}') { + if (opts.nobrace === true || state.braces === 0) { + push({ type: 'text', value, output: '\\' + value }); + continue; + } -function pairs(obj, scheduler) { - if (!scheduler) { - return new _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"](function (subscriber) { - var keys = Object.keys(obj); - for (var i = 0; i < keys.length && !subscriber.closed; i++) { - var key = keys[i]; - if (obj.hasOwnProperty(key)) { - subscriber.next([key, obj[key]]); - } - } - subscriber.complete(); - }); - } - else { - return new _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"](function (subscriber) { - var keys = Object.keys(obj); - var subscription = new _Subscription__WEBPACK_IMPORTED_MODULE_1__["Subscription"](); - subscription.add(scheduler.schedule(dispatch, 0, { keys: keys, index: 0, subscriber: subscriber, subscription: subscription, obj: obj })); - return subscription; - }); - } -} -function dispatch(state) { - var keys = state.keys, index = state.index, subscriber = state.subscriber, subscription = state.subscription, obj = state.obj; - if (!subscriber.closed) { - if (index < keys.length) { - var key = keys[index]; - subscriber.next([key, obj[key]]); - subscription.add(this.schedule({ keys: keys, index: index + 1, subscriber: subscriber, subscription: subscription, obj: obj })); - } - else { - subscriber.complete(); - } - } -} -//# sourceMappingURL=pairs.js.map + let output = ')'; + if (state.dots === true) { + let arr = tokens.slice(); + let range = []; -/***/ }), -/* 369 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + for (let i = arr.length - 1; i >= 0; i--) { + tokens.pop(); + if (arr[i].type === 'brace') { + break; + } + if (arr[i].type !== 'dots') { + range.unshift(arr[i].value); + } + } -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "partition", function() { return partition; }); -/* harmony import */ var _util_not__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(370); -/* harmony import */ var _util_subscribeTo__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(339); -/* harmony import */ var _operators_filter__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(371); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(276); -/** PURE_IMPORTS_START _util_not,_util_subscribeTo,_operators_filter,_Observable PURE_IMPORTS_END */ + output = expandRange(range, opts); + state.backtrack = true; + } + push({ type: 'brace', value, output }); + decrement('braces'); + continue; + } + /** + * Pipes + */ + if (value === '|') { + if (extglobs.length > 0) { + extglobs[extglobs.length - 1].conditions++; + } + push({ type: 'text', value }); + continue; + } -function partition(source, predicate, thisArg) { - return [ - Object(_operators_filter__WEBPACK_IMPORTED_MODULE_2__["filter"])(predicate, thisArg)(new _Observable__WEBPACK_IMPORTED_MODULE_3__["Observable"](Object(_util_subscribeTo__WEBPACK_IMPORTED_MODULE_1__["subscribeTo"])(source))), - Object(_operators_filter__WEBPACK_IMPORTED_MODULE_2__["filter"])(Object(_util_not__WEBPACK_IMPORTED_MODULE_0__["not"])(predicate, thisArg))(new _Observable__WEBPACK_IMPORTED_MODULE_3__["Observable"](Object(_util_subscribeTo__WEBPACK_IMPORTED_MODULE_1__["subscribeTo"])(source))) - ]; -} -//# sourceMappingURL=partition.js.map + /** + * Commas + */ + if (value === ',') { + let output = value; -/***/ }), -/* 370 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + if (state.braces > 0 && stack[stack.length - 1] === 'braces') { + output = '|'; + } -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "not", function() { return not; }); -/** PURE_IMPORTS_START PURE_IMPORTS_END */ -function not(pred, thisArg) { - function notPred() { - return !(notPred.pred.apply(notPred.thisArg, arguments)); + push({ type: 'comma', value, output }); + continue; } - notPred.pred = pred; - notPred.thisArg = thisArg; - return notPred; -} -//# sourceMappingURL=not.js.map + /** + * Slashes + */ -/***/ }), -/* 371 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + if (value === '/') { + // if the beginning of the glob is "./", advance the start + // to the current index, and don't add the "./" characters + // to the state. This greatly simplifies lookbehinds when + // checking for BOS characters like "!" and "." (not "./") + if (prev.type === 'dot' && state.index === 1) { + state.start = state.index + 1; + state.consumed = ''; + state.output = ''; + tokens.pop(); + prev = bos; // reset "prev" to the first token + continue; + } -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "filter", function() { return filter; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); -/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ + push({ type: 'slash', value, output: SLASH_LITERAL }); + continue; + } + /** + * Dots + */ -function filter(predicate, thisArg) { - return function filterOperatorFunction(source) { - return source.lift(new FilterOperator(predicate, thisArg)); - }; -} -var FilterOperator = /*@__PURE__*/ (function () { - function FilterOperator(predicate, thisArg) { - this.predicate = predicate; - this.thisArg = thisArg; - } - FilterOperator.prototype.call = function (subscriber, source) { - return source.subscribe(new FilterSubscriber(subscriber, this.predicate, this.thisArg)); - }; - return FilterOperator; -}()); -var FilterSubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](FilterSubscriber, _super); - function FilterSubscriber(destination, predicate, thisArg) { - var _this = _super.call(this, destination) || this; - _this.predicate = predicate; - _this.thisArg = thisArg; - _this.count = 0; - return _this; + if (value === '.') { + if (state.braces > 0 && prev.type === 'dot') { + if (prev.value === '.') prev.output = DOT_LITERAL; + prev.type = 'dots'; + prev.output += value; + prev.value += value; + state.dots = true; + continue; + } + + push({ type: 'dot', value, output: DOT_LITERAL }); + continue; } - FilterSubscriber.prototype._next = function (value) { - var result; - try { - result = this.predicate.call(this.thisArg, value, this.count++); - } - catch (err) { - this.destination.error(err); - return; - } - if (result) { - this.destination.next(value); - } - }; - return FilterSubscriber; -}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); -//# sourceMappingURL=filter.js.map + /** + * Question marks + */ -/***/ }), -/* 372 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + if (value === '?') { + if (prev && prev.type === 'paren') { + let next = peek(); + let output = value; -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "race", function() { return race; }); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "RaceOperator", function() { return RaceOperator; }); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "RaceSubscriber", function() { return RaceSubscriber; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(285); -/* harmony import */ var _fromArray__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(313); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(336); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(337); -/** PURE_IMPORTS_START tslib,_util_isArray,_fromArray,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ + if (next === '<' && !utils.supportsLookbehinds()) { + throw new Error('Node.js v10 or higher is required for regex lookbehinds'); + } + if (prev.value === '(' && !/[!=<:]/.test(next) || (next === '<' && !/[!=]/.test(peek(2)))) { + output = '\\' + value; + } + push({ type: 'text', value, output }); + continue; + } + if (opts.noextglob !== true && peek() === '(' && peek(2) !== '?') { + extglobOpen('qmark', value); + continue; + } + if (opts.dot !== true && (prev.type === 'slash' || prev.type === 'bos')) { + push({ type: 'qmark', value, output: QMARK_NO_DOT }); + continue; + } -function race() { - var observables = []; - for (var _i = 0; _i < arguments.length; _i++) { - observables[_i] = arguments[_i]; + push({ type: 'qmark', value, output: QMARK }); + continue; } - if (observables.length === 1) { - if (Object(_util_isArray__WEBPACK_IMPORTED_MODULE_1__["isArray"])(observables[0])) { - observables = observables[0]; - } - else { - return observables[0]; + + /** + * Exclamation + */ + + if (value === '!') { + if (opts.noextglob !== true && peek() === '(') { + if (peek(2) !== '?' || !/[!=<:]/.test(peek(3))) { + extglobOpen('negate', value); + continue; } - } - return Object(_fromArray__WEBPACK_IMPORTED_MODULE_2__["fromArray"])(observables, undefined).lift(new RaceOperator()); -} -var RaceOperator = /*@__PURE__*/ (function () { - function RaceOperator() { - } - RaceOperator.prototype.call = function (subscriber, source) { - return source.subscribe(new RaceSubscriber(subscriber)); - }; - return RaceOperator; -}()); + } -var RaceSubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](RaceSubscriber, _super); - function RaceSubscriber(destination) { - var _this = _super.call(this, destination) || this; - _this.hasFirst = false; - _this.observables = []; - _this.subscriptions = []; - return _this; + if (opts.nonegate !== true && state.index === 0) { + negate(state); + continue; + } } - RaceSubscriber.prototype._next = function (observable) { - this.observables.push(observable); - }; - RaceSubscriber.prototype._complete = function () { - var observables = this.observables; - var len = observables.length; - if (len === 0) { - this.destination.complete(); - } - else { - for (var i = 0; i < len && !this.hasFirst; i++) { - var observable = observables[i]; - var subscription = Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_4__["subscribeToResult"])(this, observable, observable, i); - if (this.subscriptions) { - this.subscriptions.push(subscription); - } - this.add(subscription); - } - this.observables = null; - } - }; - RaceSubscriber.prototype.notifyNext = function (outerValue, innerValue, outerIndex, innerIndex, innerSub) { - if (!this.hasFirst) { - this.hasFirst = true; - for (var i = 0; i < this.subscriptions.length; i++) { - if (i !== outerIndex) { - var subscription = this.subscriptions[i]; - subscription.unsubscribe(); - this.remove(subscription); - } - } - this.subscriptions = null; - } - this.destination.next(innerValue); - }; - return RaceSubscriber; -}(_OuterSubscriber__WEBPACK_IMPORTED_MODULE_3__["OuterSubscriber"])); -//# sourceMappingURL=race.js.map + /** + * Plus + */ + if (value === '+') { + if (opts.noextglob !== true && peek() === '(' && peek(2) !== '?') { + extglobOpen('plus', value); + continue; + } -/***/ }), -/* 373 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + if (prev && (prev.type === 'bracket' || prev.type === 'paren' || prev.type === 'brace')) { + let output = prev.extglob === true ? '\\' + value : value; + push({ type: 'plus', value, output }); + continue; + } -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "range", function() { return range; }); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "dispatch", function() { return dispatch; }); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); -/** PURE_IMPORTS_START _Observable PURE_IMPORTS_END */ + // use regex behavior inside parens + if (state.parens > 0 && opts.regex !== false) { + push({ type: 'plus', value }); + continue; + } -function range(start, count, scheduler) { - if (start === void 0) { - start = 0; + push({ type: 'plus', value: PLUS_LITERAL }); + continue; } - return new _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"](function (subscriber) { - if (count === undefined) { - count = start; - start = 0; - } - var index = 0; - var current = start; - if (scheduler) { - return scheduler.schedule(dispatch, 0, { - index: index, count: count, start: start, subscriber: subscriber - }); - } - else { - do { - if (index++ >= count) { - subscriber.complete(); - break; - } - subscriber.next(current++); - if (subscriber.closed) { - break; - } - } while (true); - } - return undefined; - }); -} -function dispatch(state) { - var start = state.start, index = state.index, count = state.count, subscriber = state.subscriber; - if (index >= count) { - subscriber.complete(); - return; + + /** + * Plain text + */ + + if (value === '@') { + if (opts.noextglob !== true && peek() === '(' && peek(2) !== '?') { + push({ type: 'at', value, output: '' }); + continue; + } + + push({ type: 'text', value }); + continue; } - subscriber.next(start); - if (subscriber.closed) { - return; + + /** + * Plain text + */ + + if (value !== '*') { + if (value === '$' || value === '^') { + value = '\\' + value; + } + + let match = REGEX_NON_SPECIAL_CHAR.exec(input.slice(state.index + 1)); + if (match) { + value += match[0]; + state.index += match[0].length; + } + + push({ type: 'text', value }); + continue; } - state.index = index + 1; - state.start = start + 1; - this.schedule(state); -} -//# sourceMappingURL=range.js.map + /** + * Stars + */ -/***/ }), -/* 374 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + if (prev && (prev.type === 'globstar' || prev.star === true)) { + prev.type = 'star'; + prev.star = true; + prev.value += value; + prev.output = star; + state.backtrack = true; + state.consumed += value; + continue; + } -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "timer", function() { return timer; }); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); -/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(322); -/* harmony import */ var _util_isNumeric__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(364); -/* harmony import */ var _util_isScheduler__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(312); -/** PURE_IMPORTS_START _Observable,_scheduler_async,_util_isNumeric,_util_isScheduler PURE_IMPORTS_END */ + if (opts.noextglob !== true && peek() === '(' && peek(2) !== '?') { + extglobOpen('star', value); + continue; + } + if (prev.type === 'star') { + if (opts.noglobstar === true) { + state.consumed += value; + continue; + } + let prior = prev.prev; + let before = prior.prev; + let isStart = prior.type === 'slash' || prior.type === 'bos'; + let afterStar = before && (before.type === 'star' || before.type === 'globstar'); + if (opts.bash === true && (!isStart || (!eos() && peek() !== '/'))) { + push({ type: 'star', value, output: '' }); + continue; + } -function timer(dueTime, periodOrScheduler, scheduler) { - if (dueTime === void 0) { - dueTime = 0; - } - var period = -1; - if (Object(_util_isNumeric__WEBPACK_IMPORTED_MODULE_2__["isNumeric"])(periodOrScheduler)) { - period = Number(periodOrScheduler) < 1 && 1 || Number(periodOrScheduler); - } - else if (Object(_util_isScheduler__WEBPACK_IMPORTED_MODULE_3__["isScheduler"])(periodOrScheduler)) { - scheduler = periodOrScheduler; - } - if (!Object(_util_isScheduler__WEBPACK_IMPORTED_MODULE_3__["isScheduler"])(scheduler)) { - scheduler = _scheduler_async__WEBPACK_IMPORTED_MODULE_1__["async"]; - } - return new _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"](function (subscriber) { - var due = Object(_util_isNumeric__WEBPACK_IMPORTED_MODULE_2__["isNumeric"])(dueTime) - ? dueTime - : (+dueTime - scheduler.now()); - return scheduler.schedule(dispatch, due, { - index: 0, period: period, subscriber: subscriber - }); - }); -} -function dispatch(state) { - var index = state.index, period = state.period, subscriber = state.subscriber; - subscriber.next(index); - if (subscriber.closed) { - return; - } - else if (period === -1) { - return subscriber.complete(); - } - state.index = index + 1; - this.schedule(state, period); -} -//# sourceMappingURL=timer.js.map + let isBrace = state.braces > 0 && (prior.type === 'comma' || prior.type === 'brace'); + let isExtglob = extglobs.length && (prior.type === 'pipe' || prior.type === 'paren'); + if (!isStart && prior.type !== 'paren' && !isBrace && !isExtglob) { + push({ type: 'star', value, output: '' }); + continue; + } + // strip consecutive `/**/` + while (input.slice(state.index + 1, state.index + 4) === '/**') { + let after = input[state.index + 4]; + if (after && after !== '/') { + break; + } + state.consumed += '/**'; + state.index += 3; + } -/***/ }), -/* 375 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + if (prior.type === 'bos' && eos()) { + prev.type = 'globstar'; + prev.value += value; + prev.output = globstar(opts); + state.output = prev.output; + state.consumed += value; + continue; + } -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "using", function() { return using; }); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(276); -/* harmony import */ var _from__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(350); -/* harmony import */ var _empty__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(310); -/** PURE_IMPORTS_START _Observable,_from,_empty PURE_IMPORTS_END */ + if (prior.type === 'slash' && prior.prev.type !== 'bos' && !afterStar && eos()) { + state.output = state.output.slice(0, -(prior.output + prev.output).length); + prior.output = '(?:' + prior.output; + prev.type = 'globstar'; + prev.output = globstar(opts) + '|$)'; + prev.value += value; + state.output += prior.output + prev.output; + state.consumed += value; + continue; + } -function using(resourceFactory, observableFactory) { - return new _Observable__WEBPACK_IMPORTED_MODULE_0__["Observable"](function (subscriber) { - var resource; - try { - resource = resourceFactory(); - } - catch (err) { - subscriber.error(err); - return undefined; - } - var result; - try { - result = observableFactory(resource); - } - catch (err) { - subscriber.error(err); - return undefined; - } - var source = result ? Object(_from__WEBPACK_IMPORTED_MODULE_1__["from"])(result) : _empty__WEBPACK_IMPORTED_MODULE_2__["EMPTY"]; - var subscription = source.subscribe(subscriber); - return function () { - subscription.unsubscribe(); - if (resource) { - resource.unsubscribe(); - } - }; - }); -} -//# sourceMappingURL=using.js.map + let next = peek(); + if (prior.type === 'slash' && prior.prev.type !== 'bos' && next === '/') { + let end = peek(2) !== void 0 ? '|$' : ''; + state.output = state.output.slice(0, -(prior.output + prev.output).length); + prior.output = '(?:' + prior.output; -/***/ }), -/* 376 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + prev.type = 'globstar'; + prev.output = `${globstar(opts)}${SLASH_LITERAL}|${SLASH_LITERAL}${end})`; + prev.value += value; -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "zip", function() { return zip; }); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ZipOperator", function() { return ZipOperator; }); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ZipSubscriber", function() { return ZipSubscriber; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _fromArray__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(313); -/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(285); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(278); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(336); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(337); -/* harmony import */ var _internal_symbol_iterator__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(342); -/** PURE_IMPORTS_START tslib,_fromArray,_util_isArray,_Subscriber,_OuterSubscriber,_util_subscribeToResult,_.._internal_symbol_iterator PURE_IMPORTS_END */ + state.output += prior.output + prev.output; + state.consumed += value + advance(); + push({ type: 'slash', value, output: '' }); + continue; + } + if (prior.type === 'bos' && next === '/') { + prev.type = 'globstar'; + prev.value += value; + prev.output = `(?:^|${SLASH_LITERAL}|${globstar(opts)}${SLASH_LITERAL})`; + state.output = prev.output; + state.consumed += value + advance(); + push({ type: 'slash', value, output: '' }); + continue; + } + // remove single star from output + state.output = state.output.slice(0, -prev.output.length); + // reset previous token to globstar + prev.type = 'globstar'; + prev.output = globstar(opts); + prev.value += value; + // reset output with globstar + state.output += prev.output; + state.consumed += value; + continue; + } + let token = { type: 'star', value, output: star }; -function zip() { - var observables = []; - for (var _i = 0; _i < arguments.length; _i++) { - observables[_i] = arguments[_i]; - } - var resultSelector = observables[observables.length - 1]; - if (typeof resultSelector === 'function') { - observables.pop(); - } - return Object(_fromArray__WEBPACK_IMPORTED_MODULE_1__["fromArray"])(observables, undefined).lift(new ZipOperator(resultSelector)); -} -var ZipOperator = /*@__PURE__*/ (function () { - function ZipOperator(resultSelector) { - this.resultSelector = resultSelector; + if (opts.bash === true) { + token.output = '.*?'; + if (prev.type === 'bos' || prev.type === 'slash') { + token.output = nodot + token.output; + } + push(token); + continue; } - ZipOperator.prototype.call = function (subscriber, source) { - return source.subscribe(new ZipSubscriber(subscriber, this.resultSelector)); - }; - return ZipOperator; -}()); -var ZipSubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](ZipSubscriber, _super); - function ZipSubscriber(destination, resultSelector, values) { - if (values === void 0) { - values = Object.create(null); - } - var _this = _super.call(this, destination) || this; - _this.iterators = []; - _this.active = 0; - _this.resultSelector = (typeof resultSelector === 'function') ? resultSelector : null; - _this.values = values; - return _this; + if (prev && (prev.type === 'bracket' || prev.type === 'paren') && opts.regex === true) { + token.output = value; + push(token); + continue; } - ZipSubscriber.prototype._next = function (value) { - var iterators = this.iterators; - if (Object(_util_isArray__WEBPACK_IMPORTED_MODULE_2__["isArray"])(value)) { - iterators.push(new StaticArrayIterator(value)); - } - else if (typeof value[_internal_symbol_iterator__WEBPACK_IMPORTED_MODULE_6__["iterator"]] === 'function') { - iterators.push(new StaticIterator(value[_internal_symbol_iterator__WEBPACK_IMPORTED_MODULE_6__["iterator"]]())); - } - else { - iterators.push(new ZipBufferIterator(this.destination, this, value)); - } - }; - ZipSubscriber.prototype._complete = function () { - var iterators = this.iterators; - var len = iterators.length; - this.unsubscribe(); - if (len === 0) { - this.destination.complete(); - return; - } - this.active = len; - for (var i = 0; i < len; i++) { - var iterator = iterators[i]; - if (iterator.stillUnsubscribed) { - var destination = this.destination; - destination.add(iterator.subscribe(iterator, i)); - } - else { - this.active--; - } - } - }; - ZipSubscriber.prototype.notifyInactive = function () { - this.active--; - if (this.active === 0) { - this.destination.complete(); - } - }; - ZipSubscriber.prototype.checkIterators = function () { - var iterators = this.iterators; - var len = iterators.length; - var destination = this.destination; - for (var i = 0; i < len; i++) { - var iterator = iterators[i]; - if (typeof iterator.hasValue === 'function' && !iterator.hasValue()) { - return; - } - } - var shouldComplete = false; - var args = []; - for (var i = 0; i < len; i++) { - var iterator = iterators[i]; - var result = iterator.next(); - if (iterator.hasCompleted()) { - shouldComplete = true; - } - if (result.done) { - destination.complete(); - return; - } - args.push(result.value); - } - if (this.resultSelector) { - this._tryresultSelector(args); - } - else { - destination.next(args); - } - if (shouldComplete) { - destination.complete(); - } - }; - ZipSubscriber.prototype._tryresultSelector = function (args) { - var result; - try { - result = this.resultSelector.apply(this, args); - } - catch (err) { - this.destination.error(err); - return; - } - this.destination.next(result); - }; - return ZipSubscriber; -}(_Subscriber__WEBPACK_IMPORTED_MODULE_3__["Subscriber"])); -var StaticIterator = /*@__PURE__*/ (function () { - function StaticIterator(iterator) { - this.iterator = iterator; - this.nextResult = iterator.next(); - } - StaticIterator.prototype.hasValue = function () { - return true; - }; - StaticIterator.prototype.next = function () { - var result = this.nextResult; - this.nextResult = this.iterator.next(); - return result; - }; - StaticIterator.prototype.hasCompleted = function () { - var nextResult = this.nextResult; - return nextResult && nextResult.done; - }; - return StaticIterator; -}()); -var StaticArrayIterator = /*@__PURE__*/ (function () { - function StaticArrayIterator(array) { - this.array = array; - this.index = 0; - this.length = 0; - this.length = array.length; - } - StaticArrayIterator.prototype[_internal_symbol_iterator__WEBPACK_IMPORTED_MODULE_6__["iterator"]] = function () { - return this; - }; - StaticArrayIterator.prototype.next = function (value) { - var i = this.index++; - var array = this.array; - return i < this.length ? { value: array[i], done: false } : { value: null, done: true }; - }; - StaticArrayIterator.prototype.hasValue = function () { - return this.array.length > this.index; - }; - StaticArrayIterator.prototype.hasCompleted = function () { - return this.array.length === this.index; - }; - return StaticArrayIterator; -}()); -var ZipBufferIterator = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](ZipBufferIterator, _super); - function ZipBufferIterator(destination, parent, observable) { - var _this = _super.call(this, destination) || this; - _this.parent = parent; - _this.observable = observable; - _this.stillUnsubscribed = true; - _this.buffer = []; - _this.isComplete = false; - return _this; - } - ZipBufferIterator.prototype[_internal_symbol_iterator__WEBPACK_IMPORTED_MODULE_6__["iterator"]] = function () { - return this; - }; - ZipBufferIterator.prototype.next = function () { - var buffer = this.buffer; - if (buffer.length === 0 && this.isComplete) { - return { value: null, done: true }; - } - else { - return { value: buffer.shift(), done: false }; - } - }; - ZipBufferIterator.prototype.hasValue = function () { - return this.buffer.length > 0; - }; - ZipBufferIterator.prototype.hasCompleted = function () { - return this.buffer.length === 0 && this.isComplete; - }; - ZipBufferIterator.prototype.notifyComplete = function () { - if (this.buffer.length > 0) { - this.isComplete = true; - this.parent.notifyInactive(); - } - else { - this.destination.complete(); - } - }; - ZipBufferIterator.prototype.notifyNext = function (outerValue, innerValue, outerIndex, innerIndex, innerSub) { - this.buffer.push(innerValue); - this.parent.checkIterators(); - }; - ZipBufferIterator.prototype.subscribe = function (value, index) { - return Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_5__["subscribeToResult"])(this, this.observable, this, index); - }; - return ZipBufferIterator; -}(_OuterSubscriber__WEBPACK_IMPORTED_MODULE_4__["OuterSubscriber"])); -//# sourceMappingURL=zip.js.map + if (state.index === state.start || prev.type === 'slash' || prev.type === 'dot') { + if (prev.type === 'dot') { + state.output += NO_DOT_SLASH; + prev.output += NO_DOT_SLASH; + } else if (opts.dot === true) { + state.output += NO_DOTS_SLASH; + prev.output += NO_DOTS_SLASH; -/***/ }), -/* 377 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + } else { + state.output += nodot; + prev.output += nodot; + } -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony import */ var _internal_operators_audit__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(378); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "audit", function() { return _internal_operators_audit__WEBPACK_IMPORTED_MODULE_0__["audit"]; }); + if (peek() !== '*') { + state.output += ONE_CHAR; + prev.output += ONE_CHAR; + } + } -/* harmony import */ var _internal_operators_auditTime__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(379); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "auditTime", function() { return _internal_operators_auditTime__WEBPACK_IMPORTED_MODULE_1__["auditTime"]; }); + push(token); + } -/* harmony import */ var _internal_operators_buffer__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(380); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "buffer", function() { return _internal_operators_buffer__WEBPACK_IMPORTED_MODULE_2__["buffer"]; }); + while (state.brackets > 0) { + if (opts.strictBrackets === true) throw new SyntaxError(syntaxError('closing', ']')); + state.output = utils.escapeLast(state.output, '['); + decrement('brackets'); + } -/* harmony import */ var _internal_operators_bufferCount__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(381); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "bufferCount", function() { return _internal_operators_bufferCount__WEBPACK_IMPORTED_MODULE_3__["bufferCount"]; }); + while (state.parens > 0) { + if (opts.strictBrackets === true) throw new SyntaxError(syntaxError('closing', ')')); + state.output = utils.escapeLast(state.output, '('); + decrement('parens'); + } -/* harmony import */ var _internal_operators_bufferTime__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(382); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "bufferTime", function() { return _internal_operators_bufferTime__WEBPACK_IMPORTED_MODULE_4__["bufferTime"]; }); + while (state.braces > 0) { + if (opts.strictBrackets === true) throw new SyntaxError(syntaxError('closing', '}')); + state.output = utils.escapeLast(state.output, '{'); + decrement('braces'); + } -/* harmony import */ var _internal_operators_bufferToggle__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(383); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "bufferToggle", function() { return _internal_operators_bufferToggle__WEBPACK_IMPORTED_MODULE_5__["bufferToggle"]; }); + if (opts.strictSlashes !== true && (prev.type === 'star' || prev.type === 'bracket')) { + push({ type: 'maybe_slash', value: '', output: `${SLASH_LITERAL}?` }); + } -/* harmony import */ var _internal_operators_bufferWhen__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(384); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "bufferWhen", function() { return _internal_operators_bufferWhen__WEBPACK_IMPORTED_MODULE_6__["bufferWhen"]; }); + // rebuild the output if we had to backtrack at any point + if (state.backtrack === true) { + state.output = ''; -/* harmony import */ var _internal_operators_catchError__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(385); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "catchError", function() { return _internal_operators_catchError__WEBPACK_IMPORTED_MODULE_7__["catchError"]; }); + for (let token of state.tokens) { + state.output += token.output != null ? token.output : token.value; -/* harmony import */ var _internal_operators_combineAll__WEBPACK_IMPORTED_MODULE_8__ = __webpack_require__(386); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "combineAll", function() { return _internal_operators_combineAll__WEBPACK_IMPORTED_MODULE_8__["combineAll"]; }); + if (token.suffix) { + state.output += token.suffix; + } + } + } -/* harmony import */ var _internal_operators_combineLatest__WEBPACK_IMPORTED_MODULE_9__ = __webpack_require__(387); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "combineLatest", function() { return _internal_operators_combineLatest__WEBPACK_IMPORTED_MODULE_9__["combineLatest"]; }); + return state; +}; -/* harmony import */ var _internal_operators_concat__WEBPACK_IMPORTED_MODULE_10__ = __webpack_require__(388); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "concat", function() { return _internal_operators_concat__WEBPACK_IMPORTED_MODULE_10__["concat"]; }); +/** + * Fast paths for creating regular expressions for common glob patterns. + * This can significantly speed up processing and has very little downside + * impact when none of the fast paths match. + */ -/* harmony import */ var _internal_operators_concatAll__WEBPACK_IMPORTED_MODULE_11__ = __webpack_require__(347); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "concatAll", function() { return _internal_operators_concatAll__WEBPACK_IMPORTED_MODULE_11__["concatAll"]; }); +parse.fastpaths = (input, options) => { + let opts = { ...options }; + let max = typeof opts.maxLength === 'number' ? Math.min(MAX_LENGTH, opts.maxLength) : MAX_LENGTH; + let len = input.length; + if (len > max) { + throw new SyntaxError(`Input length: ${len}, exceeds maximum allowed length: ${max}`); + } -/* harmony import */ var _internal_operators_concatMap__WEBPACK_IMPORTED_MODULE_12__ = __webpack_require__(389); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "concatMap", function() { return _internal_operators_concatMap__WEBPACK_IMPORTED_MODULE_12__["concatMap"]; }); + input = REPLACEMENTS[input] || input; + let win32 = utils.isWindows(options); -/* harmony import */ var _internal_operators_concatMapTo__WEBPACK_IMPORTED_MODULE_13__ = __webpack_require__(390); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "concatMapTo", function() { return _internal_operators_concatMapTo__WEBPACK_IMPORTED_MODULE_13__["concatMapTo"]; }); + // create constants based on platform, for windows or posix + const { + DOT_LITERAL, + SLASH_LITERAL, + ONE_CHAR, + DOTS_SLASH, + NO_DOT, + NO_DOTS, + NO_DOTS_SLASH, + STAR, + START_ANCHOR + } = constants.globChars(win32); -/* harmony import */ var _internal_operators_count__WEBPACK_IMPORTED_MODULE_14__ = __webpack_require__(391); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "count", function() { return _internal_operators_count__WEBPACK_IMPORTED_MODULE_14__["count"]; }); + let capture = opts.capture ? '' : '?:'; + let star = opts.bash === true ? '.*?' : STAR; + let nodot = opts.dot ? NO_DOTS : NO_DOT; + let slashDot = opts.dot ? NO_DOTS_SLASH : NO_DOT; -/* harmony import */ var _internal_operators_debounce__WEBPACK_IMPORTED_MODULE_15__ = __webpack_require__(392); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "debounce", function() { return _internal_operators_debounce__WEBPACK_IMPORTED_MODULE_15__["debounce"]; }); + if (opts.capture) { + star = `(${star})`; + } -/* harmony import */ var _internal_operators_debounceTime__WEBPACK_IMPORTED_MODULE_16__ = __webpack_require__(393); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "debounceTime", function() { return _internal_operators_debounceTime__WEBPACK_IMPORTED_MODULE_16__["debounceTime"]; }); + const globstar = (opts) => { + return `(${capture}(?:(?!${START_ANCHOR}${opts.dot ? DOTS_SLASH : DOT_LITERAL}).)*?)`; + }; -/* harmony import */ var _internal_operators_defaultIfEmpty__WEBPACK_IMPORTED_MODULE_17__ = __webpack_require__(394); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "defaultIfEmpty", function() { return _internal_operators_defaultIfEmpty__WEBPACK_IMPORTED_MODULE_17__["defaultIfEmpty"]; }); + const create = str => { + switch (str) { + case '*': + return `${nodot}${ONE_CHAR}${star}`; -/* harmony import */ var _internal_operators_delay__WEBPACK_IMPORTED_MODULE_18__ = __webpack_require__(395); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "delay", function() { return _internal_operators_delay__WEBPACK_IMPORTED_MODULE_18__["delay"]; }); + case '.*': + return `${DOT_LITERAL}${ONE_CHAR}${star}`; -/* harmony import */ var _internal_operators_delayWhen__WEBPACK_IMPORTED_MODULE_19__ = __webpack_require__(397); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "delayWhen", function() { return _internal_operators_delayWhen__WEBPACK_IMPORTED_MODULE_19__["delayWhen"]; }); + case '*.*': + return `${nodot}${star}${DOT_LITERAL}${ONE_CHAR}${star}`; -/* harmony import */ var _internal_operators_dematerialize__WEBPACK_IMPORTED_MODULE_20__ = __webpack_require__(398); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "dematerialize", function() { return _internal_operators_dematerialize__WEBPACK_IMPORTED_MODULE_20__["dematerialize"]; }); + case '*/*': + return `${nodot}${star}${SLASH_LITERAL}${ONE_CHAR}${slashDot}${star}`; -/* harmony import */ var _internal_operators_distinct__WEBPACK_IMPORTED_MODULE_21__ = __webpack_require__(399); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "distinct", function() { return _internal_operators_distinct__WEBPACK_IMPORTED_MODULE_21__["distinct"]; }); + case '**': + return nodot + globstar(opts); -/* harmony import */ var _internal_operators_distinctUntilChanged__WEBPACK_IMPORTED_MODULE_22__ = __webpack_require__(400); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "distinctUntilChanged", function() { return _internal_operators_distinctUntilChanged__WEBPACK_IMPORTED_MODULE_22__["distinctUntilChanged"]; }); + case '**/*': + return `(?:${nodot}${globstar(opts)}${SLASH_LITERAL})?${slashDot}${ONE_CHAR}${star}`; -/* harmony import */ var _internal_operators_distinctUntilKeyChanged__WEBPACK_IMPORTED_MODULE_23__ = __webpack_require__(401); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "distinctUntilKeyChanged", function() { return _internal_operators_distinctUntilKeyChanged__WEBPACK_IMPORTED_MODULE_23__["distinctUntilKeyChanged"]; }); + case '**/*.*': + return `(?:${nodot}${globstar(opts)}${SLASH_LITERAL})?${slashDot}${star}${DOT_LITERAL}${ONE_CHAR}${star}`; -/* harmony import */ var _internal_operators_elementAt__WEBPACK_IMPORTED_MODULE_24__ = __webpack_require__(402); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "elementAt", function() { return _internal_operators_elementAt__WEBPACK_IMPORTED_MODULE_24__["elementAt"]; }); + case '**/.*': + return `(?:${nodot}${globstar(opts)}${SLASH_LITERAL})?${DOT_LITERAL}${ONE_CHAR}${star}`; -/* harmony import */ var _internal_operators_endWith__WEBPACK_IMPORTED_MODULE_25__ = __webpack_require__(405); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "endWith", function() { return _internal_operators_endWith__WEBPACK_IMPORTED_MODULE_25__["endWith"]; }); + default: { + let match = /^(.*?)\.(\w+)$/.exec(str); + if (!match) return; -/* harmony import */ var _internal_operators_every__WEBPACK_IMPORTED_MODULE_26__ = __webpack_require__(406); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "every", function() { return _internal_operators_every__WEBPACK_IMPORTED_MODULE_26__["every"]; }); + let source = create(match[1], options); + if (!source) return; -/* harmony import */ var _internal_operators_exhaust__WEBPACK_IMPORTED_MODULE_27__ = __webpack_require__(407); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "exhaust", function() { return _internal_operators_exhaust__WEBPACK_IMPORTED_MODULE_27__["exhaust"]; }); + return source + DOT_LITERAL + match[2]; + } + } + }; -/* harmony import */ var _internal_operators_exhaustMap__WEBPACK_IMPORTED_MODULE_28__ = __webpack_require__(408); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "exhaustMap", function() { return _internal_operators_exhaustMap__WEBPACK_IMPORTED_MODULE_28__["exhaustMap"]; }); + let output = create(input); + if (output && opts.strictSlashes !== true) { + output += `${SLASH_LITERAL}?`; + } -/* harmony import */ var _internal_operators_expand__WEBPACK_IMPORTED_MODULE_29__ = __webpack_require__(409); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "expand", function() { return _internal_operators_expand__WEBPACK_IMPORTED_MODULE_29__["expand"]; }); + return output; +}; -/* harmony import */ var _internal_operators_filter__WEBPACK_IMPORTED_MODULE_30__ = __webpack_require__(371); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "filter", function() { return _internal_operators_filter__WEBPACK_IMPORTED_MODULE_30__["filter"]; }); +module.exports = parse; -/* harmony import */ var _internal_operators_finalize__WEBPACK_IMPORTED_MODULE_31__ = __webpack_require__(410); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "finalize", function() { return _internal_operators_finalize__WEBPACK_IMPORTED_MODULE_31__["finalize"]; }); -/* harmony import */ var _internal_operators_find__WEBPACK_IMPORTED_MODULE_32__ = __webpack_require__(411); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "find", function() { return _internal_operators_find__WEBPACK_IMPORTED_MODULE_32__["find"]; }); +/***/ }), +/* 620 */ +/***/ (function(module, exports, __webpack_require__) { -/* harmony import */ var _internal_operators_findIndex__WEBPACK_IMPORTED_MODULE_33__ = __webpack_require__(412); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "findIndex", function() { return _internal_operators_findIndex__WEBPACK_IMPORTED_MODULE_33__["findIndex"]; }); +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const merge2 = __webpack_require__(591); +function merge(streams) { + const mergedStream = merge2(streams); + streams.forEach((stream) => { + stream.once('error', (err) => mergedStream.emit('error', err)); + }); + return mergedStream; +} +exports.merge = merge; -/* harmony import */ var _internal_operators_first__WEBPACK_IMPORTED_MODULE_34__ = __webpack_require__(413); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "first", function() { return _internal_operators_first__WEBPACK_IMPORTED_MODULE_34__["first"]; }); -/* harmony import */ var _internal_operators_groupBy__WEBPACK_IMPORTED_MODULE_35__ = __webpack_require__(298); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "groupBy", function() { return _internal_operators_groupBy__WEBPACK_IMPORTED_MODULE_35__["groupBy"]; }); +/***/ }), +/* 621 */ +/***/ (function(module, exports, __webpack_require__) { -/* harmony import */ var _internal_operators_ignoreElements__WEBPACK_IMPORTED_MODULE_36__ = __webpack_require__(414); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "ignoreElements", function() { return _internal_operators_ignoreElements__WEBPACK_IMPORTED_MODULE_36__["ignoreElements"]; }); +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const stream_1 = __webpack_require__(622); +const provider_1 = __webpack_require__(649); +class ProviderAsync extends provider_1.default { + constructor() { + super(...arguments); + this._reader = new stream_1.default(this._settings); + } + read(task) { + const root = this._getRootDirectory(task); + const options = this._getReaderOptions(task); + const entries = []; + return new Promise((resolve, reject) => { + const stream = this.api(root, task, options); + stream.once('error', reject); + stream.on('data', (entry) => entries.push(options.transform(entry))); + stream.once('end', () => resolve(entries)); + }); + } + api(root, task, options) { + if (task.dynamic) { + return this._reader.dynamic(root, options); + } + return this._reader.static(task.patterns, options); + } +} +exports.default = ProviderAsync; -/* harmony import */ var _internal_operators_isEmpty__WEBPACK_IMPORTED_MODULE_37__ = __webpack_require__(415); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "isEmpty", function() { return _internal_operators_isEmpty__WEBPACK_IMPORTED_MODULE_37__["isEmpty"]; }); -/* harmony import */ var _internal_operators_last__WEBPACK_IMPORTED_MODULE_38__ = __webpack_require__(416); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "last", function() { return _internal_operators_last__WEBPACK_IMPORTED_MODULE_38__["last"]; }); +/***/ }), +/* 622 */ +/***/ (function(module, exports, __webpack_require__) { -/* harmony import */ var _internal_operators_map__WEBPACK_IMPORTED_MODULE_39__ = __webpack_require__(333); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "map", function() { return _internal_operators_map__WEBPACK_IMPORTED_MODULE_39__["map"]; }); +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const stream_1 = __webpack_require__(27); +const fsStat = __webpack_require__(623); +const fsWalk = __webpack_require__(628); +const reader_1 = __webpack_require__(648); +class ReaderStream extends reader_1.default { + constructor() { + super(...arguments); + this._walkStream = fsWalk.walkStream; + this._stat = fsStat.stat; + } + dynamic(root, options) { + return this._walkStream(root, options); + } + static(patterns, options) { + const filepaths = patterns.map(this._getFullEntryPath, this); + const stream = new stream_1.PassThrough({ objectMode: true }); + stream._write = (index, _enc, done) => { + return this._getEntry(filepaths[index], patterns[index], options) + .then((entry) => { + if (entry !== null && options.entryFilter(entry)) { + stream.push(entry); + } + if (index === filepaths.length - 1) { + stream.end(); + } + done(); + }) + .catch(done); + }; + for (let i = 0; i < filepaths.length; i++) { + stream.write(i); + } + return stream; + } + _getEntry(filepath, pattern, options) { + return this._getStat(filepath) + .then((stats) => this._makeEntry(stats, pattern)) + .catch((error) => { + if (options.errorFilter(error)) { + return null; + } + throw error; + }); + } + _getStat(filepath) { + return new Promise((resolve, reject) => { + this._stat(filepath, this._fsStatSettings, (error, stats) => { + error ? reject(error) : resolve(stats); + }); + }); + } +} +exports.default = ReaderStream; -/* harmony import */ var _internal_operators_mapTo__WEBPACK_IMPORTED_MODULE_40__ = __webpack_require__(418); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "mapTo", function() { return _internal_operators_mapTo__WEBPACK_IMPORTED_MODULE_40__["mapTo"]; }); -/* harmony import */ var _internal_operators_materialize__WEBPACK_IMPORTED_MODULE_41__ = __webpack_require__(419); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "materialize", function() { return _internal_operators_materialize__WEBPACK_IMPORTED_MODULE_41__["materialize"]; }); +/***/ }), +/* 623 */ +/***/ (function(module, exports, __webpack_require__) { -/* harmony import */ var _internal_operators_max__WEBPACK_IMPORTED_MODULE_42__ = __webpack_require__(420); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "max", function() { return _internal_operators_max__WEBPACK_IMPORTED_MODULE_42__["max"]; }); +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const async = __webpack_require__(624); +const sync = __webpack_require__(625); +const settings_1 = __webpack_require__(626); +exports.Settings = settings_1.default; +function stat(path, optionsOrSettingsOrCallback, callback) { + if (typeof optionsOrSettingsOrCallback === 'function') { + return async.read(path, getSettings(), optionsOrSettingsOrCallback); + } + async.read(path, getSettings(optionsOrSettingsOrCallback), callback); +} +exports.stat = stat; +function statSync(path, optionsOrSettings) { + const settings = getSettings(optionsOrSettings); + return sync.read(path, settings); +} +exports.statSync = statSync; +function getSettings(settingsOrOptions = {}) { + if (settingsOrOptions instanceof settings_1.default) { + return settingsOrOptions; + } + return new settings_1.default(settingsOrOptions); +} -/* harmony import */ var _internal_operators_merge__WEBPACK_IMPORTED_MODULE_43__ = __webpack_require__(423); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "merge", function() { return _internal_operators_merge__WEBPACK_IMPORTED_MODULE_43__["merge"]; }); -/* harmony import */ var _internal_operators_mergeAll__WEBPACK_IMPORTED_MODULE_44__ = __webpack_require__(348); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "mergeAll", function() { return _internal_operators_mergeAll__WEBPACK_IMPORTED_MODULE_44__["mergeAll"]; }); +/***/ }), +/* 624 */ +/***/ (function(module, exports, __webpack_require__) { -/* harmony import */ var _internal_operators_mergeMap__WEBPACK_IMPORTED_MODULE_45__ = __webpack_require__(349); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "mergeMap", function() { return _internal_operators_mergeMap__WEBPACK_IMPORTED_MODULE_45__["mergeMap"]; }); +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +function read(path, settings, callback) { + settings.fs.lstat(path, (lstatError, lstat) => { + if (lstatError) { + return callFailureCallback(callback, lstatError); + } + if (!lstat.isSymbolicLink() || !settings.followSymbolicLink) { + return callSuccessCallback(callback, lstat); + } + settings.fs.stat(path, (statError, stat) => { + if (statError) { + if (settings.throwErrorOnBrokenSymbolicLink) { + return callFailureCallback(callback, statError); + } + return callSuccessCallback(callback, lstat); + } + if (settings.markSymbolicLink) { + stat.isSymbolicLink = () => true; + } + callSuccessCallback(callback, stat); + }); + }); +} +exports.read = read; +function callFailureCallback(callback, error) { + callback(error); +} +function callSuccessCallback(callback, result) { + callback(null, result); +} -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "flatMap", function() { return _internal_operators_mergeMap__WEBPACK_IMPORTED_MODULE_45__["mergeMap"]; }); -/* harmony import */ var _internal_operators_mergeMapTo__WEBPACK_IMPORTED_MODULE_46__ = __webpack_require__(424); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "mergeMapTo", function() { return _internal_operators_mergeMapTo__WEBPACK_IMPORTED_MODULE_46__["mergeMapTo"]; }); +/***/ }), +/* 625 */ +/***/ (function(module, exports, __webpack_require__) { -/* harmony import */ var _internal_operators_mergeScan__WEBPACK_IMPORTED_MODULE_47__ = __webpack_require__(425); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "mergeScan", function() { return _internal_operators_mergeScan__WEBPACK_IMPORTED_MODULE_47__["mergeScan"]; }); +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +function read(path, settings) { + const lstat = settings.fs.lstatSync(path); + if (!lstat.isSymbolicLink() || !settings.followSymbolicLink) { + return lstat; + } + try { + const stat = settings.fs.statSync(path); + if (settings.markSymbolicLink) { + stat.isSymbolicLink = () => true; + } + return stat; + } + catch (error) { + if (!settings.throwErrorOnBrokenSymbolicLink) { + return lstat; + } + throw error; + } +} +exports.read = read; -/* harmony import */ var _internal_operators_min__WEBPACK_IMPORTED_MODULE_48__ = __webpack_require__(426); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "min", function() { return _internal_operators_min__WEBPACK_IMPORTED_MODULE_48__["min"]; }); -/* harmony import */ var _internal_operators_multicast__WEBPACK_IMPORTED_MODULE_49__ = __webpack_require__(427); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "multicast", function() { return _internal_operators_multicast__WEBPACK_IMPORTED_MODULE_49__["multicast"]; }); +/***/ }), +/* 626 */ +/***/ (function(module, exports, __webpack_require__) { -/* harmony import */ var _internal_operators_observeOn__WEBPACK_IMPORTED_MODULE_50__ = __webpack_require__(308); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "observeOn", function() { return _internal_operators_observeOn__WEBPACK_IMPORTED_MODULE_50__["observeOn"]; }); +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const fs = __webpack_require__(627); +class Settings { + constructor(_options = {}) { + this._options = _options; + this.followSymbolicLink = this._getValue(this._options.followSymbolicLink, true); + this.fs = fs.createFileSystemAdapter(this._options.fs); + this.markSymbolicLink = this._getValue(this._options.markSymbolicLink, false); + this.throwErrorOnBrokenSymbolicLink = this._getValue(this._options.throwErrorOnBrokenSymbolicLink, true); + } + _getValue(option, value) { + return option === undefined ? value : option; + } +} +exports.default = Settings; -/* harmony import */ var _internal_operators_onErrorResumeNext__WEBPACK_IMPORTED_MODULE_51__ = __webpack_require__(428); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "onErrorResumeNext", function() { return _internal_operators_onErrorResumeNext__WEBPACK_IMPORTED_MODULE_51__["onErrorResumeNext"]; }); -/* harmony import */ var _internal_operators_pairwise__WEBPACK_IMPORTED_MODULE_52__ = __webpack_require__(429); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "pairwise", function() { return _internal_operators_pairwise__WEBPACK_IMPORTED_MODULE_52__["pairwise"]; }); +/***/ }), +/* 627 */ +/***/ (function(module, exports, __webpack_require__) { -/* harmony import */ var _internal_operators_partition__WEBPACK_IMPORTED_MODULE_53__ = __webpack_require__(430); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "partition", function() { return _internal_operators_partition__WEBPACK_IMPORTED_MODULE_53__["partition"]; }); +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const fs = __webpack_require__(23); +exports.FILE_SYSTEM_ADAPTER = { + lstat: fs.lstat, + stat: fs.stat, + lstatSync: fs.lstatSync, + statSync: fs.statSync +}; +function createFileSystemAdapter(fsMethods) { + if (!fsMethods) { + return exports.FILE_SYSTEM_ADAPTER; + } + return Object.assign({}, exports.FILE_SYSTEM_ADAPTER, fsMethods); +} +exports.createFileSystemAdapter = createFileSystemAdapter; -/* harmony import */ var _internal_operators_pluck__WEBPACK_IMPORTED_MODULE_54__ = __webpack_require__(431); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "pluck", function() { return _internal_operators_pluck__WEBPACK_IMPORTED_MODULE_54__["pluck"]; }); -/* harmony import */ var _internal_operators_publish__WEBPACK_IMPORTED_MODULE_55__ = __webpack_require__(432); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "publish", function() { return _internal_operators_publish__WEBPACK_IMPORTED_MODULE_55__["publish"]; }); +/***/ }), +/* 628 */ +/***/ (function(module, exports, __webpack_require__) { -/* harmony import */ var _internal_operators_publishBehavior__WEBPACK_IMPORTED_MODULE_56__ = __webpack_require__(433); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "publishBehavior", function() { return _internal_operators_publishBehavior__WEBPACK_IMPORTED_MODULE_56__["publishBehavior"]; }); +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const async_1 = __webpack_require__(629); +const stream_1 = __webpack_require__(644); +const sync_1 = __webpack_require__(645); +const settings_1 = __webpack_require__(647); +exports.Settings = settings_1.default; +function walk(dir, optionsOrSettingsOrCallback, callback) { + if (typeof optionsOrSettingsOrCallback === 'function') { + return new async_1.default(dir, getSettings()).read(optionsOrSettingsOrCallback); + } + new async_1.default(dir, getSettings(optionsOrSettingsOrCallback)).read(callback); +} +exports.walk = walk; +function walkSync(dir, optionsOrSettings) { + const settings = getSettings(optionsOrSettings); + const provider = new sync_1.default(dir, settings); + return provider.read(); +} +exports.walkSync = walkSync; +function walkStream(dir, optionsOrSettings) { + const settings = getSettings(optionsOrSettings); + const provider = new stream_1.default(dir, settings); + return provider.read(); +} +exports.walkStream = walkStream; +function getSettings(settingsOrOptions = {}) { + if (settingsOrOptions instanceof settings_1.default) { + return settingsOrOptions; + } + return new settings_1.default(settingsOrOptions); +} -/* harmony import */ var _internal_operators_publishLast__WEBPACK_IMPORTED_MODULE_57__ = __webpack_require__(434); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "publishLast", function() { return _internal_operators_publishLast__WEBPACK_IMPORTED_MODULE_57__["publishLast"]; }); -/* harmony import */ var _internal_operators_publishReplay__WEBPACK_IMPORTED_MODULE_58__ = __webpack_require__(435); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "publishReplay", function() { return _internal_operators_publishReplay__WEBPACK_IMPORTED_MODULE_58__["publishReplay"]; }); +/***/ }), +/* 629 */ +/***/ (function(module, exports, __webpack_require__) { -/* harmony import */ var _internal_operators_race__WEBPACK_IMPORTED_MODULE_59__ = __webpack_require__(436); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "race", function() { return _internal_operators_race__WEBPACK_IMPORTED_MODULE_59__["race"]; }); +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const async_1 = __webpack_require__(630); +class AsyncProvider { + constructor(_root, _settings) { + this._root = _root; + this._settings = _settings; + this._reader = new async_1.default(this._root, this._settings); + this._storage = new Set(); + } + read(callback) { + this._reader.onError((error) => { + callFailureCallback(callback, error); + }); + this._reader.onEntry((entry) => { + this._storage.add(entry); + }); + this._reader.onEnd(() => { + callSuccessCallback(callback, Array.from(this._storage)); + }); + this._reader.read(); + } +} +exports.default = AsyncProvider; +function callFailureCallback(callback, error) { + callback(error); +} +function callSuccessCallback(callback, entries) { + callback(null, entries); +} -/* harmony import */ var _internal_operators_reduce__WEBPACK_IMPORTED_MODULE_60__ = __webpack_require__(421); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "reduce", function() { return _internal_operators_reduce__WEBPACK_IMPORTED_MODULE_60__["reduce"]; }); -/* harmony import */ var _internal_operators_repeat__WEBPACK_IMPORTED_MODULE_61__ = __webpack_require__(437); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "repeat", function() { return _internal_operators_repeat__WEBPACK_IMPORTED_MODULE_61__["repeat"]; }); +/***/ }), +/* 630 */ +/***/ (function(module, exports, __webpack_require__) { -/* harmony import */ var _internal_operators_repeatWhen__WEBPACK_IMPORTED_MODULE_62__ = __webpack_require__(438); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "repeatWhen", function() { return _internal_operators_repeatWhen__WEBPACK_IMPORTED_MODULE_62__["repeatWhen"]; }); +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const events_1 = __webpack_require__(379); +const fsScandir = __webpack_require__(631); +const fastq = __webpack_require__(640); +const common = __webpack_require__(642); +const reader_1 = __webpack_require__(643); +class AsyncReader extends reader_1.default { + constructor(_root, _settings) { + super(_root, _settings); + this._settings = _settings; + this._scandir = fsScandir.scandir; + this._emitter = new events_1.EventEmitter(); + this._queue = fastq(this._worker.bind(this), this._settings.concurrency); + this._isFatalError = false; + this._isDestroyed = false; + this._queue.drain = () => { + if (!this._isFatalError) { + this._emitter.emit('end'); + } + }; + } + read() { + this._isFatalError = false; + this._isDestroyed = false; + setImmediate(() => { + this._pushToQueue(this._root, this._settings.basePath); + }); + return this._emitter; + } + destroy() { + if (this._isDestroyed) { + throw new Error('The reader is already destroyed'); + } + this._isDestroyed = true; + this._queue.killAndDrain(); + } + onEntry(callback) { + this._emitter.on('entry', callback); + } + onError(callback) { + this._emitter.once('error', callback); + } + onEnd(callback) { + this._emitter.once('end', callback); + } + _pushToQueue(dir, base) { + const queueItem = { dir, base }; + this._queue.push(queueItem, (error) => { + if (error) { + this._handleError(error); + } + }); + } + _worker(item, done) { + this._scandir(item.dir, this._settings.fsScandirSettings, (error, entries) => { + if (error) { + return done(error, undefined); + } + for (const entry of entries) { + this._handleEntry(entry, item.base); + } + done(null, undefined); + }); + } + _handleError(error) { + if (!common.isFatalError(this._settings, error)) { + return; + } + this._isFatalError = true; + this._isDestroyed = true; + this._emitter.emit('error', error); + } + _handleEntry(entry, base) { + if (this._isDestroyed || this._isFatalError) { + return; + } + const fullpath = entry.path; + if (base !== undefined) { + entry.path = common.joinPathSegments(base, entry.name, this._settings.pathSegmentSeparator); + } + if (common.isAppliedFilter(this._settings.entryFilter, entry)) { + this._emitEntry(entry); + } + if (entry.dirent.isDirectory() && common.isAppliedFilter(this._settings.deepFilter, entry)) { + this._pushToQueue(fullpath, entry.path); + } + } + _emitEntry(entry) { + this._emitter.emit('entry', entry); + } +} +exports.default = AsyncReader; -/* harmony import */ var _internal_operators_retry__WEBPACK_IMPORTED_MODULE_63__ = __webpack_require__(439); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "retry", function() { return _internal_operators_retry__WEBPACK_IMPORTED_MODULE_63__["retry"]; }); -/* harmony import */ var _internal_operators_retryWhen__WEBPACK_IMPORTED_MODULE_64__ = __webpack_require__(440); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "retryWhen", function() { return _internal_operators_retryWhen__WEBPACK_IMPORTED_MODULE_64__["retryWhen"]; }); +/***/ }), +/* 631 */ +/***/ (function(module, exports, __webpack_require__) { -/* harmony import */ var _internal_operators_refCount__WEBPACK_IMPORTED_MODULE_65__ = __webpack_require__(297); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "refCount", function() { return _internal_operators_refCount__WEBPACK_IMPORTED_MODULE_65__["refCount"]; }); +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const async = __webpack_require__(632); +const sync = __webpack_require__(637); +const settings_1 = __webpack_require__(638); +exports.Settings = settings_1.default; +function scandir(path, optionsOrSettingsOrCallback, callback) { + if (typeof optionsOrSettingsOrCallback === 'function') { + return async.read(path, getSettings(), optionsOrSettingsOrCallback); + } + async.read(path, getSettings(optionsOrSettingsOrCallback), callback); +} +exports.scandir = scandir; +function scandirSync(path, optionsOrSettings) { + const settings = getSettings(optionsOrSettings); + return sync.read(path, settings); +} +exports.scandirSync = scandirSync; +function getSettings(settingsOrOptions = {}) { + if (settingsOrOptions instanceof settings_1.default) { + return settingsOrOptions; + } + return new settings_1.default(settingsOrOptions); +} -/* harmony import */ var _internal_operators_sample__WEBPACK_IMPORTED_MODULE_66__ = __webpack_require__(441); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "sample", function() { return _internal_operators_sample__WEBPACK_IMPORTED_MODULE_66__["sample"]; }); -/* harmony import */ var _internal_operators_sampleTime__WEBPACK_IMPORTED_MODULE_67__ = __webpack_require__(442); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "sampleTime", function() { return _internal_operators_sampleTime__WEBPACK_IMPORTED_MODULE_67__["sampleTime"]; }); +/***/ }), +/* 632 */ +/***/ (function(module, exports, __webpack_require__) { -/* harmony import */ var _internal_operators_scan__WEBPACK_IMPORTED_MODULE_68__ = __webpack_require__(422); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "scan", function() { return _internal_operators_scan__WEBPACK_IMPORTED_MODULE_68__["scan"]; }); +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const fsStat = __webpack_require__(623); +const rpl = __webpack_require__(633); +const constants_1 = __webpack_require__(634); +const utils = __webpack_require__(635); +function read(dir, settings, callback) { + if (!settings.stats && constants_1.IS_SUPPORT_READDIR_WITH_FILE_TYPES) { + return readdirWithFileTypes(dir, settings, callback); + } + return readdir(dir, settings, callback); +} +exports.read = read; +function readdirWithFileTypes(dir, settings, callback) { + settings.fs.readdir(dir, { withFileTypes: true }, (readdirError, dirents) => { + if (readdirError) { + return callFailureCallback(callback, readdirError); + } + const entries = dirents.map((dirent) => ({ + dirent, + name: dirent.name, + path: `${dir}${settings.pathSegmentSeparator}${dirent.name}` + })); + if (!settings.followSymbolicLinks) { + return callSuccessCallback(callback, entries); + } + const tasks = entries.map((entry) => makeRplTaskEntry(entry, settings)); + rpl(tasks, (rplError, rplEntries) => { + if (rplError) { + return callFailureCallback(callback, rplError); + } + callSuccessCallback(callback, rplEntries); + }); + }); +} +exports.readdirWithFileTypes = readdirWithFileTypes; +function makeRplTaskEntry(entry, settings) { + return (done) => { + if (!entry.dirent.isSymbolicLink()) { + return done(null, entry); + } + settings.fs.stat(entry.path, (statError, stats) => { + if (statError) { + if (settings.throwErrorOnBrokenSymbolicLink) { + return done(statError); + } + return done(null, entry); + } + entry.dirent = utils.fs.createDirentFromStats(entry.name, stats); + return done(null, entry); + }); + }; +} +function readdir(dir, settings, callback) { + settings.fs.readdir(dir, (readdirError, names) => { + if (readdirError) { + return callFailureCallback(callback, readdirError); + } + const filepaths = names.map((name) => `${dir}${settings.pathSegmentSeparator}${name}`); + const tasks = filepaths.map((filepath) => { + return (done) => fsStat.stat(filepath, settings.fsStatSettings, done); + }); + rpl(tasks, (rplError, results) => { + if (rplError) { + return callFailureCallback(callback, rplError); + } + const entries = []; + for (let index = 0; index < names.length; index++) { + const name = names[index]; + const stats = results[index]; + const entry = { + name, + path: filepaths[index], + dirent: utils.fs.createDirentFromStats(name, stats) + }; + if (settings.stats) { + entry.stats = stats; + } + entries.push(entry); + } + callSuccessCallback(callback, entries); + }); + }); +} +exports.readdir = readdir; +function callFailureCallback(callback, error) { + callback(error); +} +function callSuccessCallback(callback, result) { + callback(null, result); +} -/* harmony import */ var _internal_operators_sequenceEqual__WEBPACK_IMPORTED_MODULE_69__ = __webpack_require__(443); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "sequenceEqual", function() { return _internal_operators_sequenceEqual__WEBPACK_IMPORTED_MODULE_69__["sequenceEqual"]; }); -/* harmony import */ var _internal_operators_share__WEBPACK_IMPORTED_MODULE_70__ = __webpack_require__(444); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "share", function() { return _internal_operators_share__WEBPACK_IMPORTED_MODULE_70__["share"]; }); +/***/ }), +/* 633 */ +/***/ (function(module, exports) { -/* harmony import */ var _internal_operators_shareReplay__WEBPACK_IMPORTED_MODULE_71__ = __webpack_require__(445); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "shareReplay", function() { return _internal_operators_shareReplay__WEBPACK_IMPORTED_MODULE_71__["shareReplay"]; }); +module.exports = runParallel -/* harmony import */ var _internal_operators_single__WEBPACK_IMPORTED_MODULE_72__ = __webpack_require__(446); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "single", function() { return _internal_operators_single__WEBPACK_IMPORTED_MODULE_72__["single"]; }); +function runParallel (tasks, cb) { + var results, pending, keys + var isSync = true -/* harmony import */ var _internal_operators_skip__WEBPACK_IMPORTED_MODULE_73__ = __webpack_require__(447); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "skip", function() { return _internal_operators_skip__WEBPACK_IMPORTED_MODULE_73__["skip"]; }); + if (Array.isArray(tasks)) { + results = [] + pending = tasks.length + } else { + keys = Object.keys(tasks) + results = {} + pending = keys.length + } -/* harmony import */ var _internal_operators_skipLast__WEBPACK_IMPORTED_MODULE_74__ = __webpack_require__(448); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "skipLast", function() { return _internal_operators_skipLast__WEBPACK_IMPORTED_MODULE_74__["skipLast"]; }); + function done (err) { + function end () { + if (cb) cb(err, results) + cb = null + } + if (isSync) process.nextTick(end) + else end() + } -/* harmony import */ var _internal_operators_skipUntil__WEBPACK_IMPORTED_MODULE_75__ = __webpack_require__(449); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "skipUntil", function() { return _internal_operators_skipUntil__WEBPACK_IMPORTED_MODULE_75__["skipUntil"]; }); + function each (i, err, result) { + results[i] = result + if (--pending === 0 || err) { + done(err) + } + } -/* harmony import */ var _internal_operators_skipWhile__WEBPACK_IMPORTED_MODULE_76__ = __webpack_require__(450); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "skipWhile", function() { return _internal_operators_skipWhile__WEBPACK_IMPORTED_MODULE_76__["skipWhile"]; }); + if (!pending) { + // empty + done(null) + } else if (keys) { + // object + keys.forEach(function (key) { + tasks[key](function (err, result) { each(key, err, result) }) + }) + } else { + // array + tasks.forEach(function (task, i) { + task(function (err, result) { each(i, err, result) }) + }) + } -/* harmony import */ var _internal_operators_startWith__WEBPACK_IMPORTED_MODULE_77__ = __webpack_require__(451); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "startWith", function() { return _internal_operators_startWith__WEBPACK_IMPORTED_MODULE_77__["startWith"]; }); + isSync = false +} -/* harmony import */ var _internal_operators_subscribeOn__WEBPACK_IMPORTED_MODULE_78__ = __webpack_require__(452); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "subscribeOn", function() { return _internal_operators_subscribeOn__WEBPACK_IMPORTED_MODULE_78__["subscribeOn"]; }); -/* harmony import */ var _internal_operators_switchAll__WEBPACK_IMPORTED_MODULE_79__ = __webpack_require__(454); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "switchAll", function() { return _internal_operators_switchAll__WEBPACK_IMPORTED_MODULE_79__["switchAll"]; }); +/***/ }), +/* 634 */ +/***/ (function(module, exports, __webpack_require__) { -/* harmony import */ var _internal_operators_switchMap__WEBPACK_IMPORTED_MODULE_80__ = __webpack_require__(455); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "switchMap", function() { return _internal_operators_switchMap__WEBPACK_IMPORTED_MODULE_80__["switchMap"]; }); +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const NODE_PROCESS_VERSION_PARTS = process.versions.node.split('.'); +const MAJOR_VERSION = parseInt(NODE_PROCESS_VERSION_PARTS[0], 10); +const MINOR_VERSION = parseInt(NODE_PROCESS_VERSION_PARTS[1], 10); +/** + * IS `true` for Node.js 10.10 and greater. + */ +exports.IS_SUPPORT_READDIR_WITH_FILE_TYPES = MAJOR_VERSION > 10 || (MAJOR_VERSION === 10 && MINOR_VERSION >= 10); -/* harmony import */ var _internal_operators_switchMapTo__WEBPACK_IMPORTED_MODULE_81__ = __webpack_require__(456); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "switchMapTo", function() { return _internal_operators_switchMapTo__WEBPACK_IMPORTED_MODULE_81__["switchMapTo"]; }); -/* harmony import */ var _internal_operators_take__WEBPACK_IMPORTED_MODULE_82__ = __webpack_require__(404); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "take", function() { return _internal_operators_take__WEBPACK_IMPORTED_MODULE_82__["take"]; }); +/***/ }), +/* 635 */ +/***/ (function(module, exports, __webpack_require__) { -/* harmony import */ var _internal_operators_takeLast__WEBPACK_IMPORTED_MODULE_83__ = __webpack_require__(417); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "takeLast", function() { return _internal_operators_takeLast__WEBPACK_IMPORTED_MODULE_83__["takeLast"]; }); +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const fs = __webpack_require__(636); +exports.fs = fs; -/* harmony import */ var _internal_operators_takeUntil__WEBPACK_IMPORTED_MODULE_84__ = __webpack_require__(457); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "takeUntil", function() { return _internal_operators_takeUntil__WEBPACK_IMPORTED_MODULE_84__["takeUntil"]; }); -/* harmony import */ var _internal_operators_takeWhile__WEBPACK_IMPORTED_MODULE_85__ = __webpack_require__(458); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "takeWhile", function() { return _internal_operators_takeWhile__WEBPACK_IMPORTED_MODULE_85__["takeWhile"]; }); +/***/ }), +/* 636 */ +/***/ (function(module, exports, __webpack_require__) { -/* harmony import */ var _internal_operators_tap__WEBPACK_IMPORTED_MODULE_86__ = __webpack_require__(459); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "tap", function() { return _internal_operators_tap__WEBPACK_IMPORTED_MODULE_86__["tap"]; }); +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +class DirentFromStats { + constructor(name, stats) { + this.name = name; + this.isBlockDevice = stats.isBlockDevice.bind(stats); + this.isCharacterDevice = stats.isCharacterDevice.bind(stats); + this.isDirectory = stats.isDirectory.bind(stats); + this.isFIFO = stats.isFIFO.bind(stats); + this.isFile = stats.isFile.bind(stats); + this.isSocket = stats.isSocket.bind(stats); + this.isSymbolicLink = stats.isSymbolicLink.bind(stats); + } +} +function createDirentFromStats(name, stats) { + return new DirentFromStats(name, stats); +} +exports.createDirentFromStats = createDirentFromStats; -/* harmony import */ var _internal_operators_throttle__WEBPACK_IMPORTED_MODULE_87__ = __webpack_require__(460); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "throttle", function() { return _internal_operators_throttle__WEBPACK_IMPORTED_MODULE_87__["throttle"]; }); -/* harmony import */ var _internal_operators_throttleTime__WEBPACK_IMPORTED_MODULE_88__ = __webpack_require__(461); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "throttleTime", function() { return _internal_operators_throttleTime__WEBPACK_IMPORTED_MODULE_88__["throttleTime"]; }); +/***/ }), +/* 637 */ +/***/ (function(module, exports, __webpack_require__) { -/* harmony import */ var _internal_operators_throwIfEmpty__WEBPACK_IMPORTED_MODULE_89__ = __webpack_require__(403); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "throwIfEmpty", function() { return _internal_operators_throwIfEmpty__WEBPACK_IMPORTED_MODULE_89__["throwIfEmpty"]; }); +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const fsStat = __webpack_require__(623); +const constants_1 = __webpack_require__(634); +const utils = __webpack_require__(635); +function read(dir, settings) { + if (!settings.stats && constants_1.IS_SUPPORT_READDIR_WITH_FILE_TYPES) { + return readdirWithFileTypes(dir, settings); + } + return readdir(dir, settings); +} +exports.read = read; +function readdirWithFileTypes(dir, settings) { + const dirents = settings.fs.readdirSync(dir, { withFileTypes: true }); + return dirents.map((dirent) => { + const entry = { + dirent, + name: dirent.name, + path: `${dir}${settings.pathSegmentSeparator}${dirent.name}` + }; + if (entry.dirent.isSymbolicLink() && settings.followSymbolicLinks) { + try { + const stats = settings.fs.statSync(entry.path); + entry.dirent = utils.fs.createDirentFromStats(entry.name, stats); + } + catch (error) { + if (settings.throwErrorOnBrokenSymbolicLink) { + throw error; + } + } + } + return entry; + }); +} +exports.readdirWithFileTypes = readdirWithFileTypes; +function readdir(dir, settings) { + const names = settings.fs.readdirSync(dir); + return names.map((name) => { + const entryPath = `${dir}${settings.pathSegmentSeparator}${name}`; + const stats = fsStat.statSync(entryPath, settings.fsStatSettings); + const entry = { + name, + path: entryPath, + dirent: utils.fs.createDirentFromStats(name, stats) + }; + if (settings.stats) { + entry.stats = stats; + } + return entry; + }); +} +exports.readdir = readdir; -/* harmony import */ var _internal_operators_timeInterval__WEBPACK_IMPORTED_MODULE_90__ = __webpack_require__(462); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "timeInterval", function() { return _internal_operators_timeInterval__WEBPACK_IMPORTED_MODULE_90__["timeInterval"]; }); -/* harmony import */ var _internal_operators_timeout__WEBPACK_IMPORTED_MODULE_91__ = __webpack_require__(463); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "timeout", function() { return _internal_operators_timeout__WEBPACK_IMPORTED_MODULE_91__["timeout"]; }); +/***/ }), +/* 638 */ +/***/ (function(module, exports, __webpack_require__) { -/* harmony import */ var _internal_operators_timeoutWith__WEBPACK_IMPORTED_MODULE_92__ = __webpack_require__(464); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "timeoutWith", function() { return _internal_operators_timeoutWith__WEBPACK_IMPORTED_MODULE_92__["timeoutWith"]; }); +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const path = __webpack_require__(16); +const fsStat = __webpack_require__(623); +const fs = __webpack_require__(639); +class Settings { + constructor(_options = {}) { + this._options = _options; + this.followSymbolicLinks = this._getValue(this._options.followSymbolicLinks, false); + this.fs = fs.createFileSystemAdapter(this._options.fs); + this.pathSegmentSeparator = this._getValue(this._options.pathSegmentSeparator, path.sep); + this.stats = this._getValue(this._options.stats, false); + this.throwErrorOnBrokenSymbolicLink = this._getValue(this._options.throwErrorOnBrokenSymbolicLink, true); + this.fsStatSettings = new fsStat.Settings({ + followSymbolicLink: this.followSymbolicLinks, + fs: this.fs, + throwErrorOnBrokenSymbolicLink: this.throwErrorOnBrokenSymbolicLink + }); + } + _getValue(option, value) { + return option === undefined ? value : option; + } +} +exports.default = Settings; -/* harmony import */ var _internal_operators_timestamp__WEBPACK_IMPORTED_MODULE_93__ = __webpack_require__(465); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "timestamp", function() { return _internal_operators_timestamp__WEBPACK_IMPORTED_MODULE_93__["timestamp"]; }); -/* harmony import */ var _internal_operators_toArray__WEBPACK_IMPORTED_MODULE_94__ = __webpack_require__(466); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "toArray", function() { return _internal_operators_toArray__WEBPACK_IMPORTED_MODULE_94__["toArray"]; }); +/***/ }), +/* 639 */ +/***/ (function(module, exports, __webpack_require__) { -/* harmony import */ var _internal_operators_window__WEBPACK_IMPORTED_MODULE_95__ = __webpack_require__(467); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "window", function() { return _internal_operators_window__WEBPACK_IMPORTED_MODULE_95__["window"]; }); +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const fs = __webpack_require__(23); +exports.FILE_SYSTEM_ADAPTER = { + lstat: fs.lstat, + stat: fs.stat, + lstatSync: fs.lstatSync, + statSync: fs.statSync, + readdir: fs.readdir, + readdirSync: fs.readdirSync +}; +function createFileSystemAdapter(fsMethods) { + if (!fsMethods) { + return exports.FILE_SYSTEM_ADAPTER; + } + return Object.assign({}, exports.FILE_SYSTEM_ADAPTER, fsMethods); +} +exports.createFileSystemAdapter = createFileSystemAdapter; -/* harmony import */ var _internal_operators_windowCount__WEBPACK_IMPORTED_MODULE_96__ = __webpack_require__(468); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "windowCount", function() { return _internal_operators_windowCount__WEBPACK_IMPORTED_MODULE_96__["windowCount"]; }); -/* harmony import */ var _internal_operators_windowTime__WEBPACK_IMPORTED_MODULE_97__ = __webpack_require__(469); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "windowTime", function() { return _internal_operators_windowTime__WEBPACK_IMPORTED_MODULE_97__["windowTime"]; }); +/***/ }), +/* 640 */ +/***/ (function(module, exports, __webpack_require__) { -/* harmony import */ var _internal_operators_windowToggle__WEBPACK_IMPORTED_MODULE_98__ = __webpack_require__(470); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "windowToggle", function() { return _internal_operators_windowToggle__WEBPACK_IMPORTED_MODULE_98__["windowToggle"]; }); +"use strict"; -/* harmony import */ var _internal_operators_windowWhen__WEBPACK_IMPORTED_MODULE_99__ = __webpack_require__(471); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "windowWhen", function() { return _internal_operators_windowWhen__WEBPACK_IMPORTED_MODULE_99__["windowWhen"]; }); -/* harmony import */ var _internal_operators_withLatestFrom__WEBPACK_IMPORTED_MODULE_100__ = __webpack_require__(472); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "withLatestFrom", function() { return _internal_operators_withLatestFrom__WEBPACK_IMPORTED_MODULE_100__["withLatestFrom"]; }); +var reusify = __webpack_require__(641) -/* harmony import */ var _internal_operators_zip__WEBPACK_IMPORTED_MODULE_101__ = __webpack_require__(473); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "zip", function() { return _internal_operators_zip__WEBPACK_IMPORTED_MODULE_101__["zip"]; }); +function fastqueue (context, worker, concurrency) { + if (typeof context === 'function') { + concurrency = worker + worker = context + context = null + } -/* harmony import */ var _internal_operators_zipAll__WEBPACK_IMPORTED_MODULE_102__ = __webpack_require__(474); -/* harmony reexport (safe) */ __webpack_require__.d(__webpack_exports__, "zipAll", function() { return _internal_operators_zipAll__WEBPACK_IMPORTED_MODULE_102__["zipAll"]; }); + var cache = reusify(Task) + var queueHead = null + var queueTail = null + var _running = 0 -/** PURE_IMPORTS_START PURE_IMPORTS_END */ + var self = { + push: push, + drain: noop, + saturated: noop, + pause: pause, + paused: false, + concurrency: concurrency, + running: running, + resume: resume, + idle: idle, + length: length, + unshift: unshift, + empty: noop, + kill: kill, + killAndDrain: killAndDrain + } + return self + function running () { + return _running + } + function pause () { + self.paused = true + } + function length () { + var current = queueHead + var counter = 0 + while (current) { + current = current.next + counter++ + } + return counter + } + function resume () { + if (!self.paused) return + self.paused = false + for (var i = 0; i < self.concurrency; i++) { + _running++ + release() + } + } + function idle () { + return _running === 0 && self.length() === 0 + } + function push (value, done) { + var current = cache.get() + current.context = context + current.release = release + current.value = value + current.callback = done || noop + if (_running === self.concurrency || self.paused) { + if (queueTail) { + queueTail.next = current + queueTail = current + } else { + queueHead = current + queueTail = current + self.saturated() + } + } else { + _running++ + worker.call(context, current.value, current.worked) + } + } + function unshift (value, done) { + var current = cache.get() + current.context = context + current.release = release + current.value = value + current.callback = done || noop + if (_running === self.concurrency || self.paused) { + if (queueHead) { + current.next = queueHead + queueHead = current + } else { + queueHead = current + queueTail = current + self.saturated() + } + } else { + _running++ + worker.call(context, current.value, current.worked) + } + } + function release (holder) { + if (holder) { + cache.release(holder) + } + var next = queueHead + if (next) { + if (!self.paused) { + if (queueTail === queueHead) { + queueTail = null + } + queueHead = next.next + next.next = null + worker.call(context, next.value, next.worked) + if (queueTail === null) { + self.empty() + } + } else { + _running-- + } + } else if (--_running === 0) { + self.drain() + } + } + function kill () { + queueHead = null + queueTail = null + self.drain = noop + } + function killAndDrain () { + queueHead = null + queueTail = null + self.drain() + self.drain = noop + } +} +function noop () {} +function Task () { + this.value = null + this.callback = noop + this.next = null + this.release = noop + this.context = null + var self = this + this.worked = function worked (err, result) { + var callback = self.callback + self.value = null + self.callback = noop + callback.call(self.context, err, result) + self.release(self) + } +} +module.exports = fastqueue +/***/ }), +/* 641 */ +/***/ (function(module, exports, __webpack_require__) { +"use strict"; +function reusify (Constructor) { + var head = new Constructor() + var tail = head + function get () { + var current = head + if (current.next) { + head = current.next + } else { + head = new Constructor() + tail = head + } + current.next = null + return current + } + function release (obj) { + tail.next = obj + tail = obj + } + return { + get: get, + release: release + } +} +module.exports = reusify +/***/ }), +/* 642 */ +/***/ (function(module, exports, __webpack_require__) { +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +function isFatalError(settings, error) { + if (settings.errorFilter === null) { + return true; + } + return !settings.errorFilter(error); +} +exports.isFatalError = isFatalError; +function isAppliedFilter(filter, value) { + return filter === null || filter(value); +} +exports.isAppliedFilter = isAppliedFilter; +function replacePathSegmentSeparator(filepath, separator) { + return filepath.split(/[\\\/]/).join(separator); +} +exports.replacePathSegmentSeparator = replacePathSegmentSeparator; +function joinPathSegments(a, b, separator) { + if (a === '') { + return b; + } + return a + separator + b; +} +exports.joinPathSegments = joinPathSegments; +/***/ }), +/* 643 */ +/***/ (function(module, exports, __webpack_require__) { +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const common = __webpack_require__(642); +class Reader { + constructor(_root, _settings) { + this._root = _root; + this._settings = _settings; + this._root = common.replacePathSegmentSeparator(_root, _settings.pathSegmentSeparator); + } +} +exports.default = Reader; +/***/ }), +/* 644 */ +/***/ (function(module, exports, __webpack_require__) { +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const stream_1 = __webpack_require__(27); +const async_1 = __webpack_require__(630); +class StreamProvider { + constructor(_root, _settings) { + this._root = _root; + this._settings = _settings; + this._reader = new async_1.default(this._root, this._settings); + this._stream = new stream_1.Readable({ + objectMode: true, + read: () => { }, + destroy: this._reader.destroy.bind(this._reader) + }); + } + read() { + this._reader.onError((error) => { + this._stream.emit('error', error); + }); + this._reader.onEntry((entry) => { + this._stream.push(entry); + }); + this._reader.onEnd(() => { + this._stream.push(null); + }); + this._reader.read(); + return this._stream; + } +} +exports.default = StreamProvider; +/***/ }), +/* 645 */ +/***/ (function(module, exports, __webpack_require__) { +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const sync_1 = __webpack_require__(646); +class SyncProvider { + constructor(_root, _settings) { + this._root = _root; + this._settings = _settings; + this._reader = new sync_1.default(this._root, this._settings); + } + read() { + return this._reader.read(); + } +} +exports.default = SyncProvider; +/***/ }), +/* 646 */ +/***/ (function(module, exports, __webpack_require__) { +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const fsScandir = __webpack_require__(631); +const common = __webpack_require__(642); +const reader_1 = __webpack_require__(643); +class SyncReader extends reader_1.default { + constructor() { + super(...arguments); + this._scandir = fsScandir.scandirSync; + this._storage = new Set(); + this._queue = new Set(); + } + read() { + this._pushToQueue(this._root, this._settings.basePath); + this._handleQueue(); + return Array.from(this._storage); + } + _pushToQueue(dir, base) { + this._queue.add({ dir, base }); + } + _handleQueue() { + for (const item of this._queue.values()) { + this._handleDirectory(item.dir, item.base); + } + } + _handleDirectory(dir, base) { + try { + const entries = this._scandir(dir, this._settings.fsScandirSettings); + for (const entry of entries) { + this._handleEntry(entry, base); + } + } + catch (error) { + this._handleError(error); + } + } + _handleError(error) { + if (!common.isFatalError(this._settings, error)) { + return; + } + throw error; + } + _handleEntry(entry, base) { + const fullpath = entry.path; + if (base !== undefined) { + entry.path = common.joinPathSegments(base, entry.name, this._settings.pathSegmentSeparator); + } + if (common.isAppliedFilter(this._settings.entryFilter, entry)) { + this._pushToStorage(entry); + } + if (entry.dirent.isDirectory() && common.isAppliedFilter(this._settings.deepFilter, entry)) { + this._pushToQueue(fullpath, entry.path); + } + } + _pushToStorage(entry) { + this._storage.add(entry); + } +} +exports.default = SyncReader; +/***/ }), +/* 647 */ +/***/ (function(module, exports, __webpack_require__) { +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const path = __webpack_require__(16); +const fsScandir = __webpack_require__(631); +class Settings { + constructor(_options = {}) { + this._options = _options; + this.basePath = this._getValue(this._options.basePath, undefined); + this.concurrency = this._getValue(this._options.concurrency, Infinity); + this.deepFilter = this._getValue(this._options.deepFilter, null); + this.entryFilter = this._getValue(this._options.entryFilter, null); + this.errorFilter = this._getValue(this._options.errorFilter, null); + this.pathSegmentSeparator = this._getValue(this._options.pathSegmentSeparator, path.sep); + this.fsScandirSettings = new fsScandir.Settings({ + followSymbolicLinks: this._options.followSymbolicLinks, + fs: this._options.fs, + pathSegmentSeparator: this._options.pathSegmentSeparator, + stats: this._options.stats, + throwErrorOnBrokenSymbolicLink: this._options.throwErrorOnBrokenSymbolicLink + }); + } + _getValue(option, value) { + return option === undefined ? value : option; + } +} +exports.default = Settings; +/***/ }), +/* 648 */ +/***/ (function(module, exports, __webpack_require__) { +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const path = __webpack_require__(16); +const fsStat = __webpack_require__(623); +const utils = __webpack_require__(594); +class Reader { + constructor(_settings) { + this._settings = _settings; + this._fsStatSettings = new fsStat.Settings({ + followSymbolicLink: this._settings.followSymbolicLinks, + fs: this._settings.fs, + throwErrorOnBrokenSymbolicLink: this._settings.followSymbolicLinks + }); + } + _getFullEntryPath(filepath) { + return path.resolve(this._settings.cwd, filepath); + } + _makeEntry(stats, pattern) { + const entry = { + name: pattern, + path: pattern, + dirent: utils.fs.createDirentFromStats(pattern, stats) + }; + if (this._settings.stats) { + entry.stats = stats; + } + return entry; + } + _isFatalError(error) { + return !utils.errno.isEnoentCodeError(error) && !this._settings.suppressErrors; + } +} +exports.default = Reader; +/***/ }), +/* 649 */ +/***/ (function(module, exports, __webpack_require__) { +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const path = __webpack_require__(16); +const deep_1 = __webpack_require__(650); +const entry_1 = __webpack_require__(651); +const error_1 = __webpack_require__(652); +const entry_2 = __webpack_require__(653); +class Provider { + constructor(_settings) { + this._settings = _settings; + this.errorFilter = new error_1.default(this._settings); + this.entryFilter = new entry_1.default(this._settings, this._getMicromatchOptions()); + this.deepFilter = new deep_1.default(this._settings, this._getMicromatchOptions()); + this.entryTransformer = new entry_2.default(this._settings); + } + _getRootDirectory(task) { + return path.resolve(this._settings.cwd, task.base); + } + _getReaderOptions(task) { + const basePath = task.base === '.' ? '' : task.base; + return { + basePath, + pathSegmentSeparator: '/', + concurrency: this._settings.concurrency, + deepFilter: this.deepFilter.getFilter(basePath, task.positive, task.negative), + entryFilter: this.entryFilter.getFilter(task.positive, task.negative), + errorFilter: this.errorFilter.getFilter(), + followSymbolicLinks: this._settings.followSymbolicLinks, + fs: this._settings.fs, + stats: this._settings.stats, + throwErrorOnBrokenSymbolicLink: this._settings.throwErrorOnBrokenSymbolicLink, + transform: this.entryTransformer.getTransformer() + }; + } + _getMicromatchOptions() { + return { + dot: this._settings.dot, + matchBase: this._settings.baseNameMatch, + nobrace: !this._settings.braceExpansion, + nocase: !this._settings.caseSensitiveMatch, + noext: !this._settings.extglob, + noglobstar: !this._settings.globstar, + posix: true, + strictSlashes: false + }; + } +} +exports.default = Provider; +/***/ }), +/* 650 */ +/***/ (function(module, exports, __webpack_require__) { +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const utils = __webpack_require__(594); +class DeepFilter { + constructor(_settings, _micromatchOptions) { + this._settings = _settings; + this._micromatchOptions = _micromatchOptions; + } + getFilter(basePath, positive, negative) { + const maxPatternDepth = this._getMaxPatternDepth(positive); + const negativeRe = this._getNegativePatternsRe(negative); + return (entry) => this._filter(basePath, entry, negativeRe, maxPatternDepth); + } + _getMaxPatternDepth(patterns) { + const globstar = patterns.some(utils.pattern.hasGlobStar); + return globstar ? Infinity : utils.pattern.getMaxNaivePatternsDepth(patterns); + } + _getNegativePatternsRe(patterns) { + const affectDepthOfReadingPatterns = patterns.filter(utils.pattern.isAffectDepthOfReadingPattern); + return utils.pattern.convertPatternsToRe(affectDepthOfReadingPatterns, this._micromatchOptions); + } + _filter(basePath, entry, negativeRe, maxPatternDepth) { + const depth = this._getEntryDepth(basePath, entry.path); + if (this._isSkippedByDeep(depth)) { + return false; + } + if (this._isSkippedByMaxPatternDepth(depth, maxPatternDepth)) { + return false; + } + if (this._isSkippedSymbolicLink(entry)) { + return false; + } + if (this._isSkippedDotDirectory(entry)) { + return false; + } + return this._isSkippedByNegativePatterns(entry, negativeRe); + } + _getEntryDepth(basePath, entryPath) { + const basePathDepth = basePath.split('/').length; + const entryPathDepth = entryPath.split('/').length; + return entryPathDepth - (basePath === '' ? 0 : basePathDepth); + } + _isSkippedByDeep(entryDepth) { + return entryDepth >= this._settings.deep; + } + _isSkippedByMaxPatternDepth(entryDepth, maxPatternDepth) { + return !this._settings.baseNameMatch && maxPatternDepth !== Infinity && entryDepth > maxPatternDepth; + } + _isSkippedSymbolicLink(entry) { + return !this._settings.followSymbolicLinks && entry.dirent.isSymbolicLink(); + } + _isSkippedDotDirectory(entry) { + return !this._settings.dot && entry.name.startsWith('.'); + } + _isSkippedByNegativePatterns(entry, negativeRe) { + return !utils.pattern.matchAny(entry.path, negativeRe); + } +} +exports.default = DeepFilter; +/***/ }), +/* 651 */ +/***/ (function(module, exports, __webpack_require__) { +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const utils = __webpack_require__(594); +class EntryFilter { + constructor(_settings, _micromatchOptions) { + this._settings = _settings; + this._micromatchOptions = _micromatchOptions; + this.index = new Map(); + } + getFilter(positive, negative) { + const positiveRe = utils.pattern.convertPatternsToRe(positive, this._micromatchOptions); + const negativeRe = utils.pattern.convertPatternsToRe(negative, this._micromatchOptions); + return (entry) => this._filter(entry, positiveRe, negativeRe); + } + _filter(entry, positiveRe, negativeRe) { + if (this._settings.unique) { + if (this._isDuplicateEntry(entry)) { + return false; + } + this._createIndexRecord(entry); + } + if (this._onlyFileFilter(entry) || this._onlyDirectoryFilter(entry)) { + return false; + } + if (this._isSkippedByAbsoluteNegativePatterns(entry, negativeRe)) { + return false; + } + const filepath = this._settings.baseNameMatch ? entry.name : entry.path; + return this._isMatchToPatterns(filepath, positiveRe) && !this._isMatchToPatterns(entry.path, negativeRe); + } + _isDuplicateEntry(entry) { + return this.index.has(entry.path); + } + _createIndexRecord(entry) { + this.index.set(entry.path, undefined); + } + _onlyFileFilter(entry) { + return this._settings.onlyFiles && !entry.dirent.isFile(); + } + _onlyDirectoryFilter(entry) { + return this._settings.onlyDirectories && !entry.dirent.isDirectory(); + } + _isSkippedByAbsoluteNegativePatterns(entry, negativeRe) { + if (!this._settings.absolute) { + return false; + } + const fullpath = utils.path.makeAbsolute(this._settings.cwd, entry.path); + return this._isMatchToPatterns(fullpath, negativeRe); + } + _isMatchToPatterns(filepath, patternsRe) { + return utils.pattern.matchAny(filepath, patternsRe); + } +} +exports.default = EntryFilter; +/***/ }), +/* 652 */ +/***/ (function(module, exports, __webpack_require__) { +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const utils = __webpack_require__(594); +class ErrorFilter { + constructor(_settings) { + this._settings = _settings; + } + getFilter() { + return (error) => this._isNonFatalError(error); + } + _isNonFatalError(error) { + return utils.errno.isEnoentCodeError(error) || this._settings.suppressErrors; + } +} +exports.default = ErrorFilter; +/***/ }), +/* 653 */ +/***/ (function(module, exports, __webpack_require__) { +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const utils = __webpack_require__(594); +class EntryTransformer { + constructor(_settings) { + this._settings = _settings; + } + getTransformer() { + return (entry) => this._transform(entry); + } + _transform(entry) { + let filepath = entry.path; + if (this._settings.absolute) { + filepath = utils.path.makeAbsolute(this._settings.cwd, filepath); + filepath = utils.path.unixify(filepath); + } + if (this._settings.markDirectories && entry.dirent.isDirectory()) { + filepath += '/'; + } + if (!this._settings.objectMode) { + return filepath; + } + return Object.assign({}, entry, { path: filepath }); + } +} +exports.default = EntryTransformer; +/***/ }), +/* 654 */ +/***/ (function(module, exports, __webpack_require__) { +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const stream_1 = __webpack_require__(27); +const stream_2 = __webpack_require__(622); +const provider_1 = __webpack_require__(649); +class ProviderStream extends provider_1.default { + constructor() { + super(...arguments); + this._reader = new stream_2.default(this._settings); + } + read(task) { + const root = this._getRootDirectory(task); + const options = this._getReaderOptions(task); + const source = this.api(root, task, options); + const dest = new stream_1.Readable({ objectMode: true, read: () => { } }); + source + .once('error', (error) => dest.emit('error', error)) + .on('data', (entry) => dest.emit('data', options.transform(entry))) + .once('end', () => dest.emit('end')); + return dest; + } + api(root, task, options) { + if (task.dynamic) { + return this._reader.dynamic(root, options); + } + return this._reader.static(task.patterns, options); + } +} +exports.default = ProviderStream; +/***/ }), +/* 655 */ +/***/ (function(module, exports, __webpack_require__) { +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const sync_1 = __webpack_require__(656); +const provider_1 = __webpack_require__(649); +class ProviderSync extends provider_1.default { + constructor() { + super(...arguments); + this._reader = new sync_1.default(this._settings); + } + read(task) { + const root = this._getRootDirectory(task); + const options = this._getReaderOptions(task); + const entries = this.api(root, task, options); + return entries.map(options.transform); + } + api(root, task, options) { + if (task.dynamic) { + return this._reader.dynamic(root, options); + } + return this._reader.static(task.patterns, options); + } +} +exports.default = ProviderSync; +/***/ }), +/* 656 */ +/***/ (function(module, exports, __webpack_require__) { +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const fsStat = __webpack_require__(623); +const fsWalk = __webpack_require__(628); +const reader_1 = __webpack_require__(648); +class ReaderSync extends reader_1.default { + constructor() { + super(...arguments); + this._walkSync = fsWalk.walkSync; + this._statSync = fsStat.statSync; + } + dynamic(root, options) { + return this._walkSync(root, options); + } + static(patterns, options) { + const entries = []; + for (const pattern of patterns) { + const filepath = this._getFullEntryPath(pattern); + const entry = this._getEntry(filepath, pattern, options); + if (entry === null || !options.entryFilter(entry)) { + continue; + } + entries.push(entry); + } + return entries; + } + _getEntry(filepath, pattern, options) { + try { + const stats = this._getStat(filepath); + return this._makeEntry(stats, pattern); + } + catch (error) { + if (options.errorFilter(error)) { + return null; + } + throw error; + } + } + _getStat(filepath) { + return this._statSync(filepath, this._fsStatSettings); + } +} +exports.default = ReaderSync; +/***/ }), +/* 657 */ +/***/ (function(module, exports, __webpack_require__) { +"use strict"; + +Object.defineProperty(exports, "__esModule", { value: true }); +const fs = __webpack_require__(23); +const os = __webpack_require__(11); +const CPU_COUNT = os.cpus().length; +exports.DEFAULT_FILE_SYSTEM_ADAPTER = { + lstat: fs.lstat, + lstatSync: fs.lstatSync, + stat: fs.stat, + statSync: fs.statSync, + readdir: fs.readdir, + readdirSync: fs.readdirSync +}; +// tslint:enable no-redundant-jsdoc +class Settings { + constructor(_options = {}) { + this._options = _options; + this.absolute = this._getValue(this._options.absolute, false); + this.baseNameMatch = this._getValue(this._options.baseNameMatch, false); + this.braceExpansion = this._getValue(this._options.braceExpansion, true); + this.caseSensitiveMatch = this._getValue(this._options.caseSensitiveMatch, true); + this.concurrency = this._getValue(this._options.concurrency, CPU_COUNT); + this.cwd = this._getValue(this._options.cwd, process.cwd()); + this.deep = this._getValue(this._options.deep, Infinity); + this.dot = this._getValue(this._options.dot, false); + this.extglob = this._getValue(this._options.extglob, true); + this.followSymbolicLinks = this._getValue(this._options.followSymbolicLinks, true); + this.fs = this._getFileSystemMethods(this._options.fs); + this.globstar = this._getValue(this._options.globstar, true); + this.ignore = this._getValue(this._options.ignore, []); + this.markDirectories = this._getValue(this._options.markDirectories, false); + this.objectMode = this._getValue(this._options.objectMode, false); + this.onlyDirectories = this._getValue(this._options.onlyDirectories, false); + this.onlyFiles = this._getValue(this._options.onlyFiles, true); + this.stats = this._getValue(this._options.stats, false); + this.suppressErrors = this._getValue(this._options.suppressErrors, false); + this.throwErrorOnBrokenSymbolicLink = this._getValue(this._options.throwErrorOnBrokenSymbolicLink, false); + this.unique = this._getValue(this._options.unique, true); + if (this.onlyDirectories) { + this.onlyFiles = false; + } + if (this.stats) { + this.objectMode = true; + } + } + _getValue(option, value) { + return option === undefined ? value : option; + } + _getFileSystemMethods(methods = {}) { + return Object.assign({}, exports.DEFAULT_FILE_SYSTEM_ADAPTER, methods); + } +} +exports.default = Settings; +/***/ }), +/* 658 */ +/***/ (function(module, exports, __webpack_require__) { +"use strict"; +const path = __webpack_require__(16); +const pathType = __webpack_require__(659); +const getExtensions = extensions => extensions.length > 1 ? `{${extensions.join(',')}}` : extensions[0]; +const getPath = (filepath, cwd) => { + const pth = filepath[0] === '!' ? filepath.slice(1) : filepath; + return path.isAbsolute(pth) ? pth : path.join(cwd, pth); +}; +const addExtensions = (file, extensions) => { + if (path.extname(file)) { + return `**/${file}`; + } + return `**/${file}.${getExtensions(extensions)}`; +}; +const getGlob = (directory, options) => { + if (options.files && !Array.isArray(options.files)) { + throw new TypeError(`Expected \`files\` to be of type \`Array\` but received type \`${typeof options.files}\``); + } + if (options.extensions && !Array.isArray(options.extensions)) { + throw new TypeError(`Expected \`extensions\` to be of type \`Array\` but received type \`${typeof options.extensions}\``); + } + if (options.files && options.extensions) { + return options.files.map(x => path.posix.join(directory, addExtensions(x, options.extensions))); + } + if (options.files) { + return options.files.map(x => path.posix.join(directory, `**/${x}`)); + } + if (options.extensions) { + return [path.posix.join(directory, `**/*.${getExtensions(options.extensions)}`)]; + } + return [path.posix.join(directory, '**')]; +}; +module.exports = async (input, options) => { + options = { + cwd: process.cwd(), + ...options + }; + if (typeof options.cwd !== 'string') { + throw new TypeError(`Expected \`cwd\` to be of type \`string\` but received type \`${typeof options.cwd}\``); + } + const globs = await Promise.all([].concat(input).map(async x => { + const isDirectory = await pathType.isDirectory(getPath(x, options.cwd)); + return isDirectory ? getGlob(x, options) : x; + })); + return [].concat.apply([], globs); // eslint-disable-line prefer-spread +}; +module.exports.sync = (input, options) => { + options = { + cwd: process.cwd(), + ...options + }; + if (typeof options.cwd !== 'string') { + throw new TypeError(`Expected \`cwd\` to be of type \`string\` but received type \`${typeof options.cwd}\``); + } + const globs = [].concat(input).map(x => pathType.isDirectorySync(getPath(x, options.cwd)) ? getGlob(x, options) : x); -//# sourceMappingURL=index.js.map + return [].concat.apply([], globs); // eslint-disable-line prefer-spread +}; /***/ }), -/* 378 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/* 659 */ +/***/ (function(module, exports, __webpack_require__) { "use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "audit", function() { return audit; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(336); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(337); -/** PURE_IMPORTS_START tslib,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ - - - -function audit(durationSelector) { - return function auditOperatorFunction(source) { - return source.lift(new AuditOperator(durationSelector)); - }; -} -var AuditOperator = /*@__PURE__*/ (function () { - function AuditOperator(durationSelector) { - this.durationSelector = durationSelector; - } - AuditOperator.prototype.call = function (subscriber, source) { - return source.subscribe(new AuditSubscriber(subscriber, this.durationSelector)); - }; - return AuditOperator; -}()); -var AuditSubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](AuditSubscriber, _super); - function AuditSubscriber(destination, durationSelector) { - var _this = _super.call(this, destination) || this; - _this.durationSelector = durationSelector; - _this.hasValue = false; - return _this; - } - AuditSubscriber.prototype._next = function (value) { - this.value = value; - this.hasValue = true; - if (!this.throttled) { - var duration = void 0; - try { - var durationSelector = this.durationSelector; - duration = durationSelector(value); - } - catch (err) { - return this.destination.error(err); - } - var innerSubscription = Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__["subscribeToResult"])(this, duration); - if (!innerSubscription || innerSubscription.closed) { - this.clearThrottle(); - } - else { - this.add(this.throttled = innerSubscription); - } - } - }; - AuditSubscriber.prototype.clearThrottle = function () { - var _a = this, value = _a.value, hasValue = _a.hasValue, throttled = _a.throttled; - if (throttled) { - this.remove(throttled); - this.throttled = null; - throttled.unsubscribe(); - } - if (hasValue) { - this.value = null; - this.hasValue = false; - this.destination.next(value); - } - }; - AuditSubscriber.prototype.notifyNext = function (outerValue, innerValue, outerIndex, innerIndex) { - this.clearThrottle(); - }; - AuditSubscriber.prototype.notifyComplete = function () { - this.clearThrottle(); - }; - return AuditSubscriber; -}(_OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__["OuterSubscriber"])); -//# sourceMappingURL=audit.js.map +const {promisify} = __webpack_require__(29); +const fs = __webpack_require__(23); -/***/ }), -/* 379 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { - -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "auditTime", function() { return auditTime; }); -/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(322); -/* harmony import */ var _audit__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(378); -/* harmony import */ var _observable_timer__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(374); -/** PURE_IMPORTS_START _scheduler_async,_audit,_observable_timer PURE_IMPORTS_END */ - +async function isType(fsStatType, statsMethodName, filePath) { + if (typeof filePath !== 'string') { + throw new TypeError(`Expected a string, got ${typeof filePath}`); + } + try { + const stats = await promisify(fs[fsStatType])(filePath); + return stats[statsMethodName](); + } catch (error) { + if (error.code === 'ENOENT') { + return false; + } -function auditTime(duration, scheduler) { - if (scheduler === void 0) { - scheduler = _scheduler_async__WEBPACK_IMPORTED_MODULE_0__["async"]; - } - return Object(_audit__WEBPACK_IMPORTED_MODULE_1__["audit"])(function () { return Object(_observable_timer__WEBPACK_IMPORTED_MODULE_2__["timer"])(duration, scheduler); }); + throw error; + } } -//# sourceMappingURL=auditTime.js.map - - -/***/ }), -/* 380 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { - -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "buffer", function() { return buffer; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(336); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(337); -/** PURE_IMPORTS_START tslib,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ +function isTypeSync(fsStatType, statsMethodName, filePath) { + if (typeof filePath !== 'string') { + throw new TypeError(`Expected a string, got ${typeof filePath}`); + } + try { + return fs[fsStatType](filePath)[statsMethodName](); + } catch (error) { + if (error.code === 'ENOENT') { + return false; + } -function buffer(closingNotifier) { - return function bufferOperatorFunction(source) { - return source.lift(new BufferOperator(closingNotifier)); - }; + throw error; + } } -var BufferOperator = /*@__PURE__*/ (function () { - function BufferOperator(closingNotifier) { - this.closingNotifier = closingNotifier; - } - BufferOperator.prototype.call = function (subscriber, source) { - return source.subscribe(new BufferSubscriber(subscriber, this.closingNotifier)); - }; - return BufferOperator; -}()); -var BufferSubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](BufferSubscriber, _super); - function BufferSubscriber(destination, closingNotifier) { - var _this = _super.call(this, destination) || this; - _this.buffer = []; - _this.add(Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__["subscribeToResult"])(_this, closingNotifier)); - return _this; - } - BufferSubscriber.prototype._next = function (value) { - this.buffer.push(value); - }; - BufferSubscriber.prototype.notifyNext = function (outerValue, innerValue, outerIndex, innerIndex, innerSub) { - var buffer = this.buffer; - this.buffer = []; - this.destination.next(buffer); - }; - return BufferSubscriber; -}(_OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__["OuterSubscriber"])); -//# sourceMappingURL=buffer.js.map - - -/***/ }), -/* 381 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { - -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "bufferCount", function() { return bufferCount; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); -/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ - -function bufferCount(bufferSize, startBufferEvery) { - if (startBufferEvery === void 0) { - startBufferEvery = null; - } - return function bufferCountOperatorFunction(source) { - return source.lift(new BufferCountOperator(bufferSize, startBufferEvery)); - }; -} -var BufferCountOperator = /*@__PURE__*/ (function () { - function BufferCountOperator(bufferSize, startBufferEvery) { - this.bufferSize = bufferSize; - this.startBufferEvery = startBufferEvery; - if (!startBufferEvery || bufferSize === startBufferEvery) { - this.subscriberClass = BufferCountSubscriber; - } - else { - this.subscriberClass = BufferSkipCountSubscriber; - } - } - BufferCountOperator.prototype.call = function (subscriber, source) { - return source.subscribe(new this.subscriberClass(subscriber, this.bufferSize, this.startBufferEvery)); - }; - return BufferCountOperator; -}()); -var BufferCountSubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](BufferCountSubscriber, _super); - function BufferCountSubscriber(destination, bufferSize) { - var _this = _super.call(this, destination) || this; - _this.bufferSize = bufferSize; - _this.buffer = []; - return _this; - } - BufferCountSubscriber.prototype._next = function (value) { - var buffer = this.buffer; - buffer.push(value); - if (buffer.length == this.bufferSize) { - this.destination.next(buffer); - this.buffer = []; - } - }; - BufferCountSubscriber.prototype._complete = function () { - var buffer = this.buffer; - if (buffer.length > 0) { - this.destination.next(buffer); - } - _super.prototype._complete.call(this); - }; - return BufferCountSubscriber; -}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); -var BufferSkipCountSubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](BufferSkipCountSubscriber, _super); - function BufferSkipCountSubscriber(destination, bufferSize, startBufferEvery) { - var _this = _super.call(this, destination) || this; - _this.bufferSize = bufferSize; - _this.startBufferEvery = startBufferEvery; - _this.buffers = []; - _this.count = 0; - return _this; - } - BufferSkipCountSubscriber.prototype._next = function (value) { - var _a = this, bufferSize = _a.bufferSize, startBufferEvery = _a.startBufferEvery, buffers = _a.buffers, count = _a.count; - this.count++; - if (count % startBufferEvery === 0) { - buffers.push([]); - } - for (var i = buffers.length; i--;) { - var buffer = buffers[i]; - buffer.push(value); - if (buffer.length === bufferSize) { - buffers.splice(i, 1); - this.destination.next(buffer); - } - } - }; - BufferSkipCountSubscriber.prototype._complete = function () { - var _a = this, buffers = _a.buffers, destination = _a.destination; - while (buffers.length > 0) { - var buffer = buffers.shift(); - if (buffer.length > 0) { - destination.next(buffer); - } - } - _super.prototype._complete.call(this); - }; - return BufferSkipCountSubscriber; -}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); -//# sourceMappingURL=bufferCount.js.map +exports.isFile = isType.bind(null, 'stat', 'isFile'); +exports.isDirectory = isType.bind(null, 'stat', 'isDirectory'); +exports.isSymlink = isType.bind(null, 'lstat', 'isSymbolicLink'); +exports.isFileSync = isTypeSync.bind(null, 'statSync', 'isFile'); +exports.isDirectorySync = isTypeSync.bind(null, 'statSync', 'isDirectory'); +exports.isSymlinkSync = isTypeSync.bind(null, 'lstatSync', 'isSymbolicLink'); /***/ }), -/* 382 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/* 660 */ +/***/ (function(module, exports, __webpack_require__) { "use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "bufferTime", function() { return bufferTime; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(322); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(278); -/* harmony import */ var _util_isScheduler__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(312); -/** PURE_IMPORTS_START tslib,_scheduler_async,_Subscriber,_util_isScheduler PURE_IMPORTS_END */ - +const {promisify} = __webpack_require__(29); +const fs = __webpack_require__(23); +const path = __webpack_require__(16); +const fastGlob = __webpack_require__(592); +const gitIgnore = __webpack_require__(661); +const slash = __webpack_require__(662); +const DEFAULT_IGNORE = [ + '**/node_modules/**', + '**/flow-typed/**', + '**/coverage/**', + '**/.git' +]; -function bufferTime(bufferTimeSpan) { - var length = arguments.length; - var scheduler = _scheduler_async__WEBPACK_IMPORTED_MODULE_1__["async"]; - if (Object(_util_isScheduler__WEBPACK_IMPORTED_MODULE_3__["isScheduler"])(arguments[arguments.length - 1])) { - scheduler = arguments[arguments.length - 1]; - length--; - } - var bufferCreationInterval = null; - if (length >= 2) { - bufferCreationInterval = arguments[1]; - } - var maxBufferSize = Number.POSITIVE_INFINITY; - if (length >= 3) { - maxBufferSize = arguments[2]; - } - return function bufferTimeOperatorFunction(source) { - return source.lift(new BufferTimeOperator(bufferTimeSpan, bufferCreationInterval, maxBufferSize, scheduler)); - }; -} -var BufferTimeOperator = /*@__PURE__*/ (function () { - function BufferTimeOperator(bufferTimeSpan, bufferCreationInterval, maxBufferSize, scheduler) { - this.bufferTimeSpan = bufferTimeSpan; - this.bufferCreationInterval = bufferCreationInterval; - this.maxBufferSize = maxBufferSize; - this.scheduler = scheduler; - } - BufferTimeOperator.prototype.call = function (subscriber, source) { - return source.subscribe(new BufferTimeSubscriber(subscriber, this.bufferTimeSpan, this.bufferCreationInterval, this.maxBufferSize, this.scheduler)); - }; - return BufferTimeOperator; -}()); -var Context = /*@__PURE__*/ (function () { - function Context() { - this.buffer = []; - } - return Context; -}()); -var BufferTimeSubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](BufferTimeSubscriber, _super); - function BufferTimeSubscriber(destination, bufferTimeSpan, bufferCreationInterval, maxBufferSize, scheduler) { - var _this = _super.call(this, destination) || this; - _this.bufferTimeSpan = bufferTimeSpan; - _this.bufferCreationInterval = bufferCreationInterval; - _this.maxBufferSize = maxBufferSize; - _this.scheduler = scheduler; - _this.contexts = []; - var context = _this.openContext(); - _this.timespanOnly = bufferCreationInterval == null || bufferCreationInterval < 0; - if (_this.timespanOnly) { - var timeSpanOnlyState = { subscriber: _this, context: context, bufferTimeSpan: bufferTimeSpan }; - _this.add(context.closeAction = scheduler.schedule(dispatchBufferTimeSpanOnly, bufferTimeSpan, timeSpanOnlyState)); - } - else { - var closeState = { subscriber: _this, context: context }; - var creationState = { bufferTimeSpan: bufferTimeSpan, bufferCreationInterval: bufferCreationInterval, subscriber: _this, scheduler: scheduler }; - _this.add(context.closeAction = scheduler.schedule(dispatchBufferClose, bufferTimeSpan, closeState)); - _this.add(scheduler.schedule(dispatchBufferCreation, bufferCreationInterval, creationState)); - } - return _this; - } - BufferTimeSubscriber.prototype._next = function (value) { - var contexts = this.contexts; - var len = contexts.length; - var filledBufferContext; - for (var i = 0; i < len; i++) { - var context_1 = contexts[i]; - var buffer = context_1.buffer; - buffer.push(value); - if (buffer.length == this.maxBufferSize) { - filledBufferContext = context_1; - } - } - if (filledBufferContext) { - this.onBufferFull(filledBufferContext); - } - }; - BufferTimeSubscriber.prototype._error = function (err) { - this.contexts.length = 0; - _super.prototype._error.call(this, err); - }; - BufferTimeSubscriber.prototype._complete = function () { - var _a = this, contexts = _a.contexts, destination = _a.destination; - while (contexts.length > 0) { - var context_2 = contexts.shift(); - destination.next(context_2.buffer); - } - _super.prototype._complete.call(this); - }; - BufferTimeSubscriber.prototype._unsubscribe = function () { - this.contexts = null; - }; - BufferTimeSubscriber.prototype.onBufferFull = function (context) { - this.closeContext(context); - var closeAction = context.closeAction; - closeAction.unsubscribe(); - this.remove(closeAction); - if (!this.closed && this.timespanOnly) { - context = this.openContext(); - var bufferTimeSpan = this.bufferTimeSpan; - var timeSpanOnlyState = { subscriber: this, context: context, bufferTimeSpan: bufferTimeSpan }; - this.add(context.closeAction = this.scheduler.schedule(dispatchBufferTimeSpanOnly, bufferTimeSpan, timeSpanOnlyState)); - } - }; - BufferTimeSubscriber.prototype.openContext = function () { - var context = new Context(); - this.contexts.push(context); - return context; - }; - BufferTimeSubscriber.prototype.closeContext = function (context) { - this.destination.next(context.buffer); - var contexts = this.contexts; - var spliceIndex = contexts ? contexts.indexOf(context) : -1; - if (spliceIndex >= 0) { - contexts.splice(contexts.indexOf(context), 1); - } - }; - return BufferTimeSubscriber; -}(_Subscriber__WEBPACK_IMPORTED_MODULE_2__["Subscriber"])); -function dispatchBufferTimeSpanOnly(state) { - var subscriber = state.subscriber; - var prevContext = state.context; - if (prevContext) { - subscriber.closeContext(prevContext); - } - if (!subscriber.closed) { - state.context = subscriber.openContext(); - state.context.closeAction = this.schedule(state, state.bufferTimeSpan); - } -} -function dispatchBufferCreation(state) { - var bufferCreationInterval = state.bufferCreationInterval, bufferTimeSpan = state.bufferTimeSpan, subscriber = state.subscriber, scheduler = state.scheduler; - var context = subscriber.openContext(); - var action = this; - if (!subscriber.closed) { - subscriber.add(context.closeAction = scheduler.schedule(dispatchBufferClose, bufferTimeSpan, { subscriber: subscriber, context: context })); - action.schedule(state, bufferCreationInterval); - } -} -function dispatchBufferClose(arg) { - var subscriber = arg.subscriber, context = arg.context; - subscriber.closeContext(context); -} -//# sourceMappingURL=bufferTime.js.map - - -/***/ }), -/* 383 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +const readFileP = promisify(fs.readFile); -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "bufferToggle", function() { return bufferToggle; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(284); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(337); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(336); -/** PURE_IMPORTS_START tslib,_Subscription,_util_subscribeToResult,_OuterSubscriber PURE_IMPORTS_END */ +const mapGitIgnorePatternTo = base => ignore => { + if (ignore.startsWith('!')) { + return '!' + path.posix.join(base, ignore.slice(1)); + } + return path.posix.join(base, ignore); +}; +const parseGitIgnore = (content, options) => { + const base = slash(path.relative(options.cwd, path.dirname(options.fileName))); + return content + .split(/\r?\n/) + .filter(Boolean) + .filter(line => !line.startsWith('#')) + .map(mapGitIgnorePatternTo(base)); +}; -function bufferToggle(openings, closingSelector) { - return function bufferToggleOperatorFunction(source) { - return source.lift(new BufferToggleOperator(openings, closingSelector)); - }; -} -var BufferToggleOperator = /*@__PURE__*/ (function () { - function BufferToggleOperator(openings, closingSelector) { - this.openings = openings; - this.closingSelector = closingSelector; - } - BufferToggleOperator.prototype.call = function (subscriber, source) { - return source.subscribe(new BufferToggleSubscriber(subscriber, this.openings, this.closingSelector)); - }; - return BufferToggleOperator; -}()); -var BufferToggleSubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](BufferToggleSubscriber, _super); - function BufferToggleSubscriber(destination, openings, closingSelector) { - var _this = _super.call(this, destination) || this; - _this.openings = openings; - _this.closingSelector = closingSelector; - _this.contexts = []; - _this.add(Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__["subscribeToResult"])(_this, openings)); - return _this; - } - BufferToggleSubscriber.prototype._next = function (value) { - var contexts = this.contexts; - var len = contexts.length; - for (var i = 0; i < len; i++) { - contexts[i].buffer.push(value); - } - }; - BufferToggleSubscriber.prototype._error = function (err) { - var contexts = this.contexts; - while (contexts.length > 0) { - var context_1 = contexts.shift(); - context_1.subscription.unsubscribe(); - context_1.buffer = null; - context_1.subscription = null; - } - this.contexts = null; - _super.prototype._error.call(this, err); - }; - BufferToggleSubscriber.prototype._complete = function () { - var contexts = this.contexts; - while (contexts.length > 0) { - var context_2 = contexts.shift(); - this.destination.next(context_2.buffer); - context_2.subscription.unsubscribe(); - context_2.buffer = null; - context_2.subscription = null; - } - this.contexts = null; - _super.prototype._complete.call(this); - }; - BufferToggleSubscriber.prototype.notifyNext = function (outerValue, innerValue, outerIndex, innerIndex, innerSub) { - outerValue ? this.closeBuffer(outerValue) : this.openBuffer(innerValue); - }; - BufferToggleSubscriber.prototype.notifyComplete = function (innerSub) { - this.closeBuffer(innerSub.context); - }; - BufferToggleSubscriber.prototype.openBuffer = function (value) { - try { - var closingSelector = this.closingSelector; - var closingNotifier = closingSelector.call(this, value); - if (closingNotifier) { - this.trySubscribe(closingNotifier); - } - } - catch (err) { - this._error(err); - } - }; - BufferToggleSubscriber.prototype.closeBuffer = function (context) { - var contexts = this.contexts; - if (contexts && context) { - var buffer = context.buffer, subscription = context.subscription; - this.destination.next(buffer); - contexts.splice(contexts.indexOf(context), 1); - this.remove(subscription); - subscription.unsubscribe(); - } - }; - BufferToggleSubscriber.prototype.trySubscribe = function (closingNotifier) { - var contexts = this.contexts; - var buffer = []; - var subscription = new _Subscription__WEBPACK_IMPORTED_MODULE_1__["Subscription"](); - var context = { buffer: buffer, subscription: subscription }; - contexts.push(context); - var innerSubscription = Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__["subscribeToResult"])(this, closingNotifier, context); - if (!innerSubscription || innerSubscription.closed) { - this.closeBuffer(context); - } - else { - innerSubscription.context = context; - this.add(innerSubscription); - subscription.add(innerSubscription); - } - }; - return BufferToggleSubscriber; -}(_OuterSubscriber__WEBPACK_IMPORTED_MODULE_3__["OuterSubscriber"])); -//# sourceMappingURL=bufferToggle.js.map +const reduceIgnore = files => { + return files.reduce((ignores, file) => { + ignores.add(parseGitIgnore(file.content, { + cwd: file.cwd, + fileName: file.filePath + })); + return ignores; + }, gitIgnore()); +}; +const ensureAbsolutePathForCwd = (cwd, p) => { + if (path.isAbsolute(p)) { + if (p.startsWith(cwd)) { + return p; + } -/***/ }), -/* 384 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + throw new Error(`Path ${p} is not in cwd ${cwd}`); + } -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "bufferWhen", function() { return bufferWhen; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(284); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(336); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(337); -/** PURE_IMPORTS_START tslib,_Subscription,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ + return path.join(cwd, p); +}; +const getIsIgnoredPredecate = (ignores, cwd) => { + return p => ignores.ignores(slash(path.relative(cwd, ensureAbsolutePathForCwd(cwd, p)))); +}; +const getFile = async (file, cwd) => { + const filePath = path.join(cwd, file); + const content = await readFileP(filePath, 'utf8'); + return { + cwd, + filePath, + content + }; +}; -function bufferWhen(closingSelector) { - return function (source) { - return source.lift(new BufferWhenOperator(closingSelector)); - }; -} -var BufferWhenOperator = /*@__PURE__*/ (function () { - function BufferWhenOperator(closingSelector) { - this.closingSelector = closingSelector; - } - BufferWhenOperator.prototype.call = function (subscriber, source) { - return source.subscribe(new BufferWhenSubscriber(subscriber, this.closingSelector)); - }; - return BufferWhenOperator; -}()); -var BufferWhenSubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](BufferWhenSubscriber, _super); - function BufferWhenSubscriber(destination, closingSelector) { - var _this = _super.call(this, destination) || this; - _this.closingSelector = closingSelector; - _this.subscribing = false; - _this.openBuffer(); - return _this; - } - BufferWhenSubscriber.prototype._next = function (value) { - this.buffer.push(value); - }; - BufferWhenSubscriber.prototype._complete = function () { - var buffer = this.buffer; - if (buffer) { - this.destination.next(buffer); - } - _super.prototype._complete.call(this); - }; - BufferWhenSubscriber.prototype._unsubscribe = function () { - this.buffer = null; - this.subscribing = false; - }; - BufferWhenSubscriber.prototype.notifyNext = function (outerValue, innerValue, outerIndex, innerIndex, innerSub) { - this.openBuffer(); - }; - BufferWhenSubscriber.prototype.notifyComplete = function () { - if (this.subscribing) { - this.complete(); - } - else { - this.openBuffer(); - } - }; - BufferWhenSubscriber.prototype.openBuffer = function () { - var closingSubscription = this.closingSubscription; - if (closingSubscription) { - this.remove(closingSubscription); - closingSubscription.unsubscribe(); - } - var buffer = this.buffer; - if (this.buffer) { - this.destination.next(buffer); - } - this.buffer = []; - var closingNotifier; - try { - var closingSelector = this.closingSelector; - closingNotifier = closingSelector(); - } - catch (err) { - return this.error(err); - } - closingSubscription = new _Subscription__WEBPACK_IMPORTED_MODULE_1__["Subscription"](); - this.closingSubscription = closingSubscription; - this.add(closingSubscription); - this.subscribing = true; - closingSubscription.add(Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_3__["subscribeToResult"])(this, closingNotifier)); - this.subscribing = false; - }; - return BufferWhenSubscriber; -}(_OuterSubscriber__WEBPACK_IMPORTED_MODULE_2__["OuterSubscriber"])); -//# sourceMappingURL=bufferWhen.js.map +const getFileSync = (file, cwd) => { + const filePath = path.join(cwd, file); + const content = fs.readFileSync(filePath, 'utf8'); + return { + cwd, + filePath, + content + }; +}; -/***/ }), -/* 385 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +const normalizeOptions = ({ + ignore = [], + cwd = process.cwd() +} = {}) => { + return {ignore, cwd}; +}; -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "catchError", function() { return catchError; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(336); -/* harmony import */ var _InnerSubscriber__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(338); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(337); -/** PURE_IMPORTS_START tslib,_OuterSubscriber,_InnerSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ +module.exports = async options => { + options = normalizeOptions(options); + const paths = await fastGlob('**/.gitignore', { + ignore: DEFAULT_IGNORE.concat(options.ignore), + cwd: options.cwd + }); + const files = await Promise.all(paths.map(file => getFile(file, options.cwd))); + const ignores = reduceIgnore(files); + return getIsIgnoredPredecate(ignores, options.cwd); +}; -function catchError(selector) { - return function catchErrorOperatorFunction(source) { - var operator = new CatchOperator(selector); - var caught = source.lift(operator); - return (operator.caught = caught); - }; -} -var CatchOperator = /*@__PURE__*/ (function () { - function CatchOperator(selector) { - this.selector = selector; - } - CatchOperator.prototype.call = function (subscriber, source) { - return source.subscribe(new CatchSubscriber(subscriber, this.selector, this.caught)); - }; - return CatchOperator; -}()); -var CatchSubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](CatchSubscriber, _super); - function CatchSubscriber(destination, selector, caught) { - var _this = _super.call(this, destination) || this; - _this.selector = selector; - _this.caught = caught; - return _this; - } - CatchSubscriber.prototype.error = function (err) { - if (!this.isStopped) { - var result = void 0; - try { - result = this.selector(err, this.caught); - } - catch (err2) { - _super.prototype.error.call(this, err2); - return; - } - this._unsubscribeAndRecycle(); - var innerSubscriber = new _InnerSubscriber__WEBPACK_IMPORTED_MODULE_2__["InnerSubscriber"](this, undefined, undefined); - this.add(innerSubscriber); - Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_3__["subscribeToResult"])(this, result, undefined, undefined, innerSubscriber); - } - }; - return CatchSubscriber; -}(_OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__["OuterSubscriber"])); -//# sourceMappingURL=catchError.js.map +module.exports.sync = options => { + options = normalizeOptions(options); + const paths = fastGlob.sync('**/.gitignore', { + ignore: DEFAULT_IGNORE.concat(options.ignore), + cwd: options.cwd + }); -/***/ }), -/* 386 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + const files = paths.map(file => getFileSync(file, options.cwd)); + const ignores = reduceIgnore(files); -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "combineAll", function() { return combineAll; }); -/* harmony import */ var _observable_combineLatest__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(335); -/** PURE_IMPORTS_START _observable_combineLatest PURE_IMPORTS_END */ + return getIsIgnoredPredecate(ignores, options.cwd); +}; -function combineAll(project) { - return function (source) { return source.lift(new _observable_combineLatest__WEBPACK_IMPORTED_MODULE_0__["CombineLatestOperator"](project)); }; + +/***/ }), +/* 661 */ +/***/ (function(module, exports) { + +// A simple implementation of make-array +function makeArray (subject) { + return Array.isArray(subject) + ? subject + : [subject] } -//# sourceMappingURL=combineAll.js.map +const REGEX_TEST_BLANK_LINE = /^\s+$/ +const REGEX_REPLACE_LEADING_EXCAPED_EXCLAMATION = /^\\!/ +const REGEX_REPLACE_LEADING_EXCAPED_HASH = /^\\#/ +const REGEX_SPLITALL_CRLF = /\r?\n/g +// /foo, +// ./foo, +// ../foo, +// . +// .. +const REGEX_TEST_INVALID_PATH = /^\.*\/|^\.+$/ + +const SLASH = '/' +const KEY_IGNORE = typeof Symbol !== 'undefined' + ? Symbol.for('node-ignore') + /* istanbul ignore next */ + : 'node-ignore' -/***/ }), -/* 387 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +const define = (object, key, value) => + Object.defineProperty(object, key, {value}) -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "combineLatest", function() { return combineLatest; }); -/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(285); -/* harmony import */ var _observable_combineLatest__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(335); -/* harmony import */ var _observable_from__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(350); -/** PURE_IMPORTS_START _util_isArray,_observable_combineLatest,_observable_from PURE_IMPORTS_END */ +const REGEX_REGEXP_RANGE = /([0-z])-([0-z])/g + +// Sanitize the range of a regular expression +// The cases are complicated, see test cases for details +const sanitizeRange = range => range.replace( + REGEX_REGEXP_RANGE, + (match, from, to) => from.charCodeAt(0) <= to.charCodeAt(0) + ? match + // Invalid range (out of order) which is ok for gitignore rules but + // fatal for JavaScript regular expression, so eliminate it. + : '' +) +// > If the pattern ends with a slash, +// > it is removed for the purpose of the following description, +// > but it would only find a match with a directory. +// > In other words, foo/ will match a directory foo and paths underneath it, +// > but will not match a regular file or a symbolic link foo +// > (this is consistent with the way how pathspec works in general in Git). +// '`foo/`' will not match regular file '`foo`' or symbolic link '`foo`' +// -> ignore-rules will not deal with it, because it costs extra `fs.stat` call +// you could use option `mark: true` with `glob` +// '`foo/`' should not continue with the '`..`' +const DEFAULT_REPLACER_PREFIX = [ -var none = {}; -function combineLatest() { - var observables = []; - for (var _i = 0; _i < arguments.length; _i++) { - observables[_i] = arguments[_i]; - } - var project = null; - if (typeof observables[observables.length - 1] === 'function') { - project = observables.pop(); - } - if (observables.length === 1 && Object(_util_isArray__WEBPACK_IMPORTED_MODULE_0__["isArray"])(observables[0])) { - observables = observables[0].slice(); - } - return function (source) { return source.lift.call(Object(_observable_from__WEBPACK_IMPORTED_MODULE_2__["from"])([source].concat(observables)), new _observable_combineLatest__WEBPACK_IMPORTED_MODULE_1__["CombineLatestOperator"](project)); }; -} -//# sourceMappingURL=combineLatest.js.map + // > Trailing spaces are ignored unless they are quoted with backslash ("\") + [ + // (a\ ) -> (a ) + // (a ) -> (a) + // (a \ ) -> (a ) + /\\?\s+$/, + match => match.indexOf('\\') === 0 + ? ' ' + : '' + ], + // replace (\ ) with ' ' + [ + /\\\s/g, + () => ' ' + ], -/***/ }), -/* 388 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + // Escape metacharacters + // which is written down by users but means special for regular expressions. -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "concat", function() { return concat; }); -/* harmony import */ var _observable_concat__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(346); -/** PURE_IMPORTS_START _observable_concat PURE_IMPORTS_END */ + // > There are 12 characters with special meanings: + // > - the backslash \, + // > - the caret ^, + // > - the dollar sign $, + // > - the period or dot ., + // > - the vertical bar or pipe symbol |, + // > - the question mark ?, + // > - the asterisk or star *, + // > - the plus sign +, + // > - the opening parenthesis (, + // > - the closing parenthesis ), + // > - and the opening square bracket [, + // > - the opening curly brace {, + // > These special characters are often called "metacharacters". + [ + /[\\^$.|*+(){]/g, + match => `\\${match}` + ], + + [ + // > [abc] matches any character inside the brackets + // > (in this case a, b, or c); + /\[([^\]/]*)($|\])/g, + (match, p1, p2) => p2 === ']' + ? `[${sanitizeRange(p1)}]` + : `\\${match}` + ], + + [ + // > a question mark (?) matches a single character + /(?!\\)\?/g, + () => '[^/]' + ], + + // leading slash + [ + + // > A leading slash matches the beginning of the pathname. + // > For example, "/*.c" matches "cat-file.c" but not "mozilla-sha1/sha1.c". + // A leading slash matches the beginning of the pathname + /^\//, + () => '^' + ], + + // replace special metacharacter slash after the leading slash + [ + /\//g, + () => '\\/' + ], + + [ + // > A leading "**" followed by a slash means match in all directories. + // > For example, "**/foo" matches file or directory "foo" anywhere, + // > the same as pattern "foo". + // > "**/foo/bar" matches file or directory "bar" anywhere that is directly + // > under directory "foo". + // Notice that the '*'s have been replaced as '\\*' + /^\^*\\\*\\\*\\\//, + + // '**/foo' <-> 'foo' + () => '^(?:.*\\/)?' + ] +] + +const DEFAULT_REPLACER_SUFFIX = [ + // starting + [ + // there will be no leading '/' + // (which has been replaced by section "leading slash") + // If starts with '**', adding a '^' to the regular expression also works + /^(?=[^^])/, + function startingReplacer () { + return !/\/(?!$)/.test(this) + // > If the pattern does not contain a slash /, + // > Git treats it as a shell glob pattern + // Actually, if there is only a trailing slash, + // git also treats it as a shell glob pattern + ? '(?:^|\\/)' -function concat() { - var observables = []; - for (var _i = 0; _i < arguments.length; _i++) { - observables[_i] = arguments[_i]; + // > Otherwise, Git treats the pattern as a shell glob suitable for + // > consumption by fnmatch(3) + : '^' } - return function (source) { return source.lift.call(_observable_concat__WEBPACK_IMPORTED_MODULE_0__["concat"].apply(void 0, [source].concat(observables))); }; -} -//# sourceMappingURL=concat.js.map + ], + // two globstars + [ + // Use lookahead assertions so that we could match more than one `'/**'` + /\\\/\\\*\\\*(?=\\\/|$)/g, -/***/ }), -/* 389 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + // Zero, one or several directories + // should not use '*', or it will be replaced by the next replacer -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "concatMap", function() { return concatMap; }); -/* harmony import */ var _mergeMap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(349); -/** PURE_IMPORTS_START _mergeMap PURE_IMPORTS_END */ + // Check if it is not the last `'/**'` + (_, index, str) => index + 6 < str.length -function concatMap(project, resultSelector) { - return Object(_mergeMap__WEBPACK_IMPORTED_MODULE_0__["mergeMap"])(project, resultSelector, 1); -} -//# sourceMappingURL=concatMap.js.map + // case: /**/ + // > A slash followed by two consecutive asterisks then a slash matches + // > zero or more directories. + // > For example, "a/**/b" matches "a/b", "a/x/b", "a/x/y/b" and so on. + // '/**/' + ? '(?:\\/[^\\/]+)*' + // case: /** + // > A trailing `"/**"` matches everything inside. -/***/ }), -/* 390 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + // #21: everything inside but it should not include the current folder + : '\\/.+' + ], -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "concatMapTo", function() { return concatMapTo; }); -/* harmony import */ var _concatMap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(389); -/** PURE_IMPORTS_START _concatMap PURE_IMPORTS_END */ + // intermediate wildcards + [ + // Never replace escaped '*' + // ignore rule '\*' will match the path '*' -function concatMapTo(innerObservable, resultSelector) { - return Object(_concatMap__WEBPACK_IMPORTED_MODULE_0__["concatMap"])(function () { return innerObservable; }, resultSelector); -} -//# sourceMappingURL=concatMapTo.js.map + // 'abc.*/' -> go + // 'abc.*' -> skip this rule + /(^|[^\\]+)\\\*(?=.+)/g, + // '*.js' matches '.js' + // '*.js' doesn't match 'abc' + (_, p1) => `${p1}[^\\/]*` + ], -/***/ }), -/* 391 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + // trailing wildcard + [ + /(\^|\\\/)?\\\*$/, + (_, p1) => { + const prefix = p1 + // '\^': + // '/*' does not match '' + // '/*' does not match everything -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "count", function() { return count; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); -/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ + // '\\\/': + // 'abc/*' does not match 'abc/' + ? `${p1}[^/]+` + // 'a*' matches 'a' + // 'a*' matches 'aa' + : '[^/]*' -function count(predicate) { - return function (source) { return source.lift(new CountOperator(predicate, source)); }; -} -var CountOperator = /*@__PURE__*/ (function () { - function CountOperator(predicate, source) { - this.predicate = predicate; - this.source = source; - } - CountOperator.prototype.call = function (subscriber, source) { - return source.subscribe(new CountSubscriber(subscriber, this.predicate, this.source)); - }; - return CountOperator; -}()); -var CountSubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](CountSubscriber, _super); - function CountSubscriber(destination, predicate, source) { - var _this = _super.call(this, destination) || this; - _this.predicate = predicate; - _this.source = source; - _this.count = 0; - _this.index = 0; - return _this; + return `${prefix}(?=$|\\/$)` } - CountSubscriber.prototype._next = function (value) { - if (this.predicate) { - this._tryPredicate(value); - } - else { - this.count++; - } - }; - CountSubscriber.prototype._tryPredicate = function (value) { - var result; - try { - result = this.predicate(value, this.index++, this.source); - } - catch (err) { - this.destination.error(err); - return; - } - if (result) { - this.count++; - } - }; - CountSubscriber.prototype._complete = function () { - this.destination.next(this.count); - this.destination.complete(); - }; - return CountSubscriber; -}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); -//# sourceMappingURL=count.js.map - - -/***/ }), -/* 392 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + ], -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "debounce", function() { return debounce; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(336); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(337); -/** PURE_IMPORTS_START tslib,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ + [ + // unescape + /\\\\\\/g, + () => '\\' + ] +] +const POSITIVE_REPLACERS = [ + ...DEFAULT_REPLACER_PREFIX, + // 'f' + // matches + // - /f(end) + // - /f/ + // - (start)f(end) + // - (start)f/ + // doesn't match + // - oof + // - foo + // pseudo: + // -> (^|/)f(/|$) -function debounce(durationSelector) { - return function (source) { return source.lift(new DebounceOperator(durationSelector)); }; -} -var DebounceOperator = /*@__PURE__*/ (function () { - function DebounceOperator(durationSelector) { - this.durationSelector = durationSelector; - } - DebounceOperator.prototype.call = function (subscriber, source) { - return source.subscribe(new DebounceSubscriber(subscriber, this.durationSelector)); - }; - return DebounceOperator; -}()); -var DebounceSubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](DebounceSubscriber, _super); - function DebounceSubscriber(destination, durationSelector) { - var _this = _super.call(this, destination) || this; - _this.durationSelector = durationSelector; - _this.hasValue = false; - _this.durationSubscription = null; - return _this; - } - DebounceSubscriber.prototype._next = function (value) { - try { - var result = this.durationSelector.call(this, value); - if (result) { - this._tryNext(value, result); - } - } - catch (err) { - this.destination.error(err); - } - }; - DebounceSubscriber.prototype._complete = function () { - this.emitValue(); - this.destination.complete(); - }; - DebounceSubscriber.prototype._tryNext = function (value, duration) { - var subscription = this.durationSubscription; - this.value = value; - this.hasValue = true; - if (subscription) { - subscription.unsubscribe(); - this.remove(subscription); - } - subscription = Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__["subscribeToResult"])(this, duration); - if (subscription && !subscription.closed) { - this.add(this.durationSubscription = subscription); - } - }; - DebounceSubscriber.prototype.notifyNext = function (outerValue, innerValue, outerIndex, innerIndex, innerSub) { - this.emitValue(); - }; - DebounceSubscriber.prototype.notifyComplete = function () { - this.emitValue(); - }; - DebounceSubscriber.prototype.emitValue = function () { - if (this.hasValue) { - var value = this.value; - var subscription = this.durationSubscription; - if (subscription) { - this.durationSubscription = null; - subscription.unsubscribe(); - this.remove(subscription); - } - this.value = null; - this.hasValue = false; - _super.prototype._next.call(this, value); - } - }; - return DebounceSubscriber; -}(_OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__["OuterSubscriber"])); -//# sourceMappingURL=debounce.js.map + // ending + [ + // 'js' will not match 'js.' + // 'ab' will not match 'abc' + /(?:[^*/])$/, + // 'js*' will not match 'a.js' + // 'js/' will not match 'a.js' + // 'js' will match 'a.js' and 'a.js/' + match => `${match}(?=$|\\/)` + ], -/***/ }), -/* 393 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + ...DEFAULT_REPLACER_SUFFIX +] -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "debounceTime", function() { return debounceTime; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); -/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(322); -/** PURE_IMPORTS_START tslib,_Subscriber,_scheduler_async PURE_IMPORTS_END */ +const NEGATIVE_REPLACERS = [ + ...DEFAULT_REPLACER_PREFIX, + // #24, #38 + // The MISSING rule of [gitignore docs](https://git-scm.com/docs/gitignore) + // A negative pattern without a trailing wildcard should not + // re-include the things inside that directory. + // eg: + // ['node_modules/*', '!node_modules'] + // should ignore `node_modules/a.js` + [ + /(?:[^*])$/, + match => `${match}(?=$|\\/$)` + ], -function debounceTime(dueTime, scheduler) { - if (scheduler === void 0) { - scheduler = _scheduler_async__WEBPACK_IMPORTED_MODULE_2__["async"]; - } - return function (source) { return source.lift(new DebounceTimeOperator(dueTime, scheduler)); }; -} -var DebounceTimeOperator = /*@__PURE__*/ (function () { - function DebounceTimeOperator(dueTime, scheduler) { - this.dueTime = dueTime; - this.scheduler = scheduler; - } - DebounceTimeOperator.prototype.call = function (subscriber, source) { - return source.subscribe(new DebounceTimeSubscriber(subscriber, this.dueTime, this.scheduler)); - }; - return DebounceTimeOperator; -}()); -var DebounceTimeSubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](DebounceTimeSubscriber, _super); - function DebounceTimeSubscriber(destination, dueTime, scheduler) { - var _this = _super.call(this, destination) || this; - _this.dueTime = dueTime; - _this.scheduler = scheduler; - _this.debouncedSubscription = null; - _this.lastValue = null; - _this.hasValue = false; - return _this; - } - DebounceTimeSubscriber.prototype._next = function (value) { - this.clearDebounce(); - this.lastValue = value; - this.hasValue = true; - this.add(this.debouncedSubscription = this.scheduler.schedule(dispatchNext, this.dueTime, this)); - }; - DebounceTimeSubscriber.prototype._complete = function () { - this.debouncedNext(); - this.destination.complete(); - }; - DebounceTimeSubscriber.prototype.debouncedNext = function () { - this.clearDebounce(); - if (this.hasValue) { - var lastValue = this.lastValue; - this.lastValue = null; - this.hasValue = false; - this.destination.next(lastValue); - } - }; - DebounceTimeSubscriber.prototype.clearDebounce = function () { - var debouncedSubscription = this.debouncedSubscription; - if (debouncedSubscription !== null) { - this.remove(debouncedSubscription); - debouncedSubscription.unsubscribe(); - this.debouncedSubscription = null; - } - }; - return DebounceTimeSubscriber; -}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); -function dispatchNext(subscriber) { - subscriber.debouncedNext(); -} -//# sourceMappingURL=debounceTime.js.map + ...DEFAULT_REPLACER_SUFFIX +] +// A simple cache, because an ignore rule only has only one certain meaning +const regexCache = Object.create(null) -/***/ }), -/* 394 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +// @param {pattern} +const makeRegex = (pattern, negative, ignorecase) => { + const r = regexCache[pattern] + if (r) { + return r + } -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "defaultIfEmpty", function() { return defaultIfEmpty; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); -/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ + const replacers = negative + ? NEGATIVE_REPLACERS + : POSITIVE_REPLACERS + const source = replacers.reduce( + (prev, current) => prev.replace(current[0], current[1].bind(pattern)), + pattern + ) -function defaultIfEmpty(defaultValue) { - if (defaultValue === void 0) { - defaultValue = null; - } - return function (source) { return source.lift(new DefaultIfEmptyOperator(defaultValue)); }; + return regexCache[pattern] = ignorecase + ? new RegExp(source, 'i') + : new RegExp(source) } -var DefaultIfEmptyOperator = /*@__PURE__*/ (function () { - function DefaultIfEmptyOperator(defaultValue) { - this.defaultValue = defaultValue; - } - DefaultIfEmptyOperator.prototype.call = function (subscriber, source) { - return source.subscribe(new DefaultIfEmptySubscriber(subscriber, this.defaultValue)); - }; - return DefaultIfEmptyOperator; -}()); -var DefaultIfEmptySubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](DefaultIfEmptySubscriber, _super); - function DefaultIfEmptySubscriber(destination, defaultValue) { - var _this = _super.call(this, destination) || this; - _this.defaultValue = defaultValue; - _this.isEmpty = true; - return _this; - } - DefaultIfEmptySubscriber.prototype._next = function (value) { - this.isEmpty = false; - this.destination.next(value); - }; - DefaultIfEmptySubscriber.prototype._complete = function () { - if (this.isEmpty) { - this.destination.next(this.defaultValue); - } - this.destination.complete(); - }; - return DefaultIfEmptySubscriber; -}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); -//# sourceMappingURL=defaultIfEmpty.js.map - -/***/ }), -/* 395 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +const isString = subject => typeof subject === 'string' -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "delay", function() { return delay; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(322); -/* harmony import */ var _util_isDate__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(396); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(278); -/* harmony import */ var _Notification__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(309); -/** PURE_IMPORTS_START tslib,_scheduler_async,_util_isDate,_Subscriber,_Notification PURE_IMPORTS_END */ +// > A blank line matches no files, so it can serve as a separator for readability. +const checkPattern = pattern => pattern + && isString(pattern) + && !REGEX_TEST_BLANK_LINE.test(pattern) + // > A line starting with # serves as a comment. + && pattern.indexOf('#') !== 0 +const splitPattern = pattern => pattern.split(REGEX_SPLITALL_CRLF) +class IgnoreRule { + constructor ( + origin, + pattern, + negative, + regex + ) { + this.origin = origin + this.pattern = pattern + this.negative = negative + this.regex = regex + } +} +const createRule = (pattern, ignorecase) => { + const origin = pattern + let negative = false -function delay(delay, scheduler) { - if (scheduler === void 0) { - scheduler = _scheduler_async__WEBPACK_IMPORTED_MODULE_1__["async"]; - } - var absoluteDelay = Object(_util_isDate__WEBPACK_IMPORTED_MODULE_2__["isDate"])(delay); - var delayFor = absoluteDelay ? (+delay - scheduler.now()) : Math.abs(delay); - return function (source) { return source.lift(new DelayOperator(delayFor, scheduler)); }; -} -var DelayOperator = /*@__PURE__*/ (function () { - function DelayOperator(delay, scheduler) { - this.delay = delay; - this.scheduler = scheduler; - } - DelayOperator.prototype.call = function (subscriber, source) { - return source.subscribe(new DelaySubscriber(subscriber, this.delay, this.scheduler)); - }; - return DelayOperator; -}()); -var DelaySubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](DelaySubscriber, _super); - function DelaySubscriber(destination, delay, scheduler) { - var _this = _super.call(this, destination) || this; - _this.delay = delay; - _this.scheduler = scheduler; - _this.queue = []; - _this.active = false; - _this.errored = false; - return _this; - } - DelaySubscriber.dispatch = function (state) { - var source = state.source; - var queue = source.queue; - var scheduler = state.scheduler; - var destination = state.destination; - while (queue.length > 0 && (queue[0].time - scheduler.now()) <= 0) { - queue.shift().notification.observe(destination); - } - if (queue.length > 0) { - var delay_1 = Math.max(0, queue[0].time - scheduler.now()); - this.schedule(state, delay_1); - } - else { - this.unsubscribe(); - source.active = false; - } - }; - DelaySubscriber.prototype._schedule = function (scheduler) { - this.active = true; - var destination = this.destination; - destination.add(scheduler.schedule(DelaySubscriber.dispatch, this.delay, { - source: this, destination: this.destination, scheduler: scheduler - })); - }; - DelaySubscriber.prototype.scheduleNotification = function (notification) { - if (this.errored === true) { - return; - } - var scheduler = this.scheduler; - var message = new DelayMessage(scheduler.now() + this.delay, notification); - this.queue.push(message); - if (this.active === false) { - this._schedule(scheduler); - } - }; - DelaySubscriber.prototype._next = function (value) { - this.scheduleNotification(_Notification__WEBPACK_IMPORTED_MODULE_4__["Notification"].createNext(value)); - }; - DelaySubscriber.prototype._error = function (err) { - this.errored = true; - this.queue = []; - this.destination.error(err); - this.unsubscribe(); - }; - DelaySubscriber.prototype._complete = function () { - this.scheduleNotification(_Notification__WEBPACK_IMPORTED_MODULE_4__["Notification"].createComplete()); - this.unsubscribe(); - }; - return DelaySubscriber; -}(_Subscriber__WEBPACK_IMPORTED_MODULE_3__["Subscriber"])); -var DelayMessage = /*@__PURE__*/ (function () { - function DelayMessage(time, notification) { - this.time = time; - this.notification = notification; - } - return DelayMessage; -}()); -//# sourceMappingURL=delay.js.map + // > An optional prefix "!" which negates the pattern; + if (pattern.indexOf('!') === 0) { + negative = true + pattern = pattern.substr(1) + } + pattern = pattern + // > Put a backslash ("\") in front of the first "!" for patterns that + // > begin with a literal "!", for example, `"\!important!.txt"`. + .replace(REGEX_REPLACE_LEADING_EXCAPED_EXCLAMATION, '!') + // > Put a backslash ("\") in front of the first hash for patterns that + // > begin with a hash. + .replace(REGEX_REPLACE_LEADING_EXCAPED_HASH, '#') -/***/ }), -/* 396 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + const regex = makeRegex(pattern, negative, ignorecase) -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "isDate", function() { return isDate; }); -/** PURE_IMPORTS_START PURE_IMPORTS_END */ -function isDate(value) { - return value instanceof Date && !isNaN(+value); + return new IgnoreRule( + origin, + pattern, + negative, + regex + ) } -//# sourceMappingURL=isDate.js.map +const throwError = (message, Ctor) => { + throw new Ctor(message) +} -/***/ }), -/* 397 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +const checkPath = (path, originalPath, doThrow) => { + if (!isString(path)) { + return doThrow( + `path must be a string, but got \`${originalPath}\``, + TypeError + ) + } -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "delayWhen", function() { return delayWhen; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(276); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(336); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(337); -/** PURE_IMPORTS_START tslib,_Subscriber,_Observable,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ + // We don't know if we should ignore '', so throw + if (!path) { + return doThrow(`path must not be empty`, TypeError) + } + // Check if it is a relative path + if (checkPath.isNotRelative(path)) { + const r = '`path.relative()`d' + return doThrow( + `path should be a ${r} string, but got "${originalPath}"`, + RangeError + ) + } + return true +} +const isNotRelative = path => REGEX_TEST_INVALID_PATH.test(path) +checkPath.isNotRelative = isNotRelative +checkPath.convert = p => p -function delayWhen(delayDurationSelector, subscriptionDelay) { - if (subscriptionDelay) { - return function (source) { - return new SubscriptionDelayObservable(source, subscriptionDelay) - .lift(new DelayWhenOperator(delayDurationSelector)); - }; - } - return function (source) { return source.lift(new DelayWhenOperator(delayDurationSelector)); }; -} -var DelayWhenOperator = /*@__PURE__*/ (function () { - function DelayWhenOperator(delayDurationSelector) { - this.delayDurationSelector = delayDurationSelector; +class Ignore { + constructor ({ + ignorecase = true + } = {}) { + this._rules = [] + this._ignorecase = ignorecase + define(this, KEY_IGNORE, true) + this._initCache() + } + + _initCache () { + this._ignoreCache = Object.create(null) + this._testCache = Object.create(null) + } + + _addPattern (pattern) { + // #32 + if (pattern && pattern[KEY_IGNORE]) { + this._rules = this._rules.concat(pattern._rules) + this._added = true + return } - DelayWhenOperator.prototype.call = function (subscriber, source) { - return source.subscribe(new DelayWhenSubscriber(subscriber, this.delayDurationSelector)); - }; - return DelayWhenOperator; -}()); -var DelayWhenSubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](DelayWhenSubscriber, _super); - function DelayWhenSubscriber(destination, delayDurationSelector) { - var _this = _super.call(this, destination) || this; - _this.delayDurationSelector = delayDurationSelector; - _this.completed = false; - _this.delayNotifierSubscriptions = []; - _this.index = 0; - return _this; + + if (checkPattern(pattern)) { + const rule = createRule(pattern, this._ignorecase) + this._added = true + this._rules.push(rule) } - DelayWhenSubscriber.prototype.notifyNext = function (outerValue, innerValue, outerIndex, innerIndex, innerSub) { - this.destination.next(outerValue); - this.removeSubscription(innerSub); - this.tryComplete(); - }; - DelayWhenSubscriber.prototype.notifyError = function (error, innerSub) { - this._error(error); - }; - DelayWhenSubscriber.prototype.notifyComplete = function (innerSub) { - var value = this.removeSubscription(innerSub); - if (value) { - this.destination.next(value); - } - this.tryComplete(); - }; - DelayWhenSubscriber.prototype._next = function (value) { - var index = this.index++; - try { - var delayNotifier = this.delayDurationSelector(value, index); - if (delayNotifier) { - this.tryDelay(delayNotifier, value); - } - } - catch (err) { - this.destination.error(err); - } - }; - DelayWhenSubscriber.prototype._complete = function () { - this.completed = true; - this.tryComplete(); - this.unsubscribe(); - }; - DelayWhenSubscriber.prototype.removeSubscription = function (subscription) { - subscription.unsubscribe(); - var subscriptionIdx = this.delayNotifierSubscriptions.indexOf(subscription); - if (subscriptionIdx !== -1) { - this.delayNotifierSubscriptions.splice(subscriptionIdx, 1); - } - return subscription.outerValue; - }; - DelayWhenSubscriber.prototype.tryDelay = function (delayNotifier, value) { - var notifierSubscription = Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_4__["subscribeToResult"])(this, delayNotifier, value); - if (notifierSubscription && !notifierSubscription.closed) { - var destination = this.destination; - destination.add(notifierSubscription); - this.delayNotifierSubscriptions.push(notifierSubscription); - } - }; - DelayWhenSubscriber.prototype.tryComplete = function () { - if (this.completed && this.delayNotifierSubscriptions.length === 0) { - this.destination.complete(); - } - }; - return DelayWhenSubscriber; -}(_OuterSubscriber__WEBPACK_IMPORTED_MODULE_3__["OuterSubscriber"])); -var SubscriptionDelayObservable = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](SubscriptionDelayObservable, _super); - function SubscriptionDelayObservable(source, subscriptionDelay) { - var _this = _super.call(this) || this; - _this.source = source; - _this.subscriptionDelay = subscriptionDelay; - return _this; + } + + // @param {Array | string | Ignore} pattern + add (pattern) { + this._added = false + + makeArray( + isString(pattern) + ? splitPattern(pattern) + : pattern + ).forEach(this._addPattern, this) + + // Some rules have just added to the ignore, + // making the behavior changed. + if (this._added) { + this._initCache() } - SubscriptionDelayObservable.prototype._subscribe = function (subscriber) { - this.subscriptionDelay.subscribe(new SubscriptionDelaySubscriber(subscriber, this.source)); - }; - return SubscriptionDelayObservable; -}(_Observable__WEBPACK_IMPORTED_MODULE_2__["Observable"])); -var SubscriptionDelaySubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](SubscriptionDelaySubscriber, _super); - function SubscriptionDelaySubscriber(parent, source) { - var _this = _super.call(this) || this; - _this.parent = parent; - _this.source = source; - _this.sourceSubscribed = false; - return _this; + + return this + } + + // legacy + addPattern (pattern) { + return this.add(pattern) + } + + // | ignored : unignored + // negative | 0:0 | 0:1 | 1:0 | 1:1 + // -------- | ------- | ------- | ------- | -------- + // 0 | TEST | TEST | SKIP | X + // 1 | TESTIF | SKIP | TEST | X + + // - SKIP: always skip + // - TEST: always test + // - TESTIF: only test if checkUnignored + // - X: that never happen + + // @param {boolean} whether should check if the path is unignored, + // setting `checkUnignored` to `false` could reduce additional + // path matching. + + // @returns {TestResult} true if a file is ignored + _testOne (path, checkUnignored) { + let ignored = false + let unignored = false + + this._rules.forEach(rule => { + const {negative} = rule + if ( + unignored === negative && ignored !== unignored + || negative && !ignored && !unignored && !checkUnignored + ) { + return + } + + const matched = rule.regex.test(path) + + if (matched) { + ignored = !negative + unignored = negative + } + }) + + return { + ignored, + unignored } - SubscriptionDelaySubscriber.prototype._next = function (unused) { - this.subscribeToSource(); - }; - SubscriptionDelaySubscriber.prototype._error = function (err) { - this.unsubscribe(); - this.parent.error(err); - }; - SubscriptionDelaySubscriber.prototype._complete = function () { - this.unsubscribe(); - this.subscribeToSource(); - }; - SubscriptionDelaySubscriber.prototype.subscribeToSource = function () { - if (!this.sourceSubscribed) { - this.sourceSubscribed = true; - this.unsubscribe(); - this.source.subscribe(this.parent); - } - }; - return SubscriptionDelaySubscriber; -}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); -//# sourceMappingURL=delayWhen.js.map + } + // @returns {TestResult} + _test (originalPath, cache, checkUnignored, slices) { + const path = originalPath + // Supports nullable path + && checkPath.convert(originalPath) -/***/ }), -/* 398 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + checkPath(path, originalPath, throwError) -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "dematerialize", function() { return dematerialize; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); -/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ + return this._t(path, cache, checkUnignored, slices) + } + _t (path, cache, checkUnignored, slices) { + if (path in cache) { + return cache[path] + } -function dematerialize() { - return function dematerializeOperatorFunction(source) { - return source.lift(new DeMaterializeOperator()); - }; -} -var DeMaterializeOperator = /*@__PURE__*/ (function () { - function DeMaterializeOperator() { + if (!slices) { + // path/to/a.js + // ['path', 'to', 'a.js'] + slices = path.split(SLASH) } - DeMaterializeOperator.prototype.call = function (subscriber, source) { - return source.subscribe(new DeMaterializeSubscriber(subscriber)); - }; - return DeMaterializeOperator; -}()); -var DeMaterializeSubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](DeMaterializeSubscriber, _super); - function DeMaterializeSubscriber(destination) { - return _super.call(this, destination) || this; + + slices.pop() + + // If the path has no parent directory, just test it + if (!slices.length) { + return cache[path] = this._testOne(path, checkUnignored) } - DeMaterializeSubscriber.prototype._next = function (value) { - value.observe(this.destination); - }; - return DeMaterializeSubscriber; -}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); -//# sourceMappingURL=dematerialize.js.map + const parent = this._t( + slices.join(SLASH) + SLASH, + cache, + checkUnignored, + slices + ) -/***/ }), -/* 399 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + // If the path contains a parent directory, check the parent first + return cache[path] = parent.ignored + // > It is not possible to re-include a file if a parent directory of + // > that file is excluded. + ? parent + : this._testOne(path, checkUnignored) + } -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "distinct", function() { return distinct; }); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "DistinctSubscriber", function() { return DistinctSubscriber; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(336); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(337); -/** PURE_IMPORTS_START tslib,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ + ignores (path) { + return this._test(path, this._ignoreCache, false).ignored + } + createFilter () { + return path => !this.ignores(path) + } + filter (paths) { + return makeArray(paths).filter(this.createFilter()) + } -function distinct(keySelector, flushes) { - return function (source) { return source.lift(new DistinctOperator(keySelector, flushes)); }; + // @returns {TestResult} + test (path) { + return this._test(path, this._testCache, true) + } } -var DistinctOperator = /*@__PURE__*/ (function () { - function DistinctOperator(keySelector, flushes) { - this.keySelector = keySelector; - this.flushes = flushes; - } - DistinctOperator.prototype.call = function (subscriber, source) { - return source.subscribe(new DistinctSubscriber(subscriber, this.keySelector, this.flushes)); - }; - return DistinctOperator; -}()); -var DistinctSubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](DistinctSubscriber, _super); - function DistinctSubscriber(destination, keySelector, flushes) { - var _this = _super.call(this, destination) || this; - _this.keySelector = keySelector; - _this.values = new Set(); - if (flushes) { - _this.add(Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__["subscribeToResult"])(_this, flushes)); - } - return _this; - } - DistinctSubscriber.prototype.notifyNext = function (outerValue, innerValue, outerIndex, innerIndex, innerSub) { - this.values.clear(); - }; - DistinctSubscriber.prototype.notifyError = function (error, innerSub) { - this._error(error); - }; - DistinctSubscriber.prototype._next = function (value) { - if (this.keySelector) { - this._useKeySelector(value); - } - else { - this._finalizeNext(value, value); - } - }; - DistinctSubscriber.prototype._useKeySelector = function (value) { - var key; - var destination = this.destination; - try { - key = this.keySelector(value); - } - catch (err) { - destination.error(err); - return; - } - this._finalizeNext(key, value); - }; - DistinctSubscriber.prototype._finalizeNext = function (key, value) { - var values = this.values; - if (!values.has(key)) { - values.add(key); - this.destination.next(value); - } - }; - return DistinctSubscriber; -}(_OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__["OuterSubscriber"])); -//# sourceMappingURL=distinct.js.map +const factory = options => new Ignore(options) +const returnFalse = () => false -/***/ }), -/* 400 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +const isPathValid = path => + checkPath(path && checkPath.convert(path), path, returnFalse) -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "distinctUntilChanged", function() { return distinctUntilChanged; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); -/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ +factory.isPathValid = isPathValid +// Fixes typescript +factory.default = factory -function distinctUntilChanged(compare, keySelector) { - return function (source) { return source.lift(new DistinctUntilChangedOperator(compare, keySelector)); }; -} -var DistinctUntilChangedOperator = /*@__PURE__*/ (function () { - function DistinctUntilChangedOperator(compare, keySelector) { - this.compare = compare; - this.keySelector = keySelector; - } - DistinctUntilChangedOperator.prototype.call = function (subscriber, source) { - return source.subscribe(new DistinctUntilChangedSubscriber(subscriber, this.compare, this.keySelector)); - }; - return DistinctUntilChangedOperator; -}()); -var DistinctUntilChangedSubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](DistinctUntilChangedSubscriber, _super); - function DistinctUntilChangedSubscriber(destination, compare, keySelector) { - var _this = _super.call(this, destination) || this; - _this.keySelector = keySelector; - _this.hasKey = false; - if (typeof compare === 'function') { - _this.compare = compare; - } - return _this; - } - DistinctUntilChangedSubscriber.prototype.compare = function (x, y) { - return x === y; - }; - DistinctUntilChangedSubscriber.prototype._next = function (value) { - var key; - try { - var keySelector = this.keySelector; - key = keySelector ? keySelector(value) : value; - } - catch (err) { - return this.destination.error(err); - } - var result = false; - if (this.hasKey) { - try { - var compare = this.compare; - result = compare(this.key, key); - } - catch (err) { - return this.destination.error(err); - } - } - else { - this.hasKey = true; - } - if (!result) { - this.key = key; - this.destination.next(value); - } - }; - return DistinctUntilChangedSubscriber; -}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); -//# sourceMappingURL=distinctUntilChanged.js.map +module.exports = factory + +// Windows +// -------------------------------------------------------------- +/* istanbul ignore if */ +if ( + // Detect `process` so that it can run in browsers. + typeof process !== 'undefined' + && ( + process.env && process.env.IGNORE_TEST_WIN32 + || process.platform === 'win32' + ) +) { + /* eslint no-control-regex: "off" */ + const makePosix = str => /^\\\\\?\\/.test(str) + || /["<>|\u0000-\u001F]+/u.test(str) + ? str + : str.replace(/\\/g, '/') + + checkPath.convert = makePosix + + // 'C:\\foo' <- 'C:\\foo' has been converted to 'C:/' + // 'd:\\foo' + const REGIX_IS_WINDOWS_PATH_ABSOLUTE = /^[a-z]:\//i + checkPath.isNotRelative = path => + REGIX_IS_WINDOWS_PATH_ABSOLUTE.test(path) + || isNotRelative(path) +} /***/ }), -/* 401 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/* 662 */ +/***/ (function(module, exports, __webpack_require__) { "use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "distinctUntilKeyChanged", function() { return distinctUntilKeyChanged; }); -/* harmony import */ var _distinctUntilChanged__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(400); -/** PURE_IMPORTS_START _distinctUntilChanged PURE_IMPORTS_END */ -function distinctUntilKeyChanged(key, compare) { - return Object(_distinctUntilChanged__WEBPACK_IMPORTED_MODULE_0__["distinctUntilChanged"])(function (x, y) { return compare ? compare(x[key], y[key]) : x[key] === y[key]; }); -} -//# sourceMappingURL=distinctUntilKeyChanged.js.map +module.exports = path => { + const isExtendedLengthPath = /^\\\\\?\\/.test(path); + const hasNonAscii = /[^\u0000-\u0080]+/.test(path); // eslint-disable-line no-control-regex + + if (isExtendedLengthPath || hasNonAscii) { + return path; + } + + return path.replace(/\\/g, '/'); +}; /***/ }), -/* 402 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/* 663 */ +/***/ (function(module, exports, __webpack_require__) { "use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "elementAt", function() { return elementAt; }); -/* harmony import */ var _util_ArgumentOutOfRangeError__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(329); -/* harmony import */ var _filter__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(371); -/* harmony import */ var _throwIfEmpty__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(403); -/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(394); -/* harmony import */ var _take__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(404); -/** PURE_IMPORTS_START _util_ArgumentOutOfRangeError,_filter,_throwIfEmpty,_defaultIfEmpty,_take PURE_IMPORTS_END */ +const {Transform} = __webpack_require__(27); +class ObjectTransform extends Transform { + constructor() { + super({ + objectMode: true + }); + } +} +class FilterStream extends ObjectTransform { + constructor(filter) { + super(); + this._filter = filter; + } + _transform(data, encoding, callback) { + if (this._filter(data)) { + this.push(data); + } -function elementAt(index, defaultValue) { - if (index < 0) { - throw new _util_ArgumentOutOfRangeError__WEBPACK_IMPORTED_MODULE_0__["ArgumentOutOfRangeError"](); - } - var hasDefaultValue = arguments.length >= 2; - return function (source) { - return source.pipe(Object(_filter__WEBPACK_IMPORTED_MODULE_1__["filter"])(function (v, i) { return i === index; }), Object(_take__WEBPACK_IMPORTED_MODULE_4__["take"])(1), hasDefaultValue - ? Object(_defaultIfEmpty__WEBPACK_IMPORTED_MODULE_3__["defaultIfEmpty"])(defaultValue) - : Object(_throwIfEmpty__WEBPACK_IMPORTED_MODULE_2__["throwIfEmpty"])(function () { return new _util_ArgumentOutOfRangeError__WEBPACK_IMPORTED_MODULE_0__["ArgumentOutOfRangeError"](); })); - }; + callback(); + } } -//# sourceMappingURL=elementAt.js.map + +class UniqueStream extends ObjectTransform { + constructor() { + super(); + this._pushed = new Set(); + } + + _transform(data, encoding, callback) { + if (!this._pushed.has(data)) { + this.push(data); + this._pushed.add(data); + } + + callback(); + } +} + +module.exports = { + FilterStream, + UniqueStream +}; /***/ }), -/* 403 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/* 664 */ +/***/ (function(module, exports, __webpack_require__) { -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "throwIfEmpty", function() { return throwIfEmpty; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _util_EmptyError__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(330); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(278); -/** PURE_IMPORTS_START tslib,_util_EmptyError,_Subscriber PURE_IMPORTS_END */ +var fs = __webpack_require__(23) +var polyfills = __webpack_require__(665) +var legacy = __webpack_require__(666) +var clone = __webpack_require__(667) +var util = __webpack_require__(29) +/* istanbul ignore next - node 0.x polyfill */ +var gracefulQueue +var previousSymbol -function throwIfEmpty(errorFactory) { - if (errorFactory === void 0) { - errorFactory = defaultErrorFactory; - } - return function (source) { - return source.lift(new ThrowIfEmptyOperator(errorFactory)); - }; +/* istanbul ignore else - node 0.x polyfill */ +if (typeof Symbol === 'function' && typeof Symbol.for === 'function') { + gracefulQueue = Symbol.for('graceful-fs.queue') + // This is used in testing by future versions + previousSymbol = Symbol.for('graceful-fs.previous') +} else { + gracefulQueue = '___graceful-fs.queue' + previousSymbol = '___graceful-fs.previous' } -var ThrowIfEmptyOperator = /*@__PURE__*/ (function () { - function ThrowIfEmptyOperator(errorFactory) { - this.errorFactory = errorFactory; - } - ThrowIfEmptyOperator.prototype.call = function (subscriber, source) { - return source.subscribe(new ThrowIfEmptySubscriber(subscriber, this.errorFactory)); - }; - return ThrowIfEmptyOperator; -}()); -var ThrowIfEmptySubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](ThrowIfEmptySubscriber, _super); - function ThrowIfEmptySubscriber(destination, errorFactory) { - var _this = _super.call(this, destination) || this; - _this.errorFactory = errorFactory; - _this.hasValue = false; - return _this; + +function noop () {} + +var debug = noop +if (util.debuglog) + debug = util.debuglog('gfs4') +else if (/\bgfs4\b/i.test(process.env.NODE_DEBUG || '')) + debug = function() { + var m = util.format.apply(util, arguments) + m = 'GFS4: ' + m.split(/\n/).join('\nGFS4: ') + console.error(m) + } + +// Once time initialization +if (!global[gracefulQueue]) { + // This queue can be shared by multiple loaded instances + var queue = [] + Object.defineProperty(global, gracefulQueue, { + get: function() { + return queue } - ThrowIfEmptySubscriber.prototype._next = function (value) { - this.hasValue = true; - this.destination.next(value); - }; - ThrowIfEmptySubscriber.prototype._complete = function () { - if (!this.hasValue) { - var err = void 0; - try { - err = this.errorFactory(); - } - catch (e) { - err = e; - } - this.destination.error(err); - } - else { - return this.destination.complete(); + }) + + // Patch fs.close/closeSync to shared queue version, because we need + // to retry() whenever a close happens *anywhere* in the program. + // This is essential when multiple graceful-fs instances are + // in play at the same time. + fs.close = (function (fs$close) { + function close (fd, cb) { + return fs$close.call(fs, fd, function (err) { + // This function uses the graceful-fs shared queue + if (!err) { + retry() } - }; - return ThrowIfEmptySubscriber; -}(_Subscriber__WEBPACK_IMPORTED_MODULE_2__["Subscriber"])); -function defaultErrorFactory() { - return new _util_EmptyError__WEBPACK_IMPORTED_MODULE_1__["EmptyError"](); -} -//# sourceMappingURL=throwIfEmpty.js.map + if (typeof cb === 'function') + cb.apply(this, arguments) + }) + } -/***/ }), -/* 404 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + Object.defineProperty(close, previousSymbol, { + value: fs$close + }) + return close + })(fs.close) -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "take", function() { return take; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); -/* harmony import */ var _util_ArgumentOutOfRangeError__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(329); -/* harmony import */ var _observable_empty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(310); -/** PURE_IMPORTS_START tslib,_Subscriber,_util_ArgumentOutOfRangeError,_observable_empty PURE_IMPORTS_END */ + fs.closeSync = (function (fs$closeSync) { + function closeSync (fd) { + // This function uses the graceful-fs shared queue + fs$closeSync.apply(fs, arguments) + retry() + } + + Object.defineProperty(closeSync, previousSymbol, { + value: fs$closeSync + }) + return closeSync + })(fs.closeSync) + + if (/\bgfs4\b/i.test(process.env.NODE_DEBUG || '')) { + process.on('exit', function() { + debug(global[gracefulQueue]) + __webpack_require__(30).equal(global[gracefulQueue].length, 0) + }) + } +} + +module.exports = patch(clone(fs)) +if (process.env.TEST_GRACEFUL_FS_GLOBAL_PATCH && !fs.__patched) { + module.exports = patch(fs) + fs.__patched = true; +} +function patch (fs) { + // Everything that references the open() function needs to be in here + polyfills(fs) + fs.gracefulify = patch + fs.createReadStream = createReadStream + fs.createWriteStream = createWriteStream + var fs$readFile = fs.readFile + fs.readFile = readFile + function readFile (path, options, cb) { + if (typeof options === 'function') + cb = options, options = null + return go$readFile(path, options, cb) -function take(count) { - return function (source) { - if (count === 0) { - return Object(_observable_empty__WEBPACK_IMPORTED_MODULE_3__["empty"])(); - } + function go$readFile (path, options, cb) { + return fs$readFile(path, options, function (err) { + if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) + enqueue([go$readFile, [path, options, cb]]) else { - return source.lift(new TakeOperator(count)); - } - }; -} -var TakeOperator = /*@__PURE__*/ (function () { - function TakeOperator(total) { - this.total = total; - if (this.total < 0) { - throw new _util_ArgumentOutOfRangeError__WEBPACK_IMPORTED_MODULE_2__["ArgumentOutOfRangeError"]; + if (typeof cb === 'function') + cb.apply(this, arguments) + retry() } + }) } - TakeOperator.prototype.call = function (subscriber, source) { - return source.subscribe(new TakeSubscriber(subscriber, this.total)); - }; - return TakeOperator; -}()); -var TakeSubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](TakeSubscriber, _super); - function TakeSubscriber(destination, total) { - var _this = _super.call(this, destination) || this; - _this.total = total; - _this.count = 0; - return _this; + } + + var fs$writeFile = fs.writeFile + fs.writeFile = writeFile + function writeFile (path, data, options, cb) { + if (typeof options === 'function') + cb = options, options = null + + return go$writeFile(path, data, options, cb) + + function go$writeFile (path, data, options, cb) { + return fs$writeFile(path, data, options, function (err) { + if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) + enqueue([go$writeFile, [path, data, options, cb]]) + else { + if (typeof cb === 'function') + cb.apply(this, arguments) + retry() + } + }) } - TakeSubscriber.prototype._next = function (value) { - var total = this.total; - var count = ++this.count; - if (count <= total) { - this.destination.next(value); - if (count === total) { - this.destination.complete(); - this.unsubscribe(); - } + } + + var fs$appendFile = fs.appendFile + if (fs$appendFile) + fs.appendFile = appendFile + function appendFile (path, data, options, cb) { + if (typeof options === 'function') + cb = options, options = null + + return go$appendFile(path, data, options, cb) + + function go$appendFile (path, data, options, cb) { + return fs$appendFile(path, data, options, function (err) { + if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) + enqueue([go$appendFile, [path, data, options, cb]]) + else { + if (typeof cb === 'function') + cb.apply(this, arguments) + retry() } - }; - return TakeSubscriber; -}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); -//# sourceMappingURL=take.js.map + }) + } + } + var fs$readdir = fs.readdir + fs.readdir = readdir + function readdir (path, options, cb) { + var args = [path] + if (typeof options !== 'function') { + args.push(options) + } else { + cb = options + } + args.push(go$readdir$cb) -/***/ }), -/* 405 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + return go$readdir(args) -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "endWith", function() { return endWith; }); -/* harmony import */ var _observable_concat__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(346); -/* harmony import */ var _observable_of__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(311); -/** PURE_IMPORTS_START _observable_concat,_observable_of PURE_IMPORTS_END */ + function go$readdir$cb (err, files) { + if (files && files.sort) + files.sort() + if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) + enqueue([go$readdir, [args]]) -function endWith() { - var array = []; - for (var _i = 0; _i < arguments.length; _i++) { - array[_i] = arguments[_i]; + else { + if (typeof cb === 'function') + cb.apply(this, arguments) + retry() + } } - return function (source) { return Object(_observable_concat__WEBPACK_IMPORTED_MODULE_0__["concat"])(source, _observable_of__WEBPACK_IMPORTED_MODULE_1__["of"].apply(void 0, array)); }; -} -//# sourceMappingURL=endWith.js.map + } + function go$readdir (args) { + return fs$readdir.apply(fs, args) + } -/***/ }), -/* 406 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + if (process.version.substr(0, 4) === 'v0.8') { + var legStreams = legacy(fs) + ReadStream = legStreams.ReadStream + WriteStream = legStreams.WriteStream + } -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "every", function() { return every; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); -/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ + var fs$ReadStream = fs.ReadStream + if (fs$ReadStream) { + ReadStream.prototype = Object.create(fs$ReadStream.prototype) + ReadStream.prototype.open = ReadStream$open + } + var fs$WriteStream = fs.WriteStream + if (fs$WriteStream) { + WriteStream.prototype = Object.create(fs$WriteStream.prototype) + WriteStream.prototype.open = WriteStream$open + } -function every(predicate, thisArg) { - return function (source) { return source.lift(new EveryOperator(predicate, thisArg, source)); }; -} -var EveryOperator = /*@__PURE__*/ (function () { - function EveryOperator(predicate, thisArg, source) { - this.predicate = predicate; - this.thisArg = thisArg; - this.source = source; - } - EveryOperator.prototype.call = function (observer, source) { - return source.subscribe(new EverySubscriber(observer, this.predicate, this.thisArg, this.source)); - }; - return EveryOperator; -}()); -var EverySubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](EverySubscriber, _super); - function EverySubscriber(destination, predicate, thisArg, source) { - var _this = _super.call(this, destination) || this; - _this.predicate = predicate; - _this.thisArg = thisArg; - _this.source = source; - _this.index = 0; - _this.thisArg = thisArg || _this; - return _this; - } - EverySubscriber.prototype.notifyComplete = function (everyValueMatch) { - this.destination.next(everyValueMatch); - this.destination.complete(); - }; - EverySubscriber.prototype._next = function (value) { - var result = false; - try { - result = this.predicate.call(this.thisArg, value, this.index++, this.source); - } - catch (err) { - this.destination.error(err); - return; - } - if (!result) { - this.notifyComplete(false); - } - }; - EverySubscriber.prototype._complete = function () { - this.notifyComplete(true); - }; - return EverySubscriber; -}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); -//# sourceMappingURL=every.js.map + Object.defineProperty(fs, 'ReadStream', { + get: function () { + return ReadStream + }, + set: function (val) { + ReadStream = val + }, + enumerable: true, + configurable: true + }) + Object.defineProperty(fs, 'WriteStream', { + get: function () { + return WriteStream + }, + set: function (val) { + WriteStream = val + }, + enumerable: true, + configurable: true + }) + + // legacy names + Object.defineProperty(fs, 'FileReadStream', { + get: function () { + return ReadStream + }, + set: function (val) { + ReadStream = val + }, + enumerable: true, + configurable: true + }) + Object.defineProperty(fs, 'FileWriteStream', { + get: function () { + return WriteStream + }, + set: function (val) { + WriteStream = val + }, + enumerable: true, + configurable: true + }) + function ReadStream (path, options) { + if (this instanceof ReadStream) + return fs$ReadStream.apply(this, arguments), this + else + return ReadStream.apply(Object.create(ReadStream.prototype), arguments) + } + + function ReadStream$open () { + var that = this + open(that.path, that.flags, that.mode, function (err, fd) { + if (err) { + if (that.autoClose) + that.destroy() + + that.emit('error', err) + } else { + that.fd = fd + that.emit('open', fd) + that.read() + } + }) + } + + function WriteStream (path, options) { + if (this instanceof WriteStream) + return fs$WriteStream.apply(this, arguments), this + else + return WriteStream.apply(Object.create(WriteStream.prototype), arguments) + } + + function WriteStream$open () { + var that = this + open(that.path, that.flags, that.mode, function (err, fd) { + if (err) { + that.destroy() + that.emit('error', err) + } else { + that.fd = fd + that.emit('open', fd) + } + }) + } -/***/ }), -/* 407 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + function createReadStream (path, options) { + return new fs.ReadStream(path, options) + } -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "exhaust", function() { return exhaust; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(336); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(337); -/** PURE_IMPORTS_START tslib,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ + function createWriteStream (path, options) { + return new fs.WriteStream(path, options) + } + var fs$open = fs.open + fs.open = open + function open (path, flags, mode, cb) { + if (typeof mode === 'function') + cb = mode, mode = null + return go$open(path, flags, mode, cb) -function exhaust() { - return function (source) { return source.lift(new SwitchFirstOperator()); }; -} -var SwitchFirstOperator = /*@__PURE__*/ (function () { - function SwitchFirstOperator() { - } - SwitchFirstOperator.prototype.call = function (subscriber, source) { - return source.subscribe(new SwitchFirstSubscriber(subscriber)); - }; - return SwitchFirstOperator; -}()); -var SwitchFirstSubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](SwitchFirstSubscriber, _super); - function SwitchFirstSubscriber(destination) { - var _this = _super.call(this, destination) || this; - _this.hasCompleted = false; - _this.hasSubscription = false; - return _this; - } - SwitchFirstSubscriber.prototype._next = function (value) { - if (!this.hasSubscription) { - this.hasSubscription = true; - this.add(Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__["subscribeToResult"])(this, value)); - } - }; - SwitchFirstSubscriber.prototype._complete = function () { - this.hasCompleted = true; - if (!this.hasSubscription) { - this.destination.complete(); - } - }; - SwitchFirstSubscriber.prototype.notifyComplete = function (innerSub) { - this.remove(innerSub); - this.hasSubscription = false; - if (this.hasCompleted) { - this.destination.complete(); + function go$open (path, flags, mode, cb) { + return fs$open(path, flags, mode, function (err, fd) { + if (err && (err.code === 'EMFILE' || err.code === 'ENFILE')) + enqueue([go$open, [path, flags, mode, cb]]) + else { + if (typeof cb === 'function') + cb.apply(this, arguments) + retry() } - }; - return SwitchFirstSubscriber; -}(_OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__["OuterSubscriber"])); -//# sourceMappingURL=exhaust.js.map + }) + } + } + return fs +} -/***/ }), -/* 408 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +function enqueue (elem) { + debug('ENQUEUE', elem[0].name, elem[1]) + global[gracefulQueue].push(elem) +} -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "exhaustMap", function() { return exhaustMap; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(336); -/* harmony import */ var _InnerSubscriber__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(338); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(337); -/* harmony import */ var _map__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(333); -/* harmony import */ var _observable_from__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(350); -/** PURE_IMPORTS_START tslib,_OuterSubscriber,_InnerSubscriber,_util_subscribeToResult,_map,_observable_from PURE_IMPORTS_END */ +function retry () { + var elem = global[gracefulQueue].shift() + if (elem) { + debug('RETRY', elem[0].name, elem[1]) + elem[0].apply(null, elem[1]) + } +} +/***/ }), +/* 665 */ +/***/ (function(module, exports, __webpack_require__) { +var constants = __webpack_require__(25) +var origCwd = process.cwd +var cwd = null +var platform = process.env.GRACEFUL_FS_PLATFORM || process.platform -function exhaustMap(project, resultSelector) { - if (resultSelector) { - return function (source) { return source.pipe(exhaustMap(function (a, i) { return Object(_observable_from__WEBPACK_IMPORTED_MODULE_5__["from"])(project(a, i)).pipe(Object(_map__WEBPACK_IMPORTED_MODULE_4__["map"])(function (b, ii) { return resultSelector(a, b, i, ii); })); })); }; - } - return function (source) { - return source.lift(new ExhaustMapOperator(project)); - }; +process.cwd = function() { + if (!cwd) + cwd = origCwd.call(process) + return cwd } -var ExhaustMapOperator = /*@__PURE__*/ (function () { - function ExhaustMapOperator(project) { - this.project = project; - } - ExhaustMapOperator.prototype.call = function (subscriber, source) { - return source.subscribe(new ExhaustMapSubscriber(subscriber, this.project)); - }; - return ExhaustMapOperator; -}()); -var ExhaustMapSubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](ExhaustMapSubscriber, _super); - function ExhaustMapSubscriber(destination, project) { - var _this = _super.call(this, destination) || this; - _this.project = project; - _this.hasSubscription = false; - _this.hasCompleted = false; - _this.index = 0; - return _this; - } - ExhaustMapSubscriber.prototype._next = function (value) { - if (!this.hasSubscription) { - this.tryNext(value); - } - }; - ExhaustMapSubscriber.prototype.tryNext = function (value) { - var result; - var index = this.index++; - try { - result = this.project(value, index); - } - catch (err) { - this.destination.error(err); - return; - } - this.hasSubscription = true; - this._innerSub(result, value, index); - }; - ExhaustMapSubscriber.prototype._innerSub = function (result, value, index) { - var innerSubscriber = new _InnerSubscriber__WEBPACK_IMPORTED_MODULE_2__["InnerSubscriber"](this, undefined, undefined); - var destination = this.destination; - destination.add(innerSubscriber); - Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_3__["subscribeToResult"])(this, result, value, index, innerSubscriber); - }; - ExhaustMapSubscriber.prototype._complete = function () { - this.hasCompleted = true; - if (!this.hasSubscription) { - this.destination.complete(); - } - this.unsubscribe(); - }; - ExhaustMapSubscriber.prototype.notifyNext = function (outerValue, innerValue, outerIndex, innerIndex, innerSub) { - this.destination.next(innerValue); - }; - ExhaustMapSubscriber.prototype.notifyError = function (err) { - this.destination.error(err); - }; - ExhaustMapSubscriber.prototype.notifyComplete = function (innerSub) { - var destination = this.destination; - destination.remove(innerSub); - this.hasSubscription = false; - if (this.hasCompleted) { - this.destination.complete(); - } - }; - return ExhaustMapSubscriber; -}(_OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__["OuterSubscriber"])); -//# sourceMappingURL=exhaustMap.js.map - +try { + process.cwd() +} catch (er) {} -/***/ }), -/* 409 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +var chdir = process.chdir +process.chdir = function(d) { + cwd = null + chdir.call(process, d) +} -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "expand", function() { return expand; }); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ExpandOperator", function() { return ExpandOperator; }); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ExpandSubscriber", function() { return ExpandSubscriber; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(336); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(337); -/** PURE_IMPORTS_START tslib,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ +module.exports = patch +function patch (fs) { + // (re-)implement some things that are known busted or missing. + // lchmod, broken prior to 0.6.2 + // back-port the fix here. + if (constants.hasOwnProperty('O_SYMLINK') && + process.version.match(/^v0\.6\.[0-2]|^v0\.5\./)) { + patchLchmod(fs) + } -function expand(project, concurrent, scheduler) { - if (concurrent === void 0) { - concurrent = Number.POSITIVE_INFINITY; - } - if (scheduler === void 0) { - scheduler = undefined; - } - concurrent = (concurrent || 0) < 1 ? Number.POSITIVE_INFINITY : concurrent; - return function (source) { return source.lift(new ExpandOperator(project, concurrent, scheduler)); }; -} -var ExpandOperator = /*@__PURE__*/ (function () { - function ExpandOperator(project, concurrent, scheduler) { - this.project = project; - this.concurrent = concurrent; - this.scheduler = scheduler; - } - ExpandOperator.prototype.call = function (subscriber, source) { - return source.subscribe(new ExpandSubscriber(subscriber, this.project, this.concurrent, this.scheduler)); - }; - return ExpandOperator; -}()); + // lutimes implementation, or no-op + if (!fs.lutimes) { + patchLutimes(fs) + } -var ExpandSubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](ExpandSubscriber, _super); - function ExpandSubscriber(destination, project, concurrent, scheduler) { - var _this = _super.call(this, destination) || this; - _this.project = project; - _this.concurrent = concurrent; - _this.scheduler = scheduler; - _this.index = 0; - _this.active = 0; - _this.hasCompleted = false; - if (concurrent < Number.POSITIVE_INFINITY) { - _this.buffer = []; - } - return _this; - } - ExpandSubscriber.dispatch = function (arg) { - var subscriber = arg.subscriber, result = arg.result, value = arg.value, index = arg.index; - subscriber.subscribeToProjection(result, value, index); - }; - ExpandSubscriber.prototype._next = function (value) { - var destination = this.destination; - if (destination.closed) { - this._complete(); - return; - } - var index = this.index++; - if (this.active < this.concurrent) { - destination.next(value); - try { - var project = this.project; - var result = project(value, index); - if (!this.scheduler) { - this.subscribeToProjection(result, value, index); - } - else { - var state = { subscriber: this, result: result, value: value, index: index }; - var destination_1 = this.destination; - destination_1.add(this.scheduler.schedule(ExpandSubscriber.dispatch, 0, state)); - } - } - catch (e) { - destination.error(e); - } - } - else { - this.buffer.push(value); - } - }; - ExpandSubscriber.prototype.subscribeToProjection = function (result, value, index) { - this.active++; - var destination = this.destination; - destination.add(Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__["subscribeToResult"])(this, result, value, index)); - }; - ExpandSubscriber.prototype._complete = function () { - this.hasCompleted = true; - if (this.hasCompleted && this.active === 0) { - this.destination.complete(); - } - this.unsubscribe(); - }; - ExpandSubscriber.prototype.notifyNext = function (outerValue, innerValue, outerIndex, innerIndex, innerSub) { - this._next(innerValue); - }; - ExpandSubscriber.prototype.notifyComplete = function (innerSub) { - var buffer = this.buffer; - var destination = this.destination; - destination.remove(innerSub); - this.active--; - if (buffer && buffer.length > 0) { - this._next(buffer.shift()); - } - if (this.hasCompleted && this.active === 0) { - this.destination.complete(); - } - }; - return ExpandSubscriber; -}(_OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__["OuterSubscriber"])); + // https://github.com/isaacs/node-graceful-fs/issues/4 + // Chown should not fail on einval or eperm if non-root. + // It should not fail on enosys ever, as this just indicates + // that a fs doesn't support the intended operation. -//# sourceMappingURL=expand.js.map + fs.chown = chownFix(fs.chown) + fs.fchown = chownFix(fs.fchown) + fs.lchown = chownFix(fs.lchown) + fs.chmod = chmodFix(fs.chmod) + fs.fchmod = chmodFix(fs.fchmod) + fs.lchmod = chmodFix(fs.lchmod) -/***/ }), -/* 410 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + fs.chownSync = chownFixSync(fs.chownSync) + fs.fchownSync = chownFixSync(fs.fchownSync) + fs.lchownSync = chownFixSync(fs.lchownSync) -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "finalize", function() { return finalize; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); -/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(284); -/** PURE_IMPORTS_START tslib,_Subscriber,_Subscription PURE_IMPORTS_END */ + fs.chmodSync = chmodFixSync(fs.chmodSync) + fs.fchmodSync = chmodFixSync(fs.fchmodSync) + fs.lchmodSync = chmodFixSync(fs.lchmodSync) + fs.stat = statFix(fs.stat) + fs.fstat = statFix(fs.fstat) + fs.lstat = statFix(fs.lstat) + fs.statSync = statFixSync(fs.statSync) + fs.fstatSync = statFixSync(fs.fstatSync) + fs.lstatSync = statFixSync(fs.lstatSync) -function finalize(callback) { - return function (source) { return source.lift(new FinallyOperator(callback)); }; -} -var FinallyOperator = /*@__PURE__*/ (function () { - function FinallyOperator(callback) { - this.callback = callback; + // if lchmod/lchown do not exist, then make them no-ops + if (!fs.lchmod) { + fs.lchmod = function (path, mode, cb) { + if (cb) process.nextTick(cb) } - FinallyOperator.prototype.call = function (subscriber, source) { - return source.subscribe(new FinallySubscriber(subscriber, this.callback)); - }; - return FinallyOperator; -}()); -var FinallySubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](FinallySubscriber, _super); - function FinallySubscriber(destination, callback) { - var _this = _super.call(this, destination) || this; - _this.add(new _Subscription__WEBPACK_IMPORTED_MODULE_2__["Subscription"](callback)); - return _this; + fs.lchmodSync = function () {} + } + if (!fs.lchown) { + fs.lchown = function (path, uid, gid, cb) { + if (cb) process.nextTick(cb) } - return FinallySubscriber; -}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); -//# sourceMappingURL=finalize.js.map + fs.lchownSync = function () {} + } + // on Windows, A/V software can lock the directory, causing this + // to fail with an EACCES or EPERM if the directory contains newly + // created files. Try again on failure, for up to 60 seconds. -/***/ }), -/* 411 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + // Set the timeout this long because some Windows Anti-Virus, such as Parity + // bit9, may lock files for up to a minute, causing npm package install + // failures. Also, take care to yield the scheduler. Windows scheduling gives + // CPU to a busy looping process, which can cause the program causing the lock + // contention to be starved of CPU by node, so the contention doesn't resolve. + if (platform === "win32") { + fs.rename = (function (fs$rename) { return function (from, to, cb) { + var start = Date.now() + var backoff = 0; + fs$rename(from, to, function CB (er) { + if (er + && (er.code === "EACCES" || er.code === "EPERM") + && Date.now() - start < 60000) { + setTimeout(function() { + fs.stat(to, function (stater, st) { + if (stater && stater.code === "ENOENT") + fs$rename(from, to, CB); + else + cb(er) + }) + }, backoff) + if (backoff < 100) + backoff += 10; + return; + } + if (cb) cb(er) + }) + }})(fs.rename) + } -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "find", function() { return find; }); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "FindValueOperator", function() { return FindValueOperator; }); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "FindValueSubscriber", function() { return FindValueSubscriber; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); -/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ + // if read() returns EAGAIN, then just try it again. + fs.read = (function (fs$read) { + function read (fd, buffer, offset, length, position, callback_) { + var callback + if (callback_ && typeof callback_ === 'function') { + var eagCounter = 0 + callback = function (er, _, __) { + if (er && er.code === 'EAGAIN' && eagCounter < 10) { + eagCounter ++ + return fs$read.call(fs, fd, buffer, offset, length, position, callback) + } + callback_.apply(this, arguments) + } + } + return fs$read.call(fs, fd, buffer, offset, length, position, callback) + } + // This ensures `util.promisify` works as it does for native `fs.read`. + read.__proto__ = fs$read + return read + })(fs.read) -function find(predicate, thisArg) { - if (typeof predicate !== 'function') { - throw new TypeError('predicate is not a function'); - } - return function (source) { return source.lift(new FindValueOperator(predicate, source, false, thisArg)); }; -} -var FindValueOperator = /*@__PURE__*/ (function () { - function FindValueOperator(predicate, source, yieldIndex, thisArg) { - this.predicate = predicate; - this.source = source; - this.yieldIndex = yieldIndex; - this.thisArg = thisArg; + fs.readSync = (function (fs$readSync) { return function (fd, buffer, offset, length, position) { + var eagCounter = 0 + while (true) { + try { + return fs$readSync.call(fs, fd, buffer, offset, length, position) + } catch (er) { + if (er.code === 'EAGAIN' && eagCounter < 10) { + eagCounter ++ + continue + } + throw er + } } - FindValueOperator.prototype.call = function (observer, source) { - return source.subscribe(new FindValueSubscriber(observer, this.predicate, this.source, this.yieldIndex, this.thisArg)); - }; - return FindValueOperator; -}()); + }})(fs.readSync) -var FindValueSubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](FindValueSubscriber, _super); - function FindValueSubscriber(destination, predicate, source, yieldIndex, thisArg) { - var _this = _super.call(this, destination) || this; - _this.predicate = predicate; - _this.source = source; - _this.yieldIndex = yieldIndex; - _this.thisArg = thisArg; - _this.index = 0; - return _this; - } - FindValueSubscriber.prototype.notifyComplete = function (value) { - var destination = this.destination; - destination.next(value); - destination.complete(); - this.unsubscribe(); - }; - FindValueSubscriber.prototype._next = function (value) { - var _a = this, predicate = _a.predicate, thisArg = _a.thisArg; - var index = this.index++; - try { - var result = predicate.call(thisArg || this, value, index, this.source); - if (result) { - this.notifyComplete(this.yieldIndex ? index : value); - } + function patchLchmod (fs) { + fs.lchmod = function (path, mode, callback) { + fs.open( path + , constants.O_WRONLY | constants.O_SYMLINK + , mode + , function (err, fd) { + if (err) { + if (callback) callback(err) + return } - catch (err) { - this.destination.error(err); + // prefer to return the chmod error, if one occurs, + // but still try to close, and report closing errors if they occur. + fs.fchmod(fd, mode, function (err) { + fs.close(fd, function(err2) { + if (callback) callback(err || err2) + }) + }) + }) + } + + fs.lchmodSync = function (path, mode) { + var fd = fs.openSync(path, constants.O_WRONLY | constants.O_SYMLINK, mode) + + // prefer to return the chmod error, if one occurs, + // but still try to close, and report closing errors if they occur. + var threw = true + var ret + try { + ret = fs.fchmodSync(fd, mode) + threw = false + } finally { + if (threw) { + try { + fs.closeSync(fd) + } catch (er) {} + } else { + fs.closeSync(fd) } - }; - FindValueSubscriber.prototype._complete = function () { - this.notifyComplete(this.yieldIndex ? -1 : undefined); - }; - return FindValueSubscriber; -}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); + } + return ret + } + } -//# sourceMappingURL=find.js.map + function patchLutimes (fs) { + if (constants.hasOwnProperty("O_SYMLINK")) { + fs.lutimes = function (path, at, mt, cb) { + fs.open(path, constants.O_SYMLINK, function (er, fd) { + if (er) { + if (cb) cb(er) + return + } + fs.futimes(fd, at, mt, function (er) { + fs.close(fd, function (er2) { + if (cb) cb(er || er2) + }) + }) + }) + } + fs.lutimesSync = function (path, at, mt) { + var fd = fs.openSync(path, constants.O_SYMLINK) + var ret + var threw = true + try { + ret = fs.futimesSync(fd, at, mt) + threw = false + } finally { + if (threw) { + try { + fs.closeSync(fd) + } catch (er) {} + } else { + fs.closeSync(fd) + } + } + return ret + } -/***/ }), -/* 412 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + } else { + fs.lutimes = function (_a, _b, _c, cb) { if (cb) process.nextTick(cb) } + fs.lutimesSync = function () {} + } + } -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "findIndex", function() { return findIndex; }); -/* harmony import */ var _operators_find__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(411); -/** PURE_IMPORTS_START _operators_find PURE_IMPORTS_END */ + function chmodFix (orig) { + if (!orig) return orig + return function (target, mode, cb) { + return orig.call(fs, target, mode, function (er) { + if (chownErOk(er)) er = null + if (cb) cb.apply(this, arguments) + }) + } + } -function findIndex(predicate, thisArg) { - return function (source) { return source.lift(new _operators_find__WEBPACK_IMPORTED_MODULE_0__["FindValueOperator"](predicate, source, true, thisArg)); }; -} -//# sourceMappingURL=findIndex.js.map + function chmodFixSync (orig) { + if (!orig) return orig + return function (target, mode) { + try { + return orig.call(fs, target, mode) + } catch (er) { + if (!chownErOk(er)) throw er + } + } + } -/***/ }), -/* 413 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + function chownFix (orig) { + if (!orig) return orig + return function (target, uid, gid, cb) { + return orig.call(fs, target, uid, gid, function (er) { + if (chownErOk(er)) er = null + if (cb) cb.apply(this, arguments) + }) + } + } -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "first", function() { return first; }); -/* harmony import */ var _util_EmptyError__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(330); -/* harmony import */ var _filter__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(371); -/* harmony import */ var _take__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(404); -/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(394); -/* harmony import */ var _throwIfEmpty__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(403); -/* harmony import */ var _util_identity__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(327); -/** PURE_IMPORTS_START _util_EmptyError,_filter,_take,_defaultIfEmpty,_throwIfEmpty,_util_identity PURE_IMPORTS_END */ + function chownFixSync (orig) { + if (!orig) return orig + return function (target, uid, gid) { + try { + return orig.call(fs, target, uid, gid) + } catch (er) { + if (!chownErOk(er)) throw er + } + } + } + function statFix (orig) { + if (!orig) return orig + // Older versions of Node erroneously returned signed integers for + // uid + gid. + return function (target, options, cb) { + if (typeof options === 'function') { + cb = options + options = null + } + function callback (er, stats) { + if (stats) { + if (stats.uid < 0) stats.uid += 0x100000000 + if (stats.gid < 0) stats.gid += 0x100000000 + } + if (cb) cb.apply(this, arguments) + } + return options ? orig.call(fs, target, options, callback) + : orig.call(fs, target, callback) + } + } + function statFixSync (orig) { + if (!orig) return orig + // Older versions of Node erroneously returned signed integers for + // uid + gid. + return function (target, options) { + var stats = options ? orig.call(fs, target, options) + : orig.call(fs, target) + if (stats.uid < 0) stats.uid += 0x100000000 + if (stats.gid < 0) stats.gid += 0x100000000 + return stats; + } + } + // ENOSYS means that the fs doesn't support the op. Just ignore + // that, because it doesn't matter. + // + // if there's no getuid, or if getuid() is something other + // than 0, and the error is EINVAL or EPERM, then just ignore + // it. + // + // This specific case is a silent failure in cp, install, tar, + // and most other unix tools that manage permissions. + // + // When running as root, or if other types of errors are + // encountered, then it's strict. + function chownErOk (er) { + if (!er) + return true + if (er.code === "ENOSYS") + return true + var nonroot = !process.getuid || process.getuid() !== 0 + if (nonroot) { + if (er.code === "EINVAL" || er.code === "EPERM") + return true + } -function first(predicate, defaultValue) { - var hasDefaultValue = arguments.length >= 2; - return function (source) { return source.pipe(predicate ? Object(_filter__WEBPACK_IMPORTED_MODULE_1__["filter"])(function (v, i) { return predicate(v, i, source); }) : _util_identity__WEBPACK_IMPORTED_MODULE_5__["identity"], Object(_take__WEBPACK_IMPORTED_MODULE_2__["take"])(1), hasDefaultValue ? Object(_defaultIfEmpty__WEBPACK_IMPORTED_MODULE_3__["defaultIfEmpty"])(defaultValue) : Object(_throwIfEmpty__WEBPACK_IMPORTED_MODULE_4__["throwIfEmpty"])(function () { return new _util_EmptyError__WEBPACK_IMPORTED_MODULE_0__["EmptyError"](); })); }; + return false + } } -//# sourceMappingURL=first.js.map /***/ }), -/* 414 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { - -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "ignoreElements", function() { return ignoreElements; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); -/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ +/* 666 */ +/***/ (function(module, exports, __webpack_require__) { +var Stream = __webpack_require__(27).Stream -function ignoreElements() { - return function ignoreElementsOperatorFunction(source) { - return source.lift(new IgnoreElementsOperator()); - }; -} -var IgnoreElementsOperator = /*@__PURE__*/ (function () { - function IgnoreElementsOperator() { - } - IgnoreElementsOperator.prototype.call = function (subscriber, source) { - return source.subscribe(new IgnoreElementsSubscriber(subscriber)); - }; - return IgnoreElementsOperator; -}()); -var IgnoreElementsSubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](IgnoreElementsSubscriber, _super); - function IgnoreElementsSubscriber() { - return _super !== null && _super.apply(this, arguments) || this; - } - IgnoreElementsSubscriber.prototype._next = function (unused) { - }; - return IgnoreElementsSubscriber; -}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); -//# sourceMappingURL=ignoreElements.js.map +module.exports = legacy +function legacy (fs) { + return { + ReadStream: ReadStream, + WriteStream: WriteStream + } -/***/ }), -/* 415 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + function ReadStream (path, options) { + if (!(this instanceof ReadStream)) return new ReadStream(path, options); -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "isEmpty", function() { return isEmpty; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); -/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ + Stream.call(this); + var self = this; -function isEmpty() { - return function (source) { return source.lift(new IsEmptyOperator()); }; -} -var IsEmptyOperator = /*@__PURE__*/ (function () { - function IsEmptyOperator() { - } - IsEmptyOperator.prototype.call = function (observer, source) { - return source.subscribe(new IsEmptySubscriber(observer)); - }; - return IsEmptyOperator; -}()); -var IsEmptySubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](IsEmptySubscriber, _super); - function IsEmptySubscriber(destination) { - return _super.call(this, destination) || this; - } - IsEmptySubscriber.prototype.notifyComplete = function (isEmpty) { - var destination = this.destination; - destination.next(isEmpty); - destination.complete(); - }; - IsEmptySubscriber.prototype._next = function (value) { - this.notifyComplete(false); - }; - IsEmptySubscriber.prototype._complete = function () { - this.notifyComplete(true); - }; - return IsEmptySubscriber; -}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); -//# sourceMappingURL=isEmpty.js.map + this.path = path; + this.fd = null; + this.readable = true; + this.paused = false; + this.flags = 'r'; + this.mode = 438; /*=0666*/ + this.bufferSize = 64 * 1024; -/***/ }), -/* 416 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + options = options || {}; -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "last", function() { return last; }); -/* harmony import */ var _util_EmptyError__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(330); -/* harmony import */ var _filter__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(371); -/* harmony import */ var _takeLast__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(417); -/* harmony import */ var _throwIfEmpty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(403); -/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(394); -/* harmony import */ var _util_identity__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(327); -/** PURE_IMPORTS_START _util_EmptyError,_filter,_takeLast,_throwIfEmpty,_defaultIfEmpty,_util_identity PURE_IMPORTS_END */ + // Mixin options into this + var keys = Object.keys(options); + for (var index = 0, length = keys.length; index < length; index++) { + var key = keys[index]; + this[key] = options[key]; + } + if (this.encoding) this.setEncoding(this.encoding); + if (this.start !== undefined) { + if ('number' !== typeof this.start) { + throw TypeError('start must be a Number'); + } + if (this.end === undefined) { + this.end = Infinity; + } else if ('number' !== typeof this.end) { + throw TypeError('end must be a Number'); + } + if (this.start > this.end) { + throw new Error('start must be <= end'); + } + this.pos = this.start; + } + if (this.fd !== null) { + process.nextTick(function() { + self._read(); + }); + return; + } -function last(predicate, defaultValue) { - var hasDefaultValue = arguments.length >= 2; - return function (source) { return source.pipe(predicate ? Object(_filter__WEBPACK_IMPORTED_MODULE_1__["filter"])(function (v, i) { return predicate(v, i, source); }) : _util_identity__WEBPACK_IMPORTED_MODULE_5__["identity"], Object(_takeLast__WEBPACK_IMPORTED_MODULE_2__["takeLast"])(1), hasDefaultValue ? Object(_defaultIfEmpty__WEBPACK_IMPORTED_MODULE_4__["defaultIfEmpty"])(defaultValue) : Object(_throwIfEmpty__WEBPACK_IMPORTED_MODULE_3__["throwIfEmpty"])(function () { return new _util_EmptyError__WEBPACK_IMPORTED_MODULE_0__["EmptyError"](); })); }; -} -//# sourceMappingURL=last.js.map + fs.open(this.path, this.flags, this.mode, function (err, fd) { + if (err) { + self.emit('error', err); + self.readable = false; + return; + } + self.fd = fd; + self.emit('open', fd); + self._read(); + }) + } -/***/ }), -/* 417 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + function WriteStream (path, options) { + if (!(this instanceof WriteStream)) return new WriteStream(path, options); -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "takeLast", function() { return takeLast; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); -/* harmony import */ var _util_ArgumentOutOfRangeError__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(329); -/* harmony import */ var _observable_empty__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(310); -/** PURE_IMPORTS_START tslib,_Subscriber,_util_ArgumentOutOfRangeError,_observable_empty PURE_IMPORTS_END */ + Stream.call(this); + this.path = path; + this.fd = null; + this.writable = true; + this.flags = 'w'; + this.encoding = 'binary'; + this.mode = 438; /*=0666*/ + this.bytesWritten = 0; + options = options || {}; -function takeLast(count) { - return function takeLastOperatorFunction(source) { - if (count === 0) { - return Object(_observable_empty__WEBPACK_IMPORTED_MODULE_3__["empty"])(); - } - else { - return source.lift(new TakeLastOperator(count)); - } - }; -} -var TakeLastOperator = /*@__PURE__*/ (function () { - function TakeLastOperator(total) { - this.total = total; - if (this.total < 0) { - throw new _util_ArgumentOutOfRangeError__WEBPACK_IMPORTED_MODULE_2__["ArgumentOutOfRangeError"]; - } - } - TakeLastOperator.prototype.call = function (subscriber, source) { - return source.subscribe(new TakeLastSubscriber(subscriber, this.total)); - }; - return TakeLastOperator; -}()); -var TakeLastSubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](TakeLastSubscriber, _super); - function TakeLastSubscriber(destination, total) { - var _this = _super.call(this, destination) || this; - _this.total = total; - _this.ring = new Array(); - _this.count = 0; - return _this; + // Mixin options into this + var keys = Object.keys(options); + for (var index = 0, length = keys.length; index < length; index++) { + var key = keys[index]; + this[key] = options[key]; } - TakeLastSubscriber.prototype._next = function (value) { - var ring = this.ring; - var total = this.total; - var count = this.count++; - if (ring.length < total) { - ring.push(value); - } - else { - var index = count % total; - ring[index] = value; - } - }; - TakeLastSubscriber.prototype._complete = function () { - var destination = this.destination; - var count = this.count; - if (count > 0) { - var total = this.count >= this.total ? this.total : this.count; - var ring = this.ring; - for (var i = 0; i < total; i++) { - var idx = (count++) % total; - destination.next(ring[idx]); - } - } - destination.complete(); - }; - return TakeLastSubscriber; -}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); -//# sourceMappingURL=takeLast.js.map - -/***/ }), -/* 418 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + if (this.start !== undefined) { + if ('number' !== typeof this.start) { + throw TypeError('start must be a Number'); + } + if (this.start < 0) { + throw new Error('start must be >= zero'); + } -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "mapTo", function() { return mapTo; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); -/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ + this.pos = this.start; + } + this.busy = false; + this._queue = []; -function mapTo(value) { - return function (source) { return source.lift(new MapToOperator(value)); }; -} -var MapToOperator = /*@__PURE__*/ (function () { - function MapToOperator(value) { - this.value = value; - } - MapToOperator.prototype.call = function (subscriber, source) { - return source.subscribe(new MapToSubscriber(subscriber, this.value)); - }; - return MapToOperator; -}()); -var MapToSubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](MapToSubscriber, _super); - function MapToSubscriber(destination, value) { - var _this = _super.call(this, destination) || this; - _this.value = value; - return _this; + if (this.fd === null) { + this._open = fs.open; + this._queue.push([this._open, this.path, this.flags, this.mode, undefined]); + this.flush(); } - MapToSubscriber.prototype._next = function (x) { - this.destination.next(this.value); - }; - return MapToSubscriber; -}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); -//# sourceMappingURL=mapTo.js.map + } +} /***/ }), -/* 419 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/* 667 */ +/***/ (function(module, exports, __webpack_require__) { "use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "materialize", function() { return materialize; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); -/* harmony import */ var _Notification__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(309); -/** PURE_IMPORTS_START tslib,_Subscriber,_Notification PURE_IMPORTS_END */ - -function materialize() { - return function materializeOperatorFunction(source) { - return source.lift(new MaterializeOperator()); - }; -} -var MaterializeOperator = /*@__PURE__*/ (function () { - function MaterializeOperator() { - } - MaterializeOperator.prototype.call = function (subscriber, source) { - return source.subscribe(new MaterializeSubscriber(subscriber)); - }; - return MaterializeOperator; -}()); -var MaterializeSubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](MaterializeSubscriber, _super); - function MaterializeSubscriber(destination) { - return _super.call(this, destination) || this; - } - MaterializeSubscriber.prototype._next = function (value) { - this.destination.next(_Notification__WEBPACK_IMPORTED_MODULE_2__["Notification"].createNext(value)); - }; - MaterializeSubscriber.prototype._error = function (err) { - var destination = this.destination; - destination.next(_Notification__WEBPACK_IMPORTED_MODULE_2__["Notification"].createError(err)); - destination.complete(); - }; - MaterializeSubscriber.prototype._complete = function () { - var destination = this.destination; - destination.next(_Notification__WEBPACK_IMPORTED_MODULE_2__["Notification"].createComplete()); - destination.complete(); - }; - return MaterializeSubscriber; -}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); -//# sourceMappingURL=materialize.js.map - +module.exports = clone -/***/ }), -/* 420 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +function clone (obj) { + if (obj === null || typeof obj !== 'object') + return obj -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "max", function() { return max; }); -/* harmony import */ var _reduce__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(421); -/** PURE_IMPORTS_START _reduce PURE_IMPORTS_END */ + if (obj instanceof Object) + var copy = { __proto__: obj.__proto__ } + else + var copy = Object.create(null) -function max(comparer) { - var max = (typeof comparer === 'function') - ? function (x, y) { return comparer(x, y) > 0 ? x : y; } - : function (x, y) { return x > y ? x : y; }; - return Object(_reduce__WEBPACK_IMPORTED_MODULE_0__["reduce"])(max); + Object.getOwnPropertyNames(obj).forEach(function (key) { + Object.defineProperty(copy, key, Object.getOwnPropertyDescriptor(obj, key)) + }) + + return copy } -//# sourceMappingURL=max.js.map /***/ }), -/* 421 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/* 668 */ +/***/ (function(module, exports, __webpack_require__) { "use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "reduce", function() { return reduce; }); -/* harmony import */ var _scan__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(422); -/* harmony import */ var _takeLast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(417); -/* harmony import */ var _defaultIfEmpty__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(394); -/* harmony import */ var _util_pipe__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(291); -/** PURE_IMPORTS_START _scan,_takeLast,_defaultIfEmpty,_util_pipe PURE_IMPORTS_END */ +const path = __webpack_require__(16); + +module.exports = path_ => { + let cwd = process.cwd(); + path_ = path.resolve(path_); + if (process.platform === 'win32') { + cwd = cwd.toLowerCase(); + path_ = path_.toLowerCase(); + } -function reduce(accumulator, seed) { - if (arguments.length >= 2) { - return function reduceOperatorFunctionWithSeed(source) { - return Object(_util_pipe__WEBPACK_IMPORTED_MODULE_3__["pipe"])(Object(_scan__WEBPACK_IMPORTED_MODULE_0__["scan"])(accumulator, seed), Object(_takeLast__WEBPACK_IMPORTED_MODULE_1__["takeLast"])(1), Object(_defaultIfEmpty__WEBPACK_IMPORTED_MODULE_2__["defaultIfEmpty"])(seed))(source); - }; - } - return function reduceOperatorFunction(source) { - return Object(_util_pipe__WEBPACK_IMPORTED_MODULE_3__["pipe"])(Object(_scan__WEBPACK_IMPORTED_MODULE_0__["scan"])(function (acc, value, index) { return accumulator(acc, value, index + 1); }), Object(_takeLast__WEBPACK_IMPORTED_MODULE_1__["takeLast"])(1))(source); - }; -} -//# sourceMappingURL=reduce.js.map + return path_ === cwd; +}; /***/ }), -/* 422 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/* 669 */ +/***/ (function(module, exports, __webpack_require__) { "use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "scan", function() { return scan; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); -/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ +const path = __webpack_require__(16); -function scan(accumulator, seed) { - var hasSeed = false; - if (arguments.length >= 2) { - hasSeed = true; - } - return function scanOperatorFunction(source) { - return source.lift(new ScanOperator(accumulator, seed, hasSeed)); - }; -} -var ScanOperator = /*@__PURE__*/ (function () { - function ScanOperator(accumulator, seed, hasSeed) { - if (hasSeed === void 0) { - hasSeed = false; - } - this.accumulator = accumulator; - this.seed = seed; - this.hasSeed = hasSeed; - } - ScanOperator.prototype.call = function (subscriber, source) { - return source.subscribe(new ScanSubscriber(subscriber, this.accumulator, this.seed, this.hasSeed)); - }; - return ScanOperator; -}()); -var ScanSubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](ScanSubscriber, _super); - function ScanSubscriber(destination, accumulator, _seed, hasSeed) { - var _this = _super.call(this, destination) || this; - _this.accumulator = accumulator; - _this._seed = _seed; - _this.hasSeed = hasSeed; - _this.index = 0; - return _this; - } - Object.defineProperty(ScanSubscriber.prototype, "seed", { - get: function () { - return this._seed; - }, - set: function (value) { - this.hasSeed = true; - this._seed = value; - }, - enumerable: true, - configurable: true - }); - ScanSubscriber.prototype._next = function (value) { - if (!this.hasSeed) { - this.seed = value; - this.destination.next(value); - } - else { - return this._tryNext(value); - } - }; - ScanSubscriber.prototype._tryNext = function (value) { - var index = this.index++; - var result; - try { - result = this.accumulator(this.seed, value, index); - } - catch (err) { - this.destination.error(err); - } - this.seed = result; - this.destination.next(result); - }; - return ScanSubscriber; -}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); -//# sourceMappingURL=scan.js.map +module.exports = (childPath, parentPath) => { + childPath = path.resolve(childPath); + parentPath = path.resolve(parentPath); + if (process.platform === 'win32') { + childPath = childPath.toLowerCase(); + parentPath = parentPath.toLowerCase(); + } -/***/ }), -/* 423 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + if (childPath === parentPath) { + return false; + } -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "merge", function() { return merge; }); -/* harmony import */ var _observable_merge__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(365); -/** PURE_IMPORTS_START _observable_merge PURE_IMPORTS_END */ + childPath += path.sep; + parentPath += path.sep; -function merge() { - var observables = []; - for (var _i = 0; _i < arguments.length; _i++) { - observables[_i] = arguments[_i]; - } - return function (source) { return source.lift.call(_observable_merge__WEBPACK_IMPORTED_MODULE_0__["merge"].apply(void 0, [source].concat(observables))); }; -} -//# sourceMappingURL=merge.js.map + return childPath.startsWith(parentPath); +}; /***/ }), -/* 424 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { - -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "mergeMapTo", function() { return mergeMapTo; }); -/* harmony import */ var _mergeMap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(349); -/** PURE_IMPORTS_START _mergeMap PURE_IMPORTS_END */ +/* 670 */ +/***/ (function(module, exports, __webpack_require__) { -function mergeMapTo(innerObservable, resultSelector, concurrent) { - if (concurrent === void 0) { - concurrent = Number.POSITIVE_INFINITY; - } - if (typeof resultSelector === 'function') { - return Object(_mergeMap__WEBPACK_IMPORTED_MODULE_0__["mergeMap"])(function () { return innerObservable; }, resultSelector, concurrent); - } - if (typeof resultSelector === 'number') { - concurrent = resultSelector; - } - return Object(_mergeMap__WEBPACK_IMPORTED_MODULE_0__["mergeMap"])(function () { return innerObservable; }, concurrent); +const assert = __webpack_require__(30) +const path = __webpack_require__(16) +const fs = __webpack_require__(23) +let glob = undefined +try { + glob = __webpack_require__(502) +} catch (_err) { + // treat glob as optional. } -//# sourceMappingURL=mergeMapTo.js.map - -/***/ }), -/* 425 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { - -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "mergeScan", function() { return mergeScan; }); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "MergeScanOperator", function() { return MergeScanOperator; }); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "MergeScanSubscriber", function() { return MergeScanSubscriber; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(337); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(336); -/* harmony import */ var _InnerSubscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(338); -/** PURE_IMPORTS_START tslib,_util_subscribeToResult,_OuterSubscriber,_InnerSubscriber PURE_IMPORTS_END */ +const defaultGlobOpts = { + nosort: true, + silent: true +} +// for EMFILE handling +let timeout = 0 +const isWindows = (process.platform === "win32") +const defaults = options => { + const methods = [ + 'unlink', + 'chmod', + 'stat', + 'lstat', + 'rmdir', + 'readdir' + ] + methods.forEach(m => { + options[m] = options[m] || fs[m] + m = m + 'Sync' + options[m] = options[m] || fs[m] + }) -function mergeScan(accumulator, seed, concurrent) { - if (concurrent === void 0) { - concurrent = Number.POSITIVE_INFINITY; - } - return function (source) { return source.lift(new MergeScanOperator(accumulator, seed, concurrent)); }; + options.maxBusyTries = options.maxBusyTries || 3 + options.emfileWait = options.emfileWait || 1000 + if (options.glob === false) { + options.disableGlob = true + } + if (options.disableGlob !== true && glob === undefined) { + throw Error('glob dependency not found, set `options.disableGlob = true` if intentional') + } + options.disableGlob = options.disableGlob || false + options.glob = options.glob || defaultGlobOpts } -var MergeScanOperator = /*@__PURE__*/ (function () { - function MergeScanOperator(accumulator, seed, concurrent) { - this.accumulator = accumulator; - this.seed = seed; - this.concurrent = concurrent; - } - MergeScanOperator.prototype.call = function (subscriber, source) { - return source.subscribe(new MergeScanSubscriber(subscriber, this.accumulator, this.seed, this.concurrent)); - }; - return MergeScanOperator; -}()); -var MergeScanSubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](MergeScanSubscriber, _super); - function MergeScanSubscriber(destination, accumulator, acc, concurrent) { - var _this = _super.call(this, destination) || this; - _this.accumulator = accumulator; - _this.acc = acc; - _this.concurrent = concurrent; - _this.hasValue = false; - _this.hasCompleted = false; - _this.buffer = []; - _this.active = 0; - _this.index = 0; - return _this; - } - MergeScanSubscriber.prototype._next = function (value) { - if (this.active < this.concurrent) { - var index = this.index++; - var destination = this.destination; - var ish = void 0; - try { - var accumulator = this.accumulator; - ish = accumulator(this.acc, value, index); - } - catch (e) { - return destination.error(e); - } - this.active++; - this._innerSub(ish, value, index); - } - else { - this.buffer.push(value); - } - }; - MergeScanSubscriber.prototype._innerSub = function (ish, value, index) { - var innerSubscriber = new _InnerSubscriber__WEBPACK_IMPORTED_MODULE_3__["InnerSubscriber"](this, undefined, undefined); - var destination = this.destination; - destination.add(innerSubscriber); - Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_1__["subscribeToResult"])(this, ish, value, index, innerSubscriber); - }; - MergeScanSubscriber.prototype._complete = function () { - this.hasCompleted = true; - if (this.active === 0 && this.buffer.length === 0) { - if (this.hasValue === false) { - this.destination.next(this.acc); - } - this.destination.complete(); - } - this.unsubscribe(); - }; - MergeScanSubscriber.prototype.notifyNext = function (outerValue, innerValue, outerIndex, innerIndex, innerSub) { - var destination = this.destination; - this.acc = innerValue; - this.hasValue = true; - destination.next(innerValue); - }; - MergeScanSubscriber.prototype.notifyComplete = function (innerSub) { - var buffer = this.buffer; - var destination = this.destination; - destination.remove(innerSub); - this.active--; - if (buffer.length > 0) { - this._next(buffer.shift()); - } - else if (this.active === 0 && this.hasCompleted) { - if (this.hasValue === false) { - this.destination.next(this.acc); - } - this.destination.complete(); - } - }; - return MergeScanSubscriber; -}(_OuterSubscriber__WEBPACK_IMPORTED_MODULE_2__["OuterSubscriber"])); +const rimraf = (p, options, cb) => { + if (typeof options === 'function') { + cb = options + options = {} + } -//# sourceMappingURL=mergeScan.js.map + assert(p, 'rimraf: missing path') + assert.equal(typeof p, 'string', 'rimraf: path should be a string') + assert.equal(typeof cb, 'function', 'rimraf: callback function required') + assert(options, 'rimraf: invalid options argument provided') + assert.equal(typeof options, 'object', 'rimraf: options should be object') + defaults(options) -/***/ }), -/* 426 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + let busyTries = 0 + let errState = null + let n = 0 -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "min", function() { return min; }); -/* harmony import */ var _reduce__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(421); -/** PURE_IMPORTS_START _reduce PURE_IMPORTS_END */ + const next = (er) => { + errState = errState || er + if (--n === 0) + cb(errState) + } -function min(comparer) { - var min = (typeof comparer === 'function') - ? function (x, y) { return comparer(x, y) < 0 ? x : y; } - : function (x, y) { return x < y ? x : y; }; - return Object(_reduce__WEBPACK_IMPORTED_MODULE_0__["reduce"])(min); -} -//# sourceMappingURL=min.js.map + const afterGlob = (er, results) => { + if (er) + return cb(er) + n = results.length + if (n === 0) + return cb() -/***/ }), -/* 427 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + results.forEach(p => { + const CB = (er) => { + if (er) { + if ((er.code === "EBUSY" || er.code === "ENOTEMPTY" || er.code === "EPERM") && + busyTries < options.maxBusyTries) { + busyTries ++ + // try again, with the same exact callback as this one. + return setTimeout(() => rimraf_(p, options, CB), busyTries * 100) + } -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "multicast", function() { return multicast; }); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "MulticastOperator", function() { return MulticastOperator; }); -/* harmony import */ var _observable_ConnectableObservable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(293); -/** PURE_IMPORTS_START _observable_ConnectableObservable PURE_IMPORTS_END */ + // this one won't happen if graceful-fs is used. + if (er.code === "EMFILE" && timeout < options.emfileWait) { + return setTimeout(() => rimraf_(p, options, CB), timeout ++) + } -function multicast(subjectOrSubjectFactory, selector) { - return function multicastOperatorFunction(source) { - var subjectFactory; - if (typeof subjectOrSubjectFactory === 'function') { - subjectFactory = subjectOrSubjectFactory; - } - else { - subjectFactory = function subjectFactory() { - return subjectOrSubjectFactory; - }; - } - if (typeof selector === 'function') { - return source.lift(new MulticastOperator(subjectFactory, selector)); + // already gone + if (er.code === "ENOENT") er = null } - var connectable = Object.create(source, _observable_ConnectableObservable__WEBPACK_IMPORTED_MODULE_0__["connectableObservableDescriptor"]); - connectable.source = source; - connectable.subjectFactory = subjectFactory; - return connectable; - }; -} -var MulticastOperator = /*@__PURE__*/ (function () { - function MulticastOperator(subjectFactory, selector) { - this.subjectFactory = subjectFactory; - this.selector = selector; - } - MulticastOperator.prototype.call = function (subscriber, source) { - var selector = this.selector; - var subject = this.subjectFactory(); - var subscription = selector(subject).subscribe(subscriber); - subscription.add(source.subscribe(subject)); - return subscription; - }; - return MulticastOperator; -}()); -//# sourceMappingURL=multicast.js.map + timeout = 0 + next(er) + } + rimraf_(p, options, CB) + }) + } + if (options.disableGlob || !glob.hasMagic(p)) + return afterGlob(null, [p]) -/***/ }), -/* 428 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + options.lstat(p, (er, stat) => { + if (!er) + return afterGlob(null, [p]) -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "onErrorResumeNext", function() { return onErrorResumeNext; }); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "onErrorResumeNextStatic", function() { return onErrorResumeNextStatic; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _observable_from__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(350); -/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(285); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(336); -/* harmony import */ var _InnerSubscriber__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(338); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(337); -/** PURE_IMPORTS_START tslib,_observable_from,_util_isArray,_OuterSubscriber,_InnerSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ + glob(p, options.glob, afterGlob) + }) +} +// Two possible strategies. +// 1. Assume it's a file. unlink it, then do the dir stuff on EPERM or EISDIR +// 2. Assume it's a directory. readdir, then do the file stuff on ENOTDIR +// +// Both result in an extra syscall when you guess wrong. However, there +// are likely far more normal files in the world than directories. This +// is based on the assumption that a the average number of files per +// directory is >= 1. +// +// If anyone ever complains about this, then I guess the strategy could +// be made configurable somehow. But until then, YAGNI. +const rimraf_ = (p, options, cb) => { + assert(p) + assert(options) + assert(typeof cb === 'function') + // sunos lets the root user unlink directories, which is... weird. + // so we have to lstat here and make sure it's not a dir. + options.lstat(p, (er, st) => { + if (er && er.code === "ENOENT") + return cb(null) + // Windows can EPERM on stat. Life is suffering. + if (er && er.code === "EPERM" && isWindows) + fixWinEPERM(p, options, er, cb) + if (st && st.isDirectory()) + return rmdir(p, options, er, cb) -function onErrorResumeNext() { - var nextSources = []; - for (var _i = 0; _i < arguments.length; _i++) { - nextSources[_i] = arguments[_i]; - } - if (nextSources.length === 1 && Object(_util_isArray__WEBPACK_IMPORTED_MODULE_2__["isArray"])(nextSources[0])) { - nextSources = nextSources[0]; - } - return function (source) { return source.lift(new OnErrorResumeNextOperator(nextSources)); }; -} -function onErrorResumeNextStatic() { - var nextSources = []; - for (var _i = 0; _i < arguments.length; _i++) { - nextSources[_i] = arguments[_i]; - } - var source = null; - if (nextSources.length === 1 && Object(_util_isArray__WEBPACK_IMPORTED_MODULE_2__["isArray"])(nextSources[0])) { - nextSources = nextSources[0]; - } - source = nextSources.shift(); - return Object(_observable_from__WEBPACK_IMPORTED_MODULE_1__["from"])(source, null).lift(new OnErrorResumeNextOperator(nextSources)); -} -var OnErrorResumeNextOperator = /*@__PURE__*/ (function () { - function OnErrorResumeNextOperator(nextSources) { - this.nextSources = nextSources; - } - OnErrorResumeNextOperator.prototype.call = function (subscriber, source) { - return source.subscribe(new OnErrorResumeNextSubscriber(subscriber, this.nextSources)); - }; - return OnErrorResumeNextOperator; -}()); -var OnErrorResumeNextSubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](OnErrorResumeNextSubscriber, _super); - function OnErrorResumeNextSubscriber(destination, nextSources) { - var _this = _super.call(this, destination) || this; - _this.destination = destination; - _this.nextSources = nextSources; - return _this; - } - OnErrorResumeNextSubscriber.prototype.notifyError = function (error, innerSub) { - this.subscribeToNextSource(); - }; - OnErrorResumeNextSubscriber.prototype.notifyComplete = function (innerSub) { - this.subscribeToNextSource(); - }; - OnErrorResumeNextSubscriber.prototype._error = function (err) { - this.subscribeToNextSource(); - this.unsubscribe(); - }; - OnErrorResumeNextSubscriber.prototype._complete = function () { - this.subscribeToNextSource(); - this.unsubscribe(); - }; - OnErrorResumeNextSubscriber.prototype.subscribeToNextSource = function () { - var next = this.nextSources.shift(); - if (!!next) { - var innerSubscriber = new _InnerSubscriber__WEBPACK_IMPORTED_MODULE_4__["InnerSubscriber"](this, undefined, undefined); - var destination = this.destination; - destination.add(innerSubscriber); - Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_5__["subscribeToResult"])(this, next, undefined, undefined, innerSubscriber); - } - else { - this.destination.complete(); - } - }; - return OnErrorResumeNextSubscriber; -}(_OuterSubscriber__WEBPACK_IMPORTED_MODULE_3__["OuterSubscriber"])); -//# sourceMappingURL=onErrorResumeNext.js.map + options.unlink(p, er => { + if (er) { + if (er.code === "ENOENT") + return cb(null) + if (er.code === "EPERM") + return (isWindows) + ? fixWinEPERM(p, options, er, cb) + : rmdir(p, options, er, cb) + if (er.code === "EISDIR") + return rmdir(p, options, er, cb) + } + return cb(er) + }) + }) +} +const fixWinEPERM = (p, options, er, cb) => { + assert(p) + assert(options) + assert(typeof cb === 'function') + if (er) + assert(er instanceof Error) -/***/ }), -/* 429 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + options.chmod(p, 0o666, er2 => { + if (er2) + cb(er2.code === "ENOENT" ? null : er) + else + options.stat(p, (er3, stats) => { + if (er3) + cb(er3.code === "ENOENT" ? null : er) + else if (stats.isDirectory()) + rmdir(p, options, er, cb) + else + options.unlink(p, cb) + }) + }) +} -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "pairwise", function() { return pairwise; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); -/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ +const fixWinEPERMSync = (p, options, er) => { + assert(p) + assert(options) + if (er) + assert(er instanceof Error) + try { + options.chmodSync(p, 0o666) + } catch (er2) { + if (er2.code === "ENOENT") + return + else + throw er + } -function pairwise() { - return function (source) { return source.lift(new PairwiseOperator()); }; -} -var PairwiseOperator = /*@__PURE__*/ (function () { - function PairwiseOperator() { - } - PairwiseOperator.prototype.call = function (subscriber, source) { - return source.subscribe(new PairwiseSubscriber(subscriber)); - }; - return PairwiseOperator; -}()); -var PairwiseSubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](PairwiseSubscriber, _super); - function PairwiseSubscriber(destination) { - var _this = _super.call(this, destination) || this; - _this.hasPrev = false; - return _this; - } - PairwiseSubscriber.prototype._next = function (value) { - var pair; - if (this.hasPrev) { - pair = [this.prev, value]; - } - else { - this.hasPrev = true; - } - this.prev = value; - if (pair) { - this.destination.next(pair); - } - }; - return PairwiseSubscriber; -}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); -//# sourceMappingURL=pairwise.js.map + let stats + try { + stats = options.statSync(p) + } catch (er3) { + if (er3.code === "ENOENT") + return + else + throw er + } + if (stats.isDirectory()) + rmdirSync(p, options, er) + else + options.unlinkSync(p) +} -/***/ }), -/* 430 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +const rmdir = (p, options, originalEr, cb) => { + assert(p) + assert(options) + if (originalEr) + assert(originalEr instanceof Error) + assert(typeof cb === 'function') -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "partition", function() { return partition; }); -/* harmony import */ var _util_not__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(370); -/* harmony import */ var _filter__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(371); -/** PURE_IMPORTS_START _util_not,_filter PURE_IMPORTS_END */ + // try to rmdir first, and only readdir on ENOTEMPTY or EEXIST (SunOS) + // if we guessed wrong, and it's not a directory, then + // raise the original error. + options.rmdir(p, er => { + if (er && (er.code === "ENOTEMPTY" || er.code === "EEXIST" || er.code === "EPERM")) + rmkids(p, options, cb) + else if (er && er.code === "ENOTDIR") + cb(originalEr) + else + cb(er) + }) +} +const rmkids = (p, options, cb) => { + assert(p) + assert(options) + assert(typeof cb === 'function') -function partition(predicate, thisArg) { - return function (source) { - return [ - Object(_filter__WEBPACK_IMPORTED_MODULE_1__["filter"])(predicate, thisArg)(source), - Object(_filter__WEBPACK_IMPORTED_MODULE_1__["filter"])(Object(_util_not__WEBPACK_IMPORTED_MODULE_0__["not"])(predicate, thisArg))(source) - ]; - }; + options.readdir(p, (er, files) => { + if (er) + return cb(er) + let n = files.length + if (n === 0) + return options.rmdir(p, cb) + let errState + files.forEach(f => { + rimraf(path.join(p, f), options, er => { + if (errState) + return + if (er) + return cb(errState = er) + if (--n === 0) + options.rmdir(p, cb) + }) + }) + }) } -//# sourceMappingURL=partition.js.map +// this looks simpler, and is strictly *faster*, but will +// tie up the JavaScript thread and fail on excessively +// deep directory trees. +const rimrafSync = (p, options) => { + options = options || {} + defaults(options) -/***/ }), -/* 431 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + assert(p, 'rimraf: missing path') + assert.equal(typeof p, 'string', 'rimraf: path should be a string') + assert(options, 'rimraf: missing options') + assert.equal(typeof options, 'object', 'rimraf: options should be object') -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "pluck", function() { return pluck; }); -/* harmony import */ var _map__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(333); -/** PURE_IMPORTS_START _map PURE_IMPORTS_END */ + let results -function pluck() { - var properties = []; - for (var _i = 0; _i < arguments.length; _i++) { - properties[_i] = arguments[_i]; + if (options.disableGlob || !glob.hasMagic(p)) { + results = [p] + } else { + try { + options.lstatSync(p) + results = [p] + } catch (er) { + results = glob.sync(p, options.glob) } - var length = properties.length; - if (length === 0) { - throw new Error('list of properties cannot be empty.'); + } + + if (!results.length) + return + + for (let i = 0; i < results.length; i++) { + const p = results[i] + + let st + try { + st = options.lstatSync(p) + } catch (er) { + if (er.code === "ENOENT") + return + + // Windows can EPERM on stat. Life is suffering. + if (er.code === "EPERM" && isWindows) + fixWinEPERMSync(p, options, er) } - return function (source) { return Object(_map__WEBPACK_IMPORTED_MODULE_0__["map"])(plucker(properties, length))(source); }; -} -function plucker(props, length) { - var mapper = function (x) { - var currentProp = x; - for (var i = 0; i < length; i++) { - var p = currentProp[props[i]]; - if (typeof p !== 'undefined') { - currentProp = p; - } - else { - return undefined; - } - } - return currentProp; - }; - return mapper; -} -//# sourceMappingURL=pluck.js.map + try { + // sunos lets the root user unlink directories, which is... weird. + if (st && st.isDirectory()) + rmdirSync(p, options, null) + else + options.unlinkSync(p) + } catch (er) { + if (er.code === "ENOENT") + return + if (er.code === "EPERM") + return isWindows ? fixWinEPERMSync(p, options, er) : rmdirSync(p, options, er) + if (er.code !== "EISDIR") + throw er -/***/ }), -/* 432 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + rmdirSync(p, options, er) + } + } +} -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "publish", function() { return publish; }); -/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(294); -/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(427); -/** PURE_IMPORTS_START _Subject,_multicast PURE_IMPORTS_END */ +const rmdirSync = (p, options, originalEr) => { + assert(p) + assert(options) + if (originalEr) + assert(originalEr instanceof Error) + + try { + options.rmdirSync(p) + } catch (er) { + if (er.code === "ENOENT") + return + if (er.code === "ENOTDIR") + throw originalEr + if (er.code === "ENOTEMPTY" || er.code === "EEXIST" || er.code === "EPERM") + rmkidsSync(p, options) + } +} +const rmkidsSync = (p, options) => { + assert(p) + assert(options) + options.readdirSync(p).forEach(f => rimrafSync(path.join(p, f), options)) -function publish(selector) { - return selector ? - Object(_multicast__WEBPACK_IMPORTED_MODULE_1__["multicast"])(function () { return new _Subject__WEBPACK_IMPORTED_MODULE_0__["Subject"](); }, selector) : - Object(_multicast__WEBPACK_IMPORTED_MODULE_1__["multicast"])(new _Subject__WEBPACK_IMPORTED_MODULE_0__["Subject"]()); + // We only end up here once we got ENOTEMPTY at least once, and + // at this point, we are guaranteed to have removed all the kids. + // So, we know that it won't be ENOENT or ENOTDIR or anything else. + // try really hard to delete stuff on windows, because it has a + // PROFOUNDLY annoying habit of not closing handles promptly when + // files are deleted, resulting in spurious ENOTEMPTY errors. + const retries = isWindows ? 100 : 1 + let i = 0 + do { + let threw = true + try { + const ret = options.rmdirSync(p, options) + threw = false + return ret + } finally { + if (++i < retries && threw) + continue + } + } while (true) } -//# sourceMappingURL=publish.js.map + +module.exports = rimraf +rimraf.sync = rimrafSync /***/ }), -/* 433 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/* 671 */ +/***/ (function(module, exports, __webpack_require__) { "use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "publishBehavior", function() { return publishBehavior; }); -/* harmony import */ var _BehaviorSubject__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(299); -/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(427); -/** PURE_IMPORTS_START _BehaviorSubject,_multicast PURE_IMPORTS_END */ +const AggregateError = __webpack_require__(672); -function publishBehavior(value) { - return function (source) { return Object(_multicast__WEBPACK_IMPORTED_MODULE_1__["multicast"])(new _BehaviorSubject__WEBPACK_IMPORTED_MODULE_0__["BehaviorSubject"](value))(source); }; -} -//# sourceMappingURL=publishBehavior.js.map +module.exports = async ( + iterable, + mapper, + { + concurrency = Infinity, + stopOnError = true + } = {} +) => { + return new Promise((resolve, reject) => { + if (typeof mapper !== 'function') { + throw new TypeError('Mapper function is required'); + } + if (!(typeof concurrency === 'number' && concurrency >= 1)) { + throw new TypeError(`Expected \`concurrency\` to be a number from 1 and up, got \`${concurrency}\` (${typeof concurrency})`); + } -/***/ }), -/* 434 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + const ret = []; + const errors = []; + const iterator = iterable[Symbol.iterator](); + let isRejected = false; + let isIterableDone = false; + let resolvingCount = 0; + let currentIndex = 0; -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "publishLast", function() { return publishLast; }); -/* harmony import */ var _AsyncSubject__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(317); -/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(427); -/** PURE_IMPORTS_START _AsyncSubject,_multicast PURE_IMPORTS_END */ + const next = () => { + if (isRejected) { + return; + } + + const nextItem = iterator.next(); + const i = currentIndex; + currentIndex++; + if (nextItem.done) { + isIterableDone = true; -function publishLast() { - return function (source) { return Object(_multicast__WEBPACK_IMPORTED_MODULE_1__["multicast"])(new _AsyncSubject__WEBPACK_IMPORTED_MODULE_0__["AsyncSubject"]())(source); }; -} -//# sourceMappingURL=publishLast.js.map + if (resolvingCount === 0) { + if (!stopOnError && errors.length !== 0) { + reject(new AggregateError(errors)); + } else { + resolve(ret); + } + } + return; + } -/***/ }), -/* 435 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + resolvingCount++; -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "publishReplay", function() { return publishReplay; }); -/* harmony import */ var _ReplaySubject__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(300); -/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(427); -/** PURE_IMPORTS_START _ReplaySubject,_multicast PURE_IMPORTS_END */ + (async () => { + try { + const element = await nextItem.value; + ret[i] = await mapper(element, i); + resolvingCount--; + next(); + } catch (error) { + if (stopOnError) { + isRejected = true; + reject(error); + } else { + errors.push(error); + resolvingCount--; + next(); + } + } + })(); + }; + for (let i = 0; i < concurrency; i++) { + next(); -function publishReplay(bufferSize, windowTime, selectorOrScheduler, scheduler) { - if (selectorOrScheduler && typeof selectorOrScheduler !== 'function') { - scheduler = selectorOrScheduler; - } - var selector = typeof selectorOrScheduler === 'function' ? selectorOrScheduler : undefined; - var subject = new _ReplaySubject__WEBPACK_IMPORTED_MODULE_0__["ReplaySubject"](bufferSize, windowTime, scheduler); - return function (source) { return Object(_multicast__WEBPACK_IMPORTED_MODULE_1__["multicast"])(function () { return subject; }, selector)(source); }; -} -//# sourceMappingURL=publishReplay.js.map + if (isIterableDone) { + break; + } + } + }); +}; /***/ }), -/* 436 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/* 672 */ +/***/ (function(module, exports, __webpack_require__) { "use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "race", function() { return race; }); -/* harmony import */ var _util_isArray__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(285); -/* harmony import */ var _observable_race__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(372); -/** PURE_IMPORTS_START _util_isArray,_observable_race PURE_IMPORTS_END */ +const indentString = __webpack_require__(673); +const cleanStack = __webpack_require__(674); -function race() { - var observables = []; - for (var _i = 0; _i < arguments.length; _i++) { - observables[_i] = arguments[_i]; - } - return function raceOperatorFunction(source) { - if (observables.length === 1 && Object(_util_isArray__WEBPACK_IMPORTED_MODULE_0__["isArray"])(observables[0])) { - observables = observables[0]; - } - return source.lift.call(_observable_race__WEBPACK_IMPORTED_MODULE_1__["race"].apply(void 0, [source].concat(observables))); - }; -} -//# sourceMappingURL=race.js.map +const cleanInternalStack = stack => stack.replace(/\s+at .*aggregate-error\/index.js:\d+:\d+\)?/g, ''); +class AggregateError extends Error { + constructor(errors) { + if (!Array.isArray(errors)) { + throw new TypeError(`Expected input to be an Array, got ${typeof errors}`); + } -/***/ }), -/* 437 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + errors = [...errors].map(error => { + if (error instanceof Error) { + return error; + } -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "repeat", function() { return repeat; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); -/* harmony import */ var _observable_empty__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(310); -/** PURE_IMPORTS_START tslib,_Subscriber,_observable_empty PURE_IMPORTS_END */ + if (error !== null && typeof error === 'object') { + // Handle plain error objects with message property and/or possibly other metadata + return Object.assign(new Error(error.message), error); + } + + return new Error(error); + }); + + let message = errors + .map(error => { + // The `stack` property is not standardized, so we can't assume it exists + return typeof error.stack === 'string' ? cleanInternalStack(cleanStack(error.stack)) : String(error); + }) + .join('\n'); + message = '\n' + indentString(message, 4); + super(message); + this.name = 'AggregateError'; + Object.defineProperty(this, '_errors', {value: errors}); + } -function repeat(count) { - if (count === void 0) { - count = -1; - } - return function (source) { - if (count === 0) { - return Object(_observable_empty__WEBPACK_IMPORTED_MODULE_2__["empty"])(); - } - else if (count < 0) { - return source.lift(new RepeatOperator(-1, source)); - } - else { - return source.lift(new RepeatOperator(count - 1, source)); - } - }; + * [Symbol.iterator]() { + for (const error of this._errors) { + yield error; + } + } } -var RepeatOperator = /*@__PURE__*/ (function () { - function RepeatOperator(count, source) { - this.count = count; - this.source = source; - } - RepeatOperator.prototype.call = function (subscriber, source) { - return source.subscribe(new RepeatSubscriber(subscriber, this.count, this.source)); - }; - return RepeatOperator; -}()); -var RepeatSubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](RepeatSubscriber, _super); - function RepeatSubscriber(destination, count, source) { - var _this = _super.call(this, destination) || this; - _this.count = count; - _this.source = source; - return _this; - } - RepeatSubscriber.prototype.complete = function () { - if (!this.isStopped) { - var _a = this, source = _a.source, count = _a.count; - if (count === 0) { - return _super.prototype.complete.call(this); - } - else if (count > -1) { - this.count = count - 1; - } - source.subscribe(this._unsubscribeAndRecycle()); - } - }; - return RepeatSubscriber; -}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); -//# sourceMappingURL=repeat.js.map + +module.exports = AggregateError; /***/ }), -/* 438 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/* 673 */ +/***/ (function(module, exports, __webpack_require__) { "use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "repeatWhen", function() { return repeatWhen; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(294); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(336); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(337); -/** PURE_IMPORTS_START tslib,_Subject,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ +module.exports = (str, count, opts) => { + // Support older versions: use the third parameter as options.indent + // TODO: Remove the workaround in the next major version + const options = typeof opts === 'object' ? Object.assign({indent: ' '}, opts) : {indent: opts || ' '}; + count = count === undefined ? 1 : count; + + if (typeof str !== 'string') { + throw new TypeError(`Expected \`input\` to be a \`string\`, got \`${typeof str}\``); + } + + if (typeof count !== 'number') { + throw new TypeError(`Expected \`count\` to be a \`number\`, got \`${typeof count}\``); + } + if (typeof options.indent !== 'string') { + throw new TypeError(`Expected \`options.indent\` to be a \`string\`, got \`${typeof options.indent}\``); + } + if (count === 0) { + return str; + } -function repeatWhen(notifier) { - return function (source) { return source.lift(new RepeatWhenOperator(notifier)); }; + const regex = options.includeEmptyLines ? /^/mg : /^(?!\s*$)/mg; + return str.replace(regex, options.indent.repeat(count)); } -var RepeatWhenOperator = /*@__PURE__*/ (function () { - function RepeatWhenOperator(notifier) { - this.notifier = notifier; - } - RepeatWhenOperator.prototype.call = function (subscriber, source) { - return source.subscribe(new RepeatWhenSubscriber(subscriber, this.notifier, source)); - }; - return RepeatWhenOperator; -}()); -var RepeatWhenSubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](RepeatWhenSubscriber, _super); - function RepeatWhenSubscriber(destination, notifier, source) { - var _this = _super.call(this, destination) || this; - _this.notifier = notifier; - _this.source = source; - _this.sourceIsBeingSubscribedTo = true; - return _this; - } - RepeatWhenSubscriber.prototype.notifyNext = function (outerValue, innerValue, outerIndex, innerIndex, innerSub) { - this.sourceIsBeingSubscribedTo = true; - this.source.subscribe(this); - }; - RepeatWhenSubscriber.prototype.notifyComplete = function (innerSub) { - if (this.sourceIsBeingSubscribedTo === false) { - return _super.prototype.complete.call(this); - } - }; - RepeatWhenSubscriber.prototype.complete = function () { - this.sourceIsBeingSubscribedTo = false; - if (!this.isStopped) { - if (!this.retries) { - this.subscribeToRetries(); - } - if (!this.retriesSubscription || this.retriesSubscription.closed) { - return _super.prototype.complete.call(this); - } - this._unsubscribeAndRecycle(); - this.notifications.next(); - } - }; - RepeatWhenSubscriber.prototype._unsubscribe = function () { - var _a = this, notifications = _a.notifications, retriesSubscription = _a.retriesSubscription; - if (notifications) { - notifications.unsubscribe(); - this.notifications = null; - } - if (retriesSubscription) { - retriesSubscription.unsubscribe(); - this.retriesSubscription = null; - } - this.retries = null; - }; - RepeatWhenSubscriber.prototype._unsubscribeAndRecycle = function () { - var _unsubscribe = this._unsubscribe; - this._unsubscribe = null; - _super.prototype._unsubscribeAndRecycle.call(this); - this._unsubscribe = _unsubscribe; - return this; - }; - RepeatWhenSubscriber.prototype.subscribeToRetries = function () { - this.notifications = new _Subject__WEBPACK_IMPORTED_MODULE_1__["Subject"](); - var retries; - try { - var notifier = this.notifier; - retries = notifier(this.notifications); - } - catch (e) { - return _super.prototype.complete.call(this); - } - this.retries = retries; - this.retriesSubscription = Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_3__["subscribeToResult"])(this, retries); - }; - return RepeatWhenSubscriber; -}(_OuterSubscriber__WEBPACK_IMPORTED_MODULE_2__["OuterSubscriber"])); -//# sourceMappingURL=repeatWhen.js.map +; /***/ }), -/* 439 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/* 674 */ +/***/ (function(module, exports, __webpack_require__) { "use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "retry", function() { return retry; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); -/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ - -function retry(count) { - if (count === void 0) { - count = -1; - } - return function (source) { return source.lift(new RetryOperator(count, source)); }; -} -var RetryOperator = /*@__PURE__*/ (function () { - function RetryOperator(count, source) { - this.count = count; - this.source = source; - } - RetryOperator.prototype.call = function (subscriber, source) { - return source.subscribe(new RetrySubscriber(subscriber, this.count, this.source)); - }; - return RetryOperator; -}()); -var RetrySubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](RetrySubscriber, _super); - function RetrySubscriber(destination, count, source) { - var _this = _super.call(this, destination) || this; - _this.count = count; - _this.source = source; - return _this; - } - RetrySubscriber.prototype.error = function (err) { - if (!this.isStopped) { - var _a = this, source = _a.source, count = _a.count; - if (count === 0) { - return _super.prototype.error.call(this, err); - } - else if (count > -1) { - this.count = count - 1; - } - source.subscribe(this._unsubscribeAndRecycle()); - } - }; - return RetrySubscriber; -}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); -//# sourceMappingURL=retry.js.map +const os = __webpack_require__(11); +const extractPathRegex = /\s+at.*(?:\(|\s)(.*)\)?/; +const pathRegex = /^(?:(?:(?:node|(?:internal\/[\w/]*|.*node_modules\/(?:babel-polyfill|pirates)\/.*)?\w+)\.js:\d+:\d+)|native)/; +const homeDir = os.homedir(); -/***/ }), -/* 440 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +module.exports = (stack, options) => { + options = Object.assign({pretty: false}, options); -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "retryWhen", function() { return retryWhen; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(294); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(336); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(337); -/** PURE_IMPORTS_START tslib,_Subject,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ + return stack.replace(/\\/g, '/') + .split('\n') + .filter(line => { + const pathMatches = line.match(extractPathRegex); + if (pathMatches === null || !pathMatches[1]) { + return true; + } + const match = pathMatches[1]; + // Electron + if ( + match.includes('.app/Contents/Resources/electron.asar') || + match.includes('.app/Contents/Resources/default_app.asar') + ) { + return false; + } + return !pathRegex.test(match); + }) + .filter(line => line.trim() !== '') + .map(line => { + if (options.pretty) { + return line.replace(extractPathRegex, (m, p1) => m.replace(p1, p1.replace(homeDir, '~'))); + } -function retryWhen(notifier) { - return function (source) { return source.lift(new RetryWhenOperator(notifier, source)); }; -} -var RetryWhenOperator = /*@__PURE__*/ (function () { - function RetryWhenOperator(notifier, source) { - this.notifier = notifier; - this.source = source; - } - RetryWhenOperator.prototype.call = function (subscriber, source) { - return source.subscribe(new RetryWhenSubscriber(subscriber, this.notifier, this.source)); - }; - return RetryWhenOperator; -}()); -var RetryWhenSubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](RetryWhenSubscriber, _super); - function RetryWhenSubscriber(destination, notifier, source) { - var _this = _super.call(this, destination) || this; - _this.notifier = notifier; - _this.source = source; - return _this; - } - RetryWhenSubscriber.prototype.error = function (err) { - if (!this.isStopped) { - var errors = this.errors; - var retries = this.retries; - var retriesSubscription = this.retriesSubscription; - if (!retries) { - errors = new _Subject__WEBPACK_IMPORTED_MODULE_1__["Subject"](); - try { - var notifier = this.notifier; - retries = notifier(errors); - } - catch (e) { - return _super.prototype.error.call(this, e); - } - retriesSubscription = Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_3__["subscribeToResult"])(this, retries); - } - else { - this.errors = null; - this.retriesSubscription = null; - } - this._unsubscribeAndRecycle(); - this.errors = errors; - this.retries = retries; - this.retriesSubscription = retriesSubscription; - errors.next(err); - } - }; - RetryWhenSubscriber.prototype._unsubscribe = function () { - var _a = this, errors = _a.errors, retriesSubscription = _a.retriesSubscription; - if (errors) { - errors.unsubscribe(); - this.errors = null; - } - if (retriesSubscription) { - retriesSubscription.unsubscribe(); - this.retriesSubscription = null; - } - this.retries = null; - }; - RetryWhenSubscriber.prototype.notifyNext = function (outerValue, innerValue, outerIndex, innerIndex, innerSub) { - var _unsubscribe = this._unsubscribe; - this._unsubscribe = null; - this._unsubscribeAndRecycle(); - this._unsubscribe = _unsubscribe; - this.source.subscribe(this); - }; - return RetryWhenSubscriber; -}(_OuterSubscriber__WEBPACK_IMPORTED_MODULE_2__["OuterSubscriber"])); -//# sourceMappingURL=retryWhen.js.map + return line; + }) + .join('\n'); +}; /***/ }), -/* 441 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/* 675 */ +/***/ (function(module, exports, __webpack_require__) { "use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "sample", function() { return sample; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(336); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(337); -/** PURE_IMPORTS_START tslib,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ - +const chalk = __webpack_require__(676); +const cliCursor = __webpack_require__(680); +const cliSpinners = __webpack_require__(684); +const logSymbols = __webpack_require__(566); -function sample(notifier) { - return function (source) { return source.lift(new SampleOperator(notifier)); }; -} -var SampleOperator = /*@__PURE__*/ (function () { - function SampleOperator(notifier) { - this.notifier = notifier; - } - SampleOperator.prototype.call = function (subscriber, source) { - var sampleSubscriber = new SampleSubscriber(subscriber); - var subscription = source.subscribe(sampleSubscriber); - subscription.add(Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__["subscribeToResult"])(sampleSubscriber, this.notifier)); - return subscription; - }; - return SampleOperator; -}()); -var SampleSubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](SampleSubscriber, _super); - function SampleSubscriber() { - var _this = _super !== null && _super.apply(this, arguments) || this; - _this.hasValue = false; - return _this; - } - SampleSubscriber.prototype._next = function (value) { - this.value = value; - this.hasValue = true; - }; - SampleSubscriber.prototype.notifyNext = function (outerValue, innerValue, outerIndex, innerIndex, innerSub) { - this.emitValue(); - }; - SampleSubscriber.prototype.notifyComplete = function () { - this.emitValue(); - }; - SampleSubscriber.prototype.emitValue = function () { - if (this.hasValue) { - this.hasValue = false; - this.destination.next(this.value); - } - }; - return SampleSubscriber; -}(_OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__["OuterSubscriber"])); -//# sourceMappingURL=sample.js.map +class Ora { + constructor(options) { + if (typeof options === 'string') { + options = { + text: options + }; + } + this.options = Object.assign({ + text: '', + color: 'cyan', + stream: process.stderr + }, options); -/***/ }), -/* 442 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + const sp = this.options.spinner; + this.spinner = typeof sp === 'object' ? sp : (process.platform === 'win32' ? cliSpinners.line : (cliSpinners[sp] || cliSpinners.dots)); // eslint-disable-line no-nested-ternary -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "sampleTime", function() { return sampleTime; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); -/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(322); -/** PURE_IMPORTS_START tslib,_Subscriber,_scheduler_async PURE_IMPORTS_END */ + if (this.spinner.frames === undefined) { + throw new Error('Spinner must define `frames`'); + } + this.text = this.options.text; + this.color = this.options.color; + this.interval = this.options.interval || this.spinner.interval || 100; + this.stream = this.options.stream; + this.id = null; + this.frameIndex = 0; + this.enabled = typeof this.options.enabled === 'boolean' ? this.options.enabled : ((this.stream && this.stream.isTTY) && !process.env.CI); + } + frame() { + const frames = this.spinner.frames; + let frame = frames[this.frameIndex]; + if (this.color) { + frame = chalk[this.color](frame); + } -function sampleTime(period, scheduler) { - if (scheduler === void 0) { - scheduler = _scheduler_async__WEBPACK_IMPORTED_MODULE_2__["async"]; - } - return function (source) { return source.lift(new SampleTimeOperator(period, scheduler)); }; -} -var SampleTimeOperator = /*@__PURE__*/ (function () { - function SampleTimeOperator(period, scheduler) { - this.period = period; - this.scheduler = scheduler; - } - SampleTimeOperator.prototype.call = function (subscriber, source) { - return source.subscribe(new SampleTimeSubscriber(subscriber, this.period, this.scheduler)); - }; - return SampleTimeOperator; -}()); -var SampleTimeSubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](SampleTimeSubscriber, _super); - function SampleTimeSubscriber(destination, period, scheduler) { - var _this = _super.call(this, destination) || this; - _this.period = period; - _this.scheduler = scheduler; - _this.hasValue = false; - _this.add(scheduler.schedule(dispatchNotification, period, { subscriber: _this, period: period })); - return _this; - } - SampleTimeSubscriber.prototype._next = function (value) { - this.lastValue = value; - this.hasValue = true; - }; - SampleTimeSubscriber.prototype.notifyNext = function () { - if (this.hasValue) { - this.hasValue = false; - this.destination.next(this.lastValue); - } - }; - return SampleTimeSubscriber; -}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); -function dispatchNotification(state) { - var subscriber = state.subscriber, period = state.period; - subscriber.notifyNext(); - this.schedule(state, period); -} -//# sourceMappingURL=sampleTime.js.map + this.frameIndex = ++this.frameIndex % frames.length; + return frame + ' ' + this.text; + } + clear() { + if (!this.enabled) { + return this; + } -/***/ }), -/* 443 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + this.stream.clearLine(); + this.stream.cursorTo(0); -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "sequenceEqual", function() { return sequenceEqual; }); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "SequenceEqualOperator", function() { return SequenceEqualOperator; }); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "SequenceEqualSubscriber", function() { return SequenceEqualSubscriber; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); -/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ + return this; + } + render() { + this.clear(); + this.stream.write(this.frame()); + return this; + } + start(text) { + if (text) { + this.text = text; + } -function sequenceEqual(compareTo, comparator) { - return function (source) { return source.lift(new SequenceEqualOperator(compareTo, comparator)); }; -} -var SequenceEqualOperator = /*@__PURE__*/ (function () { - function SequenceEqualOperator(compareTo, comparator) { - this.compareTo = compareTo; - this.comparator = comparator; - } - SequenceEqualOperator.prototype.call = function (subscriber, source) { - return source.subscribe(new SequenceEqualSubscriber(subscriber, this.compareTo, this.comparator)); - }; - return SequenceEqualOperator; -}()); + if (!this.enabled || this.id) { + return this; + } -var SequenceEqualSubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](SequenceEqualSubscriber, _super); - function SequenceEqualSubscriber(destination, compareTo, comparator) { - var _this = _super.call(this, destination) || this; - _this.compareTo = compareTo; - _this.comparator = comparator; - _this._a = []; - _this._b = []; - _this._oneComplete = false; - _this.destination.add(compareTo.subscribe(new SequenceEqualCompareToSubscriber(destination, _this))); - return _this; - } - SequenceEqualSubscriber.prototype._next = function (value) { - if (this._oneComplete && this._b.length === 0) { - this.emit(false); - } - else { - this._a.push(value); - this.checkValues(); - } - }; - SequenceEqualSubscriber.prototype._complete = function () { - if (this._oneComplete) { - this.emit(this._a.length === 0 && this._b.length === 0); - } - else { - this._oneComplete = true; - } - this.unsubscribe(); - }; - SequenceEqualSubscriber.prototype.checkValues = function () { - var _c = this, _a = _c._a, _b = _c._b, comparator = _c.comparator; - while (_a.length > 0 && _b.length > 0) { - var a = _a.shift(); - var b = _b.shift(); - var areEqual = false; - try { - areEqual = comparator ? comparator(a, b) : a === b; - } - catch (e) { - this.destination.error(e); - } - if (!areEqual) { - this.emit(false); - } - } - }; - SequenceEqualSubscriber.prototype.emit = function (value) { - var destination = this.destination; - destination.next(value); - destination.complete(); - }; - SequenceEqualSubscriber.prototype.nextB = function (value) { - if (this._oneComplete && this._a.length === 0) { - this.emit(false); - } - else { - this._b.push(value); - this.checkValues(); - } - }; - SequenceEqualSubscriber.prototype.completeB = function () { - if (this._oneComplete) { - this.emit(this._a.length === 0 && this._b.length === 0); - } - else { - this._oneComplete = true; - } - }; - return SequenceEqualSubscriber; -}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); + cliCursor.hide(this.stream); + this.render(); + this.id = setInterval(this.render.bind(this), this.interval); -var SequenceEqualCompareToSubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](SequenceEqualCompareToSubscriber, _super); - function SequenceEqualCompareToSubscriber(destination, parent) { - var _this = _super.call(this, destination) || this; - _this.parent = parent; - return _this; - } - SequenceEqualCompareToSubscriber.prototype._next = function (value) { - this.parent.nextB(value); - }; - SequenceEqualCompareToSubscriber.prototype._error = function (err) { - this.parent.error(err); - this.unsubscribe(); - }; - SequenceEqualCompareToSubscriber.prototype._complete = function () { - this.parent.completeB(); - this.unsubscribe(); - }; - return SequenceEqualCompareToSubscriber; -}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); -//# sourceMappingURL=sequenceEqual.js.map + return this; + } + stop() { + if (!this.enabled) { + return this; + } + clearInterval(this.id); + this.id = null; + this.frameIndex = 0; + this.clear(); + cliCursor.show(this.stream); -/***/ }), -/* 444 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + return this; + } + succeed(text) { + return this.stopAndPersist({symbol: logSymbols.success, text}); + } + fail(text) { + return this.stopAndPersist({symbol: logSymbols.error, text}); + } + warn(text) { + return this.stopAndPersist({symbol: logSymbols.warning, text}); + } + info(text) { + return this.stopAndPersist({symbol: logSymbols.info, text}); + } + stopAndPersist(options) { + if (!this.enabled) { + return this; + } -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "share", function() { return share; }); -/* harmony import */ var _multicast__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(427); -/* harmony import */ var _refCount__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(297); -/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(294); -/** PURE_IMPORTS_START _multicast,_refCount,_Subject PURE_IMPORTS_END */ + // Legacy argument + // TODO: Deprecate sometime in the future + if (typeof options === 'string') { + options = { + symbol: options + }; + } + options = options || {}; + this.stop(); + this.stream.write(`${options.symbol || ' '} ${options.text || this.text}\n`); -function shareSubjectFactory() { - return new _Subject__WEBPACK_IMPORTED_MODULE_2__["Subject"](); -} -function share() { - return function (source) { return Object(_refCount__WEBPACK_IMPORTED_MODULE_1__["refCount"])()(Object(_multicast__WEBPACK_IMPORTED_MODULE_0__["multicast"])(shareSubjectFactory)(source)); }; + return this; + } } -//# sourceMappingURL=share.js.map +module.exports = function (opts) { + return new Ora(opts); +}; -/***/ }), -/* 445 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +module.exports.promise = (action, options) => { + if (typeof action.then !== 'function') { + throw new TypeError('Parameter `action` must be a Promise'); + } -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "shareReplay", function() { return shareReplay; }); -/* harmony import */ var _ReplaySubject__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(300); -/** PURE_IMPORTS_START _ReplaySubject PURE_IMPORTS_END */ + const spinner = new Ora(options); + spinner.start(); -function shareReplay(configOrBufferSize, windowTime, scheduler) { - var config; - if (configOrBufferSize && typeof configOrBufferSize === 'object') { - config = configOrBufferSize; - } - else { - config = { - bufferSize: configOrBufferSize, - windowTime: windowTime, - refCount: false, - scheduler: scheduler - }; - } - return function (source) { return source.lift(shareReplayOperator(config)); }; -} -function shareReplayOperator(_a) { - var _b = _a.bufferSize, bufferSize = _b === void 0 ? Number.POSITIVE_INFINITY : _b, _c = _a.windowTime, windowTime = _c === void 0 ? Number.POSITIVE_INFINITY : _c, useRefCount = _a.refCount, scheduler = _a.scheduler; - var subject; - var refCount = 0; - var subscription; - var hasError = false; - var isComplete = false; - return function shareReplayOperation(source) { - refCount++; - if (!subject || hasError) { - hasError = false; - subject = new _ReplaySubject__WEBPACK_IMPORTED_MODULE_0__["ReplaySubject"](bufferSize, windowTime, scheduler); - subscription = source.subscribe({ - next: function (value) { subject.next(value); }, - error: function (err) { - hasError = true; - subject.error(err); - }, - complete: function () { - isComplete = true; - subject.complete(); - }, - }); - } - var innerSub = subject.subscribe(this); - this.add(function () { - refCount--; - innerSub.unsubscribe(); - if (subscription && !isComplete && useRefCount && refCount === 0) { - subscription.unsubscribe(); - subscription = undefined; - subject = undefined; - } - }); - }; -} -//# sourceMappingURL=shareReplay.js.map + action.then( + () => { + spinner.succeed(); + }, + () => { + spinner.fail(); + } + ); + + return spinner; +}; /***/ }), -/* 446 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/* 676 */ +/***/ (function(module, exports, __webpack_require__) { "use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "single", function() { return single; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); -/* harmony import */ var _util_EmptyError__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(330); -/** PURE_IMPORTS_START tslib,_Subscriber,_util_EmptyError PURE_IMPORTS_END */ +const escapeStringRegexp = __webpack_require__(3); +const ansiStyles = __webpack_require__(677); +const stdoutColor = __webpack_require__(678).stdout; +const template = __webpack_require__(679); -function single(predicate) { - return function (source) { return source.lift(new SingleOperator(predicate, source)); }; -} -var SingleOperator = /*@__PURE__*/ (function () { - function SingleOperator(predicate, source) { - this.predicate = predicate; - this.source = source; - } - SingleOperator.prototype.call = function (subscriber, source) { - return source.subscribe(new SingleSubscriber(subscriber, this.predicate, this.source)); - }; - return SingleOperator; -}()); -var SingleSubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](SingleSubscriber, _super); - function SingleSubscriber(destination, predicate, source) { - var _this = _super.call(this, destination) || this; - _this.predicate = predicate; - _this.source = source; - _this.seenValue = false; - _this.index = 0; - return _this; - } - SingleSubscriber.prototype.applySingleValue = function (value) { - if (this.seenValue) { - this.destination.error('Sequence contains more than one element'); - } - else { - this.seenValue = true; - this.singleValue = value; - } - }; - SingleSubscriber.prototype._next = function (value) { - var index = this.index++; - if (this.predicate) { - this.tryNext(value, index); - } - else { - this.applySingleValue(value); - } - }; - SingleSubscriber.prototype.tryNext = function (value, index) { - try { - if (this.predicate(value, index, this.source)) { - this.applySingleValue(value); - } - } - catch (err) { - this.destination.error(err); - } - }; - SingleSubscriber.prototype._complete = function () { - var destination = this.destination; - if (this.index > 0) { - destination.next(this.seenValue ? this.singleValue : undefined); - destination.complete(); - } - else { - destination.error(new _util_EmptyError__WEBPACK_IMPORTED_MODULE_2__["EmptyError"]); - } - }; - return SingleSubscriber; -}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); -//# sourceMappingURL=single.js.map +const isSimpleWindowsTerm = process.platform === 'win32' && !(process.env.TERM || '').toLowerCase().startsWith('xterm'); +// `supportsColor.level` → `ansiStyles.color[name]` mapping +const levelMapping = ['ansi', 'ansi', 'ansi256', 'ansi16m']; -/***/ }), -/* 447 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +// `color-convert` models to exclude from the Chalk API due to conflicts and such +const skipModels = new Set(['gray']); -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "skip", function() { return skip; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); -/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ +const styles = Object.create(null); +function applyOptions(obj, options) { + options = options || {}; -function skip(count) { - return function (source) { return source.lift(new SkipOperator(count)); }; + // Detect level if not set manually + const scLevel = stdoutColor ? stdoutColor.level : 0; + obj.level = options.level === undefined ? scLevel : options.level; + obj.enabled = 'enabled' in options ? options.enabled : obj.level > 0; } -var SkipOperator = /*@__PURE__*/ (function () { - function SkipOperator(total) { - this.total = total; - } - SkipOperator.prototype.call = function (subscriber, source) { - return source.subscribe(new SkipSubscriber(subscriber, this.total)); - }; - return SkipOperator; -}()); -var SkipSubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](SkipSubscriber, _super); - function SkipSubscriber(destination, total) { - var _this = _super.call(this, destination) || this; - _this.total = total; - _this.count = 0; - return _this; - } - SkipSubscriber.prototype._next = function (x) { - if (++this.count > this.total) { - this.destination.next(x); - } - }; - return SkipSubscriber; -}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); -//# sourceMappingURL=skip.js.map +function Chalk(options) { + // We check for this.template here since calling `chalk.constructor()` + // by itself will have a `this` of a previously constructed chalk object + if (!this || !(this instanceof Chalk) || this.template) { + const chalk = {}; + applyOptions(chalk, options); -/***/ }), -/* 448 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + chalk.template = function () { + const args = [].slice.call(arguments); + return chalkTag.apply(null, [chalk.template].concat(args)); + }; -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "skipLast", function() { return skipLast; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); -/* harmony import */ var _util_ArgumentOutOfRangeError__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(329); -/** PURE_IMPORTS_START tslib,_Subscriber,_util_ArgumentOutOfRangeError PURE_IMPORTS_END */ + Object.setPrototypeOf(chalk, Chalk.prototype); + Object.setPrototypeOf(chalk.template, chalk); + chalk.template.constructor = Chalk; + return chalk.template; + } -function skipLast(count) { - return function (source) { return source.lift(new SkipLastOperator(count)); }; + applyOptions(this, options); } -var SkipLastOperator = /*@__PURE__*/ (function () { - function SkipLastOperator(_skipCount) { - this._skipCount = _skipCount; - if (this._skipCount < 0) { - throw new _util_ArgumentOutOfRangeError__WEBPACK_IMPORTED_MODULE_2__["ArgumentOutOfRangeError"]; - } - } - SkipLastOperator.prototype.call = function (subscriber, source) { - if (this._skipCount === 0) { - return source.subscribe(new _Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"](subscriber)); - } - else { - return source.subscribe(new SkipLastSubscriber(subscriber, this._skipCount)); - } - }; - return SkipLastOperator; -}()); -var SkipLastSubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](SkipLastSubscriber, _super); - function SkipLastSubscriber(destination, _skipCount) { - var _this = _super.call(this, destination) || this; - _this._skipCount = _skipCount; - _this._count = 0; - _this._ring = new Array(_skipCount); - return _this; - } - SkipLastSubscriber.prototype._next = function (value) { - var skipCount = this._skipCount; - var count = this._count++; - if (count < skipCount) { - this._ring[count] = value; - } - else { - var currentIndex = count % skipCount; - var ring = this._ring; - var oldValue = ring[currentIndex]; - ring[currentIndex] = value; - this.destination.next(oldValue); - } - }; - return SkipLastSubscriber; -}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); -//# sourceMappingURL=skipLast.js.map +// Use bright blue on Windows as the normal blue color is illegible +if (isSimpleWindowsTerm) { + ansiStyles.blue.open = '\u001B[94m'; +} -/***/ }), -/* 449 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { - -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "skipUntil", function() { return skipUntil; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(336); -/* harmony import */ var _InnerSubscriber__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(338); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(337); -/** PURE_IMPORTS_START tslib,_OuterSubscriber,_InnerSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ +for (const key of Object.keys(ansiStyles)) { + ansiStyles[key].closeRe = new RegExp(escapeStringRegexp(ansiStyles[key].close), 'g'); + styles[key] = { + get() { + const codes = ansiStyles[key]; + return build.call(this, this._styles ? this._styles.concat(codes) : [codes], this._empty, key); + } + }; +} +styles.visible = { + get() { + return build.call(this, this._styles || [], true, 'visible'); + } +}; +ansiStyles.color.closeRe = new RegExp(escapeStringRegexp(ansiStyles.color.close), 'g'); +for (const model of Object.keys(ansiStyles.color.ansi)) { + if (skipModels.has(model)) { + continue; + } -function skipUntil(notifier) { - return function (source) { return source.lift(new SkipUntilOperator(notifier)); }; + styles[model] = { + get() { + const level = this.level; + return function () { + const open = ansiStyles.color[levelMapping[level]][model].apply(null, arguments); + const codes = { + open, + close: ansiStyles.color.close, + closeRe: ansiStyles.color.closeRe + }; + return build.call(this, this._styles ? this._styles.concat(codes) : [codes], this._empty, model); + }; + } + }; } -var SkipUntilOperator = /*@__PURE__*/ (function () { - function SkipUntilOperator(notifier) { - this.notifier = notifier; - } - SkipUntilOperator.prototype.call = function (destination, source) { - return source.subscribe(new SkipUntilSubscriber(destination, this.notifier)); - }; - return SkipUntilOperator; -}()); -var SkipUntilSubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](SkipUntilSubscriber, _super); - function SkipUntilSubscriber(destination, notifier) { - var _this = _super.call(this, destination) || this; - _this.hasValue = false; - var innerSubscriber = new _InnerSubscriber__WEBPACK_IMPORTED_MODULE_2__["InnerSubscriber"](_this, undefined, undefined); - _this.add(innerSubscriber); - _this.innerSubscription = innerSubscriber; - Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_3__["subscribeToResult"])(_this, notifier, undefined, undefined, innerSubscriber); - return _this; - } - SkipUntilSubscriber.prototype._next = function (value) { - if (this.hasValue) { - _super.prototype._next.call(this, value); - } - }; - SkipUntilSubscriber.prototype.notifyNext = function (outerValue, innerValue, outerIndex, innerIndex, innerSub) { - this.hasValue = true; - if (this.innerSubscription) { - this.innerSubscription.unsubscribe(); - } - }; - SkipUntilSubscriber.prototype.notifyComplete = function () { - }; - return SkipUntilSubscriber; -}(_OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__["OuterSubscriber"])); -//# sourceMappingURL=skipUntil.js.map +ansiStyles.bgColor.closeRe = new RegExp(escapeStringRegexp(ansiStyles.bgColor.close), 'g'); +for (const model of Object.keys(ansiStyles.bgColor.ansi)) { + if (skipModels.has(model)) { + continue; + } -/***/ }), -/* 450 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + const bgModel = 'bg' + model[0].toUpperCase() + model.slice(1); + styles[bgModel] = { + get() { + const level = this.level; + return function () { + const open = ansiStyles.bgColor[levelMapping[level]][model].apply(null, arguments); + const codes = { + open, + close: ansiStyles.bgColor.close, + closeRe: ansiStyles.bgColor.closeRe + }; + return build.call(this, this._styles ? this._styles.concat(codes) : [codes], this._empty, model); + }; + } + }; +} -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "skipWhile", function() { return skipWhile; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); -/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ +const proto = Object.defineProperties(() => {}, styles); +function build(_styles, _empty, key) { + const builder = function () { + return applyStyle.apply(builder, arguments); + }; -function skipWhile(predicate) { - return function (source) { return source.lift(new SkipWhileOperator(predicate)); }; -} -var SkipWhileOperator = /*@__PURE__*/ (function () { - function SkipWhileOperator(predicate) { - this.predicate = predicate; - } - SkipWhileOperator.prototype.call = function (subscriber, source) { - return source.subscribe(new SkipWhileSubscriber(subscriber, this.predicate)); - }; - return SkipWhileOperator; -}()); -var SkipWhileSubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](SkipWhileSubscriber, _super); - function SkipWhileSubscriber(destination, predicate) { - var _this = _super.call(this, destination) || this; - _this.predicate = predicate; - _this.skipping = true; - _this.index = 0; - return _this; - } - SkipWhileSubscriber.prototype._next = function (value) { - var destination = this.destination; - if (this.skipping) { - this.tryCallPredicate(value); - } - if (!this.skipping) { - destination.next(value); - } - }; - SkipWhileSubscriber.prototype.tryCallPredicate = function (value) { - try { - var result = this.predicate(value, this.index++); - this.skipping = Boolean(result); - } - catch (err) { - this.destination.error(err); - } - }; - return SkipWhileSubscriber; -}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); -//# sourceMappingURL=skipWhile.js.map + builder._styles = _styles; + builder._empty = _empty; + + const self = this; + Object.defineProperty(builder, 'level', { + enumerable: true, + get() { + return self.level; + }, + set(level) { + self.level = level; + } + }); -/***/ }), -/* 451 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + Object.defineProperty(builder, 'enabled', { + enumerable: true, + get() { + return self.enabled; + }, + set(enabled) { + self.enabled = enabled; + } + }); -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "startWith", function() { return startWith; }); -/* harmony import */ var _observable_concat__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(346); -/* harmony import */ var _util_isScheduler__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(312); -/** PURE_IMPORTS_START _observable_concat,_util_isScheduler PURE_IMPORTS_END */ + // See below for fix regarding invisible grey/dim combination on Windows + builder.hasGrey = this.hasGrey || key === 'gray' || key === 'grey'; + // `__proto__` is used because we must return a function, but there is + // no way to create a function with a different prototype + builder.__proto__ = proto; // eslint-disable-line no-proto -function startWith() { - var array = []; - for (var _i = 0; _i < arguments.length; _i++) { - array[_i] = arguments[_i]; - } - var scheduler = array[array.length - 1]; - if (Object(_util_isScheduler__WEBPACK_IMPORTED_MODULE_1__["isScheduler"])(scheduler)) { - array.pop(); - return function (source) { return Object(_observable_concat__WEBPACK_IMPORTED_MODULE_0__["concat"])(array, source, scheduler); }; - } - else { - return function (source) { return Object(_observable_concat__WEBPACK_IMPORTED_MODULE_0__["concat"])(array, source); }; - } + return builder; } -//# sourceMappingURL=startWith.js.map +function applyStyle() { + // Support varags, but simply cast to string in case there's only one arg + const args = arguments; + const argsLen = args.length; + let str = String(arguments[0]); -/***/ }), -/* 452 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + if (argsLen === 0) { + return ''; + } -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "subscribeOn", function() { return subscribeOn; }); -/* harmony import */ var _observable_SubscribeOnObservable__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(453); -/** PURE_IMPORTS_START _observable_SubscribeOnObservable PURE_IMPORTS_END */ + if (argsLen > 1) { + // Don't slice `arguments`, it prevents V8 optimizations + for (let a = 1; a < argsLen; a++) { + str += ' ' + args[a]; + } + } -function subscribeOn(scheduler, delay) { - if (delay === void 0) { - delay = 0; - } - return function subscribeOnOperatorFunction(source) { - return source.lift(new SubscribeOnOperator(scheduler, delay)); - }; -} -var SubscribeOnOperator = /*@__PURE__*/ (function () { - function SubscribeOnOperator(scheduler, delay) { - this.scheduler = scheduler; - this.delay = delay; - } - SubscribeOnOperator.prototype.call = function (subscriber, source) { - return new _observable_SubscribeOnObservable__WEBPACK_IMPORTED_MODULE_0__["SubscribeOnObservable"](source, this.delay, this.scheduler).subscribe(subscriber); - }; - return SubscribeOnOperator; -}()); -//# sourceMappingURL=subscribeOn.js.map + if (!this.enabled || this.level <= 0 || !str) { + return this._empty ? '' : str; + } + // Turns out that on Windows dimmed gray text becomes invisible in cmd.exe, + // see https://github.com/chalk/chalk/issues/58 + // If we're on Windows and we're dealing with a gray color, temporarily make 'dim' a noop. + const originalDim = ansiStyles.dim.open; + if (isSimpleWindowsTerm && this.hasGrey) { + ansiStyles.dim.open = ''; + } -/***/ }), -/* 453 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + for (const code of this._styles.slice().reverse()) { + // Replace any instances already present with a re-opening code + // otherwise only the part of the string until said closing code + // will be colored, and the rest will simply be 'plain'. + str = code.open + str.replace(code.closeRe, code.open) + code.close; -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "SubscribeOnObservable", function() { return SubscribeOnObservable; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _Observable__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(276); -/* harmony import */ var _scheduler_asap__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(318); -/* harmony import */ var _util_isNumeric__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(364); -/** PURE_IMPORTS_START tslib,_Observable,_scheduler_asap,_util_isNumeric PURE_IMPORTS_END */ + // Close the styling before a linebreak and reopen + // after next line to fix a bleed issue on macOS + // https://github.com/chalk/chalk/pull/92 + str = str.replace(/\r?\n/g, `${code.close}$&${code.open}`); + } + // Reset the original `dim` if we changed it to work around the Windows dimmed gray issue + ansiStyles.dim.open = originalDim; + return str; +} +function chalkTag(chalk, strings) { + if (!Array.isArray(strings)) { + // If chalk() was called by itself or with a string, + // return the string itself as a string. + return [].slice.call(arguments, 1).join(' '); + } -var SubscribeOnObservable = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](SubscribeOnObservable, _super); - function SubscribeOnObservable(source, delayTime, scheduler) { - if (delayTime === void 0) { - delayTime = 0; - } - if (scheduler === void 0) { - scheduler = _scheduler_asap__WEBPACK_IMPORTED_MODULE_2__["asap"]; - } - var _this = _super.call(this) || this; - _this.source = source; - _this.delayTime = delayTime; - _this.scheduler = scheduler; - if (!Object(_util_isNumeric__WEBPACK_IMPORTED_MODULE_3__["isNumeric"])(delayTime) || delayTime < 0) { - _this.delayTime = 0; - } - if (!scheduler || typeof scheduler.schedule !== 'function') { - _this.scheduler = _scheduler_asap__WEBPACK_IMPORTED_MODULE_2__["asap"]; - } - return _this; - } - SubscribeOnObservable.create = function (source, delay, scheduler) { - if (delay === void 0) { - delay = 0; - } - if (scheduler === void 0) { - scheduler = _scheduler_asap__WEBPACK_IMPORTED_MODULE_2__["asap"]; - } - return new SubscribeOnObservable(source, delay, scheduler); - }; - SubscribeOnObservable.dispatch = function (arg) { - var source = arg.source, subscriber = arg.subscriber; - return this.add(source.subscribe(subscriber)); - }; - SubscribeOnObservable.prototype._subscribe = function (subscriber) { - var delay = this.delayTime; - var source = this.source; - var scheduler = this.scheduler; - return scheduler.schedule(SubscribeOnObservable.dispatch, delay, { - source: source, subscriber: subscriber - }); - }; - return SubscribeOnObservable; -}(_Observable__WEBPACK_IMPORTED_MODULE_1__["Observable"])); + const args = [].slice.call(arguments, 2); + const parts = [strings.raw[0]]; -//# sourceMappingURL=SubscribeOnObservable.js.map + for (let i = 1; i < strings.length; i++) { + parts.push(String(args[i - 1]).replace(/[{}\\]/g, '\\$&')); + parts.push(String(strings.raw[i])); + } + + return template(chalk, parts.join('')); +} + +Object.defineProperties(Chalk.prototype, styles); + +module.exports = Chalk(); // eslint-disable-line new-cap +module.exports.supportsColor = stdoutColor; +module.exports.default = module.exports; // For TypeScript /***/ }), -/* 454 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/* 677 */ +/***/ (function(module, exports, __webpack_require__) { "use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "switchAll", function() { return switchAll; }); -/* harmony import */ var _switchMap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(455); -/* harmony import */ var _util_identity__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(327); -/** PURE_IMPORTS_START _switchMap,_util_identity PURE_IMPORTS_END */ - +/* WEBPACK VAR INJECTION */(function(module) { +const colorConvert = __webpack_require__(6); -function switchAll() { - return Object(_switchMap__WEBPACK_IMPORTED_MODULE_0__["switchMap"])(_util_identity__WEBPACK_IMPORTED_MODULE_1__["identity"]); -} -//# sourceMappingURL=switchAll.js.map +const wrapAnsi16 = (fn, offset) => function () { + const code = fn.apply(colorConvert, arguments); + return `\u001B[${code + offset}m`; +}; +const wrapAnsi256 = (fn, offset) => function () { + const code = fn.apply(colorConvert, arguments); + return `\u001B[${38 + offset};5;${code}m`; +}; -/***/ }), -/* 455 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +const wrapAnsi16m = (fn, offset) => function () { + const rgb = fn.apply(colorConvert, arguments); + return `\u001B[${38 + offset};2;${rgb[0]};${rgb[1]};${rgb[2]}m`; +}; -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "switchMap", function() { return switchMap; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(336); -/* harmony import */ var _InnerSubscriber__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(338); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(337); -/* harmony import */ var _map__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(333); -/* harmony import */ var _observable_from__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(350); -/** PURE_IMPORTS_START tslib,_OuterSubscriber,_InnerSubscriber,_util_subscribeToResult,_map,_observable_from PURE_IMPORTS_END */ +function assembleStyles() { + const codes = new Map(); + const styles = { + modifier: { + reset: [0, 0], + // 21 isn't widely supported and 22 does the same thing + bold: [1, 22], + dim: [2, 22], + italic: [3, 23], + underline: [4, 24], + inverse: [7, 27], + hidden: [8, 28], + strikethrough: [9, 29] + }, + color: { + black: [30, 39], + red: [31, 39], + green: [32, 39], + yellow: [33, 39], + blue: [34, 39], + magenta: [35, 39], + cyan: [36, 39], + white: [37, 39], + gray: [90, 39], + // Bright color + redBright: [91, 39], + greenBright: [92, 39], + yellowBright: [93, 39], + blueBright: [94, 39], + magentaBright: [95, 39], + cyanBright: [96, 39], + whiteBright: [97, 39] + }, + bgColor: { + bgBlack: [40, 49], + bgRed: [41, 49], + bgGreen: [42, 49], + bgYellow: [43, 49], + bgBlue: [44, 49], + bgMagenta: [45, 49], + bgCyan: [46, 49], + bgWhite: [47, 49], + // Bright color + bgBlackBright: [100, 49], + bgRedBright: [101, 49], + bgGreenBright: [102, 49], + bgYellowBright: [103, 49], + bgBlueBright: [104, 49], + bgMagentaBright: [105, 49], + bgCyanBright: [106, 49], + bgWhiteBright: [107, 49] + } + }; + // Fix humans + styles.color.grey = styles.color.gray; + for (const groupName of Object.keys(styles)) { + const group = styles[groupName]; + for (const styleName of Object.keys(group)) { + const style = group[styleName]; -function switchMap(project, resultSelector) { - if (typeof resultSelector === 'function') { - return function (source) { return source.pipe(switchMap(function (a, i) { return Object(_observable_from__WEBPACK_IMPORTED_MODULE_5__["from"])(project(a, i)).pipe(Object(_map__WEBPACK_IMPORTED_MODULE_4__["map"])(function (b, ii) { return resultSelector(a, b, i, ii); })); })); }; - } - return function (source) { return source.lift(new SwitchMapOperator(project)); }; -} -var SwitchMapOperator = /*@__PURE__*/ (function () { - function SwitchMapOperator(project) { - this.project = project; - } - SwitchMapOperator.prototype.call = function (subscriber, source) { - return source.subscribe(new SwitchMapSubscriber(subscriber, this.project)); - }; - return SwitchMapOperator; -}()); -var SwitchMapSubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](SwitchMapSubscriber, _super); - function SwitchMapSubscriber(destination, project) { - var _this = _super.call(this, destination) || this; - _this.project = project; - _this.index = 0; - return _this; - } - SwitchMapSubscriber.prototype._next = function (value) { - var result; - var index = this.index++; - try { - result = this.project(value, index); - } - catch (error) { - this.destination.error(error); - return; - } - this._innerSub(result, value, index); - }; - SwitchMapSubscriber.prototype._innerSub = function (result, value, index) { - var innerSubscription = this.innerSubscription; - if (innerSubscription) { - innerSubscription.unsubscribe(); - } - var innerSubscriber = new _InnerSubscriber__WEBPACK_IMPORTED_MODULE_2__["InnerSubscriber"](this, undefined, undefined); - var destination = this.destination; - destination.add(innerSubscriber); - this.innerSubscription = Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_3__["subscribeToResult"])(this, result, value, index, innerSubscriber); - }; - SwitchMapSubscriber.prototype._complete = function () { - var innerSubscription = this.innerSubscription; - if (!innerSubscription || innerSubscription.closed) { - _super.prototype._complete.call(this); - } - this.unsubscribe(); - }; - SwitchMapSubscriber.prototype._unsubscribe = function () { - this.innerSubscription = null; - }; - SwitchMapSubscriber.prototype.notifyComplete = function (innerSub) { - var destination = this.destination; - destination.remove(innerSub); - this.innerSubscription = null; - if (this.isStopped) { - _super.prototype._complete.call(this); - } - }; - SwitchMapSubscriber.prototype.notifyNext = function (outerValue, innerValue, outerIndex, innerIndex, innerSub) { - this.destination.next(innerValue); - }; - return SwitchMapSubscriber; -}(_OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__["OuterSubscriber"])); -//# sourceMappingURL=switchMap.js.map + styles[styleName] = { + open: `\u001B[${style[0]}m`, + close: `\u001B[${style[1]}m` + }; + group[styleName] = styles[styleName]; -/***/ }), -/* 456 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + codes.set(style[0], style[1]); + } -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "switchMapTo", function() { return switchMapTo; }); -/* harmony import */ var _switchMap__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(455); -/** PURE_IMPORTS_START _switchMap PURE_IMPORTS_END */ + Object.defineProperty(styles, groupName, { + value: group, + enumerable: false + }); -function switchMapTo(innerObservable, resultSelector) { - return resultSelector ? Object(_switchMap__WEBPACK_IMPORTED_MODULE_0__["switchMap"])(function () { return innerObservable; }, resultSelector) : Object(_switchMap__WEBPACK_IMPORTED_MODULE_0__["switchMap"])(function () { return innerObservable; }); -} -//# sourceMappingURL=switchMapTo.js.map + Object.defineProperty(styles, 'codes', { + value: codes, + enumerable: false + }); + } + const ansi2ansi = n => n; + const rgb2rgb = (r, g, b) => [r, g, b]; -/***/ }), -/* 457 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + styles.color.close = '\u001B[39m'; + styles.bgColor.close = '\u001B[49m'; -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "takeUntil", function() { return takeUntil; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(336); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(337); -/** PURE_IMPORTS_START tslib,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ + styles.color.ansi = { + ansi: wrapAnsi16(ansi2ansi, 0) + }; + styles.color.ansi256 = { + ansi256: wrapAnsi256(ansi2ansi, 0) + }; + styles.color.ansi16m = { + rgb: wrapAnsi16m(rgb2rgb, 0) + }; + styles.bgColor.ansi = { + ansi: wrapAnsi16(ansi2ansi, 10) + }; + styles.bgColor.ansi256 = { + ansi256: wrapAnsi256(ansi2ansi, 10) + }; + styles.bgColor.ansi16m = { + rgb: wrapAnsi16m(rgb2rgb, 10) + }; + for (let key of Object.keys(colorConvert)) { + if (typeof colorConvert[key] !== 'object') { + continue; + } -function takeUntil(notifier) { - return function (source) { return source.lift(new TakeUntilOperator(notifier)); }; -} -var TakeUntilOperator = /*@__PURE__*/ (function () { - function TakeUntilOperator(notifier) { - this.notifier = notifier; - } - TakeUntilOperator.prototype.call = function (subscriber, source) { - var takeUntilSubscriber = new TakeUntilSubscriber(subscriber); - var notifierSubscription = Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__["subscribeToResult"])(takeUntilSubscriber, this.notifier); - if (notifierSubscription && !takeUntilSubscriber.seenValue) { - takeUntilSubscriber.add(notifierSubscription); - return source.subscribe(takeUntilSubscriber); - } - return takeUntilSubscriber; - }; - return TakeUntilOperator; -}()); -var TakeUntilSubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](TakeUntilSubscriber, _super); - function TakeUntilSubscriber(destination) { - var _this = _super.call(this, destination) || this; - _this.seenValue = false; - return _this; - } - TakeUntilSubscriber.prototype.notifyNext = function (outerValue, innerValue, outerIndex, innerIndex, innerSub) { - this.seenValue = true; - this.complete(); - }; - TakeUntilSubscriber.prototype.notifyComplete = function () { - }; - return TakeUntilSubscriber; -}(_OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__["OuterSubscriber"])); -//# sourceMappingURL=takeUntil.js.map + const suite = colorConvert[key]; + if (key === 'ansi16') { + key = 'ansi'; + } -/***/ }), -/* 458 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + if ('ansi16' in suite) { + styles.color.ansi[key] = wrapAnsi16(suite.ansi16, 0); + styles.bgColor.ansi[key] = wrapAnsi16(suite.ansi16, 10); + } -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "takeWhile", function() { return takeWhile; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); -/** PURE_IMPORTS_START tslib,_Subscriber PURE_IMPORTS_END */ + if ('ansi256' in suite) { + styles.color.ansi256[key] = wrapAnsi256(suite.ansi256, 0); + styles.bgColor.ansi256[key] = wrapAnsi256(suite.ansi256, 10); + } + if ('rgb' in suite) { + styles.color.ansi16m[key] = wrapAnsi16m(suite.rgb, 0); + styles.bgColor.ansi16m[key] = wrapAnsi16m(suite.rgb, 10); + } + } -function takeWhile(predicate, inclusive) { - if (inclusive === void 0) { - inclusive = false; - } - return function (source) { - return source.lift(new TakeWhileOperator(predicate, inclusive)); - }; + return styles; } -var TakeWhileOperator = /*@__PURE__*/ (function () { - function TakeWhileOperator(predicate, inclusive) { - this.predicate = predicate; - this.inclusive = inclusive; - } - TakeWhileOperator.prototype.call = function (subscriber, source) { - return source.subscribe(new TakeWhileSubscriber(subscriber, this.predicate, this.inclusive)); - }; - return TakeWhileOperator; -}()); -var TakeWhileSubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](TakeWhileSubscriber, _super); - function TakeWhileSubscriber(destination, predicate, inclusive) { - var _this = _super.call(this, destination) || this; - _this.predicate = predicate; - _this.inclusive = inclusive; - _this.index = 0; - return _this; - } - TakeWhileSubscriber.prototype._next = function (value) { - var destination = this.destination; - var result; - try { - result = this.predicate(value, this.index++); - } - catch (err) { - destination.error(err); - return; - } - this.nextOrComplete(value, result); - }; - TakeWhileSubscriber.prototype.nextOrComplete = function (value, predicateResult) { - var destination = this.destination; - if (Boolean(predicateResult)) { - destination.next(value); - } - else { - if (this.inclusive) { - destination.next(value); - } - destination.complete(); - } - }; - return TakeWhileSubscriber; -}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); -//# sourceMappingURL=takeWhile.js.map +// Make the export immutable +Object.defineProperty(module, 'exports', { + enumerable: true, + get: assembleStyles +}); + +/* WEBPACK VAR INJECTION */}.call(this, __webpack_require__(5)(module))) /***/ }), -/* 459 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/* 678 */ +/***/ (function(module, exports, __webpack_require__) { "use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "tap", function() { return tap; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); -/* harmony import */ var _util_noop__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(292); -/* harmony import */ var _util_isFunction__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(280); -/** PURE_IMPORTS_START tslib,_Subscriber,_util_noop,_util_isFunction PURE_IMPORTS_END */ +const os = __webpack_require__(11); +const hasFlag = __webpack_require__(12); +const env = process.env; +let forceColor; +if (hasFlag('no-color') || + hasFlag('no-colors') || + hasFlag('color=false')) { + forceColor = false; +} else if (hasFlag('color') || + hasFlag('colors') || + hasFlag('color=true') || + hasFlag('color=always')) { + forceColor = true; +} +if ('FORCE_COLOR' in env) { + forceColor = env.FORCE_COLOR.length === 0 || parseInt(env.FORCE_COLOR, 10) !== 0; +} -function tap(nextOrObserver, error, complete) { - return function tapOperatorFunction(source) { - return source.lift(new DoOperator(nextOrObserver, error, complete)); - }; +function translateLevel(level) { + if (level === 0) { + return false; + } + + return { + level, + hasBasic: true, + has256: level >= 2, + has16m: level >= 3 + }; } -var DoOperator = /*@__PURE__*/ (function () { - function DoOperator(nextOrObserver, error, complete) { - this.nextOrObserver = nextOrObserver; - this.error = error; - this.complete = complete; - } - DoOperator.prototype.call = function (subscriber, source) { - return source.subscribe(new TapSubscriber(subscriber, this.nextOrObserver, this.error, this.complete)); - }; - return DoOperator; -}()); -var TapSubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](TapSubscriber, _super); - function TapSubscriber(destination, observerOrNext, error, complete) { - var _this = _super.call(this, destination) || this; - _this._tapNext = _util_noop__WEBPACK_IMPORTED_MODULE_2__["noop"]; - _this._tapError = _util_noop__WEBPACK_IMPORTED_MODULE_2__["noop"]; - _this._tapComplete = _util_noop__WEBPACK_IMPORTED_MODULE_2__["noop"]; - _this._tapError = error || _util_noop__WEBPACK_IMPORTED_MODULE_2__["noop"]; - _this._tapComplete = complete || _util_noop__WEBPACK_IMPORTED_MODULE_2__["noop"]; - if (Object(_util_isFunction__WEBPACK_IMPORTED_MODULE_3__["isFunction"])(observerOrNext)) { - _this._context = _this; - _this._tapNext = observerOrNext; - } - else if (observerOrNext) { - _this._context = observerOrNext; - _this._tapNext = observerOrNext.next || _util_noop__WEBPACK_IMPORTED_MODULE_2__["noop"]; - _this._tapError = observerOrNext.error || _util_noop__WEBPACK_IMPORTED_MODULE_2__["noop"]; - _this._tapComplete = observerOrNext.complete || _util_noop__WEBPACK_IMPORTED_MODULE_2__["noop"]; - } - return _this; - } - TapSubscriber.prototype._next = function (value) { - try { - this._tapNext.call(this._context, value); - } - catch (err) { - this.destination.error(err); - return; - } - this.destination.next(value); - }; - TapSubscriber.prototype._error = function (err) { - try { - this._tapError.call(this._context, err); - } - catch (err) { - this.destination.error(err); - return; - } - this.destination.error(err); - }; - TapSubscriber.prototype._complete = function () { - try { - this._tapComplete.call(this._context); - } - catch (err) { - this.destination.error(err); - return; - } - return this.destination.complete(); - }; - return TapSubscriber; -}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); -//# sourceMappingURL=tap.js.map +function supportsColor(stream) { + if (forceColor === false) { + return 0; + } -/***/ }), -/* 460 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + if (hasFlag('color=16m') || + hasFlag('color=full') || + hasFlag('color=truecolor')) { + return 3; + } -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "defaultThrottleConfig", function() { return defaultThrottleConfig; }); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "throttle", function() { return throttle; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(336); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(337); -/** PURE_IMPORTS_START tslib,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ + if (hasFlag('color=256')) { + return 2; + } + + if (stream && !stream.isTTY && forceColor !== true) { + // VS code debugger doesn't have isTTY set + if (env.VSCODE_PID) { + return 1; + } + return 0; + } + const min = forceColor ? 1 : 0; + if (process.platform === 'win32') { + // Node.js 7.5.0 is the first version of Node.js to include a patch to + // libuv that enables 256 color output on Windows. Anything earlier and it + // won't work. However, here we target Node.js 8 at minimum as it is an LTS + // release, and Node.js 7 is not. Windows 10 build 10586 is the first Windows + // release that supports 256 colors. Windows 10 build 14931 is the first release + // that supports 16m/TrueColor. + const osRelease = os.release().split('.'); + if ( + Number(process.versions.node.split('.')[0]) >= 8 && + Number(osRelease[0]) >= 10 && + Number(osRelease[2]) >= 10586 + ) { + return Number(osRelease[2]) >= 14931 ? 3 : 2; + } -var defaultThrottleConfig = { - leading: true, - trailing: false -}; -function throttle(durationSelector, config) { - if (config === void 0) { - config = defaultThrottleConfig; - } - return function (source) { return source.lift(new ThrottleOperator(durationSelector, config.leading, config.trailing)); }; -} -var ThrottleOperator = /*@__PURE__*/ (function () { - function ThrottleOperator(durationSelector, leading, trailing) { - this.durationSelector = durationSelector; - this.leading = leading; - this.trailing = trailing; - } - ThrottleOperator.prototype.call = function (subscriber, source) { - return source.subscribe(new ThrottleSubscriber(subscriber, this.durationSelector, this.leading, this.trailing)); - }; - return ThrottleOperator; -}()); -var ThrottleSubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](ThrottleSubscriber, _super); - function ThrottleSubscriber(destination, durationSelector, _leading, _trailing) { - var _this = _super.call(this, destination) || this; - _this.destination = destination; - _this.durationSelector = durationSelector; - _this._leading = _leading; - _this._trailing = _trailing; - _this._hasValue = false; - return _this; - } - ThrottleSubscriber.prototype._next = function (value) { - this._hasValue = true; - this._sendValue = value; - if (!this._throttled) { - if (this._leading) { - this.send(); - } - else { - this.throttle(value); - } - } - }; - ThrottleSubscriber.prototype.send = function () { - var _a = this, _hasValue = _a._hasValue, _sendValue = _a._sendValue; - if (_hasValue) { - this.destination.next(_sendValue); - this.throttle(_sendValue); - } - this._hasValue = false; - this._sendValue = null; - }; - ThrottleSubscriber.prototype.throttle = function (value) { - var duration = this.tryDurationSelector(value); - if (!!duration) { - this.add(this._throttled = Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__["subscribeToResult"])(this, duration)); - } - }; - ThrottleSubscriber.prototype.tryDurationSelector = function (value) { - try { - return this.durationSelector(value); - } - catch (err) { - this.destination.error(err); - return null; - } - }; - ThrottleSubscriber.prototype.throttlingDone = function () { - var _a = this, _throttled = _a._throttled, _trailing = _a._trailing; - if (_throttled) { - _throttled.unsubscribe(); - } - this._throttled = null; - if (_trailing) { - this.send(); - } - }; - ThrottleSubscriber.prototype.notifyNext = function (outerValue, innerValue, outerIndex, innerIndex, innerSub) { - this.throttlingDone(); - }; - ThrottleSubscriber.prototype.notifyComplete = function () { - this.throttlingDone(); - }; - return ThrottleSubscriber; -}(_OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__["OuterSubscriber"])); -//# sourceMappingURL=throttle.js.map + return 1; + } + + if ('CI' in env) { + if (['TRAVIS', 'CIRCLECI', 'APPVEYOR', 'GITLAB_CI'].some(sign => sign in env) || env.CI_NAME === 'codeship') { + return 1; + } + return min; + } -/***/ }), -/* 461 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + if ('TEAMCITY_VERSION' in env) { + return /^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(env.TEAMCITY_VERSION) ? 1 : 0; + } -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "throttleTime", function() { return throttleTime; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); -/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(322); -/* harmony import */ var _throttle__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(460); -/** PURE_IMPORTS_START tslib,_Subscriber,_scheduler_async,_throttle PURE_IMPORTS_END */ + if (env.COLORTERM === 'truecolor') { + return 3; + } + + if ('TERM_PROGRAM' in env) { + const version = parseInt((env.TERM_PROGRAM_VERSION || '').split('.')[0], 10); + + switch (env.TERM_PROGRAM) { + case 'iTerm.app': + return version >= 3 ? 3 : 2; + case 'Apple_Terminal': + return 2; + // No default + } + } + if (/-256(color)?$/i.test(env.TERM)) { + return 2; + } + if (/^screen|^xterm|^vt100|^rxvt|color|ansi|cygwin|linux/i.test(env.TERM)) { + return 1; + } + if ('COLORTERM' in env) { + return 1; + } -function throttleTime(duration, scheduler, config) { - if (scheduler === void 0) { - scheduler = _scheduler_async__WEBPACK_IMPORTED_MODULE_2__["async"]; - } - if (config === void 0) { - config = _throttle__WEBPACK_IMPORTED_MODULE_3__["defaultThrottleConfig"]; - } - return function (source) { return source.lift(new ThrottleTimeOperator(duration, scheduler, config.leading, config.trailing)); }; + if (env.TERM === 'dumb') { + return min; + } + + return min; } -var ThrottleTimeOperator = /*@__PURE__*/ (function () { - function ThrottleTimeOperator(duration, scheduler, leading, trailing) { - this.duration = duration; - this.scheduler = scheduler; - this.leading = leading; - this.trailing = trailing; - } - ThrottleTimeOperator.prototype.call = function (subscriber, source) { - return source.subscribe(new ThrottleTimeSubscriber(subscriber, this.duration, this.scheduler, this.leading, this.trailing)); - }; - return ThrottleTimeOperator; -}()); -var ThrottleTimeSubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](ThrottleTimeSubscriber, _super); - function ThrottleTimeSubscriber(destination, duration, scheduler, leading, trailing) { - var _this = _super.call(this, destination) || this; - _this.duration = duration; - _this.scheduler = scheduler; - _this.leading = leading; - _this.trailing = trailing; - _this._hasTrailingValue = false; - _this._trailingValue = null; - return _this; - } - ThrottleTimeSubscriber.prototype._next = function (value) { - if (this.throttled) { - if (this.trailing) { - this._trailingValue = value; - this._hasTrailingValue = true; - } - } - else { - this.add(this.throttled = this.scheduler.schedule(dispatchNext, this.duration, { subscriber: this })); - if (this.leading) { - this.destination.next(value); - } - else if (this.trailing) { - this._trailingValue = value; - this._hasTrailingValue = true; - } - } - }; - ThrottleTimeSubscriber.prototype._complete = function () { - if (this._hasTrailingValue) { - this.destination.next(this._trailingValue); - this.destination.complete(); - } - else { - this.destination.complete(); - } - }; - ThrottleTimeSubscriber.prototype.clearThrottle = function () { - var throttled = this.throttled; - if (throttled) { - if (this.trailing && this._hasTrailingValue) { - this.destination.next(this._trailingValue); - this._trailingValue = null; - this._hasTrailingValue = false; - } - throttled.unsubscribe(); - this.remove(throttled); - this.throttled = null; - } - }; - return ThrottleTimeSubscriber; -}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); -function dispatchNext(arg) { - var subscriber = arg.subscriber; - subscriber.clearThrottle(); + +function getSupportLevel(stream) { + const level = supportsColor(stream); + return translateLevel(level); } -//# sourceMappingURL=throttleTime.js.map + +module.exports = { + supportsColor: getSupportLevel, + stdout: getSupportLevel(process.stdout), + stderr: getSupportLevel(process.stderr) +}; /***/ }), -/* 462 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/* 679 */ +/***/ (function(module, exports, __webpack_require__) { "use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "timeInterval", function() { return timeInterval; }); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "TimeInterval", function() { return TimeInterval; }); -/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(322); -/* harmony import */ var _scan__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(422); -/* harmony import */ var _observable_defer__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(357); -/* harmony import */ var _map__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(333); -/** PURE_IMPORTS_START _scheduler_async,_scan,_observable_defer,_map PURE_IMPORTS_END */ +const TEMPLATE_REGEX = /(?:\\(u[a-f\d]{4}|x[a-f\d]{2}|.))|(?:\{(~)?(\w+(?:\([^)]*\))?(?:\.\w+(?:\([^)]*\))?)*)(?:[ \t]|(?=\r?\n)))|(\})|((?:.|[\r\n\f])+?)/gi; +const STYLE_REGEX = /(?:^|\.)(\w+)(?:\(([^)]*)\))?/g; +const STRING_REGEX = /^(['"])((?:\\.|(?!\1)[^\\])*)\1$/; +const ESCAPE_REGEX = /\\(u[a-f\d]{4}|x[a-f\d]{2}|.)|([^\\])/gi; + +const ESCAPES = new Map([ + ['n', '\n'], + ['r', '\r'], + ['t', '\t'], + ['b', '\b'], + ['f', '\f'], + ['v', '\v'], + ['0', '\0'], + ['\\', '\\'], + ['e', '\u001B'], + ['a', '\u0007'] +]); +function unescape(c) { + if ((c[0] === 'u' && c.length === 5) || (c[0] === 'x' && c.length === 3)) { + return String.fromCharCode(parseInt(c.slice(1), 16)); + } + return ESCAPES.get(c) || c; +} -function timeInterval(scheduler) { - if (scheduler === void 0) { - scheduler = _scheduler_async__WEBPACK_IMPORTED_MODULE_0__["async"]; - } - return function (source) { - return Object(_observable_defer__WEBPACK_IMPORTED_MODULE_2__["defer"])(function () { - return source.pipe(Object(_scan__WEBPACK_IMPORTED_MODULE_1__["scan"])(function (_a, value) { - var current = _a.current; - return ({ value: value, current: scheduler.now(), last: current }); - }, { current: scheduler.now(), value: undefined, last: undefined }), Object(_map__WEBPACK_IMPORTED_MODULE_3__["map"])(function (_a) { - var current = _a.current, last = _a.last, value = _a.value; - return new TimeInterval(value, current - last); - })); - }); - }; +function parseArguments(name, args) { + const results = []; + const chunks = args.trim().split(/\s*,\s*/g); + let matches; + + for (const chunk of chunks) { + if (!isNaN(chunk)) { + results.push(Number(chunk)); + } else if ((matches = chunk.match(STRING_REGEX))) { + results.push(matches[2].replace(ESCAPE_REGEX, (m, escape, chr) => escape ? unescape(escape) : chr)); + } else { + throw new Error(`Invalid Chalk template style argument: ${chunk} (in style '${name}')`); + } + } + + return results; } -var TimeInterval = /*@__PURE__*/ (function () { - function TimeInterval(value, interval) { - this.value = value; - this.interval = interval; - } - return TimeInterval; -}()); -//# sourceMappingURL=timeInterval.js.map +function parseStyle(style) { + STYLE_REGEX.lastIndex = 0; + const results = []; + let matches; -/***/ }), -/* 463 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + while ((matches = STYLE_REGEX.exec(style)) !== null) { + const name = matches[1]; -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "timeout", function() { return timeout; }); -/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(322); -/* harmony import */ var _util_TimeoutError__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(331); -/* harmony import */ var _timeoutWith__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(464); -/* harmony import */ var _observable_throwError__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(316); -/** PURE_IMPORTS_START _scheduler_async,_util_TimeoutError,_timeoutWith,_observable_throwError PURE_IMPORTS_END */ + if (matches[2]) { + const args = parseArguments(name, matches[2]); + results.push([name].concat(args)); + } else { + results.push([name]); + } + } + + return results; +} + +function buildStyle(chalk, styles) { + const enabled = {}; + + for (const layer of styles) { + for (const style of layer.styles) { + enabled[style[0]] = layer.inverse ? null : style.slice(1); + } + } + + let current = chalk; + for (const styleName of Object.keys(enabled)) { + if (Array.isArray(enabled[styleName])) { + if (!(styleName in current)) { + throw new Error(`Unknown Chalk style: ${styleName}`); + } + + if (enabled[styleName].length > 0) { + current = current[styleName].apply(current, enabled[styleName]); + } else { + current = current[styleName]; + } + } + } + + return current; +} + +module.exports = (chalk, tmp) => { + const styles = []; + const chunks = []; + let chunk = []; + + // eslint-disable-next-line max-params + tmp.replace(TEMPLATE_REGEX, (m, escapeChar, inverse, style, close, chr) => { + if (escapeChar) { + chunk.push(unescape(escapeChar)); + } else if (style) { + const str = chunk.join(''); + chunk = []; + chunks.push(styles.length === 0 ? str : buildStyle(chalk, styles)(str)); + styles.push({inverse, styles: parseStyle(style)}); + } else if (close) { + if (styles.length === 0) { + throw new Error('Found extraneous } in Chalk template literal'); + } + chunks.push(buildStyle(chalk, styles)(chunk.join(''))); + chunk = []; + styles.pop(); + } else { + chunk.push(chr); + } + }); + chunks.push(chunk.join('')); + if (styles.length > 0) { + const errMsg = `Chalk template literal is missing ${styles.length} closing bracket${styles.length === 1 ? '' : 's'} (\`}\`)`; + throw new Error(errMsg); + } -function timeout(due, scheduler) { - if (scheduler === void 0) { - scheduler = _scheduler_async__WEBPACK_IMPORTED_MODULE_0__["async"]; - } - return Object(_timeoutWith__WEBPACK_IMPORTED_MODULE_2__["timeoutWith"])(due, Object(_observable_throwError__WEBPACK_IMPORTED_MODULE_3__["throwError"])(new _util_TimeoutError__WEBPACK_IMPORTED_MODULE_1__["TimeoutError"]()), scheduler); -} -//# sourceMappingURL=timeout.js.map + return chunks.join(''); +}; /***/ }), -/* 464 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/* 680 */ +/***/ (function(module, exports, __webpack_require__) { "use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "timeoutWith", function() { return timeoutWith; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(322); -/* harmony import */ var _util_isDate__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(396); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(336); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(337); -/** PURE_IMPORTS_START tslib,_scheduler_async,_util_isDate,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ - +const restoreCursor = __webpack_require__(681); +let hidden = false; +exports.show = stream => { + const s = stream || process.stderr; -function timeoutWith(due, withObservable, scheduler) { - if (scheduler === void 0) { - scheduler = _scheduler_async__WEBPACK_IMPORTED_MODULE_1__["async"]; - } - return function (source) { - var absoluteTimeout = Object(_util_isDate__WEBPACK_IMPORTED_MODULE_2__["isDate"])(due); - var waitFor = absoluteTimeout ? (+due - scheduler.now()) : Math.abs(due); - return source.lift(new TimeoutWithOperator(waitFor, absoluteTimeout, withObservable, scheduler)); - }; -} -var TimeoutWithOperator = /*@__PURE__*/ (function () { - function TimeoutWithOperator(waitFor, absoluteTimeout, withObservable, scheduler) { - this.waitFor = waitFor; - this.absoluteTimeout = absoluteTimeout; - this.withObservable = withObservable; - this.scheduler = scheduler; - } - TimeoutWithOperator.prototype.call = function (subscriber, source) { - return source.subscribe(new TimeoutWithSubscriber(subscriber, this.absoluteTimeout, this.waitFor, this.withObservable, this.scheduler)); - }; - return TimeoutWithOperator; -}()); -var TimeoutWithSubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](TimeoutWithSubscriber, _super); - function TimeoutWithSubscriber(destination, absoluteTimeout, waitFor, withObservable, scheduler) { - var _this = _super.call(this, destination) || this; - _this.absoluteTimeout = absoluteTimeout; - _this.waitFor = waitFor; - _this.withObservable = withObservable; - _this.scheduler = scheduler; - _this.action = null; - _this.scheduleTimeout(); - return _this; - } - TimeoutWithSubscriber.dispatchTimeout = function (subscriber) { - var withObservable = subscriber.withObservable; - subscriber._unsubscribeAndRecycle(); - subscriber.add(Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_4__["subscribeToResult"])(subscriber, withObservable)); - }; - TimeoutWithSubscriber.prototype.scheduleTimeout = function () { - var action = this.action; - if (action) { - this.action = action.schedule(this, this.waitFor); - } - else { - this.add(this.action = this.scheduler.schedule(TimeoutWithSubscriber.dispatchTimeout, this.waitFor, this)); - } - }; - TimeoutWithSubscriber.prototype._next = function (value) { - if (!this.absoluteTimeout) { - this.scheduleTimeout(); - } - _super.prototype._next.call(this, value); - }; - TimeoutWithSubscriber.prototype._unsubscribe = function () { - this.action = null; - this.scheduler = null; - this.withObservable = null; - }; - return TimeoutWithSubscriber; -}(_OuterSubscriber__WEBPACK_IMPORTED_MODULE_3__["OuterSubscriber"])); -//# sourceMappingURL=timeoutWith.js.map + if (!s.isTTY) { + return; + } + hidden = false; + s.write('\u001b[?25h'); +}; -/***/ }), -/* 465 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +exports.hide = stream => { + const s = stream || process.stderr; -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "timestamp", function() { return timestamp; }); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Timestamp", function() { return Timestamp; }); -/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(322); -/* harmony import */ var _map__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(333); -/** PURE_IMPORTS_START _scheduler_async,_map PURE_IMPORTS_END */ + if (!s.isTTY) { + return; + } + restoreCursor(); + hidden = true; + s.write('\u001b[?25l'); +}; -function timestamp(scheduler) { - if (scheduler === void 0) { - scheduler = _scheduler_async__WEBPACK_IMPORTED_MODULE_0__["async"]; - } - return Object(_map__WEBPACK_IMPORTED_MODULE_1__["map"])(function (value) { return new Timestamp(value, scheduler.now()); }); -} -var Timestamp = /*@__PURE__*/ (function () { - function Timestamp(value, timestamp) { - this.value = value; - this.timestamp = timestamp; - } - return Timestamp; -}()); +exports.toggle = (force, stream) => { + if (force !== undefined) { + hidden = force; + } -//# sourceMappingURL=timestamp.js.map + if (hidden) { + exports.show(stream); + } else { + exports.hide(stream); + } +}; /***/ }), -/* 466 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/* 681 */ +/***/ (function(module, exports, __webpack_require__) { "use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "toArray", function() { return toArray; }); -/* harmony import */ var _reduce__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(421); -/** PURE_IMPORTS_START _reduce PURE_IMPORTS_END */ -function toArrayReducer(arr, item, index) { - if (index === 0) { - return [item]; - } - arr.push(item); - return arr; -} -function toArray() { - return Object(_reduce__WEBPACK_IMPORTED_MODULE_0__["reduce"])(toArrayReducer, []); -} -//# sourceMappingURL=toArray.js.map +const onetime = __webpack_require__(682); +const signalExit = __webpack_require__(377); + +module.exports = onetime(() => { + signalExit(() => { + process.stderr.write('\u001b[?25h'); + }, {alwaysLast: true}); +}); /***/ }), -/* 467 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/* 682 */ +/***/ (function(module, exports, __webpack_require__) { "use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "window", function() { return window; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(294); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(336); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(337); -/** PURE_IMPORTS_START tslib,_Subject,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ +const mimicFn = __webpack_require__(683); +module.exports = (fn, opts) => { + // TODO: Remove this in v3 + if (opts === true) { + throw new TypeError('The second argument is now an options object'); + } + if (typeof fn !== 'function') { + throw new TypeError('Expected a function'); + } -function window(windowBoundaries) { - return function windowOperatorFunction(source) { - return source.lift(new WindowOperator(windowBoundaries)); - }; -} -var WindowOperator = /*@__PURE__*/ (function () { - function WindowOperator(windowBoundaries) { - this.windowBoundaries = windowBoundaries; - } - WindowOperator.prototype.call = function (subscriber, source) { - var windowSubscriber = new WindowSubscriber(subscriber); - var sourceSubscription = source.subscribe(windowSubscriber); - if (!sourceSubscription.closed) { - windowSubscriber.add(Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_3__["subscribeToResult"])(windowSubscriber, this.windowBoundaries)); - } - return sourceSubscription; - }; - return WindowOperator; -}()); -var WindowSubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](WindowSubscriber, _super); - function WindowSubscriber(destination) { - var _this = _super.call(this, destination) || this; - _this.window = new _Subject__WEBPACK_IMPORTED_MODULE_1__["Subject"](); - destination.next(_this.window); - return _this; - } - WindowSubscriber.prototype.notifyNext = function (outerValue, innerValue, outerIndex, innerIndex, innerSub) { - this.openWindow(); - }; - WindowSubscriber.prototype.notifyError = function (error, innerSub) { - this._error(error); - }; - WindowSubscriber.prototype.notifyComplete = function (innerSub) { - this._complete(); - }; - WindowSubscriber.prototype._next = function (value) { - this.window.next(value); - }; - WindowSubscriber.prototype._error = function (err) { - this.window.error(err); - this.destination.error(err); - }; - WindowSubscriber.prototype._complete = function () { - this.window.complete(); - this.destination.complete(); - }; - WindowSubscriber.prototype._unsubscribe = function () { - this.window = null; - }; - WindowSubscriber.prototype.openWindow = function () { - var prevWindow = this.window; - if (prevWindow) { - prevWindow.complete(); - } - var destination = this.destination; - var newWindow = this.window = new _Subject__WEBPACK_IMPORTED_MODULE_1__["Subject"](); - destination.next(newWindow); - }; - return WindowSubscriber; -}(_OuterSubscriber__WEBPACK_IMPORTED_MODULE_2__["OuterSubscriber"])); -//# sourceMappingURL=window.js.map + opts = opts || {}; + let ret; + let called = false; + const fnName = fn.displayName || fn.name || ''; -/***/ }), -/* 468 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { + const onetime = function () { + if (called) { + if (opts.throw === true) { + throw new Error(`Function \`${fnName}\` can only be called once`); + } -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "windowCount", function() { return windowCount; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(278); -/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(294); -/** PURE_IMPORTS_START tslib,_Subscriber,_Subject PURE_IMPORTS_END */ + return ret; + } + called = true; + ret = fn.apply(this, arguments); + fn = null; + return ret; + }; -function windowCount(windowSize, startWindowEvery) { - if (startWindowEvery === void 0) { - startWindowEvery = 0; - } - return function windowCountOperatorFunction(source) { - return source.lift(new WindowCountOperator(windowSize, startWindowEvery)); - }; -} -var WindowCountOperator = /*@__PURE__*/ (function () { - function WindowCountOperator(windowSize, startWindowEvery) { - this.windowSize = windowSize; - this.startWindowEvery = startWindowEvery; - } - WindowCountOperator.prototype.call = function (subscriber, source) { - return source.subscribe(new WindowCountSubscriber(subscriber, this.windowSize, this.startWindowEvery)); - }; - return WindowCountOperator; -}()); -var WindowCountSubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](WindowCountSubscriber, _super); - function WindowCountSubscriber(destination, windowSize, startWindowEvery) { - var _this = _super.call(this, destination) || this; - _this.destination = destination; - _this.windowSize = windowSize; - _this.startWindowEvery = startWindowEvery; - _this.windows = [new _Subject__WEBPACK_IMPORTED_MODULE_2__["Subject"]()]; - _this.count = 0; - destination.next(_this.windows[0]); - return _this; - } - WindowCountSubscriber.prototype._next = function (value) { - var startWindowEvery = (this.startWindowEvery > 0) ? this.startWindowEvery : this.windowSize; - var destination = this.destination; - var windowSize = this.windowSize; - var windows = this.windows; - var len = windows.length; - for (var i = 0; i < len && !this.closed; i++) { - windows[i].next(value); - } - var c = this.count - windowSize + 1; - if (c >= 0 && c % startWindowEvery === 0 && !this.closed) { - windows.shift().complete(); - } - if (++this.count % startWindowEvery === 0 && !this.closed) { - var window_1 = new _Subject__WEBPACK_IMPORTED_MODULE_2__["Subject"](); - windows.push(window_1); - destination.next(window_1); - } - }; - WindowCountSubscriber.prototype._error = function (err) { - var windows = this.windows; - if (windows) { - while (windows.length > 0 && !this.closed) { - windows.shift().error(err); - } - } - this.destination.error(err); - }; - WindowCountSubscriber.prototype._complete = function () { - var windows = this.windows; - if (windows) { - while (windows.length > 0 && !this.closed) { - windows.shift().complete(); - } - } - this.destination.complete(); - }; - WindowCountSubscriber.prototype._unsubscribe = function () { - this.count = 0; - this.windows = null; - }; - return WindowCountSubscriber; -}(_Subscriber__WEBPACK_IMPORTED_MODULE_1__["Subscriber"])); -//# sourceMappingURL=windowCount.js.map + mimicFn(onetime, fn); + + return onetime; +}; /***/ }), -/* 469 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/* 683 */ +/***/ (function(module, exports, __webpack_require__) { "use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "windowTime", function() { return windowTime; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(294); -/* harmony import */ var _scheduler_async__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(322); -/* harmony import */ var _Subscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(278); -/* harmony import */ var _util_isNumeric__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(364); -/* harmony import */ var _util_isScheduler__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(312); -/** PURE_IMPORTS_START tslib,_Subject,_scheduler_async,_Subscriber,_util_isNumeric,_util_isScheduler PURE_IMPORTS_END */ +module.exports = (to, from) => { + // TODO: use `Reflect.ownKeys()` when targeting Node.js 6 + for (const prop of Object.getOwnPropertyNames(from).concat(Object.getOwnPropertySymbols(from))) { + Object.defineProperty(to, prop, Object.getOwnPropertyDescriptor(from, prop)); + } + return to; +}; +/***/ }), +/* 684 */ +/***/ (function(module, exports, __webpack_require__) { +"use strict"; -function windowTime(windowTimeSpan) { - var scheduler = _scheduler_async__WEBPACK_IMPORTED_MODULE_2__["async"]; - var windowCreationInterval = null; - var maxWindowSize = Number.POSITIVE_INFINITY; - if (Object(_util_isScheduler__WEBPACK_IMPORTED_MODULE_5__["isScheduler"])(arguments[3])) { - scheduler = arguments[3]; - } - if (Object(_util_isScheduler__WEBPACK_IMPORTED_MODULE_5__["isScheduler"])(arguments[2])) { - scheduler = arguments[2]; - } - else if (Object(_util_isNumeric__WEBPACK_IMPORTED_MODULE_4__["isNumeric"])(arguments[2])) { - maxWindowSize = arguments[2]; - } - if (Object(_util_isScheduler__WEBPACK_IMPORTED_MODULE_5__["isScheduler"])(arguments[1])) { - scheduler = arguments[1]; - } - else if (Object(_util_isNumeric__WEBPACK_IMPORTED_MODULE_4__["isNumeric"])(arguments[1])) { - windowCreationInterval = arguments[1]; - } - return function windowTimeOperatorFunction(source) { - return source.lift(new WindowTimeOperator(windowTimeSpan, windowCreationInterval, maxWindowSize, scheduler)); - }; -} -var WindowTimeOperator = /*@__PURE__*/ (function () { - function WindowTimeOperator(windowTimeSpan, windowCreationInterval, maxWindowSize, scheduler) { - this.windowTimeSpan = windowTimeSpan; - this.windowCreationInterval = windowCreationInterval; - this.maxWindowSize = maxWindowSize; - this.scheduler = scheduler; - } - WindowTimeOperator.prototype.call = function (subscriber, source) { - return source.subscribe(new WindowTimeSubscriber(subscriber, this.windowTimeSpan, this.windowCreationInterval, this.maxWindowSize, this.scheduler)); - }; - return WindowTimeOperator; -}()); -var CountedSubject = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](CountedSubject, _super); - function CountedSubject() { - var _this = _super !== null && _super.apply(this, arguments) || this; - _this._numberOfNextedValues = 0; - return _this; - } - CountedSubject.prototype.next = function (value) { - this._numberOfNextedValues++; - _super.prototype.next.call(this, value); - }; - Object.defineProperty(CountedSubject.prototype, "numberOfNextedValues", { - get: function () { - return this._numberOfNextedValues; - }, - enumerable: true, - configurable: true - }); - return CountedSubject; -}(_Subject__WEBPACK_IMPORTED_MODULE_1__["Subject"])); -var WindowTimeSubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](WindowTimeSubscriber, _super); - function WindowTimeSubscriber(destination, windowTimeSpan, windowCreationInterval, maxWindowSize, scheduler) { - var _this = _super.call(this, destination) || this; - _this.destination = destination; - _this.windowTimeSpan = windowTimeSpan; - _this.windowCreationInterval = windowCreationInterval; - _this.maxWindowSize = maxWindowSize; - _this.scheduler = scheduler; - _this.windows = []; - var window = _this.openWindow(); - if (windowCreationInterval !== null && windowCreationInterval >= 0) { - var closeState = { subscriber: _this, window: window, context: null }; - var creationState = { windowTimeSpan: windowTimeSpan, windowCreationInterval: windowCreationInterval, subscriber: _this, scheduler: scheduler }; - _this.add(scheduler.schedule(dispatchWindowClose, windowTimeSpan, closeState)); - _this.add(scheduler.schedule(dispatchWindowCreation, windowCreationInterval, creationState)); - } - else { - var timeSpanOnlyState = { subscriber: _this, window: window, windowTimeSpan: windowTimeSpan }; - _this.add(scheduler.schedule(dispatchWindowTimeSpanOnly, windowTimeSpan, timeSpanOnlyState)); - } - return _this; - } - WindowTimeSubscriber.prototype._next = function (value) { - var windows = this.windows; - var len = windows.length; - for (var i = 0; i < len; i++) { - var window_1 = windows[i]; - if (!window_1.closed) { - window_1.next(value); - if (window_1.numberOfNextedValues >= this.maxWindowSize) { - this.closeWindow(window_1); - } - } - } - }; - WindowTimeSubscriber.prototype._error = function (err) { - var windows = this.windows; - while (windows.length > 0) { - windows.shift().error(err); - } - this.destination.error(err); - }; - WindowTimeSubscriber.prototype._complete = function () { - var windows = this.windows; - while (windows.length > 0) { - var window_2 = windows.shift(); - if (!window_2.closed) { - window_2.complete(); - } - } - this.destination.complete(); - }; - WindowTimeSubscriber.prototype.openWindow = function () { - var window = new CountedSubject(); - this.windows.push(window); - var destination = this.destination; - destination.next(window); - return window; - }; - WindowTimeSubscriber.prototype.closeWindow = function (window) { - window.complete(); - var windows = this.windows; - windows.splice(windows.indexOf(window), 1); - }; - return WindowTimeSubscriber; -}(_Subscriber__WEBPACK_IMPORTED_MODULE_3__["Subscriber"])); -function dispatchWindowTimeSpanOnly(state) { - var subscriber = state.subscriber, windowTimeSpan = state.windowTimeSpan, window = state.window; - if (window) { - subscriber.closeWindow(window); - } - state.window = subscriber.openWindow(); - this.schedule(state, windowTimeSpan); -} -function dispatchWindowCreation(state) { - var windowTimeSpan = state.windowTimeSpan, subscriber = state.subscriber, scheduler = state.scheduler, windowCreationInterval = state.windowCreationInterval; - var window = subscriber.openWindow(); - var action = this; - var context = { action: action, subscription: null }; - var timeSpanState = { subscriber: subscriber, window: window, context: context }; - context.subscription = scheduler.schedule(dispatchWindowClose, windowTimeSpan, timeSpanState); - action.add(context.subscription); - action.schedule(state, windowCreationInterval); -} -function dispatchWindowClose(state) { - var subscriber = state.subscriber, window = state.window, context = state.context; - if (context && context.action && context.subscription) { - context.action.remove(context.subscription); - } - subscriber.closeWindow(window); -} -//# sourceMappingURL=windowTime.js.map +module.exports = __webpack_require__(685); + + +/***/ }), +/* 685 */ +/***/ (function(module) { +module.exports = JSON.parse("{\"dots\":{\"interval\":80,\"frames\":[\"⠋\",\"⠙\",\"⠹\",\"⠸\",\"⠼\",\"⠴\",\"⠦\",\"⠧\",\"⠇\",\"⠏\"]},\"dots2\":{\"interval\":80,\"frames\":[\"⣾\",\"⣽\",\"⣻\",\"⢿\",\"⡿\",\"⣟\",\"⣯\",\"⣷\"]},\"dots3\":{\"interval\":80,\"frames\":[\"⠋\",\"⠙\",\"⠚\",\"⠞\",\"⠖\",\"⠦\",\"⠴\",\"⠲\",\"⠳\",\"⠓\"]},\"dots4\":{\"interval\":80,\"frames\":[\"⠄\",\"⠆\",\"⠇\",\"⠋\",\"⠙\",\"⠸\",\"⠰\",\"⠠\",\"⠰\",\"⠸\",\"⠙\",\"⠋\",\"⠇\",\"⠆\"]},\"dots5\":{\"interval\":80,\"frames\":[\"⠋\",\"⠙\",\"⠚\",\"⠒\",\"⠂\",\"⠂\",\"⠒\",\"⠲\",\"⠴\",\"⠦\",\"⠖\",\"⠒\",\"⠐\",\"⠐\",\"⠒\",\"⠓\",\"⠋\"]},\"dots6\":{\"interval\":80,\"frames\":[\"⠁\",\"⠉\",\"⠙\",\"⠚\",\"⠒\",\"⠂\",\"⠂\",\"⠒\",\"⠲\",\"⠴\",\"⠤\",\"⠄\",\"⠄\",\"⠤\",\"⠴\",\"⠲\",\"⠒\",\"⠂\",\"⠂\",\"⠒\",\"⠚\",\"⠙\",\"⠉\",\"⠁\"]},\"dots7\":{\"interval\":80,\"frames\":[\"⠈\",\"⠉\",\"⠋\",\"⠓\",\"⠒\",\"⠐\",\"⠐\",\"⠒\",\"⠖\",\"⠦\",\"⠤\",\"⠠\",\"⠠\",\"⠤\",\"⠦\",\"⠖\",\"⠒\",\"⠐\",\"⠐\",\"⠒\",\"⠓\",\"⠋\",\"⠉\",\"⠈\"]},\"dots8\":{\"interval\":80,\"frames\":[\"⠁\",\"⠁\",\"⠉\",\"⠙\",\"⠚\",\"⠒\",\"⠂\",\"⠂\",\"⠒\",\"⠲\",\"⠴\",\"⠤\",\"⠄\",\"⠄\",\"⠤\",\"⠠\",\"⠠\",\"⠤\",\"⠦\",\"⠖\",\"⠒\",\"⠐\",\"⠐\",\"⠒\",\"⠓\",\"⠋\",\"⠉\",\"⠈\",\"⠈\"]},\"dots9\":{\"interval\":80,\"frames\":[\"⢹\",\"⢺\",\"⢼\",\"⣸\",\"⣇\",\"⡧\",\"⡗\",\"⡏\"]},\"dots10\":{\"interval\":80,\"frames\":[\"⢄\",\"⢂\",\"⢁\",\"⡁\",\"⡈\",\"⡐\",\"⡠\"]},\"dots11\":{\"interval\":100,\"frames\":[\"⠁\",\"⠂\",\"⠄\",\"⡀\",\"⢀\",\"⠠\",\"⠐\",\"⠈\"]},\"dots12\":{\"interval\":80,\"frames\":[\"⢀⠀\",\"⡀⠀\",\"⠄⠀\",\"⢂⠀\",\"⡂⠀\",\"⠅⠀\",\"⢃⠀\",\"⡃⠀\",\"⠍⠀\",\"⢋⠀\",\"⡋⠀\",\"⠍⠁\",\"⢋⠁\",\"⡋⠁\",\"⠍⠉\",\"⠋⠉\",\"⠋⠉\",\"⠉⠙\",\"⠉⠙\",\"⠉⠩\",\"⠈⢙\",\"⠈⡙\",\"⢈⠩\",\"⡀⢙\",\"⠄⡙\",\"⢂⠩\",\"⡂⢘\",\"⠅⡘\",\"⢃⠨\",\"⡃⢐\",\"⠍⡐\",\"⢋⠠\",\"⡋⢀\",\"⠍⡁\",\"⢋⠁\",\"⡋⠁\",\"⠍⠉\",\"⠋⠉\",\"⠋⠉\",\"⠉⠙\",\"⠉⠙\",\"⠉⠩\",\"⠈⢙\",\"⠈⡙\",\"⠈⠩\",\"⠀⢙\",\"⠀⡙\",\"⠀⠩\",\"⠀⢘\",\"⠀⡘\",\"⠀⠨\",\"⠀⢐\",\"⠀⡐\",\"⠀⠠\",\"⠀⢀\",\"⠀⡀\"]},\"line\":{\"interval\":130,\"frames\":[\"-\",\"\\\\\",\"|\",\"/\"]},\"line2\":{\"interval\":100,\"frames\":[\"⠂\",\"-\",\"–\",\"—\",\"–\",\"-\"]},\"pipe\":{\"interval\":100,\"frames\":[\"┤\",\"┘\",\"┴\",\"└\",\"├\",\"┌\",\"┬\",\"┐\"]},\"simpleDots\":{\"interval\":400,\"frames\":[\". \",\".. \",\"...\",\" \"]},\"simpleDotsScrolling\":{\"interval\":200,\"frames\":[\". \",\".. \",\"...\",\" ..\",\" .\",\" \"]},\"star\":{\"interval\":70,\"frames\":[\"✶\",\"✸\",\"✹\",\"✺\",\"✹\",\"✷\"]},\"star2\":{\"interval\":80,\"frames\":[\"+\",\"x\",\"*\"]},\"flip\":{\"interval\":70,\"frames\":[\"_\",\"_\",\"_\",\"-\",\"`\",\"`\",\"'\",\"´\",\"-\",\"_\",\"_\",\"_\"]},\"hamburger\":{\"interval\":100,\"frames\":[\"☱\",\"☲\",\"☴\"]},\"growVertical\":{\"interval\":120,\"frames\":[\"▁\",\"▃\",\"▄\",\"▅\",\"▆\",\"▇\",\"▆\",\"▅\",\"▄\",\"▃\"]},\"growHorizontal\":{\"interval\":120,\"frames\":[\"▏\",\"▎\",\"▍\",\"▌\",\"▋\",\"▊\",\"▉\",\"▊\",\"▋\",\"▌\",\"▍\",\"▎\"]},\"balloon\":{\"interval\":140,\"frames\":[\" \",\".\",\"o\",\"O\",\"@\",\"*\",\" \"]},\"balloon2\":{\"interval\":120,\"frames\":[\".\",\"o\",\"O\",\"°\",\"O\",\"o\",\".\"]},\"noise\":{\"interval\":100,\"frames\":[\"▓\",\"▒\",\"░\"]},\"bounce\":{\"interval\":120,\"frames\":[\"⠁\",\"⠂\",\"⠄\",\"⠂\"]},\"boxBounce\":{\"interval\":120,\"frames\":[\"▖\",\"▘\",\"▝\",\"▗\"]},\"boxBounce2\":{\"interval\":100,\"frames\":[\"▌\",\"▀\",\"▐\",\"▄\"]},\"triangle\":{\"interval\":50,\"frames\":[\"◢\",\"◣\",\"◤\",\"◥\"]},\"arc\":{\"interval\":100,\"frames\":[\"◜\",\"◠\",\"◝\",\"◞\",\"◡\",\"◟\"]},\"circle\":{\"interval\":120,\"frames\":[\"◡\",\"⊙\",\"◠\"]},\"squareCorners\":{\"interval\":180,\"frames\":[\"◰\",\"◳\",\"◲\",\"◱\"]},\"circleQuarters\":{\"interval\":120,\"frames\":[\"◴\",\"◷\",\"◶\",\"◵\"]},\"circleHalves\":{\"interval\":50,\"frames\":[\"◐\",\"◓\",\"◑\",\"◒\"]},\"squish\":{\"interval\":100,\"frames\":[\"╫\",\"╪\"]},\"toggle\":{\"interval\":250,\"frames\":[\"⊶\",\"⊷\"]},\"toggle2\":{\"interval\":80,\"frames\":[\"▫\",\"▪\"]},\"toggle3\":{\"interval\":120,\"frames\":[\"□\",\"■\"]},\"toggle4\":{\"interval\":100,\"frames\":[\"■\",\"□\",\"▪\",\"▫\"]},\"toggle5\":{\"interval\":100,\"frames\":[\"▮\",\"▯\"]},\"toggle6\":{\"interval\":300,\"frames\":[\"ဝ\",\"၀\"]},\"toggle7\":{\"interval\":80,\"frames\":[\"⦾\",\"⦿\"]},\"toggle8\":{\"interval\":100,\"frames\":[\"◍\",\"◌\"]},\"toggle9\":{\"interval\":100,\"frames\":[\"◉\",\"◎\"]},\"toggle10\":{\"interval\":100,\"frames\":[\"㊂\",\"㊀\",\"㊁\"]},\"toggle11\":{\"interval\":50,\"frames\":[\"⧇\",\"⧆\"]},\"toggle12\":{\"interval\":120,\"frames\":[\"☗\",\"☖\"]},\"toggle13\":{\"interval\":80,\"frames\":[\"=\",\"*\",\"-\"]},\"arrow\":{\"interval\":100,\"frames\":[\"←\",\"↖\",\"↑\",\"↗\",\"→\",\"↘\",\"↓\",\"↙\"]},\"arrow2\":{\"interval\":80,\"frames\":[\"⬆️ \",\"↗️ \",\"➡️ \",\"↘️ \",\"⬇️ \",\"↙️ \",\"⬅️ \",\"↖️ \"]},\"arrow3\":{\"interval\":120,\"frames\":[\"▹▹▹▹▹\",\"▸▹▹▹▹\",\"▹▸▹▹▹\",\"▹▹▸▹▹\",\"▹▹▹▸▹\",\"▹▹▹▹▸\"]},\"bouncingBar\":{\"interval\":80,\"frames\":[\"[ ]\",\"[= ]\",\"[== ]\",\"[=== ]\",\"[ ===]\",\"[ ==]\",\"[ =]\",\"[ ]\",\"[ =]\",\"[ ==]\",\"[ ===]\",\"[====]\",\"[=== ]\",\"[== ]\",\"[= ]\"]},\"bouncingBall\":{\"interval\":80,\"frames\":[\"( ● )\",\"( ● )\",\"( ● )\",\"( ● )\",\"( ●)\",\"( ● )\",\"( ● )\",\"( ● )\",\"( ● )\",\"(● )\"]},\"smiley\":{\"interval\":200,\"frames\":[\"😄 \",\"😝 \"]},\"monkey\":{\"interval\":300,\"frames\":[\"🙈 \",\"🙈 \",\"🙉 \",\"🙊 \"]},\"hearts\":{\"interval\":100,\"frames\":[\"💛 \",\"💙 \",\"💜 \",\"💚 \",\"❤️ \"]},\"clock\":{\"interval\":100,\"frames\":[\"🕐 \",\"🕑 \",\"🕒 \",\"🕓 \",\"🕔 \",\"🕕 \",\"🕖 \",\"🕗 \",\"🕘 \",\"🕙 \",\"🕚 \"]},\"earth\":{\"interval\":180,\"frames\":[\"🌍 \",\"🌎 \",\"🌏 \"]},\"moon\":{\"interval\":80,\"frames\":[\"🌑 \",\"🌒 \",\"🌓 \",\"🌔 \",\"🌕 \",\"🌖 \",\"🌗 \",\"🌘 \"]},\"runner\":{\"interval\":140,\"frames\":[\"🚶 \",\"🏃 \"]},\"pong\":{\"interval\":80,\"frames\":[\"▐⠂ ▌\",\"▐⠈ ▌\",\"▐ ⠂ ▌\",\"▐ ⠠ ▌\",\"▐ ⡀ ▌\",\"▐ ⠠ ▌\",\"▐ ⠂ ▌\",\"▐ ⠈ ▌\",\"▐ ⠂ ▌\",\"▐ ⠠ ▌\",\"▐ ⡀ ▌\",\"▐ ⠠ ▌\",\"▐ ⠂ ▌\",\"▐ ⠈ ▌\",\"▐ ⠂▌\",\"▐ ⠠▌\",\"▐ ⡀▌\",\"▐ ⠠ ▌\",\"▐ ⠂ ▌\",\"▐ ⠈ ▌\",\"▐ ⠂ ▌\",\"▐ ⠠ ▌\",\"▐ ⡀ ▌\",\"▐ ⠠ ▌\",\"▐ ⠂ ▌\",\"▐ ⠈ ▌\",\"▐ ⠂ ▌\",\"▐ ⠠ ▌\",\"▐ ⡀ ▌\",\"▐⠠ ▌\"]},\"shark\":{\"interval\":120,\"frames\":[\"▐|\\\\____________▌\",\"▐_|\\\\___________▌\",\"▐__|\\\\__________▌\",\"▐___|\\\\_________▌\",\"▐____|\\\\________▌\",\"▐_____|\\\\_______▌\",\"▐______|\\\\______▌\",\"▐_______|\\\\_____▌\",\"▐________|\\\\____▌\",\"▐_________|\\\\___▌\",\"▐__________|\\\\__▌\",\"▐___________|\\\\_▌\",\"▐____________|\\\\▌\",\"▐____________/|▌\",\"▐___________/|_▌\",\"▐__________/|__▌\",\"▐_________/|___▌\",\"▐________/|____▌\",\"▐_______/|_____▌\",\"▐______/|______▌\",\"▐_____/|_______▌\",\"▐____/|________▌\",\"▐___/|_________▌\",\"▐__/|__________▌\",\"▐_/|___________▌\",\"▐/|____________▌\"]},\"dqpb\":{\"interval\":100,\"frames\":[\"d\",\"q\",\"p\",\"b\"]},\"weather\":{\"interval\":100,\"frames\":[\"☀️ \",\"☀️ \",\"☀️ \",\"🌤 \",\"⛅️ \",\"🌥 \",\"☁️ \",\"🌧 \",\"🌨 \",\"🌧 \",\"🌨 \",\"🌧 \",\"🌨 \",\"⛈ \",\"🌨 \",\"🌧 \",\"🌨 \",\"☁️ \",\"🌥 \",\"⛅️ \",\"🌤 \",\"☀️ \",\"☀️ \"]},\"christmas\":{\"interval\":400,\"frames\":[\"🌲\",\"🎄\"]}}"); /***/ }), -/* 470 */ +/* 686 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "windowToggle", function() { return windowToggle; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(294); -/* harmony import */ var _Subscription__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(284); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(336); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(337); -/** PURE_IMPORTS_START tslib,_Subject,_Subscription,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "RunCommand", function() { return RunCommand; }); +/* harmony import */ var chalk__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2); +/* harmony import */ var chalk__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(chalk__WEBPACK_IMPORTED_MODULE_0__); +/* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(34); +/* harmony import */ var _utils_parallelize__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(500); +/* harmony import */ var _utils_projects__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(501); +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +const RunCommand = { + description: 'Run script defined in package.json in each package that contains that script.', + name: 'run', + async run(projects, projectGraph, { + extraArgs + }) { + const batchedProjects = Object(_utils_projects__WEBPACK_IMPORTED_MODULE_3__["topologicallyBatchProjects"])(projects, projectGraph); -function windowToggle(openings, closingSelector) { - return function (source) { return source.lift(new WindowToggleOperator(openings, closingSelector)); }; -} -var WindowToggleOperator = /*@__PURE__*/ (function () { - function WindowToggleOperator(openings, closingSelector) { - this.openings = openings; - this.closingSelector = closingSelector; - } - WindowToggleOperator.prototype.call = function (subscriber, source) { - return source.subscribe(new WindowToggleSubscriber(subscriber, this.openings, this.closingSelector)); - }; - return WindowToggleOperator; -}()); -var WindowToggleSubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](WindowToggleSubscriber, _super); - function WindowToggleSubscriber(destination, openings, closingSelector) { - var _this = _super.call(this, destination) || this; - _this.openings = openings; - _this.closingSelector = closingSelector; - _this.contexts = []; - _this.add(_this.openSubscription = Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_4__["subscribeToResult"])(_this, openings, openings)); - return _this; + if (extraArgs.length === 0) { + _utils_log__WEBPACK_IMPORTED_MODULE_1__["log"].write(chalk__WEBPACK_IMPORTED_MODULE_0___default.a.red.bold('\nNo script specified')); + process.exit(1); } - WindowToggleSubscriber.prototype._next = function (value) { - var contexts = this.contexts; - if (contexts) { - var len = contexts.length; - for (var i = 0; i < len; i++) { - contexts[i].window.next(value); - } - } - }; - WindowToggleSubscriber.prototype._error = function (err) { - var contexts = this.contexts; - this.contexts = null; - if (contexts) { - var len = contexts.length; - var index = -1; - while (++index < len) { - var context_1 = contexts[index]; - context_1.window.error(err); - context_1.subscription.unsubscribe(); - } - } - _super.prototype._error.call(this, err); - }; - WindowToggleSubscriber.prototype._complete = function () { - var contexts = this.contexts; - this.contexts = null; - if (contexts) { - var len = contexts.length; - var index = -1; - while (++index < len) { - var context_2 = contexts[index]; - context_2.window.complete(); - context_2.subscription.unsubscribe(); - } - } - _super.prototype._complete.call(this); - }; - WindowToggleSubscriber.prototype._unsubscribe = function () { - var contexts = this.contexts; - this.contexts = null; - if (contexts) { - var len = contexts.length; - var index = -1; - while (++index < len) { - var context_3 = contexts[index]; - context_3.window.unsubscribe(); - context_3.subscription.unsubscribe(); - } - } - }; - WindowToggleSubscriber.prototype.notifyNext = function (outerValue, innerValue, outerIndex, innerIndex, innerSub) { - if (outerValue === this.openings) { - var closingNotifier = void 0; - try { - var closingSelector = this.closingSelector; - closingNotifier = closingSelector(innerValue); - } - catch (e) { - return this.error(e); - } - var window_1 = new _Subject__WEBPACK_IMPORTED_MODULE_1__["Subject"](); - var subscription = new _Subscription__WEBPACK_IMPORTED_MODULE_2__["Subscription"](); - var context_4 = { window: window_1, subscription: subscription }; - this.contexts.push(context_4); - var innerSubscription = Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_4__["subscribeToResult"])(this, closingNotifier, context_4); - if (innerSubscription.closed) { - this.closeWindow(this.contexts.length - 1); - } - else { - innerSubscription.context = context_4; - subscription.add(innerSubscription); - } - this.destination.next(window_1); - } - else { - this.closeWindow(this.contexts.indexOf(outerValue)); - } - }; - WindowToggleSubscriber.prototype.notifyError = function (err) { - this.error(err); - }; - WindowToggleSubscriber.prototype.notifyComplete = function (inner) { - if (inner !== this.openSubscription) { - this.closeWindow(this.contexts.indexOf(inner.context)); - } - }; - WindowToggleSubscriber.prototype.closeWindow = function (index) { - if (index === -1) { - return; - } - var contexts = this.contexts; - var context = contexts[index]; - var window = context.window, subscription = context.subscription; - contexts.splice(index, 1); - window.complete(); - subscription.unsubscribe(); - }; - return WindowToggleSubscriber; -}(_OuterSubscriber__WEBPACK_IMPORTED_MODULE_3__["OuterSubscriber"])); -//# sourceMappingURL=windowToggle.js.map + const scriptName = extraArgs[0]; + const scriptArgs = extraArgs.slice(1); + _utils_log__WEBPACK_IMPORTED_MODULE_1__["log"].write(chalk__WEBPACK_IMPORTED_MODULE_0___default.a.bold(`\nRunning script [${chalk__WEBPACK_IMPORTED_MODULE_0___default.a.green(scriptName)}] in batched topological order\n`)); + await Object(_utils_parallelize__WEBPACK_IMPORTED_MODULE_2__["parallelizeBatches"])(batchedProjects, async pkg => { + if (pkg.hasScript(scriptName)) { + await pkg.runScriptStreaming(scriptName, scriptArgs); + } + }); + } + +}; /***/ }), -/* 471 */ +/* 687 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "windowWhen", function() { return windowWhen; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _Subject__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(294); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(336); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(337); -/** PURE_IMPORTS_START tslib,_Subject,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "WatchCommand", function() { return WatchCommand; }); +/* harmony import */ var chalk__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2); +/* harmony import */ var chalk__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(chalk__WEBPACK_IMPORTED_MODULE_0__); +/* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(34); +/* harmony import */ var _utils_parallelize__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(500); +/* harmony import */ var _utils_projects__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(501); +/* harmony import */ var _utils_watch__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(688); +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ -function windowWhen(closingSelector) { - return function windowWhenOperatorFunction(source) { - return source.lift(new WindowOperator(closingSelector)); - }; -} -var WindowOperator = /*@__PURE__*/ (function () { - function WindowOperator(closingSelector) { - this.closingSelector = closingSelector; - } - WindowOperator.prototype.call = function (subscriber, source) { - return source.subscribe(new WindowSubscriber(subscriber, this.closingSelector)); - }; - return WindowOperator; -}()); -var WindowSubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](WindowSubscriber, _super); - function WindowSubscriber(destination, closingSelector) { - var _this = _super.call(this, destination) || this; - _this.destination = destination; - _this.closingSelector = closingSelector; - _this.openWindow(); - return _this; - } - WindowSubscriber.prototype.notifyNext = function (outerValue, innerValue, outerIndex, innerIndex, innerSub) { - this.openWindow(innerSub); - }; - WindowSubscriber.prototype.notifyError = function (error, innerSub) { - this._error(error); - }; - WindowSubscriber.prototype.notifyComplete = function (innerSub) { - this.openWindow(innerSub); - }; - WindowSubscriber.prototype._next = function (value) { - this.window.next(value); - }; - WindowSubscriber.prototype._error = function (err) { - this.window.error(err); - this.destination.error(err); - this.unsubscribeClosingNotification(); - }; - WindowSubscriber.prototype._complete = function () { - this.window.complete(); - this.destination.complete(); - this.unsubscribeClosingNotification(); - }; - WindowSubscriber.prototype.unsubscribeClosingNotification = function () { - if (this.closingNotification) { - this.closingNotification.unsubscribe(); - } - }; - WindowSubscriber.prototype.openWindow = function (innerSub) { - if (innerSub === void 0) { - innerSub = null; - } - if (innerSub) { - this.remove(innerSub); - innerSub.unsubscribe(); - } - var prevWindow = this.window; - if (prevWindow) { - prevWindow.complete(); - } - var window = this.window = new _Subject__WEBPACK_IMPORTED_MODULE_1__["Subject"](); - this.destination.next(window); - var closingNotifier; - try { - var closingSelector = this.closingSelector; - closingNotifier = closingSelector(); - } - catch (e) { - this.destination.error(e); - this.window.error(e); - return; - } - this.add(this.closingNotification = Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_3__["subscribeToResult"])(this, closingNotifier)); - }; - return WindowSubscriber; -}(_OuterSubscriber__WEBPACK_IMPORTED_MODULE_2__["OuterSubscriber"])); -//# sourceMappingURL=windowWhen.js.map -/***/ }), -/* 472 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +/** + * Name of the script in the package/project package.json file to run during `kbn watch`. + */ +const watchScriptName = 'kbn:watch'; +/** + * Name of the Kibana project. + */ -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "withLatestFrom", function() { return withLatestFrom; }); -/* harmony import */ var tslib__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(279); -/* harmony import */ var _OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(336); -/* harmony import */ var _util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(337); -/** PURE_IMPORTS_START tslib,_OuterSubscriber,_util_subscribeToResult PURE_IMPORTS_END */ +const kibanaProjectName = 'kibana'; +/** + * Command that traverses through list of available projects/packages that have `kbn:watch` script in their + * package.json files, groups them into topology aware batches and then processes theses batches one by one + * running `kbn:watch` scripts in parallel within the same batch. + * + * Command internally relies on the fact that most of the build systems that are triggered by `kbn:watch` + * will emit special "marker" once build/watch process is ready that we can use as completion condition for + * the `kbn:watch` script and eventually for the entire batch. Currently we support completion "markers" for + * `webpack` and `tsc` only, for the rest we rely on predefined timeouts. + */ +const WatchCommand = { + description: 'Runs `kbn:watch` script for every project.', + name: 'watch', + async run(projects, projectGraph) { + const projectsToWatch = new Map(); -function withLatestFrom() { - var args = []; - for (var _i = 0; _i < arguments.length; _i++) { - args[_i] = arguments[_i]; + for (const project of projects.values()) { + // We can't watch project that doesn't have `kbn:watch` script. + if (project.hasScript(watchScriptName)) { + projectsToWatch.set(project.name, project); + } } - return function (source) { - var project; - if (typeof args[args.length - 1] === 'function') { - project = args.pop(); - } - var observables = args; - return source.lift(new WithLatestFromOperator(observables, project)); - }; -} -var WithLatestFromOperator = /*@__PURE__*/ (function () { - function WithLatestFromOperator(observables, project) { - this.observables = observables; - this.project = project; + + if (projectsToWatch.size === 0) { + _utils_log__WEBPACK_IMPORTED_MODULE_1__["log"].write(chalk__WEBPACK_IMPORTED_MODULE_0___default.a.red(`\nThere are no projects to watch found. Make sure that projects define 'kbn:watch' script in 'package.json'.\n`)); + return; } - WithLatestFromOperator.prototype.call = function (subscriber, source) { - return source.subscribe(new WithLatestFromSubscriber(subscriber, this.observables, this.project)); - }; - return WithLatestFromOperator; -}()); -var WithLatestFromSubscriber = /*@__PURE__*/ (function (_super) { - tslib__WEBPACK_IMPORTED_MODULE_0__["__extends"](WithLatestFromSubscriber, _super); - function WithLatestFromSubscriber(destination, observables, project) { - var _this = _super.call(this, destination) || this; - _this.observables = observables; - _this.project = project; - _this.toRespond = []; - var len = observables.length; - _this.values = new Array(len); - for (var i = 0; i < len; i++) { - _this.toRespond.push(i); - } - for (var i = 0; i < len; i++) { - var observable = observables[i]; - _this.add(Object(_util_subscribeToResult__WEBPACK_IMPORTED_MODULE_2__["subscribeToResult"])(_this, observable, observable, i)); - } - return _this; + + const projectNames = Array.from(projectsToWatch.keys()); + _utils_log__WEBPACK_IMPORTED_MODULE_1__["log"].write(chalk__WEBPACK_IMPORTED_MODULE_0___default.a.bold(chalk__WEBPACK_IMPORTED_MODULE_0___default.a.green(`Running ${watchScriptName} scripts for [${projectNames.join(', ')}].`))); // Kibana should always be run the last, so we don't rely on automatic + // topological batching and push it to the last one-entry batch manually. + + const shouldWatchKibanaProject = projectsToWatch.delete(kibanaProjectName); + const batchedProjects = Object(_utils_projects__WEBPACK_IMPORTED_MODULE_3__["topologicallyBatchProjects"])(projectsToWatch, projectGraph); + + if (shouldWatchKibanaProject) { + batchedProjects.push([projects.get(kibanaProjectName)]); } - WithLatestFromSubscriber.prototype.notifyNext = function (outerValue, innerValue, outerIndex, innerIndex, innerSub) { - this.values[outerIndex] = innerValue; - var toRespond = this.toRespond; - if (toRespond.length > 0) { - var found = toRespond.indexOf(outerIndex); - if (found !== -1) { - toRespond.splice(found, 1); - } - } - }; - WithLatestFromSubscriber.prototype.notifyComplete = function () { - }; - WithLatestFromSubscriber.prototype._next = function (value) { - if (this.toRespond.length === 0) { - var args = [value].concat(this.values); - if (this.project) { - this._tryProject(args); - } - else { - this.destination.next(args); - } - } - }; - WithLatestFromSubscriber.prototype._tryProject = function (args) { - var result; - try { - result = this.project.apply(this, args); - } - catch (err) { - this.destination.error(err); - return; - } - this.destination.next(result); - }; - return WithLatestFromSubscriber; -}(_OuterSubscriber__WEBPACK_IMPORTED_MODULE_1__["OuterSubscriber"])); -//# sourceMappingURL=withLatestFrom.js.map + await Object(_utils_parallelize__WEBPACK_IMPORTED_MODULE_2__["parallelizeBatches"])(batchedProjects, async pkg => { + const completionHint = await Object(_utils_watch__WEBPACK_IMPORTED_MODULE_4__["waitUntilWatchIsReady"])(pkg.runScriptStreaming(watchScriptName).stdout); + _utils_log__WEBPACK_IMPORTED_MODULE_1__["log"].write(chalk__WEBPACK_IMPORTED_MODULE_0___default.a.bold(`[${chalk__WEBPACK_IMPORTED_MODULE_0___default.a.green(pkg.name)}] Initial build completed (${completionHint}).`)); + }); + } + +}; /***/ }), -/* 473 */ +/* 688 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "zip", function() { return zip; }); -/* harmony import */ var _observable_zip__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(376); -/** PURE_IMPORTS_START _observable_zip PURE_IMPORTS_END */ +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "waitUntilWatchIsReady", function() { return waitUntilWatchIsReady; }); +/* harmony import */ var rxjs__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(392); +/* harmony import */ var rxjs_operators__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(169); +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ -function zip() { - var observables = []; - for (var _i = 0; _i < arguments.length; _i++) { - observables[_i] = arguments[_i]; - } - return function zipOperatorFunction(source) { - return source.lift.call(_observable_zip__WEBPACK_IMPORTED_MODULE_0__["zip"].apply(void 0, [source].concat(observables))); - }; -} -//# sourceMappingURL=zip.js.map +/** + * Number of milliseconds we wait before we fall back to the default watch handler. + */ -/***/ }), -/* 474 */ -/***/ (function(module, __webpack_exports__, __webpack_require__) { +const defaultHandlerDelay = 3000; +/** + * If default watch handler is used, then it's the number of milliseconds we wait for + * any build output before we consider watch task ready. + */ -"use strict"; -__webpack_require__.r(__webpack_exports__); -/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "zipAll", function() { return zipAll; }); -/* harmony import */ var _observable_zip__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(376); -/** PURE_IMPORTS_START _observable_zip PURE_IMPORTS_END */ +const defaultHandlerReadinessTimeout = 2000; +/** + * Describes configurable watch options. + */ -function zipAll(project) { - return function (source) { return source.lift(new _observable_zip__WEBPACK_IMPORTED_MODULE_0__["ZipOperator"](project)); }; +function getWatchHandlers(buildOutput$, { + handlerDelay = defaultHandlerDelay, + handlerReadinessTimeout = defaultHandlerReadinessTimeout +}) { + const typescriptHandler = buildOutput$.pipe(Object(rxjs_operators__WEBPACK_IMPORTED_MODULE_1__["first"])(data => data.includes('$ tsc')), Object(rxjs_operators__WEBPACK_IMPORTED_MODULE_1__["map"])(() => buildOutput$.pipe(Object(rxjs_operators__WEBPACK_IMPORTED_MODULE_1__["first"])(data => data.includes('Compilation complete.')), Object(rxjs_operators__WEBPACK_IMPORTED_MODULE_1__["mapTo"])('tsc')))); + const webpackHandler = buildOutput$.pipe(Object(rxjs_operators__WEBPACK_IMPORTED_MODULE_1__["first"])(data => data.includes('$ webpack')), Object(rxjs_operators__WEBPACK_IMPORTED_MODULE_1__["map"])(() => buildOutput$.pipe(Object(rxjs_operators__WEBPACK_IMPORTED_MODULE_1__["first"])(data => data.includes('Chunk Names')), Object(rxjs_operators__WEBPACK_IMPORTED_MODULE_1__["mapTo"])('webpack')))); + const defaultHandler = rxjs__WEBPACK_IMPORTED_MODULE_0__["of"](undefined).pipe(Object(rxjs_operators__WEBPACK_IMPORTED_MODULE_1__["delay"])(handlerReadinessTimeout), Object(rxjs_operators__WEBPACK_IMPORTED_MODULE_1__["map"])(() => buildOutput$.pipe(Object(rxjs_operators__WEBPACK_IMPORTED_MODULE_1__["timeout"])(handlerDelay), Object(rxjs_operators__WEBPACK_IMPORTED_MODULE_1__["catchError"])(() => rxjs__WEBPACK_IMPORTED_MODULE_0__["of"]('timeout'))))); + return [typescriptHandler, webpackHandler, defaultHandler]; } -//# sourceMappingURL=zipAll.js.map +function waitUntilWatchIsReady(stream, opts = {}) { + const buildOutput$ = new rxjs__WEBPACK_IMPORTED_MODULE_0__["Subject"](); + + const onDataListener = data => buildOutput$.next(data.toString('utf-8')); + + const onEndListener = () => buildOutput$.complete(); + + const onErrorListener = e => buildOutput$.error(e); + + stream.once('end', onEndListener); + stream.once('error', onErrorListener); + stream.on('data', onDataListener); + return rxjs__WEBPACK_IMPORTED_MODULE_0__["race"](getWatchHandlers(buildOutput$, opts)).pipe(Object(rxjs_operators__WEBPACK_IMPORTED_MODULE_1__["mergeMap"])(whenReady => whenReady), Object(rxjs_operators__WEBPACK_IMPORTED_MODULE_1__["finalize"])(() => { + stream.removeListener('data', onDataListener); + stream.removeListener('end', onEndListener); + stream.removeListener('error', onErrorListener); + buildOutput$.complete(); + })).toPromise(); +} /***/ }), -/* 475 */ +/* 689 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -44954,15 +79034,21 @@ __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "runCommand", function() { return runCommand; }); /* harmony import */ var chalk__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(2); /* harmony import */ var chalk__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(chalk__WEBPACK_IMPORTED_MODULE_0__); -/* harmony import */ var indent_string__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(259); +/* harmony import */ var indent_string__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(673); /* harmony import */ var indent_string__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(indent_string__WEBPACK_IMPORTED_MODULE_1__); -/* harmony import */ var wrap_ansi__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(476); +/* harmony import */ var wrap_ansi__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(690); /* harmony import */ var wrap_ansi__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(wrap_ansi__WEBPACK_IMPORTED_MODULE_2__); -/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(172); -/* harmony import */ var _utils_errors__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(53); -/* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(34); -/* harmony import */ var _utils_projects__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(36); -/* harmony import */ var _utils_projects_tree__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(483); +/* harmony import */ var _utils_errors__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(515); +/* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(34); +/* harmony import */ var _utils_projects__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(501); +/* harmony import */ var _utils_projects_tree__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(697); +/* harmony import */ var _utils_kibana__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(698); +function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } + +function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(source, true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(source).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } + +function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } + /* * Licensed to Elasticsearch B.V. under one or more contributor * license agreements. See the NOTICE file distributed with @@ -44991,28 +79077,32 @@ __webpack_require__.r(__webpack_exports__); async function runCommand(command, config) { try { - _utils_log__WEBPACK_IMPORTED_MODULE_5__["log"].write(chalk__WEBPACK_IMPORTED_MODULE_0___default.a.bold(`Running [${chalk__WEBPACK_IMPORTED_MODULE_0___default.a.green(command.name)}] command from [${chalk__WEBPACK_IMPORTED_MODULE_0___default.a.yellow(config.rootPath)}]:\n`)); - const projectPaths = Object(_config__WEBPACK_IMPORTED_MODULE_3__["getProjectPaths"])(config.rootPath, config.options); - const projects = await Object(_utils_projects__WEBPACK_IMPORTED_MODULE_6__["getProjects"])(config.rootPath, projectPaths, { + _utils_log__WEBPACK_IMPORTED_MODULE_4__["log"].write(chalk__WEBPACK_IMPORTED_MODULE_0___default.a.bold(`Running [${chalk__WEBPACK_IMPORTED_MODULE_0___default.a.green(command.name)}] command from [${chalk__WEBPACK_IMPORTED_MODULE_0___default.a.yellow(config.rootPath)}]:\n`)); + const kbn = await _utils_kibana__WEBPACK_IMPORTED_MODULE_7__["Kibana"].loadFrom(config.rootPath); + const projects = kbn.getFilteredProjects({ + skipKibanaPlugins: Boolean(config.options['skip-kibana-plugins']), + ossOnly: Boolean(config.options.oss), exclude: toArray(config.options.exclude), include: toArray(config.options.include) }); if (projects.size === 0) { - _utils_log__WEBPACK_IMPORTED_MODULE_5__["log"].write(chalk__WEBPACK_IMPORTED_MODULE_0___default.a.red(`There are no projects found. Double check project name(s) in '-i/--include' and '-e/--exclude' filters.\n`)); + _utils_log__WEBPACK_IMPORTED_MODULE_4__["log"].write(chalk__WEBPACK_IMPORTED_MODULE_0___default.a.red(`There are no projects found. Double check project name(s) in '-i/--include' and '-e/--exclude' filters.\n`)); return process.exit(1); } - const projectGraph = Object(_utils_projects__WEBPACK_IMPORTED_MODULE_6__["buildProjectGraph"])(projects); - _utils_log__WEBPACK_IMPORTED_MODULE_5__["log"].write(chalk__WEBPACK_IMPORTED_MODULE_0___default.a.bold(`Found [${chalk__WEBPACK_IMPORTED_MODULE_0___default.a.green(projects.size.toString())}] projects:\n`)); - _utils_log__WEBPACK_IMPORTED_MODULE_5__["log"].write(Object(_utils_projects_tree__WEBPACK_IMPORTED_MODULE_7__["renderProjectsTree"])(config.rootPath, projects)); - await command.run(projects, projectGraph, config); + const projectGraph = Object(_utils_projects__WEBPACK_IMPORTED_MODULE_5__["buildProjectGraph"])(projects); + _utils_log__WEBPACK_IMPORTED_MODULE_4__["log"].write(chalk__WEBPACK_IMPORTED_MODULE_0___default.a.bold(`Found [${chalk__WEBPACK_IMPORTED_MODULE_0___default.a.green(projects.size.toString())}] projects:\n`)); + _utils_log__WEBPACK_IMPORTED_MODULE_4__["log"].write(Object(_utils_projects_tree__WEBPACK_IMPORTED_MODULE_6__["renderProjectsTree"])(config.rootPath, projects)); + await command.run(projects, projectGraph, _objectSpread({}, config, { + kbn + })); } catch (e) { - _utils_log__WEBPACK_IMPORTED_MODULE_5__["log"].write(chalk__WEBPACK_IMPORTED_MODULE_0___default.a.bold.red(`\n[${command.name}] failed:\n`)); + _utils_log__WEBPACK_IMPORTED_MODULE_4__["log"].write(chalk__WEBPACK_IMPORTED_MODULE_0___default.a.bold.red(`\n[${command.name}] failed:\n`)); - if (e instanceof _utils_errors__WEBPACK_IMPORTED_MODULE_4__["CliError"]) { + if (e instanceof _utils_errors__WEBPACK_IMPORTED_MODULE_3__["CliError"]) { const msg = chalk__WEBPACK_IMPORTED_MODULE_0___default.a.red(`CliError: ${e.message}\n`); - _utils_log__WEBPACK_IMPORTED_MODULE_5__["log"].write(wrap_ansi__WEBPACK_IMPORTED_MODULE_2___default()(msg, 80)); + _utils_log__WEBPACK_IMPORTED_MODULE_4__["log"].write(wrap_ansi__WEBPACK_IMPORTED_MODULE_2___default()(msg, 80)); const keys = Object.keys(e.meta); if (keys.length > 0) { @@ -45020,11 +79110,11 @@ async function runCommand(command, config) { const value = e.meta[key]; return `${key}: ${value}`; }); - _utils_log__WEBPACK_IMPORTED_MODULE_5__["log"].write('Additional debugging info:\n'); - _utils_log__WEBPACK_IMPORTED_MODULE_5__["log"].write(indent_string__WEBPACK_IMPORTED_MODULE_1___default()(metaOutput.join('\n'), 3)); + _utils_log__WEBPACK_IMPORTED_MODULE_4__["log"].write('Additional debugging info:\n'); + _utils_log__WEBPACK_IMPORTED_MODULE_4__["log"].write(indent_string__WEBPACK_IMPORTED_MODULE_1___default()(metaOutput.join('\n'), 3)); } } else { - _utils_log__WEBPACK_IMPORTED_MODULE_5__["log"].write(e.stack); + _utils_log__WEBPACK_IMPORTED_MODULE_4__["log"].write(e.stack); } process.exit(1); @@ -45040,13 +79130,13 @@ function toArray(value) { } /***/ }), -/* 476 */ +/* 690 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const stringWidth = __webpack_require__(477); -const stripAnsi = __webpack_require__(481); +const stringWidth = __webpack_require__(691); +const stripAnsi = __webpack_require__(695); const ESCAPES = new Set([ '\u001B', @@ -45240,13 +79330,13 @@ module.exports = (str, cols, opts) => { /***/ }), -/* 477 */ +/* 691 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const stripAnsi = __webpack_require__(478); -const isFullwidthCodePoint = __webpack_require__(480); +const stripAnsi = __webpack_require__(692); +const isFullwidthCodePoint = __webpack_require__(694); module.exports = str => { if (typeof str !== 'string' || str.length === 0) { @@ -45283,18 +79373,18 @@ module.exports = str => { /***/ }), -/* 478 */ +/* 692 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const ansiRegex = __webpack_require__(479); +const ansiRegex = __webpack_require__(693); module.exports = input => typeof input === 'string' ? input.replace(ansiRegex(), '') : input; /***/ }), -/* 479 */ +/* 693 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -45311,7 +79401,7 @@ module.exports = () => { /***/ }), -/* 480 */ +/* 694 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -45364,18 +79454,18 @@ module.exports = x => { /***/ }), -/* 481 */ +/* 695 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const ansiRegex = __webpack_require__(482); +const ansiRegex = __webpack_require__(696); module.exports = input => typeof input === 'string' ? input.replace(ansiRegex(), '') : input; /***/ }), -/* 482 */ +/* 696 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -45392,7 +79482,7 @@ module.exports = () => { /***/ }), -/* 483 */ +/* 697 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; @@ -45545,15 +79635,247 @@ function addProjectToTree(tree, pathParts, project) { } /***/ }), -/* 484 */ +/* 698 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); -/* harmony import */ var _build_production_projects__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(485); +/* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "Kibana", function() { return Kibana; }); +/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(16); +/* harmony import */ var path__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_0__); +/* harmony import */ var multimatch__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(699); +/* harmony import */ var multimatch__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(multimatch__WEBPACK_IMPORTED_MODULE_1__); +/* harmony import */ var _projects__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(501); +/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(580); +function ownKeys(object, enumerableOnly) { var keys = Object.keys(object); if (Object.getOwnPropertySymbols) { var symbols = Object.getOwnPropertySymbols(object); if (enumerableOnly) symbols = symbols.filter(function (sym) { return Object.getOwnPropertyDescriptor(object, sym).enumerable; }); keys.push.apply(keys, symbols); } return keys; } + +function _objectSpread(target) { for (var i = 1; i < arguments.length; i++) { var source = arguments[i] != null ? arguments[i] : {}; if (i % 2) { ownKeys(source, true).forEach(function (key) { _defineProperty(target, key, source[key]); }); } else if (Object.getOwnPropertyDescriptors) { Object.defineProperties(target, Object.getOwnPropertyDescriptors(source)); } else { ownKeys(source).forEach(function (key) { Object.defineProperty(target, key, Object.getOwnPropertyDescriptor(source, key)); }); } } return target; } + +function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } + +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + + + + +/** + * Helper class for dealing with a set of projects as children of + * the Kibana project. The kbn/pm is currently implemented to be + * more generic, where everything is an operation of generic projects, + * but that leads to exceptions where we need the kibana project and + * do things like `project.get('kibana')!`. + * + * Using this helper we can restructre the generic list of projects + * as a Kibana object which encapulates all the projects in the + * workspace and knows about the root Kibana project. + */ + +class Kibana { + static async loadFrom(rootPath) { + return new Kibana((await Object(_projects__WEBPACK_IMPORTED_MODULE_2__["getProjects"])(rootPath, Object(_config__WEBPACK_IMPORTED_MODULE_3__["getProjectPaths"])({ + rootPath + })))); + } + + constructor(allWorkspaceProjects) { + this.allWorkspaceProjects = allWorkspaceProjects; + + _defineProperty(this, "kibanaProject", void 0); + + const kibanaProject = allWorkspaceProjects.get('kibana'); + + if (!kibanaProject) { + throw new TypeError('Unable to create Kibana object without all projects, including the Kibana project.'); + } + + this.kibanaProject = kibanaProject; + } + /** make an absolute path by resolving subPath relative to the kibana repo */ + + + getAbsolute(...subPath) { + return path__WEBPACK_IMPORTED_MODULE_0___default.a.resolve(this.kibanaProject.path, ...subPath); + } + /** convert an absolute path to a relative path, relative to the kibana repo */ + + + getRelative(absolute) { + return path__WEBPACK_IMPORTED_MODULE_0___default.a.relative(this.kibanaProject.path, absolute); + } + /** get a copy of the map of all projects in the kibana workspace */ + + + getAllProjects() { + return new Map(this.allWorkspaceProjects); + } + /** determine if a project with the given name exists */ + + + hasProject(name) { + return this.allWorkspaceProjects.has(name); + } + /** get a specific project, throws if the name is not known (use hasProject() first) */ + + + getProject(name) { + const project = this.allWorkspaceProjects.get(name); + + if (!project) { + throw new Error(`No package with name "${name}" in the workspace`); + } + + return project; + } + /** get a project and all of the projects it depends on in a ProjectMap */ + + + getProjectAndDeps(name) { + const project = this.getProject(name); + return Object(_projects__WEBPACK_IMPORTED_MODULE_2__["includeTransitiveProjects"])([project], this.allWorkspaceProjects); + } + /** filter the projects to just those matching certain paths/include/exclude tags */ + + + getFilteredProjects(options) { + const allProjects = this.getAllProjects(); + const filteredProjects = new Map(); + const pkgJsonPaths = Array.from(allProjects.values()).map(p => p.packageJsonLocation); + const filteredPkgJsonGlobs = Object(_config__WEBPACK_IMPORTED_MODULE_3__["getProjectPaths"])(_objectSpread({}, options, { + rootPath: this.kibanaProject.path + })).map(g => path__WEBPACK_IMPORTED_MODULE_0___default.a.resolve(g, 'package.json')); + const matchingPkgJsonPaths = multimatch__WEBPACK_IMPORTED_MODULE_1___default()(pkgJsonPaths, filteredPkgJsonGlobs); + + for (const project of allProjects.values()) { + const pathMatches = matchingPkgJsonPaths.includes(project.packageJsonLocation); + const notExcluded = !options.exclude.includes(project.name); + const isIncluded = !options.include.length || options.include.includes(project.name); + + if (pathMatches && notExcluded && isIncluded) { + filteredProjects.set(project.name, project); + } + } + + return filteredProjects; + } + +} + +/***/ }), +/* 699 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + +const minimatch = __webpack_require__(505); +const arrayUnion = __webpack_require__(700); +const arrayDiffer = __webpack_require__(701); +const arrify = __webpack_require__(702); + +module.exports = (list, patterns, options = {}) => { + list = arrify(list); + patterns = arrify(patterns); + + if (list.length === 0 || patterns.length === 0) { + return []; + } + + return patterns.reduce((result, pattern) => { + let process = arrayUnion; + + if (pattern[0] === '!') { + pattern = pattern.slice(1); + process = arrayDiffer; + } + + return process(result, minimatch.match(list, pattern, options)); + }, []); +}; + + +/***/ }), +/* 700 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +module.exports = (...arguments_) => { + return [...new Set([].concat(...arguments_))]; +}; + + +/***/ }), +/* 701 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +const arrayDiffer = (array, ...values) => { + const rest = new Set([].concat(...values)); + return array.filter(element => !rest.has(element)); +}; + +module.exports = arrayDiffer; + + +/***/ }), +/* 702 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +const arrify = value => { + if (value === null || value === undefined) { + return []; + } + + if (Array.isArray(value)) { + return value; + } + + if (typeof value === 'string') { + return [value]; + } + + if (typeof value[Symbol.iterator] === 'function') { + return [...value]; + } + + return [value]; +}; + +module.exports = arrify; + + +/***/ }), +/* 703 */ +/***/ (function(module, __webpack_exports__, __webpack_require__) { + +"use strict"; +__webpack_require__.r(__webpack_exports__); +/* harmony import */ var _build_production_projects__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(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__(692); +/* 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"]; }); /* @@ -45578,23 +79900,23 @@ __webpack_require__.r(__webpack_exports__); /***/ }), -/* 485 */ +/* 704 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "buildProductionProjects", function() { return buildProductionProjects; }); -/* harmony import */ var cpy__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(486); +/* harmony import */ var cpy__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(705); /* harmony import */ var cpy__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(cpy__WEBPACK_IMPORTED_MODULE_0__); -/* harmony import */ var del__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(174); +/* harmony import */ var del__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(588); /* harmony import */ var del__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(del__WEBPACK_IMPORTED_MODULE_1__); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(16); /* harmony import */ var path__WEBPACK_IMPORTED_MODULE_2___default = /*#__PURE__*/__webpack_require__.n(path__WEBPACK_IMPORTED_MODULE_2__); -/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(172); +/* harmony import */ var _config__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(580); /* harmony import */ var _utils_fs__WEBPACK_IMPORTED_MODULE_4__ = __webpack_require__(20); /* harmony import */ var _utils_log__WEBPACK_IMPORTED_MODULE_5__ = __webpack_require__(34); -/* harmony import */ var _utils_package_json__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(55); -/* harmony import */ var _utils_projects__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(36); +/* harmony import */ var _utils_package_json__WEBPACK_IMPORTED_MODULE_6__ = __webpack_require__(517); +/* harmony import */ var _utils_projects__WEBPACK_IMPORTED_MODULE_7__ = __webpack_require__(501); /* * Licensed to Elasticsearch B.V. under one or more contributor * license agreements. See the NOTICE file distributed with @@ -45649,7 +79971,9 @@ async function buildProductionProjects({ */ async function getProductionProjects(rootPath, onlyOSS) { - const projectPaths = Object(_config__WEBPACK_IMPORTED_MODULE_3__["getProjectPaths"])(rootPath, {}); + const projectPaths = Object(_config__WEBPACK_IMPORTED_MODULE_3__["getProjectPaths"])({ + rootPath + }); const projects = await Object(_utils_projects__WEBPACK_IMPORTED_MODULE_7__["getProjects"])(rootPath, projectPaths); const projectsSubset = [projects.get('kibana')]; @@ -45724,17 +80048,17 @@ async function copyToBuild(project, kibanaRoot, buildRoot) { } /***/ }), -/* 486 */ +/* 705 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const EventEmitter = __webpack_require__(46); +const EventEmitter = __webpack_require__(379); const path = __webpack_require__(16); -const arrify = __webpack_require__(487); -const globby = __webpack_require__(488); -const cpFile = __webpack_require__(682); -const CpyError = __webpack_require__(690); +const arrify = __webpack_require__(706); +const globby = __webpack_require__(707); +const cpFile = __webpack_require__(905); +const CpyError = __webpack_require__(912); const preprocessSrcPath = (srcPath, options) => options.cwd ? path.resolve(options.cwd, srcPath) : srcPath; @@ -45839,7 +80163,7 @@ module.exports.default = cpy; /***/ }), -/* 487 */ +/* 706 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -45854,17 +80178,17 @@ module.exports = function (val) { /***/ }), -/* 488 */ +/* 707 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(23); -const arrayUnion = __webpack_require__(489); -const glob = __webpack_require__(37); -const fastGlob = __webpack_require__(491); -const dirGlob = __webpack_require__(675); -const gitignore = __webpack_require__(678); +const arrayUnion = __webpack_require__(708); +const glob = __webpack_require__(502); +const fastGlob = __webpack_require__(710); +const dirGlob = __webpack_require__(898); +const gitignore = __webpack_require__(901); const DEFAULT_FILTER = () => false; @@ -46009,12 +80333,12 @@ module.exports.gitignore = gitignore; /***/ }), -/* 489 */ +/* 708 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var arrayUniq = __webpack_require__(490); +var arrayUniq = __webpack_require__(709); module.exports = function () { return arrayUniq([].concat.apply([], arguments)); @@ -46022,7 +80346,7 @@ module.exports = function () { /***/ }), -/* 490 */ +/* 709 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -46091,10 +80415,10 @@ if ('Set' in global) { /***/ }), -/* 491 */ +/* 710 */ /***/ (function(module, exports, __webpack_require__) { -const pkg = __webpack_require__(492); +const pkg = __webpack_require__(711); module.exports = pkg.async; module.exports.default = pkg.async; @@ -46107,19 +80431,19 @@ module.exports.generateTasks = pkg.generateTasks; /***/ }), -/* 492 */ +/* 711 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var optionsManager = __webpack_require__(493); -var taskManager = __webpack_require__(494); -var reader_async_1 = __webpack_require__(646); -var reader_stream_1 = __webpack_require__(670); -var reader_sync_1 = __webpack_require__(671); -var arrayUtils = __webpack_require__(673); -var streamUtils = __webpack_require__(674); +var optionsManager = __webpack_require__(712); +var taskManager = __webpack_require__(713); +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. */ @@ -46185,7 +80509,7 @@ function isString(source) { /***/ }), -/* 493 */ +/* 712 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -46223,13 +80547,13 @@ exports.prepare = prepare; /***/ }), -/* 494 */ +/* 713 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var patternUtils = __webpack_require__(495); +var patternUtils = __webpack_require__(714); /** * Generate tasks based on parent directory of each pattern. */ @@ -46320,16 +80644,16 @@ exports.convertPatternGroupToTask = convertPatternGroupToTask; /***/ }), -/* 495 */ +/* 714 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var path = __webpack_require__(16); -var globParent = __webpack_require__(496); -var isGlob = __webpack_require__(499); -var micromatch = __webpack_require__(500); +var globParent = __webpack_require__(715); +var isGlob = __webpack_require__(718); +var micromatch = __webpack_require__(719); var GLOBSTAR = '**'; /** * Return true for static pattern. @@ -46475,15 +80799,15 @@ exports.matchAny = matchAny; /***/ }), -/* 496 */ +/* 715 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var path = __webpack_require__(16); -var isglob = __webpack_require__(497); -var pathDirname = __webpack_require__(498); +var isglob = __webpack_require__(716); +var pathDirname = __webpack_require__(717); var isWin32 = __webpack_require__(11).platform() === 'win32'; module.exports = function globParent(str) { @@ -46506,7 +80830,7 @@ module.exports = function globParent(str) { /***/ }), -/* 497 */ +/* 716 */ /***/ (function(module, exports, __webpack_require__) { /*! @@ -46516,7 +80840,7 @@ module.exports = function globParent(str) { * Licensed under the MIT License. */ -var isExtglob = __webpack_require__(188); +var isExtglob = __webpack_require__(602); module.exports = function isGlob(str) { if (typeof str !== 'string' || str === '') { @@ -46537,7 +80861,7 @@ module.exports = function isGlob(str) { /***/ }), -/* 498 */ +/* 717 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -46687,7 +81011,7 @@ module.exports.win32 = win32; /***/ }), -/* 499 */ +/* 718 */ /***/ (function(module, exports, __webpack_require__) { /*! @@ -46697,7 +81021,7 @@ module.exports.win32 = win32; * Released under the MIT License. */ -var isExtglob = __webpack_require__(188); +var isExtglob = __webpack_require__(602); var chars = { '{': '}', '(': ')', '[': ']'}; module.exports = function isGlob(str, options) { @@ -46739,7 +81063,7 @@ module.exports = function isGlob(str, options) { /***/ }), -/* 500 */ +/* 719 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -46750,18 +81074,18 @@ module.exports = function isGlob(str, options) { */ var util = __webpack_require__(29); -var braces = __webpack_require__(501); -var toRegex = __webpack_require__(604); -var extend = __webpack_require__(612); +var braces = __webpack_require__(720); +var toRegex = __webpack_require__(822); +var extend = __webpack_require__(830); /** * Local dependencies */ -var compilers = __webpack_require__(615); -var parsers = __webpack_require__(642); -var cache = __webpack_require__(643); -var utils = __webpack_require__(644); +var compilers = __webpack_require__(833); +var parsers = __webpack_require__(865); +var cache = __webpack_require__(866); +var utils = __webpack_require__(867); var MAX_LENGTH = 1024 * 64; /** @@ -47623,7 +81947,7 @@ module.exports = micromatch; /***/ }), -/* 501 */ +/* 720 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -47633,18 +81957,18 @@ module.exports = micromatch; * Module dependencies */ -var toRegex = __webpack_require__(502); -var unique = __webpack_require__(514); -var extend = __webpack_require__(511); +var toRegex = __webpack_require__(721); +var unique = __webpack_require__(733); +var extend = __webpack_require__(730); /** * Local dependencies */ -var compilers = __webpack_require__(515); -var parsers = __webpack_require__(530); -var Braces = __webpack_require__(540); -var utils = __webpack_require__(516); +var compilers = __webpack_require__(734); +var parsers = __webpack_require__(749); +var Braces = __webpack_require__(759); +var utils = __webpack_require__(735); var MAX_LENGTH = 1024 * 64; var cache = {}; @@ -47948,15 +82272,15 @@ module.exports = braces; /***/ }), -/* 502 */ +/* 721 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var define = __webpack_require__(503); -var extend = __webpack_require__(511); -var not = __webpack_require__(513); +var define = __webpack_require__(722); +var extend = __webpack_require__(730); +var not = __webpack_require__(732); var MAX_LENGTH = 1024 * 64; /** @@ -48103,7 +82427,7 @@ module.exports.makeRe = makeRe; /***/ }), -/* 503 */ +/* 722 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -48116,7 +82440,7 @@ module.exports.makeRe = makeRe; -var isDescriptor = __webpack_require__(504); +var isDescriptor = __webpack_require__(723); module.exports = function defineProperty(obj, prop, val) { if (typeof obj !== 'object' && typeof obj !== 'function') { @@ -48141,7 +82465,7 @@ module.exports = function defineProperty(obj, prop, val) { /***/ }), -/* 504 */ +/* 723 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -48154,9 +82478,9 @@ module.exports = function defineProperty(obj, prop, val) { -var typeOf = __webpack_require__(505); -var isAccessor = __webpack_require__(506); -var isData = __webpack_require__(509); +var typeOf = __webpack_require__(724); +var isAccessor = __webpack_require__(725); +var isData = __webpack_require__(728); module.exports = function isDescriptor(obj, key) { if (typeOf(obj) !== 'object') { @@ -48170,7 +82494,7 @@ module.exports = function isDescriptor(obj, key) { /***/ }), -/* 505 */ +/* 724 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -48323,7 +82647,7 @@ function isBuffer(val) { /***/ }), -/* 506 */ +/* 725 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -48336,7 +82660,7 @@ function isBuffer(val) { -var typeOf = __webpack_require__(507); +var typeOf = __webpack_require__(726); // accessor descriptor properties var accessor = { @@ -48399,10 +82723,10 @@ module.exports = isAccessorDescriptor; /***/ }), -/* 507 */ +/* 726 */ /***/ (function(module, exports, __webpack_require__) { -var isBuffer = __webpack_require__(508); +var isBuffer = __webpack_require__(727); var toString = Object.prototype.toString; /** @@ -48521,7 +82845,7 @@ module.exports = function kindOf(val) { /***/ }), -/* 508 */ +/* 727 */ /***/ (function(module, exports) { /*! @@ -48548,7 +82872,7 @@ function isSlowBuffer (obj) { /***/ }), -/* 509 */ +/* 728 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -48561,7 +82885,7 @@ function isSlowBuffer (obj) { -var typeOf = __webpack_require__(510); +var typeOf = __webpack_require__(729); // data descriptor properties var data = { @@ -48610,10 +82934,10 @@ module.exports = isDataDescriptor; /***/ }), -/* 510 */ +/* 729 */ /***/ (function(module, exports, __webpack_require__) { -var isBuffer = __webpack_require__(508); +var isBuffer = __webpack_require__(727); var toString = Object.prototype.toString; /** @@ -48732,13 +83056,13 @@ module.exports = function kindOf(val) { /***/ }), -/* 511 */ +/* 730 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isObject = __webpack_require__(512); +var isObject = __webpack_require__(731); module.exports = function extend(o/*, objects*/) { if (!isObject(o)) { o = {}; } @@ -48772,7 +83096,7 @@ function hasOwn(obj, key) { /***/ }), -/* 512 */ +/* 731 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -48792,13 +83116,13 @@ module.exports = function isExtendable(val) { /***/ }), -/* 513 */ +/* 732 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var extend = __webpack_require__(511); +var extend = __webpack_require__(730); /** * The main export is a function that takes a `pattern` string and an `options` object. @@ -48865,7 +83189,7 @@ module.exports = toRegex; /***/ }), -/* 514 */ +/* 733 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -48915,13 +83239,13 @@ module.exports.immutable = function uniqueImmutable(arr) { /***/ }), -/* 515 */ +/* 734 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var utils = __webpack_require__(516); +var utils = __webpack_require__(735); module.exports = function(braces, options) { braces.compiler @@ -49204,25 +83528,25 @@ function hasQueue(node) { /***/ }), -/* 516 */ +/* 735 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var splitString = __webpack_require__(517); +var splitString = __webpack_require__(736); var utils = module.exports; /** * Module dependencies */ -utils.extend = __webpack_require__(511); -utils.flatten = __webpack_require__(523); -utils.isObject = __webpack_require__(521); -utils.fillRange = __webpack_require__(524); -utils.repeat = __webpack_require__(529); -utils.unique = __webpack_require__(514); +utils.extend = __webpack_require__(730); +utils.flatten = __webpack_require__(742); +utils.isObject = __webpack_require__(740); +utils.fillRange = __webpack_require__(743); +utils.repeat = __webpack_require__(748); +utils.unique = __webpack_require__(733); utils.define = function(obj, key, val) { Object.defineProperty(obj, key, { @@ -49554,7 +83878,7 @@ utils.escapeRegex = function(str) { /***/ }), -/* 517 */ +/* 736 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -49567,7 +83891,7 @@ utils.escapeRegex = function(str) { -var extend = __webpack_require__(518); +var extend = __webpack_require__(737); module.exports = function(str, options, fn) { if (typeof str !== 'string') { @@ -49732,14 +84056,14 @@ function keepEscaping(opts, str, idx) { /***/ }), -/* 518 */ +/* 737 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(519); -var assignSymbols = __webpack_require__(522); +var isExtendable = __webpack_require__(738); +var assignSymbols = __webpack_require__(741); module.exports = Object.assign || function(obj/*, objects*/) { if (obj === null || typeof obj === 'undefined') { @@ -49799,7 +84123,7 @@ function isEnum(obj, key) { /***/ }), -/* 519 */ +/* 738 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -49812,7 +84136,7 @@ function isEnum(obj, key) { -var isPlainObject = __webpack_require__(520); +var isPlainObject = __webpack_require__(739); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -49820,7 +84144,7 @@ module.exports = function isExtendable(val) { /***/ }), -/* 520 */ +/* 739 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -49833,7 +84157,7 @@ module.exports = function isExtendable(val) { -var isObject = __webpack_require__(521); +var isObject = __webpack_require__(740); function isObjectObject(o) { return isObject(o) === true @@ -49864,7 +84188,7 @@ module.exports = function isPlainObject(o) { /***/ }), -/* 521 */ +/* 740 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -49883,7 +84207,7 @@ module.exports = function isObject(val) { /***/ }), -/* 522 */ +/* 741 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -49930,7 +84254,7 @@ module.exports = function(receiver, objects) { /***/ }), -/* 523 */ +/* 742 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -49959,7 +84283,7 @@ function flat(arr, res) { /***/ }), -/* 524 */ +/* 743 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -49973,10 +84297,10 @@ function flat(arr, res) { var util = __webpack_require__(29); -var isNumber = __webpack_require__(525); -var extend = __webpack_require__(511); -var repeat = __webpack_require__(527); -var toRegex = __webpack_require__(528); +var isNumber = __webpack_require__(744); +var extend = __webpack_require__(730); +var repeat = __webpack_require__(746); +var toRegex = __webpack_require__(747); /** * Return a range of numbers or letters. @@ -50174,7 +84498,7 @@ module.exports = fillRange; /***/ }), -/* 525 */ +/* 744 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -50187,7 +84511,7 @@ module.exports = fillRange; -var typeOf = __webpack_require__(526); +var typeOf = __webpack_require__(745); module.exports = function isNumber(num) { var type = typeOf(num); @@ -50203,10 +84527,10 @@ module.exports = function isNumber(num) { /***/ }), -/* 526 */ +/* 745 */ /***/ (function(module, exports, __webpack_require__) { -var isBuffer = __webpack_require__(508); +var isBuffer = __webpack_require__(727); var toString = Object.prototype.toString; /** @@ -50325,7 +84649,7 @@ module.exports = function kindOf(val) { /***/ }), -/* 527 */ +/* 746 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -50402,7 +84726,7 @@ function repeat(str, num) { /***/ }), -/* 528 */ +/* 747 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -50415,8 +84739,8 @@ function repeat(str, num) { -var repeat = __webpack_require__(527); -var isNumber = __webpack_require__(525); +var repeat = __webpack_require__(746); +var isNumber = __webpack_require__(744); var cache = {}; function toRegexRange(min, max, options) { @@ -50703,7 +85027,7 @@ module.exports = toRegexRange; /***/ }), -/* 529 */ +/* 748 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -50728,14 +85052,14 @@ module.exports = function repeat(ele, num) { /***/ }), -/* 530 */ +/* 749 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var Node = __webpack_require__(531); -var utils = __webpack_require__(516); +var Node = __webpack_require__(750); +var utils = __webpack_require__(735); /** * Braces parsers @@ -51095,15 +85419,15 @@ function concatNodes(pos, node, parent, options) { /***/ }), -/* 531 */ +/* 750 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isObject = __webpack_require__(521); -var define = __webpack_require__(532); -var utils = __webpack_require__(539); +var isObject = __webpack_require__(740); +var define = __webpack_require__(751); +var utils = __webpack_require__(758); var ownNames; /** @@ -51594,7 +85918,7 @@ exports = module.exports = Node; /***/ }), -/* 532 */ +/* 751 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -51607,7 +85931,7 @@ exports = module.exports = Node; -var isDescriptor = __webpack_require__(533); +var isDescriptor = __webpack_require__(752); module.exports = function defineProperty(obj, prop, val) { if (typeof obj !== 'object' && typeof obj !== 'function') { @@ -51632,7 +85956,7 @@ module.exports = function defineProperty(obj, prop, val) { /***/ }), -/* 533 */ +/* 752 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -51645,9 +85969,9 @@ module.exports = function defineProperty(obj, prop, val) { -var typeOf = __webpack_require__(534); -var isAccessor = __webpack_require__(535); -var isData = __webpack_require__(537); +var typeOf = __webpack_require__(753); +var isAccessor = __webpack_require__(754); +var isData = __webpack_require__(756); module.exports = function isDescriptor(obj, key) { if (typeOf(obj) !== 'object') { @@ -51661,7 +85985,7 @@ module.exports = function isDescriptor(obj, key) { /***/ }), -/* 534 */ +/* 753 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -51796,7 +86120,7 @@ function isBuffer(val) { /***/ }), -/* 535 */ +/* 754 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -51809,7 +86133,7 @@ function isBuffer(val) { -var typeOf = __webpack_require__(536); +var typeOf = __webpack_require__(755); // accessor descriptor properties var accessor = { @@ -51872,7 +86196,7 @@ module.exports = isAccessorDescriptor; /***/ }), -/* 536 */ +/* 755 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -52007,7 +86331,7 @@ function isBuffer(val) { /***/ }), -/* 537 */ +/* 756 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -52020,7 +86344,7 @@ function isBuffer(val) { -var typeOf = __webpack_require__(538); +var typeOf = __webpack_require__(757); module.exports = function isDataDescriptor(obj, prop) { // data descriptor properties @@ -52063,7 +86387,7 @@ module.exports = function isDataDescriptor(obj, prop) { /***/ }), -/* 538 */ +/* 757 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -52198,13 +86522,13 @@ function isBuffer(val) { /***/ }), -/* 539 */ +/* 758 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var typeOf = __webpack_require__(526); +var typeOf = __webpack_require__(745); var utils = module.exports; /** @@ -53224,17 +87548,17 @@ function assert(val, message) { /***/ }), -/* 540 */ +/* 759 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var extend = __webpack_require__(511); -var Snapdragon = __webpack_require__(541); -var compilers = __webpack_require__(515); -var parsers = __webpack_require__(530); -var utils = __webpack_require__(516); +var extend = __webpack_require__(730); +var Snapdragon = __webpack_require__(760); +var compilers = __webpack_require__(734); +var parsers = __webpack_require__(749); +var utils = __webpack_require__(735); /** * Customize Snapdragon parser and renderer @@ -53335,17 +87659,17 @@ module.exports = Braces; /***/ }), -/* 541 */ +/* 760 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var Base = __webpack_require__(542); -var define = __webpack_require__(503); -var Compiler = __webpack_require__(571); -var Parser = __webpack_require__(601); -var utils = __webpack_require__(581); +var Base = __webpack_require__(761); +var define = __webpack_require__(722); +var Compiler = __webpack_require__(790); +var Parser = __webpack_require__(819); +var utils = __webpack_require__(799); var regexCache = {}; var cache = {}; @@ -53516,20 +87840,20 @@ module.exports.Parser = Parser; /***/ }), -/* 542 */ +/* 761 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var util = __webpack_require__(29); -var define = __webpack_require__(543); -var CacheBase = __webpack_require__(544); -var Emitter = __webpack_require__(545); -var isObject = __webpack_require__(521); -var merge = __webpack_require__(562); -var pascal = __webpack_require__(565); -var cu = __webpack_require__(566); +var define = __webpack_require__(762); +var CacheBase = __webpack_require__(763); +var Emitter = __webpack_require__(764); +var isObject = __webpack_require__(740); +var merge = __webpack_require__(781); +var pascal = __webpack_require__(784); +var cu = __webpack_require__(785); /** * Optionally define a custom `cache` namespace to use. @@ -53958,7 +88282,7 @@ module.exports.namespace = namespace; /***/ }), -/* 543 */ +/* 762 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -53971,7 +88295,7 @@ module.exports.namespace = namespace; -var isDescriptor = __webpack_require__(533); +var isDescriptor = __webpack_require__(752); module.exports = function defineProperty(obj, prop, val) { if (typeof obj !== 'object' && typeof obj !== 'function') { @@ -53996,21 +88320,21 @@ module.exports = function defineProperty(obj, prop, val) { /***/ }), -/* 544 */ +/* 763 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isObject = __webpack_require__(521); -var Emitter = __webpack_require__(545); -var visit = __webpack_require__(546); -var toPath = __webpack_require__(549); -var union = __webpack_require__(550); -var del = __webpack_require__(554); -var get = __webpack_require__(552); -var has = __webpack_require__(559); -var set = __webpack_require__(553); +var isObject = __webpack_require__(740); +var Emitter = __webpack_require__(764); +var visit = __webpack_require__(765); +var toPath = __webpack_require__(768); +var union = __webpack_require__(769); +var del = __webpack_require__(773); +var get = __webpack_require__(771); +var has = __webpack_require__(778); +var set = __webpack_require__(772); /** * Create a `Cache` constructor that when instantiated will @@ -54264,7 +88588,7 @@ module.exports.namespace = namespace; /***/ }), -/* 545 */ +/* 764 */ /***/ (function(module, exports, __webpack_require__) { @@ -54433,7 +88757,7 @@ Emitter.prototype.hasListeners = function(event){ /***/ }), -/* 546 */ +/* 765 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -54446,8 +88770,8 @@ Emitter.prototype.hasListeners = function(event){ -var visit = __webpack_require__(547); -var mapVisit = __webpack_require__(548); +var visit = __webpack_require__(766); +var mapVisit = __webpack_require__(767); module.exports = function(collection, method, val) { var result; @@ -54470,7 +88794,7 @@ module.exports = function(collection, method, val) { /***/ }), -/* 547 */ +/* 766 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -54483,7 +88807,7 @@ module.exports = function(collection, method, val) { -var isObject = __webpack_require__(521); +var isObject = __webpack_require__(740); module.exports = function visit(thisArg, method, target, val) { if (!isObject(thisArg) && typeof thisArg !== 'function') { @@ -54510,14 +88834,14 @@ module.exports = function visit(thisArg, method, target, val) { /***/ }), -/* 548 */ +/* 767 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var util = __webpack_require__(29); -var visit = __webpack_require__(547); +var visit = __webpack_require__(766); /** * Map `visit` over an array of objects. @@ -54554,7 +88878,7 @@ function isObject(val) { /***/ }), -/* 549 */ +/* 768 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -54567,7 +88891,7 @@ function isObject(val) { -var typeOf = __webpack_require__(526); +var typeOf = __webpack_require__(745); module.exports = function toPath(args) { if (typeOf(args) !== 'arguments') { @@ -54594,16 +88918,16 @@ function filter(arr) { /***/ }), -/* 550 */ +/* 769 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isObject = __webpack_require__(512); -var union = __webpack_require__(551); -var get = __webpack_require__(552); -var set = __webpack_require__(553); +var isObject = __webpack_require__(731); +var union = __webpack_require__(770); +var get = __webpack_require__(771); +var set = __webpack_require__(772); module.exports = function unionValue(obj, prop, value) { if (!isObject(obj)) { @@ -54631,7 +88955,7 @@ function arrayify(val) { /***/ }), -/* 551 */ +/* 770 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -54667,7 +88991,7 @@ module.exports = function union(init) { /***/ }), -/* 552 */ +/* 771 */ /***/ (function(module, exports) { /*! @@ -54723,7 +89047,7 @@ function toString(val) { /***/ }), -/* 553 */ +/* 772 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -54736,10 +89060,10 @@ function toString(val) { -var split = __webpack_require__(517); -var extend = __webpack_require__(511); -var isPlainObject = __webpack_require__(520); -var isObject = __webpack_require__(512); +var split = __webpack_require__(736); +var extend = __webpack_require__(730); +var isPlainObject = __webpack_require__(739); +var isObject = __webpack_require__(731); module.exports = function(obj, prop, val) { if (!isObject(obj)) { @@ -54785,7 +89109,7 @@ function isValidKey(key) { /***/ }), -/* 554 */ +/* 773 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -54798,8 +89122,8 @@ function isValidKey(key) { -var isObject = __webpack_require__(521); -var has = __webpack_require__(555); +var isObject = __webpack_require__(740); +var has = __webpack_require__(774); module.exports = function unset(obj, prop) { if (!isObject(obj)) { @@ -54824,7 +89148,7 @@ module.exports = function unset(obj, prop) { /***/ }), -/* 555 */ +/* 774 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -54837,9 +89161,9 @@ module.exports = function unset(obj, prop) { -var isObject = __webpack_require__(556); -var hasValues = __webpack_require__(558); -var get = __webpack_require__(552); +var isObject = __webpack_require__(775); +var hasValues = __webpack_require__(777); +var get = __webpack_require__(771); module.exports = function(obj, prop, noZero) { if (isObject(obj)) { @@ -54850,7 +89174,7 @@ module.exports = function(obj, prop, noZero) { /***/ }), -/* 556 */ +/* 775 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -54863,7 +89187,7 @@ module.exports = function(obj, prop, noZero) { -var isArray = __webpack_require__(557); +var isArray = __webpack_require__(776); module.exports = function isObject(val) { return val != null && typeof val === 'object' && isArray(val) === false; @@ -54871,7 +89195,7 @@ module.exports = function isObject(val) { /***/ }), -/* 557 */ +/* 776 */ /***/ (function(module, exports) { var toString = {}.toString; @@ -54882,7 +89206,7 @@ module.exports = Array.isArray || function (arr) { /***/ }), -/* 558 */ +/* 777 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -54925,7 +89249,7 @@ module.exports = function hasValue(o, noZero) { /***/ }), -/* 559 */ +/* 778 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -54938,9 +89262,9 @@ module.exports = function hasValue(o, noZero) { -var isObject = __webpack_require__(521); -var hasValues = __webpack_require__(560); -var get = __webpack_require__(552); +var isObject = __webpack_require__(740); +var hasValues = __webpack_require__(779); +var get = __webpack_require__(771); module.exports = function(val, prop) { return hasValues(isObject(val) && prop ? get(val, prop) : val); @@ -54948,7 +89272,7 @@ module.exports = function(val, prop) { /***/ }), -/* 560 */ +/* 779 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -54961,8 +89285,8 @@ module.exports = function(val, prop) { -var typeOf = __webpack_require__(561); -var isNumber = __webpack_require__(525); +var typeOf = __webpack_require__(780); +var isNumber = __webpack_require__(744); module.exports = function hasValue(val) { // is-number checks for NaN and other edge cases @@ -55015,10 +89339,10 @@ module.exports = function hasValue(val) { /***/ }), -/* 561 */ +/* 780 */ /***/ (function(module, exports, __webpack_require__) { -var isBuffer = __webpack_require__(508); +var isBuffer = __webpack_require__(727); var toString = Object.prototype.toString; /** @@ -55140,14 +89464,14 @@ module.exports = function kindOf(val) { /***/ }), -/* 562 */ +/* 781 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(563); -var forIn = __webpack_require__(564); +var isExtendable = __webpack_require__(782); +var forIn = __webpack_require__(783); function mixinDeep(target, objects) { var len = arguments.length, i = 0; @@ -55211,7 +89535,7 @@ module.exports = mixinDeep; /***/ }), -/* 563 */ +/* 782 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -55224,7 +89548,7 @@ module.exports = mixinDeep; -var isPlainObject = __webpack_require__(520); +var isPlainObject = __webpack_require__(739); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -55232,7 +89556,7 @@ module.exports = function isExtendable(val) { /***/ }), -/* 564 */ +/* 783 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -55255,7 +89579,7 @@ module.exports = function forIn(obj, fn, thisArg) { /***/ }), -/* 565 */ +/* 784 */ /***/ (function(module, exports) { /*! @@ -55282,14 +89606,14 @@ module.exports = pascalcase; /***/ }), -/* 566 */ +/* 785 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; var util = __webpack_require__(29); -var utils = __webpack_require__(567); +var utils = __webpack_require__(786); /** * Expose class utils @@ -55654,7 +89978,7 @@ cu.bubble = function(Parent, events) { /***/ }), -/* 567 */ +/* 786 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -55668,10 +89992,10 @@ var utils = {}; * Lazily required module dependencies */ -utils.union = __webpack_require__(551); -utils.define = __webpack_require__(503); -utils.isObj = __webpack_require__(521); -utils.staticExtend = __webpack_require__(568); +utils.union = __webpack_require__(770); +utils.define = __webpack_require__(722); +utils.isObj = __webpack_require__(740); +utils.staticExtend = __webpack_require__(787); /** @@ -55682,7 +90006,7 @@ module.exports = utils; /***/ }), -/* 568 */ +/* 787 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -55695,8 +90019,8 @@ module.exports = utils; -var copy = __webpack_require__(569); -var define = __webpack_require__(503); +var copy = __webpack_require__(788); +var define = __webpack_require__(722); var util = __webpack_require__(29); /** @@ -55779,15 +90103,15 @@ module.exports = extend; /***/ }), -/* 569 */ +/* 788 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var typeOf = __webpack_require__(526); -var copyDescriptor = __webpack_require__(570); -var define = __webpack_require__(503); +var typeOf = __webpack_require__(745); +var copyDescriptor = __webpack_require__(789); +var define = __webpack_require__(722); /** * Copy static properties, prototype properties, and descriptors from one object to another. @@ -55960,7 +90284,7 @@ module.exports.has = has; /***/ }), -/* 570 */ +/* 789 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -56048,16 +90372,16 @@ function isObject(val) { /***/ }), -/* 571 */ +/* 790 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var use = __webpack_require__(572); -var define = __webpack_require__(503); -var debug = __webpack_require__(574)('snapdragon:compiler'); -var utils = __webpack_require__(581); +var use = __webpack_require__(791); +var define = __webpack_require__(722); +var debug = __webpack_require__(793)('snapdragon:compiler'); +var utils = __webpack_require__(799); /** * Create a new `Compiler` with the given `options`. @@ -56211,7 +90535,7 @@ Compiler.prototype = { // source map support if (opts.sourcemap) { - var sourcemaps = __webpack_require__(600); + var sourcemaps = __webpack_require__(818); sourcemaps(this); this.mapVisit(this.ast.nodes); this.applySourceMaps(); @@ -56232,7 +90556,7 @@ module.exports = Compiler; /***/ }), -/* 572 */ +/* 791 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -56245,7 +90569,7 @@ module.exports = Compiler; -var utils = __webpack_require__(573); +var utils = __webpack_require__(792); module.exports = function base(app, opts) { if (!utils.isObject(app) && typeof app !== 'function') { @@ -56360,7 +90684,7 @@ module.exports = function base(app, opts) { /***/ }), -/* 573 */ +/* 792 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -56374,8 +90698,8 @@ var utils = {}; * Lazily required module dependencies */ -utils.define = __webpack_require__(503); -utils.isObject = __webpack_require__(521); +utils.define = __webpack_require__(722); +utils.isObject = __webpack_require__(740); utils.isString = function(val) { @@ -56390,7 +90714,7 @@ module.exports = utils; /***/ }), -/* 574 */ +/* 793 */ /***/ (function(module, exports, __webpack_require__) { /** @@ -56399,14 +90723,14 @@ module.exports = utils; */ if (typeof process !== 'undefined' && process.type === 'renderer') { - module.exports = __webpack_require__(575); + module.exports = __webpack_require__(794); } else { - module.exports = __webpack_require__(578); + module.exports = __webpack_require__(797); } /***/ }), -/* 575 */ +/* 794 */ /***/ (function(module, exports, __webpack_require__) { /** @@ -56415,7 +90739,7 @@ if (typeof process !== 'undefined' && process.type === 'renderer') { * Expose `debug()` as the module. */ -exports = module.exports = __webpack_require__(576); +exports = module.exports = __webpack_require__(795); exports.log = log; exports.formatArgs = formatArgs; exports.save = save; @@ -56597,7 +90921,7 @@ function localstorage() { /***/ }), -/* 576 */ +/* 795 */ /***/ (function(module, exports, __webpack_require__) { @@ -56613,7 +90937,7 @@ exports.coerce = coerce; exports.disable = disable; exports.enable = enable; exports.enabled = enabled; -exports.humanize = __webpack_require__(577); +exports.humanize = __webpack_require__(796); /** * The currently active debug mode names, and names to skip. @@ -56805,7 +91129,7 @@ function coerce(val) { /***/ }), -/* 577 */ +/* 796 */ /***/ (function(module, exports) { /** @@ -56963,14 +91287,14 @@ function plural(ms, n, name) { /***/ }), -/* 578 */ +/* 797 */ /***/ (function(module, exports, __webpack_require__) { /** * Module dependencies. */ -var tty = __webpack_require__(579); +var tty = __webpack_require__(480); var util = __webpack_require__(29); /** @@ -56979,7 +91303,7 @@ var util = __webpack_require__(29); * Expose `debug()` as the module. */ -exports = module.exports = __webpack_require__(576); +exports = module.exports = __webpack_require__(795); exports.init = init; exports.log = log; exports.formatArgs = formatArgs; @@ -57158,7 +91482,7 @@ function createWritableStdioStream (fd) { case 'PIPE': case 'TCP': - var net = __webpack_require__(580); + var net = __webpack_require__(798); stream = new net.Socket({ fd: fd, readable: false, @@ -57217,19 +91541,13 @@ exports.enable(load()); /***/ }), -/* 579 */ -/***/ (function(module, exports) { - -module.exports = require("tty"); - -/***/ }), -/* 580 */ +/* 798 */ /***/ (function(module, exports) { module.exports = require("net"); /***/ }), -/* 581 */ +/* 799 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -57239,9 +91557,9 @@ module.exports = require("net"); * Module dependencies */ -exports.extend = __webpack_require__(511); -exports.SourceMap = __webpack_require__(582); -exports.sourceMapResolve = __webpack_require__(593); +exports.extend = __webpack_require__(730); +exports.SourceMap = __webpack_require__(800); +exports.sourceMapResolve = __webpack_require__(811); /** * Convert backslash in the given string to forward slashes @@ -57284,7 +91602,7 @@ exports.last = function(arr, n) { /***/ }), -/* 582 */ +/* 800 */ /***/ (function(module, exports, __webpack_require__) { /* @@ -57292,13 +91610,13 @@ exports.last = function(arr, n) { * Licensed under the New BSD license. See LICENSE.txt or: * http://opensource.org/licenses/BSD-3-Clause */ -exports.SourceMapGenerator = __webpack_require__(583).SourceMapGenerator; -exports.SourceMapConsumer = __webpack_require__(589).SourceMapConsumer; -exports.SourceNode = __webpack_require__(592).SourceNode; +exports.SourceMapGenerator = __webpack_require__(801).SourceMapGenerator; +exports.SourceMapConsumer = __webpack_require__(807).SourceMapConsumer; +exports.SourceNode = __webpack_require__(810).SourceNode; /***/ }), -/* 583 */ +/* 801 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -57308,10 +91626,10 @@ exports.SourceNode = __webpack_require__(592).SourceNode; * http://opensource.org/licenses/BSD-3-Clause */ -var base64VLQ = __webpack_require__(584); -var util = __webpack_require__(586); -var ArraySet = __webpack_require__(587).ArraySet; -var MappingList = __webpack_require__(588).MappingList; +var base64VLQ = __webpack_require__(802); +var util = __webpack_require__(804); +var ArraySet = __webpack_require__(805).ArraySet; +var MappingList = __webpack_require__(806).MappingList; /** * An instance of the SourceMapGenerator represents a source map which is @@ -57720,7 +92038,7 @@ exports.SourceMapGenerator = SourceMapGenerator; /***/ }), -/* 584 */ +/* 802 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -57760,7 +92078,7 @@ exports.SourceMapGenerator = SourceMapGenerator; * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ -var base64 = __webpack_require__(585); +var base64 = __webpack_require__(803); // A single base 64 digit can contain 6 bits of data. For the base 64 variable // length quantities we use in the source map spec, the first bit is the sign, @@ -57866,7 +92184,7 @@ exports.decode = function base64VLQ_decode(aStr, aIndex, aOutParam) { /***/ }), -/* 585 */ +/* 803 */ /***/ (function(module, exports) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -57939,7 +92257,7 @@ exports.decode = function (charCode) { /***/ }), -/* 586 */ +/* 804 */ /***/ (function(module, exports) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -58362,7 +92680,7 @@ exports.compareByGeneratedPositionsInflated = compareByGeneratedPositionsInflate /***/ }), -/* 587 */ +/* 805 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -58372,7 +92690,7 @@ exports.compareByGeneratedPositionsInflated = compareByGeneratedPositionsInflate * http://opensource.org/licenses/BSD-3-Clause */ -var util = __webpack_require__(586); +var util = __webpack_require__(804); var has = Object.prototype.hasOwnProperty; var hasNativeMap = typeof Map !== "undefined"; @@ -58489,7 +92807,7 @@ exports.ArraySet = ArraySet; /***/ }), -/* 588 */ +/* 806 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -58499,7 +92817,7 @@ exports.ArraySet = ArraySet; * http://opensource.org/licenses/BSD-3-Clause */ -var util = __webpack_require__(586); +var util = __webpack_require__(804); /** * Determine whether mappingB is after mappingA with respect to generated @@ -58574,7 +92892,7 @@ exports.MappingList = MappingList; /***/ }), -/* 589 */ +/* 807 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -58584,11 +92902,11 @@ exports.MappingList = MappingList; * http://opensource.org/licenses/BSD-3-Clause */ -var util = __webpack_require__(586); -var binarySearch = __webpack_require__(590); -var ArraySet = __webpack_require__(587).ArraySet; -var base64VLQ = __webpack_require__(584); -var quickSort = __webpack_require__(591).quickSort; +var util = __webpack_require__(804); +var binarySearch = __webpack_require__(808); +var ArraySet = __webpack_require__(805).ArraySet; +var base64VLQ = __webpack_require__(802); +var quickSort = __webpack_require__(809).quickSort; function SourceMapConsumer(aSourceMap) { var sourceMap = aSourceMap; @@ -59662,7 +93980,7 @@ exports.IndexedSourceMapConsumer = IndexedSourceMapConsumer; /***/ }), -/* 590 */ +/* 808 */ /***/ (function(module, exports) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -59779,7 +94097,7 @@ exports.search = function search(aNeedle, aHaystack, aCompare, aBias) { /***/ }), -/* 591 */ +/* 809 */ /***/ (function(module, exports) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -59899,7 +94217,7 @@ exports.quickSort = function (ary, comparator) { /***/ }), -/* 592 */ +/* 810 */ /***/ (function(module, exports, __webpack_require__) { /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -59909,8 +94227,8 @@ exports.quickSort = function (ary, comparator) { * http://opensource.org/licenses/BSD-3-Clause */ -var SourceMapGenerator = __webpack_require__(583).SourceMapGenerator; -var util = __webpack_require__(586); +var SourceMapGenerator = __webpack_require__(801).SourceMapGenerator; +var util = __webpack_require__(804); // Matches a Windows-style `\r\n` newline or a `\n` newline used by all other // operating systems these days (capturing the result). @@ -60318,17 +94636,17 @@ exports.SourceNode = SourceNode; /***/ }), -/* 593 */ +/* 811 */ /***/ (function(module, exports, __webpack_require__) { // Copyright 2014, 2015, 2016, 2017 Simon Lydell // X11 (“MIT”) Licensed. (See LICENSE.) -var sourceMappingURL = __webpack_require__(594) -var resolveUrl = __webpack_require__(595) -var decodeUriComponent = __webpack_require__(596) -var urix = __webpack_require__(598) -var atob = __webpack_require__(599) +var sourceMappingURL = __webpack_require__(812) +var resolveUrl = __webpack_require__(813) +var decodeUriComponent = __webpack_require__(814) +var urix = __webpack_require__(816) +var atob = __webpack_require__(817) @@ -60626,7 +94944,7 @@ module.exports = { /***/ }), -/* 594 */ +/* 812 */ /***/ (function(module, exports, __webpack_require__) { var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_RESULT__;// Copyright 2014 Simon Lydell @@ -60689,13 +95007,13 @@ void (function(root, factory) { /***/ }), -/* 595 */ +/* 813 */ /***/ (function(module, exports, __webpack_require__) { // Copyright 2014 Simon Lydell // X11 (“MIT”) Licensed. (See LICENSE.) -var url = __webpack_require__(82) +var url = __webpack_require__(454) function resolveUrl(/* ...urls */) { return Array.prototype.reduce.call(arguments, function(resolved, nextUrl) { @@ -60707,13 +95025,13 @@ module.exports = resolveUrl /***/ }), -/* 596 */ +/* 814 */ /***/ (function(module, exports, __webpack_require__) { // Copyright 2017 Simon Lydell // X11 (“MIT”) Licensed. (See LICENSE.) -var decodeUriComponent = __webpack_require__(597) +var decodeUriComponent = __webpack_require__(815) function customDecodeUriComponent(string) { // `decodeUriComponent` turns `+` into ` `, but that's not wanted. @@ -60724,7 +95042,7 @@ module.exports = customDecodeUriComponent /***/ }), -/* 597 */ +/* 815 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -60825,7 +95143,7 @@ module.exports = function (encodedURI) { /***/ }), -/* 598 */ +/* 816 */ /***/ (function(module, exports, __webpack_require__) { // Copyright 2014 Simon Lydell @@ -60848,7 +95166,7 @@ module.exports = urix /***/ }), -/* 599 */ +/* 817 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -60862,7 +95180,7 @@ module.exports = atob.atob = atob; /***/ }), -/* 600 */ +/* 818 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -60870,8 +95188,8 @@ module.exports = atob.atob = atob; var fs = __webpack_require__(23); var path = __webpack_require__(16); -var define = __webpack_require__(503); -var utils = __webpack_require__(581); +var define = __webpack_require__(722); +var utils = __webpack_require__(799); /** * Expose `mixin()`. @@ -61014,19 +95332,19 @@ exports.comment = function(node) { /***/ }), -/* 601 */ +/* 819 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var use = __webpack_require__(572); +var use = __webpack_require__(791); var util = __webpack_require__(29); -var Cache = __webpack_require__(602); -var define = __webpack_require__(503); -var debug = __webpack_require__(574)('snapdragon:parser'); -var Position = __webpack_require__(603); -var utils = __webpack_require__(581); +var Cache = __webpack_require__(820); +var define = __webpack_require__(722); +var debug = __webpack_require__(793)('snapdragon:parser'); +var Position = __webpack_require__(821); +var utils = __webpack_require__(799); /** * Create a new `Parser` with the given `input` and `options`. @@ -61554,7 +95872,7 @@ module.exports = Parser; /***/ }), -/* 602 */ +/* 820 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -61661,13 +95979,13 @@ MapCache.prototype.del = function mapDelete(key) { /***/ }), -/* 603 */ +/* 821 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var define = __webpack_require__(503); +var define = __webpack_require__(722); /** * Store position for a node @@ -61682,16 +96000,16 @@ module.exports = function Position(start, parser) { /***/ }), -/* 604 */ +/* 822 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var safe = __webpack_require__(605); -var define = __webpack_require__(611); -var extend = __webpack_require__(612); -var not = __webpack_require__(614); +var safe = __webpack_require__(823); +var define = __webpack_require__(829); +var extend = __webpack_require__(830); +var not = __webpack_require__(832); var MAX_LENGTH = 1024 * 64; /** @@ -61844,10 +96162,10 @@ module.exports.makeRe = makeRe; /***/ }), -/* 605 */ +/* 823 */ /***/ (function(module, exports, __webpack_require__) { -var parse = __webpack_require__(606); +var parse = __webpack_require__(824); var types = parse.types; module.exports = function (re, opts) { @@ -61893,13 +96211,13 @@ function isRegExp (x) { /***/ }), -/* 606 */ +/* 824 */ /***/ (function(module, exports, __webpack_require__) { -var util = __webpack_require__(607); -var types = __webpack_require__(608); -var sets = __webpack_require__(609); -var positions = __webpack_require__(610); +var util = __webpack_require__(825); +var types = __webpack_require__(826); +var sets = __webpack_require__(827); +var positions = __webpack_require__(828); module.exports = function(regexpStr) { @@ -62181,11 +96499,11 @@ module.exports.types = types; /***/ }), -/* 607 */ +/* 825 */ /***/ (function(module, exports, __webpack_require__) { -var types = __webpack_require__(608); -var sets = __webpack_require__(609); +var types = __webpack_require__(826); +var sets = __webpack_require__(827); // All of these are private and only used by randexp. @@ -62298,7 +96616,7 @@ exports.error = function(regexp, msg) { /***/ }), -/* 608 */ +/* 826 */ /***/ (function(module, exports) { module.exports = { @@ -62314,10 +96632,10 @@ module.exports = { /***/ }), -/* 609 */ +/* 827 */ /***/ (function(module, exports, __webpack_require__) { -var types = __webpack_require__(608); +var types = __webpack_require__(826); var INTS = function() { return [{ type: types.RANGE , from: 48, to: 57 }]; @@ -62402,10 +96720,10 @@ exports.anyChar = function() { /***/ }), -/* 610 */ +/* 828 */ /***/ (function(module, exports, __webpack_require__) { -var types = __webpack_require__(608); +var types = __webpack_require__(826); exports.wordBoundary = function() { return { type: types.POSITION, value: 'b' }; @@ -62425,7 +96743,7 @@ exports.end = function() { /***/ }), -/* 611 */ +/* 829 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -62438,8 +96756,8 @@ exports.end = function() { -var isobject = __webpack_require__(521); -var isDescriptor = __webpack_require__(533); +var isobject = __webpack_require__(740); +var isDescriptor = __webpack_require__(752); var define = (typeof Reflect !== 'undefined' && Reflect.defineProperty) ? Reflect.defineProperty : Object.defineProperty; @@ -62470,14 +96788,14 @@ module.exports = function defineProperty(obj, key, val) { /***/ }), -/* 612 */ +/* 830 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(613); -var assignSymbols = __webpack_require__(522); +var isExtendable = __webpack_require__(831); +var assignSymbols = __webpack_require__(741); module.exports = Object.assign || function(obj/*, objects*/) { if (obj === null || typeof obj === 'undefined') { @@ -62537,7 +96855,7 @@ function isEnum(obj, key) { /***/ }), -/* 613 */ +/* 831 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -62550,7 +96868,7 @@ function isEnum(obj, key) { -var isPlainObject = __webpack_require__(520); +var isPlainObject = __webpack_require__(739); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -62558,14 +96876,14 @@ module.exports = function isExtendable(val) { /***/ }), -/* 614 */ +/* 832 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var extend = __webpack_require__(612); -var safe = __webpack_require__(605); +var extend = __webpack_require__(830); +var safe = __webpack_require__(823); /** * The main export is a function that takes a `pattern` string and an `options` object. @@ -62637,14 +96955,14 @@ module.exports = toRegex; /***/ }), -/* 615 */ +/* 833 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var nanomatch = __webpack_require__(616); -var extglob = __webpack_require__(631); +var nanomatch = __webpack_require__(834); +var extglob = __webpack_require__(849); module.exports = function(snapdragon) { var compilers = snapdragon.compiler.compilers; @@ -62721,7 +97039,7 @@ function escapeExtglobs(compiler) { /***/ }), -/* 616 */ +/* 834 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -62732,17 +97050,17 @@ function escapeExtglobs(compiler) { */ var util = __webpack_require__(29); -var toRegex = __webpack_require__(502); -var extend = __webpack_require__(617); +var toRegex = __webpack_require__(721); +var extend = __webpack_require__(835); /** * Local dependencies */ -var compilers = __webpack_require__(619); -var parsers = __webpack_require__(620); -var cache = __webpack_require__(623); -var utils = __webpack_require__(625); +var compilers = __webpack_require__(837); +var parsers = __webpack_require__(838); +var cache = __webpack_require__(841); +var utils = __webpack_require__(843); var MAX_LENGTH = 1024 * 64; /** @@ -63566,14 +97884,14 @@ module.exports = nanomatch; /***/ }), -/* 617 */ +/* 835 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var isExtendable = __webpack_require__(618); -var assignSymbols = __webpack_require__(522); +var isExtendable = __webpack_require__(836); +var assignSymbols = __webpack_require__(741); module.exports = Object.assign || function(obj/*, objects*/) { if (obj === null || typeof obj === 'undefined') { @@ -63633,7 +97951,7 @@ function isEnum(obj, key) { /***/ }), -/* 618 */ +/* 836 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -63646,7 +97964,7 @@ function isEnum(obj, key) { -var isPlainObject = __webpack_require__(520); +var isPlainObject = __webpack_require__(739); module.exports = function isExtendable(val) { return isPlainObject(val) || typeof val === 'function' || Array.isArray(val); @@ -63654,7 +97972,7 @@ module.exports = function isExtendable(val) { /***/ }), -/* 619 */ +/* 837 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -64000,15 +98318,15 @@ module.exports = function(nanomatch, options) { /***/ }), -/* 620 */ +/* 838 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var regexNot = __webpack_require__(513); -var toRegex = __webpack_require__(502); -var isOdd = __webpack_require__(621); +var regexNot = __webpack_require__(732); +var toRegex = __webpack_require__(721); +var isOdd = __webpack_require__(839); /** * Characters to use in negation regex (we want to "not" match @@ -64394,7 +98712,7 @@ module.exports.not = NOT_REGEX; /***/ }), -/* 621 */ +/* 839 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -64407,7 +98725,7 @@ module.exports.not = NOT_REGEX; -var isNumber = __webpack_require__(622); +var isNumber = __webpack_require__(840); module.exports = function isOdd(i) { if (!isNumber(i)) { @@ -64421,7 +98739,7 @@ module.exports = function isOdd(i) { /***/ }), -/* 622 */ +/* 840 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -64449,14 +98767,14 @@ module.exports = function isNumber(num) { /***/ }), -/* 623 */ +/* 841 */ /***/ (function(module, exports, __webpack_require__) { -module.exports = new (__webpack_require__(624))(); +module.exports = new (__webpack_require__(842))(); /***/ }), -/* 624 */ +/* 842 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -64469,7 +98787,7 @@ module.exports = new (__webpack_require__(624))(); -var MapCache = __webpack_require__(602); +var MapCache = __webpack_require__(820); /** * Create a new `FragmentCache` with an optional object to use for `caches`. @@ -64591,7 +98909,7 @@ exports = module.exports = FragmentCache; /***/ }), -/* 625 */ +/* 843 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -64604,14 +98922,14 @@ var path = __webpack_require__(16); * Module dependencies */ -var isWindows = __webpack_require__(626)(); -var Snapdragon = __webpack_require__(541); -utils.define = __webpack_require__(627); -utils.diff = __webpack_require__(628); -utils.extend = __webpack_require__(617); -utils.pick = __webpack_require__(629); -utils.typeOf = __webpack_require__(630); -utils.unique = __webpack_require__(514); +var isWindows = __webpack_require__(844)(); +var Snapdragon = __webpack_require__(760); +utils.define = __webpack_require__(845); +utils.diff = __webpack_require__(846); +utils.extend = __webpack_require__(835); +utils.pick = __webpack_require__(847); +utils.typeOf = __webpack_require__(848); +utils.unique = __webpack_require__(733); /** * Returns true if the given value is effectively an empty string @@ -64977,7 +99295,7 @@ utils.unixify = function(options) { /***/ }), -/* 626 */ +/* 844 */ /***/ (function(module, exports, __webpack_require__) { var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_DEFINE_RESULT__;/*! @@ -65005,7 +99323,7 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ /***/ }), -/* 627 */ +/* 845 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -65018,8 +99336,8 @@ var __WEBPACK_AMD_DEFINE_FACTORY__, __WEBPACK_AMD_DEFINE_ARRAY__, __WEBPACK_AMD_ -var isobject = __webpack_require__(521); -var isDescriptor = __webpack_require__(533); +var isobject = __webpack_require__(740); +var isDescriptor = __webpack_require__(752); var define = (typeof Reflect !== 'undefined' && Reflect.defineProperty) ? Reflect.defineProperty : Object.defineProperty; @@ -65050,7 +99368,7 @@ module.exports = function defineProperty(obj, key, val) { /***/ }), -/* 628 */ +/* 846 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -65104,7 +99422,7 @@ function diffArray(one, two) { /***/ }), -/* 629 */ +/* 847 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -65117,7 +99435,7 @@ function diffArray(one, two) { -var isObject = __webpack_require__(521); +var isObject = __webpack_require__(740); module.exports = function pick(obj, keys) { if (!isObject(obj) && typeof obj !== 'function') { @@ -65146,7 +99464,7 @@ module.exports = function pick(obj, keys) { /***/ }), -/* 630 */ +/* 848 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -65267,1151 +99585,1978 @@ function isArguments(val) { return false; } -/** - * If you need to support Safari 5-7 (8-10 yr-old browser), - * take a look at https://github.com/feross/is-buffer - */ +/** + * If you need to support Safari 5-7 (8-10 yr-old browser), + * take a look at https://github.com/feross/is-buffer + */ + +function isBuffer(val) { + if (val.constructor && typeof val.constructor.isBuffer === 'function') { + return val.constructor.isBuffer(val); + } + return false; +} + + +/***/ }), +/* 849 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +/** + * Module dependencies + */ + +var extend = __webpack_require__(730); +var unique = __webpack_require__(733); +var toRegex = __webpack_require__(721); + +/** + * Local dependencies + */ + +var compilers = __webpack_require__(850); +var parsers = __webpack_require__(861); +var Extglob = __webpack_require__(864); +var utils = __webpack_require__(863); +var MAX_LENGTH = 1024 * 64; + +/** + * Convert the given `extglob` pattern into a regex-compatible string. Returns + * an object with the compiled result and the parsed AST. + * + * ```js + * var extglob = require('extglob'); + * console.log(extglob('*.!(*a)')); + * //=> '(?!\\.)[^/]*?\\.(?!(?!\\.)[^/]*?a\\b).*?' + * ``` + * @param {String} `pattern` + * @param {Object} `options` + * @return {String} + * @api public + */ + +function extglob(pattern, options) { + return extglob.create(pattern, options).output; +} + +/** + * Takes an array of strings and an extglob pattern and returns a new + * array that contains only the strings that match the pattern. + * + * ```js + * var extglob = require('extglob'); + * console.log(extglob.match(['a.a', 'a.b', 'a.c'], '*.!(*a)')); + * //=> ['a.b', 'a.c'] + * ``` + * @param {Array} `list` Array of strings to match + * @param {String} `pattern` Extglob pattern + * @param {Object} `options` + * @return {Array} Returns an array of matches + * @api public + */ + +extglob.match = function(list, pattern, options) { + if (typeof pattern !== 'string') { + throw new TypeError('expected pattern to be a string'); + } + + list = utils.arrayify(list); + var isMatch = extglob.matcher(pattern, options); + var len = list.length; + var idx = -1; + var matches = []; + + while (++idx < len) { + var ele = list[idx]; + + if (isMatch(ele)) { + matches.push(ele); + } + } + + // if no options were passed, uniquify results and return + if (typeof options === 'undefined') { + return unique(matches); + } + + if (matches.length === 0) { + if (options.failglob === true) { + throw new Error('no matches found for "' + pattern + '"'); + } + if (options.nonull === true || options.nullglob === true) { + return [pattern.split('\\').join('')]; + } + } + + return options.nodupes !== false ? unique(matches) : matches; +}; + +/** + * Returns true if the specified `string` matches the given + * extglob `pattern`. + * + * ```js + * var extglob = require('extglob'); + * + * console.log(extglob.isMatch('a.a', '*.!(*a)')); + * //=> false + * console.log(extglob.isMatch('a.b', '*.!(*a)')); + * //=> true + * ``` + * @param {String} `string` String to match + * @param {String} `pattern` Extglob pattern + * @param {String} `options` + * @return {Boolean} + * @api public + */ + +extglob.isMatch = function(str, pattern, options) { + if (typeof pattern !== 'string') { + throw new TypeError('expected pattern to be a string'); + } + + if (typeof str !== 'string') { + throw new TypeError('expected a string'); + } + + if (pattern === str) { + return true; + } + + if (pattern === '' || pattern === ' ' || pattern === '.') { + return pattern === str; + } + + var isMatch = utils.memoize('isMatch', pattern, options, extglob.matcher); + return isMatch(str); +}; + +/** + * Returns true if the given `string` contains the given pattern. Similar to `.isMatch` but + * the pattern can match any part of the string. + * + * ```js + * var extglob = require('extglob'); + * console.log(extglob.contains('aa/bb/cc', '*b')); + * //=> true + * console.log(extglob.contains('aa/bb/cc', '*d')); + * //=> false + * ``` + * @param {String} `str` The string to match. + * @param {String} `pattern` Glob pattern to use for matching. + * @param {Object} `options` + * @return {Boolean} Returns true if the patter matches any part of `str`. + * @api public + */ + +extglob.contains = function(str, pattern, options) { + if (typeof str !== 'string') { + throw new TypeError('expected a string'); + } + + if (pattern === '' || pattern === ' ' || pattern === '.') { + return pattern === str; + } + + var opts = extend({}, options, {contains: true}); + opts.strictClose = false; + opts.strictOpen = false; + return extglob.isMatch(str, pattern, opts); +}; + +/** + * Takes an extglob pattern and returns a matcher function. The returned + * function takes the string to match as its only argument. + * + * ```js + * var extglob = require('extglob'); + * var isMatch = extglob.matcher('*.!(*a)'); + * + * console.log(isMatch('a.a')); + * //=> false + * console.log(isMatch('a.b')); + * //=> true + * ``` + * @param {String} `pattern` Extglob pattern + * @param {String} `options` + * @return {Boolean} + * @api public + */ + +extglob.matcher = function(pattern, options) { + if (typeof pattern !== 'string') { + throw new TypeError('expected pattern to be a string'); + } + + function matcher() { + var re = extglob.makeRe(pattern, options); + return function(str) { + return re.test(str); + }; + } + + return utils.memoize('matcher', pattern, options, matcher); +}; + +/** + * Convert the given `extglob` pattern into a regex-compatible string. Returns + * an object with the compiled result and the parsed AST. + * + * ```js + * var extglob = require('extglob'); + * console.log(extglob.create('*.!(*a)').output); + * //=> '(?!\\.)[^/]*?\\.(?!(?!\\.)[^/]*?a\\b).*?' + * ``` + * @param {String} `str` + * @param {Object} `options` + * @return {String} + * @api public + */ + +extglob.create = function(pattern, options) { + if (typeof pattern !== 'string') { + throw new TypeError('expected pattern to be a string'); + } + + function create() { + var ext = new Extglob(options); + var ast = ext.parse(pattern, options); + return ext.compile(ast, options); + } + + return utils.memoize('create', pattern, options, create); +}; + +/** + * Returns an array of matches captured by `pattern` in `string`, or `null` + * if the pattern did not match. + * + * ```js + * var extglob = require('extglob'); + * extglob.capture(pattern, string[, options]); + * + * console.log(extglob.capture('test/*.js', 'test/foo.js')); + * //=> ['foo'] + * console.log(extglob.capture('test/*.js', 'foo/bar.css')); + * //=> null + * ``` + * @param {String} `pattern` Glob pattern to use for matching. + * @param {String} `string` String to match + * @param {Object} `options` See available [options](#options) for changing how matches are performed + * @return {Boolean} Returns an array of captures if the string matches the glob pattern, otherwise `null`. + * @api public + */ + +extglob.capture = function(pattern, str, options) { + var re = extglob.makeRe(pattern, extend({capture: true}, options)); + + function match() { + return function(string) { + var match = re.exec(string); + if (!match) { + return null; + } + + return match.slice(1); + }; + } + + var capture = utils.memoize('capture', pattern, options, match); + return capture(str); +}; + +/** + * Create a regular expression from the given `pattern` and `options`. + * + * ```js + * var extglob = require('extglob'); + * var re = extglob.makeRe('*.!(*a)'); + * console.log(re); + * //=> /^[^\/]*?\.(?![^\/]*?a)[^\/]*?$/ + * ``` + * @param {String} `pattern` The pattern to convert to regex. + * @param {Object} `options` + * @return {RegExp} + * @api public + */ + +extglob.makeRe = function(pattern, options) { + if (pattern instanceof RegExp) { + return pattern; + } + + if (typeof pattern !== 'string') { + throw new TypeError('expected pattern to be a string'); + } + + if (pattern.length > MAX_LENGTH) { + throw new Error('expected pattern to be less than ' + MAX_LENGTH + ' characters'); + } + + function makeRe() { + var opts = extend({strictErrors: false}, options); + if (opts.strictErrors === true) opts.strict = true; + var res = extglob.create(pattern, opts); + return toRegex(res.output, opts); + } + + var regex = utils.memoize('makeRe', pattern, options, makeRe); + if (regex.source.length > MAX_LENGTH) { + throw new SyntaxError('potentially malicious regex detected'); + } + + return regex; +}; + +/** + * Cache + */ + +extglob.cache = utils.cache; +extglob.clearCache = function() { + extglob.cache.__data__ = {}; +}; + +/** + * Expose `Extglob` constructor, parsers and compilers + */ + +extglob.Extglob = Extglob; +extglob.compilers = compilers; +extglob.parsers = parsers; + +/** + * Expose `extglob` + * @type {Function} + */ + +module.exports = extglob; + + +/***/ }), +/* 850 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var brackets = __webpack_require__(851); + +/** + * Extglob compilers + */ + +module.exports = function(extglob) { + function star() { + if (typeof extglob.options.star === 'function') { + return extglob.options.star.apply(this, arguments); + } + if (typeof extglob.options.star === 'string') { + return extglob.options.star; + } + return '.*?'; + } + + /** + * Use `expand-brackets` compilers + */ + + extglob.use(brackets.compilers); + extglob.compiler + + /** + * Escaped: "\\*" + */ + + .set('escape', function(node) { + return this.emit(node.val, node); + }) + + /** + * Dot: "." + */ + + .set('dot', function(node) { + return this.emit('\\' + node.val, node); + }) + + /** + * Question mark: "?" + */ + + .set('qmark', function(node) { + var val = '[^\\\\/.]'; + var prev = this.prev(); + + if (node.parsed.slice(-1) === '(') { + var ch = node.rest.charAt(0); + if (ch !== '!' && ch !== '=' && ch !== ':') { + return this.emit(val, node); + } + return this.emit(node.val, node); + } + + if (prev.type === 'text' && prev.val) { + return this.emit(val, node); + } + + if (node.val.length > 1) { + val += '{' + node.val.length + '}'; + } + return this.emit(val, node); + }) + + /** + * Plus: "+" + */ + + .set('plus', function(node) { + var prev = node.parsed.slice(-1); + if (prev === ']' || prev === ')') { + return this.emit(node.val, node); + } + var ch = this.output.slice(-1); + if (!this.output || (/[?*+]/.test(ch) && node.parent.type !== 'bracket')) { + return this.emit('\\+', node); + } + if (/\w/.test(ch) && !node.inside) { + return this.emit('+\\+?', node); + } + return this.emit('+', node); + }) + + /** + * Star: "*" + */ + + .set('star', function(node) { + var prev = this.prev(); + var prefix = prev.type !== 'text' && prev.type !== 'escape' + ? '(?!\\.)' + : ''; + + return this.emit(prefix + star.call(this, node), node); + }) + + /** + * Parens + */ + + .set('paren', function(node) { + return this.mapVisit(node.nodes); + }) + .set('paren.open', function(node) { + var capture = this.options.capture ? '(' : ''; + + switch (node.parent.prefix) { + case '!': + case '^': + return this.emit(capture + '(?:(?!(?:', node); + case '*': + case '+': + case '?': + case '@': + return this.emit(capture + '(?:', node); + default: { + var val = node.val; + if (this.options.bash === true) { + val = '\\' + val; + } else if (!this.options.capture && val === '(' && node.parent.rest[0] !== '?') { + val += '?:'; + } + + return this.emit(val, node); + } + } + }) + .set('paren.close', function(node) { + var capture = this.options.capture ? ')' : ''; + + switch (node.prefix) { + case '!': + case '^': + var prefix = /^(\)|$)/.test(node.rest) ? '$' : ''; + var str = star.call(this, node); + + // if the extglob has a slash explicitly defined, we know the user wants + // to match slashes, so we need to ensure the "star" regex allows for it + if (node.parent.hasSlash && !this.options.star && this.options.slash !== false) { + str = '.*?'; + } -function isBuffer(val) { - if (val.constructor && typeof val.constructor.isBuffer === 'function') { - return val.constructor.isBuffer(val); - } - return false; -} + return this.emit(prefix + ('))' + str + ')') + capture, node); + case '*': + case '+': + case '?': + return this.emit(')' + node.prefix + capture, node); + case '@': + return this.emit(')' + capture, node); + default: { + var val = (this.options.bash === true ? '\\' : '') + ')'; + return this.emit(val, node); + } + } + }) + + /** + * Text + */ + + .set('text', function(node) { + var val = node.val.replace(/[\[\]]/g, '\\$&'); + return this.emit(val, node); + }); +}; /***/ }), -/* 631 */ +/* 851 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; /** - * Module dependencies + * Local dependencies */ -var extend = __webpack_require__(511); -var unique = __webpack_require__(514); -var toRegex = __webpack_require__(502); +var compilers = __webpack_require__(852); +var parsers = __webpack_require__(854); /** - * Local dependencies + * Module dependencies */ -var compilers = __webpack_require__(632); -var parsers = __webpack_require__(638); -var Extglob = __webpack_require__(641); -var utils = __webpack_require__(640); -var MAX_LENGTH = 1024 * 64; +var debug = __webpack_require__(856)('expand-brackets'); +var extend = __webpack_require__(730); +var Snapdragon = __webpack_require__(760); +var toRegex = __webpack_require__(721); /** - * Convert the given `extglob` pattern into a regex-compatible string. Returns - * an object with the compiled result and the parsed AST. + * Parses the given POSIX character class `pattern` and returns a + * string that can be used for creating regular expressions for matching. * - * ```js - * var extglob = require('extglob'); - * console.log(extglob('*.!(*a)')); - * //=> '(?!\\.)[^/]*?\\.(?!(?!\\.)[^/]*?a\\b).*?' - * ``` * @param {String} `pattern` * @param {Object} `options` - * @return {String} + * @return {Object} * @api public */ -function extglob(pattern, options) { - return extglob.create(pattern, options).output; +function brackets(pattern, options) { + debug('initializing from <%s>', __filename); + var res = brackets.create(pattern, options); + return res.output; } /** - * Takes an array of strings and an extglob pattern and returns a new - * array that contains only the strings that match the pattern. + * Takes an array of strings and a POSIX character class pattern, and returns a new + * array with only the strings that matched the pattern. * * ```js - * var extglob = require('extglob'); - * console.log(extglob.match(['a.a', 'a.b', 'a.c'], '*.!(*a)')); - * //=> ['a.b', 'a.c'] + * var brackets = require('expand-brackets'); + * console.log(brackets.match(['1', 'a', 'ab'], '[[:alpha:]]')); + * //=> ['a'] + * + * console.log(brackets.match(['1', 'a', 'ab'], '[[:alpha:]]+')); + * //=> ['a', 'ab'] * ``` - * @param {Array} `list` Array of strings to match - * @param {String} `pattern` Extglob pattern + * @param {Array} `arr` Array of strings to match + * @param {String} `pattern` POSIX character class pattern(s) * @param {Object} `options` - * @return {Array} Returns an array of matches + * @return {Array} * @api public */ -extglob.match = function(list, pattern, options) { - if (typeof pattern !== 'string') { - throw new TypeError('expected pattern to be a string'); - } - - list = utils.arrayify(list); - var isMatch = extglob.matcher(pattern, options); - var len = list.length; +brackets.match = function(arr, pattern, options) { + arr = [].concat(arr); + var opts = extend({}, options); + var isMatch = brackets.matcher(pattern, opts); + var len = arr.length; var idx = -1; - var matches = []; + var res = []; while (++idx < len) { - var ele = list[idx]; - + var ele = arr[idx]; if (isMatch(ele)) { - matches.push(ele); + res.push(ele); } } - // if no options were passed, uniquify results and return - if (typeof options === 'undefined') { - return unique(matches); - } - - if (matches.length === 0) { - if (options.failglob === true) { + if (res.length === 0) { + if (opts.failglob === true) { throw new Error('no matches found for "' + pattern + '"'); } - if (options.nonull === true || options.nullglob === true) { + + if (opts.nonull === true || opts.nullglob === true) { return [pattern.split('\\').join('')]; } } - - return options.nodupes !== false ? unique(matches) : matches; + return res; }; /** * Returns true if the specified `string` matches the given - * extglob `pattern`. + * brackets `pattern`. * * ```js - * var extglob = require('extglob'); + * var brackets = require('expand-brackets'); * - * console.log(extglob.isMatch('a.a', '*.!(*a)')); - * //=> false - * console.log(extglob.isMatch('a.b', '*.!(*a)')); + * console.log(brackets.isMatch('a.a', '[[:alpha:]].[[:alpha:]]')); * //=> true + * console.log(brackets.isMatch('1.2', '[[:alpha:]].[[:alpha:]]')); + * //=> false * ``` * @param {String} `string` String to match - * @param {String} `pattern` Extglob pattern + * @param {String} `pattern` Poxis pattern * @param {String} `options` * @return {Boolean} * @api public */ -extglob.isMatch = function(str, pattern, options) { - if (typeof pattern !== 'string') { - throw new TypeError('expected pattern to be a string'); - } +brackets.isMatch = function(str, pattern, options) { + return brackets.matcher(pattern, options)(str); +}; - if (typeof str !== 'string') { - throw new TypeError('expected a string'); - } +/** + * Takes a POSIX character class pattern and returns a matcher function. The returned + * function takes the string to match as its only argument. + * + * ```js + * var brackets = require('expand-brackets'); + * var isMatch = brackets.matcher('[[:lower:]].[[:upper:]]'); + * + * console.log(isMatch('a.a')); + * //=> false + * console.log(isMatch('a.A')); + * //=> true + * ``` + * @param {String} `pattern` Poxis pattern + * @param {String} `options` + * @return {Boolean} + * @api public + */ - if (pattern === str) { - return true; - } +brackets.matcher = function(pattern, options) { + var re = brackets.makeRe(pattern, options); + return function(str) { + return re.test(str); + }; +}; - if (pattern === '' || pattern === ' ' || pattern === '.') { - return pattern === str; - } +/** + * Create a regular expression from the given `pattern`. + * + * ```js + * var brackets = require('expand-brackets'); + * var re = brackets.makeRe('[[:alpha:]]'); + * console.log(re); + * //=> /^(?:[a-zA-Z])$/ + * ``` + * @param {String} `pattern` The pattern to convert to regex. + * @param {Object} `options` + * @return {RegExp} + * @api public + */ - var isMatch = utils.memoize('isMatch', pattern, options, extglob.matcher); - return isMatch(str); +brackets.makeRe = function(pattern, options) { + var res = brackets.create(pattern, options); + var opts = extend({strictErrors: false}, options); + return toRegex(res.output, opts); }; /** - * Returns true if the given `string` contains the given pattern. Similar to `.isMatch` but - * the pattern can match any part of the string. + * Parses the given POSIX character class `pattern` and returns an object + * with the compiled `output` and optional source `map`. * * ```js - * var extglob = require('extglob'); - * console.log(extglob.contains('aa/bb/cc', '*b')); - * //=> true - * console.log(extglob.contains('aa/bb/cc', '*d')); - * //=> false + * var brackets = require('expand-brackets'); + * console.log(brackets('[[:alpha:]]')); + * // { options: { source: 'string' }, + * // input: '[[:alpha:]]', + * // state: {}, + * // compilers: + * // { eos: [Function], + * // noop: [Function], + * // bos: [Function], + * // not: [Function], + * // escape: [Function], + * // text: [Function], + * // posix: [Function], + * // bracket: [Function], + * // 'bracket.open': [Function], + * // 'bracket.inner': [Function], + * // 'bracket.literal': [Function], + * // 'bracket.close': [Function] }, + * // output: '[a-zA-Z]', + * // ast: + * // { type: 'root', + * // errors: [], + * // nodes: [ [Object], [Object], [Object] ] }, + * // parsingErrors: [] } * ``` - * @param {String} `str` The string to match. - * @param {String} `pattern` Glob pattern to use for matching. + * @param {String} `pattern` * @param {Object} `options` - * @return {Boolean} Returns true if the patter matches any part of `str`. + * @return {Object} * @api public */ -extglob.contains = function(str, pattern, options) { - if (typeof str !== 'string') { - throw new TypeError('expected a string'); - } +brackets.create = function(pattern, options) { + var snapdragon = (options && options.snapdragon) || new Snapdragon(options); + compilers(snapdragon); + parsers(snapdragon); + + var ast = snapdragon.parse(pattern, options); + ast.input = pattern; + var res = snapdragon.compile(ast, options); + res.input = pattern; + return res; +}; + +/** + * Expose `brackets` constructor, parsers and compilers + */ + +brackets.compilers = compilers; +brackets.parsers = parsers; + +/** + * Expose `brackets` + * @type {Function} + */ + +module.exports = brackets; + + +/***/ }), +/* 852 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var posix = __webpack_require__(853); + +module.exports = function(brackets) { + brackets.compiler + + /** + * Escaped characters + */ + + .set('escape', function(node) { + return this.emit('\\' + node.val.replace(/^\\/, ''), node); + }) + + /** + * Text + */ + + .set('text', function(node) { + return this.emit(node.val.replace(/([{}])/g, '\\$1'), node); + }) + + /** + * POSIX character classes + */ + + .set('posix', function(node) { + if (node.val === '[::]') { + return this.emit('\\[::\\]', node); + } + + var val = posix[node.inner]; + if (typeof val === 'undefined') { + val = '[' + node.inner + ']'; + } + return this.emit(val, node); + }) + + /** + * Non-posix brackets + */ + + .set('bracket', function(node) { + return this.mapVisit(node.nodes); + }) + .set('bracket.open', function(node) { + return this.emit(node.val, node); + }) + .set('bracket.inner', function(node) { + var inner = node.val; + + if (inner === '[' || inner === ']') { + return this.emit('\\' + node.val, node); + } + if (inner === '^]') { + return this.emit('^\\]', node); + } + if (inner === '^') { + return this.emit('^', node); + } + + if (/-/.test(inner) && !/(\d-\d|\w-\w)/.test(inner)) { + inner = inner.split('-').join('\\-'); + } + + var isNegated = inner.charAt(0) === '^'; + // add slashes to negated brackets, per spec + if (isNegated && inner.indexOf('/') === -1) { + inner += '/'; + } + if (isNegated && inner.indexOf('.') === -1) { + inner += '.'; + } + + // don't unescape `0` (octal literal) + inner = inner.replace(/\\([1-9])/g, '$1'); + return this.emit(inner, node); + }) + .set('bracket.close', function(node) { + var val = node.val.replace(/^\\/, ''); + if (node.parent.escaped === true) { + return this.emit('\\' + val, node); + } + return this.emit(val, node); + }); +}; + + +/***/ }), +/* 853 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +/** + * POSIX character classes + */ + +module.exports = { + alnum: 'a-zA-Z0-9', + alpha: 'a-zA-Z', + ascii: '\\x00-\\x7F', + blank: ' \\t', + cntrl: '\\x00-\\x1F\\x7F', + digit: '0-9', + graph: '\\x21-\\x7E', + lower: 'a-z', + print: '\\x20-\\x7E ', + punct: '\\-!"#$%&\'()\\*+,./:;<=>?@[\\]^_`{|}~', + space: ' \\t\\r\\n\\v\\f', + upper: 'A-Z', + word: 'A-Za-z0-9_', + xdigit: 'A-Fa-f0-9' +}; + + +/***/ }), +/* 854 */ +/***/ (function(module, exports, __webpack_require__) { + +"use strict"; + + +var utils = __webpack_require__(855); +var define = __webpack_require__(722); + +/** + * Text regex + */ + +var TEXT_REGEX = '(\\[(?=.*\\])|\\])+'; +var not = utils.createRegex(TEXT_REGEX); + +/** + * Brackets parsers + */ + +function parsers(brackets) { + brackets.state = brackets.state || {}; + brackets.parser.sets.bracket = brackets.parser.sets.bracket || []; + brackets.parser + + .capture('escape', function() { + if (this.isInside('bracket')) return; + var pos = this.position(); + var m = this.match(/^\\(.)/); + if (!m) return; + + return pos({ + type: 'escape', + val: m[0] + }); + }) + + /** + * Text parser + */ + + .capture('text', function() { + if (this.isInside('bracket')) return; + var pos = this.position(); + var m = this.match(not); + if (!m || !m[0]) return; + + return pos({ + type: 'text', + val: m[0] + }); + }) + + /** + * POSIX character classes: "[[:alpha:][:digits:]]" + */ + + .capture('posix', function() { + var pos = this.position(); + var m = this.match(/^\[:(.*?):\](?=.*\])/); + if (!m) return; + + var inside = this.isInside('bracket'); + if (inside) { + brackets.posix++; + } + + return pos({ + type: 'posix', + insideBracket: inside, + inner: m[1], + val: m[0] + }); + }) + + /** + * Bracket (noop) + */ + + .capture('bracket', function() {}) + + /** + * Open: '[' + */ + + .capture('bracket.open', function() { + var parsed = this.parsed; + var pos = this.position(); + var m = this.match(/^\[(?=.*\])/); + if (!m) return; + + var prev = this.prev(); + var last = utils.last(prev.nodes); + + if (parsed.slice(-1) === '\\' && !this.isInside('bracket')) { + last.val = last.val.slice(0, last.val.length - 1); + return pos({ + type: 'escape', + val: m[0] + }); + } + + var open = pos({ + type: 'bracket.open', + val: m[0] + }); + + if (last.type === 'bracket.open' || this.isInside('bracket')) { + open.val = '\\' + open.val; + open.type = 'bracket.inner'; + open.escaped = true; + return open; + } + + var node = pos({ + type: 'bracket', + nodes: [open] + }); + + define(node, 'parent', prev); + define(open, 'parent', node); + this.push('bracket', node); + prev.nodes.push(node); + }) + + /** + * Bracket text + */ + + .capture('bracket.inner', function() { + if (!this.isInside('bracket')) return; + var pos = this.position(); + var m = this.match(not); + if (!m || !m[0]) return; + + var next = this.input.charAt(0); + var val = m[0]; + + var node = pos({ + type: 'bracket.inner', + val: val + }); - if (pattern === '' || pattern === ' ' || pattern === '.') { - return pattern === str; - } + if (val === '\\\\') { + return node; + } - var opts = extend({}, options, {contains: true}); - opts.strictClose = false; - opts.strictOpen = false; - return extglob.isMatch(str, pattern, opts); -}; + var first = val.charAt(0); + var last = val.slice(-1); -/** - * Takes an extglob pattern and returns a matcher function. The returned - * function takes the string to match as its only argument. - * - * ```js - * var extglob = require('extglob'); - * var isMatch = extglob.matcher('*.!(*a)'); - * - * console.log(isMatch('a.a')); - * //=> false - * console.log(isMatch('a.b')); - * //=> true - * ``` - * @param {String} `pattern` Extglob pattern - * @param {String} `options` - * @return {Boolean} - * @api public - */ + if (first === '!') { + val = '^' + val.slice(1); + } -extglob.matcher = function(pattern, options) { - if (typeof pattern !== 'string') { - throw new TypeError('expected pattern to be a string'); - } + if (last === '\\' || (val === '^' && next === ']')) { + val += this.input[0]; + this.consume(1); + } - function matcher() { - var re = extglob.makeRe(pattern, options); - return function(str) { - return re.test(str); - }; - } + node.val = val; + return node; + }) - return utils.memoize('matcher', pattern, options, matcher); -}; + /** + * Close: ']' + */ -/** - * Convert the given `extglob` pattern into a regex-compatible string. Returns - * an object with the compiled result and the parsed AST. - * - * ```js - * var extglob = require('extglob'); - * console.log(extglob.create('*.!(*a)').output); - * //=> '(?!\\.)[^/]*?\\.(?!(?!\\.)[^/]*?a\\b).*?' - * ``` - * @param {String} `str` - * @param {Object} `options` - * @return {String} - * @api public - */ + .capture('bracket.close', function() { + var parsed = this.parsed; + var pos = this.position(); + var m = this.match(/^\]/); + if (!m) return; -extglob.create = function(pattern, options) { - if (typeof pattern !== 'string') { - throw new TypeError('expected pattern to be a string'); - } + var prev = this.prev(); + var last = utils.last(prev.nodes); - function create() { - var ext = new Extglob(options); - var ast = ext.parse(pattern, options); - return ext.compile(ast, options); - } + if (parsed.slice(-1) === '\\' && !this.isInside('bracket')) { + last.val = last.val.slice(0, last.val.length - 1); - return utils.memoize('create', pattern, options, create); -}; + return pos({ + type: 'escape', + val: m[0] + }); + } -/** - * Returns an array of matches captured by `pattern` in `string`, or `null` - * if the pattern did not match. - * - * ```js - * var extglob = require('extglob'); - * extglob.capture(pattern, string[, options]); - * - * console.log(extglob.capture('test/*.js', 'test/foo.js')); - * //=> ['foo'] - * console.log(extglob.capture('test/*.js', 'foo/bar.css')); - * //=> null - * ``` - * @param {String} `pattern` Glob pattern to use for matching. - * @param {String} `string` String to match - * @param {Object} `options` See available [options](#options) for changing how matches are performed - * @return {Boolean} Returns an array of captures if the string matches the glob pattern, otherwise `null`. - * @api public - */ + var node = pos({ + type: 'bracket.close', + rest: this.input, + val: m[0] + }); -extglob.capture = function(pattern, str, options) { - var re = extglob.makeRe(pattern, extend({capture: true}, options)); + if (last.type === 'bracket.open') { + node.type = 'bracket.inner'; + node.escaped = true; + return node; + } - function match() { - return function(string) { - var match = re.exec(string); - if (!match) { - return null; + var bracket = this.pop('bracket'); + if (!this.isType(bracket, 'bracket')) { + if (this.options.strict) { + throw new Error('missing opening "["'); + } + node.type = 'bracket.inner'; + node.escaped = true; + return node; } - return match.slice(1); - }; - } + bracket.nodes.push(node); + define(node, 'parent', bracket); + }); +} - var capture = utils.memoize('capture', pattern, options, match); - return capture(str); -}; +/** + * Brackets parsers + */ + +module.exports = parsers; /** - * Create a regular expression from the given `pattern` and `options`. - * - * ```js - * var extglob = require('extglob'); - * var re = extglob.makeRe('*.!(*a)'); - * console.log(re); - * //=> /^[^\/]*?\.(?![^\/]*?a)[^\/]*?$/ - * ``` - * @param {String} `pattern` The pattern to convert to regex. - * @param {Object} `options` - * @return {RegExp} - * @api public + * Expose text regex */ -extglob.makeRe = function(pattern, options) { - if (pattern instanceof RegExp) { - return pattern; - } +module.exports.TEXT_REGEX = TEXT_REGEX; - if (typeof pattern !== 'string') { - throw new TypeError('expected pattern to be a string'); - } - if (pattern.length > MAX_LENGTH) { - throw new Error('expected pattern to be less than ' + MAX_LENGTH + ' characters'); - } +/***/ }), +/* 855 */ +/***/ (function(module, exports, __webpack_require__) { - function makeRe() { - var opts = extend({strictErrors: false}, options); - if (opts.strictErrors === true) opts.strict = true; - var res = extglob.create(pattern, opts); - return toRegex(res.output, opts); - } +"use strict"; - var regex = utils.memoize('makeRe', pattern, options, makeRe); - if (regex.source.length > MAX_LENGTH) { - throw new SyntaxError('potentially malicious regex detected'); - } - return regex; -}; +var toRegex = __webpack_require__(721); +var regexNot = __webpack_require__(732); +var cached; /** - * Cache + * Get the last element from `array` + * @param {Array} `array` + * @return {*} */ -extglob.cache = utils.cache; -extglob.clearCache = function() { - extglob.cache.__data__ = {}; +exports.last = function(arr) { + return arr[arr.length - 1]; }; /** - * Expose `Extglob` constructor, parsers and compilers + * Create and cache regex to use for text nodes */ -extglob.Extglob = Extglob; -extglob.compilers = compilers; -extglob.parsers = parsers; +exports.createRegex = function(pattern, include) { + if (cached) return cached; + var opts = {contains: true, strictClose: false}; + var not = regexNot.create(pattern, opts); + var re; + + if (typeof include === 'string') { + re = toRegex('^(?:' + include + '|' + not + ')', opts); + } else { + re = toRegex(not, opts); + } + + return (cached = re); +}; + + +/***/ }), +/* 856 */ +/***/ (function(module, exports, __webpack_require__) { /** - * Expose `extglob` - * @type {Function} + * Detect Electron renderer process, which is node, but we should + * treat as a browser. */ -module.exports = extglob; +if (typeof process !== 'undefined' && process.type === 'renderer') { + module.exports = __webpack_require__(857); +} else { + module.exports = __webpack_require__(860); +} /***/ }), -/* 632 */ +/* 857 */ /***/ (function(module, exports, __webpack_require__) { -"use strict"; - +/** + * This is the web browser implementation of `debug()`. + * + * Expose `debug()` as the module. + */ -var brackets = __webpack_require__(633); +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(); /** - * Extglob compilers + * Colors. */ -module.exports = function(extglob) { - function star() { - if (typeof extglob.options.star === 'function') { - return extglob.options.star.apply(this, arguments); - } - if (typeof extglob.options.star === 'string') { - return extglob.options.star; - } - return '.*?'; - } +exports.colors = [ + 'lightseagreen', + 'forestgreen', + 'goldenrod', + 'dodgerblue', + 'darkorchid', + 'crimson' +]; - /** - * Use `expand-brackets` compilers - */ +/** + * 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 + */ - extglob.use(brackets.compilers); - extglob.compiler +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; + } - /** - * Escaped: "\\*" - */ + // 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+)/)); +} - .set('escape', function(node) { - return this.emit(node.val, node); - }) +/** + * Map %j to `JSON.stringify()`, since no Web Inspectors do that by default. + */ - /** - * Dot: "." - */ +exports.formatters.j = function(v) { + try { + return JSON.stringify(v); + } catch (err) { + return '[UnexpectedJSONParseError]: ' + err.message; + } +}; - .set('dot', function(node) { - return this.emit('\\' + node.val, node); - }) - /** - * Question mark: "?" - */ +/** + * Colorize log arguments if enabled. + * + * @api public + */ - .set('qmark', function(node) { - var val = '[^\\\\/.]'; - var prev = this.prev(); +function formatArgs(args) { + var useColors = this.useColors; - if (node.parsed.slice(-1) === '(') { - var ch = node.rest.charAt(0); - if (ch !== '!' && ch !== '=' && ch !== ':') { - return this.emit(val, node); - } - return this.emit(node.val, node); - } + args[0] = (useColors ? '%c' : '') + + this.namespace + + (useColors ? ' %c' : ' ') + + args[0] + + (useColors ? '%c ' : ' ') + + '+' + exports.humanize(this.diff); - if (prev.type === 'text' && prev.val) { - return this.emit(val, node); - } + if (!useColors) return; - if (node.val.length > 1) { - val += '{' + node.val.length + '}'; - } - return this.emit(val, node); - }) + var c = 'color: ' + this.color; + args.splice(1, 0, c, 'color: inherit') - /** - * Plus: "+" - */ + // 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; + } + }); - .set('plus', function(node) { - var prev = node.parsed.slice(-1); - if (prev === ']' || prev === ')') { - return this.emit(node.val, node); - } - var ch = this.output.slice(-1); - if (!this.output || (/[?*+]/.test(ch) && node.parent.type !== 'bracket')) { - return this.emit('\\+', node); - } - if (/\w/.test(ch) && !node.inside) { - return this.emit('+\\+?', node); - } - return this.emit('+', node); - }) + args.splice(lastC, 0, c); +} - /** - * Star: "*" - */ +/** + * Invokes `console.log()` when available. + * No-op when `console.log` is not a "function". + * + * @api public + */ - .set('star', function(node) { - var prev = this.prev(); - var prefix = prev.type !== 'text' && prev.type !== 'escape' - ? '(?!\\.)' - : ''; +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); +} - return this.emit(prefix + star.call(this, node), node); - }) +/** + * Save `namespaces`. + * + * @param {String} namespaces + * @api private + */ - /** - * Parens - */ +function save(namespaces) { + try { + if (null == namespaces) { + exports.storage.removeItem('debug'); + } else { + exports.storage.debug = namespaces; + } + } catch(e) {} +} - .set('paren', function(node) { - return this.mapVisit(node.nodes); - }) - .set('paren.open', function(node) { - var capture = this.options.capture ? '(' : ''; +/** + * Load `namespaces`. + * + * @return {String} returns the previously persisted debug modes + * @api private + */ - switch (node.parent.prefix) { - case '!': - case '^': - return this.emit(capture + '(?:(?!(?:', node); - case '*': - case '+': - case '?': - case '@': - return this.emit(capture + '(?:', node); - default: { - var val = node.val; - if (this.options.bash === true) { - val = '\\' + val; - } else if (!this.options.capture && val === '(' && node.parent.rest[0] !== '?') { - val += '?:'; - } +function load() { + var r; + try { + r = exports.storage.debug; + } catch(e) {} - return this.emit(val, node); - } - } - }) - .set('paren.close', function(node) { - var capture = this.options.capture ? ')' : ''; + // 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; + } - switch (node.prefix) { - case '!': - case '^': - var prefix = /^(\)|$)/.test(node.rest) ? '$' : ''; - var str = star.call(this, node); + return r; +} - // if the extglob has a slash explicitly defined, we know the user wants - // to match slashes, so we need to ensure the "star" regex allows for it - if (node.parent.hasSlash && !this.options.star && this.options.slash !== false) { - str = '.*?'; - } +/** + * Enable namespaces listed in `localStorage.debug` initially. + */ - return this.emit(prefix + ('))' + str + ')') + capture, node); - case '*': - case '+': - case '?': - return this.emit(')' + node.prefix + capture, node); - case '@': - return this.emit(')' + capture, node); - default: { - var val = (this.options.bash === true ? '\\' : '') + ')'; - return this.emit(val, node); - } - } - }) +exports.enable(load()); - /** - * Text - */ +/** + * 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 + */ - .set('text', function(node) { - var val = node.val.replace(/[\[\]]/g, '\\$&'); - return this.emit(val, node); - }); -}; +function localstorage() { + try { + return window.localStorage; + } catch (e) {} +} /***/ }), -/* 633 */ +/* 858 */ /***/ (function(module, exports, __webpack_require__) { -"use strict"; - /** - * Local dependencies + * This is the common logic for both the Node.js and web browser + * implementations of `debug()`. + * + * Expose `debug()` as the module. */ -var compilers = __webpack_require__(634); -var parsers = __webpack_require__(636); +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); /** - * Module dependencies + * The currently active debug mode names, and names to skip. */ -var debug = __webpack_require__(574)('expand-brackets'); -var extend = __webpack_require__(511); -var Snapdragon = __webpack_require__(541); -var toRegex = __webpack_require__(502); +exports.names = []; +exports.skips = []; /** - * Parses the given POSIX character class `pattern` and returns a - * string that can be used for creating regular expressions for matching. + * Map of special "%n" handling functions, for the debug "format" argument. * - * @param {String} `pattern` - * @param {Object} `options` - * @return {Object} - * @api public + * Valid key names are a single, lower or upper-case letter, i.e. "n" and "N". */ -function brackets(pattern, options) { - debug('initializing from <%s>', __filename); - var res = brackets.create(pattern, options); - return res.output; +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]; } /** - * Takes an array of strings and a POSIX character class pattern, and returns a new - * array with only the strings that matched the pattern. - * - * ```js - * var brackets = require('expand-brackets'); - * console.log(brackets.match(['1', 'a', 'ab'], '[[:alpha:]]')); - * //=> ['a'] + * Create a debugger with the given `namespace`. * - * console.log(brackets.match(['1', 'a', 'ab'], '[[:alpha:]]+')); - * //=> ['a', 'ab'] - * ``` - * @param {Array} `arr` Array of strings to match - * @param {String} `pattern` POSIX character class pattern(s) - * @param {Object} `options` - * @return {Array} + * @param {String} namespace + * @return {Function} * @api public */ -brackets.match = function(arr, pattern, options) { - arr = [].concat(arr); - var opts = extend({}, options); - var isMatch = brackets.matcher(pattern, opts); - var len = arr.length; - var idx = -1; - var res = []; +function createDebug(namespace) { - while (++idx < len) { - var ele = arr[idx]; - if (isMatch(ele)) { - res.push(ele); - } - } + function debug() { + // disabled? + if (!debug.enabled) return; - if (res.length === 0) { - if (opts.failglob === true) { - throw new Error('no matches found for "' + pattern + '"'); + 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]; } - if (opts.nonull === true || opts.nullglob === true) { - return [pattern.split('\\').join('')]; + 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); } - return res; -}; + + 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; +} /** - * Returns true if the specified `string` matches the given - * brackets `pattern`. - * - * ```js - * var brackets = require('expand-brackets'); + * Enables a debug mode by namespaces. This can include modes + * separated by a colon and wildcards. * - * console.log(brackets.isMatch('a.a', '[[:alpha:]].[[:alpha:]]')); - * //=> true - * console.log(brackets.isMatch('1.2', '[[:alpha:]].[[:alpha:]]')); - * //=> false - * ``` - * @param {String} `string` String to match - * @param {String} `pattern` Poxis pattern - * @param {String} `options` - * @return {Boolean} + * @param {String} namespaces * @api public */ -brackets.isMatch = function(str, pattern, options) { - return brackets.matcher(pattern, options)(str); -}; +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 + '$')); + } + } +} /** - * Takes a POSIX character class pattern and returns a matcher function. The returned - * function takes the string to match as its only argument. - * - * ```js - * var brackets = require('expand-brackets'); - * var isMatch = brackets.matcher('[[:lower:]].[[:upper:]]'); + * Disable debug output. * - * console.log(isMatch('a.a')); - * //=> false - * console.log(isMatch('a.A')); - * //=> true - * ``` - * @param {String} `pattern` Poxis pattern - * @param {String} `options` - * @return {Boolean} * @api public */ -brackets.matcher = function(pattern, options) { - var re = brackets.makeRe(pattern, options); - return function(str) { - return re.test(str); - }; -}; +function disable() { + exports.enable(''); +} /** - * Create a regular expression from the given `pattern`. + * Returns true if the given mode name is enabled, false otherwise. * - * ```js - * var brackets = require('expand-brackets'); - * var re = brackets.makeRe('[[:alpha:]]'); - * console.log(re); - * //=> /^(?:[a-zA-Z])$/ - * ``` - * @param {String} `pattern` The pattern to convert to regex. - * @param {Object} `options` - * @return {RegExp} + * @param {String} name + * @return {Boolean} * @api public */ -brackets.makeRe = function(pattern, options) { - var res = brackets.create(pattern, options); - var opts = extend({strictErrors: false}, options); - return toRegex(res.output, opts); -}; +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; +} /** - * Parses the given POSIX character class `pattern` and returns an object - * with the compiled `output` and optional source `map`. + * Coerce `val`. * - * ```js - * var brackets = require('expand-brackets'); - * console.log(brackets('[[:alpha:]]')); - * // { options: { source: 'string' }, - * // input: '[[:alpha:]]', - * // state: {}, - * // compilers: - * // { eos: [Function], - * // noop: [Function], - * // bos: [Function], - * // not: [Function], - * // escape: [Function], - * // text: [Function], - * // posix: [Function], - * // bracket: [Function], - * // 'bracket.open': [Function], - * // 'bracket.inner': [Function], - * // 'bracket.literal': [Function], - * // 'bracket.close': [Function] }, - * // output: '[a-zA-Z]', - * // ast: - * // { type: 'root', - * // errors: [], - * // nodes: [ [Object], [Object], [Object] ] }, - * // parsingErrors: [] } - * ``` - * @param {String} `pattern` - * @param {Object} `options` - * @return {Object} - * @api public + * @param {Mixed} val + * @return {Mixed} + * @api private */ -brackets.create = function(pattern, options) { - var snapdragon = (options && options.snapdragon) || new Snapdragon(options); - compilers(snapdragon); - parsers(snapdragon); +function coerce(val) { + if (val instanceof Error) return val.stack || val.message; + return val; +} - var ast = snapdragon.parse(pattern, options); - ast.input = pattern; - var res = snapdragon.compile(ast, options); - res.input = pattern; - return res; -}; + +/***/ }), +/* 859 */ +/***/ (function(module, exports) { /** - * Expose `brackets` constructor, parsers and compilers + * Helpers. */ -brackets.compilers = compilers; -brackets.parsers = parsers; +var s = 1000; +var m = s * 60; +var h = m * 60; +var d = h * 24; +var y = d * 365.25; /** - * Expose `brackets` - * @type {Function} + * 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 = brackets; - - -/***/ }), -/* 634 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -var posix = __webpack_require__(635); - -module.exports = function(brackets) { - brackets.compiler - - /** - * Escaped characters - */ - - .set('escape', function(node) { - return this.emit('\\' + node.val.replace(/^\\/, ''), node); - }) - - /** - * Text - */ - - .set('text', function(node) { - return this.emit(node.val.replace(/([{}])/g, '\\$1'), node); - }) - - /** - * POSIX character classes - */ - - .set('posix', function(node) { - if (node.val === '[::]') { - return this.emit('\\[::\\]', node); - } - - var val = posix[node.inner]; - if (typeof val === 'undefined') { - val = '[' + node.inner + ']'; - } - return this.emit(val, node); - }) - - /** - * Non-posix brackets - */ - - .set('bracket', function(node) { - return this.mapVisit(node.nodes); - }) - .set('bracket.open', function(node) { - return this.emit(node.val, node); - }) - .set('bracket.inner', function(node) { - var inner = node.val; - - if (inner === '[' || inner === ']') { - return this.emit('\\' + node.val, node); - } - if (inner === '^]') { - return this.emit('^\\]', node); - } - if (inner === '^') { - return this.emit('^', node); - } - - if (/-/.test(inner) && !/(\d-\d|\w-\w)/.test(inner)) { - inner = inner.split('-').join('\\-'); - } +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) + ); +}; - var isNegated = inner.charAt(0) === '^'; - // add slashes to negated brackets, per spec - if (isNegated && inner.indexOf('/') === -1) { - inner += '/'; - } - if (isNegated && inner.indexOf('.') === -1) { - inner += '.'; - } +/** + * Parse the given `str` and return milliseconds. + * + * @param {String} str + * @return {Number} + * @api private + */ - // don't unescape `0` (octal literal) - inner = inner.replace(/\\([1-9])/g, '$1'); - return this.emit(inner, node); - }) - .set('bracket.close', function(node) { - var val = node.val.replace(/^\\/, ''); - if (node.parent.escaped === true) { - return this.emit('\\' + val, node); - } - return this.emit(val, node); - }); -}; +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 + */ -/***/ }), -/* 635 */ -/***/ (function(module, exports, __webpack_require__) { +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'; +} -"use strict"; +/** + * 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'; +} /** - * POSIX character classes + * Pluralization helper. */ -module.exports = { - alnum: 'a-zA-Z0-9', - alpha: 'a-zA-Z', - ascii: '\\x00-\\x7F', - blank: ' \\t', - cntrl: '\\x00-\\x1F\\x7F', - digit: '0-9', - graph: '\\x21-\\x7E', - lower: 'a-z', - print: '\\x20-\\x7E ', - punct: '\\-!"#$%&\'()\\*+,./:;<=>?@[\\]^_`{|}~', - space: ' \\t\\r\\n\\v\\f', - upper: 'A-Z', - word: 'A-Za-z0-9_', - xdigit: 'A-Fa-f0-9' -}; +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'; +} /***/ }), -/* 636 */ +/* 860 */ /***/ (function(module, exports, __webpack_require__) { -"use strict"; - +/** + * Module dependencies. + */ -var utils = __webpack_require__(637); -var define = __webpack_require__(503); +var tty = __webpack_require__(480); +var util = __webpack_require__(29); /** - * Text regex + * This is the Node.js implementation of `debug()`. + * + * Expose `debug()` as the module. */ -var TEXT_REGEX = '(\\[(?=.*\\])|\\])+'; -var not = utils.createRegex(TEXT_REGEX); +exports = module.exports = __webpack_require__(858); +exports.init = init; +exports.log = log; +exports.formatArgs = formatArgs; +exports.save = save; +exports.load = load; +exports.useColors = useColors; /** - * Brackets parsers + * Colors. */ -function parsers(brackets) { - brackets.state = brackets.state || {}; - brackets.parser.sets.bracket = brackets.parser.sets.bracket || []; - brackets.parser +exports.colors = [6, 2, 3, 4, 5, 1]; - .capture('escape', function() { - if (this.isInside('bracket')) return; - var pos = this.position(); - var m = this.match(/^\\(.)/); - if (!m) return; +/** + * Build up the default `inspectOpts` object from the environment variables. + * + * $ DEBUG_COLORS=no DEBUG_DEPTH=10 DEBUG_SHOW_HIDDEN=enabled node script.js + */ - return pos({ - type: 'escape', - val: m[0] - }); - }) +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() }); - /** - * Text parser - */ + // 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); - .capture('text', function() { - if (this.isInside('bracket')) return; - var pos = this.position(); - var m = this.match(not); - if (!m || !m[0]) return; + obj[prop] = val; + return obj; +}, {}); - return pos({ - type: 'text', - val: m[0] - }); - }) +/** + * 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 + */ - /** - * POSIX character classes: "[[:alpha:][:digits:]]" - */ +var fd = parseInt(process.env.DEBUG_FD, 10) || 2; - .capture('posix', function() { - var pos = this.position(); - var m = this.match(/^\[:(.*?):\](?=.*\])/); - if (!m) return; +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 inside = this.isInside('bracket'); - if (inside) { - brackets.posix++; - } +var stream = 1 === fd ? process.stdout : + 2 === fd ? process.stderr : + createWritableStdioStream(fd); - return pos({ - type: 'posix', - insideBracket: inside, - inner: m[1], - val: m[0] - }); - }) +/** + * Is stdout a TTY? Colored output is enabled when `true`. + */ - /** - * Bracket (noop) - */ +function useColors() { + return 'colors' in exports.inspectOpts + ? Boolean(exports.inspectOpts.colors) + : tty.isatty(fd); +} - .capture('bracket', function() {}) +/** + * Map %o to `util.inspect()`, all on a single line. + */ - /** - * Open: '[' - */ +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(' '); +}; - .capture('bracket.open', function() { - var parsed = this.parsed; - var pos = this.position(); - var m = this.match(/^\[(?=.*\])/); - if (!m) return; +/** + * Map %o to `util.inspect()`, allowing multiple lines if needed. + */ - var prev = this.prev(); - var last = utils.last(prev.nodes); +exports.formatters.O = function(v) { + this.inspectOpts.colors = this.useColors; + return util.inspect(v, this.inspectOpts); +}; - if (parsed.slice(-1) === '\\' && !this.isInside('bracket')) { - last.val = last.val.slice(0, last.val.length - 1); - return pos({ - type: 'escape', - val: m[0] - }); - } +/** + * Adds ANSI color escape codes if enabled. + * + * @api public + */ - var open = pos({ - type: 'bracket.open', - val: m[0] - }); +function formatArgs(args) { + var name = this.namespace; + var useColors = this.useColors; - if (last.type === 'bracket.open' || this.isInside('bracket')) { - open.val = '\\' + open.val; - open.type = 'bracket.inner'; - open.escaped = true; - return open; - } + if (useColors) { + var c = this.color; + var prefix = ' \u001b[3' + c + ';1m' + name + ' ' + '\u001b[0m'; - var node = pos({ - type: 'bracket', - nodes: [open] - }); + 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]; + } +} - define(node, 'parent', prev); - define(open, 'parent', node); - this.push('bracket', node); - prev.nodes.push(node); - }) +/** + * Invokes `util.format()` with the specified arguments and writes to `stream`. + */ - /** - * Bracket text - */ +function log() { + return stream.write(util.format.apply(util, arguments) + '\n'); +} - .capture('bracket.inner', function() { - if (!this.isInside('bracket')) return; - var pos = this.position(); - var m = this.match(not); - if (!m || !m[0]) return; +/** + * Save `namespaces`. + * + * @param {String} namespaces + * @api private + */ - var next = this.input.charAt(0); - var val = m[0]; +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; + } +} - var node = pos({ - type: 'bracket.inner', - val: val - }); +/** + * Load `namespaces`. + * + * @return {String} returns the previously persisted debug modes + * @api private + */ - if (val === '\\\\') { - return node; - } +function load() { + return process.env.DEBUG; +} - var first = val.charAt(0); - var last = val.slice(-1); +/** + * 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. + */ - if (first === '!') { - val = '^' + val.slice(1); - } +function createWritableStdioStream (fd) { + var stream; + var tty_wrap = process.binding('tty_wrap'); - if (last === '\\' || (val === '^' && next === ']')) { - val += this.input[0]; - this.consume(1); - } + // Note stream._type is used for test-module-load-list.js - node.val = val; - return node; - }) + switch (tty_wrap.guessHandleType(fd)) { + case 'TTY': + stream = new tty.WriteStream(fd); + stream._type = 'tty'; - /** - * Close: ']' - */ + // 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; - .capture('bracket.close', function() { - var parsed = this.parsed; - var pos = this.position(); - var m = this.match(/^\]/); - if (!m) return; + case 'FILE': + var fs = __webpack_require__(23); + stream = new fs.SyncWriteStream(fd, { autoClose: false }); + stream._type = 'fs'; + break; - var prev = this.prev(); - var last = utils.last(prev.nodes); + case 'PIPE': + case 'TCP': + var net = __webpack_require__(798); + stream = new net.Socket({ + fd: fd, + readable: false, + writable: true + }); - if (parsed.slice(-1) === '\\' && !this.isInside('bracket')) { - last.val = last.val.slice(0, last.val.length - 1); + // 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'; - return pos({ - type: 'escape', - val: m[0] - }); + // 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; - var node = pos({ - type: 'bracket.close', - rest: this.input, - val: m[0] - }); + default: + // Probably an error on in uv_guess_handle() + throw new Error('Implement me. Unknown stream file type!'); + } - if (last.type === 'bracket.open') { - node.type = 'bracket.inner'; - node.escaped = true; - return node; - } + // For supporting legacy API we put the FD here. + stream.fd = fd; - var bracket = this.pop('bracket'); - if (!this.isType(bracket, 'bracket')) { - if (this.options.strict) { - throw new Error('missing opening "["'); - } - node.type = 'bracket.inner'; - node.escaped = true; - return node; - } + stream._isStdio = true; - bracket.nodes.push(node); - define(node, 'parent', bracket); - }); + return stream; } /** - * Brackets parsers - */ - -module.exports = parsers; - -/** - * Expose text regex + * Init logic for `debug` instances. + * + * Create a new `inspectOpts` object in case `useColors` is set + * differently for a particular `debug` instance. */ -module.exports.TEXT_REGEX = TEXT_REGEX; - - -/***/ }), -/* 637 */ -/***/ (function(module, exports, __webpack_require__) { - -"use strict"; - - -var toRegex = __webpack_require__(502); -var regexNot = __webpack_require__(513); -var cached; - -/** - * Get the last element from `array` - * @param {Array} `array` - * @return {*} - */ +function init (debug) { + debug.inspectOpts = {}; -exports.last = function(arr) { - return arr[arr.length - 1]; -}; + var keys = Object.keys(exports.inspectOpts); + for (var i = 0; i < keys.length; i++) { + debug.inspectOpts[keys[i]] = exports.inspectOpts[keys[i]]; + } +} /** - * Create and cache regex to use for text nodes + * Enable namespaces listed in `process.env.DEBUG` initially. */ -exports.createRegex = function(pattern, include) { - if (cached) return cached; - var opts = {contains: true, strictClose: false}; - var not = regexNot.create(pattern, opts); - var re; - - if (typeof include === 'string') { - re = toRegex('^(?:' + include + '|' + not + ')', opts); - } else { - re = toRegex(not, opts); - } - - return (cached = re); -}; +exports.enable(load()); /***/ }), -/* 638 */ +/* 861 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var brackets = __webpack_require__(633); -var define = __webpack_require__(639); -var utils = __webpack_require__(640); +var brackets = __webpack_require__(851); +var define = __webpack_require__(862); +var utils = __webpack_require__(863); /** * Characters to use in text regex (we want to "not" match @@ -66566,7 +101711,7 @@ module.exports = parsers; /***/ }), -/* 639 */ +/* 862 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -66579,7 +101724,7 @@ module.exports = parsers; -var isDescriptor = __webpack_require__(533); +var isDescriptor = __webpack_require__(752); module.exports = function defineProperty(obj, prop, val) { if (typeof obj !== 'object' && typeof obj !== 'function') { @@ -66604,14 +101749,14 @@ module.exports = function defineProperty(obj, prop, val) { /***/ }), -/* 640 */ +/* 863 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var regex = __webpack_require__(513); -var Cache = __webpack_require__(624); +var regex = __webpack_require__(732); +var Cache = __webpack_require__(842); /** * Utils @@ -66680,7 +101825,7 @@ utils.createRegex = function(str) { /***/ }), -/* 641 */ +/* 864 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -66690,16 +101835,16 @@ utils.createRegex = function(str) { * Module dependencies */ -var Snapdragon = __webpack_require__(541); -var define = __webpack_require__(639); -var extend = __webpack_require__(511); +var Snapdragon = __webpack_require__(760); +var define = __webpack_require__(862); +var extend = __webpack_require__(730); /** * Local dependencies */ -var compilers = __webpack_require__(632); -var parsers = __webpack_require__(638); +var compilers = __webpack_require__(850); +var parsers = __webpack_require__(861); /** * Customize Snapdragon parser and renderer @@ -66765,16 +101910,16 @@ module.exports = Extglob; /***/ }), -/* 642 */ +/* 865 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -var extglob = __webpack_require__(631); -var nanomatch = __webpack_require__(616); -var regexNot = __webpack_require__(513); -var toRegex = __webpack_require__(604); +var extglob = __webpack_require__(849); +var nanomatch = __webpack_require__(834); +var regexNot = __webpack_require__(732); +var toRegex = __webpack_require__(822); var not; /** @@ -66855,14 +102000,14 @@ function textRegex(pattern) { /***/ }), -/* 643 */ +/* 866 */ /***/ (function(module, exports, __webpack_require__) { -module.exports = new (__webpack_require__(624))(); +module.exports = new (__webpack_require__(842))(); /***/ }), -/* 644 */ +/* 867 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -66875,13 +102020,13 @@ var path = __webpack_require__(16); * Module dependencies */ -var Snapdragon = __webpack_require__(541); -utils.define = __webpack_require__(611); -utils.diff = __webpack_require__(628); -utils.extend = __webpack_require__(612); -utils.pick = __webpack_require__(629); -utils.typeOf = __webpack_require__(645); -utils.unique = __webpack_require__(514); +var Snapdragon = __webpack_require__(760); +utils.define = __webpack_require__(829); +utils.diff = __webpack_require__(846); +utils.extend = __webpack_require__(830); +utils.pick = __webpack_require__(847); +utils.typeOf = __webpack_require__(868); +utils.unique = __webpack_require__(733); /** * Returns true if the platform is windows, or `path.sep` is `\\`. @@ -67178,7 +102323,7 @@ utils.unixify = function(options) { /***/ }), -/* 645 */ +/* 868 */ /***/ (function(module, exports) { var toString = Object.prototype.toString; @@ -67313,7 +102458,7 @@ function isBuffer(val) { /***/ }), -/* 646 */ +/* 869 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67332,9 +102477,9 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -var readdir = __webpack_require__(647); -var reader_1 = __webpack_require__(660); -var fs_stream_1 = __webpack_require__(664); +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() { @@ -67395,15 +102540,15 @@ exports.default = ReaderAsync; /***/ }), -/* 647 */ +/* 870 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const readdirSync = __webpack_require__(648); -const readdirAsync = __webpack_require__(656); -const readdirStream = __webpack_require__(659); +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; @@ -67487,7 +102632,7 @@ function readdirStreamStat (dir, options) { /***/ }), -/* 648 */ +/* 871 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -67495,11 +102640,11 @@ function readdirStreamStat (dir, options) { module.exports = readdirSync; -const DirectoryReader = __webpack_require__(649); +const DirectoryReader = __webpack_require__(872); let syncFacade = { - fs: __webpack_require__(654), - forEach: __webpack_require__(655), + fs: __webpack_require__(877), + forEach: __webpack_require__(878), sync: true }; @@ -67528,18 +102673,18 @@ function readdirSync (dir, options, internalOptions) { /***/ }), -/* 649 */ +/* 872 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const Readable = __webpack_require__(28).Readable; -const EventEmitter = __webpack_require__(46).EventEmitter; +const Readable = __webpack_require__(27).Readable; +const EventEmitter = __webpack_require__(379).EventEmitter; const path = __webpack_require__(16); -const normalizeOptions = __webpack_require__(650); -const stat = __webpack_require__(652); -const call = __webpack_require__(653); +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 @@ -67915,14 +103060,14 @@ module.exports = DirectoryReader; /***/ }), -/* 650 */ +/* 873 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(16); -const globToRegExp = __webpack_require__(651); +const globToRegExp = __webpack_require__(874); module.exports = normalizeOptions; @@ -68099,7 +103244,7 @@ function normalizeOptions (options, internalOptions) { /***/ }), -/* 651 */ +/* 874 */ /***/ (function(module, exports) { module.exports = function (glob, opts) { @@ -68236,13 +103381,13 @@ module.exports = function (glob, opts) { /***/ }), -/* 652 */ +/* 875 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const call = __webpack_require__(653); +const call = __webpack_require__(876); module.exports = stat; @@ -68317,7 +103462,7 @@ function symlinkStat (fs, path, lstats, callback) { /***/ }), -/* 653 */ +/* 876 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68378,14 +103523,14 @@ function callOnce (fn) { /***/ }), -/* 654 */ +/* 877 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(23); -const call = __webpack_require__(653); +const call = __webpack_require__(876); /** * A facade around {@link fs.readdirSync} that allows it to be called @@ -68449,7 +103594,7 @@ exports.lstat = function (path, callback) { /***/ }), -/* 655 */ +/* 878 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68478,7 +103623,7 @@ function syncForEach (array, iterator, done) { /***/ }), -/* 656 */ +/* 879 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68486,12 +103631,12 @@ function syncForEach (array, iterator, done) { module.exports = readdirAsync; -const maybe = __webpack_require__(657); -const DirectoryReader = __webpack_require__(649); +const maybe = __webpack_require__(880); +const DirectoryReader = __webpack_require__(872); let asyncFacade = { fs: __webpack_require__(23), - forEach: __webpack_require__(658), + forEach: __webpack_require__(881), async: true }; @@ -68533,7 +103678,7 @@ function readdirAsync (dir, options, callback, internalOptions) { /***/ }), -/* 657 */ +/* 880 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68560,7 +103705,7 @@ module.exports = function maybe (cb, promise) { /***/ }), -/* 658 */ +/* 881 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68596,7 +103741,7 @@ function asyncForEach (array, iterator, done) { /***/ }), -/* 659 */ +/* 882 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68604,11 +103749,11 @@ function asyncForEach (array, iterator, done) { module.exports = readdirStream; -const DirectoryReader = __webpack_require__(649); +const DirectoryReader = __webpack_require__(872); let streamFacade = { fs: __webpack_require__(23), - forEach: __webpack_require__(658), + forEach: __webpack_require__(881), async: true }; @@ -68628,16 +103773,16 @@ function readdirStream (dir, options, internalOptions) { /***/ }), -/* 660 */ +/* 883 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); var path = __webpack_require__(16); -var deep_1 = __webpack_require__(661); -var entry_1 = __webpack_require__(663); -var pathUtil = __webpack_require__(662); +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; @@ -68703,14 +103848,14 @@ exports.default = Reader; /***/ }), -/* 661 */ +/* 884 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var pathUtils = __webpack_require__(662); -var patternUtils = __webpack_require__(495); +var pathUtils = __webpack_require__(885); +var patternUtils = __webpack_require__(714); var DeepFilter = /** @class */ (function () { function DeepFilter(options, micromatchOptions) { this.options = options; @@ -68793,7 +103938,7 @@ exports.default = DeepFilter; /***/ }), -/* 662 */ +/* 885 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68824,14 +103969,14 @@ exports.makeAbsolute = makeAbsolute; /***/ }), -/* 663 */ +/* 886 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var pathUtils = __webpack_require__(662); -var patternUtils = __webpack_require__(495); +var pathUtils = __webpack_require__(885); +var patternUtils = __webpack_require__(714); var EntryFilter = /** @class */ (function () { function EntryFilter(options, micromatchOptions) { this.options = options; @@ -68916,7 +104061,7 @@ exports.default = EntryFilter; /***/ }), -/* 664 */ +/* 887 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -68935,9 +104080,9 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -var stream = __webpack_require__(28); -var fsStat = __webpack_require__(665); -var fs_1 = __webpack_require__(669); +var stream = __webpack_require__(27); +var fsStat = __webpack_require__(888); +var fs_1 = __webpack_require__(892); var FileSystemStream = /** @class */ (function (_super) { __extends(FileSystemStream, _super); function FileSystemStream() { @@ -68987,14 +104132,14 @@ exports.default = FileSystemStream; /***/ }), -/* 665 */ +/* 888 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const optionsManager = __webpack_require__(666); -const statProvider = __webpack_require__(668); +const optionsManager = __webpack_require__(889); +const statProvider = __webpack_require__(891); /** * Asynchronous API. */ @@ -69025,13 +104170,13 @@ exports.statSync = statSync; /***/ }), -/* 666 */ +/* 889 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -const fsAdapter = __webpack_require__(667); +const fsAdapter = __webpack_require__(890); function prepare(opts) { const options = Object.assign({ fs: fsAdapter.getFileSystemAdapter(opts ? opts.fs : undefined), @@ -69044,7 +104189,7 @@ exports.prepare = prepare; /***/ }), -/* 667 */ +/* 890 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69067,7 +104212,7 @@ exports.getFileSystemAdapter = getFileSystemAdapter; /***/ }), -/* 668 */ +/* 891 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69119,7 +104264,7 @@ exports.isFollowedSymlink = isFollowedSymlink; /***/ }), -/* 669 */ +/* 892 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69150,7 +104295,7 @@ exports.default = FileSystem; /***/ }), -/* 670 */ +/* 893 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69169,10 +104314,10 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -var stream = __webpack_require__(28); -var readdir = __webpack_require__(647); -var reader_1 = __webpack_require__(660); -var fs_stream_1 = __webpack_require__(664); +var stream = __webpack_require__(27); +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) { @@ -69240,7 +104385,7 @@ exports.default = ReaderStream; /***/ }), -/* 671 */ +/* 894 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69259,9 +104404,9 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -var readdir = __webpack_require__(647); -var reader_1 = __webpack_require__(660); -var fs_sync_1 = __webpack_require__(672); +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() { @@ -69321,7 +104466,7 @@ exports.default = ReaderSync; /***/ }), -/* 672 */ +/* 895 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69340,8 +104485,8 @@ var __extends = (this && this.__extends) || (function () { }; })(); Object.defineProperty(exports, "__esModule", { value: true }); -var fsStat = __webpack_require__(665); -var fs_1 = __webpack_require__(669); +var fsStat = __webpack_require__(888); +var fs_1 = __webpack_require__(892); var FileSystemSync = /** @class */ (function (_super) { __extends(FileSystemSync, _super); function FileSystemSync() { @@ -69387,7 +104532,7 @@ exports.default = FileSystemSync; /***/ }), -/* 673 */ +/* 896 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69403,13 +104548,13 @@ exports.flatten = flatten; /***/ }), -/* 674 */ +/* 897 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -var merge2 = __webpack_require__(177); +var merge2 = __webpack_require__(591); /** * Merge multiple streams and propagate their errors into one stream in parallel. */ @@ -69424,13 +104569,13 @@ exports.merge = merge; /***/ }), -/* 675 */ +/* 898 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(16); -const pathType = __webpack_require__(676); +const pathType = __webpack_require__(899); const getExtensions = extensions => extensions.length > 1 ? `{${extensions.join(',')}}` : extensions[0]; @@ -69496,13 +104641,13 @@ module.exports.sync = (input, opts) => { /***/ }), -/* 676 */ +/* 899 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(23); -const pify = __webpack_require__(677); +const pify = __webpack_require__(900); function type(fn, fn2, fp) { if (typeof fp !== 'string') { @@ -69545,7 +104690,7 @@ exports.symlinkSync = typeSync.bind(null, 'lstatSync', 'isSymbolicLink'); /***/ }), -/* 677 */ +/* 900 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -69636,17 +104781,17 @@ module.exports = (obj, opts) => { /***/ }), -/* 678 */ +/* 901 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(23); const path = __webpack_require__(16); -const fastGlob = __webpack_require__(491); -const gitIgnore = __webpack_require__(679); -const pify = __webpack_require__(680); -const slash = __webpack_require__(681); +const fastGlob = __webpack_require__(710); +const gitIgnore = __webpack_require__(902); +const pify = __webpack_require__(903); +const slash = __webpack_require__(904); const DEFAULT_IGNORE = [ '**/node_modules/**', @@ -69744,7 +104889,7 @@ module.exports.sync = options => { /***/ }), -/* 679 */ +/* 902 */ /***/ (function(module, exports) { // A simple implementation of make-array @@ -70213,7 +105358,7 @@ module.exports = options => new IgnoreBase(options) /***/ }), -/* 680 */ +/* 903 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -70288,7 +105433,7 @@ module.exports = (input, options) => { /***/ }), -/* 681 */ +/* 904 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -70306,17 +105451,17 @@ module.exports = input => { /***/ }), -/* 682 */ +/* 905 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const path = __webpack_require__(16); const {constants: fsConstants} = __webpack_require__(23); -const {Buffer} = __webpack_require__(683); -const CpFileError = __webpack_require__(685); -const fs = __webpack_require__(687); -const ProgressEmitter = __webpack_require__(689); +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) { @@ -70470,11 +105615,11 @@ module.exports.sync = (source, destination, options) => { /***/ }), -/* 683 */ +/* 906 */ /***/ (function(module, exports, __webpack_require__) { /* eslint-disable node/no-deprecated-api */ -var buffer = __webpack_require__(684) +var buffer = __webpack_require__(585) var Buffer = buffer.Buffer // alternative to using Object.keys for old browsers @@ -70538,18 +105683,12 @@ SafeBuffer.allocUnsafeSlow = function (size) { /***/ }), -/* 684 */ -/***/ (function(module, exports) { - -module.exports = require("buffer"); - -/***/ }), -/* 685 */ +/* 907 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const NestedError = __webpack_require__(686); +const NestedError = __webpack_require__(908); class CpFileError extends NestedError { constructor(message, nested) { @@ -70563,10 +105702,10 @@ module.exports = CpFileError; /***/ }), -/* 686 */ +/* 908 */ /***/ (function(module, exports, __webpack_require__) { -var inherits = __webpack_require__(44); +var inherits = __webpack_require__(509); var NestedError = function (message, nested) { this.nested = nested; @@ -70617,15 +105756,15 @@ module.exports = NestedError; /***/ }), -/* 687 */ +/* 909 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; const fs = __webpack_require__(22); -const makeDir = __webpack_require__(115); -const pify = __webpack_require__(688); -const CpFileError = __webpack_require__(685); +const makeDir = __webpack_require__(559); +const pify = __webpack_require__(910); +const CpFileError = __webpack_require__(907); const fsP = pify(fs); @@ -70770,7 +105909,7 @@ if (fs.copyFileSync) { /***/ }), -/* 688 */ +/* 910 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; @@ -70845,12 +105984,12 @@ module.exports = (input, options) => { /***/ }), -/* 689 */ +/* 911 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const EventEmitter = __webpack_require__(46); +const EventEmitter = __webpack_require__(379); const written = new WeakMap(); @@ -70886,12 +106025,12 @@ module.exports = ProgressEmitter; /***/ }), -/* 690 */ +/* 912 */ /***/ (function(module, exports, __webpack_require__) { "use strict"; -const NestedError = __webpack_require__(691); +const NestedError = __webpack_require__(913); class CpyError extends NestedError { constructor(message, nested) { @@ -70905,7 +106044,7 @@ module.exports = CpyError; /***/ }), -/* 691 */ +/* 913 */ /***/ (function(module, exports, __webpack_require__) { var inherits = __webpack_require__(29).inherits; @@ -70961,14 +106100,14 @@ module.exports = NestedError; /***/ }), -/* 692 */ +/* 914 */ /***/ (function(module, __webpack_exports__, __webpack_require__) { "use strict"; __webpack_require__.r(__webpack_exports__); /* harmony export (binding) */ __webpack_require__.d(__webpack_exports__, "prepareExternalProjectDependencies", function() { return prepareExternalProjectDependencies; }); -/* harmony import */ var _utils_package_json__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(55); -/* harmony import */ var _utils_project__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(54); +/* harmony import */ var _utils_package_json__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(517); +/* harmony import */ var _utils_project__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(516); /* * Licensed to Elasticsearch B.V. under one or more contributor * license agreements. See the NOTICE file distributed with diff --git a/packages/kbn-pm/package.json b/packages/kbn-pm/package.json index ead454410a8b3..f57365905292b 100644 --- a/packages/kbn-pm/package.json +++ b/packages/kbn-pm/package.json @@ -34,6 +34,8 @@ "@types/tempy": "^0.2.0", "@types/wrap-ansi": "^2.0.15", "@types/write-pkg": "^3.1.0", + "@kbn/dev-utils": "1.0.0", + "@yarnpkg/lockfile": "^1.1.0", "babel-loader": "^8.0.6", "chalk": "^2.4.2", "cmd-shim": "^2.1.0", @@ -48,6 +50,7 @@ "indent-string": "^3.2.0", "lodash.clonedeepwith": "^4.5.0", "log-symbols": "^2.2.0", + "multimatch": "^4.0.0", "ncp": "^2.0.0", "ora": "^1.4.0", "prettier": "^1.19.1", diff --git a/packages/kbn-pm/src/cli.ts b/packages/kbn-pm/src/cli.ts index 91274ed8872f9..c2f49356957f7 100644 --- a/packages/kbn-pm/src/cli.ts +++ b/packages/kbn-pm/src/cli.ts @@ -47,6 +47,7 @@ function help() { -i, --include Include only specified projects. If left unspecified, it defaults to including all projects. --oss Do not include the x-pack when running command. --skip-kibana-plugins Filter all plugins in ./plugins and ../kibana-extra when running command. + --no-cache Disable the bootstrap cache `); } @@ -65,7 +66,10 @@ export async function run(argv: string[]) { h: 'help', i: 'include', }, - boolean: ['prefer-offline', 'frozen-lockfile'], + default: { + cache: true, + }, + boolean: ['prefer-offline', 'frozen-lockfile', 'cache'], }); const args = options._; diff --git a/packages/kbn-pm/src/commands/__snapshots__/bootstrap.test.ts.snap b/packages/kbn-pm/src/commands/__snapshots__/bootstrap.test.ts.snap index 53c942de0aff8..c0505710f5670 100644 --- a/packages/kbn-pm/src/commands/__snapshots__/bootstrap.test.ts.snap +++ b/packages/kbn-pm/src/commands/__snapshots__/bootstrap.test.ts.snap @@ -31,6 +31,7 @@ Array [ }, "scripts": Object {}, "targetLocation": "/packages/kbn-pm/src/commands/target", + "version": "1.0.0", }, "bar" => Project { "allDependencies": Object {}, @@ -52,6 +53,7 @@ Array [ "kbn:bootstrap": "node ./bar.js", }, "targetLocation": "/packages/kbn-pm/src/commands/packages/bar/target", + "version": "1.0.0", }, }, Map { @@ -76,6 +78,7 @@ Array [ "kbn:bootstrap": "node ./bar.js", }, "targetLocation": "/packages/kbn-pm/src/commands/packages/bar/target", + "version": "1.0.0", }, ], "bar" => Array [], @@ -109,6 +112,7 @@ Array [ "kbn:bootstrap": "node ./bar.js", }, "targetLocation": "/packages/kbn-pm/src/commands/packages/bar/target", + "version": "1.0.0", }, ], ] diff --git a/packages/kbn-pm/src/commands/bootstrap.test.ts b/packages/kbn-pm/src/commands/bootstrap.test.ts index b36246d97c1ad..072f34611f8fc 100644 --- a/packages/kbn-pm/src/commands/bootstrap.test.ts +++ b/packages/kbn-pm/src/commands/bootstrap.test.ts @@ -29,6 +29,7 @@ import { Project } from '../utils/project'; import { buildProjectGraph } from '../utils/projects'; import { installInDir, runScriptInPackageStreaming, yarnWorkspacesInfo } from '../utils/scripts'; import { BootstrapCommand } from './bootstrap'; +import { Kibana } from '../utils/kibana'; const mockInstallInDir = installInDir as jest.Mock; const mockRunScriptInPackageStreaming = runScriptInPackageStreaming as jest.Mock; @@ -107,6 +108,7 @@ test('handles dependencies of dependencies', async () => { ['bar', bar], ['baz', baz], ]); + const kbn = new Kibana(projects); const projectGraph = buildProjectGraph(projects); const logMock = jest.spyOn(console, 'log').mockImplementation(noop); @@ -115,6 +117,7 @@ test('handles dependencies of dependencies', async () => { extraArgs: [], options: {}, rootPath: '', + kbn, }); expect(mockInstallInDir.mock.calls).toMatchSnapshot('install in dir'); @@ -142,6 +145,7 @@ test('does not run installer if no deps in package', async () => { ['kibana', kibana], ['bar', bar], ]); + const kbn = new Kibana(projects); const projectGraph = buildProjectGraph(projects); const logMock = jest.spyOn(console, 'log').mockImplementation(noop); @@ -150,6 +154,7 @@ test('does not run installer if no deps in package', async () => { extraArgs: [], options: {}, rootPath: '', + kbn, }); expect(mockInstallInDir.mock.calls).toMatchSnapshot('install in dir'); @@ -167,6 +172,7 @@ test('handles "frozen-lockfile"', async () => { }); const projects = new Map([['kibana', kibana]]); + const kbn = new Kibana(projects); const projectGraph = buildProjectGraph(projects); jest.spyOn(console, 'log').mockImplementation(noop); @@ -177,6 +183,7 @@ test('handles "frozen-lockfile"', async () => { 'frozen-lockfile': true, }, rootPath: '', + kbn, }); expect(mockInstallInDir.mock.calls).toMatchSnapshot('install in dir'); @@ -205,6 +212,7 @@ test('calls "kbn:bootstrap" scripts and links executables after installing deps' ['kibana', kibana], ['bar', bar], ]); + const kbn = new Kibana(projects); const projectGraph = buildProjectGraph(projects); jest.spyOn(console, 'log').mockImplementation(noop); @@ -213,6 +221,7 @@ test('calls "kbn:bootstrap" scripts and links executables after installing deps' extraArgs: [], options: {}, rootPath: '', + kbn, }); expect(mockLinkProjectExecutables.mock.calls).toMatchSnapshot('link bins'); diff --git a/packages/kbn-pm/src/commands/bootstrap.ts b/packages/kbn-pm/src/commands/bootstrap.ts index be4b9da7bf516..d0aa220f25f66 100644 --- a/packages/kbn-pm/src/commands/bootstrap.ts +++ b/packages/kbn-pm/src/commands/bootstrap.ts @@ -24,12 +24,14 @@ import { log } from '../utils/log'; import { parallelizeBatches } from '../utils/parallelize'; import { topologicallyBatchProjects } from '../utils/projects'; import { ICommand } from './'; +import { getAllChecksums } from '../utils/project_checksums'; +import { BootstrapCacheFile } from '../utils/bootstrap_cache_file'; export const BootstrapCommand: ICommand = { description: 'Install dependencies and crosslink projects', name: 'bootstrap', - async run(projects, projectGraph, { options }) { + async run(projects, projectGraph, { options, kbn }) { const batchedProjectsByWorkspace = topologicallyBatchProjects(projects, projectGraph, { batchByWorkspace: true, }); @@ -65,9 +67,18 @@ export const BootstrapCommand: ICommand = { * have to, as it will slow down the bootstrapping process. */ log.write(chalk.bold('\nLinking executables completed, running `kbn:bootstrap` scripts\n')); - await parallelizeBatches(batchedProjects, async pkg => { - if (pkg.hasScript('kbn:bootstrap')) { - await pkg.runScriptStreaming('kbn:bootstrap'); + + const checksums = options.cache ? await getAllChecksums(kbn, log) : false; + await parallelizeBatches(batchedProjects, async project => { + if (project.hasScript('kbn:bootstrap')) { + const cacheFile = new BootstrapCacheFile(kbn, project, checksums); + if (cacheFile.isValid()) { + log.success(`[${project.name}] cache up to date`); + } else { + cacheFile.delete(); + await project.runScriptStreaming('kbn:bootstrap'); + cacheFile.write(); + } } }); diff --git a/packages/kbn-pm/src/commands/index.ts b/packages/kbn-pm/src/commands/index.ts index 21de92bcbf47c..b73e054b03373 100644 --- a/packages/kbn-pm/src/commands/index.ts +++ b/packages/kbn-pm/src/commands/index.ts @@ -23,6 +23,7 @@ export interface ICommandConfig { extraArgs: string[]; options: { [key: string]: any }; rootPath: string; + kbn: Kibana; } export interface ICommand { @@ -36,6 +37,7 @@ import { BootstrapCommand } from './bootstrap'; import { CleanCommand } from './clean'; import { RunCommand } from './run'; import { WatchCommand } from './watch'; +import { Kibana } from '../utils/kibana'; export const commands: { [key: string]: ICommand } = { bootstrap: BootstrapCommand, diff --git a/packages/kbn-pm/src/config.ts b/packages/kbn-pm/src/config.ts index 2e42a182e7ec3..6ba8d58a26f88 100644 --- a/packages/kbn-pm/src/config.ts +++ b/packages/kbn-pm/src/config.ts @@ -19,18 +19,16 @@ import { resolve } from 'path'; -export interface IProjectPathOptions { - 'skip-kibana-plugins'?: boolean; - oss?: boolean; +interface Options { + rootPath: string; + skipKibanaPlugins?: boolean; + ossOnly?: boolean; } /** * Returns all the paths where plugins are located */ -export function getProjectPaths(rootPath: string, options: IProjectPathOptions = {}) { - const skipKibanaPlugins = Boolean(options['skip-kibana-plugins']); - const ossOnly = Boolean(options.oss); - +export function getProjectPaths({ rootPath, ossOnly, skipKibanaPlugins }: Options) { const projectPaths = [rootPath, resolve(rootPath, 'packages/*')]; // This is needed in order to install the dependencies for the declared @@ -48,6 +46,7 @@ export function getProjectPaths(rootPath: string, options: IProjectPathOptions = if (!ossOnly) { projectPaths.push(resolve(rootPath, 'x-pack')); + projectPaths.push(resolve(rootPath, 'x-pack/plugins/*')); projectPaths.push(resolve(rootPath, 'x-pack/legacy/plugins/*')); } diff --git a/packages/kbn-pm/src/production/build_production_projects.ts b/packages/kbn-pm/src/production/build_production_projects.ts index fd5c4107292fc..0d4be8b016077 100644 --- a/packages/kbn-pm/src/production/build_production_projects.ts +++ b/packages/kbn-pm/src/production/build_production_projects.ts @@ -66,7 +66,7 @@ export async function buildProductionProjects({ * is supplied, we omit projects with build.oss in their package.json set to false. */ async function getProductionProjects(rootPath: string, onlyOSS?: boolean) { - const projectPaths = getProjectPaths(rootPath, {}); + const projectPaths = getProjectPaths({ rootPath }); const projects = await getProjects(rootPath, projectPaths); const projectsSubset = [projects.get('kibana')!]; diff --git a/packages/kbn-pm/src/run.test.ts b/packages/kbn-pm/src/run.test.ts index 13cac6eb745ba..ff0dc2666afea 100644 --- a/packages/kbn-pm/src/run.test.ts +++ b/packages/kbn-pm/src/run.test.ts @@ -38,7 +38,7 @@ function getExpectedProjectsAndGraph(runMock: any) { } let command: ICommand; -let config: ICommandConfig; +let config: Omit; beforeEach(() => { command = { description: 'test description', diff --git a/packages/kbn-pm/src/run.ts b/packages/kbn-pm/src/run.ts index 980997200a194..44bf5a91ee1b1 100644 --- a/packages/kbn-pm/src/run.ts +++ b/packages/kbn-pm/src/run.ts @@ -22,13 +22,13 @@ import indentString from 'indent-string'; import wrapAnsi from 'wrap-ansi'; import { ICommand, ICommandConfig } from './commands'; -import { getProjectPaths, IProjectPathOptions } from './config'; import { CliError } from './utils/errors'; import { log } from './utils/log'; -import { buildProjectGraph, getProjects } from './utils/projects'; +import { buildProjectGraph } from './utils/projects'; import { renderProjectsTree } from './utils/projects_tree'; +import { Kibana } from './utils/kibana'; -export async function runCommand(command: ICommand, config: ICommandConfig) { +export async function runCommand(command: ICommand, config: Omit) { try { log.write( chalk.bold( @@ -36,9 +36,10 @@ export async function runCommand(command: ICommand, config: ICommandConfig) { ) ); - const projectPaths = getProjectPaths(config.rootPath, config.options as IProjectPathOptions); - - const projects = await getProjects(config.rootPath, projectPaths, { + const kbn = await Kibana.loadFrom(config.rootPath); + const projects = kbn.getFilteredProjects({ + skipKibanaPlugins: Boolean(config.options['skip-kibana-plugins']), + ossOnly: Boolean(config.options.oss), exclude: toArray(config.options.exclude), include: toArray(config.options.include), }); @@ -57,7 +58,10 @@ export async function runCommand(command: ICommand, config: ICommandConfig) { log.write(chalk.bold(`Found [${chalk.green(projects.size.toString())}] projects:\n`)); log.write(renderProjectsTree(config.rootPath, projects)); - await command.run(projects, projectGraph, config); + await command.run(projects, projectGraph, { + ...config, + kbn, + }); } catch (e) { log.write(chalk.bold.red(`\n[${command.name}] failed:\n`)); diff --git a/packages/kbn-pm/src/utils/bootstrap_cache_file.ts b/packages/kbn-pm/src/utils/bootstrap_cache_file.ts new file mode 100644 index 0000000000000..7d87179f34605 --- /dev/null +++ b/packages/kbn-pm/src/utils/bootstrap_cache_file.ts @@ -0,0 +1,92 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import Fs from 'fs'; +import Path from 'path'; + +import { ChecksumMap } from './project_checksums'; +import { Project } from '../utils/project'; +import { Kibana } from '../utils/kibana'; + +export class BootstrapCacheFile { + private readonly path: string; + private readonly expectedValue: string | undefined; + + constructor(kbn: Kibana, project: Project, checksums: ChecksumMap | false) { + this.path = Path.resolve(project.targetLocation, '.bootstrap-cache'); + + if (!checksums) { + return; + } + + const projectAndDepCacheKeys = Array.from(kbn.getProjectAndDeps(project.name).values()) + // sort deps by name so that the key is stable + .sort((a, b) => a.name.localeCompare(b.name)) + // get the cacheKey for each project, return undefined if the cache key couldn't be determined + .map(p => { + const cacheKey = checksums.get(p.name); + if (cacheKey) { + return `${p.name}:${cacheKey}`; + } + }); + + // if any of the relevant cache keys are undefined then the projectCacheKey must be too + this.expectedValue = projectAndDepCacheKeys.some(k => !k) + ? undefined + : [ + `# this is only human readable for debugging, please don't try to parse this`, + ...projectAndDepCacheKeys, + ].join('\n'); + } + + isValid() { + if (!this.expectedValue) { + return false; + } + + try { + return Fs.readFileSync(this.path, 'utf8') === this.expectedValue; + } catch (error) { + if (error.code === 'ENOENT') { + return false; + } + + throw error; + } + } + + delete() { + try { + Fs.unlinkSync(this.path); + } catch (error) { + if (error.code !== 'ENOENT') { + throw error; + } + } + } + + write() { + if (!this.expectedValue) { + return; + } + + Fs.mkdirSync(Path.dirname(this.path), { recursive: true }); + Fs.writeFileSync(this.path, this.expectedValue); + } +} diff --git a/packages/kbn-pm/src/utils/kibana.ts b/packages/kbn-pm/src/utils/kibana.ts new file mode 100644 index 0000000000000..36f697d19fc1f --- /dev/null +++ b/packages/kbn-pm/src/utils/kibana.ts @@ -0,0 +1,124 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import Path from 'path'; + +import multimatch from 'multimatch'; + +import { ProjectMap, getProjects, includeTransitiveProjects } from './projects'; +import { Project } from './project'; +import { getProjectPaths } from '../config'; + +/** + * Helper class for dealing with a set of projects as children of + * the Kibana project. The kbn/pm is currently implemented to be + * more generic, where everything is an operation of generic projects, + * but that leads to exceptions where we need the kibana project and + * do things like `project.get('kibana')!`. + * + * Using this helper we can restructre the generic list of projects + * as a Kibana object which encapulates all the projects in the + * workspace and knows about the root Kibana project. + */ +export class Kibana { + static async loadFrom(rootPath: string) { + return new Kibana(await getProjects(rootPath, getProjectPaths({ rootPath }))); + } + + private readonly kibanaProject: Project; + + constructor(private readonly allWorkspaceProjects: ProjectMap) { + const kibanaProject = allWorkspaceProjects.get('kibana'); + + if (!kibanaProject) { + throw new TypeError( + 'Unable to create Kibana object without all projects, including the Kibana project.' + ); + } + + this.kibanaProject = kibanaProject; + } + + /** make an absolute path by resolving subPath relative to the kibana repo */ + getAbsolute(...subPath: string[]) { + return Path.resolve(this.kibanaProject.path, ...subPath); + } + + /** convert an absolute path to a relative path, relative to the kibana repo */ + getRelative(absolute: string) { + return Path.relative(this.kibanaProject.path, absolute); + } + + /** get a copy of the map of all projects in the kibana workspace */ + getAllProjects() { + return new Map(this.allWorkspaceProjects); + } + + /** determine if a project with the given name exists */ + hasProject(name: string) { + return this.allWorkspaceProjects.has(name); + } + + /** get a specific project, throws if the name is not known (use hasProject() first) */ + getProject(name: string) { + const project = this.allWorkspaceProjects.get(name); + + if (!project) { + throw new Error(`No package with name "${name}" in the workspace`); + } + + return project; + } + + /** get a project and all of the projects it depends on in a ProjectMap */ + getProjectAndDeps(name: string) { + const project = this.getProject(name); + return includeTransitiveProjects([project], this.allWorkspaceProjects); + } + + /** filter the projects to just those matching certain paths/include/exclude tags */ + getFilteredProjects(options: { + skipKibanaPlugins: boolean; + ossOnly: boolean; + exclude: string[]; + include: string[]; + }) { + const allProjects = this.getAllProjects(); + const filteredProjects: ProjectMap = new Map(); + + const pkgJsonPaths = Array.from(allProjects.values()).map(p => p.packageJsonLocation); + const filteredPkgJsonGlobs = getProjectPaths({ + ...options, + rootPath: this.kibanaProject.path, + }).map(g => Path.resolve(g, 'package.json')); + const matchingPkgJsonPaths = multimatch(pkgJsonPaths, filteredPkgJsonGlobs); + + for (const project of allProjects.values()) { + const pathMatches = matchingPkgJsonPaths.includes(project.packageJsonLocation); + const notExcluded = !options.exclude.includes(project.name); + const isIncluded = !options.include.length || options.include.includes(project.name); + + if (pathMatches && notExcluded && isIncluded) { + filteredProjects.set(project.name, project); + } + } + + return filteredProjects; + } +} diff --git a/packages/kbn-pm/src/utils/log.ts b/packages/kbn-pm/src/utils/log.ts index 45ad08b8a24a0..9e5630be5bd19 100644 --- a/packages/kbn-pm/src/utils/log.ts +++ b/packages/kbn-pm/src/utils/log.ts @@ -17,7 +17,18 @@ * under the License. */ -export const log = { +import { ToolingLog, ToolingLogCollectingWriter } from '@kbn/dev-utils'; + +class Log extends ToolingLog { + testWriter?: ToolingLogCollectingWriter; + + constructor() { + super({ + level: 'info', + writeTo: process.stdout, + }); + } + /** * Log something to the console. Ideally we would use a real logger in * kbn-pm, but that's a pretty big change for now. @@ -26,5 +37,7 @@ export const log = { write(...args: any[]) { // eslint-disable-next-line no-console console.log(...args); - }, -}; + } +} + +export const log = new Log(); diff --git a/packages/kbn-pm/src/utils/project.ts b/packages/kbn-pm/src/utils/project.ts index 83192b33bd756..7b0bbed5c3f46 100644 --- a/packages/kbn-pm/src/utils/project.ts +++ b/packages/kbn-pm/src/utils/project.ts @@ -19,7 +19,7 @@ import chalk from 'chalk'; import fs from 'fs'; -import { relative, resolve as resolvePath } from 'path'; +import Path from 'path'; import { inspect } from 'util'; import { CliError } from './errors'; @@ -54,15 +54,27 @@ export class Project { return new Project(pkgJson, path); } + /** parsed package.json */ public readonly json: IPackageJson; + /** absolute path to the package.json file in the project */ public readonly packageJsonLocation: string; + /** absolute path to the node_modules in the project (might not actually exist) */ public readonly nodeModulesLocation: string; + /** absolute path to the target directory in the project (might not actually exist) */ public readonly targetLocation: string; + /** absolute path to the directory containing the project */ public readonly path: string; + /** the version of the project */ + public readonly version: string; + /** merged set of dependencies of the project, [name => version range] */ public readonly allDependencies: IPackageDependencies; + /** regular dependencies of the project, [name => version range] */ public readonly productionDependencies: IPackageDependencies; + /** development dependencies of the project, [name => version range] */ public readonly devDependencies: IPackageDependencies; + /** scripts defined in the package.json file for the project [name => body] */ public readonly scripts: IPackageScripts; + public isWorkspaceRoot = false; public isWorkspaceProject = false; @@ -70,10 +82,11 @@ export class Project { this.json = Object.freeze(packageJson); this.path = projectPath; - this.packageJsonLocation = resolvePath(this.path, 'package.json'); - this.nodeModulesLocation = resolvePath(this.path, 'node_modules'); - this.targetLocation = resolvePath(this.path, 'target'); + this.packageJsonLocation = Path.resolve(this.path, 'package.json'); + this.nodeModulesLocation = Path.resolve(this.path, 'node_modules'); + this.targetLocation = Path.resolve(this.path, 'target'); + this.version = this.json.version; this.productionDependencies = this.json.dependencies || {}; this.devDependencies = this.json.devDependencies || {}; this.allDependencies = { @@ -96,7 +109,7 @@ export class Project { if (dependentProjectIsInWorkspace) { expectedVersionInPackageJson = project.json.version; } else { - const relativePathToProject = normalizePath(relative(this.path, project.path)); + const relativePathToProject = normalizePath(Path.relative(this.path, project.path)); expectedVersionInPackageJson = `link:${relativePathToProject}`; } @@ -134,7 +147,7 @@ export class Project { * instead of everything located in the project directory. */ public getIntermediateBuildDirectory() { - return resolvePath(this.path, this.getBuildConfig().intermediateBuildDirectory || '.'); + return Path.resolve(this.path, this.getBuildConfig().intermediateBuildDirectory || '.'); } public getCleanConfig(): CleanConfig { @@ -154,14 +167,14 @@ export class Project { if (typeof raw === 'string') { return { - [this.name]: resolvePath(this.path, raw), + [this.name]: Path.resolve(this.path, raw), }; } if (typeof raw === 'object') { const binsConfig: { [k: string]: string } = {}; for (const binName of Object.keys(raw)) { - binsConfig[binName] = resolvePath(this.path, raw[binName]); + binsConfig[binName] = Path.resolve(this.path, raw[binName]); } return binsConfig; } @@ -221,7 +234,7 @@ export class Project { unusedWorkspaces.forEach(name => { const { dependencies, devDependencies } = this.json; - const nodeModulesPath = resolvePath(this.nodeModulesLocation, name); + const nodeModulesPath = Path.resolve(this.nodeModulesLocation, name); const isDependency = dependencies && dependencies.hasOwnProperty(name); const isDevDependency = devDependencies && devDependencies.hasOwnProperty(name); diff --git a/packages/kbn-pm/src/utils/project_checksums.ts b/packages/kbn-pm/src/utils/project_checksums.ts new file mode 100644 index 0000000000000..2fd24c8fc9577 --- /dev/null +++ b/packages/kbn-pm/src/utils/project_checksums.ts @@ -0,0 +1,257 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import Fs from 'fs'; +import Crypto from 'crypto'; + +import { promisify } from 'util'; + +import execa from 'execa'; +import { ToolingLog } from '@kbn/dev-utils'; + +import { readYarnLock, YarnLock } from './yarn_lock'; +import { ProjectMap } from '../utils/projects'; +import { Project } from '../utils/project'; +import { Kibana } from '../utils/kibana'; + +export type ChecksumMap = Map; +/** map of [repo relative path to changed file, type of change] */ +type Changes = Map; + +const statAsync = promisify(Fs.stat); +const projectBySpecificitySorter = (a: Project, b: Project) => b.path.length - a.path.length; + +/** Get the changed files for a set of projects */ +async function getChangesForProjects(projects: ProjectMap, kbn: Kibana, log: ToolingLog) { + log.verbose('getting changed files'); + + const { stdout } = await execa( + 'git', + ['ls-files', '-dmt', '--', ...Array.from(projects.values()).map(p => p.path)], + { + cwd: kbn.getAbsolute(), + } + ); + + const output = stdout.trim(); + const unassignedChanges: Changes = new Map(); + + if (output) { + for (const line of output.split('\n')) { + const [tag, ...pathParts] = line.trim().split(' '); + const path = pathParts.join(' '); + switch (tag) { + case 'M': + case 'C': + // for some reason ls-files returns deleted files as both deleted + // and modified, so make sure not to overwrite changes already + // tracked as "deleted" + if (unassignedChanges.get(path) !== 'deleted') { + unassignedChanges.set(path, 'modified'); + } + break; + + case 'R': + unassignedChanges.set(path, 'deleted'); + break; + + case 'H': + case 'S': + case 'K': + case '?': + default: + log.warning(`unexpected modification status "${tag}" for ${path}, please report this!`); + unassignedChanges.set(path, 'invalid'); + break; + } + } + } + + const sortedRelevantProjects = Array.from(projects.values()).sort(projectBySpecificitySorter); + const changesByProject = new Map(); + + for (const project of sortedRelevantProjects) { + const ownChanges: Changes = new Map(); + const prefix = kbn.getRelative(project.path); + + for (const [path, type] of unassignedChanges) { + if (path.startsWith(prefix)) { + ownChanges.set(path, type); + unassignedChanges.delete(path); + } + } + + log.verbose(`[${project.name}] found ${ownChanges.size} changes`); + changesByProject.set(project, ownChanges); + } + + if (unassignedChanges.size) { + throw new Error( + `unable to assign all change paths to a project: ${JSON.stringify( + Array.from(unassignedChanges.entries()) + )}` + ); + } + + return changesByProject; +} + +/** Get the latest commit sha for a project */ +async function getLatestSha(project: Project, kbn: Kibana) { + const { stdout } = await execa( + 'git', + ['log', '-n', '1', '--pretty=format:%H', '--', project.path], + { + cwd: kbn.getAbsolute(), + } + ); + + return stdout.trim() || undefined; +} + +/** + * Get a list of the absolute dependencies of this project, as resolved + * in the yarn.lock file, does not include other projects in the workspace + * or their dependencies + */ +function resolveDepsForProject(project: Project, yarnLock: YarnLock, kbn: Kibana, log: ToolingLog) { + /** map of [name@range, name@resolved] */ + const resolved = new Map(); + + const queue: Array<[string, string]> = Object.entries(project.allDependencies); + + while (queue.length) { + const [name, versionRange] = queue.shift()!; + const req = `${name}@${versionRange}`; + + if (resolved.has(req)) { + continue; + } + + if (!kbn.hasProject(name)) { + const pkg = yarnLock[req]; + if (!pkg) { + log.warning( + 'yarn.lock file is out of date, please run `yarn kbn bootstrap` to re-enable caching' + ); + return; + } + + const res = `${name}@${pkg.version}`; + resolved.set(req, res); + + const allDepsEntries = [ + ...Object.entries(pkg.dependencies || {}), + ...Object.entries(pkg.optionalDependencies || {}), + ]; + + for (const [childName, childVersionRange] of allDepsEntries) { + queue.push([childName, childVersionRange]); + } + } + } + + return Array.from(resolved.values()).sort((a, b) => a.localeCompare(b)); +} + +/** + * Get the checksum for a specific project in the workspace + */ +async function getChecksum( + project: Project, + changes: Changes, + yarnLock: YarnLock, + kbn: Kibana, + log: ToolingLog +) { + const sha = await getLatestSha(project, kbn); + if (sha) { + log.verbose(`[${project.name}] local sha:`, sha); + } + + if (Array.from(changes.values()).includes('invalid')) { + log.warning(`[${project.name}] unable to determine local changes, caching disabled`); + return; + } + + const changesSummary = await Promise.all( + Array.from(changes) + .sort((a, b) => a[0].localeCompare(b[0])) + .map(async ([path, type]) => { + if (type === 'deleted') { + return `${path}:deleted`; + } + + const stats = await statAsync(kbn.getAbsolute(path)); + log.verbose(`[${project.name}] modified time ${stats.mtimeMs} for ${path}`); + return `${path}:${stats.mtimeMs}`; + }) + ); + + const deps = await resolveDepsForProject(project, yarnLock, kbn, log); + if (!deps) { + return; + } + + log.verbose(`[${project.name}] resolved %d deps`, deps.length); + + const checksum = JSON.stringify( + { + sha, + changes: changesSummary, + deps, + }, + null, + 2 + ); + + if (process.env.BOOTSTRAP_CACHE_DEBUG_CHECKSUM) { + return checksum; + } + + const hash = Crypto.createHash('sha1'); + hash.update(checksum); + return hash.digest('hex'); +} + +/** + * Calculate checksums for all projects in the workspace based on + * - last git commit to project directory + * - un-committed changes + * - resolved dependencies from yarn.lock referenced by project package.json + */ +export async function getAllChecksums(kbn: Kibana, log: ToolingLog) { + const projects = kbn.getAllProjects(); + const changesByProject = await getChangesForProjects(projects, kbn, log); + const yarnLock = await readYarnLock(kbn); + + /** map of [project.name, cacheKey] */ + const cacheKeys: ChecksumMap = new Map(); + + await Promise.all( + Array.from(projects.values()).map(async project => { + cacheKeys.set( + project.name, + await getChecksum(project, changesByProject.get(project)!, yarnLock, kbn, log) + ); + }) + ); + + return cacheKeys; +} diff --git a/packages/kbn-pm/src/utils/projects.test.ts b/packages/kbn-pm/src/utils/projects.test.ts index 093f178f1813a..ba093b9d5eba1 100644 --- a/packages/kbn-pm/src/utils/projects.test.ts +++ b/packages/kbn-pm/src/utils/projects.test.ts @@ -80,7 +80,7 @@ describe('#getProjects', () => { }); test('includes additional projects in package.json', async () => { - const projectPaths = getProjectPaths(rootPath, {}); + const projectPaths = getProjectPaths({ rootPath }); const projects = await getProjects(rootPath, projectPaths); const expectedProjects = [ @@ -100,7 +100,7 @@ describe('#getProjects', () => { describe('with exclude/include filters', () => { let projectPaths: string[]; beforeEach(() => { - projectPaths = getProjectPaths(rootPath, {}); + projectPaths = getProjectPaths({ rootPath }); }); test('excludes projects specified in `exclude` filter', async () => { diff --git a/packages/kbn-pm/src/utils/projects.ts b/packages/kbn-pm/src/utils/projects.ts index e8a40cce3011a..a6f174b1fc5a1 100644 --- a/packages/kbn-pm/src/utils/projects.ts +++ b/packages/kbn-pm/src/utils/projects.ts @@ -27,6 +27,7 @@ import { workspacePackagePaths } from './workspaces'; const glob = promisify(globSync); +/** a Map of project names to Project instances */ export type ProjectMap = Map; export type ProjectGraph = Map; export interface IProjectsOptions { @@ -198,7 +199,7 @@ export function includeTransitiveProjects( allProjects: ProjectMap, { onlyProductionDependencies = false } = {} ) { - const dependentProjects: ProjectMap = new Map(); + const projectsWithDependents: ProjectMap = new Map(); // the current list of packages we are expanding using breadth-first-search const toProcess = [...subsetOfProjects]; @@ -216,8 +217,8 @@ export function includeTransitiveProjects( } }); - dependentProjects.set(project.name, project); + projectsWithDependents.set(project.name, project); } - return dependentProjects; + return projectsWithDependents; } diff --git a/packages/kbn-pm/src/utils/projects_tree.test.ts b/packages/kbn-pm/src/utils/projects_tree.test.ts index f8c99d22eaf22..693400080f4f8 100644 --- a/packages/kbn-pm/src/utils/projects_tree.test.ts +++ b/packages/kbn-pm/src/utils/projects_tree.test.ts @@ -43,7 +43,7 @@ test('handles projects outside root folder', async () => { }); test('handles projects within projects outside root folder', async () => { - const projectPaths = getProjectPaths(rootPath, {}); + const projectPaths = getProjectPaths({ rootPath }); const projects = await getProjects(rootPath, projectPaths); const tree = await renderProjectsTree(rootPath, projects); diff --git a/packages/kbn-pm/src/utils/workspaces.ts b/packages/kbn-pm/src/utils/workspaces.ts index 1cc5c5838989f..22fa8636aea90 100644 --- a/packages/kbn-pm/src/utils/workspaces.ts +++ b/packages/kbn-pm/src/utils/workspaces.ts @@ -56,7 +56,7 @@ export async function workspacePackagePaths(rootPath: string): Promise } export async function copyWorkspacePackages(rootPath: string): Promise { - const projectPaths = getProjectPaths(rootPath, {}); + const projectPaths = getProjectPaths({ rootPath }); const projects = await getProjects(rootPath, projectPaths); for (const project of projects.values()) { diff --git a/packages/kbn-pm/src/utils/yarn_lock.ts b/packages/kbn-pm/src/utils/yarn_lock.ts new file mode 100644 index 0000000000000..f46240c0e1d2e --- /dev/null +++ b/packages/kbn-pm/src/utils/yarn_lock.ts @@ -0,0 +1,63 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +// @ts-ignore published types are worthless +import { parse as parseLockfile } from '@yarnpkg/lockfile'; + +import { readFile } from '../utils/fs'; +import { Kibana } from '../utils/kibana'; + +export interface YarnLock { + /** a simple map of version@versionrange tags to metadata about a package */ + [key: string]: { + /** resolved version installed for this pacakge */ + version: string; + /** resolved url for this pacakge */ + resolved: string; + /** yarn calculated integrity value for this package */ + integrity: string; + dependencies?: { + /** name => versionRange dependencies listed in package's manifest */ + [key: string]: string; + }; + optionalDependencies?: { + /** name => versionRange dependencies listed in package's manifest */ + [key: string]: string; + }; + }; +} + +export async function readYarnLock(kbn: Kibana): Promise { + try { + const contents = await readFile(kbn.getAbsolute('yarn.lock'), 'utf8'); + const yarnLock = parseLockfile(contents); + + if (yarnLock.type === 'success') { + return yarnLock.object; + } + + throw new Error('unable to read yarn.lock file, please run `yarn kbn bootstrap`'); + } catch (error) { + if (error.code !== 'ENOENT') { + throw error; + } + } + + return {}; +} diff --git a/packages/kbn-pm/tsconfig.json b/packages/kbn-pm/tsconfig.json index 2141c5502ae98..bfb13ee8dcf8a 100644 --- a/packages/kbn-pm/tsconfig.json +++ b/packages/kbn-pm/tsconfig.json @@ -1,11 +1,10 @@ { "extends": "../../tsconfig.json", - "exclude": [ - "dist" - ], "include": [ "./src/**/*.ts", + "./dist/*.d.ts", ], + "exclude": [], "compilerOptions": { "types": [ "jest", diff --git a/packages/kbn-test/src/failed_tests_reporter/add_messages_to_report.test.ts b/packages/kbn-test/src/failed_tests_reporter/add_messages_to_report.test.ts index 0df95f419a1ff..0c824754b1237 100644 --- a/packages/kbn-test/src/failed_tests_reporter/add_messages_to_report.test.ts +++ b/packages/kbn-test/src/failed_tests_reporter/add_messages_to_report.test.ts @@ -36,7 +36,7 @@ expect.addSnapshotSerializer({ jest.mock('fs', () => { const realFs = jest.requireActual('fs'); return { - readFile: realFs.read, + ...realFs, writeFile: (...args: any[]) => { setTimeout(args[args.length - 1], 0); }, diff --git a/packages/kbn-ui-shared-deps/README.md b/packages/kbn-ui-shared-deps/README.md new file mode 100644 index 0000000000000..3d3ee37ca5a75 --- /dev/null +++ b/packages/kbn-ui-shared-deps/README.md @@ -0,0 +1,3 @@ +# `@kbn/ui-shared-deps` + +Shared dependencies that must only have a single instance are installed and re-exported from here. To consume them, import the package and merge the `externals` export into your webpack config so that all references to the supported modules will be remapped to use the global versions. \ No newline at end of file diff --git a/packages/kbn-ui-shared-deps/entry.js b/packages/kbn-ui-shared-deps/entry.js new file mode 100644 index 0000000000000..250abd162f91d --- /dev/null +++ b/packages/kbn-ui-shared-deps/entry.js @@ -0,0 +1,39 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +// must load before angular +export const Jquery = require('jquery'); +window.$ = window.jQuery = Jquery; + +export const Angular = require('angular'); +export const ElasticCharts = require('@elastic/charts'); +export const ElasticEui = require('@elastic/eui'); +export const ElasticEuiLibServices = require('@elastic/eui/lib/services'); +export const ElasticEuiLightTheme = require('@elastic/eui/dist/eui_theme_light.json'); +export const ElasticEuiDarkTheme = require('@elastic/eui/dist/eui_theme_dark.json'); +export const Moment = require('moment'); +export const MomentTimezone = require('moment-timezone/moment-timezone'); +export const React = require('react'); +export const ReactDom = require('react-dom'); +export const ReactIntl = require('react-intl'); +export const ReactRouter = require('react-router'); // eslint-disable-line +export const ReactRouterDom = require('react-router-dom'); + +// load timezone data into moment-timezone +Moment.tz.load(require('moment-timezone/data/packed/latest.json')); diff --git a/packages/kbn-ui-shared-deps/index.d.ts b/packages/kbn-ui-shared-deps/index.d.ts new file mode 100644 index 0000000000000..132445bbde745 --- /dev/null +++ b/packages/kbn-ui-shared-deps/index.d.ts @@ -0,0 +1,45 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * Absolute path to the distributable directory + */ +export const distDir: string; + +/** + * Filename of the main bundle file in the distributable directory + */ +export const distFilename: string; + +/** + * Filename of the dark-theme css file in the distributable directory + */ +export const darkCssDistFilename: string; + +/** + * Filename of the light-theme css file in the distributable directory + */ +export const lightCssDistFilename: string; + +/** + * Externals mapping inteded to be used in a webpack config + */ +export const externals: { + [key: string]: string; +}; diff --git a/packages/kbn-ui-shared-deps/index.js b/packages/kbn-ui-shared-deps/index.js new file mode 100644 index 0000000000000..cef25295b35d7 --- /dev/null +++ b/packages/kbn-ui-shared-deps/index.js @@ -0,0 +1,41 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +const Path = require('path'); + +exports.distDir = Path.resolve(__dirname, 'target'); +exports.distFilename = 'kbn-ui-shared-deps.js'; +exports.lightCssDistFilename = 'kbn-ui-shared-deps.light.css'; +exports.darkCssDistFilename = 'kbn-ui-shared-deps.dark.css'; +exports.externals = { + angular: '__kbnSharedDeps__.Angular', + '@elastic/charts': '__kbnSharedDeps__.ElasticCharts', + '@elastic/eui': '__kbnSharedDeps__.ElasticEui', + '@elastic/eui/lib/services': '__kbnSharedDeps__.ElasticEuiLibServices', + '@elastic/eui/dist/eui_theme_light.json': '__kbnSharedDeps__.ElasticEuiLightTheme', + '@elastic/eui/dist/eui_theme_dark.json': '__kbnSharedDeps__.ElasticEuiDarkTheme', + jquery: '__kbnSharedDeps__.Jquery', + moment: '__kbnSharedDeps__.Moment', + 'moment-timezone': '__kbnSharedDeps__.MomentTimezone', + react: '__kbnSharedDeps__.React', + 'react-dom': '__kbnSharedDeps__.ReactDom', + 'react-intl': '__kbnSharedDeps__.ReactIntl', + 'react-router': '__kbnSharedDeps__.ReactRouter', + 'react-router-dom': '__kbnSharedDeps__.ReactRouterDom', +}; diff --git a/packages/kbn-ui-shared-deps/package.json b/packages/kbn-ui-shared-deps/package.json new file mode 100644 index 0000000000000..014467d204d96 --- /dev/null +++ b/packages/kbn-ui-shared-deps/package.json @@ -0,0 +1,29 @@ +{ + "name": "@kbn/ui-shared-deps", + "version": "1.0.0", + "license": "Apache-2.0", + "private": true, + "scripts": { + "build": "node scripts/build", + "kbn:bootstrap": "node scripts/build --dev", + "kbn:watch": "node scripts/build --watch" + }, + "devDependencies": { + "@elastic/eui": "17.3.1", + "@elastic/charts": "^16.1.0", + "@kbn/dev-utils": "1.0.0", + "@yarnpkg/lockfile": "^1.1.0", + "angular": "^1.7.9", + "css-loader": "^2.1.1", + "del": "^5.1.0", + "jquery": "^3.4.1", + "mini-css-extract-plugin": "0.8.0", + "moment": "^2.24.0", + "moment-timezone": "^0.5.27", + "react-dom": "^16.12.0", + "react-intl": "^2.8.0", + "react": "^16.12.0", + "read-pkg": "^5.2.0", + "webpack": "4.41.0" + } +} \ No newline at end of file diff --git a/packages/kbn-ui-shared-deps/scripts/build.js b/packages/kbn-ui-shared-deps/scripts/build.js new file mode 100644 index 0000000000000..8b7c22dac24ff --- /dev/null +++ b/packages/kbn-ui-shared-deps/scripts/build.js @@ -0,0 +1,105 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +const Path = require('path'); + +const { run, createFailError } = require('@kbn/dev-utils'); +const webpack = require('webpack'); +const Stats = require('webpack/lib/Stats'); +const del = require('del'); + +const { getWebpackConfig } = require('../webpack.config'); + +run( + async ({ log, flags }) => { + log.info('cleaning previous build output'); + await del(Path.resolve(__dirname, '../target')); + + const compiler = webpack( + getWebpackConfig({ + dev: flags.dev, + }) + ); + + /** @param {webpack.Stats} stats */ + const onCompilationComplete = stats => { + const took = Math.round((stats.endTime - stats.startTime) / 1000); + + if (!stats.hasErrors() && !stats.hasWarnings()) { + log.success(`webpack completed in about ${took} seconds`); + return; + } + + throw createFailError( + `webpack failure in about ${took} seconds\n${stats.toString({ + colors: true, + ...Stats.presetToOptions('minimal'), + })}` + ); + }; + + if (flags.watch) { + compiler.hooks.done.tap('report on stats', stats => { + try { + onCompilationComplete(stats); + } catch (error) { + log.error(error.message); + } + }); + + compiler.hooks.watchRun.tap('report on start', () => { + process.stdout.cursorTo(0, 0); + process.stdout.clearScreenDown(); + log.info('Running webpack compilation...'); + }); + + compiler.watch({}, error => { + if (error) { + log.error('Fatal webpack error'); + log.error(error); + process.exit(1); + } + }); + + return; + } + + onCompilationComplete( + await new Promise((resolve, reject) => { + compiler.run((error, stats) => { + if (error) { + reject(error); + } else { + resolve(stats); + } + }); + }) + ); + }, + { + description: 'build @kbn/ui-shared-deps', + flags: { + boolean: ['watch', 'dev'], + help: ` + --watch Run in watch mode + --dev Build development friendly version + `, + }, + } +); diff --git a/packages/kbn-ui-shared-deps/tsconfig.json b/packages/kbn-ui-shared-deps/tsconfig.json new file mode 100644 index 0000000000000..c5c3cba147fcf --- /dev/null +++ b/packages/kbn-ui-shared-deps/tsconfig.json @@ -0,0 +1,6 @@ +{ + "extends": "../../tsconfig.json", + "include": [ + "index.d.ts" + ] +} diff --git a/packages/kbn-ui-shared-deps/webpack.config.js b/packages/kbn-ui-shared-deps/webpack.config.js new file mode 100644 index 0000000000000..87cca2cc897f8 --- /dev/null +++ b/packages/kbn-ui-shared-deps/webpack.config.js @@ -0,0 +1,90 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +const Path = require('path'); + +const MiniCssExtractPlugin = require('mini-css-extract-plugin'); +const { REPO_ROOT } = require('@kbn/dev-utils'); +const webpack = require('webpack'); + +const SharedDeps = require('./index'); + +const MOMENT_SRC = require.resolve('moment/min/moment-with-locales.js'); + +exports.getWebpackConfig = ({ dev = false } = {}) => ({ + mode: dev ? 'development' : 'production', + entry: { + [SharedDeps.distFilename.replace(/\.js$/, '')]: './entry.js', + [SharedDeps.darkCssDistFilename.replace(/\.css$/, '')]: [ + '@elastic/eui/dist/eui_theme_dark.css', + '@elastic/charts/dist/theme_only_dark.css', + ], + [SharedDeps.lightCssDistFilename.replace(/\.css$/, '')]: [ + '@elastic/eui/dist/eui_theme_light.css', + '@elastic/charts/dist/theme_only_light.css', + ], + }, + context: __dirname, + devtool: dev ? '#cheap-source-map' : false, + output: { + path: SharedDeps.distDir, + filename: '[name].js', + sourceMapFilename: '[file].map', + publicPath: '__REPLACE_WITH_PUBLIC_PATH__', + devtoolModuleFilenameTemplate: info => + `kbn-ui-shared-deps/${Path.relative(REPO_ROOT, info.absoluteResourcePath)}`, + library: '__kbnSharedDeps__', + }, + + module: { + noParse: [MOMENT_SRC], + rules: [ + { + test: /\.css$/, + use: [MiniCssExtractPlugin.loader, 'css-loader'], + }, + ], + }, + + resolve: { + alias: { + moment: MOMENT_SRC, + }, + }, + + optimization: { + noEmitOnErrors: true, + }, + + performance: { + // NOTE: we are disabling this as those hints + // are more tailored for the final bundles result + // and not for the webpack compilations performance itself + hints: false, + }, + + plugins: [ + new MiniCssExtractPlugin({ + filename: '[name].css', + }), + new webpack.DefinePlugin({ + 'process.env.NODE_ENV': dev ? '"development"' : '"production"', + }), + ], +}); diff --git a/renovate.json5 b/renovate.json5 index ecc9b3b2ceb62..7f67fae894110 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', @@ -345,6 +210,14 @@ '@types/dedent', ], }, + { + groupSlug: 'deep-freeze-strict', + groupName: 'deep-freeze-strict related packages', + packageNames: [ + 'deep-freeze-strict', + '@types/deep-freeze-strict', + ], + }, { groupSlug: 'delete-empty', groupName: 'delete-empty related packages', @@ -353,6 +226,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 +250,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 +273,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 +289,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 +322,395 @@ ], }, { - groupSlug: 'has-ansi', - groupName: 'has-ansi related packages', - packageNames: [ - 'has-ansi', - '@types/has-ansi', + groupSlug: 'graphql', + groupName: 'graphql related packages', + packagePatterns: [ + '(\\b|_)graphql(\\b|_)', + '(\\b|_)apollo(\\b|_)', ], }, { - groupSlug: 'history', - groupName: 'history related packages', - packageNames: [ - 'history', - '@types/history', + groupSlug: 'grunt', + groupName: 'grunt related packages', + packagePatterns: [ + '(\\b|_)grunt(\\b|_)', ], }, { - groupSlug: 'jquery', - groupName: 'jquery related packages', - packageNames: [ - 'jquery', - '@types/jquery', + groupSlug: 'gulp', + groupName: 'gulp related packages', + packagePatterns: [ + '(\\b|_)gulp(\\b|_)', ], }, { - groupSlug: 'js-yaml', - groupName: 'js-yaml related packages', - packageNames: [ - 'js-yaml', - '@types/js-yaml', + groupSlug: 'hapi', + groupName: 'hapi related packages', + packagePatterns: [ + '(\\b|_)hapi(\\b|_)', ], - }, - { - groupSlug: 'json5', - groupName: 'json5 related packages', packageNames: [ - 'json5', - '@types/json5', + '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: 'license-checker', - groupName: 'license-checker related packages', + groupSlug: 'has-ansi', + groupName: 'has-ansi related packages', packageNames: [ - 'license-checker', - '@types/license-checker', + 'has-ansi', + '@types/has-ansi', ], }, { - groupSlug: 'listr', - groupName: 'listr related packages', + groupSlug: 'history', + groupName: 'history related packages', packageNames: [ - 'listr', - '@types/listr', + 'history', + '@types/history', ], }, { - groupSlug: 'lodash', - groupName: 'lodash related packages', + groupSlug: 'indent-string', + groupName: 'indent-string related packages', packageNames: [ - 'lodash', - '@types/lodash', + 'indent-string', + '@types/indent-string', ], }, { - groupSlug: 'lru-cache', - groupName: 'lru-cache related packages', + groupSlug: 'intl-relativeformat', + groupName: 'intl-relativeformat related packages', packageNames: [ - 'lru-cache', - '@types/lru-cache', + 'intl-relativeformat', + '@types/intl-relativeformat', ], }, { - groupSlug: 'markdown-it', - groupName: 'markdown-it related packages', - packageNames: [ - 'markdown-it', - '@types/markdown-it', + groupSlug: 'jest', + groupName: 'jest related packages', + packagePatterns: [ + '(\\b|_)jest(\\b|_)', ], }, { - groupSlug: 'minimatch', - groupName: 'minimatch related packages', + groupSlug: 'jquery', + groupName: 'jquery related packages', packageNames: [ - 'minimatch', - '@types/minimatch', + 'jquery', + '@types/jquery', ], }, { - groupSlug: 'mustache', - groupName: 'mustache related packages', + groupSlug: 'js-yaml', + groupName: 'js-yaml related packages', packageNames: [ - 'mustache', - '@types/mustache', + 'js-yaml', + '@types/js-yaml', ], }, { - groupSlug: 'node', - groupName: 'node related packages', + groupSlug: 'jsdom', + groupName: 'jsdom related packages', packageNames: [ - 'node', - '@types/node', + 'jsdom', + '@types/jsdom', ], }, { - groupSlug: 'opn', - groupName: 'opn related packages', + groupSlug: 'json-stable-stringify', + groupName: 'json-stable-stringify related packages', packageNames: [ - 'opn', - '@types/opn', + 'json-stable-stringify', + '@types/json-stable-stringify', ], }, { - groupSlug: 'pngjs', - groupName: 'pngjs related packages', + groupSlug: 'json5', + groupName: 'json5 related packages', packageNames: [ - 'pngjs', - '@types/pngjs', + 'json5', + '@types/json5', ], }, { - groupSlug: 'podium', - groupName: 'podium related packages', + groupSlug: 'jsonwebtoken', + groupName: 'jsonwebtoken related packages', packageNames: [ - 'podium', - '@types/podium', + 'jsonwebtoken', + '@types/jsonwebtoken', ], }, { - groupSlug: '@reach/router', - groupName: '@reach/router related packages', + groupSlug: 'jsts', + groupName: 'jsts related packages', packageNames: [ - '@reach/router', - '@types/reach__router', + 'jsts', + '@types/jsts', ], + allowedVersions: '^1.6.2', }, { - groupSlug: 'request', - groupName: 'request related packages', - packageNames: [ - 'request', - '@types/request', + groupSlug: 'karma', + groupName: 'karma related packages', + packagePatterns: [ + '(\\b|_)karma(\\b|_)', ], }, { - groupSlug: 'selenium-webdriver', - groupName: 'selenium-webdriver related packages', + groupSlug: 'language server', + groupName: 'language server related packages', packageNames: [ - 'selenium-webdriver', - '@types/selenium-webdriver', + 'vscode-jsonrpc', + '@types/vscode-jsonrpc', + 'vscode-languageserver', + '@types/vscode-languageserver', + 'vscode-languageserver-types', + '@types/vscode-languageserver-types', ], }, { - groupSlug: 'semver', - groupName: 'semver related packages', + groupSlug: 'license-checker', + groupName: 'license-checker related packages', packageNames: [ - 'semver', - '@types/semver', + 'license-checker', + '@types/license-checker', ], }, { - groupSlug: 'sinon', - groupName: 'sinon related packages', + groupSlug: 'listr', + groupName: 'listr related packages', packageNames: [ - 'sinon', - '@types/sinon', + 'listr', + '@types/listr', ], }, { - groupSlug: 'strip-ansi', - groupName: 'strip-ansi related packages', + groupSlug: 'lodash', + groupName: 'lodash related packages', packageNames: [ - 'strip-ansi', - '@types/strip-ansi', + 'lodash', + '@types/lodash', ], }, { - groupSlug: 'styled-components', - groupName: 'styled-components related packages', + groupSlug: 'lodash.clonedeep', + groupName: 'lodash.clonedeep related packages', packageNames: [ - 'styled-components', - '@types/styled-components', + 'lodash.clonedeep', + '@types/lodash.clonedeep', ], }, { - groupSlug: 'supertest', - groupName: 'supertest related packages', + groupSlug: 'lodash.clonedeepwith', + groupName: 'lodash.clonedeepwith related packages', packageNames: [ - 'supertest', - '@types/supertest', + 'lodash.clonedeepwith', + '@types/lodash.clonedeepwith', ], }, { - groupSlug: 'supertest-as-promised', - groupName: 'supertest-as-promised related packages', + groupSlug: 'log-symbols', + groupName: 'log-symbols related packages', packageNames: [ - 'supertest-as-promised', - '@types/supertest-as-promised', + 'log-symbols', + '@types/log-symbols', ], }, { - groupSlug: 'type-detect', - groupName: 'type-detect related packages', + groupSlug: 'lru-cache', + groupName: 'lru-cache related packages', packageNames: [ - 'type-detect', - '@types/type-detect', + 'lru-cache', + '@types/lru-cache', ], }, { - groupSlug: 'uuid', - groupName: 'uuid related packages', + groupSlug: 'mapbox-gl', + groupName: 'mapbox-gl related packages', packageNames: [ - 'uuid', - '@types/uuid', + 'mapbox-gl', + '@types/mapbox-gl', ], }, { - groupSlug: 'vinyl-fs', - groupName: 'vinyl-fs related packages', + groupSlug: 'markdown-it', + groupName: 'markdown-it related packages', packageNames: [ - 'vinyl-fs', - '@types/vinyl-fs', + 'markdown-it', + '@types/markdown-it', ], }, { - groupSlug: 'zen-observable', - groupName: 'zen-observable related packages', + groupSlug: 'memoize-one', + groupName: 'memoize-one related packages', packageNames: [ - 'zen-observable', - '@types/zen-observable', + 'memoize-one', + '@types/memoize-one', ], }, { - groupSlug: 'archiver', - groupName: 'archiver related packages', + groupSlug: 'mime', + groupName: 'mime related packages', packageNames: [ - 'archiver', - '@types/archiver', + 'mime', + '@types/mime', ], }, { - groupSlug: 'base64-js', - groupName: 'base64-js related packages', + groupSlug: 'minimatch', + groupName: 'minimatch related packages', packageNames: [ - 'base64-js', - '@types/base64-js', + 'minimatch', + '@types/minimatch', ], }, { - groupSlug: 'chroma-js', - groupName: 'chroma-js related packages', - packageNames: [ - 'chroma-js', - '@types/chroma-js', + groupSlug: 'mocha', + groupName: 'mocha related packages', + packagePatterns: [ + '(\\b|_)mocha(\\b|_)', ], }, { - groupSlug: 'color', - groupName: 'color related packages', - packageNames: [ - 'color', - '@types/color', + groupSlug: 'moment', + groupName: 'moment related packages', + packagePatterns: [ + '(\\b|_)moment(\\b|_)', ], }, { - groupSlug: 'cytoscape', - groupName: 'cytoscape related packages', + groupSlug: 'mustache', + groupName: 'mustache related packages', packageNames: [ - 'cytoscape', - '@types/cytoscape', + 'mustache', + '@types/mustache', ], }, { - groupSlug: 'fancy-log', - groupName: 'fancy-log related packages', + groupSlug: 'ncp', + groupName: 'ncp related packages', packageNames: [ - 'fancy-log', - '@types/fancy-log', + 'ncp', + '@types/ncp', ], }, { - groupSlug: 'file-saver', - groupName: 'file-saver related packages', + groupSlug: 'nock', + groupName: 'nock related packages', packageNames: [ - 'file-saver', - '@types/file-saver', + 'nock', + '@types/nock', ], }, { - groupSlug: 'getos', - groupName: 'getos related packages', + groupSlug: 'node', + groupName: 'node related packages', packageNames: [ - 'getos', - '@types/getos', + 'node', + '@types/node', ], }, { - groupSlug: 'git-url-parse', - groupName: 'git-url-parse related packages', + groupSlug: 'node-fetch', + groupName: 'node-fetch related packages', packageNames: [ - 'git-url-parse', - '@types/git-url-parse', + 'node-fetch', + '@types/node-fetch', ], }, { - groupSlug: 'jsdom', - groupName: 'jsdom related packages', + groupSlug: 'node-forge', + groupName: 'node-forge related packages', packageNames: [ - 'jsdom', - '@types/jsdom', + 'node-forge', + '@types/node-forge', ], }, { - 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 +729,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 +767,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 +946,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/cli/serve/serve.js b/src/cli/serve/serve.js index 976eac7f95da6..9cf5691b88399 100644 --- a/src/cli/serve/serve.js +++ b/src/cli/serve/serve.js @@ -102,6 +102,8 @@ function applyConfigOverrides(rawConfig, opts, extraCliOptions) { } ensureNotDefined('server.ssl.certificate'); ensureNotDefined('server.ssl.key'); + ensureNotDefined('server.ssl.keystore.path'); + ensureNotDefined('server.ssl.truststore.path'); ensureNotDefined('elasticsearch.ssl.certificateAuthorities'); const elasticsearchHosts = ( @@ -119,6 +121,8 @@ function applyConfigOverrides(rawConfig, opts, extraCliOptions) { }); set('server.ssl.enabled', true); + // TODO: change this cert/key to KBN_CERT_PATH and KBN_KEY_PATH from '@kbn/dev-utils'; will require some work to avoid breaking + // functional tests. Once that is done, the existing test cert/key at DEV_SSL_CERT_PATH and DEV_SSL_KEY_PATH can be deleted. set('server.ssl.certificate', DEV_SSL_CERT_PATH); set('server.ssl.key', DEV_SSL_KEY_PATH); set('elasticsearch.hosts', elasticsearchHosts); @@ -140,23 +144,12 @@ function applyConfigOverrides(rawConfig, opts, extraCliOptions) { } set('plugins.scanDirs', _.compact([].concat(get('plugins.scanDirs'), opts.pluginDir))); - set( 'plugins.paths', _.compact( [].concat( get('plugins.paths'), opts.pluginPath, - opts.runExamples - ? [ - // Ideally this would automatically include all plugins in the examples dir - fromRoot('examples/demo_search'), - fromRoot('examples/search_explorer'), - fromRoot('examples/embeddable_examples'), - fromRoot('examples/embeddable_explorer'), - ] - : [], - XPACK_INSTALLED && !opts.oss ? [XPACK_DIR] : [] ) ) @@ -253,6 +246,7 @@ export default function(program) { silent: !!opts.silent, watch: !!opts.watch, repl: !!opts.repl, + runExamples: !!opts.runExamples, // We want to run without base path when the `--run-examples` flag is given so that we can use local // links in other documentation sources, like "View this tutorial [here](http://localhost:5601/app/tutorial/xyz)". // We can tell users they only have to run with `yarn start --run-examples` to get those diff --git a/src/core/MIGRATION.md b/src/core/MIGRATION.md index 1c78de966c46f..173d73ffab664 100644 --- a/src/core/MIGRATION.md +++ b/src/core/MIGRATION.md @@ -55,6 +55,7 @@ - [Provide Legacy Platform API to the New platform plugin](#provide-legacy-platform-api-to-the-new-platform-plugin) - [On the server side](#on-the-server-side) - [On the client side](#on-the-client-side) + - [Updates an application navlink at runtime](#updates-an-app-navlink-at-runtime) Make no mistake, it is going to take a lot of work to move certain plugins to the new platform. Our target is to migrate the entire repo over to the new platform throughout 7.x and to remove the legacy plugin system no later than 8.0, and this is only possible if teams start on the effort now. @@ -1194,6 +1195,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) | | @@ -1623,3 +1625,31 @@ class MyPlugin { It's not currently possible to use a similar pattern on the client-side. Because Legacy platform plugins heavily rely on global angular modules, which aren't available on the new platform. So you can utilize the same approach for only *stateless Angular components*, as long as they are not consumed by a New Platform application. When New Platform applications are on the page, no legacy code is executed, so the `registerLegacyAPI` function would not be called. + +### Updates an application navlink at runtime + +The application API now provides a way to updates some of a registered application's properties after registration. + +```typescript +// inside your plugin's setup function +export class MyPlugin implements Plugin { + private appUpdater = new BehaviorSubject(() => ({})); + setup({ application }) { + application.register({ + id: 'my-app', + title: 'My App', + updater$: this.appUpdater, + async mount(params) { + const { renderApp } = await import('./application'); + return renderApp(params); + }, + }); + } + start() { + // later, when the navlink needs to be updated + appUpdater.next(() => { + navLinkStatus: AppNavLinkStatus.disabled, + tooltip: 'Application disabled', + }) + } +``` \ No newline at end of file diff --git a/src/core/README.md b/src/core/README.md index 8863658e0040c..f4539191d448b 100644 --- a/src/core/README.md +++ b/src/core/README.md @@ -7,6 +7,7 @@ Core Plugin API Documentation: - [Core Public API](/docs/development/core/public/kibana-plugin-public.md) - [Core Server API](/docs/development/core/server/kibana-plugin-server.md) - [Conventions for Plugins](./CONVENTIONS.md) + - [Testing Kibana Plugins](./TESTING.md) - [Migration guide for porting existing plugins](./MIGRATION.md) Internal Documentation: diff --git a/src/core/TESTING.md b/src/core/TESTING.md new file mode 100644 index 0000000000000..6139820d02a14 --- /dev/null +++ b/src/core/TESTING.md @@ -0,0 +1,254 @@ +# Testing Kibana Plugins + +This document outlines best practices and patterns for testing Kibana Plugins. + +- [Strategy](#strategy) +- [Core Integrations](#core-integrations) + - [Core Mocks](#core-mocks) + - [Strategies for specific Core APIs](#strategies-for-specific-core-apis) + - [HTTP Routes](#http-routes) + - [SavedObjects](#savedobjects) + - [Elasticsearch](#elasticsearch) +- [Plugin Integrations](#plugin-integrations) +- [Plugin Contracts](#plugin-contracts) + +## Strategy + +In general, we recommend three tiers of tests: +- Unit tests: small, fast, exhaustive, make heavy use of mocks for external dependencies +- Integration tests: higher-level tests that verify interactions between systems (eg. HTTP APIs, Elasticsearch API calls, calling other plugin contracts). +- End-to-end tests (e2e): tests that verify user-facing behavior through the browser + +These tiers should roughly follow the traditional ["testing pyramid"](https://martinfowler.com/articles/practical-test-pyramid.html), where there are more exhaustive testing at the unit level, fewer at the integration level, and very few at the functional level. + +## New concerns in the Kibana Platform + +The Kibana Platform introduces new concepts that legacy plugins did not have concern themselves with. Namely: +- **Lifecycles**: plugins now have explicit lifecycle methods that must interop with Core APIs and other plugins. +- **Shared runtime**: plugins now all run in the same process at the same time. On the frontend, this is different behavior than the legacy plugins. Developers should take care not to break other plugins when interacting with their enviornment (Node.js or Browser). +- **Single page application**: Kibana's frontend is now a single-page application where all plugins are running, but only one application is mounted at a time. Plugins need to handle mounting and unmounting, cleanup, and avoid overriding global browser behaviors in this shared space. +- **Dependency management**: plugins must now explicitly declare their dependencies on other plugins, both required and optional. Plugins should ensure to test conditions where a optional dependency is missing. + +Simply porting over existing tests when migrating your plugin to the Kibana Platform will leave blind spots in test coverage. It is highly recommended that plugins add new tests that cover these new concerns. + +## Core Integrations + +### Core Mocks + +When testing a plugin's integration points with Core APIs, it is heavily recommended to utilize the mocks provided in `src/core/server/mocks` and `src/core/public/mocks`. The majority of these mocks are dumb `jest` mocks that mimic the interface of their respective Core APIs, however they do not return realistic return values. + +If the unit under test expects a particular response from a Core API, the test will need to set this return value explicitly. The return values are type checked to match the Core API where possible to ensure that mocks are updated when Core APIs changed. + +#### Example + +```ts +import { elasticsearchServiceMock } from 'src/core/server/mocks'; + +test('my test', async () => { + // Setup mock and faked response + const esClient = elasticsearchServiceMock.createScopedClusterClient(); + esClient.callAsCurrentUser.mockResolvedValue(/** insert ES response here */); + + // Call unit under test with mocked client + const result = await myFunction(esClient); + + // Assert that client was called with expected arguments + expect(esClient.callAsCurrentUser).toHaveBeenCalledWith(/** expected args */); + // Expect that unit under test returns expected value based on client's response + expect(result).toEqual(/** expected return value */) +}); +``` + +### Strategies for specific Core APIs + +#### HTTP Routes + +_How to test route handlers_ + +### Applications + +Kibana Platform applications have less control over the page than legacy applications did. It is important that your app is built to handle it's co-habitance with other plugins in the browser. Applications are mounted and unmounted from the DOM as the user navigates between them, without full-page refreshes, as a single-page application (SPA). + +These long-lived sessions make cleanup more important than before. It's entirely possible a user has a single browsing session open for weeks at a time, without ever doing a full-page refresh. Common things that need to be cleaned up (and tested!) when your application is unmounted: +- Subscriptions and polling (eg. `uiSettings.get$()`) +- Any Core API calls that set state (eg. `core.chrome.setIsVisible`). +- Open connections (eg. a Websocket) + +While applications do get an opportunity to unmount and run cleanup logic, it is also important that you do not _depend_ on this logic to run. The browser tab may get closed without running cleanup logic, so it is not guaranteed to be run. For instance, you should not depend on unmounting logic to run in order to save state to `localStorage` or to the backend. + +#### Example + +By following the [renderApp](./CONVENTIONS.md#applications) convention, you can greatly reduce the amount of logic in your application's mount function. This makes testing your application's actual rendering logic easier. + +```tsx +/** public/plugin.ts */ +class Plugin { + setup(core) { + core.application.register({ + // id, title, etc. + async mount(params) { + const [{ renderApp }, [coreStart, startDeps]] = await Promise.all([ + import('./application'), + core.getStartServices() + ]); + + return renderApp(params, coreStart, startDeps); + } + }) + } +} +``` + +We _could_ still write tests for this logic, but you may find that you're just asserting the same things that would be covered by type-checks. + +
+See example + +```ts +/** public/plugin.test.ts */ +jest.mock('./application', () => ({ renderApp: jest.fn() })); +import { coreMock } from 'src/core/public/mocks'; +import { renderApp: renderAppMock } from './application'; +import { Plugin } from './plugin'; + +describe('Plugin', () => { + it('registers an app', () => { + const coreSetup = coreMock.createSetup(); + new Plugin(coreMock.createPluginInitializerContext()).setup(coreSetup); + expect(coreSetup.application.register).toHaveBeenCalledWith({ + id: 'myApp', + mount: expect.any(Function) + }); + }); + + // Test the glue code from Plugin -> renderApp + it('application.mount wires up dependencies to renderApp', async () => { + const coreSetup = coreMock.createSetup(); + const [coreStartMock, startDepsMock] = await coreSetup.getStartServices(); + const unmountMock = jest.fn(); + renderAppMock.mockReturnValue(unmountMock); + const params = { element: document.createElement('div'), appBasePath: '/fake/base/path' }; + + new Plugin(coreMock.createPluginInitializerContext()).setup(coreSetup); + // Grab registered mount function + const mount = coreSetup.application.register.mock.calls[0][0].mount; + + const unmount = await mount(params); + expect(renderAppMock).toHaveBeenCalledWith(params, coreStartMock, startDepsMock); + expect(unmount).toBe(unmountMock); + }); +}); +``` + +
+ +The more interesting logic is in `renderApp`: + +```ts +/** public/application.ts */ +import React from 'react'; +import ReactDOM from 'react-dom'; + +import { AppMountParams, CoreStart } from 'src/core/public'; +import { AppRoot } from './components/app_root'; + +export const renderApp = ({ element, appBasePath }: AppMountParams, core: CoreStart, plugins: MyPluginDepsStart) => { + // Hide the chrome while this app is mounted for a full screen experience + core.chrome.setIsVisible(false); + + // uiSettings subscription + const uiSettingsClient = core.uiSettings.client; + const pollingSubscription = uiSettingClient.get$('mysetting1').subscribe(async mySetting1 => { + const value = core.http.fetch(/** use `mySetting1` in request **/); + // ... + }); + + // Render app + ReactDOM.render( + , + element + ); + + return () => { + // Unmount UI + ReactDOM.unmountComponentAtNode(element); + // Close any subscriptions + pollingSubscription.unsubscribe(); + // Make chrome visible again + core.chrome.setIsVisible(true); + }; +}; +``` + +In testing `renderApp` you should be verifying that: +1) Your application mounts and unmounts correctly +2) Cleanup logic is completed as expected + +```ts +/** public/application.test.ts */ +import { coreMock } from 'src/core/public/mocks'; +import { renderApp } from './application'; + +describe('renderApp', () => { + it('mounts and unmounts UI', () => { + const params = { element: document.createElement('div'), appBasePath: '/fake/base/path' }; + const core = coreMock.createStart(); + + // Verify some expected DOM element is rendered into the element + const unmount = renderApp(params, core, {}); + expect(params.element.querySelector('.some-app-class')).not.toBeUndefined(); + // Verify the element is empty after unmounting + unmount(); + expect(params.element.innerHTML).toEqual(''); + }); + + it('unsubscribes from uiSettings', () => { + const params = { element: document.createElement('div'), appBasePath: '/fake/base/path' }; + const core = coreMock.createStart(); + // Create a fake Subject you can use to monitor observers + const settings$ = new Subject(); + core.uiSettings.get$.mockReturnValue(settings$); + + // Verify mounting adds an observer + const unmount = renderApp(params, core, {}); + expect(settings$.observers.length).toBe(1); + // Verify no observers remaining after unmount is called + unmount(); + expect(settings$.observers.length).toBe(0); + }); + + it('resets chrome visibility', () => { + const params = { element: document.createElement('div'), appBasePath: '/fake/base/path' }; + const core = coreMock.createStart(); + + // Verify stateful Core API was called on mount + const unmount = renderApp(params, core, {}); + expect(core.chrome.setIsVisible).toHaveBeenCalledWith(false); + core.chrome.setIsVisible.mockClear(); // reset mock + // Verify stateful Core API was called on unmount + unmount(); + expect(core.chrome.setIsVisible).toHaveBeenCalledWith(true); + }) +}); +``` + +#### SavedObjects + +_How to test SO operations_ + +#### Elasticsearch + +_How to test ES clients_ + +## Plugin Integrations + +_How to test against specific plugin APIs (eg. data plugin)_ + +## Plugin Contracts + +_How to test your plugin's exposed API_ + +Guidelines: +- Plugins should never interact with other plugins' REST API directly +- Plugins should interact with other plugins via JavaScript contracts +- Exposed contracts need to be well tested to ensure breaking changes are detected easily diff --git a/src/core/public/application/application_leave.test.ts b/src/core/public/application/application_leave.test.ts new file mode 100644 index 0000000000000..e06183d8bb8d9 --- /dev/null +++ b/src/core/public/application/application_leave.test.ts @@ -0,0 +1,49 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { isConfirmAction, getLeaveAction } from './application_leave'; +import { AppLeaveActionType } from './types'; + +describe('isConfirmAction', () => { + it('returns true if action is confirm', () => { + expect(isConfirmAction({ type: AppLeaveActionType.confirm, text: 'message' })).toEqual(true); + }); + it('returns false if action is default', () => { + expect(isConfirmAction({ type: AppLeaveActionType.default })).toEqual(false); + }); +}); + +describe('getLeaveAction', () => { + it('returns the default action provided by the handler', () => { + expect(getLeaveAction(actions => actions.default())).toEqual({ + type: AppLeaveActionType.default, + }); + }); + it('returns the confirm action provided by the handler', () => { + expect(getLeaveAction(actions => actions.confirm('some message'))).toEqual({ + type: AppLeaveActionType.confirm, + text: 'some message', + }); + expect(getLeaveAction(actions => actions.confirm('another message', 'a title'))).toEqual({ + type: AppLeaveActionType.confirm, + text: 'another message', + title: 'a title', + }); + }); +}); diff --git a/src/core/public/application/application_leave.tsx b/src/core/public/application/application_leave.tsx new file mode 100644 index 0000000000000..7b69d70d3f6f6 --- /dev/null +++ b/src/core/public/application/application_leave.tsx @@ -0,0 +1,46 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { + AppLeaveActionFactory, + AppLeaveActionType, + AppLeaveAction, + AppLeaveConfirmAction, + AppLeaveHandler, +} from './types'; + +const appLeaveActionFactory: AppLeaveActionFactory = { + confirm(text: string, title?: string) { + return { type: AppLeaveActionType.confirm, text, title }; + }, + default() { + return { type: AppLeaveActionType.default }; + }, +}; + +export function isConfirmAction(action: AppLeaveAction): action is AppLeaveConfirmAction { + return action.type === AppLeaveActionType.confirm; +} + +export function getLeaveAction(handler?: AppLeaveHandler): AppLeaveAction { + if (!handler) { + return appLeaveActionFactory.default(); + } + return handler(appLeaveActionFactory); +} diff --git a/src/core/public/application/application_service.mock.ts b/src/core/public/application/application_service.mock.ts index b2e2161c92cc8..dee47315fc322 100644 --- a/src/core/public/application/application_service.mock.ts +++ b/src/core/public/application/application_service.mock.ts @@ -17,7 +17,7 @@ * under the License. */ -import { Subject } from 'rxjs'; +import { BehaviorSubject, Subject } from 'rxjs'; import { capabilitiesServiceMock } from './capabilities/capabilities_service.mock'; import { @@ -25,17 +25,21 @@ import { InternalApplicationStart, ApplicationStart, InternalApplicationSetup, + App, + LegacyApp, } from './types'; import { ApplicationServiceContract } from './test_types'; const createSetupContractMock = (): jest.Mocked => ({ register: jest.fn(), + registerAppUpdater: jest.fn(), registerMountContext: jest.fn(), }); const createInternalSetupContractMock = (): jest.Mocked => ({ register: jest.fn(), registerLegacyApp: jest.fn(), + registerAppUpdater: jest.fn(), registerMountContext: jest.fn(), }); @@ -50,8 +54,7 @@ const createInternalStartContractMock = (): jest.Mocked(); return { - availableApps: new Map(), - availableLegacyApps: new Map(), + applications$: new BehaviorSubject>(new Map()), capabilities: capabilitiesServiceMock.createStartContract().capabilities, currentAppId$: currentAppId$.asObservable(), getComponent: jest.fn(), diff --git a/src/core/public/application/application_service.test.ts b/src/core/public/application/application_service.test.ts index d064b17ace142..4672a42c9eb06 100644 --- a/src/core/public/application/application_service.test.ts +++ b/src/core/public/application/application_service.test.ts @@ -18,24 +18,42 @@ */ import { createElement } from 'react'; -import { Subject } from 'rxjs'; -import { bufferCount, skip, takeUntil } from 'rxjs/operators'; +import { BehaviorSubject, Subject } from 'rxjs'; +import { bufferCount, skip, take, takeUntil } from 'rxjs/operators'; import { shallow } from 'enzyme'; import { injectedMetadataServiceMock } from '../injected_metadata/injected_metadata_service.mock'; import { contextServiceMock } from '../context/context_service.mock'; import { httpServiceMock } from '../http/http_service.mock'; +import { overlayServiceMock } from '../overlays/overlay_service.mock'; import { MockCapabilitiesService, MockHistory } from './application_service.test.mocks'; import { MockLifecycle } from './test_types'; import { ApplicationService } from './application_service'; - -function mount() {} +import { App, AppNavLinkStatus, AppStatus, AppUpdater, LegacyApp } from './types'; + +const createApp = (props: Partial): App => { + return { + id: 'some-id', + title: 'some-title', + mount: () => () => undefined, + ...props, + }; +}; + +const createLegacyApp = (props: Partial): LegacyApp => { + return { + id: 'some-id', + title: 'some-title', + appUrl: '/my-url', + ...props, + }; +}; + +let setupDeps: MockLifecycle<'setup'>; +let startDeps: MockLifecycle<'start'>; +let service: ApplicationService; describe('#setup()', () => { - let setupDeps: MockLifecycle<'setup'>; - let startDeps: MockLifecycle<'start'>; - let service: ApplicationService; - beforeEach(() => { const http = httpServiceMock.createSetupContract({ basePath: '/test' }); setupDeps = { @@ -44,7 +62,7 @@ describe('#setup()', () => { injectedMetadata: injectedMetadataServiceMock.createSetupContract(), }; setupDeps.injectedMetadata.getLegacyMode.mockReturnValue(false); - startDeps = { http, injectedMetadata: setupDeps.injectedMetadata }; + startDeps = { http, overlays: overlayServiceMock.createStartContract() }; service = new ApplicationService(); }); @@ -52,9 +70,9 @@ describe('#setup()', () => { it('throws an error if two apps with the same id are registered', () => { const { register } = service.setup(setupDeps); - register(Symbol(), { id: 'app1', mount } as any); + register(Symbol(), createApp({ id: 'app1' })); expect(() => - register(Symbol(), { id: 'app1', mount } as any) + register(Symbol(), createApp({ id: 'app1' })) ).toThrowErrorMatchingInlineSnapshot( `"An application is already registered with the id \\"app1\\""` ); @@ -65,37 +83,91 @@ describe('#setup()', () => { await service.start(startDeps); expect(() => - register(Symbol(), { id: 'app1', mount } as any) + register(Symbol(), createApp({ id: 'app1' })) ).toThrowErrorMatchingInlineSnapshot(`"Applications cannot be registered after \\"setup\\""`); }); + it('allows to register a statusUpdater for the application', async () => { + const setup = service.setup(setupDeps); + + const pluginId = Symbol('plugin'); + const updater$ = new BehaviorSubject(app => ({})); + setup.register(pluginId, createApp({ id: 'app1', updater$ })); + setup.register(pluginId, createApp({ id: 'app2' })); + const { applications$ } = await service.start(startDeps); + + let applications = await applications$.pipe(take(1)).toPromise(); + expect(applications.size).toEqual(2); + expect(applications.get('app1')).toEqual( + expect.objectContaining({ + id: 'app1', + legacy: false, + navLinkStatus: AppNavLinkStatus.default, + status: AppStatus.accessible, + }) + ); + expect(applications.get('app2')).toEqual( + expect.objectContaining({ + id: 'app2', + legacy: false, + navLinkStatus: AppNavLinkStatus.default, + status: AppStatus.accessible, + }) + ); + + updater$.next(app => ({ + status: AppStatus.inaccessible, + tooltip: 'App inaccessible due to reason', + })); + + applications = await applications$.pipe(take(1)).toPromise(); + expect(applications.size).toEqual(2); + expect(applications.get('app1')).toEqual( + expect.objectContaining({ + id: 'app1', + legacy: false, + navLinkStatus: AppNavLinkStatus.default, + status: AppStatus.inaccessible, + tooltip: 'App inaccessible due to reason', + }) + ); + expect(applications.get('app2')).toEqual( + expect.objectContaining({ + id: 'app2', + legacy: false, + navLinkStatus: AppNavLinkStatus.default, + status: AppStatus.accessible, + }) + ); + }); + it('throws an error if an App with the same appRoute is registered', () => { const { register, registerLegacyApp } = service.setup(setupDeps); - register(Symbol(), { id: 'app1', mount } as any); + register(Symbol(), createApp({ id: 'app1' })); expect(() => - register(Symbol(), { id: 'app2', mount, appRoute: '/app/app1' } as any) + register(Symbol(), createApp({ id: 'app2', appRoute: '/app/app1' })) ).toThrowErrorMatchingInlineSnapshot( `"An application is already registered with the appRoute \\"/app/app1\\""` ); - expect(() => registerLegacyApp({ id: 'app1' } as any)).not.toThrow(); + expect(() => registerLegacyApp(createLegacyApp({ id: 'app1' }))).toThrow(); - register(Symbol(), { id: 'app-next', mount, appRoute: '/app/app3' } as any); + register(Symbol(), createApp({ id: 'app-next', appRoute: '/app/app3' })); expect(() => - register(Symbol(), { id: 'app2', mount, appRoute: '/app/app3' } as any) + register(Symbol(), createApp({ id: 'app2', appRoute: '/app/app3' })) ).toThrowErrorMatchingInlineSnapshot( `"An application is already registered with the appRoute \\"/app/app3\\""` ); - expect(() => registerLegacyApp({ id: 'app3' } as any)).not.toThrow(); + expect(() => registerLegacyApp(createLegacyApp({ id: 'app3' }))).not.toThrow(); }); it('throws an error if an App starts with the HTTP base path', () => { const { register } = service.setup(setupDeps); expect(() => - register(Symbol(), { id: 'app2', mount, appRoute: '/test/app2' } as any) + register(Symbol(), createApp({ id: 'app2', appRoute: '/test/app2' })) ).toThrowErrorMatchingInlineSnapshot( `"Cannot register an application route that includes HTTP base path"` ); @@ -106,9 +178,11 @@ describe('#setup()', () => { it('throws an error if two apps with the same id are registered', () => { const { registerLegacyApp } = service.setup(setupDeps); - registerLegacyApp({ id: 'app2' } as any); - expect(() => registerLegacyApp({ id: 'app2' } as any)).toThrowErrorMatchingInlineSnapshot( - `"A legacy application is already registered with the id \\"app2\\""` + registerLegacyApp(createLegacyApp({ id: 'app2' })); + expect(() => + registerLegacyApp(createLegacyApp({ id: 'app2' })) + ).toThrowErrorMatchingInlineSnapshot( + `"An application is already registered with the id \\"app2\\""` ); }); @@ -116,22 +190,228 @@ describe('#setup()', () => { const { registerLegacyApp } = service.setup(setupDeps); await service.start(startDeps); - expect(() => registerLegacyApp({ id: 'app2' } as any)).toThrowErrorMatchingInlineSnapshot( - `"Applications cannot be registered after \\"setup\\""` - ); + expect(() => + registerLegacyApp(createLegacyApp({ id: 'app2' })) + ).toThrowErrorMatchingInlineSnapshot(`"Applications cannot be registered after \\"setup\\""`); }); it('throws an error if a LegacyApp with the same appRoute is registered', () => { const { register, registerLegacyApp } = service.setup(setupDeps); - registerLegacyApp({ id: 'app1' } as any); + registerLegacyApp(createLegacyApp({ id: 'app1' })); expect(() => - register(Symbol(), { id: 'app2', mount, appRoute: '/app/app1' } as any) + register(Symbol(), createApp({ id: 'app2', appRoute: '/app/app1' })) ).toThrowErrorMatchingInlineSnapshot( `"An application is already registered with the appRoute \\"/app/app1\\""` ); - expect(() => registerLegacyApp({ id: 'app1:other' } as any)).not.toThrow(); + expect(() => registerLegacyApp(createLegacyApp({ id: 'app1:other' }))).not.toThrow(); + }); + }); + + describe('registerAppStatusUpdater', () => { + it('updates status fields', async () => { + const setup = service.setup(setupDeps); + + const pluginId = Symbol('plugin'); + setup.register(pluginId, createApp({ id: 'app1' })); + setup.register(pluginId, createApp({ id: 'app2' })); + setup.registerAppUpdater( + new BehaviorSubject(app => { + if (app.id === 'app1') { + return { + status: AppStatus.inaccessible, + navLinkStatus: AppNavLinkStatus.disabled, + tooltip: 'App inaccessible due to reason', + }; + } + return { + tooltip: 'App accessible', + }; + }) + ); + const start = await service.start(startDeps); + const applications = await start.applications$.pipe(take(1)).toPromise(); + + expect(applications.size).toEqual(2); + expect(applications.get('app1')).toEqual( + expect.objectContaining({ + id: 'app1', + legacy: false, + navLinkStatus: AppNavLinkStatus.disabled, + status: AppStatus.inaccessible, + tooltip: 'App inaccessible due to reason', + }) + ); + expect(applications.get('app2')).toEqual( + expect.objectContaining({ + id: 'app2', + legacy: false, + navLinkStatus: AppNavLinkStatus.default, + status: AppStatus.accessible, + tooltip: 'App accessible', + }) + ); + }); + + it(`properly combine with application's updater$`, async () => { + const setup = service.setup(setupDeps); + const pluginId = Symbol('plugin'); + const appStatusUpdater$ = new BehaviorSubject(app => ({ + status: AppStatus.inaccessible, + navLinkStatus: AppNavLinkStatus.disabled, + })); + setup.register(pluginId, createApp({ id: 'app1', updater$: appStatusUpdater$ })); + setup.register(pluginId, createApp({ id: 'app2' })); + + setup.registerAppUpdater( + new BehaviorSubject(app => { + if (app.id === 'app1') { + return { + status: AppStatus.accessible, + tooltip: 'App inaccessible due to reason', + }; + } + return { + status: AppStatus.inaccessible, + navLinkStatus: AppNavLinkStatus.hidden, + }; + }) + ); + + const { applications$ } = await service.start(startDeps); + const applications = await applications$.pipe(take(1)).toPromise(); + + expect(applications.size).toEqual(2); + expect(applications.get('app1')).toEqual( + expect.objectContaining({ + id: 'app1', + legacy: false, + navLinkStatus: AppNavLinkStatus.disabled, + status: AppStatus.inaccessible, + tooltip: 'App inaccessible due to reason', + }) + ); + expect(applications.get('app2')).toEqual( + expect.objectContaining({ + id: 'app2', + legacy: false, + status: AppStatus.inaccessible, + navLinkStatus: AppNavLinkStatus.hidden, + }) + ); + }); + + it('applies the most restrictive status in case of multiple updaters', async () => { + const setup = service.setup(setupDeps); + + const pluginId = Symbol('plugin'); + setup.register(pluginId, createApp({ id: 'app1' })); + setup.registerAppUpdater( + new BehaviorSubject(app => { + return { + status: AppStatus.inaccessible, + navLinkStatus: AppNavLinkStatus.disabled, + }; + }) + ); + setup.registerAppUpdater( + new BehaviorSubject(app => { + return { + status: AppStatus.accessible, + navLinkStatus: AppNavLinkStatus.default, + }; + }) + ); + + const start = await service.start(startDeps); + const applications = await start.applications$.pipe(take(1)).toPromise(); + + expect(applications.size).toEqual(1); + expect(applications.get('app1')).toEqual( + expect.objectContaining({ + id: 'app1', + legacy: false, + navLinkStatus: AppNavLinkStatus.disabled, + status: AppStatus.inaccessible, + }) + ); + }); + + it('emits on applications$ when a status updater changes', async () => { + const setup = service.setup(setupDeps); + + const pluginId = Symbol('plugin'); + setup.register(pluginId, createApp({ id: 'app1' })); + + const statusUpdater = new BehaviorSubject(app => { + return { + status: AppStatus.inaccessible, + navLinkStatus: AppNavLinkStatus.disabled, + }; + }); + setup.registerAppUpdater(statusUpdater); + + const start = await service.start(startDeps); + let latestValue: ReadonlyMap = new Map(); + start.applications$.subscribe(apps => { + latestValue = apps; + }); + + expect(latestValue.get('app1')).toEqual( + expect.objectContaining({ + id: 'app1', + legacy: false, + status: AppStatus.inaccessible, + navLinkStatus: AppNavLinkStatus.disabled, + }) + ); + + statusUpdater.next(app => { + return { + status: AppStatus.accessible, + navLinkStatus: AppNavLinkStatus.hidden, + }; + }); + + expect(latestValue.get('app1')).toEqual( + expect.objectContaining({ + id: 'app1', + legacy: false, + status: AppStatus.accessible, + navLinkStatus: AppNavLinkStatus.hidden, + }) + ); + }); + + it('also updates legacy apps', async () => { + const setup = service.setup(setupDeps); + + setup.registerLegacyApp(createLegacyApp({ id: 'app1' })); + + setup.registerAppUpdater( + new BehaviorSubject(app => { + return { + status: AppStatus.inaccessible, + navLinkStatus: AppNavLinkStatus.hidden, + tooltip: 'App inaccessible due to reason', + }; + }) + ); + + const start = await service.start(startDeps); + const applications = await start.applications$.pipe(take(1)).toPromise(); + + expect(applications.size).toEqual(1); + expect(applications.get('app1')).toEqual( + expect.objectContaining({ + id: 'app1', + legacy: true, + status: AppStatus.inaccessible, + navLinkStatus: AppNavLinkStatus.hidden, + tooltip: 'App inaccessible due to reason', + }) + ); }); }); @@ -140,18 +420,16 @@ describe('#setup()', () => { const container = setupDeps.context.createContextContainer.mock.results[0].value; const pluginId = Symbol(); - registerMountContext(pluginId, 'test' as any, mount as any); + const mount = () => () => undefined; + registerMountContext(pluginId, 'test' as any, mount); expect(container.registerContext).toHaveBeenCalledWith(pluginId, 'test', mount); }); }); describe('#start()', () => { - let setupDeps: MockLifecycle<'setup'>; - let startDeps: MockLifecycle<'start'>; - let service: ApplicationService; - beforeEach(() => { MockHistory.push.mockReset(); + const http = httpServiceMock.createSetupContract({ basePath: '/test' }); setupDeps = { http, @@ -159,7 +437,7 @@ describe('#start()', () => { injectedMetadata: injectedMetadataServiceMock.createSetupContract(), }; setupDeps.injectedMetadata.getLegacyMode.mockReturnValue(false); - startDeps = { http, injectedMetadata: setupDeps.injectedMetadata }; + startDeps = { http, overlays: overlayServiceMock.createStartContract() }; service = new ApplicationService(); }); @@ -173,35 +451,40 @@ describe('#start()', () => { setupDeps.injectedMetadata.getLegacyMode.mockReturnValue(true); const { register, registerLegacyApp } = service.setup(setupDeps); - register(Symbol(), { id: 'app1', mount } as any); - registerLegacyApp({ id: 'app2' } as any); - - const { availableApps, availableLegacyApps } = await service.start(startDeps); - - expect(availableApps).toMatchInlineSnapshot(` - Map { - "app1" => Object { - "appRoute": "/app/app1", - "id": "app1", - "mount": [Function], - }, - } - `); - expect(availableLegacyApps).toMatchInlineSnapshot(` - Map { - "app2" => Object { - "id": "app2", - }, - } - `); + register(Symbol(), createApp({ id: 'app1' })); + registerLegacyApp(createLegacyApp({ id: 'app2' })); + + const { applications$ } = await service.start(startDeps); + const availableApps = await applications$.pipe(take(1)).toPromise(); + + expect(availableApps.size).toEqual(2); + expect([...availableApps.keys()]).toEqual(['app1', 'app2']); + expect(availableApps.get('app1')).toEqual( + expect.objectContaining({ + appRoute: '/app/app1', + id: 'app1', + legacy: false, + navLinkStatus: AppNavLinkStatus.default, + status: AppStatus.accessible, + }) + ); + expect(availableApps.get('app2')).toEqual( + expect.objectContaining({ + appUrl: '/my-url', + id: 'app2', + legacy: true, + navLinkStatus: AppNavLinkStatus.default, + status: AppStatus.accessible, + }) + ); }); it('passes appIds to capabilities', async () => { const { register } = service.setup(setupDeps); - register(Symbol(), { id: 'app1', mount } as any); - register(Symbol(), { id: 'app2', mount } as any); - register(Symbol(), { id: 'app3', mount } as any); + register(Symbol(), createApp({ id: 'app1' })); + register(Symbol(), createApp({ id: 'app2' })); + register(Symbol(), createApp({ id: 'app3' })); await service.start(startDeps); expect(MockCapabilitiesService.start).toHaveBeenCalledWith({ @@ -224,29 +507,15 @@ describe('#start()', () => { const { register, registerLegacyApp } = service.setup(setupDeps); - register(Symbol(), { id: 'app1', mount } as any); - registerLegacyApp({ id: 'legacyApp1' } as any); - register(Symbol(), { id: 'app2', mount } as any); - registerLegacyApp({ id: 'legacyApp2' } as any); + register(Symbol(), createApp({ id: 'app1' })); + registerLegacyApp(createLegacyApp({ id: 'legacyApp1' })); + register(Symbol(), createApp({ id: 'app2' })); + registerLegacyApp(createLegacyApp({ id: 'legacyApp2' })); - const { availableApps, availableLegacyApps } = await service.start(startDeps); + const { applications$ } = await service.start(startDeps); + const availableApps = await applications$.pipe(take(1)).toPromise(); - expect(availableApps).toMatchInlineSnapshot(` - Map { - "app1" => Object { - "appRoute": "/app/app1", - "id": "app1", - "mount": [Function], - }, - } - `); - expect(availableLegacyApps).toMatchInlineSnapshot(` - Map { - "legacyApp1" => Object { - "id": "legacyApp1", - }, - } - `); + expect([...availableApps.keys()]).toEqual(['app1', 'legacyApp1']); }); describe('getComponent', () => { @@ -264,6 +533,7 @@ describe('#start()', () => { } } mounters={Map {}} + setAppLeaveHandler={[Function]} /> `); }); @@ -291,9 +561,9 @@ describe('#start()', () => { it('creates URL for registered appId', async () => { const { register, registerLegacyApp } = service.setup(setupDeps); - register(Symbol(), { id: 'app1', mount } as any); - registerLegacyApp({ id: 'legacyApp1' } as any); - register(Symbol(), { id: 'app2', mount, appRoute: '/custom/path' } as any); + register(Symbol(), createApp({ id: 'app1' })); + registerLegacyApp(createLegacyApp({ id: 'legacyApp1' })); + register(Symbol(), createApp({ id: 'app2', appRoute: '/custom/path' })); const { getUrlForApp } = await service.start(startDeps); @@ -320,41 +590,41 @@ describe('#start()', () => { const { navigateToApp } = await service.start(startDeps); - navigateToApp('myTestApp'); + await navigateToApp('myTestApp'); expect(MockHistory.push).toHaveBeenCalledWith('/app/myTestApp', undefined); - navigateToApp('myOtherApp'); + await navigateToApp('myOtherApp'); expect(MockHistory.push).toHaveBeenCalledWith('/app/myOtherApp', undefined); }); it('changes the browser history for custom appRoutes', async () => { const { register } = service.setup(setupDeps); - register(Symbol(), { id: 'app2', mount, appRoute: '/custom/path' } as any); + register(Symbol(), createApp({ id: 'app2', appRoute: '/custom/path' })); const { navigateToApp } = await service.start(startDeps); - navigateToApp('myTestApp'); + await navigateToApp('myTestApp'); expect(MockHistory.push).toHaveBeenCalledWith('/app/myTestApp', undefined); - navigateToApp('app2'); + await navigateToApp('app2'); expect(MockHistory.push).toHaveBeenCalledWith('/custom/path', undefined); }); it('appends a path if specified', async () => { const { register } = service.setup(setupDeps); - register(Symbol(), { id: 'app2', mount, appRoute: '/custom/path' } as any); + register(Symbol(), createApp({ id: 'app2', appRoute: '/custom/path' })); const { navigateToApp } = await service.start(startDeps); - navigateToApp('myTestApp', { path: 'deep/link/to/location/2' }); + await navigateToApp('myTestApp', { path: 'deep/link/to/location/2' }); expect(MockHistory.push).toHaveBeenCalledWith( '/app/myTestApp/deep/link/to/location/2', undefined ); - navigateToApp('app2', { path: 'deep/link/to/location/2' }); + await navigateToApp('app2', { path: 'deep/link/to/location/2' }); expect(MockHistory.push).toHaveBeenCalledWith( '/custom/path/deep/link/to/location/2', undefined @@ -364,14 +634,14 @@ describe('#start()', () => { it('includes state if specified', async () => { const { register } = service.setup(setupDeps); - register(Symbol(), { id: 'app2', mount, appRoute: '/custom/path' } as any); + register(Symbol(), createApp({ id: 'app2', appRoute: '/custom/path' })); const { navigateToApp } = await service.start(startDeps); - navigateToApp('myTestApp', { state: 'my-state' }); + await navigateToApp('myTestApp', { state: 'my-state' }); expect(MockHistory.push).toHaveBeenCalledWith('/app/myTestApp', 'my-state'); - navigateToApp('app2', { state: 'my-state' }); + await navigateToApp('app2', { state: 'my-state' }); expect(MockHistory.push).toHaveBeenCalledWith('/custom/path', 'my-state'); }); @@ -382,7 +652,7 @@ describe('#start()', () => { const { navigateToApp } = await service.start(startDeps); - navigateToApp('myTestApp'); + await navigateToApp('myTestApp'); expect(setupDeps.redirectTo).toHaveBeenCalledWith('/test/app/myTestApp'); }); @@ -430,7 +700,7 @@ describe('#start()', () => { const { registerLegacyApp } = service.setup(setupDeps); - registerLegacyApp({ id: 'baseApp:legacyApp1' } as any); + registerLegacyApp(createLegacyApp({ id: 'baseApp:legacyApp1' })); const { navigateToApp } = await service.start(startDeps); @@ -439,3 +709,39 @@ describe('#start()', () => { }); }); }); + +describe('#stop()', () => { + let addListenerSpy: jest.SpyInstance; + let removeListenerSpy: jest.SpyInstance; + + beforeEach(() => { + addListenerSpy = jest.spyOn(window, 'addEventListener'); + removeListenerSpy = jest.spyOn(window, 'removeEventListener'); + + MockHistory.push.mockReset(); + const http = httpServiceMock.createSetupContract({ basePath: '/test' }); + setupDeps = { + http, + context: contextServiceMock.createSetupContract(), + injectedMetadata: injectedMetadataServiceMock.createSetupContract(), + }; + setupDeps.injectedMetadata.getLegacyMode.mockReturnValue(false); + startDeps = { http, overlays: overlayServiceMock.createStartContract() }; + service = new ApplicationService(); + }); + + afterEach(() => { + jest.restoreAllMocks(); + }); + + it('removes the beforeunload listener', async () => { + service.setup(setupDeps); + await service.start(startDeps); + expect(addListenerSpy).toHaveBeenCalledTimes(1); + expect(addListenerSpy).toHaveBeenCalledWith('beforeunload', expect.any(Function)); + const handler = addListenerSpy.mock.calls[0][1]; + service.stop(); + expect(removeListenerSpy).toHaveBeenCalledTimes(1); + expect(removeListenerSpy).toHaveBeenCalledWith('beforeunload', handler); + }); +}); diff --git a/src/core/public/application/application_service.tsx b/src/core/public/application/application_service.tsx index a96b9dea9b9c7..c69b96274aa95 100644 --- a/src/core/public/application/application_service.tsx +++ b/src/core/public/application/application_service.tsx @@ -18,31 +18,40 @@ */ import React from 'react'; -import { BehaviorSubject, Subject } from 'rxjs'; -import { takeUntil } from 'rxjs/operators'; +import { BehaviorSubject, Observable, Subject, Subscription } from 'rxjs'; +import { map, takeUntil } from 'rxjs/operators'; import { createBrowserHistory, History } from 'history'; -import { InjectedMetadataSetup, InjectedMetadataStart } from '../injected_metadata'; +import { InjectedMetadataSetup } from '../injected_metadata'; import { HttpSetup, HttpStart } from '../http'; +import { OverlayStart } from '../overlays'; import { ContextSetup, IContextContainer } from '../context'; import { AppRouter } from './ui'; -import { CapabilitiesService, Capabilities } from './capabilities'; +import { Capabilities, CapabilitiesService } from './capabilities'; import { App, - LegacyApp, + AppBase, + AppLeaveHandler, AppMount, AppMountDeprecated, AppMounter, - LegacyAppMounter, - Mounter, + AppNavLinkStatus, + AppStatus, + AppUpdatableFields, + AppUpdater, InternalApplicationSetup, InternalApplicationStart, + LegacyApp, + LegacyAppMounter, + Mounter, } from './types'; +import { getLeaveAction, isConfirmAction } from './application_leave'; interface SetupDeps { context: ContextSetup; http: HttpSetup; injectedMetadata: InjectedMetadataSetup; + history?: History; /** * Only necessary for redirecting to legacy apps * @deprecated @@ -51,19 +60,20 @@ interface SetupDeps { } interface StartDeps { - injectedMetadata: InjectedMetadataStart; http: HttpStart; + overlays: OverlayStart; } // Mount functions with two arguments are assumed to expect deprecated `context` object. const isAppMountDeprecated = (mount: (...args: any[]) => any): mount is AppMountDeprecated => mount.length === 2; -const filterAvailable = (map: Map, capabilities: Capabilities) => - new Map( - [...map].filter( +function filterAvailable(m: Map, capabilities: Capabilities) { + return new Map( + [...m].filter( ([id]) => capabilities.navLinks[id] === undefined || capabilities.navLinks[id] === true ) ); +} const findMounter = (mounters: Map, appRoute?: string) => [...mounters].find(([, mounter]) => mounter.appRoute === appRoute); const getAppUrl = (mounters: Map, appId: string, path: string = '') => @@ -71,16 +81,25 @@ const getAppUrl = (mounters: Map, appId: string, path: string = .replace(/\/{2,}/g, '/') // Remove duplicate slashes .replace(/\/$/, ''); // Remove trailing slash +const allApplicationsFilter = '__ALL__'; + +interface AppUpdaterWrapper { + application: string; + updater: AppUpdater; +} + /** * Service that is responsible for registering new applications. * @internal */ export class ApplicationService { - private readonly apps = new Map(); - private readonly legacyApps = new Map(); + private readonly apps = new Map(); private readonly mounters = new Map(); private readonly capabilities = new CapabilitiesService(); + private readonly appLeaveHandlers = new Map(); private currentAppId$ = new BehaviorSubject(undefined); + private readonly statusUpdaters$ = new BehaviorSubject>(new Map()); + private readonly subscriptions: Subscription[] = []; private stop$ = new Subject(); private registrationClosed = false; private history?: History; @@ -92,19 +111,34 @@ export class ApplicationService { http: { basePath }, injectedMetadata, redirectTo = (path: string) => (window.location.href = path), + history, }: SetupDeps): InternalApplicationSetup { const basename = basePath.get(); // Only setup history if we're not in legacy mode if (!injectedMetadata.getLegacyMode()) { - this.history = createBrowserHistory({ basename }); + this.history = history || createBrowserHistory({ basename }); } // If we do not have history available, use redirectTo to do a full page refresh. this.navigate = (url, state) => // basePath not needed here because `history` is configured with basename this.history ? this.history.push(url, state) : redirectTo(basePath.prepend(url)); + this.mountContext = context.createContextContainer(); + const registerStatusUpdater = (application: string, updater$: Observable) => { + const updaterId = Symbol(); + const subscription = updater$.subscribe(updater => { + const nextValue = new Map(this.statusUpdaters$.getValue()); + nextValue.set(updaterId, { + application, + updater, + }); + this.statusUpdaters$.next(nextValue); + }); + this.subscriptions.push(subscription); + }; + return { registerMountContext: this.mountContext!.registerContext, register: (plugin, app) => { @@ -139,7 +173,17 @@ export class ApplicationService { this.currentAppId$.next(app.id); return unmount; }; - this.apps.set(app.id, app); + + const { updater$, ...appProps } = app; + this.apps.set(app.id, { + ...appProps, + status: app.status ?? AppStatus.accessible, + navLinkStatus: app.navLinkStatus ?? AppNavLinkStatus.default, + legacy: false, + }); + if (updater$) { + registerStatusUpdater(app.id, updater$); + } this.mounters.set(app.id, { appRoute: app.appRoute!, appBasePath: basePath.prepend(app.appRoute!), @@ -152,15 +196,25 @@ export class ApplicationService { if (this.registrationClosed) { throw new Error('Applications cannot be registered after "setup"'); - } else if (this.legacyApps.has(app.id)) { - throw new Error(`A legacy application is already registered with the id "${app.id}"`); + } else if (this.apps.has(app.id)) { + throw new Error(`An application is already registered with the id "${app.id}"`); } else if (basename && appRoute!.startsWith(basename)) { throw new Error('Cannot register an application route that includes HTTP base path'); } const appBasePath = basePath.prepend(appRoute); const mount: LegacyAppMounter = () => redirectTo(appBasePath); - this.legacyApps.set(app.id, app); + + const { updater$, ...appProps } = app; + this.apps.set(app.id, { + ...appProps, + status: app.status ?? AppStatus.accessible, + navLinkStatus: app.navLinkStatus ?? AppNavLinkStatus.default, + legacy: true, + }); + if (updater$) { + registerStatusUpdater(app.id, updater$); + } this.mounters.set(app.id, { appRoute, appBasePath, @@ -168,40 +222,138 @@ export class ApplicationService { unmountBeforeMounting: true, }); }, + registerAppUpdater: (appUpdater$: Observable) => + registerStatusUpdater(allApplicationsFilter, appUpdater$), }; } - public async start({ injectedMetadata, http }: StartDeps): Promise { + public async start({ http, overlays }: StartDeps): Promise { if (!this.mountContext) { throw new Error('ApplicationService#setup() must be invoked before start.'); } this.registrationClosed = true; + window.addEventListener('beforeunload', this.onBeforeUnload); + const { capabilities } = await this.capabilities.start({ appIds: [...this.mounters.keys()], http, }); const availableMounters = filterAvailable(this.mounters, capabilities); + const availableApps = filterAvailable(this.apps, capabilities); + + const applications$ = new BehaviorSubject(availableApps); + this.statusUpdaters$ + .pipe( + map(statusUpdaters => { + return new Map( + [...availableApps].map(([id, app]) => [ + id, + updateStatus(app, [...statusUpdaters.values()]), + ]) + ); + }) + ) + .subscribe(apps => applications$.next(apps)); return { - availableApps: filterAvailable(this.apps, capabilities), - availableLegacyApps: filterAvailable(this.legacyApps, capabilities), + applications$, capabilities, currentAppId$: this.currentAppId$.pipe(takeUntil(this.stop$)), registerMountContext: this.mountContext.registerContext, getUrlForApp: (appId, { path }: { path?: string } = {}) => getAppUrl(availableMounters, appId, path), - navigateToApp: (appId, { path, state }: { path?: string; state?: any } = {}) => { - this.navigate!(getAppUrl(availableMounters, appId, path), state); - this.currentAppId$.next(appId); + navigateToApp: async (appId, { path, state }: { path?: string; state?: any } = {}) => { + const app = applications$.value.get(appId); + if (app && app.status !== AppStatus.accessible) { + // should probably redirect to the error page instead + throw new Error(`Trying to navigate to an inaccessible application: ${appId}`); + } + if (await this.shouldNavigate(overlays)) { + this.appLeaveHandlers.delete(this.currentAppId$.value!); + this.navigate!(getAppUrl(availableMounters, appId, path), state); + this.currentAppId$.next(appId); + } + }, + getComponent: () => { + if (!this.history) { + return null; + } + return ( + + ); }, - getComponent: () => - this.history ? : null, }; } + private setAppLeaveHandler = (appId: string, handler: AppLeaveHandler) => { + this.appLeaveHandlers.set(appId, handler); + }; + + private async shouldNavigate(overlays: OverlayStart): Promise { + const currentAppId = this.currentAppId$.value; + if (currentAppId === undefined) { + return true; + } + const action = getLeaveAction(this.appLeaveHandlers.get(currentAppId)); + if (isConfirmAction(action)) { + const confirmed = await overlays.openConfirm(action.text, { + title: action.title, + 'data-test-subj': 'appLeaveConfirmModal', + }); + if (!confirmed) { + return false; + } + } + return true; + } + + private onBeforeUnload = (event: Event) => { + const currentAppId = this.currentAppId$.value; + if (currentAppId === undefined) { + return; + } + const action = getLeaveAction(this.appLeaveHandlers.get(currentAppId)); + if (isConfirmAction(action)) { + event.preventDefault(); + // some browsers accept a string return value being the message displayed + event.returnValue = action.text as any; + } + }; + public stop() { this.stop$.next(); this.currentAppId$.complete(); + this.statusUpdaters$.complete(); + this.subscriptions.forEach(sub => sub.unsubscribe()); + window.removeEventListener('beforeunload', this.onBeforeUnload); } } + +const updateStatus = (app: T, statusUpdaters: AppUpdaterWrapper[]): T => { + let changes: Partial = {}; + statusUpdaters.forEach(wrapper => { + if (wrapper.application !== allApplicationsFilter && wrapper.application !== app.id) { + return; + } + const fields = wrapper.updater(app); + if (fields) { + changes = { + ...changes, + ...fields, + // status and navLinkStatus enums are ordered by reversed priority + // if multiple updaters wants to change these fields, we will always follow the priority order. + status: Math.max(changes.status ?? 0, fields.status ?? 0), + navLinkStatus: Math.max(changes.navLinkStatus ?? 0, fields.navLinkStatus ?? 0), + }; + } + }); + return { + ...app, + ...changes, + }; +}; diff --git a/src/core/public/application/index.ts b/src/core/public/application/index.ts index 9c4427c772a5e..e7ea330657648 100644 --- a/src/core/public/application/index.ts +++ b/src/core/public/application/index.ts @@ -27,8 +27,17 @@ export { AppUnmount, AppMountContext, AppMountParameters, + AppStatus, + AppNavLinkStatus, + AppUpdatableFields, + AppUpdater, ApplicationSetup, ApplicationStart, + AppLeaveHandler, + AppLeaveActionType, + AppLeaveAction, + AppLeaveDefaultAction, + AppLeaveConfirmAction, // Internal types InternalApplicationStart, LegacyApp, diff --git a/src/core/public/application/integration_tests/application_service.test.tsx b/src/core/public/application/integration_tests/application_service.test.tsx new file mode 100644 index 0000000000000..edf3583f384b8 --- /dev/null +++ b/src/core/public/application/integration_tests/application_service.test.tsx @@ -0,0 +1,167 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { createRenderer } from './utils'; +import { createMemoryHistory, MemoryHistory } from 'history'; +import { ApplicationService } from '../application_service'; +import { httpServiceMock } from '../../http/http_service.mock'; +import { contextServiceMock } from '../../context/context_service.mock'; +import { injectedMetadataServiceMock } from '../../injected_metadata/injected_metadata_service.mock'; +import { MockLifecycle } from '../test_types'; +import { overlayServiceMock } from '../../overlays/overlay_service.mock'; +import { AppMountParameters } from '../types'; + +describe('ApplicationService', () => { + let setupDeps: MockLifecycle<'setup'>; + let startDeps: MockLifecycle<'start'>; + let service: ApplicationService; + let history: MemoryHistory; + let update: ReturnType; + + const navigate = (path: string) => { + history.push(path); + return update(); + }; + + beforeEach(() => { + history = createMemoryHistory(); + const http = httpServiceMock.createSetupContract({ basePath: '/test' }); + + http.post.mockResolvedValue({ navLinks: {} }); + + setupDeps = { + http, + context: contextServiceMock.createSetupContract(), + injectedMetadata: injectedMetadataServiceMock.createSetupContract(), + history: history as any, + }; + setupDeps.injectedMetadata.getLegacyMode.mockReturnValue(false); + startDeps = { http, overlays: overlayServiceMock.createStartContract() }; + service = new ApplicationService(); + }); + + describe('leaving an application that registered an app leave handler', () => { + it('navigates to the new app if action is default', async () => { + startDeps.overlays.openConfirm.mockResolvedValue(true); + + const { register } = service.setup(setupDeps); + + register(Symbol(), { + id: 'app1', + title: 'App1', + mount: ({ onAppLeave }: AppMountParameters) => { + onAppLeave(actions => actions.default()); + return () => undefined; + }, + }); + register(Symbol(), { + id: 'app2', + title: 'App2', + mount: ({}: AppMountParameters) => { + return () => undefined; + }, + }); + + const { navigateToApp, getComponent } = await service.start(startDeps); + + update = createRenderer(getComponent()); + + await navigate('/app/app1'); + await navigateToApp('app2'); + + expect(startDeps.overlays.openConfirm).not.toHaveBeenCalled(); + expect(history.entries.length).toEqual(3); + expect(history.entries[2].pathname).toEqual('/app/app2'); + }); + + it('navigates to the new app if action is confirm and user accepted', async () => { + startDeps.overlays.openConfirm.mockResolvedValue(true); + + const { register } = service.setup(setupDeps); + + register(Symbol(), { + id: 'app1', + title: 'App1', + mount: ({ onAppLeave }: AppMountParameters) => { + onAppLeave(actions => actions.confirm('confirmation-message', 'confirmation-title')); + return () => undefined; + }, + }); + register(Symbol(), { + id: 'app2', + title: 'App2', + mount: ({}: AppMountParameters) => { + return () => undefined; + }, + }); + + const { navigateToApp, getComponent } = await service.start(startDeps); + + update = createRenderer(getComponent()); + + await navigate('/app/app1'); + await navigateToApp('app2'); + + expect(startDeps.overlays.openConfirm).toHaveBeenCalledTimes(1); + expect(startDeps.overlays.openConfirm).toHaveBeenCalledWith( + 'confirmation-message', + expect.objectContaining({ title: 'confirmation-title' }) + ); + expect(history.entries.length).toEqual(3); + expect(history.entries[2].pathname).toEqual('/app/app2'); + }); + + it('blocks navigation to the new app if action is confirm and user declined', async () => { + startDeps.overlays.openConfirm.mockResolvedValue(false); + + const { register } = service.setup(setupDeps); + + register(Symbol(), { + id: 'app1', + title: 'App1', + mount: ({ onAppLeave }: AppMountParameters) => { + onAppLeave(actions => actions.confirm('confirmation-message', 'confirmation-title')); + return () => undefined; + }, + }); + register(Symbol(), { + id: 'app2', + title: 'App2', + mount: ({}: AppMountParameters) => { + return () => undefined; + }, + }); + + const { navigateToApp, getComponent } = await service.start(startDeps); + + update = createRenderer(getComponent()); + + await navigate('/app/app1'); + await navigateToApp('app2'); + + expect(startDeps.overlays.openConfirm).toHaveBeenCalledTimes(1); + expect(startDeps.overlays.openConfirm).toHaveBeenCalledWith( + 'confirmation-message', + expect.objectContaining({ title: 'confirmation-title' }) + ); + expect(history.entries.length).toEqual(2); + expect(history.entries[1].pathname).toEqual('/app/app1'); + }); + }); +}); diff --git a/src/core/public/application/integration_tests/router.test.tsx b/src/core/public/application/integration_tests/router.test.tsx index 10544c348afb0..cc71cf8722df4 100644 --- a/src/core/public/application/integration_tests/router.test.tsx +++ b/src/core/public/application/integration_tests/router.test.tsx @@ -36,6 +36,7 @@ describe('AppContainer', () => { const mockMountersToMounters = () => new Map([...mounters].map(([appId, { mounter }]) => [appId, mounter])); + const setAppLeaveHandlerMock = () => undefined; beforeEach(() => { mounters = new Map([ @@ -46,7 +47,13 @@ describe('AppContainer', () => { createAppMounter('app3', '
App 3
', '/custom/path'), ] as Array>); history = createMemoryHistory(); - update = createRenderer(); + update = createRenderer( + + ); }); it('calls mount handler and returned unmount function when navigating between apps', async () => { @@ -78,7 +85,13 @@ describe('AppContainer', () => { mounters.set(...createAppMounter('spaces', '
Custom Space
', '/spaces/fake-login')); mounters.set(...createAppMounter('login', '
Login Page
', '/fake-login')); history = createMemoryHistory(); - update = createRenderer(); + update = createRenderer( + + ); await navigate('/fake-login'); @@ -90,7 +103,13 @@ describe('AppContainer', () => { mounters.set(...createAppMounter('login', '
Login Page
', '/fake-login')); mounters.set(...createAppMounter('spaces', '
Custom Space
', '/spaces/fake-login')); history = createMemoryHistory(); - update = createRenderer(); + update = createRenderer( + + ); await navigate('/spaces/fake-login'); @@ -124,7 +143,13 @@ describe('AppContainer', () => { it('should not remount when when changing pages within app using hash history', async () => { history = createHashHistory(); - update = createRenderer(); + update = createRenderer( + + ); const { mounter, unmount } = mounters.get('app1')!; await navigate('/app/app1/page1'); @@ -153,6 +178,7 @@ describe('AppContainer', () => { Object { "appBasePath": "/app/legacyApp1", "element":
, + "onAppLeave": [Function], }, ] `); @@ -165,6 +191,7 @@ describe('AppContainer', () => { Object { "appBasePath": "/app/baseApp", "element":
, + "onAppLeave": [Function], }, ] `); diff --git a/src/core/public/application/types.ts b/src/core/public/application/types.ts index c026851af7eb8..0d955482d2226 100644 --- a/src/core/public/application/types.ts +++ b/src/core/public/application/types.ts @@ -34,6 +34,9 @@ import { SavedObjectsStart } from '../saved_objects'; /** @public */ export interface AppBase { + /** + * The unique identifier of the application + */ id: string; /** @@ -41,15 +44,62 @@ export interface AppBase { */ title: string; + /** + * The initial status of the application. + * Defaulting to `accessible` + */ + status?: AppStatus; + + /** + * The initial status of the application's navLink. + * Defaulting to `visible` if `status` is `accessible` and `hidden` if status is `inaccessible` + * See {@link AppNavLinkStatus} + */ + navLinkStatus?: AppNavLinkStatus; + + /** + * An {@link AppUpdater} observable that can be used to update the application {@link AppUpdatableFields} at runtime. + * + * @example + * + * How to update an application navLink at runtime + * + * ```ts + * // inside your plugin's setup function + * export class MyPlugin implements Plugin { + * private appUpdater = new BehaviorSubject(() => ({})); + * + * setup({ application }) { + * application.register({ + * id: 'my-app', + * title: 'My App', + * updater$: this.appUpdater, + * async mount(params) { + * const { renderApp } = await import('./application'); + * return renderApp(params); + * }, + * }); + * } + * + * start() { + * // later, when the navlink needs to be updated + * appUpdater.next(() => { + * navLinkStatus: AppNavLinkStatus.disabled, + * }) + * } + * ``` + */ + updater$?: Observable; + /** * An ordinal used to sort nav links relative to one another for display. */ order?: number; /** - * An observable for a tooltip shown when hovering over app link. + * A tooltip shown when hovering over app link. */ - tooltip$?: Observable; + tooltip?: string; /** * A EUI iconType that will be used for the app's icon. This icon @@ -67,8 +117,76 @@ export interface AppBase { * Custom capabilities defined by the app. */ capabilities?: Partial; + + /** + * Flag to keep track of legacy applications. + * For internal use only. any value will be overridden when registering an App. + * + * @internal + */ + legacy?: boolean; + + /** + * Hide the UI chrome when the application is mounted. Defaults to `false`. + * Takes precedence over chrome service visibility settings. + */ + chromeless?: boolean; } +/** + * Accessibility status of an application. + * + * @public + */ +export enum AppStatus { + /** + * Application is accessible. + */ + accessible = 0, + /** + * Application is not accessible. + */ + inaccessible = 1, +} + +/** + * Status of the application's navLink. + * + * @public + */ +export enum AppNavLinkStatus { + /** + * The application navLink will be `visible` if the application's {@link AppStatus} is set to `accessible` + * and `hidden` if the application status is set to `inaccessible`. + */ + default = 0, + /** + * The application navLink is visible and clickable in the navigation bar. + */ + visible = 1, + /** + * The application navLink is visible but inactive and not clickable in the navigation bar. + */ + disabled = 2, + /** + * The application navLink does not appear in the navigation bar. + */ + hidden = 3, +} + +/** + * Defines the list of fields that can be updated via an {@link AppUpdater}. + * @public + */ +export type AppUpdatableFields = Pick; + +/** + * Updater for applications. + * see {@link ApplicationSetup} + * @public + */ +export type AppUpdater = (app: AppBase) => Partial | undefined; + /** * Extension of {@link AppBase | common app properties} with the mount function. * @public @@ -230,6 +348,117 @@ export interface AppMountParameters { * ``` */ appBasePath: string; + + /** + * A function that can be used to register a handler that will be called + * when the user is leaving the current application, allowing to + * prompt a confirmation message before actually changing the page. + * + * This will be called either when the user goes to another application, or when + * trying to close the tab or manually changing the url. + * + * @example + * + * ```ts + * // application.tsx + * import React from 'react'; + * import ReactDOM from 'react-dom'; + * import { BrowserRouter, Route } from 'react-router-dom'; + * + * import { CoreStart, AppMountParams } from 'src/core/public'; + * import { MyPluginDepsStart } from './plugin'; + * + * export renderApp = ({ appBasePath, element, onAppLeave }: AppMountParams) => { + * const { renderApp, hasUnsavedChanges } = await import('./application'); + * onAppLeave(actions => { + * if(hasUnsavedChanges()) { + * return actions.confirm('Some changes were not saved. Are you sure you want to leave?'); + * } + * return actions.default(); + * }); + * return renderApp(params); + * } + * ``` + */ + onAppLeave: (handler: AppLeaveHandler) => void; +} + +/** + * A handler that will be executed before leaving the application, either when + * going to another application or when closing the browser tab or manually changing + * the url. + * Should return `confirm` to to prompt a message to the user before leaving the page, or `default` + * to keep the default behavior (doing nothing). + * + * See {@link AppMountParameters} for detailed usage examples. + * + * @public + */ +export type AppLeaveHandler = (factory: AppLeaveActionFactory) => AppLeaveAction; + +/** + * Possible type of actions on application leave. + * + * @public + */ +export enum AppLeaveActionType { + confirm = 'confirm', + default = 'default', +} + +/** + * Action to return from a {@link AppLeaveHandler} to execute the default + * behaviour when leaving the application. + * + * See {@link AppLeaveActionFactory} + * + * @public + */ +export interface AppLeaveDefaultAction { + type: AppLeaveActionType.default; +} + +/** + * Action to return from a {@link AppLeaveHandler} to show a confirmation + * message when trying to leave an application. + * + * See {@link AppLeaveActionFactory} + * + * @public + */ +export interface AppLeaveConfirmAction { + type: AppLeaveActionType.confirm; + text: string; + title?: string; +} + +/** + * Possible actions to return from a {@link AppLeaveHandler} + * + * See {@link AppLeaveConfirmAction} and {@link AppLeaveDefaultAction} + * + * @public + * */ +export type AppLeaveAction = AppLeaveDefaultAction | AppLeaveConfirmAction; + +/** + * Factory provided when invoking a {@link AppLeaveHandler} to retrieve the {@link AppLeaveAction} to execute. + */ +export interface AppLeaveActionFactory { + /** + * Returns a confirm action, resulting on prompting a message to the user before leaving the + * application, allowing him to choose if he wants to stay on the app or confirm that he + * wants to leave. + * + * @param text The text to display in the confirmation message + * @param title (optional) title to display in the confirmation message + */ + confirm(text: string, title?: string): AppLeaveConfirmAction; + /** + * Returns a default action, resulting on executing the default behavior when + * the user tries to leave an application + */ + default(): AppLeaveDefaultAction; } /** @@ -263,6 +492,35 @@ export interface ApplicationSetup { */ register(app: App): void; + /** + * Register an application updater that can be used to change the {@link AppUpdatableFields} fields + * of all applications at runtime. + * + * This is meant to be used by plugins that needs to updates the whole list of applications. + * To only updates a specific application, use the `updater$` property of the registered application instead. + * + * @example + * + * How to register an application updater that disables some applications: + * + * ```ts + * // inside your plugin's setup function + * export class MyPlugin implements Plugin { + * setup({ application }) { + * application.registerAppUpdater( + * new BehaviorSubject(app => { + * if (myPluginApi.shouldDisable(app)) + * return { + * status: AppStatus.inaccessible, + * }; + * }) + * ); + * } + * } + * ``` + */ + registerAppUpdater(appUpdater$: Observable): void; + /** * Register a context provider for application mounting. Will only be available to applications that depend on the * plugin that registered this context. Deprecated, use {@link CoreSetup.getStartServices}. @@ -278,7 +536,7 @@ export interface ApplicationSetup { } /** @internal */ -export interface InternalApplicationSetup { +export interface InternalApplicationSetup extends Pick { /** * Register an mountable application to the system. * @param plugin - opaque ID of the plugin that registers this application @@ -317,13 +575,13 @@ export interface ApplicationStart { capabilities: RecursiveReadonly; /** - * Navigiate to a given app + * Navigate to a given app * * @param appId * @param options.path - optional path inside application to deep link to * @param options.state - optional state to forward to the application */ - navigateToApp(appId: string, options?: { path?: string; state?: any }): void; + navigateToApp(appId: string, options?: { path?: string; state?: any }): Promise; /** * Returns a relative URL to a given app, including the global base path. @@ -351,16 +609,11 @@ export interface ApplicationStart { export interface InternalApplicationStart extends Pick { /** - * Apps available based on the current capabilities. Should be used - * to show navigation links and make routing decisions. - */ - availableApps: ReadonlyMap; - /** - * Apps available based on the current capabilities. Should be used - * to show navigation links and make routing decisions. - * @internal + * Apps available based on the current capabilities. + * Should be used to show navigation links and make routing decisions. + * Applications manually disabled from the client-side using {@link AppUpdater} */ - availableLegacyApps: ReadonlyMap; + applications$: Observable>; /** * Register a context provider for application mounting. Will only be available to applications that depend on the diff --git a/src/core/public/application/ui/app_container.tsx b/src/core/public/application/ui/app_container.tsx index 153582e805fa1..8afd4d0ca0551 100644 --- a/src/core/public/application/ui/app_container.tsx +++ b/src/core/public/application/ui/app_container.tsx @@ -26,15 +26,20 @@ import React, { MutableRefObject, } from 'react'; -import { AppUnmount, Mounter } from '../types'; +import { AppUnmount, Mounter, AppLeaveHandler } from '../types'; import { AppNotFound } from './app_not_found_screen'; interface Props { appId: string; mounter?: Mounter; + setAppLeaveHandler: (appId: string, handler: AppLeaveHandler) => void; } -export const AppContainer: FunctionComponent = ({ mounter, appId }: Props) => { +export const AppContainer: FunctionComponent = ({ + mounter, + appId, + setAppLeaveHandler, +}: Props) => { const [appNotFound, setAppNotFound] = useState(false); const elementRef = useRef(null); const unmountRef: MutableRefObject = useRef(null); @@ -59,13 +64,14 @@ export const AppContainer: FunctionComponent = ({ mounter, appId }: Props (await mounter.mount({ appBasePath: mounter.appBasePath, element: elementRef.current!, + onAppLeave: handler => setAppLeaveHandler(appId, handler), })) || null; setAppNotFound(false); }; mount(); return unmount; - }, [mounter]); + }, [appId, mounter, setAppLeaveHandler]); return ( diff --git a/src/core/public/application/ui/app_router.tsx b/src/core/public/application/ui/app_router.tsx index 8db46f9794277..2ee90c3bf5e29 100644 --- a/src/core/public/application/ui/app_router.tsx +++ b/src/core/public/application/ui/app_router.tsx @@ -21,19 +21,20 @@ import React, { FunctionComponent } from 'react'; import { History } from 'history'; import { Router, Route, RouteComponentProps, Switch } from 'react-router-dom'; -import { Mounter } from '../types'; +import { Mounter, AppLeaveHandler } from '../types'; import { AppContainer } from './app_container'; interface Props { mounters: Map; history: History; + setAppLeaveHandler: (appId: string, handler: AppLeaveHandler) => void; } interface Params { appId: string; } -export const AppRouter: FunctionComponent = ({ history, mounters }) => ( +export const AppRouter: FunctionComponent = ({ history, mounters, setAppLeaveHandler }) => ( {[...mounters].flatMap(([appId, mounter]) => @@ -45,7 +46,13 @@ export const AppRouter: FunctionComponent = ({ history, mounters }) => ( } + render={() => ( + + )} />, ] )} @@ -61,7 +68,9 @@ export const AppRouter: FunctionComponent = ({ history, mounters }) => ( ? [appId, mounters.get(appId)] : [...mounters].filter(([key]) => key.split(':')[0] === appId)[0] ?? []; - return ; + return ( + + ); }} /> diff --git a/src/core/public/chrome/chrome_service.test.ts b/src/core/public/chrome/chrome_service.test.ts index d9c35b20db03b..abd04722a49f2 100644 --- a/src/core/public/chrome/chrome_service.test.ts +++ b/src/core/public/chrome/chrome_service.test.ts @@ -18,7 +18,7 @@ */ import * as Rx from 'rxjs'; -import { toArray } from 'rxjs/operators'; +import { take, toArray } from 'rxjs/operators'; import { shallow } from 'enzyme'; import React from 'react'; @@ -54,7 +54,9 @@ function defaultStartDeps(availableApps?: App[]) { }; if (availableApps) { - deps.application.availableApps = new Map(availableApps.map(app => [app.id, app])); + deps.application.applications$ = new Rx.BehaviorSubject>( + new Map(availableApps.map(app => [app.id, app])) + ); } return deps; @@ -211,13 +213,14 @@ describe('start', () => { new FakeApp('beta', true), new FakeApp('gamma', false), ]); - const { availableApps, navigateToApp } = startDeps.application; + const { applications$, navigateToApp } = startDeps.application; const { chrome, service } = await start({ startDeps }); const promise = chrome .getIsVisible$() .pipe(toArray()) .toPromise(); + const availableApps = await applications$.pipe(take(1)).toPromise(); [...availableApps.keys()].forEach(appId => navigateToApp(appId)); service.stop(); diff --git a/src/core/public/chrome/chrome_service.tsx b/src/core/public/chrome/chrome_service.tsx index 18c0c9870d72f..a674b49a8e134 100644 --- a/src/core/public/chrome/chrome_service.tsx +++ b/src/core/public/chrome/chrome_service.tsx @@ -19,7 +19,7 @@ import React from 'react'; import { BehaviorSubject, Observable, ReplaySubject, combineLatest, of, merge } from 'rxjs'; -import { map, takeUntil } from 'rxjs/operators'; +import { flatMap, map, takeUntil } from 'rxjs/operators'; import { parse } from 'url'; import { i18n } from '@kbn/i18n'; @@ -118,11 +118,12 @@ export class ChromeService { // combineLatest below regardless of having an application value yet. of(isEmbedded), application.currentAppId$.pipe( - map( - appId => - !!appId && - application.availableApps.has(appId) && - !!application.availableApps.get(appId)!.chromeless + flatMap(appId => + application.applications$.pipe( + map(applications => { + return !!appId && applications.has(appId) && !!applications.get(appId)!.chromeless; + }) + ) ) ) ); diff --git a/src/core/public/chrome/nav_links/nav_link.ts b/src/core/public/chrome/nav_links/nav_link.ts index d87d171e028e1..3b16c030ddcc9 100644 --- a/src/core/public/chrome/nav_links/nav_link.ts +++ b/src/core/public/chrome/nav_links/nav_link.ts @@ -63,7 +63,7 @@ export interface ChromeNavLink { /** LEGACY FIELDS */ /** - * A url base that legacy apps can set to match deep URLs to an applcation. + * A url base that legacy apps can set to match deep URLs to an application. * * @internalRemarks * This should be removed once legacy apps are gone. diff --git a/src/core/public/chrome/nav_links/nav_links_service.test.ts b/src/core/public/chrome/nav_links/nav_links_service.test.ts index 5a45491df28e7..3d9a4bfdb6a56 100644 --- a/src/core/public/chrome/nav_links/nav_links_service.test.ts +++ b/src/core/public/chrome/nav_links/nav_links_service.test.ts @@ -20,34 +20,47 @@ import { NavLinksService } from './nav_links_service'; import { take, map, takeLast } from 'rxjs/operators'; import { App, LegacyApp } from '../../application'; +import { BehaviorSubject } from 'rxjs'; -const mockAppService = { - availableApps: new Map( - ([ - { id: 'app1', order: 0, title: 'App 1', icon: 'app1' }, - { - id: 'app2', - order: -10, - title: 'App 2', - euiIconType: 'canvasApp', - }, - { id: 'chromelessApp', order: 20, title: 'Chromless App', chromeless: true }, - ] as App[]).map(app => [app.id, app]) - ), - availableLegacyApps: new Map( - ([ - { id: 'legacyApp1', order: 5, title: 'Legacy App 1', icon: 'legacyApp1', appUrl: '/app1' }, - { - id: 'legacyApp2', - order: -5, - title: 'Legacy App 2', - euiIconType: 'canvasApp', - appUrl: '/app2', - }, - { id: 'legacyApp3', order: 15, title: 'Legacy App 3', appUrl: '/app3' }, - ] as LegacyApp[]).map(app => [app.id, app]) - ), -} as any; +const availableApps = new Map([ + ['app1', { id: 'app1', order: 0, title: 'App 1', icon: 'app1' }], + [ + 'app2', + { + id: 'app2', + order: -10, + title: 'App 2', + euiIconType: 'canvasApp', + }, + ], + ['chromelessApp', { id: 'chromelessApp', order: 20, title: 'Chromless App', chromeless: true }], + [ + 'legacyApp1', + { + id: 'legacyApp1', + order: 5, + title: 'Legacy App 1', + icon: 'legacyApp1', + appUrl: '/app1', + legacy: true, + }, + ], + [ + 'legacyApp2', + { + id: 'legacyApp2', + order: -10, + title: 'Legacy App 2', + euiIconType: 'canvasApp', + appUrl: '/app2', + legacy: true, + }, + ], + [ + 'legacyApp3', + { id: 'legacyApp3', order: 20, title: 'Legacy App 3', appUrl: '/app3', legacy: true }, + ], +]); const mockHttp = { basePath: { @@ -57,10 +70,16 @@ const mockHttp = { describe('NavLinksService', () => { let service: NavLinksService; + let mockAppService: any; let start: ReturnType; beforeEach(() => { service = new NavLinksService(); + mockAppService = { + applications$: new BehaviorSubject>( + availableApps as any + ), + }; start = service.start({ application: mockAppService, http: mockHttp }); }); @@ -183,22 +202,36 @@ describe('NavLinksService', () => { .toPromise() ).toEqual(['legacyApp1']); }); + + it('still removes all other links when availableApps are re-emitted', async () => { + start.showOnly('legacyApp2'); + mockAppService.applications$.next(mockAppService.applications$.value); + expect( + await start + .getNavLinks$() + .pipe( + take(1), + map(links => links.map(l => l.id)) + ) + .toPromise() + ).toEqual(['legacyApp2']); + }); }); describe('#update()', () => { it('updates the navlinks and returns the updated link', async () => { - expect(start.update('legacyApp1', { hidden: true })).toMatchInlineSnapshot(` - Object { - "appUrl": "/app1", - "baseUrl": "http://localhost/wow/app1", - "hidden": true, - "icon": "legacyApp1", - "id": "legacyApp1", - "legacy": true, - "order": 5, - "title": "Legacy App 1", - } - `); + expect(start.update('legacyApp1', { hidden: true })).toEqual( + expect.objectContaining({ + appUrl: '/app1', + disabled: false, + hidden: true, + icon: 'legacyApp1', + id: 'legacyApp1', + legacy: true, + order: 5, + title: 'Legacy App 1', + }) + ); const hiddenLinkIds = await start .getNavLinks$() .pipe( @@ -212,6 +245,19 @@ describe('NavLinksService', () => { it('returns undefined if link does not exist', () => { expect(start.update('fake', { hidden: true })).toBeUndefined(); }); + + it('keeps the updated link when availableApps are re-emitted', async () => { + start.update('legacyApp1', { hidden: true }); + mockAppService.applications$.next(mockAppService.applications$.value); + const hiddenLinkIds = await start + .getNavLinks$() + .pipe( + take(1), + map(links => links.filter(l => l.hidden).map(l => l.id)) + ) + .toPromise(); + expect(hiddenLinkIds).toEqual(['legacyApp1']); + }); }); describe('#enableForcedAppSwitcherNavigation()', () => { diff --git a/src/core/public/chrome/nav_links/nav_links_service.ts b/src/core/public/chrome/nav_links/nav_links_service.ts index 31a729f90cd93..650ef77b6fe42 100644 --- a/src/core/public/chrome/nav_links/nav_links_service.ts +++ b/src/core/public/chrome/nav_links/nav_links_service.ts @@ -18,11 +18,13 @@ */ import { sortBy } from 'lodash'; -import { BehaviorSubject, ReplaySubject, Observable } from 'rxjs'; +import { BehaviorSubject, combineLatest, Observable, ReplaySubject } from 'rxjs'; import { map, takeUntil } from 'rxjs/operators'; -import { NavLinkWrapper, ChromeNavLinkUpdateableFields, ChromeNavLink } from './nav_link'; + import { InternalApplicationStart } from '../../application'; import { HttpStart } from '../../http'; +import { ChromeNavLink, ChromeNavLinkUpdateableFields, NavLinkWrapper } from './nav_link'; +import { toNavLink } from './to_nav_link'; interface StartDeps { application: InternalApplicationStart; @@ -95,39 +97,38 @@ export interface ChromeNavLinks { getForceAppSwitcherNavigation$(): Observable; } +type LinksUpdater = (navLinks: Map) => Map; + export class NavLinksService { private readonly stop$ = new ReplaySubject(1); public start({ application, http }: StartDeps): ChromeNavLinks { - const appLinks = [...application.availableApps] - .filter(([, app]) => !app.chromeless) - .map( - ([appId, app]) => - [ - appId, - new NavLinkWrapper({ - ...app, - legacy: false, - baseUrl: relativeToAbsolute(http.basePath.prepend(`/app/${appId}`)), - }), - ] as [string, NavLinkWrapper] - ); - - const legacyAppLinks = [...application.availableLegacyApps].map( - ([appId, app]) => - [ - appId, - new NavLinkWrapper({ - ...app, - legacy: true, - baseUrl: relativeToAbsolute(http.basePath.prepend(app.appUrl)), - }), - ] as [string, NavLinkWrapper] + const appLinks$ = application.applications$.pipe( + map(apps => { + return new Map( + [...apps] + .filter(([, app]) => !app.chromeless) + .map(([appId, app]) => [appId, toNavLink(app, http.basePath)]) + ); + }) ); - const navLinks$ = new BehaviorSubject>( - new Map([...legacyAppLinks, ...appLinks]) - ); + // now that availableApps$ is an observable, we need to keep record of all + // manual link modifications to be able to re-apply then after every + // availableApps$ changes. + const linkUpdaters$ = new BehaviorSubject([]); + const navLinks$ = new BehaviorSubject>(new Map()); + + combineLatest([appLinks$, linkUpdaters$]) + .pipe( + map(([appLinks, linkUpdaters]) => { + return linkUpdaters.reduce((links, updater) => updater(links), appLinks); + }) + ) + .subscribe(navlinks => { + navLinks$.next(navlinks); + }); + const forceAppSwitcherNavigation$ = new BehaviorSubject(false); return { @@ -153,7 +154,10 @@ export class NavLinksService { return; } - navLinks$.next(new Map([...navLinks$.value.entries()].filter(([linkId]) => linkId === id))); + const updater: LinksUpdater = navLinks => + new Map([...navLinks.entries()].filter(([linkId]) => linkId === id)); + + linkUpdaters$.next([...linkUpdaters$.value, updater]); }, update(id: string, values: ChromeNavLinkUpdateableFields) { @@ -161,17 +165,17 @@ export class NavLinksService { return; } - navLinks$.next( + const updater: LinksUpdater = navLinks => new Map( - [...navLinks$.value.entries()].map(([linkId, link]) => { + [...navLinks.entries()].map(([linkId, link]) => { return [linkId, link.id === id ? link.update(values) : link] as [ string, NavLinkWrapper ]; }) - ) - ); + ); + linkUpdaters$.next([...linkUpdaters$.value, updater]); return this.get(id); }, @@ -196,10 +200,3 @@ function sortNavLinks(navLinks: ReadonlyMap) { 'order' ); } - -function relativeToAbsolute(url: string) { - // convert all link urls to absolute urls - const a = document.createElement('a'); - a.setAttribute('href', url); - return a.href; -} diff --git a/src/core/public/chrome/nav_links/to_nav_link.test.ts b/src/core/public/chrome/nav_links/to_nav_link.test.ts new file mode 100644 index 0000000000000..23fdabe0f3430 --- /dev/null +++ b/src/core/public/chrome/nav_links/to_nav_link.test.ts @@ -0,0 +1,178 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { App, AppMount, AppNavLinkStatus, AppStatus, LegacyApp } from '../../application'; +import { toNavLink } from './to_nav_link'; + +import { httpServiceMock } from '../../mocks'; + +function mount() {} + +const app = (props: Partial = {}): App => ({ + mount: (mount as unknown) as AppMount, + id: 'some-id', + title: 'some-title', + status: AppStatus.accessible, + navLinkStatus: AppNavLinkStatus.default, + appRoute: `/app/some-id`, + legacy: false, + ...props, +}); + +const legacyApp = (props: Partial = {}): LegacyApp => ({ + appUrl: '/my-app-url', + id: 'some-id', + title: 'some-title', + status: AppStatus.accessible, + navLinkStatus: AppNavLinkStatus.default, + legacy: true, + ...props, +}); + +describe('toNavLink', () => { + const basePath = httpServiceMock.createSetupContract({ basePath: '/base-path' }).basePath; + + it('uses the application properties when creating the navLink', () => { + const link = toNavLink( + app({ + id: 'id', + title: 'title', + order: 12, + tooltip: 'tooltip', + euiIconType: 'my-icon', + }), + basePath + ); + expect(link.properties).toEqual( + expect.objectContaining({ + id: 'id', + title: 'title', + order: 12, + tooltip: 'tooltip', + euiIconType: 'my-icon', + }) + ); + }); + + it('flags legacy apps when converting to navLink', () => { + expect(toNavLink(app({}), basePath).properties.legacy).toEqual(false); + expect(toNavLink(legacyApp({}), basePath).properties.legacy).toEqual(true); + }); + + it('handles applications with custom app route', () => { + const link = toNavLink( + app({ + appRoute: '/my-route/my-path', + }), + basePath + ); + expect(link.properties.baseUrl).toEqual('http://localhost/base-path/my-route/my-path'); + }); + + it('uses appUrl when converting legacy applications', () => { + expect( + toNavLink( + legacyApp({ + appUrl: '/my-legacy-app/#foo', + }), + basePath + ).properties + ).toEqual( + expect.objectContaining({ + baseUrl: 'http://localhost/base-path/my-legacy-app/#foo', + }) + ); + }); + + it('uses the application status when the navLinkStatus is set to default', () => { + expect( + toNavLink( + app({ + navLinkStatus: AppNavLinkStatus.default, + status: AppStatus.accessible, + }), + basePath + ).properties + ).toEqual( + expect.objectContaining({ + disabled: false, + hidden: false, + }) + ); + + expect( + toNavLink( + app({ + navLinkStatus: AppNavLinkStatus.default, + status: AppStatus.inaccessible, + }), + basePath + ).properties + ).toEqual( + expect.objectContaining({ + disabled: false, + hidden: true, + }) + ); + }); + + it('uses the navLinkStatus of the application to set the hidden and disabled properties', () => { + expect( + toNavLink( + app({ + navLinkStatus: AppNavLinkStatus.visible, + }), + basePath + ).properties + ).toEqual( + expect.objectContaining({ + disabled: false, + hidden: false, + }) + ); + + expect( + toNavLink( + app({ + navLinkStatus: AppNavLinkStatus.hidden, + }), + basePath + ).properties + ).toEqual( + expect.objectContaining({ + disabled: false, + hidden: true, + }) + ); + + expect( + toNavLink( + app({ + navLinkStatus: AppNavLinkStatus.disabled, + }), + basePath + ).properties + ).toEqual( + expect.objectContaining({ + disabled: true, + hidden: false, + }) + ); + }); +}); diff --git a/src/core/public/chrome/nav_links/to_nav_link.ts b/src/core/public/chrome/nav_links/to_nav_link.ts new file mode 100644 index 0000000000000..18e4b7b26b6ba --- /dev/null +++ b/src/core/public/chrome/nav_links/to_nav_link.ts @@ -0,0 +1,48 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { App, AppNavLinkStatus, AppStatus, LegacyApp } from '../../application'; +import { IBasePath } from '../../http'; +import { NavLinkWrapper } from './nav_link'; + +export function toNavLink(app: App | LegacyApp, basePath: IBasePath): NavLinkWrapper { + const useAppStatus = app.navLinkStatus === AppNavLinkStatus.default; + return new NavLinkWrapper({ + ...app, + hidden: useAppStatus + ? app.status === AppStatus.inaccessible + : app.navLinkStatus === AppNavLinkStatus.hidden, + disabled: useAppStatus ? false : app.navLinkStatus === AppNavLinkStatus.disabled, + legacy: isLegacyApp(app), + baseUrl: isLegacyApp(app) + ? relativeToAbsolute(basePath.prepend(app.appUrl)) + : relativeToAbsolute(basePath.prepend(app.appRoute!)), + }); +} + +function relativeToAbsolute(url: string) { + // convert all link urls to absolute urls + const a = document.createElement('a'); + a.setAttribute('href', url); + return a.href; +} + +function isLegacyApp(app: App | LegacyApp): app is LegacyApp { + return app.legacy === true; +} diff --git a/src/core/public/chrome/ui/header/header.tsx b/src/core/public/chrome/ui/header/header.tsx index 75f78ac8b2fa0..0447add491788 100644 --- a/src/core/public/chrome/ui/header/header.tsx +++ b/src/core/public/chrome/ui/header/header.tsx @@ -309,7 +309,7 @@ class HeaderUI extends Component { .filter(navLink => !navLink.hidden) .map(navLink => ({ key: navLink.id, - label: navLink.title, + label: navLink.tooltip ?? navLink.title, // Use href and onClick to support "open in new tab" and SPA navigation in the same link href: navLink.href, diff --git a/src/core/public/core_system.ts b/src/core/public/core_system.ts index 485c11aae6508..5b31c740518e4 100644 --- a/src/core/public/core_system.ts +++ b/src/core/public/core_system.ts @@ -214,7 +214,6 @@ export class CoreSystem { const http = await this.http.start({ injectedMetadata, fatalErrors: this.fatalErrorsSetup! }); const savedObjects = await this.savedObjects.start({ http }); const i18n = await this.i18n.start(); - const application = await this.application.start({ http, injectedMetadata }); await this.integrations.start({ uiSettings }); const coreUiTargetDomElement = document.createElement('div'); @@ -239,6 +238,7 @@ export class CoreSystem { overlays, targetDomElement: notificationsTargetDomElement, }); + const application = await this.application.start({ http, overlays }); const chrome = await this.chrome.start({ application, docLinks, diff --git a/src/core/public/doc_links/doc_links_service.ts b/src/core/public/doc_links/doc_links_service.ts index 44dc76bfe6e32..36b220f16f395 100644 --- a/src/core/public/doc_links/doc_links_service.ts +++ b/src/core/public/doc_links/doc_links_service.ts @@ -115,6 +115,9 @@ export class DocLinksService { date: { dateMath: `${ELASTICSEARCH_DOCS}common-options.html#date-math`, }, + management: { + kibanaSearchSettings: `${ELASTIC_WEBSITE_URL}guide/en/kibana/${DOC_LINK_VERSION}/advanced-options.html#kibana-search-settings`, + }, }, }); } diff --git a/src/core/public/index.ts b/src/core/public/index.ts index 7488f9b973b71..5b17eccc37f8b 100644 --- a/src/core/public/index.ts +++ b/src/core/public/index.ts @@ -89,6 +89,15 @@ export { AppUnmount, AppMountContext, AppMountParameters, + AppLeaveHandler, + AppLeaveActionType, + AppLeaveAction, + AppLeaveDefaultAction, + AppLeaveConfirmAction, + AppStatus, + AppNavLinkStatus, + AppUpdatableFields, + AppUpdater, } from './application'; export { diff --git a/src/core/public/legacy/legacy_service.ts b/src/core/public/legacy/legacy_service.ts index a4fdd86de5311..f906aff1759e2 100644 --- a/src/core/public/legacy/legacy_service.ts +++ b/src/core/public/legacy/legacy_service.ts @@ -81,6 +81,7 @@ export class LegacyPlatformService { ...core, getStartServices: () => this.startDependencies, application: { + ...core.application, register: notSupported(`core.application.register()`), registerMountContext: notSupported(`core.application.registerMountContext()`), }, diff --git a/src/core/public/notifications/toasts/global_toast_list.test.tsx b/src/core/public/notifications/toasts/global_toast_list.test.tsx index 61d73ac233188..dc2a9dabe791e 100644 --- a/src/core/public/notifications/toasts/global_toast_list.test.tsx +++ b/src/core/public/notifications/toasts/global_toast_list.test.tsx @@ -57,9 +57,9 @@ it('subscribes to toasts$ on mount and unsubscribes on unmount', () => { it('passes latest value from toasts$ to ', () => { const el = shallow( render({ - toasts$: Rx.from([[], [{ id: 1 }], [{ id: 1 }, { id: 2 }]]) as any, + toasts$: Rx.from([[], [{ id: '1' }], [{ id: '1' }, { id: '2' }]]) as any, }) ); - expect(el.find(EuiGlobalToastList).prop('toasts')).toEqual([{ id: 1 }, { id: 2 }]); + expect(el.find(EuiGlobalToastList).prop('toasts')).toEqual([{ id: '1' }, { id: '2' }]); }); diff --git a/src/core/public/overlays/modal/__snapshots__/modal_service.test.tsx.snap b/src/core/public/overlays/modal/__snapshots__/modal_service.test.tsx.snap index 131ec836f5252..7eaa1c3af2079 100644 --- a/src/core/public/overlays/modal/__snapshots__/modal_service.test.tsx.snap +++ b/src/core/public/overlays/modal/__snapshots__/modal_service.test.tsx.snap @@ -8,6 +8,129 @@ Array [ ] `; +exports[`ModalService openConfirm() renders a mountpoint confirm message 1`] = ` +Array [ + Array [ + + + + + + + , +
, + ], +] +`; + +exports[`ModalService openConfirm() renders a mountpoint confirm message 2`] = `"
Modal content
"`; + +exports[`ModalService openConfirm() renders a string confirm message 1`] = ` +Array [ + Array [ + + + + Some message + + + , +
, + ], +] +`; + +exports[`ModalService openConfirm() renders a string confirm message 2`] = `"

Some message

"`; + +exports[`ModalService openConfirm() with a currently active confirm replaces the current confirm with the new one 1`] = ` +Array [ + Array [ + + + + confirm 1 + + + , +
, + ], + Array [ + + + + some confirm + + + , +
, + ], +] +`; + +exports[`ModalService openConfirm() with a currently active modal replaces the current modal with the new confirm 1`] = ` +Array [ + Array [ + + + + + + + , +
, + ], + Array [ + + + + some confirm + + + , +
, + ], +] +`; + exports[`ModalService openModal() renders a modal to the DOM 1`] = ` Array [ Array [ @@ -31,6 +154,43 @@ Array [ exports[`ModalService openModal() renders a modal to the DOM 2`] = `"
Modal content
"`; +exports[`ModalService openModal() with a currently active confirm replaces the current confirm with the new one 1`] = ` +Array [ + Array [ + + + + confirm 1 + + + , +
, + ], + Array [ + + + + some confirm + + + , +
, + ], +] +`; + exports[`ModalService openModal() with a currently active modal replaces the current modal with a new one 1`] = ` Array [ Array [ diff --git a/src/core/public/overlays/modal/modal_service.mock.ts b/src/core/public/overlays/modal/modal_service.mock.ts index 726209b8f277c..5ac49874dcf93 100644 --- a/src/core/public/overlays/modal/modal_service.mock.ts +++ b/src/core/public/overlays/modal/modal_service.mock.ts @@ -25,6 +25,7 @@ const createStartContractMock = () => { close: jest.fn(), onClose: Promise.resolve(), }), + openConfirm: jest.fn().mockResolvedValue(true), }; return startContract; }; diff --git a/src/core/public/overlays/modal/modal_service.test.tsx b/src/core/public/overlays/modal/modal_service.test.tsx index 582c2697aef30..8b68075bb2a00 100644 --- a/src/core/public/overlays/modal/modal_service.test.tsx +++ b/src/core/public/overlays/modal/modal_service.test.tsx @@ -80,6 +80,91 @@ describe('ModalService', () => { expect(onCloseComplete).toBeCalledTimes(1); }); }); + + describe('with a currently active confirm', () => { + let confirm1: Promise; + + beforeEach(() => { + confirm1 = modals.openConfirm('confirm 1'); + }); + + it('replaces the current confirm with the new one', () => { + modals.openConfirm('some confirm'); + expect(mockReactDomRender.mock.calls).toMatchSnapshot(); + expect(mockReactDomUnmount).toHaveBeenCalledTimes(1); + }); + + it('resolves the previous confirm promise', async () => { + modals.open(mountReactNode(Flyout content 2)); + expect(await confirm1).toEqual(false); + }); + }); + }); + + describe('openConfirm()', () => { + it('renders a mountpoint confirm message', () => { + expect(mockReactDomRender).not.toHaveBeenCalled(); + modals.openConfirm(container => { + const content = document.createElement('span'); + content.textContent = 'Modal content'; + container.append(content); + return () => {}; + }); + expect(mockReactDomRender.mock.calls).toMatchSnapshot(); + const modalContent = mount(mockReactDomRender.mock.calls[0][0]); + expect(modalContent.html()).toMatchSnapshot(); + }); + + it('renders a string confirm message', () => { + expect(mockReactDomRender).not.toHaveBeenCalled(); + modals.openConfirm('Some message'); + expect(mockReactDomRender.mock.calls).toMatchSnapshot(); + const modalContent = mount(mockReactDomRender.mock.calls[0][0]); + expect(modalContent.html()).toMatchSnapshot(); + }); + + describe('with a currently active modal', () => { + let ref1: OverlayRef; + + beforeEach(() => { + ref1 = modals.open(mountReactNode(Modal content 1)); + }); + + it('replaces the current modal with the new confirm', () => { + modals.openConfirm('some confirm'); + expect(mockReactDomRender.mock.calls).toMatchSnapshot(); + expect(mockReactDomUnmount).toHaveBeenCalledTimes(1); + expect(() => ref1.close()).not.toThrowError(); + expect(mockReactDomUnmount).toHaveBeenCalledTimes(1); + }); + + it('resolves onClose on the previous ref', async () => { + const onCloseComplete = jest.fn(); + ref1.onClose.then(onCloseComplete); + modals.openConfirm('some confirm'); + await ref1.onClose; + expect(onCloseComplete).toBeCalledTimes(1); + }); + }); + + describe('with a currently active confirm', () => { + let confirm1: Promise; + + beforeEach(() => { + confirm1 = modals.openConfirm('confirm 1'); + }); + + it('replaces the current confirm with the new one', () => { + modals.openConfirm('some confirm'); + expect(mockReactDomRender.mock.calls).toMatchSnapshot(); + expect(mockReactDomUnmount).toHaveBeenCalledTimes(1); + }); + + it('resolves the previous confirm promise', async () => { + modals.openConfirm('some confirm'); + expect(await confirm1).toEqual(false); + }); + }); }); describe('ModalRef#close()', () => { diff --git a/src/core/public/overlays/modal/modal_service.tsx b/src/core/public/overlays/modal/modal_service.tsx index cb77c2ec4c88c..ba7887b1afa5c 100644 --- a/src/core/public/overlays/modal/modal_service.tsx +++ b/src/core/public/overlays/modal/modal_service.tsx @@ -19,7 +19,8 @@ /* eslint-disable max-classes-per-file */ -import { EuiModal, EuiOverlayMask } from '@elastic/eui'; +import { i18n as t } from '@kbn/i18n'; +import { EuiModal, EuiConfirmModal, EuiOverlayMask } from '@elastic/eui'; import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import { Subject } from 'rxjs'; @@ -57,6 +58,18 @@ class ModalRef implements OverlayRef { } } +/** + * @public + */ +export interface OverlayModalConfirmOptions { + title?: string; + cancelButtonText?: string; + confirmButtonText?: string; + className?: string; + closeButtonAriaLabel?: string; + 'data-test-subj'?: string; +} + /** * APIs to open and manage modal dialogs. * @@ -72,6 +85,14 @@ export interface OverlayModalStart { * @return {@link OverlayRef} A reference to the opened modal. */ open(mount: MountPoint, options?: OverlayModalOpenOptions): OverlayRef; + /** + * Opens a confirmation modal with the given text or mountpoint as a message. + * Returns a Promise resolving to `true` if user confirmed or `false` otherwise. + * + * @param message {@link MountPoint} - string or mountpoint to be used a the confirm message body + * @param options {@link OverlayModalConfirmOptions} - options for the confirm modal + */ + openConfirm(message: MountPoint | string, options?: OverlayModalConfirmOptions): Promise; } /** @@ -98,7 +119,7 @@ export class ModalService { return { open: (mount: MountPoint, options: OverlayModalOpenOptions = {}): OverlayRef => { - // If there is an active flyout session close it before opening a new one. + // If there is an active modal, close it before opening a new one. if (this.activeModal) { this.activeModal.close(); this.cleanupDom(); @@ -128,6 +149,65 @@ export class ModalService { return modal; }, + openConfirm: (message: MountPoint | string, options?: OverlayModalConfirmOptions) => { + // If there is an active modal, close it before opening a new one. + if (this.activeModal) { + this.activeModal.close(); + this.cleanupDom(); + } + + return new Promise((resolve, reject) => { + let resolved = false; + const closeModal = (confirmed: boolean) => { + resolved = true; + modal.close(); + resolve(confirmed); + }; + + const modal = new ModalRef(); + modal.onClose.then(() => { + if (this.activeModal === modal) { + this.cleanupDom(); + } + // modal.close can be called when opening a new modal/confirm, so we need to resolve the promise in that case. + if (!resolved) { + closeModal(false); + } + }); + this.activeModal = modal; + + const props = { + ...options, + children: + typeof message === 'string' ? ( + message + ) : ( + + ), + onCancel: () => closeModal(false), + onConfirm: () => closeModal(true), + cancelButtonText: + options?.cancelButtonText || + t.translate('core.overlays.confirm.cancelButton', { + defaultMessage: 'Cancel', + }), + confirmButtonText: + options?.confirmButtonText || + t.translate('core.overlays.confirm.okButton', { + defaultMessage: 'Confirm', + }), + }; + + render( + + + + + , + targetDomElement + ); + }); + }, }; } diff --git a/src/core/public/overlays/overlay_service.mock.ts b/src/core/public/overlays/overlay_service.mock.ts index 2937ec89bfc74..e29247494034f 100644 --- a/src/core/public/overlays/overlay_service.mock.ts +++ b/src/core/public/overlays/overlay_service.mock.ts @@ -22,9 +22,11 @@ import { overlayFlyoutServiceMock } from './flyout/flyout_service.mock'; import { overlayModalServiceMock } from './modal/modal_service.mock'; const createStartContractMock = () => { + const overlayStart = overlayModalServiceMock.createStartContract(); const startContract: DeeplyMockedKeys = { openFlyout: overlayFlyoutServiceMock.createStartContract().open, - openModal: overlayModalServiceMock.createStartContract().open, + openModal: overlayStart.open, + openConfirm: overlayStart.openConfirm, banners: overlayBannersServiceMock.createStartContract(), }; return startContract; diff --git a/src/core/public/overlays/overlay_service.ts b/src/core/public/overlays/overlay_service.ts index f628182e965d8..2ff43ba3fbf27 100644 --- a/src/core/public/overlays/overlay_service.ts +++ b/src/core/public/overlays/overlay_service.ts @@ -50,6 +50,7 @@ export class OverlayService { banners, openFlyout: flyouts.open.bind(flyouts), openModal: modals.open.bind(modals), + openConfirm: modals.openConfirm.bind(modals), }; } } @@ -62,4 +63,6 @@ export interface OverlayStart { openFlyout: OverlayFlyoutStart['open']; /** {@link OverlayModalStart#open} */ openModal: OverlayModalStart['open']; + /** {@link OverlayModalStart#openConfirm} */ + openConfirm: OverlayModalStart['openConfirm']; } diff --git a/src/core/public/plugins/plugin_context.ts b/src/core/public/plugins/plugin_context.ts index 848f46605d4de..f146c2452868b 100644 --- a/src/core/public/plugins/plugin_context.ts +++ b/src/core/public/plugins/plugin_context.ts @@ -96,6 +96,7 @@ export function createPluginSetupContext< return { application: { register: app => deps.application.register(plugin.opaqueId, app), + registerAppUpdater: statusUpdater$ => deps.application.registerAppUpdater(statusUpdater$), registerMountContext: (contextName, provider) => deps.application.registerMountContext(plugin.opaqueId, contextName, provider), }, 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/public/public.api.md b/src/core/public/public.api.md index f61741571dc1d..aef689162f45a 100644 --- a/src/core/public/public.api.md +++ b/src/core/public/public.api.md @@ -1,1114 +1,1179 @@ -## API Report File for "kibana" - -> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). - -```ts - -import { Breadcrumb } from '@elastic/eui'; -import { EuiButtonEmptyProps } from '@elastic/eui'; -import { EuiGlobalToastListToast } from '@elastic/eui'; -import { ExclusiveUnion } from '@elastic/eui'; -import { IconType } from '@elastic/eui'; -import { Observable } from 'rxjs'; -import React from 'react'; -import * as Rx from 'rxjs'; -import { ShallowPromise } from '@kbn/utility-types'; -import { UiSettingsParams as UiSettingsParams_2 } from 'src/core/server/types'; -import { UserProvidedValues as UserProvidedValues_2 } from 'src/core/server/types'; - -// @public -export interface App extends AppBase { - appRoute?: string; - chromeless?: boolean; - mount: AppMount | AppMountDeprecated; -} - -// @public (undocumented) -export interface AppBase { - capabilities?: Partial; - euiIconType?: string; - icon?: string; - // (undocumented) - id: string; - order?: number; - title: string; - tooltip$?: Observable; -} - -// @public (undocumented) -export interface ApplicationSetup { - register(app: App): void; - // @deprecated - registerMountContext(contextName: T, provider: IContextProvider): void; -} - -// @public (undocumented) -export interface ApplicationStart { - capabilities: RecursiveReadonly; - getUrlForApp(appId: string, options?: { - path?: string; - }): string; - navigateToApp(appId: string, options?: { - path?: string; - state?: any; - }): void; - // @deprecated - registerMountContext(contextName: T, provider: IContextProvider): void; -} - -// @public -export type AppMount = (params: AppMountParameters) => AppUnmount | Promise; - -// @public @deprecated -export interface AppMountContext { - core: { - application: Pick; - chrome: ChromeStart; - docLinks: DocLinksStart; - http: HttpStart; - i18n: I18nStart; - notifications: NotificationsStart; - overlays: OverlayStart; - savedObjects: SavedObjectsStart; - uiSettings: IUiSettingsClient; - injectedMetadata: { - getInjectedVar: (name: string, defaultValue?: any) => unknown; - }; - }; -} - -// @public @deprecated -export type AppMountDeprecated = (context: AppMountContext, params: AppMountParameters) => AppUnmount | Promise; - -// @public (undocumented) -export interface AppMountParameters { - appBasePath: string; - element: HTMLElement; -} - -// @public -export type AppUnmount = () => void; - -// @public -export interface Capabilities { - [key: string]: Record>; - catalogue: Record; - management: { - [sectionId: string]: Record; - }; - navLinks: Record; -} - -// @public (undocumented) -export interface ChromeBadge { - // (undocumented) - iconType?: IconType; - // (undocumented) - text: string; - // (undocumented) - tooltip: string; -} - -// @public (undocumented) -export interface ChromeBrand { - // (undocumented) - logo?: string; - // (undocumented) - smallLogo?: string; -} - -// @public (undocumented) -export type ChromeBreadcrumb = Breadcrumb; - -// @public -export interface ChromeDocTitle { - // @internal (undocumented) - __legacy: { - setBaseTitle(baseTitle: string): void; - }; - change(newTitle: string | string[]): void; - reset(): void; -} - -// @public (undocumented) -export interface ChromeHelpExtension { - appName: string; - content?: (element: HTMLDivElement) => () => void; - links?: ChromeHelpExtensionMenuLink[]; -} - -// @public (undocumented) -export type ChromeHelpExtensionMenuCustomLink = EuiButtonEmptyProps & { - linkType: 'custom'; - content: React.ReactNode; -}; - -// @public (undocumented) -export type ChromeHelpExtensionMenuDiscussLink = EuiButtonEmptyProps & { - linkType: 'discuss'; - href: string; -}; - -// @public (undocumented) -export type ChromeHelpExtensionMenuDocumentationLink = EuiButtonEmptyProps & { - linkType: 'documentation'; - href: string; -}; - -// @public (undocumented) -export type ChromeHelpExtensionMenuGitHubLink = EuiButtonEmptyProps & { - linkType: 'github'; - labels: string[]; - title?: string; -}; - -// @public (undocumented) -export type ChromeHelpExtensionMenuLink = ExclusiveUnion>>; - -// @public (undocumented) -export interface ChromeNavControl { - // (undocumented) - mount: MountPoint; - // (undocumented) - order?: number; -} - -// @public -export interface ChromeNavControls { - // @internal (undocumented) - getLeft$(): Observable; - // @internal (undocumented) - getRight$(): Observable; - registerLeft(navControl: ChromeNavControl): void; - registerRight(navControl: ChromeNavControl): void; -} - -// @public (undocumented) -export interface ChromeNavLink { - // @deprecated - readonly active?: boolean; - readonly baseUrl: string; - // @deprecated - readonly disabled?: boolean; - readonly euiIconType?: string; - readonly hidden?: boolean; - readonly icon?: string; - readonly id: string; - // @internal - readonly legacy: boolean; - // @deprecated - readonly linkToLastSubUrl?: boolean; - readonly order?: number; - // @deprecated - readonly subUrlBase?: string; - readonly title: string; - readonly tooltip?: string; - // @deprecated - readonly url?: string; -} - -// @public -export interface ChromeNavLinks { - enableForcedAppSwitcherNavigation(): void; - get(id: string): ChromeNavLink | undefined; - getAll(): Array>; - getForceAppSwitcherNavigation$(): Observable; - getNavLinks$(): Observable>>; - has(id: string): boolean; - showOnly(id: string): void; - update(id: string, values: ChromeNavLinkUpdateableFields): ChromeNavLink | undefined; -} - -// @public (undocumented) -export type ChromeNavLinkUpdateableFields = Partial>; - -// @public -export interface ChromeRecentlyAccessed { - // Warning: (ae-unresolved-link) The @link reference could not be resolved: No member was found with name "basePath" - add(link: string, label: string, id: string): void; - get$(): Observable; - get(): ChromeRecentlyAccessedHistoryItem[]; -} - -// @public (undocumented) -export interface ChromeRecentlyAccessedHistoryItem { - // (undocumented) - id: string; - // (undocumented) - label: string; - // (undocumented) - link: string; -} - -// @public -export interface ChromeStart { - addApplicationClass(className: string): void; - docTitle: ChromeDocTitle; - getApplicationClasses$(): Observable; - getBadge$(): Observable; - getBrand$(): Observable; - getBreadcrumbs$(): Observable; - getHelpExtension$(): Observable; - getIsCollapsed$(): Observable; - getIsVisible$(): Observable; - navControls: ChromeNavControls; - navLinks: ChromeNavLinks; - recentlyAccessed: ChromeRecentlyAccessed; - removeApplicationClass(className: string): void; - setAppTitle(appTitle: string): void; - setBadge(badge?: ChromeBadge): void; - setBrand(brand: ChromeBrand): void; - setBreadcrumbs(newBreadcrumbs: ChromeBreadcrumb[]): void; - setHelpExtension(helpExtension?: ChromeHelpExtension): void; - setHelpSupportUrl(url: string): void; - setIsCollapsed(isCollapsed: boolean): void; - setIsVisible(isVisible: boolean): void; -} - -// @public -export interface ContextSetup { - createContextContainer>(): IContextContainer; -} - -// @internal (undocumented) -export interface CoreContext { - // Warning: (ae-forgotten-export) The symbol "CoreId" needs to be exported by the entry point index.d.ts - // - // (undocumented) - coreId: CoreId; - // (undocumented) - env: { - mode: Readonly; - packageInfo: Readonly; - }; -} - -// @public -export interface CoreSetup { - // (undocumented) - application: ApplicationSetup; - // @deprecated (undocumented) - context: ContextSetup; - // (undocumented) - fatalErrors: FatalErrorsSetup; - getStartServices(): Promise<[CoreStart, TPluginsStart]>; - // (undocumented) - http: HttpSetup; - // @deprecated - injectedMetadata: { - getInjectedVar: (name: string, defaultValue?: any) => unknown; - }; - // (undocumented) - notifications: NotificationsSetup; - // (undocumented) - uiSettings: IUiSettingsClient; -} - -// @public -export interface CoreStart { - // (undocumented) - application: ApplicationStart; - // (undocumented) - chrome: ChromeStart; - // (undocumented) - docLinks: DocLinksStart; - // (undocumented) - http: HttpStart; - // (undocumented) - i18n: I18nStart; - // @deprecated - injectedMetadata: { - getInjectedVar: (name: string, defaultValue?: any) => unknown; - }; - // (undocumented) - notifications: NotificationsStart; - // (undocumented) - overlays: OverlayStart; - // (undocumented) - savedObjects: SavedObjectsStart; - // (undocumented) - uiSettings: IUiSettingsClient; -} - -// @internal -export class CoreSystem { - // Warning: (ae-forgotten-export) The symbol "Params" needs to be exported by the entry point index.d.ts - constructor(params: Params); - // (undocumented) - setup(): Promise<{ - fatalErrors: FatalErrorsSetup; - } | undefined>; - // (undocumented) - start(): Promise; - // (undocumented) - stop(): void; - } - -// @public (undocumented) -export interface DocLinksStart { - // (undocumented) - readonly DOC_LINK_VERSION: string; - // (undocumented) - readonly ELASTIC_WEBSITE_URL: string; - // (undocumented) - readonly links: { - readonly filebeat: { - readonly base: string; - readonly installation: string; - readonly configuration: string; - readonly elasticsearchOutput: string; - readonly startup: string; - readonly exportedFields: string; - }; - readonly auditbeat: { - readonly base: string; - }; - readonly metricbeat: { - readonly base: string; - }; - readonly heartbeat: { - readonly base: string; - }; - readonly logstash: { - readonly base: string; - }; - readonly functionbeat: { - readonly base: string; - }; - readonly winlogbeat: { - readonly base: string; - }; - readonly aggs: { - readonly date_histogram: string; - readonly date_range: string; - readonly filter: string; - readonly filters: string; - readonly geohash_grid: string; - readonly histogram: string; - readonly ip_range: string; - readonly range: string; - readonly significant_terms: string; - readonly terms: string; - readonly avg: string; - readonly avg_bucket: string; - readonly max_bucket: string; - readonly min_bucket: string; - readonly sum_bucket: string; - readonly cardinality: string; - readonly count: string; - readonly cumulative_sum: string; - readonly derivative: string; - readonly geo_bounds: string; - readonly geo_centroid: string; - readonly max: string; - readonly median: string; - readonly min: string; - readonly moving_avg: string; - readonly percentile_ranks: string; - readonly serial_diff: string; - readonly std_dev: string; - readonly sum: string; - readonly top_hits: string; - }; - readonly scriptedFields: { - readonly scriptFields: string; - readonly scriptAggs: string; - readonly painless: string; - readonly painlessApi: string; - readonly painlessSyntax: string; - readonly luceneExpressions: string; - }; - readonly indexPatterns: { - readonly loadingData: string; - readonly introduction: string; - }; - readonly kibana: string; - readonly siem: string; - readonly query: { - readonly luceneQuerySyntax: string; - readonly queryDsl: string; - readonly kueryQuerySyntax: string; - }; - readonly date: { - readonly dateMath: string; - }; - }; -} - -// @public (undocumented) -export interface EnvironmentMode { - // (undocumented) - dev: boolean; - // (undocumented) - name: 'development' | 'production'; - // (undocumented) - prod: boolean; -} - -// @public -export interface ErrorToastOptions { - title: string; - toastMessage?: string; -} - -// @public -export interface FatalErrorInfo { - // (undocumented) - message: string; - // (undocumented) - stack: string | undefined; -} - -// @public -export interface FatalErrorsSetup { - add: (error: string | Error, source?: string) => never; - get$: () => Rx.Observable; -} - -// @public -export type HandlerContextType> = T extends HandlerFunction ? U : never; - -// @public -export type HandlerFunction = (context: T, ...args: any[]) => any; - -// @public -export type HandlerParameters> = T extends (context: any, ...args: infer U) => any ? U : never; - -// @public (undocumented) -export interface HttpErrorRequest { - // (undocumented) - error: Error; - // (undocumented) - request: Request; -} - -// @public (undocumented) -export interface HttpErrorResponse extends IHttpResponse { - // (undocumented) - error: Error | IHttpFetchError; -} - -// @public -export interface HttpFetchOptions extends HttpRequestInit { - asResponse?: boolean; - headers?: HttpHeadersInit; - prependBasePath?: boolean; - query?: HttpFetchQuery; -} - -// @public (undocumented) -export interface HttpFetchQuery { - // (undocumented) - [key: string]: string | number | boolean | undefined; -} - -// @public -export interface HttpHandler { - // (undocumented) - (path: string, options: HttpFetchOptions & { - asResponse: true; - }): Promise>; - // (undocumented) - (path: string, options?: HttpFetchOptions): Promise; -} - -// @public (undocumented) -export interface HttpHeadersInit { - // (undocumented) - [name: string]: any; -} - -// @public -export interface HttpInterceptor { - request?(request: Request, controller: IHttpInterceptController): Promise | Request | void; - requestError?(httpErrorRequest: HttpErrorRequest, controller: IHttpInterceptController): Promise | Request | void; - response?(httpResponse: IHttpResponse, controller: IHttpInterceptController): Promise | IHttpResponseInterceptorOverrides | void; - responseError?(httpErrorResponse: HttpErrorResponse, controller: IHttpInterceptController): Promise | IHttpResponseInterceptorOverrides | void; -} - -// @public -export interface HttpRequestInit { - body?: BodyInit | null; - cache?: RequestCache; - credentials?: RequestCredentials; - // (undocumented) - headers?: HttpHeadersInit; - integrity?: string; - keepalive?: boolean; - method?: string; - mode?: RequestMode; - redirect?: RequestRedirect; - referrer?: string; - referrerPolicy?: ReferrerPolicy; - signal?: AbortSignal | null; - window?: null; -} - -// @public (undocumented) -export interface HttpSetup { - addLoadingCountSource(countSource$: Observable): void; - anonymousPaths: IAnonymousPaths; - basePath: IBasePath; - delete: HttpHandler; - fetch: HttpHandler; - get: HttpHandler; - getLoadingCount$(): Observable; - head: HttpHandler; - intercept(interceptor: HttpInterceptor): () => void; - options: HttpHandler; - patch: HttpHandler; - post: HttpHandler; - put: HttpHandler; -} - -// @public -export type HttpStart = HttpSetup; - -// @public -export interface I18nStart { - Context: ({ children }: { - children: React.ReactNode; - }) => JSX.Element; -} - -// Warning: (ae-missing-release-tag) "IAnonymousPaths" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public -export interface IAnonymousPaths { - isAnonymous(path: string): boolean; - register(path: string): void; -} - -// @public -export interface IBasePath { - get: () => string; - prepend: (url: string) => string; - remove: (url: string) => string; -} - -// @public -export interface IContextContainer> { - createHandler(pluginOpaqueId: PluginOpaqueId, handler: THandler): (...rest: HandlerParameters) => ShallowPromise>; - registerContext>(pluginOpaqueId: PluginOpaqueId, contextName: TContextName, provider: IContextProvider): this; -} - -// @public -export type IContextProvider, TContextName extends keyof HandlerContextType> = (context: Partial>, ...rest: HandlerParameters) => Promise[TContextName]> | HandlerContextType[TContextName]; - -// @public (undocumented) -export interface IHttpFetchError extends Error { - // (undocumented) - readonly body?: any; - // @deprecated (undocumented) - readonly req: Request; - // (undocumented) - readonly request: Request; - // @deprecated (undocumented) - readonly res?: Response; - // (undocumented) - readonly response?: Response; -} - -// @public -export interface IHttpInterceptController { - halt(): void; - halted: boolean; -} - -// @public (undocumented) -export interface IHttpResponse { - readonly body?: TResponseBody; - readonly request: Readonly; - readonly response?: Readonly; -} - -// @public -export interface IHttpResponseInterceptorOverrides { - readonly body?: TResponseBody; - readonly response?: Readonly; -} - -// @public -export type IToasts = Pick; - -// @public -export interface IUiSettingsClient { - get$: (key: string, defaultOverride?: T) => Observable; - get: (key: string, defaultOverride?: T) => T; - getAll: () => Readonly>; - getSaved$: () => Observable<{ - key: string; - newValue: T; - oldValue: T; - }>; - getUpdate$: () => Observable<{ - key: string; - newValue: T; - oldValue: T; - }>; - getUpdateErrors$: () => Observable; - isCustom: (key: string) => boolean; - isDeclared: (key: string) => boolean; - isDefault: (key: string) => boolean; - isOverridden: (key: string) => boolean; - overrideLocalDefault: (key: string, newDefault: any) => void; - remove: (key: string) => Promise; - set: (key: string, value: any) => Promise; -} - -// @public @deprecated -export interface LegacyCoreSetup extends CoreSetup { - // Warning: (ae-forgotten-export) The symbol "InjectedMetadataSetup" needs to be exported by the entry point index.d.ts - // - // @deprecated (undocumented) - injectedMetadata: InjectedMetadataSetup; -} - -// @public @deprecated -export interface LegacyCoreStart extends CoreStart { - // Warning: (ae-forgotten-export) The symbol "InjectedMetadataStart" needs to be exported by the entry point index.d.ts - // - // @deprecated (undocumented) - injectedMetadata: InjectedMetadataStart; -} - -// @public (undocumented) -export interface LegacyNavLink { - // (undocumented) - euiIconType?: string; - // (undocumented) - icon?: string; - // (undocumented) - id: string; - // (undocumented) - order: number; - // (undocumented) - title: string; - // (undocumented) - url: string; -} - -// @public -export type MountPoint = (element: T) => UnmountCallback; - -// @public (undocumented) -export interface NotificationsSetup { - // (undocumented) - toasts: ToastsSetup; -} - -// @public (undocumented) -export interface NotificationsStart { - // (undocumented) - toasts: ToastsStart; -} - -// @public (undocumented) -export interface OverlayBannersStart { - add(mount: MountPoint, priority?: number): string; - // Warning: (ae-forgotten-export) The symbol "OverlayBanner" needs to be exported by the entry point index.d.ts - // - // @internal (undocumented) - get$(): Observable; - // (undocumented) - getComponent(): JSX.Element; - remove(id: string): boolean; - replace(id: string | undefined, mount: MountPoint, priority?: number): string; -} - -// @public -export interface OverlayRef { - close(): Promise; - onClose: Promise; -} - -// @public (undocumented) -export interface OverlayStart { - // (undocumented) - banners: OverlayBannersStart; - // Warning: (ae-forgotten-export) The symbol "OverlayFlyoutStart" needs to be exported by the entry point index.d.ts - // - // (undocumented) - openFlyout: OverlayFlyoutStart['open']; - // Warning: (ae-forgotten-export) The symbol "OverlayModalStart" needs to be exported by the entry point index.d.ts - // - // (undocumented) - openModal: OverlayModalStart['open']; -} - -// @public (undocumented) -export interface PackageInfo { - // (undocumented) - branch: string; - // (undocumented) - buildNum: number; - // (undocumented) - buildSha: string; - // (undocumented) - dist: boolean; - // (undocumented) - version: string; -} - -// @public -export interface Plugin { - // (undocumented) - setup(core: CoreSetup, plugins: TPluginsSetup): TSetup | Promise; - // (undocumented) - start(core: CoreStart, plugins: TPluginsStart): TStart | Promise; - // (undocumented) - stop?(): void; -} - -// @public -export type PluginInitializer = (core: PluginInitializerContext) => Plugin; - -// @public -export interface PluginInitializerContext { - // (undocumented) - readonly config: { - get: () => T; - }; - // (undocumented) - readonly env: { - mode: Readonly; - packageInfo: Readonly; - }; - readonly opaqueId: PluginOpaqueId; -} - -// @public (undocumented) -export type PluginOpaqueId = symbol; - -// Warning: (ae-forgotten-export) The symbol "RecursiveReadonlyArray" needs to be exported by the entry point index.d.ts -// -// @public (undocumented) -export type RecursiveReadonly = T extends (...args: any[]) => any ? T : T extends any[] ? RecursiveReadonlyArray : T extends object ? Readonly<{ - [K in keyof T]: RecursiveReadonly; -}> : T; - -// @public (undocumented) -export interface SavedObject { - attributes: T; - // (undocumented) - error?: { - message: string; - statusCode: number; - }; - id: string; - migrationVersion?: SavedObjectsMigrationVersion; - references: SavedObjectReference[]; - type: string; - updated_at?: string; - version?: string; -} - -// @public -export type SavedObjectAttribute = SavedObjectAttributeSingle | SavedObjectAttributeSingle[]; - -// @public -export interface SavedObjectAttributes { - // (undocumented) - [key: string]: SavedObjectAttribute; -} - -// @public -export type SavedObjectAttributeSingle = string | number | boolean | null | undefined | SavedObjectAttributes; - -// @public -export interface SavedObjectReference { - // (undocumented) - id: string; - // (undocumented) - name: string; - // (undocumented) - type: string; -} - -// @public (undocumented) -export interface SavedObjectsBaseOptions { - namespace?: string; -} - -// @public (undocumented) -export interface SavedObjectsBatchResponse { - // (undocumented) - savedObjects: Array>; -} - -// @public (undocumented) -export interface SavedObjectsBulkCreateObject extends SavedObjectsCreateOptions { - // (undocumented) - attributes: T; - // (undocumented) - type: string; -} - -// @public (undocumented) -export interface SavedObjectsBulkCreateOptions { - overwrite?: boolean; -} - -// @public (undocumented) -export interface SavedObjectsBulkUpdateObject { - // (undocumented) - attributes: T; - // (undocumented) - id: string; - // (undocumented) - references?: SavedObjectReference[]; - // (undocumented) - type: string; - // (undocumented) - version?: string; -} - -// @public (undocumented) -export interface SavedObjectsBulkUpdateOptions { - // (undocumented) - namespace?: string; -} - -// @public -export class SavedObjectsClient { - // @internal - constructor(http: HttpSetup); - bulkCreate: (objects?: SavedObjectsBulkCreateObject[], options?: SavedObjectsBulkCreateOptions) => Promise>; - bulkGet: (objects?: { - id: string; - type: string; - }[]) => Promise>; - bulkUpdate(objects?: SavedObjectsBulkUpdateObject[]): Promise>; - create: (type: string, attributes: T, options?: SavedObjectsCreateOptions) => Promise>; - delete: (type: string, id: string) => Promise<{}>; - find: (options: Pick) => Promise>; - get: (type: string, id: string) => Promise>; - update(type: string, id: string, attributes: T, { version, migrationVersion, references }?: SavedObjectsUpdateOptions): Promise>; -} - -// @public -export type SavedObjectsClientContract = PublicMethodsOf; - -// @public (undocumented) -export interface SavedObjectsCreateOptions { - id?: string; - migrationVersion?: SavedObjectsMigrationVersion; - overwrite?: boolean; - // (undocumented) - references?: SavedObjectReference[]; -} - -// @public (undocumented) -export interface SavedObjectsFindOptions extends SavedObjectsBaseOptions { - // (undocumented) - defaultSearchOperator?: 'AND' | 'OR'; - fields?: string[]; - // (undocumented) - filter?: string; - // (undocumented) - hasReference?: { - type: string; - id: string; - }; - // (undocumented) - page?: number; - // (undocumented) - perPage?: number; - search?: string; - searchFields?: string[]; - // (undocumented) - sortField?: string; - // (undocumented) - sortOrder?: string; - // (undocumented) - type: string | string[]; -} - -// @public -export interface SavedObjectsFindResponsePublic extends SavedObjectsBatchResponse { - // (undocumented) - page: number; - // (undocumented) - perPage: number; - // (undocumented) - total: number; -} - -// @public -export interface SavedObjectsImportConflictError { - // (undocumented) - type: 'conflict'; -} - -// @public -export interface SavedObjectsImportError { - // (undocumented) - error: SavedObjectsImportConflictError | SavedObjectsImportUnsupportedTypeError | SavedObjectsImportMissingReferencesError | SavedObjectsImportUnknownError; - // (undocumented) - id: string; - // (undocumented) - title?: string; - // (undocumented) - type: string; -} - -// @public -export interface SavedObjectsImportMissingReferencesError { - // (undocumented) - blocking: Array<{ - type: string; - id: string; - }>; - // (undocumented) - references: Array<{ - type: string; - id: string; - }>; - // (undocumented) - type: 'missing_references'; -} - -// @public -export interface SavedObjectsImportResponse { - // (undocumented) - errors?: SavedObjectsImportError[]; - // (undocumented) - success: boolean; - // (undocumented) - successCount: number; -} - -// @public -export interface SavedObjectsImportRetry { - // (undocumented) - id: string; - // (undocumented) - overwrite: boolean; - // (undocumented) - replaceReferences: Array<{ - type: string; - from: string; - to: string; - }>; - // (undocumented) - type: string; -} - -// @public -export interface SavedObjectsImportUnknownError { - // (undocumented) - message: string; - // (undocumented) - statusCode: number; - // (undocumented) - type: 'unknown'; -} - -// @public -export interface SavedObjectsImportUnsupportedTypeError { - // (undocumented) - type: 'unsupported_type'; -} - -// @public -export interface SavedObjectsMigrationVersion { - // (undocumented) - [pluginName: string]: string; -} - -// @public (undocumented) -export interface SavedObjectsStart { - // (undocumented) - client: SavedObjectsClientContract; -} - -// @public (undocumented) -export interface SavedObjectsUpdateOptions { - migrationVersion?: SavedObjectsMigrationVersion; - // (undocumented) - references?: SavedObjectReference[]; - // (undocumented) - version?: string; -} - -// @public -export class SimpleSavedObject { - constructor(client: SavedObjectsClient, { id, type, version, attributes, error, references, migrationVersion }: SavedObject); - // (undocumented) - attributes: T; - // (undocumented) - delete(): Promise<{}>; - // (undocumented) - error: SavedObject['error']; - // (undocumented) - get(key: string): any; - // (undocumented) - has(key: string): boolean; - // (undocumented) - id: SavedObject['id']; - // (undocumented) - migrationVersion: SavedObject['migrationVersion']; - // (undocumented) - references: SavedObject['references']; - // (undocumented) - save(): Promise>; - // (undocumented) - set(key: string, value: any): T; - // (undocumented) - type: SavedObject['type']; - // (undocumented) - _version?: SavedObject['version']; -} - -// Warning: (ae-missing-release-tag) "Toast" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) -// -// @public (undocumented) -export type Toast = ToastInputFields & { - id: string; -}; - -// @public -export type ToastInput = string | ToastInputFields; - -// @public -export type ToastInputFields = Pick> & { - title?: string | MountPoint; - text?: string | MountPoint; -}; - -// @public -export class ToastsApi implements IToasts { - constructor(deps: { - uiSettings: IUiSettingsClient; - }); - add(toastOrTitle: ToastInput): Toast; - addDanger(toastOrTitle: ToastInput): Toast; - addError(error: Error, options: ErrorToastOptions): Toast; - addSuccess(toastOrTitle: ToastInput): Toast; - addWarning(toastOrTitle: ToastInput): Toast; - get$(): Rx.Observable; - remove(toastOrId: Toast | string): void; - // @internal (undocumented) - start({ overlays, i18n }: { - overlays: OverlayStart; - i18n: I18nStart; - }): void; - } - -// @public (undocumented) -export type ToastsSetup = IToasts; - -// @public (undocumented) -export type ToastsStart = IToasts; - -// @public (undocumented) -export interface UiSettingsState { - // (undocumented) - [key: string]: UiSettingsParams_2 & UserProvidedValues_2; -} - -// @public -export type UnmountCallback = () => void; - - -``` +## API Report File for "kibana" + +> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/). + +```ts + +import { Breadcrumb } from '@elastic/eui'; +import { EuiButtonEmptyProps } from '@elastic/eui'; +import { EuiGlobalToastListToast } from '@elastic/eui'; +import { ExclusiveUnion } from '@elastic/eui'; +import { IconType } from '@elastic/eui'; +import { Observable } from 'rxjs'; +import React from 'react'; +import * as Rx from 'rxjs'; +import { ShallowPromise } from '@kbn/utility-types'; +import { UiSettingsParams as UiSettingsParams_2 } from 'src/core/server/types'; +import { UserProvidedValues as UserProvidedValues_2 } from 'src/core/server/types'; + +// @public +export interface App extends AppBase { + appRoute?: string; + chromeless?: boolean; + mount: AppMount | AppMountDeprecated; +} + +// @public (undocumented) +export interface AppBase { + capabilities?: Partial; + chromeless?: boolean; + euiIconType?: string; + icon?: string; + id: string; + // @internal + legacy?: boolean; + navLinkStatus?: AppNavLinkStatus; + order?: number; + status?: AppStatus; + title: string; + tooltip?: string; + updater$?: Observable; +} + +// @public +export type AppLeaveAction = AppLeaveDefaultAction | AppLeaveConfirmAction; + +// @public +export enum AppLeaveActionType { + // (undocumented) + confirm = "confirm", + // (undocumented) + default = "default" +} + +// Warning: (ae-unresolved-link) The @link reference could not be resolved: The package "kibana" does not have an export "AppLeaveActionFactory" +// +// @public +export interface AppLeaveConfirmAction { + // (undocumented) + text: string; + // (undocumented) + title?: string; + // (undocumented) + type: AppLeaveActionType.confirm; +} + +// Warning: (ae-unresolved-link) The @link reference could not be resolved: The package "kibana" does not have an export "AppLeaveActionFactory" +// +// @public +export interface AppLeaveDefaultAction { + // (undocumented) + type: AppLeaveActionType.default; +} + +// Warning: (ae-forgotten-export) The symbol "AppLeaveActionFactory" needs to be exported by the entry point index.d.ts +// +// @public +export type AppLeaveHandler = (factory: AppLeaveActionFactory) => AppLeaveAction; + +// @public (undocumented) +export interface ApplicationSetup { + register(app: App): void; + registerAppUpdater(appUpdater$: Observable): void; + // @deprecated + registerMountContext(contextName: T, provider: IContextProvider): void; +} + +// @public (undocumented) +export interface ApplicationStart { + capabilities: RecursiveReadonly; + getUrlForApp(appId: string, options?: { + path?: string; + }): string; + navigateToApp(appId: string, options?: { + path?: string; + state?: any; + }): Promise; + // @deprecated + registerMountContext(contextName: T, provider: IContextProvider): void; +} + +// @public +export type AppMount = (params: AppMountParameters) => AppUnmount | Promise; + +// @public @deprecated +export interface AppMountContext { + core: { + application: Pick; + chrome: ChromeStart; + docLinks: DocLinksStart; + http: HttpStart; + i18n: I18nStart; + notifications: NotificationsStart; + overlays: OverlayStart; + savedObjects: SavedObjectsStart; + uiSettings: IUiSettingsClient; + injectedMetadata: { + getInjectedVar: (name: string, defaultValue?: any) => unknown; + }; + }; +} + +// @public @deprecated +export type AppMountDeprecated = (context: AppMountContext, params: AppMountParameters) => AppUnmount | Promise; + +// @public (undocumented) +export interface AppMountParameters { + appBasePath: string; + element: HTMLElement; + onAppLeave: (handler: AppLeaveHandler) => void; +} + +// @public +export enum AppNavLinkStatus { + default = 0, + disabled = 2, + hidden = 3, + visible = 1 +} + +// @public +export enum AppStatus { + accessible = 0, + inaccessible = 1 +} + +// @public +export type AppUnmount = () => void; + +// @public +export type AppUpdatableFields = Pick; + +// @public +export type AppUpdater = (app: AppBase) => Partial | undefined; + +// @public +export interface Capabilities { + [key: string]: Record>; + catalogue: Record; + management: { + [sectionId: string]: Record; + }; + navLinks: Record; +} + +// @public (undocumented) +export interface ChromeBadge { + // (undocumented) + iconType?: IconType; + // (undocumented) + text: string; + // (undocumented) + tooltip: string; +} + +// @public (undocumented) +export interface ChromeBrand { + // (undocumented) + logo?: string; + // (undocumented) + smallLogo?: string; +} + +// @public (undocumented) +export type ChromeBreadcrumb = Breadcrumb; + +// @public +export interface ChromeDocTitle { + // @internal (undocumented) + __legacy: { + setBaseTitle(baseTitle: string): void; + }; + change(newTitle: string | string[]): void; + reset(): void; +} + +// @public (undocumented) +export interface ChromeHelpExtension { + appName: string; + content?: (element: HTMLDivElement) => () => void; + links?: ChromeHelpExtensionMenuLink[]; +} + +// @public (undocumented) +export type ChromeHelpExtensionMenuCustomLink = EuiButtonEmptyProps & { + linkType: 'custom'; + content: React.ReactNode; +}; + +// @public (undocumented) +export type ChromeHelpExtensionMenuDiscussLink = EuiButtonEmptyProps & { + linkType: 'discuss'; + href: string; +}; + +// @public (undocumented) +export type ChromeHelpExtensionMenuDocumentationLink = EuiButtonEmptyProps & { + linkType: 'documentation'; + href: string; +}; + +// @public (undocumented) +export type ChromeHelpExtensionMenuGitHubLink = EuiButtonEmptyProps & { + linkType: 'github'; + labels: string[]; + title?: string; +}; + +// @public (undocumented) +export type ChromeHelpExtensionMenuLink = ExclusiveUnion>>; + +// @public (undocumented) +export interface ChromeNavControl { + // (undocumented) + mount: MountPoint; + // (undocumented) + order?: number; +} + +// @public +export interface ChromeNavControls { + // @internal (undocumented) + getLeft$(): Observable; + // @internal (undocumented) + getRight$(): Observable; + registerLeft(navControl: ChromeNavControl): void; + registerRight(navControl: ChromeNavControl): void; +} + +// @public (undocumented) +export interface ChromeNavLink { + // @deprecated + readonly active?: boolean; + readonly baseUrl: string; + // @deprecated + readonly disabled?: boolean; + readonly euiIconType?: string; + readonly hidden?: boolean; + readonly icon?: string; + readonly id: string; + // @internal + readonly legacy: boolean; + // @deprecated + readonly linkToLastSubUrl?: boolean; + readonly order?: number; + // @deprecated + readonly subUrlBase?: string; + readonly title: string; + readonly tooltip?: string; + // @deprecated + readonly url?: string; +} + +// @public +export interface ChromeNavLinks { + enableForcedAppSwitcherNavigation(): void; + get(id: string): ChromeNavLink | undefined; + getAll(): Array>; + getForceAppSwitcherNavigation$(): Observable; + getNavLinks$(): Observable>>; + has(id: string): boolean; + showOnly(id: string): void; + update(id: string, values: ChromeNavLinkUpdateableFields): ChromeNavLink | undefined; +} + +// @public (undocumented) +export type ChromeNavLinkUpdateableFields = Partial>; + +// @public +export interface ChromeRecentlyAccessed { + // Warning: (ae-unresolved-link) The @link reference could not be resolved: No member was found with name "basePath" + add(link: string, label: string, id: string): void; + get$(): Observable; + get(): ChromeRecentlyAccessedHistoryItem[]; +} + +// @public (undocumented) +export interface ChromeRecentlyAccessedHistoryItem { + // (undocumented) + id: string; + // (undocumented) + label: string; + // (undocumented) + link: string; +} + +// @public +export interface ChromeStart { + addApplicationClass(className: string): void; + docTitle: ChromeDocTitle; + getApplicationClasses$(): Observable; + getBadge$(): Observable; + getBrand$(): Observable; + getBreadcrumbs$(): Observable; + getHelpExtension$(): Observable; + getIsCollapsed$(): Observable; + getIsVisible$(): Observable; + navControls: ChromeNavControls; + navLinks: ChromeNavLinks; + recentlyAccessed: ChromeRecentlyAccessed; + removeApplicationClass(className: string): void; + setAppTitle(appTitle: string): void; + setBadge(badge?: ChromeBadge): void; + setBrand(brand: ChromeBrand): void; + setBreadcrumbs(newBreadcrumbs: ChromeBreadcrumb[]): void; + setHelpExtension(helpExtension?: ChromeHelpExtension): void; + setHelpSupportUrl(url: string): void; + setIsCollapsed(isCollapsed: boolean): void; + setIsVisible(isVisible: boolean): void; +} + +// @public +export interface ContextSetup { + createContextContainer>(): IContextContainer; +} + +// @internal (undocumented) +export interface CoreContext { + // Warning: (ae-forgotten-export) The symbol "CoreId" needs to be exported by the entry point index.d.ts + // + // (undocumented) + coreId: CoreId; + // (undocumented) + env: { + mode: Readonly; + packageInfo: Readonly; + }; +} + +// @public +export interface CoreSetup { + // (undocumented) + application: ApplicationSetup; + // @deprecated (undocumented) + context: ContextSetup; + // (undocumented) + fatalErrors: FatalErrorsSetup; + getStartServices(): Promise<[CoreStart, TPluginsStart]>; + // (undocumented) + http: HttpSetup; + // @deprecated + injectedMetadata: { + getInjectedVar: (name: string, defaultValue?: any) => unknown; + }; + // (undocumented) + notifications: NotificationsSetup; + // (undocumented) + uiSettings: IUiSettingsClient; +} + +// @public +export interface CoreStart { + // (undocumented) + application: ApplicationStart; + // (undocumented) + chrome: ChromeStart; + // (undocumented) + docLinks: DocLinksStart; + // (undocumented) + http: HttpStart; + // (undocumented) + i18n: I18nStart; + // @deprecated + injectedMetadata: { + getInjectedVar: (name: string, defaultValue?: any) => unknown; + }; + // (undocumented) + notifications: NotificationsStart; + // (undocumented) + overlays: OverlayStart; + // (undocumented) + savedObjects: SavedObjectsStart; + // (undocumented) + uiSettings: IUiSettingsClient; +} + +// @internal +export class CoreSystem { + // Warning: (ae-forgotten-export) The symbol "Params" needs to be exported by the entry point index.d.ts + constructor(params: Params); + // (undocumented) + setup(): Promise<{ + fatalErrors: FatalErrorsSetup; + } | undefined>; + // (undocumented) + start(): Promise; + // (undocumented) + stop(): void; + } + +// @public (undocumented) +export interface DocLinksStart { + // (undocumented) + readonly DOC_LINK_VERSION: string; + // (undocumented) + readonly ELASTIC_WEBSITE_URL: string; + // (undocumented) + readonly links: { + readonly filebeat: { + readonly base: string; + readonly installation: string; + readonly configuration: string; + readonly elasticsearchOutput: string; + readonly startup: string; + readonly exportedFields: string; + }; + readonly auditbeat: { + readonly base: string; + }; + readonly metricbeat: { + readonly base: string; + }; + readonly heartbeat: { + readonly base: string; + }; + readonly logstash: { + readonly base: string; + }; + readonly functionbeat: { + readonly base: string; + }; + readonly winlogbeat: { + readonly base: string; + }; + readonly aggs: { + readonly date_histogram: string; + readonly date_range: string; + readonly filter: string; + readonly filters: string; + readonly geohash_grid: string; + readonly histogram: string; + readonly ip_range: string; + readonly range: string; + readonly significant_terms: string; + readonly terms: string; + readonly avg: string; + readonly avg_bucket: string; + readonly max_bucket: string; + readonly min_bucket: string; + readonly sum_bucket: string; + readonly cardinality: string; + readonly count: string; + readonly cumulative_sum: string; + readonly derivative: string; + readonly geo_bounds: string; + readonly geo_centroid: string; + readonly max: string; + readonly median: string; + readonly min: string; + readonly moving_avg: string; + readonly percentile_ranks: string; + readonly serial_diff: string; + readonly std_dev: string; + readonly sum: string; + readonly top_hits: string; + }; + readonly scriptedFields: { + readonly scriptFields: string; + readonly scriptAggs: string; + readonly painless: string; + readonly painlessApi: string; + readonly painlessSyntax: string; + readonly luceneExpressions: string; + }; + readonly indexPatterns: { + readonly loadingData: string; + readonly introduction: string; + }; + readonly kibana: string; + readonly siem: string; + readonly query: { + readonly luceneQuerySyntax: string; + readonly queryDsl: string; + readonly kueryQuerySyntax: string; + }; + readonly date: { + readonly dateMath: string; + }; + }; +} + +// @public (undocumented) +export interface EnvironmentMode { + // (undocumented) + dev: boolean; + // (undocumented) + name: 'development' | 'production'; + // (undocumented) + prod: boolean; +} + +// @public +export interface ErrorToastOptions { + title: string; + toastMessage?: string; +} + +// @public +export interface FatalErrorInfo { + // (undocumented) + message: string; + // (undocumented) + stack: string | undefined; +} + +// @public +export interface FatalErrorsSetup { + add: (error: string | Error, source?: string) => never; + get$: () => Rx.Observable; +} + +// @public +export type HandlerContextType> = T extends HandlerFunction ? U : never; + +// @public +export type HandlerFunction = (context: T, ...args: any[]) => any; + +// @public +export type HandlerParameters> = T extends (context: any, ...args: infer U) => any ? U : never; + +// @public (undocumented) +export interface HttpErrorRequest { + // (undocumented) + error: Error; + // (undocumented) + request: Request; +} + +// @public (undocumented) +export interface HttpErrorResponse extends IHttpResponse { + // (undocumented) + error: Error | IHttpFetchError; +} + +// @public +export interface HttpFetchOptions extends HttpRequestInit { + asResponse?: boolean; + headers?: HttpHeadersInit; + prependBasePath?: boolean; + query?: HttpFetchQuery; +} + +// @public (undocumented) +export interface HttpFetchQuery { + // (undocumented) + [key: string]: string | number | boolean | undefined; +} + +// @public +export interface HttpHandler { + // (undocumented) + (path: string, options: HttpFetchOptions & { + asResponse: true; + }): Promise>; + // (undocumented) + (path: string, options?: HttpFetchOptions): Promise; +} + +// @public (undocumented) +export interface HttpHeadersInit { + // (undocumented) + [name: string]: any; +} + +// @public +export interface HttpInterceptor { + request?(request: Request, controller: IHttpInterceptController): Promise | Request | void; + requestError?(httpErrorRequest: HttpErrorRequest, controller: IHttpInterceptController): Promise | Request | void; + response?(httpResponse: IHttpResponse, controller: IHttpInterceptController): Promise | IHttpResponseInterceptorOverrides | void; + responseError?(httpErrorResponse: HttpErrorResponse, controller: IHttpInterceptController): Promise | IHttpResponseInterceptorOverrides | void; +} + +// @public +export interface HttpRequestInit { + body?: BodyInit | null; + cache?: RequestCache; + credentials?: RequestCredentials; + // (undocumented) + headers?: HttpHeadersInit; + integrity?: string; + keepalive?: boolean; + method?: string; + mode?: RequestMode; + redirect?: RequestRedirect; + referrer?: string; + referrerPolicy?: ReferrerPolicy; + signal?: AbortSignal | null; + window?: null; +} + +// @public (undocumented) +export interface HttpSetup { + addLoadingCountSource(countSource$: Observable): void; + anonymousPaths: IAnonymousPaths; + basePath: IBasePath; + delete: HttpHandler; + fetch: HttpHandler; + get: HttpHandler; + getLoadingCount$(): Observable; + head: HttpHandler; + intercept(interceptor: HttpInterceptor): () => void; + options: HttpHandler; + patch: HttpHandler; + post: HttpHandler; + put: HttpHandler; +} + +// @public +export type HttpStart = HttpSetup; + +// @public +export interface I18nStart { + Context: ({ children }: { + children: React.ReactNode; + }) => JSX.Element; +} + +// Warning: (ae-missing-release-tag) "IAnonymousPaths" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public +export interface IAnonymousPaths { + isAnonymous(path: string): boolean; + register(path: string): void; +} + +// @public +export interface IBasePath { + get: () => string; + prepend: (url: string) => string; + remove: (url: string) => string; +} + +// @public +export interface IContextContainer> { + createHandler(pluginOpaqueId: PluginOpaqueId, handler: THandler): (...rest: HandlerParameters) => ShallowPromise>; + registerContext>(pluginOpaqueId: PluginOpaqueId, contextName: TContextName, provider: IContextProvider): this; +} + +// @public +export type IContextProvider, TContextName extends keyof HandlerContextType> = (context: Partial>, ...rest: HandlerParameters) => Promise[TContextName]> | HandlerContextType[TContextName]; + +// @public (undocumented) +export interface IHttpFetchError extends Error { + // (undocumented) + readonly body?: any; + // @deprecated (undocumented) + readonly req: Request; + // (undocumented) + readonly request: Request; + // @deprecated (undocumented) + readonly res?: Response; + // (undocumented) + readonly response?: Response; +} + +// @public +export interface IHttpInterceptController { + halt(): void; + halted: boolean; +} + +// @public (undocumented) +export interface IHttpResponse { + readonly body?: TResponseBody; + readonly request: Readonly; + readonly response?: Readonly; +} + +// @public +export interface IHttpResponseInterceptorOverrides { + readonly body?: TResponseBody; + readonly response?: Readonly; +} + +// @public +export type IToasts = Pick; + +// @public +export interface IUiSettingsClient { + get$: (key: string, defaultOverride?: T) => Observable; + get: (key: string, defaultOverride?: T) => T; + getAll: () => Readonly>; + getSaved$: () => Observable<{ + key: string; + newValue: T; + oldValue: T; + }>; + getUpdate$: () => Observable<{ + key: string; + newValue: T; + oldValue: T; + }>; + getUpdateErrors$: () => Observable; + isCustom: (key: string) => boolean; + isDeclared: (key: string) => boolean; + isDefault: (key: string) => boolean; + isOverridden: (key: string) => boolean; + overrideLocalDefault: (key: string, newDefault: any) => void; + remove: (key: string) => Promise; + set: (key: string, value: any) => Promise; +} + +// @public @deprecated +export interface LegacyCoreSetup extends CoreSetup { + // Warning: (ae-forgotten-export) The symbol "InjectedMetadataSetup" needs to be exported by the entry point index.d.ts + // + // @deprecated (undocumented) + injectedMetadata: InjectedMetadataSetup; +} + +// @public @deprecated +export interface LegacyCoreStart extends CoreStart { + // Warning: (ae-forgotten-export) The symbol "InjectedMetadataStart" needs to be exported by the entry point index.d.ts + // + // @deprecated (undocumented) + injectedMetadata: InjectedMetadataStart; +} + +// @public (undocumented) +export interface LegacyNavLink { + // (undocumented) + euiIconType?: string; + // (undocumented) + icon?: string; + // (undocumented) + id: string; + // (undocumented) + order: number; + // (undocumented) + title: string; + // (undocumented) + url: string; +} + +// @public +export type MountPoint = (element: T) => UnmountCallback; + +// @public (undocumented) +export interface NotificationsSetup { + // (undocumented) + toasts: ToastsSetup; +} + +// @public (undocumented) +export interface NotificationsStart { + // (undocumented) + toasts: ToastsStart; +} + +// @public (undocumented) +export interface OverlayBannersStart { + add(mount: MountPoint, priority?: number): string; + // Warning: (ae-forgotten-export) The symbol "OverlayBanner" needs to be exported by the entry point index.d.ts + // + // @internal (undocumented) + get$(): Observable; + // (undocumented) + getComponent(): JSX.Element; + remove(id: string): boolean; + replace(id: string | undefined, mount: MountPoint, priority?: number): string; +} + +// @public +export interface OverlayRef { + close(): Promise; + onClose: Promise; +} + +// @public (undocumented) +export interface OverlayStart { + // (undocumented) + banners: OverlayBannersStart; + // (undocumented) + openConfirm: OverlayModalStart['openConfirm']; + // Warning: (ae-forgotten-export) The symbol "OverlayFlyoutStart" needs to be exported by the entry point index.d.ts + // + // (undocumented) + openFlyout: OverlayFlyoutStart['open']; + // Warning: (ae-forgotten-export) The symbol "OverlayModalStart" needs to be exported by the entry point index.d.ts + // + // (undocumented) + openModal: OverlayModalStart['open']; +} + +// @public (undocumented) +export interface PackageInfo { + // (undocumented) + branch: string; + // (undocumented) + buildNum: number; + // (undocumented) + buildSha: string; + // (undocumented) + dist: boolean; + // (undocumented) + version: string; +} + +// @public +export interface Plugin { + // (undocumented) + setup(core: CoreSetup, plugins: TPluginsSetup): TSetup | Promise; + // (undocumented) + start(core: CoreStart, plugins: TPluginsStart): TStart | Promise; + // (undocumented) + stop?(): void; +} + +// @public +export type PluginInitializer = (core: PluginInitializerContext) => Plugin; + +// @public +export interface PluginInitializerContext { + // (undocumented) + readonly config: { + get: () => T; + }; + // (undocumented) + readonly env: { + mode: Readonly; + packageInfo: Readonly; + }; + readonly opaqueId: PluginOpaqueId; +} + +// @public (undocumented) +export type PluginOpaqueId = symbol; + +// Warning: (ae-forgotten-export) The symbol "RecursiveReadonlyArray" needs to be exported by the entry point index.d.ts +// +// @public (undocumented) +export type RecursiveReadonly = T extends (...args: any[]) => any ? T : T extends any[] ? RecursiveReadonlyArray : T extends object ? Readonly<{ + [K in keyof T]: RecursiveReadonly; +}> : T; + +// @public (undocumented) +export interface SavedObject { + attributes: T; + // (undocumented) + error?: { + message: string; + statusCode: number; + }; + id: string; + migrationVersion?: SavedObjectsMigrationVersion; + references: SavedObjectReference[]; + type: string; + updated_at?: string; + version?: string; +} + +// @public +export type SavedObjectAttribute = SavedObjectAttributeSingle | SavedObjectAttributeSingle[]; + +// @public +export interface SavedObjectAttributes { + // (undocumented) + [key: string]: SavedObjectAttribute; +} + +// @public +export type SavedObjectAttributeSingle = string | number | boolean | null | undefined | SavedObjectAttributes; + +// @public +export interface SavedObjectReference { + // (undocumented) + id: string; + // (undocumented) + name: string; + // (undocumented) + type: string; +} + +// @public (undocumented) +export interface SavedObjectsBaseOptions { + namespace?: string; +} + +// @public (undocumented) +export interface SavedObjectsBatchResponse { + // (undocumented) + savedObjects: Array>; +} + +// @public (undocumented) +export interface SavedObjectsBulkCreateObject extends SavedObjectsCreateOptions { + // (undocumented) + attributes: T; + // (undocumented) + type: string; +} + +// @public (undocumented) +export interface SavedObjectsBulkCreateOptions { + overwrite?: boolean; +} + +// @public (undocumented) +export interface SavedObjectsBulkUpdateObject { + // (undocumented) + attributes: T; + // (undocumented) + id: string; + // (undocumented) + references?: SavedObjectReference[]; + // (undocumented) + type: string; + // (undocumented) + version?: string; +} + +// @public (undocumented) +export interface SavedObjectsBulkUpdateOptions { + // (undocumented) + namespace?: string; +} + +// @public +export class SavedObjectsClient { + // @internal + constructor(http: HttpSetup); + bulkCreate: (objects?: SavedObjectsBulkCreateObject[], options?: SavedObjectsBulkCreateOptions) => Promise>; + bulkGet: (objects?: { + id: string; + type: string; + }[]) => Promise>; + bulkUpdate(objects?: SavedObjectsBulkUpdateObject[]): Promise>; + create: (type: string, attributes: T, options?: SavedObjectsCreateOptions) => Promise>; + delete: (type: string, id: string) => Promise<{}>; + find: (options: Pick) => Promise>; + get: (type: string, id: string) => Promise>; + update(type: string, id: string, attributes: T, { version, migrationVersion, references }?: SavedObjectsUpdateOptions): Promise>; +} + +// @public +export type SavedObjectsClientContract = PublicMethodsOf; + +// @public (undocumented) +export interface SavedObjectsCreateOptions { + id?: string; + migrationVersion?: SavedObjectsMigrationVersion; + overwrite?: boolean; + // (undocumented) + references?: SavedObjectReference[]; +} + +// @public (undocumented) +export interface SavedObjectsFindOptions extends SavedObjectsBaseOptions { + // (undocumented) + defaultSearchOperator?: 'AND' | 'OR'; + fields?: string[]; + // (undocumented) + filter?: string; + // (undocumented) + hasReference?: { + type: string; + id: string; + }; + // (undocumented) + page?: number; + // (undocumented) + perPage?: number; + search?: string; + searchFields?: string[]; + // (undocumented) + sortField?: string; + // (undocumented) + sortOrder?: string; + // (undocumented) + type: string | string[]; +} + +// @public +export interface SavedObjectsFindResponsePublic extends SavedObjectsBatchResponse { + // (undocumented) + page: number; + // (undocumented) + perPage: number; + // (undocumented) + total: number; +} + +// @public +export interface SavedObjectsImportConflictError { + // (undocumented) + type: 'conflict'; +} + +// @public +export interface SavedObjectsImportError { + // (undocumented) + error: SavedObjectsImportConflictError | SavedObjectsImportUnsupportedTypeError | SavedObjectsImportMissingReferencesError | SavedObjectsImportUnknownError; + // (undocumented) + id: string; + // (undocumented) + title?: string; + // (undocumented) + type: string; +} + +// @public +export interface SavedObjectsImportMissingReferencesError { + // (undocumented) + blocking: Array<{ + type: string; + id: string; + }>; + // (undocumented) + references: Array<{ + type: string; + id: string; + }>; + // (undocumented) + type: 'missing_references'; +} + +// @public +export interface SavedObjectsImportResponse { + // (undocumented) + errors?: SavedObjectsImportError[]; + // (undocumented) + success: boolean; + // (undocumented) + successCount: number; +} + +// @public +export interface SavedObjectsImportRetry { + // (undocumented) + id: string; + // (undocumented) + overwrite: boolean; + // (undocumented) + replaceReferences: Array<{ + type: string; + from: string; + to: string; + }>; + // (undocumented) + type: string; +} + +// @public +export interface SavedObjectsImportUnknownError { + // (undocumented) + message: string; + // (undocumented) + statusCode: number; + // (undocumented) + type: 'unknown'; +} + +// @public +export interface SavedObjectsImportUnsupportedTypeError { + // (undocumented) + type: 'unsupported_type'; +} + +// @public +export interface SavedObjectsMigrationVersion { + // (undocumented) + [pluginName: string]: string; +} + +// @public (undocumented) +export interface SavedObjectsStart { + // (undocumented) + client: SavedObjectsClientContract; +} + +// @public (undocumented) +export interface SavedObjectsUpdateOptions { + migrationVersion?: SavedObjectsMigrationVersion; + // (undocumented) + references?: SavedObjectReference[]; + // (undocumented) + version?: string; +} + +// @public +export class SimpleSavedObject { + constructor(client: SavedObjectsClient, { id, type, version, attributes, error, references, migrationVersion }: SavedObject); + // (undocumented) + attributes: T; + // (undocumented) + delete(): Promise<{}>; + // (undocumented) + error: SavedObject['error']; + // (undocumented) + get(key: string): any; + // (undocumented) + has(key: string): boolean; + // (undocumented) + id: SavedObject['id']; + // (undocumented) + migrationVersion: SavedObject['migrationVersion']; + // (undocumented) + references: SavedObject['references']; + // (undocumented) + save(): Promise>; + // (undocumented) + set(key: string, value: any): T; + // (undocumented) + type: SavedObject['type']; + // (undocumented) + _version?: SavedObject['version']; +} + +// Warning: (ae-missing-release-tag) "Toast" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal) +// +// @public (undocumented) +export type Toast = ToastInputFields & { + id: string; +}; + +// @public +export type ToastInput = string | ToastInputFields; + +// @public +export type ToastInputFields = Pick> & { + title?: string | MountPoint; + text?: string | MountPoint; +}; + +// @public +export class ToastsApi implements IToasts { + constructor(deps: { + uiSettings: IUiSettingsClient; + }); + add(toastOrTitle: ToastInput): Toast; + addDanger(toastOrTitle: ToastInput): Toast; + addError(error: Error, options: ErrorToastOptions): Toast; + addSuccess(toastOrTitle: ToastInput): Toast; + addWarning(toastOrTitle: ToastInput): Toast; + get$(): Rx.Observable; + remove(toastOrId: Toast | string): void; + // @internal (undocumented) + start({ overlays, i18n }: { + overlays: OverlayStart; + i18n: I18nStart; + }): void; + } + +// @public (undocumented) +export type ToastsSetup = IToasts; + +// @public (undocumented) +export type ToastsStart = IToasts; + +// @public (undocumented) +export interface UiSettingsState { + // (undocumented) + [key: string]: UiSettingsParams_2 & UserProvidedValues_2; +} + +// @public +export type UnmountCallback = () => void; + + +``` diff --git a/src/core/server/config/__mocks__/env.ts b/src/core/server/config/__mocks__/env.ts index 644b499ff56d8..80cfab81fb557 100644 --- a/src/core/server/config/__mocks__/env.ts +++ b/src/core/server/config/__mocks__/env.ts @@ -38,6 +38,7 @@ export function getEnvOptions(options: DeepPartial = {}): EnvOptions basePath: false, optimize: false, oss: false, + runExamples: false, ...(options.cliArgs || {}), }, isDevClusterMaster: diff --git a/src/core/server/config/__snapshots__/env.test.ts.snap b/src/core/server/config/__snapshots__/env.test.ts.snap index 1f4661283de6e..204b8a70aa877 100644 --- a/src/core/server/config/__snapshots__/env.test.ts.snap +++ b/src/core/server/config/__snapshots__/env.test.ts.snap @@ -12,6 +12,7 @@ Env { "oss": false, "quiet": false, "repl": false, + "runExamples": false, "silent": false, "watch": false, }, @@ -40,7 +41,6 @@ Env { "/test/kibanaRoot/plugins", "/test/kibanaRoot/../kibana-extra", ], - "staticFilesDir": "/test/kibanaRoot/ui", } `; @@ -56,6 +56,7 @@ Env { "oss": false, "quiet": false, "repl": false, + "runExamples": false, "silent": false, "watch": false, }, @@ -84,7 +85,6 @@ Env { "/test/kibanaRoot/plugins", "/test/kibanaRoot/../kibana-extra", ], - "staticFilesDir": "/test/kibanaRoot/ui", } `; @@ -99,6 +99,7 @@ Env { "oss": false, "quiet": false, "repl": false, + "runExamples": false, "silent": false, "watch": false, }, @@ -127,7 +128,6 @@ Env { "/test/kibanaRoot/plugins", "/test/kibanaRoot/../kibana-extra", ], - "staticFilesDir": "/test/kibanaRoot/ui", } `; @@ -142,6 +142,7 @@ Env { "oss": false, "quiet": false, "repl": false, + "runExamples": false, "silent": false, "watch": false, }, @@ -170,7 +171,6 @@ Env { "/test/kibanaRoot/plugins", "/test/kibanaRoot/../kibana-extra", ], - "staticFilesDir": "/test/kibanaRoot/ui", } `; @@ -185,6 +185,7 @@ Env { "oss": false, "quiet": false, "repl": false, + "runExamples": false, "silent": false, "watch": false, }, @@ -213,7 +214,6 @@ Env { "/test/kibanaRoot/plugins", "/test/kibanaRoot/../kibana-extra", ], - "staticFilesDir": "/test/kibanaRoot/ui", } `; @@ -228,6 +228,7 @@ Env { "oss": false, "quiet": false, "repl": false, + "runExamples": false, "silent": false, "watch": false, }, @@ -256,6 +257,5 @@ Env { "/some/home/dir/plugins", "/some/home/dir/../kibana-extra", ], - "staticFilesDir": "/some/home/dir/ui", } `; diff --git a/src/core/server/config/deprecation/core_deprecations.ts b/src/core/server/config/deprecation/core_deprecations.ts index 6a401ec6625a2..36fe95e05cb53 100644 --- a/src/core/server/config/deprecation/core_deprecations.ts +++ b/src/core/server/config/deprecation/core_deprecations.ts @@ -97,7 +97,6 @@ export const coreDeprecationProvider: ConfigDeprecationProvider = ({ }) => [ unusedFromRoot('savedObjects.indexCheckTimeout'), unusedFromRoot('server.xsrf.token'), - unusedFromRoot('uiSettings.enabled'), renameFromRoot('optimize.lazy', 'optimize.watch'), renameFromRoot('optimize.lazyPort', 'optimize.watchPort'), renameFromRoot('optimize.lazyHost', 'optimize.watchHost'), diff --git a/src/core/server/config/env.test.ts b/src/core/server/config/env.test.ts index 5812fa93cf18f..c244012e34469 100644 --- a/src/core/server/config/env.test.ts +++ b/src/core/server/config/env.test.ts @@ -152,3 +152,25 @@ test('pluginSearchPaths does not contains x-pack plugins path if --oss flag is t expect(env.pluginSearchPaths).not.toContain('/some/home/dir/x-pack/plugins'); }); + +test('pluginSearchPaths contains examples plugins path if --run-examples flag is true', () => { + const env = new Env( + '/some/home/dir', + getEnvOptions({ + cliArgs: { runExamples: true }, + }) + ); + + expect(env.pluginSearchPaths).toContain('/some/home/dir/examples'); +}); + +test('pluginSearchPaths does not contains examples plugins path if --run-examples flag is false', () => { + const env = new Env( + '/some/home/dir', + getEnvOptions({ + cliArgs: { runExamples: false }, + }) + ); + + expect(env.pluginSearchPaths).not.toContain('/some/home/dir/examples'); +}); diff --git a/src/core/server/config/env.ts b/src/core/server/config/env.ts index 460773d89db85..db363fcd4d751 100644 --- a/src/core/server/config/env.ts +++ b/src/core/server/config/env.ts @@ -43,6 +43,7 @@ export interface CliArgs { optimize: boolean; open: boolean; oss: boolean; + runExamples: boolean; } export class Env { @@ -61,8 +62,6 @@ export class Env { /** @internal */ public readonly logDir: string; /** @internal */ - public readonly staticFilesDir: string; - /** @internal */ public readonly pluginSearchPaths: readonly string[]; /** @@ -100,14 +99,14 @@ export class Env { this.configDir = resolve(this.homeDir, 'config'); this.binDir = resolve(this.homeDir, 'bin'); this.logDir = resolve(this.homeDir, 'log'); - this.staticFilesDir = resolve(this.homeDir, 'ui'); this.pluginSearchPaths = [ resolve(this.homeDir, 'src', 'plugins'), - options.cliArgs.oss ? '' : resolve(this.homeDir, 'x-pack', 'plugins'), + ...(options.cliArgs.oss ? [] : [resolve(this.homeDir, 'x-pack', 'plugins')]), resolve(this.homeDir, 'plugins'), + ...(options.cliArgs.runExamples ? [resolve(this.homeDir, 'examples')] : []), resolve(this.homeDir, '..', 'kibana-extra'), - ].filter(Boolean); + ]; this.cliArgs = Object.freeze(options.cliArgs); this.configs = Object.freeze(options.configs); 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_client_config.test.mocks.ts b/src/core/server/elasticsearch/elasticsearch_client_config.test.mocks.ts deleted file mode 100644 index f6c6079822cb5..0000000000000 --- a/src/core/server/elasticsearch/elasticsearch_client_config.test.mocks.ts +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export const mockReadFileSync = jest.fn(); -jest.mock('fs', () => ({ readFileSync: mockReadFileSync })); diff --git a/src/core/server/elasticsearch/elasticsearch_client_config.test.ts b/src/core/server/elasticsearch/elasticsearch_client_config.test.ts index 64fb41cb3e4e5..20c10459e0e8a 100644 --- a/src/core/server/elasticsearch/elasticsearch_client_config.test.ts +++ b/src/core/server/elasticsearch/elasticsearch_client_config.test.ts @@ -17,8 +17,6 @@ * under the License. */ -import { mockReadFileSync } from './elasticsearch_client_config.test.mocks'; - import { duration } from 'moment'; import { loggingServiceMock } from '../logging/logging_service.mock'; import { @@ -66,8 +64,6 @@ Object { }); test('parses fully specified config', () => { - mockReadFileSync.mockImplementation((path: string) => `content-of-${path}`); - const elasticsearchConfig: ElasticsearchClientConfig = { apiVersion: 'v7.0.0', customHeaders: { xsrf: 'something' }, @@ -87,9 +83,9 @@ test('parses fully specified config', () => { sniffInterval: 11223344, ssl: { verificationMode: 'certificate', - certificateAuthorities: ['ca-path-1', 'ca-path-2'], - certificate: 'certificate-path', - key: 'key-path', + certificateAuthorities: ['content-of-ca-path-1', 'content-of-ca-path-2'], + certificate: 'content-of-certificate-path', + key: 'content-of-key-path', keyPassphrase: 'key-pass', alwaysPresentCertificate: true, }, @@ -497,6 +493,7 @@ Object { "sniffOnConnectionFault": true, "sniffOnStart": true, "ssl": Object { + "ca": undefined, "rejectUnauthorized": false, }, } @@ -541,6 +538,7 @@ Object { "sniffOnConnectionFault": true, "sniffOnStart": true, "ssl": Object { + "ca": undefined, "checkServerIdentity": [Function], "rejectUnauthorized": true, }, @@ -581,6 +579,7 @@ Object { "sniffOnConnectionFault": true, "sniffOnStart": true, "ssl": Object { + "ca": undefined, "rejectUnauthorized": true, }, } @@ -606,8 +605,6 @@ Object { }); test('#ignoreCertAndKey = true', () => { - mockReadFileSync.mockImplementation((path: string) => `content-of-${path}`); - expect( parseElasticsearchClientConfig( { @@ -620,9 +617,9 @@ Object { requestHeadersWhitelist: [], ssl: { verificationMode: 'certificate', - certificateAuthorities: ['ca-path'], - certificate: 'certificate-path', - key: 'key-path', + certificateAuthorities: ['content-of-ca-path'], + certificate: 'content-of-certificate-path', + key: 'content-of-key-path', keyPassphrase: 'key-pass', alwaysPresentCertificate: true, }, diff --git a/src/core/server/elasticsearch/elasticsearch_client_config.ts b/src/core/server/elasticsearch/elasticsearch_client_config.ts index dcc09f711abbe..287d835c40351 100644 --- a/src/core/server/elasticsearch/elasticsearch_client_config.ts +++ b/src/core/server/elasticsearch/elasticsearch_client_config.ts @@ -18,7 +18,6 @@ */ import { ConfigOptions } from 'elasticsearch'; -import { readFileSync } from 'fs'; import { cloneDeep } from 'lodash'; import { Duration } from 'moment'; import { checkServerIdentity } from 'tls'; @@ -165,18 +164,12 @@ export function parseElasticsearchClientConfig( throw new Error(`Unknown ssl verificationMode: ${verificationMode}`); } - const readFile = (file: string) => readFileSync(file, 'utf8'); - if ( - config.ssl.certificateAuthorities !== undefined && - config.ssl.certificateAuthorities.length > 0 - ) { - esClientConfig.ssl.ca = config.ssl.certificateAuthorities.map(readFile); - } + esClientConfig.ssl.ca = config.ssl.certificateAuthorities; // Add client certificate and key if required by elasticsearch if (!ignoreCertAndKey && config.ssl.certificate && config.ssl.key) { - esClientConfig.ssl.cert = readFile(config.ssl.certificate); - esClientConfig.ssl.key = readFile(config.ssl.key); + esClientConfig.ssl.cert = config.ssl.certificate; + esClientConfig.ssl.key = config.ssl.key; esClientConfig.ssl.passphrase = config.ssl.keyPassphrase; } diff --git a/src/core/server/elasticsearch/elasticsearch_config.test.mocks.ts b/src/core/server/elasticsearch/elasticsearch_config.test.mocks.ts new file mode 100644 index 0000000000000..d908fdbfd2e80 --- /dev/null +++ b/src/core/server/elasticsearch/elasticsearch_config.test.mocks.ts @@ -0,0 +1,28 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export const mockReadFileSync = jest.fn(); +jest.mock('fs', () => ({ readFileSync: mockReadFileSync })); + +export const mockReadPkcs12Keystore = jest.fn(); +export const mockReadPkcs12Truststore = jest.fn(); +jest.mock('../../utils', () => ({ + readPkcs12Keystore: mockReadPkcs12Keystore, + readPkcs12Truststore: mockReadPkcs12Truststore, +})); diff --git a/src/core/server/elasticsearch/elasticsearch_config.test.ts b/src/core/server/elasticsearch/elasticsearch_config.test.ts index 1d919639abe5f..1b4fc5eafec76 100644 --- a/src/core/server/elasticsearch/elasticsearch_config.test.ts +++ b/src/core/server/elasticsearch/elasticsearch_config.test.ts @@ -17,7 +17,35 @@ * under the License. */ +import { + mockReadFileSync, + mockReadPkcs12Keystore, + mockReadPkcs12Truststore, +} from './elasticsearch_config.test.mocks'; + import { ElasticsearchConfig, config } from './elasticsearch_config'; +import { applyDeprecations, configDeprecationFactory } from '../config/deprecation'; + +const CONFIG_PATH = 'elasticsearch'; + +const applyElasticsearchDeprecations = (settings: Record = {}) => { + const deprecations = config.deprecations!(configDeprecationFactory); + const deprecationMessages: string[] = []; + const _config: any = {}; + _config[CONFIG_PATH] = settings; + const migrated = applyDeprecations( + _config, + deprecations.map(deprecation => ({ + deprecation, + path: CONFIG_PATH, + })), + msg => deprecationMessages.push(msg) + ); + return { + messages: deprecationMessages, + migrated, + }; +}; test('set correct defaults', () => { const configValue = new ElasticsearchConfig(config.schema.validate({})); @@ -43,7 +71,10 @@ test('set correct defaults', () => { "sniffOnStart": false, "ssl": Object { "alwaysPresentCertificate": false, + "certificate": undefined, "certificateAuthorities": undefined, + "key": undefined, + "keyPassphrase": undefined, "verificationMode": "full", }, "username": undefined, @@ -89,23 +120,238 @@ test('#requestHeadersWhitelist accepts both string and array of strings', () => expect(configValue.requestHeadersWhitelist).toEqual(['token', 'X-Forwarded-Proto']); }); -test('#ssl.certificateAuthorities accepts both string and array of strings', () => { - let configValue = new ElasticsearchConfig( - config.schema.validate({ ssl: { certificateAuthorities: 'some-path' } }) - ); - expect(configValue.ssl.certificateAuthorities).toEqual(['some-path']); +describe('reads files', () => { + beforeEach(() => { + mockReadFileSync.mockReset(); + mockReadFileSync.mockImplementation((path: string) => `content-of-${path}`); + mockReadPkcs12Keystore.mockReset(); + mockReadPkcs12Keystore.mockImplementation((path: string) => ({ + key: `content-of-${path}.key`, + cert: `content-of-${path}.cert`, + ca: [`content-of-${path}.ca`], + })); + mockReadPkcs12Truststore.mockReset(); + mockReadPkcs12Truststore.mockImplementation((path: string) => [`content-of-${path}`]); + }); - configValue = new ElasticsearchConfig( - config.schema.validate({ ssl: { certificateAuthorities: ['some-path'] } }) - ); - expect(configValue.ssl.certificateAuthorities).toEqual(['some-path']); + it('reads certificate authorities when ssl.keystore.path is specified', () => { + const configValue = new ElasticsearchConfig( + config.schema.validate({ ssl: { keystore: { path: 'some-path' } } }) + ); + expect(mockReadPkcs12Keystore).toHaveBeenCalledTimes(1); + expect(configValue.ssl.certificateAuthorities).toEqual(['content-of-some-path.ca']); + }); - configValue = new ElasticsearchConfig( - config.schema.validate({ - ssl: { certificateAuthorities: ['some-path', 'another-path'] }, - }) - ); - expect(configValue.ssl.certificateAuthorities).toEqual(['some-path', 'another-path']); + it('reads certificate authorities when ssl.truststore.path is specified', () => { + const configValue = new ElasticsearchConfig( + config.schema.validate({ ssl: { truststore: { path: 'some-path' } } }) + ); + expect(mockReadPkcs12Truststore).toHaveBeenCalledTimes(1); + expect(configValue.ssl.certificateAuthorities).toEqual(['content-of-some-path']); + }); + + it('reads certificate authorities when ssl.certificateAuthorities is specified', () => { + let configValue = new ElasticsearchConfig( + config.schema.validate({ ssl: { certificateAuthorities: 'some-path' } }) + ); + expect(mockReadFileSync).toHaveBeenCalledTimes(1); + expect(configValue.ssl.certificateAuthorities).toEqual(['content-of-some-path']); + + mockReadFileSync.mockClear(); + configValue = new ElasticsearchConfig( + config.schema.validate({ ssl: { certificateAuthorities: ['some-path'] } }) + ); + expect(mockReadFileSync).toHaveBeenCalledTimes(1); + expect(configValue.ssl.certificateAuthorities).toEqual(['content-of-some-path']); + + mockReadFileSync.mockClear(); + configValue = new ElasticsearchConfig( + config.schema.validate({ + ssl: { certificateAuthorities: ['some-path', 'another-path'] }, + }) + ); + expect(mockReadFileSync).toHaveBeenCalledTimes(2); + expect(configValue.ssl.certificateAuthorities).toEqual([ + 'content-of-some-path', + 'content-of-another-path', + ]); + }); + + it('reads certificate authorities when ssl.keystore.path, ssl.truststore.path, and ssl.certificateAuthorities are specified', () => { + const configValue = new ElasticsearchConfig( + config.schema.validate({ + ssl: { + keystore: { path: 'some-path' }, + truststore: { path: 'another-path' }, + certificateAuthorities: 'yet-another-path', + }, + }) + ); + expect(mockReadPkcs12Keystore).toHaveBeenCalledTimes(1); + expect(mockReadPkcs12Truststore).toHaveBeenCalledTimes(1); + expect(mockReadFileSync).toHaveBeenCalledTimes(1); + expect(configValue.ssl.certificateAuthorities).toEqual([ + 'content-of-some-path.ca', + 'content-of-another-path', + 'content-of-yet-another-path', + ]); + }); + + it('reads a private key and certificate when ssl.keystore.path is specified', () => { + const configValue = new ElasticsearchConfig( + config.schema.validate({ ssl: { keystore: { path: 'some-path' } } }) + ); + expect(mockReadPkcs12Keystore).toHaveBeenCalledTimes(1); + expect(configValue.ssl.key).toEqual('content-of-some-path.key'); + expect(configValue.ssl.certificate).toEqual('content-of-some-path.cert'); + }); + + it('reads a private key when ssl.key is specified', () => { + const configValue = new ElasticsearchConfig( + config.schema.validate({ ssl: { key: 'some-path' } }) + ); + expect(mockReadFileSync).toHaveBeenCalledTimes(1); + expect(configValue.ssl.key).toEqual('content-of-some-path'); + }); + + it('reads a certificate when ssl.certificate is specified', () => { + const configValue = new ElasticsearchConfig( + config.schema.validate({ ssl: { certificate: 'some-path' } }) + ); + expect(mockReadFileSync).toHaveBeenCalledTimes(1); + expect(configValue.ssl.certificate).toEqual('content-of-some-path'); + }); +}); + +describe('throws when config is invalid', () => { + beforeAll(() => { + const realFs = jest.requireActual('fs'); + mockReadFileSync.mockImplementation((path: string) => realFs.readFileSync(path)); + const utils = jest.requireActual('../../utils'); + mockReadPkcs12Keystore.mockImplementation((path: string, password?: string) => + utils.readPkcs12Keystore(path, password) + ); + mockReadPkcs12Truststore.mockImplementation((path: string, password?: string) => + utils.readPkcs12Truststore(path, password) + ); + }); + + it('throws if key is invalid', () => { + const value = { ssl: { key: '/invalid/key' } }; + expect( + () => new ElasticsearchConfig(config.schema.validate(value)) + ).toThrowErrorMatchingInlineSnapshot( + `"ENOENT: no such file or directory, open '/invalid/key'"` + ); + }); + + it('throws if certificate is invalid', () => { + const value = { ssl: { certificate: '/invalid/cert' } }; + expect( + () => new ElasticsearchConfig(config.schema.validate(value)) + ).toThrowErrorMatchingInlineSnapshot( + `"ENOENT: no such file or directory, open '/invalid/cert'"` + ); + }); + + it('throws if certificateAuthorities is invalid', () => { + const value = { ssl: { certificateAuthorities: '/invalid/ca' } }; + expect( + () => new ElasticsearchConfig(config.schema.validate(value)) + ).toThrowErrorMatchingInlineSnapshot(`"ENOENT: no such file or directory, open '/invalid/ca'"`); + }); + + it('throws if keystore path is invalid', () => { + const value = { ssl: { keystore: { path: '/invalid/keystore' } } }; + expect( + () => new ElasticsearchConfig(config.schema.validate(value)) + ).toThrowErrorMatchingInlineSnapshot( + `"ENOENT: no such file or directory, open '/invalid/keystore'"` + ); + }); + + it('throws if keystore does not contain a key', () => { + mockReadPkcs12Keystore.mockReturnValueOnce({}); + const value = { ssl: { keystore: { path: 'some-path' } } }; + expect( + () => new ElasticsearchConfig(config.schema.validate(value)) + ).toThrowErrorMatchingInlineSnapshot(`"Did not find key in Elasticsearch keystore."`); + }); + + it('throws if keystore does not contain a certificate', () => { + mockReadPkcs12Keystore.mockReturnValueOnce({ key: 'foo' }); + const value = { ssl: { keystore: { path: 'some-path' } } }; + expect( + () => new ElasticsearchConfig(config.schema.validate(value)) + ).toThrowErrorMatchingInlineSnapshot(`"Did not find certificate in Elasticsearch keystore."`); + }); + + it('throws if truststore path is invalid', () => { + const value = { ssl: { keystore: { path: '/invalid/truststore' } } }; + expect( + () => new ElasticsearchConfig(config.schema.validate(value)) + ).toThrowErrorMatchingInlineSnapshot( + `"ENOENT: no such file or directory, open '/invalid/truststore'"` + ); + }); + + it('throws if key and keystore.path are both specified', () => { + const value = { ssl: { key: 'foo', keystore: { path: 'bar' } } }; + expect(() => config.schema.validate(value)).toThrowErrorMatchingInlineSnapshot( + `"[ssl]: cannot use [key] when [keystore.path] is specified"` + ); + }); + + it('throws if certificate and keystore.path are both specified', () => { + const value = { ssl: { certificate: 'foo', keystore: { path: 'bar' } } }; + expect(() => config.schema.validate(value)).toThrowErrorMatchingInlineSnapshot( + `"[ssl]: cannot use [certificate] when [keystore.path] is specified"` + ); + }); +}); + +describe('deprecations', () => { + it('logs a warning if elasticsearch.username is set to "elastic"', () => { + const { messages } = applyElasticsearchDeprecations({ username: 'elastic' }); + expect(messages).toMatchInlineSnapshot(` + Array [ + "Setting [${CONFIG_PATH}.username] to \\"elastic\\" is deprecated. You should use the \\"kibana\\" user instead.", + ] + `); + }); + + it('does not log a warning if elasticsearch.username is set to something besides "elastic"', () => { + const { messages } = applyElasticsearchDeprecations({ username: 'otheruser' }); + expect(messages).toHaveLength(0); + }); + + it('does not log a warning if elasticsearch.username is unset', () => { + const { messages } = applyElasticsearchDeprecations({}); + expect(messages).toHaveLength(0); + }); + + it('logs a warning if ssl.key is set and ssl.certificate is not', () => { + const { messages } = applyElasticsearchDeprecations({ ssl: { key: '' } }); + expect(messages).toMatchInlineSnapshot(` + Array [ + "Setting [${CONFIG_PATH}.ssl.key] without [${CONFIG_PATH}.ssl.certificate] is deprecated. This has no effect, you should use both settings to enable TLS client authentication to Elasticsearch.", + ] + `); + }); + + it('logs a warning if ssl.certificate is set and ssl.key is not', () => { + const { messages } = applyElasticsearchDeprecations({ ssl: { certificate: '' } }); + expect(messages).toMatchInlineSnapshot(` + Array [ + "Setting [${CONFIG_PATH}.ssl.certificate] without [${CONFIG_PATH}.ssl.key] is deprecated. This has no effect, you should use both settings to enable TLS client authentication to Elasticsearch.", + ] + `); + }); + + it('does not log a warning if both ssl.key and ssl.certificate are set', () => { + const { messages } = applyElasticsearchDeprecations({ ssl: { key: '', certificate: '' } }); + expect(messages).toEqual([]); + }); }); test('#username throws if equal to "elastic", only while running from source', () => { diff --git a/src/core/server/elasticsearch/elasticsearch_config.ts b/src/core/server/elasticsearch/elasticsearch_config.ts index 8d92b12ae4a77..5f06c51a53d53 100644 --- a/src/core/server/elasticsearch/elasticsearch_config.ts +++ b/src/core/server/elasticsearch/elasticsearch_config.ts @@ -19,55 +19,57 @@ import { schema, TypeOf } from '@kbn/config-schema'; import { Duration } from 'moment'; -import { Logger } from '../logging'; +import { readFileSync } from 'fs'; +import { ConfigDeprecationProvider } from 'src/core/server'; +import { readPkcs12Keystore, readPkcs12Truststore } from '../../utils'; +import { ServiceConfigDescriptor } from '../internal_types'; const hostURISchema = schema.uri({ scheme: ['http', 'https'] }); export const DEFAULT_API_VERSION = 'master'; -export type ElasticsearchConfigType = TypeOf; +export type ElasticsearchConfigType = TypeOf; type SslConfigSchema = ElasticsearchConfigType['ssl']; -export const config = { - path: 'elasticsearch', - schema: schema.object({ - sniffOnStart: schema.boolean({ defaultValue: false }), - sniffInterval: schema.oneOf([schema.duration(), schema.literal(false)], { - defaultValue: false, - }), - sniffOnConnectionFault: schema.boolean({ defaultValue: false }), - hosts: schema.oneOf([hostURISchema, schema.arrayOf(hostURISchema, { minSize: 1 })], { - defaultValue: 'http://localhost:9200', - }), - preserveHost: schema.boolean({ defaultValue: true }), - username: schema.maybe( - schema.conditional( - schema.contextRef('dist'), - false, - schema.string({ - validate: rawConfig => { - if (rawConfig === 'elastic') { - return ( - 'value of "elastic" is forbidden. This is a superuser account that can obfuscate ' + - 'privilege-related issues. You should use the "kibana" user instead.' - ); - } - }, - }), - schema.string() - ) - ), - password: schema.maybe(schema.string()), - requestHeadersWhitelist: schema.oneOf([schema.string(), schema.arrayOf(schema.string())], { - defaultValue: ['authorization'], - }), - customHeaders: schema.recordOf(schema.string(), schema.string(), { defaultValue: {} }), - shardTimeout: schema.duration({ defaultValue: '30s' }), - requestTimeout: schema.duration({ defaultValue: '30s' }), - pingTimeout: schema.duration({ defaultValue: schema.siblingRef('requestTimeout') }), - startupTimeout: schema.duration({ defaultValue: '5s' }), - logQueries: schema.boolean({ defaultValue: false }), - ssl: schema.object({ +const configSchema = schema.object({ + sniffOnStart: schema.boolean({ defaultValue: false }), + sniffInterval: schema.oneOf([schema.duration(), schema.literal(false)], { + defaultValue: false, + }), + sniffOnConnectionFault: schema.boolean({ defaultValue: false }), + hosts: schema.oneOf([hostURISchema, schema.arrayOf(hostURISchema, { minSize: 1 })], { + defaultValue: 'http://localhost:9200', + }), + preserveHost: schema.boolean({ defaultValue: true }), + username: schema.maybe( + schema.conditional( + schema.contextRef('dist'), + false, + schema.string({ + validate: rawConfig => { + if (rawConfig === 'elastic') { + return ( + 'value of "elastic" is forbidden. This is a superuser account that can obfuscate ' + + 'privilege-related issues. You should use the "kibana" user instead.' + ); + } + }, + }), + schema.string() + ) + ), + password: schema.maybe(schema.string()), + requestHeadersWhitelist: schema.oneOf([schema.string(), schema.arrayOf(schema.string())], { + defaultValue: ['authorization'], + }), + customHeaders: schema.recordOf(schema.string(), schema.string(), { defaultValue: {} }), + shardTimeout: schema.duration({ defaultValue: '30s' }), + requestTimeout: schema.duration({ defaultValue: '30s' }), + pingTimeout: schema.duration({ defaultValue: schema.siblingRef('requestTimeout') }), + startupTimeout: schema.duration({ defaultValue: '5s' }), + logQueries: schema.boolean({ defaultValue: false }), + ssl: schema.object( + { verificationMode: schema.oneOf( [schema.literal('none'), schema.literal('certificate'), schema.literal('full')], { defaultValue: 'full' } @@ -78,12 +80,60 @@ export const config = { certificate: schema.maybe(schema.string()), key: schema.maybe(schema.string()), keyPassphrase: schema.maybe(schema.string()), + keystore: schema.object({ + path: schema.maybe(schema.string()), + password: schema.maybe(schema.string()), + }), + truststore: schema.object({ + path: schema.maybe(schema.string()), + password: schema.maybe(schema.string()), + }), alwaysPresentCertificate: schema.boolean({ defaultValue: false }), - }), - apiVersion: schema.string({ defaultValue: DEFAULT_API_VERSION }), - healthCheck: schema.object({ delay: schema.duration({ defaultValue: 2500 }) }), - ignoreVersionMismatch: schema.boolean({ defaultValue: false }), - }), + }, + { + validate: rawConfig => { + if (rawConfig.key && rawConfig.keystore.path) { + return 'cannot use [key] when [keystore.path] is specified'; + } + if (rawConfig.certificate && rawConfig.keystore.path) { + return 'cannot use [certificate] when [keystore.path] is specified'; + } + }, + } + ), + apiVersion: schema.string({ defaultValue: DEFAULT_API_VERSION }), + healthCheck: schema.object({ delay: schema.duration({ defaultValue: 2500 }) }), + ignoreVersionMismatch: schema.boolean({ defaultValue: false }), +}); + +const deprecations: ConfigDeprecationProvider = () => [ + (settings, fromPath, log) => { + const es = settings[fromPath]; + if (!es) { + return settings; + } + if (es.username === 'elastic') { + log( + `Setting [${fromPath}.username] to "elastic" is deprecated. You should use the "kibana" user instead.` + ); + } + if (es.ssl?.key !== undefined && es.ssl?.certificate === undefined) { + log( + `Setting [${fromPath}.ssl.key] without [${fromPath}.ssl.certificate] is deprecated. This has no effect, you should use both settings to enable TLS client authentication to Elasticsearch.` + ); + } else if (es.ssl?.certificate !== undefined && es.ssl?.key === undefined) { + log( + `Setting [${fromPath}.ssl.certificate] without [${fromPath}.ssl.key] is deprecated. This has no effect, you should use both settings to enable TLS client authentication to Elasticsearch.` + ); + } + return settings; + }, +]; + +export const config: ServiceConfigDescriptor = { + path: 'elasticsearch', + schema: configSchema, + deprecations, }; export class ElasticsearchConfig { @@ -173,7 +223,7 @@ export class ElasticsearchConfig { */ public readonly ssl: Pick< SslConfigSchema, - Exclude + Exclude > & { certificateAuthorities?: string[] }; /** @@ -183,7 +233,7 @@ export class ElasticsearchConfig { */ public readonly customHeaders: ElasticsearchConfigType['customHeaders']; - constructor(rawConfig: ElasticsearchConfigType, log?: Logger) { + constructor(rawConfig: ElasticsearchConfigType) { this.ignoreVersionMismatch = rawConfig.ignoreVersionMismatch; this.apiVersion = rawConfig.apiVersion; this.logQueries = rawConfig.logQueries; @@ -202,24 +252,83 @@ export class ElasticsearchConfig { this.password = rawConfig.password; this.customHeaders = rawConfig.customHeaders; - const certificateAuthorities = Array.isArray(rawConfig.ssl.certificateAuthorities) - ? rawConfig.ssl.certificateAuthorities - : typeof rawConfig.ssl.certificateAuthorities === 'string' - ? [rawConfig.ssl.certificateAuthorities] - : undefined; + const { alwaysPresentCertificate, verificationMode } = rawConfig.ssl; + const { key, keyPassphrase, certificate, certificateAuthorities } = readKeyAndCerts(rawConfig); this.ssl = { - ...rawConfig.ssl, + alwaysPresentCertificate, + key, + keyPassphrase, + certificate, certificateAuthorities, + verificationMode, }; + } +} - if (this.username === 'elastic' && log !== undefined) { - // logger is optional / not used during tests - // TODO: logger can be removed when issue #40255 is resolved to support deprecations in NP config service - log.warn( - `Setting the elasticsearch username to "elastic" is deprecated. You should use the "kibana" user instead.`, - { tags: ['deprecation'] } - ); +const readKeyAndCerts = (rawConfig: ElasticsearchConfigType) => { + let key: string | undefined; + let keyPassphrase: string | undefined; + let certificate: string | undefined; + let certificateAuthorities: string[] | undefined; + + const addCAs = (ca: string[] | undefined) => { + if (ca && ca.length) { + certificateAuthorities = [...(certificateAuthorities || []), ...ca]; + } + }; + + if (rawConfig.ssl.keystore?.path) { + const keystore = readPkcs12Keystore( + rawConfig.ssl.keystore.path, + rawConfig.ssl.keystore.password + ); + if (!keystore.key) { + throw new Error(`Did not find key in Elasticsearch keystore.`); + } else if (!keystore.cert) { + throw new Error(`Did not find certificate in Elasticsearch keystore.`); + } + key = keystore.key; + certificate = keystore.cert; + addCAs(keystore.ca); + } else { + if (rawConfig.ssl.key) { + key = readFile(rawConfig.ssl.key); + keyPassphrase = rawConfig.ssl.keyPassphrase; + } + if (rawConfig.ssl.certificate) { + certificate = readFile(rawConfig.ssl.certificate); } } -} + + if (rawConfig.ssl.truststore?.path) { + const ca = readPkcs12Truststore( + rawConfig.ssl.truststore.path, + rawConfig.ssl.truststore.password + ); + addCAs(ca); + } + + const ca = rawConfig.ssl.certificateAuthorities; + if (ca) { + const parsed: string[] = []; + const paths = Array.isArray(ca) ? ca : [ca]; + if (paths.length > 0) { + for (const path of paths) { + parsed.push(readFile(path)); + } + addCAs(parsed); + } + } + + return { + key, + keyPassphrase, + certificate, + certificateAuthorities, + }; +}; + +const readFile = (file: string) => { + return readFileSync(file, 'utf8'); +}; diff --git a/src/core/server/elasticsearch/elasticsearch_service.mock.ts b/src/core/server/elasticsearch/elasticsearch_service.mock.ts index d935d1a66eccf..a4e51ca55b3e7 100644 --- a/src/core/server/elasticsearch/elasticsearch_service.mock.ts +++ b/src/core/server/elasticsearch/elasticsearch_service.mock.ts @@ -18,33 +18,65 @@ */ 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), }; + setupContract.adminClient.asScoped.mockReturnValue(createScopedClusterClientMock()); + setupContract.dataClient.asScoped.mockReturnValue(createScopedClusterClientMock()); return setupContract; }; @@ -55,14 +87,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..5a7d223fec7ad 100644 --- a/src/core/server/elasticsearch/elasticsearch_service.test.ts +++ b/src/core/server/elasticsearch/elasticsearch_service.test.ts @@ -21,7 +21,7 @@ import { first } from 'rxjs/operators'; import { MockClusterClient } from './elasticsearch_service.test.mocks'; -import { BehaviorSubject, combineLatest } from 'rxjs'; +import { BehaviorSubject } from 'rxjs'; import { Env } from '../config'; import { getEnvOptions } from '../config/__mocks__/env'; import { CoreContext } from '../core_context'; @@ -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,42 +70,25 @@ describe('#setup', () => { ); }); - it('returns data and admin client observables as a part of the contract', async () => { - const mockAdminClusterClientInstance = { close: jest.fn() }; - const mockDataClusterClientInstance = { close: jest.fn() }; + 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 [esConfig, adminClient, dataClient] = await combineLatest( - setupContract.legacy.config$, - setupContract.adminClient$, - setupContract.dataClient$ - ) - .pipe(first()) - .toPromise(); - - expect(adminClient).toBe(mockAdminClusterClientInstance); - expect(dataClient).toBe(mockDataClusterClientInstance); - - expect(MockClusterClient).toHaveBeenCalledTimes(2); - expect(MockClusterClient).toHaveBeenNthCalledWith( - 1, - esConfig, - expect.objectContaining({ context: ['elasticsearch', 'admin'] }), - undefined - ); - expect(MockClusterClient).toHaveBeenNthCalledWith( - 2, - esConfig, - expect.objectContaining({ context: ['elasticsearch', 'data'] }), - expect.any(Function) - ); + const adminClient = setupContract.adminClient; + const dataClient = setupContract.dataClient; + + expect(mockAdminClusterClientInstance.callAsInternalUser).toHaveBeenCalledTimes(0); + await adminClient.callAsInternalUser('any'); + expect(mockAdminClusterClientInstance.callAsInternalUser).toHaveBeenCalledTimes(1); - expect(mockAdminClusterClientInstance.close).not.toHaveBeenCalled(); - expect(mockDataClusterClientInstance.close).not.toHaveBeenCalled(); + expect(mockDataClusterClientInstance.callAsInternalUser).toHaveBeenCalledTimes(0); + await dataClient.callAsInternalUser('any'); + expect(mockDataClusterClientInstance.callAsInternalUser).toHaveBeenCalledTimes(1); }); describe('#createClient', () => { @@ -174,7 +158,11 @@ Object { undefined, ], "ssl": Object { + "alwaysPresentCertificate": undefined, + "certificate": undefined, "certificateAuthorities": undefined, + "key": undefined, + "keyPassphrase": undefined, "verificationMode": "none", }, } diff --git a/src/core/server/elasticsearch/elasticsearch_service.ts b/src/core/server/elasticsearch/elasticsearch_service.ts index be0a817c54146..aba246ce66fb5 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 { @@ -51,7 +52,7 @@ export class ElasticsearchService implements CoreService('elasticsearch') - .pipe(map(rawConfig => new ElasticsearchConfig(rawConfig, coreContext.logger.get('config')))); + .pipe(map(rawConfig => new ElasticsearchConfig(rawConfig))); } public async setup(deps: SetupDeps): Promise { @@ -94,11 +95,65 @@ 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, 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..899b273c5c60a 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 */ diff --git a/src/core/server/http/__snapshots__/http_config.test.ts.snap b/src/core/server/http/__snapshots__/http_config.test.ts.snap index 6c690f9da70c3..28933a035c870 100644 --- a/src/core/server/http/__snapshots__/http_config.test.ts.snap +++ b/src/core/server/http/__snapshots__/http_config.test.ts.snap @@ -31,11 +31,13 @@ Object { "enabled": true, }, "cors": false, + "customResponseHeaders": Object {}, "host": "localhost", "keepaliveTimeout": 120000, "maxPayload": ByteSizeValue { "valueInBytes": 1048576, }, + "name": "kibana-hostname", "port": 5601, "rewriteBasePath": false, "socketTimeout": 120000, @@ -65,10 +67,16 @@ Object { ], "clientAuthentication": "none", "enabled": false, + "keystore": Object {}, "supportedProtocols": Array [ "TLSv1.1", "TLSv1.2", ], + "truststore": Object {}, + }, + "xsrf": Object { + "disableProtection": false, + "whitelist": Array [], }, } `; @@ -81,24 +89,6 @@ exports[`throws if basepath is not specified, but rewriteBasePath is set 1`] = ` exports[`throws if invalid hostname 1`] = `"[host]: value is [asdf$%^] but it must be a valid hostname (see RFC 1123)."`; -exports[`with TLS should accept known protocols\` 1`] = ` -"[ssl.supportedProtocols.0]: types that failed validation: -- [ssl.supportedProtocols.0.0]: expected value to equal [TLSv1] but got [SOMEv100500] -- [ssl.supportedProtocols.0.1]: expected value to equal [TLSv1.1] but got [SOMEv100500] -- [ssl.supportedProtocols.0.2]: expected value to equal [TLSv1.2] but got [SOMEv100500]" -`; - -exports[`with TLS should accept known protocols\` 2`] = ` -"[ssl.supportedProtocols.3]: types that failed validation: -- [ssl.supportedProtocols.3.0]: expected value to equal [TLSv1] but got [SOMEv100500] -- [ssl.supportedProtocols.3.1]: expected value to equal [TLSv1.1] but got [SOMEv100500] -- [ssl.supportedProtocols.3.2]: expected value to equal [TLSv1.2] but got [SOMEv100500]" -`; - -exports[`with TLS throws if TLS is enabled but \`certificate\` is not specified 1`] = `"[ssl]: must specify [certificate] and [key] when ssl is enabled"`; - -exports[`with TLS throws if TLS is enabled but \`key\` is not specified 1`] = `"[ssl]: must specify [certificate] and [key] when ssl is enabled"`; - exports[`with TLS throws if TLS is enabled but \`redirectHttpFromPort\` is equal to \`port\` 1`] = `"Kibana does not accept http traffic to [port] when ssl is enabled (only https is allowed), so [ssl.redirectHttpFromPort] cannot be configured to the same value. Both are [1234]."`; exports[`with compression accepts valid referrer whitelist 1`] = ` diff --git a/src/core/server/http/cookie_session_storage.test.ts b/src/core/server/http/cookie_session_storage.test.ts index 0e4f3972fe9dc..4ce422e1f65c4 100644 --- a/src/core/server/http/cookie_session_storage.test.ts +++ b/src/core/server/http/cookie_session_storage.test.ts @@ -58,6 +58,10 @@ configService.atPath.mockReturnValue( verificationMode: 'none', }, compression: { enabled: true }, + xsrf: { + disableProtection: true, + whitelist: [], + }, } as any) ); diff --git a/src/core/server/http/http_config.test.ts b/src/core/server/http/http_config.test.ts index 9b6fab8f3daec..7ac707b0f3d83 100644 --- a/src/core/server/http/http_config.test.ts +++ b/src/core/server/http/http_config.test.ts @@ -18,13 +18,16 @@ */ import uuid from 'uuid'; -import { config, HttpConfig } from '.'; -import { Env } from '../config'; -import { getEnvOptions } from '../config/__mocks__/env'; +import { config } from '.'; const validHostnames = ['www.example.com', '8.8.8.8', '::1', 'localhost']; const invalidHostname = 'asdf$%^'; +jest.mock('os', () => ({ + ...jest.requireActual('os'), + hostname: () => 'kibana-hostname', +})); + test('has defaults for config', () => { const httpSchema = config.schema; const obj = {}; @@ -86,29 +89,25 @@ test('accepts only valid uuids for server.uuid', () => { ); }); -describe('with TLS', () => { - test('throws if TLS is enabled but `key` is not specified', () => { - const httpSchema = config.schema; - const obj = { - ssl: { - certificate: '/path/to/certificate', - enabled: true, - }, - }; - expect(() => httpSchema.validate(obj)).toThrowErrorMatchingSnapshot(); - }); +test('uses os.hostname() as default for server.name', () => { + const httpSchema = config.schema; + const validated = httpSchema.validate({}); + expect(validated.name).toEqual('kibana-hostname'); +}); - test('throws if TLS is enabled but `certificate` is not specified', () => { - const httpSchema = config.schema; - const obj = { - ssl: { - enabled: true, - key: '/path/to/key', - }, - }; - expect(() => httpSchema.validate(obj)).toThrowErrorMatchingSnapshot(); - }); +test('throws if xsrf.whitelist element does not start with a slash', () => { + const httpSchema = config.schema; + const obj = { + xsrf: { + whitelist: ['/valid-path', 'invalid-path'], + }, + }; + expect(() => httpSchema.validate(obj)).toThrowErrorMatchingInlineSnapshot( + `"[xsrf.whitelist.1]: must start with a slash"` + ); +}); +describe('with TLS', () => { test('throws if TLS is enabled but `redirectHttpFromPort` is equal to `port`', () => { const httpSchema = config.schema; const obj = { @@ -122,192 +121,16 @@ describe('with TLS', () => { }; expect(() => httpSchema.validate(obj)).toThrowErrorMatchingSnapshot(); }); +}); - test('throws if TLS is not enabled but `clientAuthentication` is `optional`', () => { - const httpSchema = config.schema; - const obj = { - port: 1234, - ssl: { - enabled: false, - clientAuthentication: 'optional', - }, - }; - expect(() => httpSchema.validate(obj)).toThrowErrorMatchingInlineSnapshot( - `"[ssl]: must enable ssl to use [clientAuthentication]"` - ); - }); - - test('throws if TLS is not enabled but `clientAuthentication` is `required`', () => { - const httpSchema = config.schema; - const obj = { - port: 1234, - ssl: { - enabled: false, - clientAuthentication: 'required', - }, - }; - expect(() => httpSchema.validate(obj)).toThrowErrorMatchingInlineSnapshot( - `"[ssl]: must enable ssl to use [clientAuthentication]"` - ); - }); - - test('can specify `none` for [clientAuthentication] if ssl is not enabled', () => { - const obj = { - ssl: { - enabled: false, - clientAuthentication: 'none', - }, - }; - - const configValue = config.schema.validate(obj); - expect(configValue.ssl.clientAuthentication).toBe('none'); - }); - - test('can specify single `certificateAuthority` as a string', () => { - const obj = { - ssl: { - certificate: '/path/to/certificate', - certificateAuthorities: '/authority/', - enabled: true, - key: '/path/to/key', - }, - }; - - const configValue = config.schema.validate(obj); - expect(configValue.ssl.certificateAuthorities).toBe('/authority/'); - }); - - test('can specify socket timeouts', () => { - const obj = { - keepaliveTimeout: 1e5, - socketTimeout: 5e5, - }; - const { keepaliveTimeout, socketTimeout } = config.schema.validate(obj); - expect(keepaliveTimeout).toBe(1e5); - expect(socketTimeout).toBe(5e5); - }); - - test('can specify several `certificateAuthorities`', () => { - const obj = { - ssl: { - certificate: '/path/to/certificate', - certificateAuthorities: ['/authority/1', '/authority/2'], - enabled: true, - key: '/path/to/key', - }, - }; - - const configValue = config.schema.validate(obj); - expect(configValue.ssl.certificateAuthorities).toEqual(['/authority/1', '/authority/2']); - }); - - test('accepts known protocols`', () => { - const httpSchema = config.schema; - const singleKnownProtocol = { - ssl: { - certificate: '/path/to/certificate', - enabled: true, - key: '/path/to/key', - supportedProtocols: ['TLSv1'], - }, - }; - - const allKnownProtocols = { - ssl: { - certificate: '/path/to/certificate', - enabled: true, - key: '/path/to/key', - supportedProtocols: ['TLSv1', 'TLSv1.1', 'TLSv1.2'], - }, - }; - - const singleKnownProtocolConfig = httpSchema.validate(singleKnownProtocol); - expect(singleKnownProtocolConfig.ssl.supportedProtocols).toEqual(['TLSv1']); - - const allKnownProtocolsConfig = httpSchema.validate(allKnownProtocols); - expect(allKnownProtocolsConfig.ssl.supportedProtocols).toEqual(['TLSv1', 'TLSv1.1', 'TLSv1.2']); - }); - - test('should accept known protocols`', () => { - const httpSchema = config.schema; - - const singleUnknownProtocol = { - ssl: { - certificate: '/path/to/certificate', - enabled: true, - key: '/path/to/key', - supportedProtocols: ['SOMEv100500'], - }, - }; - - const allKnownWithOneUnknownProtocols = { - ssl: { - certificate: '/path/to/certificate', - enabled: true, - key: '/path/to/key', - supportedProtocols: ['TLSv1', 'TLSv1.1', 'TLSv1.2', 'SOMEv100500'], - }, - }; - - expect(() => httpSchema.validate(singleUnknownProtocol)).toThrowErrorMatchingSnapshot(); - expect(() => - httpSchema.validate(allKnownWithOneUnknownProtocols) - ).toThrowErrorMatchingSnapshot(); - }); - - test('HttpConfig instance should properly interpret `none` client authentication', () => { - const httpConfig = new HttpConfig( - config.schema.validate({ - ssl: { - enabled: true, - key: 'some-key-path', - certificate: 'some-certificate-path', - clientAuthentication: 'none', - }, - }), - {} as any, - Env.createDefault(getEnvOptions()) - ); - - expect(httpConfig.ssl.requestCert).toBe(false); - expect(httpConfig.ssl.rejectUnauthorized).toBe(false); - }); - - test('HttpConfig instance should properly interpret `optional` client authentication', () => { - const httpConfig = new HttpConfig( - config.schema.validate({ - ssl: { - enabled: true, - key: 'some-key-path', - certificate: 'some-certificate-path', - clientAuthentication: 'optional', - }, - }), - {} as any, - Env.createDefault(getEnvOptions()) - ); - - expect(httpConfig.ssl.requestCert).toBe(true); - expect(httpConfig.ssl.rejectUnauthorized).toBe(false); - }); - - test('HttpConfig instance should properly interpret `required` client authentication', () => { - const httpConfig = new HttpConfig( - config.schema.validate({ - ssl: { - enabled: true, - key: 'some-key-path', - certificate: 'some-certificate-path', - clientAuthentication: 'required', - }, - }), - {} as any, - Env.createDefault(getEnvOptions()) - ); - - expect(httpConfig.ssl.requestCert).toBe(true); - expect(httpConfig.ssl.rejectUnauthorized).toBe(true); - }); +test('can specify socket timeouts', () => { + const obj = { + keepaliveTimeout: 1e5, + socketTimeout: 5e5, + }; + const { keepaliveTimeout, socketTimeout } = config.schema.validate(obj); + expect(keepaliveTimeout).toBe(1e5); + expect(socketTimeout).toBe(5e5); }); describe('with compression', () => { diff --git a/src/core/server/http/http_config.ts b/src/core/server/http/http_config.ts index ef6a9c0a5f1a5..73f44f3c5ab5c 100644 --- a/src/core/server/http/http_config.ts +++ b/src/core/server/http/http_config.ts @@ -18,7 +18,8 @@ */ import { ByteSizeValue, schema, TypeOf } from '@kbn/config-schema'; -import { Env } from '../config'; +import { hostname } from 'os'; + import { CspConfigType, CspConfig, ICspConfig } from '../csp'; import { SslConfig, sslSchema } from './ssl_config'; @@ -34,21 +35,13 @@ export const config = { path: 'server', schema: schema.object( { + name: schema.string({ defaultValue: () => hostname() }), autoListen: schema.boolean({ defaultValue: true }), basePath: schema.maybe( schema.string({ validate: match(validBasePathRegex, "must start with a slash, don't end with one"), }) ), - defaultRoute: schema.maybe( - schema.string({ - validate(value) { - if (!value.startsWith('/')) { - return 'must start with a slash'; - } - }, - }) - ), cors: schema.conditional( schema.contextRef('dev'), true, @@ -64,6 +57,9 @@ export const config = { ), schema.boolean({ defaultValue: false }) ), + customResponseHeaders: schema.recordOf(schema.string(), schema.string(), { + defaultValue: {}, + }), host: schema.string({ defaultValue: 'localhost', hostname: true, @@ -98,6 +94,13 @@ export const config = { validate: match(uuidRegexp, 'must be a valid uuid'), }) ), + xsrf: schema.object({ + disableProtection: schema.boolean({ defaultValue: false }), + whitelist: schema.arrayOf( + schema.string({ validate: match(/^\//, 'must start with a slash') }), + { defaultValue: [] } + ), + }), }, { validate: rawConfig => { @@ -126,38 +129,40 @@ export const config = { export type HttpConfigType = TypeOf; export class HttpConfig { + public name: string; public autoListen: boolean; public host: string; public keepaliveTimeout: number; public socketTimeout: number; public port: number; public cors: boolean | { origin: string[] }; + public customResponseHeaders: Record; public maxPayload: ByteSizeValue; public basePath?: string; public rewriteBasePath: boolean; - public publicDir: string; - public defaultRoute?: string; public ssl: SslConfig; public compression: { enabled: boolean; referrerWhitelist?: string[] }; public csp: ICspConfig; + public xsrf: { disableProtection: boolean; whitelist: string[] }; /** * @internal */ - constructor(rawHttpConfig: HttpConfigType, rawCspConfig: CspConfigType, env: Env) { + constructor(rawHttpConfig: HttpConfigType, rawCspConfig: CspConfigType) { this.autoListen = rawHttpConfig.autoListen; this.host = rawHttpConfig.host; this.port = rawHttpConfig.port; this.cors = rawHttpConfig.cors; + this.customResponseHeaders = rawHttpConfig.customResponseHeaders; this.maxPayload = rawHttpConfig.maxPayload; + this.name = rawHttpConfig.name; this.basePath = rawHttpConfig.basePath; this.keepaliveTimeout = rawHttpConfig.keepaliveTimeout; this.socketTimeout = rawHttpConfig.socketTimeout; this.rewriteBasePath = rawHttpConfig.rewriteBasePath; - this.publicDir = env.staticFilesDir; this.ssl = new SslConfig(rawHttpConfig.ssl || {}); - this.defaultRoute = rawHttpConfig.defaultRoute; this.compression = rawHttpConfig.compression; this.csp = new CspConfig(rawCspConfig); + this.xsrf = rawHttpConfig.xsrf; } } diff --git a/src/core/server/http/http_server.mocks.ts b/src/core/server/http/http_server.mocks.ts index ba742292e9e83..230a229b36888 100644 --- a/src/core/server/http/http_server.mocks.ts +++ b/src/core/server/http/http_server.mocks.ts @@ -30,6 +30,9 @@ import { RouteMethod, KibanaResponseFactory, } from './router'; +import { OnPreResponseToolkit } from './lifecycle/on_pre_response'; +import { OnPostAuthToolkit } from './lifecycle/on_post_auth'; +import { OnPreAuthToolkit } from './lifecycle/on_pre_auth'; interface RequestFixtureOptions { headers?: Record; @@ -137,9 +140,19 @@ const createLifecycleResponseFactoryMock = (): jest.Mocked; + +const createToolkitMock = (): ToolkitMock => { + return { + next: jest.fn(), + rewriteUrl: jest.fn(), + }; +}; + export const httpServerMock = { createKibanaRequest: createKibanaRequestMock, createRawRequest: createRawRequestMock, createResponseFactory: createResponseFactoryMock, createLifecycleResponseFactory: createLifecycleResponseFactoryMock, + createToolkit: createToolkitMock, }; diff --git a/src/core/server/http/http_server.test.ts b/src/core/server/http/http_server.test.ts index df357aeaf2731..df7b4b5af4267 100644 --- a/src/core/server/http/http_server.test.ts +++ b/src/core/server/http/http_server.test.ts @@ -18,11 +18,7 @@ */ import { Server } from 'http'; - -jest.mock('fs', () => ({ - readFileSync: jest.fn(), -})); - +import { readFileSync } from 'fs'; import supertest from 'supertest'; import { ByteSizeValue, schema } from '@kbn/config-schema'; @@ -39,6 +35,7 @@ import { loggingServiceMock } from '../logging/logging_service.mock'; import { HttpServer } from './http_server'; import { Readable } from 'stream'; import { RequestHandlerContext } from 'kibana/server'; +import { KBN_CERT_PATH, KBN_KEY_PATH } from '@kbn/dev-utils'; const cookieOptions = { name: 'sid', @@ -55,6 +52,14 @@ const loggingService = loggingServiceMock.create(); const logger = loggingService.get(); const enhanceWithContext = (fn: (...args: any[]) => any) => fn.bind(null, {}); +let certificate: string; +let key: string; + +beforeAll(() => { + certificate = readFileSync(KBN_CERT_PATH, 'utf8'); + key = readFileSync(KBN_KEY_PATH, 'utf8'); +}); + beforeEach(() => { config = { host: '127.0.0.1', @@ -68,10 +73,10 @@ beforeEach(() => { ...config, ssl: { enabled: true, - certificate: '/certificate', + certificate, cipherSuites: ['cipherSuite'], getSecureOptions: () => 0, - key: '/key', + key, redirectHttpFromPort: config.port + 1, }, } as HttpConfig; diff --git a/src/core/server/http/http_server.ts b/src/core/server/http/http_server.ts index 994a6cced8914..6b978b71c6f2b 100644 --- a/src/core/server/http/http_server.ts +++ b/src/core/server/http/http_server.ts @@ -60,6 +60,12 @@ export interface HttpServerSetup { }; } +/** @internal */ +export type LifecycleRegistrar = Pick< + HttpServerSetup, + 'registerAuth' | 'registerOnPreAuth' | 'registerOnPostAuth' | 'registerOnPreResponse' +>; + export class HttpServer { private server?: Server; private config?: HttpConfig; diff --git a/src/core/server/http/http_service.mock.ts b/src/core/server/http/http_service.mock.ts index 700ae04f00d47..6db1ca80ab437 100644 --- a/src/core/server/http/http_service.mock.ts +++ b/src/core/server/http/http_service.mock.ts @@ -68,7 +68,6 @@ const createSetupContractMock = () => { getAuthHeaders: jest.fn(), }, isTlsEnabled: false, - config: {}, }; setupContract.createCookieSessionStorageFactory.mockResolvedValue( sessionStorageMock.createFactory() diff --git a/src/core/server/http/http_service.test.mocks.ts b/src/core/server/http/http_service.test.mocks.ts index c147944f2b7d8..e18008d3b405d 100644 --- a/src/core/server/http/http_service.test.mocks.ts +++ b/src/core/server/http/http_service.test.mocks.ts @@ -27,3 +27,7 @@ jest.mock('./http_server', () => { HttpServer: mockHttpServer, }; }); + +jest.mock('./lifecycle_handlers', () => ({ + registerCoreHandlers: jest.fn(), +})); diff --git a/src/core/server/http/http_service.ts b/src/core/server/http/http_service.ts index faeae0b559b6b..ae9d53f9fd3db 100644 --- a/src/core/server/http/http_service.ts +++ b/src/core/server/http/http_service.ts @@ -21,11 +21,10 @@ import { Observable, Subscription, combineLatest } from 'rxjs'; import { first, map } from 'rxjs/operators'; import { Server } from 'hapi'; -import { LoggerFactory } from '../logging'; import { CoreService } from '../../types'; - -import { Logger } from '../logging'; +import { Logger, LoggerFactory } from '../logging'; import { ContextSetup } from '../context'; +import { Env } from '../config'; import { CoreContext } from '../core_context'; import { PluginOpaqueId } from '../plugins'; import { CspConfigType, config as cspConfig } from '../csp'; @@ -43,6 +42,7 @@ import { } from './types'; import { RequestHandlerContext } from '../../server'; +import { registerCoreHandlers } from './lifecycle_handlers'; interface SetupDeps { context: ContextSetup; @@ -57,6 +57,7 @@ export class HttpService implements CoreService(httpConfig.path), - configService.atPath(cspConfig.path) - ).pipe(map(([http, csp]) => new HttpConfig(http, csp, env))); + configService.atPath(cspConfig.path), + ]).pipe(map(([http, csp]) => new HttpConfig(http, csp))); this.httpServer = new HttpServer(logger, 'Kibana'); this.httpsRedirectServer = new HttpsRedirectServer(logger.get('http', 'redirect', 'server')); } @@ -92,6 +94,9 @@ export class HttpService implements CoreService ) => this.requestHandlerContext!.registerContext(pluginOpaqueId, contextName, provider), - - config: { - defaultRoute: config.defaultRoute, - }, }; return contract; diff --git a/src/core/server/http/http_tools.test.ts b/src/core/server/http/http_tools.test.ts index b889ebd64971f..c1322a5aa94db 100644 --- a/src/core/server/http/http_tools.test.ts +++ b/src/core/server/http/http_tools.test.ts @@ -31,8 +31,6 @@ import { HttpConfig, config } from './http_config'; import { Router } from './router'; import { loggingServiceMock } from '../logging/logging_service.mock'; import { ByteSizeValue } from '@kbn/config-schema'; -import { Env } from '../config'; -import { getEnvOptions } from '../config/__mocks__/env'; const emptyOutput = { statusCode: 400, @@ -122,8 +120,7 @@ describe('getServerOptions', () => { certificate: 'some-certificate-path', }, }), - {} as any, - Env.createDefault(getEnvOptions()) + {} as any ); expect(getServerOptions(httpConfig).tls).toMatchInlineSnapshot(` @@ -152,8 +149,7 @@ describe('getServerOptions', () => { clientAuthentication: 'required', }, }), - {} as any, - Env.createDefault(getEnvOptions()) + {} as any ); expect(getServerOptions(httpConfig).tls).toMatchInlineSnapshot(` diff --git a/src/core/server/http/http_tools.ts b/src/core/server/http/http_tools.ts index 22468a5b252f4..747fd5a10b168 100644 --- a/src/core/server/http/http_tools.ts +++ b/src/core/server/http/http_tools.ts @@ -17,7 +17,6 @@ * under the License. */ -import { readFileSync } from 'fs'; import { Lifecycle, Request, ResponseToolkit, Server, ServerOptions, Util } from 'hapi'; import Hoek from 'hoek'; import { ServerOptions as TLSOptions } from 'https'; @@ -66,14 +65,12 @@ export function getServerOptions(config: HttpConfig, { configureTLS = true } = { // TODO: Hapi types have a typo in `tls` property type definition: `https.RequestOptions` is used instead of // `https.ServerOptions`, and `honorCipherOrder` isn't presented in `https.RequestOptions`. const tlsOptions: TLSOptions = { - ca: - config.ssl.certificateAuthorities && - config.ssl.certificateAuthorities.map(caFilePath => readFileSync(caFilePath)), - cert: readFileSync(ssl.certificate!), + ca: ssl.certificateAuthorities, + cert: ssl.certificate, ciphers: config.ssl.cipherSuites.join(':'), // We use the server's cipher order rather than the client's to prevent the BEAST attack. honorCipherOrder: true, - key: readFileSync(ssl.key!), + key: ssl.key, passphrase: ssl.keyPassphrase, secureOptions: ssl.getSecureOptions(), requestCert: ssl.requestCert, diff --git a/src/core/server/http/integration_tests/core_service.test.mocks.ts b/src/core/server/http/integration_tests/core_service.test.mocks.ts index 3982df567ed7c..6fa3357168027 100644 --- a/src/core/server/http/integration_tests/core_service.test.mocks.ts +++ b/src/core/server/http/integration_tests/core_service.test.mocks.ts @@ -16,8 +16,11 @@ * specific language governing permissions and limitations * under the License. */ +import { elasticsearchServiceMock } from '../../elasticsearch/elasticsearch_service.mock'; export const clusterClientMock = jest.fn(); jest.doMock('../../elasticsearch/scoped_cluster_client', () => ({ - ScopedClusterClient: clusterClientMock, + ScopedClusterClient: clusterClientMock.mockImplementation(function() { + return elasticsearchServiceMock.createScopedClusterClient(); + }), })); diff --git a/src/core/server/http/integration_tests/core_services.test.ts b/src/core/server/http/integration_tests/core_services.test.ts index f3867faa2ae75..65c4f1432721d 100644 --- a/src/core/server/http/integration_tests/core_services.test.ts +++ b/src/core/server/http/integration_tests/core_services.test.ts @@ -133,7 +133,7 @@ describe('http service', () => { const { http } = await root.setup(); const { registerAuth } = http; - await registerAuth((req, res, toolkit) => { + registerAuth((req, res, toolkit) => { return toolkit.authenticated({ responseHeaders: authResponseHeader }); }); @@ -157,7 +157,7 @@ describe('http service', () => { const { http } = await root.setup(); const { registerAuth } = http; - await registerAuth((req, res, toolkit) => { + registerAuth((req, res, toolkit) => { return toolkit.authenticated({ responseHeaders: authResponseHeader }); }); @@ -222,12 +222,15 @@ describe('http service', () => { const { http } = await root.setup(); const { registerAuth, createRouter } = http; - await registerAuth((req, res, toolkit) => - toolkit.authenticated({ requestHeaders: authHeaders }) - ); + registerAuth((req, res, toolkit) => toolkit.authenticated({ requestHeaders: authHeaders })); const router = createRouter('/new-platform'); - router.get({ path: '/', validate: false }, (context, req, res) => res.ok()); + router.get({ path: '/', validate: false }, async (context, req, res) => { + // it forces client initialization since the core creates them lazily. + await context.core.elasticsearch.adminClient.callAsCurrentUser('ping'); + await context.core.elasticsearch.dataClient.callAsCurrentUser('ping'); + return res.ok(); + }); await root.start(); @@ -247,7 +250,12 @@ describe('http service', () => { const { createRouter } = http; const router = createRouter('/new-platform'); - router.get({ path: '/', validate: false }, (context, req, res) => res.ok()); + router.get({ path: '/', validate: false }, async (context, req, res) => { + // it forces client initialization since the core creates them lazily. + await context.core.elasticsearch.adminClient.callAsCurrentUser('ping'); + await context.core.elasticsearch.dataClient.callAsCurrentUser('ping'); + return res.ok(); + }); await root.start(); diff --git a/src/core/server/http/integration_tests/lifecycle_handlers.test.ts b/src/core/server/http/integration_tests/lifecycle_handlers.test.ts new file mode 100644 index 0000000000000..f4c5f16870c7e --- /dev/null +++ b/src/core/server/http/integration_tests/lifecycle_handlers.test.ts @@ -0,0 +1,241 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { resolve } from 'path'; +import supertest from 'supertest'; +import { BehaviorSubject } from 'rxjs'; +import { ByteSizeValue } from '@kbn/config-schema'; + +import { createHttpServer } from '../test_utils'; +import { HttpService } from '../http_service'; +import { HttpServerSetup } from '../http_server'; +import { IRouter, RouteRegistrar } from '../router'; + +import { configServiceMock } from '../../config/config_service.mock'; +import { contextServiceMock } from '../../context/context_service.mock'; + +const pkgPath = resolve(__dirname, '../../../../../package.json'); +const actualVersion = require(pkgPath).version; +const versionHeader = 'kbn-version'; +const xsrfHeader = 'kbn-xsrf'; +const nameHeader = 'kbn-name'; +const whitelistedTestPath = '/xsrf/test/route/whitelisted'; +const kibanaName = 'my-kibana-name'; +const setupDeps = { + context: contextServiceMock.createSetupContract(), +}; + +describe('core lifecycle handlers', () => { + let server: HttpService; + let innerServer: HttpServerSetup['server']; + let router: IRouter; + + beforeEach(async () => { + const configService = configServiceMock.create(); + configService.atPath.mockReturnValue( + new BehaviorSubject({ + hosts: ['localhost'], + maxPayload: new ByteSizeValue(1024), + autoListen: true, + ssl: { + enabled: false, + }, + compression: { enabled: true }, + name: kibanaName, + customResponseHeaders: { + 'some-header': 'some-value', + }, + xsrf: { disableProtection: false, whitelist: [whitelistedTestPath] }, + } as any) + ); + server = createHttpServer({ configService }); + + const serverSetup = await server.setup(setupDeps); + router = serverSetup.createRouter('/'); + innerServer = serverSetup.server; + }, 30000); + + afterEach(async () => { + await server.stop(); + }); + + describe('versionCheck post-auth handler', () => { + const testRoute = '/version_check/test/route'; + + beforeEach(async () => { + router.get({ path: testRoute, validate: false }, (context, req, res) => { + return res.ok({ body: 'ok' }); + }); + await server.start(); + }); + + it('accepts requests with the correct version passed in the version header', async () => { + await supertest(innerServer.listener) + .get(testRoute) + .set(versionHeader, actualVersion) + .expect(200, 'ok'); + }); + + it('accepts requests that do not include a version header', async () => { + await supertest(innerServer.listener) + .get(testRoute) + .expect(200, 'ok'); + }); + + it('rejects requests with an incorrect version passed in the version header', async () => { + await supertest(innerServer.listener) + .get(testRoute) + .set(versionHeader, 'invalid-version') + .expect(400, /Browser client is out of date/); + }); + }); + + describe('customHeaders pre-response handler', () => { + const testRoute = '/custom_headers/test/route'; + const testErrorRoute = '/custom_headers/test/error_route'; + + beforeEach(async () => { + router.get({ path: testRoute, validate: false }, (context, req, res) => { + return res.ok({ body: 'ok' }); + }); + router.get({ path: testErrorRoute, validate: false }, (context, req, res) => { + return res.badRequest({ body: 'bad request' }); + }); + await server.start(); + }); + + it('adds the kbn-name header', async () => { + const result = await supertest(innerServer.listener) + .get(testRoute) + .expect(200, 'ok'); + const headers = result.header as Record; + expect(headers).toEqual( + expect.objectContaining({ + [nameHeader]: kibanaName, + }) + ); + }); + + it('adds the kbn-name header in case of error', async () => { + const result = await supertest(innerServer.listener) + .get(testErrorRoute) + .expect(400); + const headers = result.header as Record; + expect(headers).toEqual( + expect.objectContaining({ + [nameHeader]: kibanaName, + }) + ); + }); + + it('adds the custom headers', async () => { + const result = await supertest(innerServer.listener) + .get(testRoute) + .expect(200, 'ok'); + const headers = result.header as Record; + expect(headers).toEqual(expect.objectContaining({ 'some-header': 'some-value' })); + }); + + it('adds the custom headers in case of error', async () => { + const result = await supertest(innerServer.listener) + .get(testErrorRoute) + .expect(400); + const headers = result.header as Record; + expect(headers).toEqual(expect.objectContaining({ 'some-header': 'some-value' })); + }); + }); + + describe('xsrf post-auth handler', () => { + const testPath = '/xsrf/test/route'; + const destructiveMethods = ['POST', 'PUT', 'DELETE']; + const nonDestructiveMethods = ['GET', 'HEAD']; + + const getSupertest = (method: string, path: string): supertest.Test => { + return (supertest(innerServer.listener) as any)[method.toLowerCase()](path) as supertest.Test; + }; + + beforeEach(async () => { + router.get({ path: testPath, validate: false }, (context, req, res) => { + return res.ok({ body: 'ok' }); + }); + + destructiveMethods.forEach(method => { + ((router as any)[method.toLowerCase()] as RouteRegistrar)( + { path: testPath, validate: false }, + (context, req, res) => { + return res.ok({ body: 'ok' }); + } + ); + ((router as any)[method.toLowerCase()] as RouteRegistrar)( + { path: whitelistedTestPath, validate: false }, + (context, req, res) => { + return res.ok({ body: 'ok' }); + } + ); + }); + + await server.start(); + }); + + nonDestructiveMethods.forEach(method => { + describe(`When using non-destructive ${method} method`, () => { + it('accepts requests without a token', async () => { + await getSupertest(method.toLowerCase(), testPath).expect( + 200, + method === 'HEAD' ? undefined : 'ok' + ); + }); + + it('accepts requests with the xsrf header', async () => { + await getSupertest(method.toLowerCase(), testPath) + .set(xsrfHeader, 'anything') + .expect(200, method === 'HEAD' ? undefined : 'ok'); + }); + }); + }); + + destructiveMethods.forEach(method => { + describe(`When using destructive ${method} method`, () => { + it('accepts requests with the xsrf header', async () => { + await getSupertest(method.toLowerCase(), testPath) + .set(xsrfHeader, 'anything') + .expect(200, 'ok'); + }); + + it('accepts requests with the version header', async () => { + await getSupertest(method.toLowerCase(), testPath) + .set(versionHeader, actualVersion) + .expect(200, 'ok'); + }); + + it('rejects requests without either an xsrf or version header', async () => { + await getSupertest(method.toLowerCase(), testPath).expect(400, { + statusCode: 400, + error: 'Bad Request', + message: 'Request must contain a kbn-xsrf header.', + }); + }); + + it('accepts whitelisted requests without either an xsrf or version header', async () => { + await getSupertest(method.toLowerCase(), whitelistedTestPath).expect(200, 'ok'); + }); + }); + }); + }); +}); diff --git a/src/core/server/http/integration_tests/router.test.ts b/src/core/server/http/integration_tests/router.test.ts index c3b9b20d84865..a1523781010d4 100644 --- a/src/core/server/http/integration_tests/router.test.ts +++ b/src/core/server/http/integration_tests/router.test.ts @@ -142,6 +142,61 @@ describe('Handler', () => { statusCode: 400, }); }); + + it('accept to receive an array payload', async () => { + const { server: innerServer, createRouter } = await server.setup(setupDeps); + const router = createRouter('/'); + + let body: any = null; + router.post( + { + path: '/', + validate: { + body: schema.arrayOf(schema.object({ foo: schema.string() })), + }, + }, + (context, req, res) => { + body = req.body; + return res.ok({ body: 'ok' }); + } + ); + await server.start(); + + await supertest(innerServer.listener) + .post('/') + .send([{ foo: 'bar' }, { foo: 'dolly' }]) + .expect(200); + + expect(body).toEqual([{ foo: 'bar' }, { foo: 'dolly' }]); + }); + + it('accept to receive a json primitive payload', async () => { + const { server: innerServer, createRouter } = await server.setup(setupDeps); + const router = createRouter('/'); + + let body: any = null; + router.post( + { + path: '/', + validate: { + body: schema.number(), + }, + }, + (context, req, res) => { + body = req.body; + return res.ok({ body: 'ok' }); + } + ); + await server.start(); + + await supertest(innerServer.listener) + .post('/') + .type('json') + .send('12') + .expect(200); + + expect(body).toEqual(12); + }); }); describe('handleLegacyErrors', () => { diff --git a/src/core/server/http/lifecycle_handlers.test.ts b/src/core/server/http/lifecycle_handlers.test.ts new file mode 100644 index 0000000000000..48a6973b741ba --- /dev/null +++ b/src/core/server/http/lifecycle_handlers.test.ts @@ -0,0 +1,269 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { + createCustomHeadersPreResponseHandler, + createVersionCheckPostAuthHandler, + createXsrfPostAuthHandler, +} from './lifecycle_handlers'; +import { httpServerMock } from './http_server.mocks'; +import { HttpConfig } from './http_config'; +import { KibanaRequest, RouteMethod } from './router'; + +const createConfig = (partial: Partial): HttpConfig => partial as HttpConfig; + +const forgeRequest = ({ + headers = {}, + path = '/', + method = 'get', +}: Partial<{ + headers: Record; + path: string; + method: RouteMethod; +}>): KibanaRequest => { + return httpServerMock.createKibanaRequest({ headers, path, method }); +}; + +describe('xsrf post-auth handler', () => { + let toolkit: ReturnType; + let responseFactory: ReturnType; + + beforeEach(() => { + toolkit = httpServerMock.createToolkit(); + responseFactory = httpServerMock.createLifecycleResponseFactory(); + }); + + describe('non destructive methods', () => { + it('accepts requests without version or xsrf header', () => { + const config = createConfig({ xsrf: { whitelist: [], disableProtection: false } }); + const handler = createXsrfPostAuthHandler(config); + const request = forgeRequest({ method: 'get', headers: {} }); + + toolkit.next.mockReturnValue('next' as any); + + const result = handler(request, responseFactory, toolkit); + + expect(responseFactory.badRequest).not.toHaveBeenCalled(); + expect(toolkit.next).toHaveBeenCalledTimes(1); + expect(result).toEqual('next'); + }); + }); + + describe('destructive methods', () => { + it('accepts requests with xsrf header', () => { + const config = createConfig({ xsrf: { whitelist: [], disableProtection: false } }); + const handler = createXsrfPostAuthHandler(config); + const request = forgeRequest({ method: 'post', headers: { 'kbn-xsrf': 'xsrf' } }); + + toolkit.next.mockReturnValue('next' as any); + + const result = handler(request, responseFactory, toolkit); + + expect(responseFactory.badRequest).not.toHaveBeenCalled(); + expect(toolkit.next).toHaveBeenCalledTimes(1); + expect(result).toEqual('next'); + }); + + it('accepts requests with version header', () => { + const config = createConfig({ xsrf: { whitelist: [], disableProtection: false } }); + const handler = createXsrfPostAuthHandler(config); + const request = forgeRequest({ method: 'post', headers: { 'kbn-version': 'some-version' } }); + + toolkit.next.mockReturnValue('next' as any); + + const result = handler(request, responseFactory, toolkit); + + expect(responseFactory.badRequest).not.toHaveBeenCalled(); + expect(toolkit.next).toHaveBeenCalledTimes(1); + expect(result).toEqual('next'); + }); + + it('returns a bad request if called without xsrf or version header', () => { + const config = createConfig({ xsrf: { whitelist: [], disableProtection: false } }); + const handler = createXsrfPostAuthHandler(config); + const request = forgeRequest({ method: 'post' }); + + responseFactory.badRequest.mockReturnValue('badRequest' as any); + + const result = handler(request, responseFactory, toolkit); + + expect(toolkit.next).not.toHaveBeenCalled(); + expect(responseFactory.badRequest).toHaveBeenCalledTimes(1); + expect(responseFactory.badRequest.mock.calls[0][0]).toMatchInlineSnapshot(` + Object { + "body": "Request must contain a kbn-xsrf header.", + } + `); + expect(result).toEqual('badRequest'); + }); + + it('accepts requests if protection is disabled', () => { + const config = createConfig({ xsrf: { whitelist: [], disableProtection: true } }); + const handler = createXsrfPostAuthHandler(config); + const request = forgeRequest({ method: 'post', headers: {} }); + + toolkit.next.mockReturnValue('next' as any); + + const result = handler(request, responseFactory, toolkit); + + expect(responseFactory.badRequest).not.toHaveBeenCalled(); + expect(toolkit.next).toHaveBeenCalledTimes(1); + expect(result).toEqual('next'); + }); + + it('accepts requests if path is whitelisted', () => { + const config = createConfig({ + xsrf: { whitelist: ['/some-path'], disableProtection: false }, + }); + const handler = createXsrfPostAuthHandler(config); + const request = forgeRequest({ method: 'post', headers: {}, path: '/some-path' }); + + toolkit.next.mockReturnValue('next' as any); + + const result = handler(request, responseFactory, toolkit); + + expect(responseFactory.badRequest).not.toHaveBeenCalled(); + expect(toolkit.next).toHaveBeenCalledTimes(1); + expect(result).toEqual('next'); + }); + }); +}); + +describe('versionCheck post-auth handler', () => { + let toolkit: ReturnType; + let responseFactory: ReturnType; + + beforeEach(() => { + toolkit = httpServerMock.createToolkit(); + responseFactory = httpServerMock.createLifecycleResponseFactory(); + }); + + it('forward the request to the next interceptor if header matches', () => { + const handler = createVersionCheckPostAuthHandler('actual-version'); + const request = forgeRequest({ headers: { 'kbn-version': 'actual-version' } }); + + toolkit.next.mockReturnValue('next' as any); + + const result = handler(request, responseFactory, toolkit); + + expect(toolkit.next).toHaveBeenCalledTimes(1); + expect(responseFactory.badRequest).not.toHaveBeenCalled(); + expect(result).toBe('next'); + }); + + it('returns a badRequest error if header does not match', () => { + const handler = createVersionCheckPostAuthHandler('actual-version'); + const request = forgeRequest({ headers: { 'kbn-version': 'another-version' } }); + + responseFactory.badRequest.mockReturnValue('badRequest' as any); + + const result = handler(request, responseFactory, toolkit); + + expect(toolkit.next).not.toHaveBeenCalled(); + expect(responseFactory.badRequest).toHaveBeenCalledTimes(1); + expect(responseFactory.badRequest.mock.calls[0][0]).toMatchInlineSnapshot(` + Object { + "body": Object { + "attributes": Object { + "expected": "actual-version", + "got": "another-version", + }, + "message": "Browser client is out of date, please refresh the page (\\"kbn-version\\" header was \\"another-version\\" but should be \\"actual-version\\")", + }, + } + `); + expect(result).toBe('badRequest'); + }); + + it('forward the request to the next interceptor if header is not present', () => { + const handler = createVersionCheckPostAuthHandler('actual-version'); + const request = forgeRequest({ headers: {} }); + + toolkit.next.mockReturnValue('next' as any); + + const result = handler(request, responseFactory, toolkit); + + expect(toolkit.next).toHaveBeenCalledTimes(1); + expect(responseFactory.badRequest).not.toHaveBeenCalled(); + expect(result).toBe('next'); + }); +}); + +describe('customHeaders pre-response handler', () => { + let toolkit: ReturnType; + + beforeEach(() => { + toolkit = httpServerMock.createToolkit(); + }); + + it('adds the kbn-name header to the response', () => { + const config = createConfig({ name: 'my-server-name' }); + const handler = createCustomHeadersPreResponseHandler(config as HttpConfig); + + handler({} as any, {} as any, toolkit); + + expect(toolkit.next).toHaveBeenCalledTimes(1); + expect(toolkit.next).toHaveBeenCalledWith({ headers: { 'kbn-name': 'my-server-name' } }); + }); + + it('adds the custom headers defined in the configuration', () => { + const config = createConfig({ + name: 'my-server-name', + customResponseHeaders: { + headerA: 'value-A', + headerB: 'value-B', + }, + }); + const handler = createCustomHeadersPreResponseHandler(config as HttpConfig); + + handler({} as any, {} as any, toolkit); + + expect(toolkit.next).toHaveBeenCalledTimes(1); + expect(toolkit.next).toHaveBeenCalledWith({ + headers: { + 'kbn-name': 'my-server-name', + headerA: 'value-A', + headerB: 'value-B', + }, + }); + }); + + it('preserve the kbn-name value from server.name if definied in custom headders ', () => { + const config = createConfig({ + name: 'my-server-name', + customResponseHeaders: { + 'kbn-name': 'custom-name', + headerA: 'value-A', + headerB: 'value-B', + }, + }); + const handler = createCustomHeadersPreResponseHandler(config as HttpConfig); + + handler({} as any, {} as any, toolkit); + + expect(toolkit.next).toHaveBeenCalledTimes(1); + expect(toolkit.next).toHaveBeenCalledWith({ + headers: { + 'kbn-name': 'my-server-name', + headerA: 'value-A', + headerB: 'value-B', + }, + }); + }); +}); diff --git a/src/core/server/http/lifecycle_handlers.ts b/src/core/server/http/lifecycle_handlers.ts new file mode 100644 index 0000000000000..ee877ee031a2b --- /dev/null +++ b/src/core/server/http/lifecycle_handlers.ts @@ -0,0 +1,93 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { OnPostAuthHandler } from './lifecycle/on_post_auth'; +import { OnPreResponseHandler } from './lifecycle/on_pre_response'; +import { HttpConfig } from './http_config'; +import { Env } from '../config'; +import { LifecycleRegistrar } from './http_server'; + +const VERSION_HEADER = 'kbn-version'; +const XSRF_HEADER = 'kbn-xsrf'; +const KIBANA_NAME_HEADER = 'kbn-name'; + +export const createXsrfPostAuthHandler = (config: HttpConfig): OnPostAuthHandler => { + const { whitelist, disableProtection } = config.xsrf; + + return (request, response, toolkit) => { + if (disableProtection || whitelist.includes(request.route.path)) { + return toolkit.next(); + } + + const isSafeMethod = request.route.method === 'get' || request.route.method === 'head'; + const hasVersionHeader = VERSION_HEADER in request.headers; + const hasXsrfHeader = XSRF_HEADER in request.headers; + + if (!isSafeMethod && !hasVersionHeader && !hasXsrfHeader) { + return response.badRequest({ body: `Request must contain a ${XSRF_HEADER} header.` }); + } + + return toolkit.next(); + }; +}; + +export const createVersionCheckPostAuthHandler = (kibanaVersion: string): OnPostAuthHandler => { + return (request, response, toolkit) => { + const requestVersion = request.headers[VERSION_HEADER]; + if (requestVersion && requestVersion !== kibanaVersion) { + return response.badRequest({ + body: { + message: + `Browser client is out of date, please refresh the page ` + + `("${VERSION_HEADER}" header was "${requestVersion}" but should be "${kibanaVersion}")`, + attributes: { + expected: kibanaVersion, + got: requestVersion, + }, + }, + }); + } + + return toolkit.next(); + }; +}; + +export const createCustomHeadersPreResponseHandler = (config: HttpConfig): OnPreResponseHandler => { + const serverName = config.name; + const customHeaders = config.customResponseHeaders; + + return (request, response, toolkit) => { + const additionalHeaders = { + ...customHeaders, + [KIBANA_NAME_HEADER]: serverName, + }; + + return toolkit.next({ headers: additionalHeaders }); + }; +}; + +export const registerCoreHandlers = ( + registrar: LifecycleRegistrar, + config: HttpConfig, + env: Env +) => { + registrar.registerOnPreResponse(createCustomHeadersPreResponseHandler(config)); + registrar.registerOnPostAuth(createXsrfPostAuthHandler(config)); + registrar.registerOnPostAuth(createVersionCheckPostAuthHandler(env.packageInfo.version)); +}; diff --git a/src/core/server/http/router/validator/validator.test.ts b/src/core/server/http/router/validator/validator.test.ts index 729eb1b60c10a..e972e2075e705 100644 --- a/src/core/server/http/router/validator/validator.test.ts +++ b/src/core/server/http/router/validator/validator.test.ts @@ -132,4 +132,62 @@ describe('Router validator', () => { 'The validation rule provided in the handler is not valid' ); }); + + it('should validate and infer type when data is an array', () => { + expect( + RouteValidator.from({ + body: schema.arrayOf(schema.string()), + }).getBody(['foo', 'bar']) + ).toStrictEqual(['foo', 'bar']); + expect( + RouteValidator.from({ + body: schema.arrayOf(schema.number()), + }).getBody([1, 2, 3]) + ).toStrictEqual([1, 2, 3]); + expect( + RouteValidator.from({ + body: schema.arrayOf(schema.object({ foo: schema.string() })), + }).getBody([{ foo: 'bar' }, { foo: 'dolly' }]) + ).toStrictEqual([{ foo: 'bar' }, { foo: 'dolly' }]); + + expect(() => + RouteValidator.from({ + body: schema.arrayOf(schema.number()), + }).getBody(['foo', 'bar', 'dolly']) + ).toThrowError('[0]: expected value of type [number] but got [string]'); + expect(() => + RouteValidator.from({ + body: schema.arrayOf(schema.number()), + }).getBody({ foo: 'bar' }) + ).toThrowError('expected value of type [array] but got [Object]'); + }); + + it('should validate and infer type when data is a primitive', () => { + expect( + RouteValidator.from({ + body: schema.string(), + }).getBody('foobar') + ).toStrictEqual('foobar'); + expect( + RouteValidator.from({ + body: schema.number(), + }).getBody(42) + ).toStrictEqual(42); + expect( + RouteValidator.from({ + body: schema.boolean(), + }).getBody(true) + ).toStrictEqual(true); + + expect(() => + RouteValidator.from({ + body: schema.string(), + }).getBody({ foo: 'bar' }) + ).toThrowError('expected value of type [string] but got [Object]'); + expect(() => + RouteValidator.from({ + body: schema.number(), + }).getBody('foobar') + ).toThrowError('expected value of type [number] but got [string]'); + }); }); diff --git a/src/core/server/http/router/validator/validator.ts b/src/core/server/http/router/validator/validator.ts index 65c0a934e6ef0..97dd2bc894f81 100644 --- a/src/core/server/http/router/validator/validator.ts +++ b/src/core/server/http/router/validator/validator.ts @@ -274,7 +274,7 @@ export class RouteValidator

{ // if options.body.output === 'stream' return schema.stream(); } else { - return schema.maybe(schema.nullable(schema.object({}, { allowUnknowns: true }))); + return schema.maybe(schema.nullable(schema.any({}))); } } } diff --git a/src/core/server/http/ssl_config.test.mocks.ts b/src/core/server/http/ssl_config.test.mocks.ts new file mode 100644 index 0000000000000..ab98c3a27920c --- /dev/null +++ b/src/core/server/http/ssl_config.test.mocks.ts @@ -0,0 +1,30 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export const mockReadFileSync = jest.fn(); +jest.mock('fs', () => { + return { readFileSync: mockReadFileSync }; +}); + +export const mockReadPkcs12Keystore = jest.fn(); +export const mockReadPkcs12Truststore = jest.fn(); +jest.mock('../../utils', () => ({ + readPkcs12Keystore: mockReadPkcs12Keystore, + readPkcs12Truststore: mockReadPkcs12Truststore, +})); diff --git a/src/core/server/http/ssl_config.test.ts b/src/core/server/http/ssl_config.test.ts new file mode 100644 index 0000000000000..738f86f7a69eb --- /dev/null +++ b/src/core/server/http/ssl_config.test.ts @@ -0,0 +1,363 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { + mockReadFileSync, + mockReadPkcs12Keystore, + mockReadPkcs12Truststore, +} from './ssl_config.test.mocks'; + +import { sslSchema, SslConfig } from './ssl_config'; + +describe('#SslConfig', () => { + const createConfig = (obj: any) => new SslConfig(sslSchema.validate(obj)); + + beforeEach(() => { + mockReadFileSync.mockReset(); + mockReadFileSync.mockImplementation((path: string) => `content-of-${path}`); + mockReadPkcs12Keystore.mockReset(); + mockReadPkcs12Keystore.mockImplementation((path: string) => ({ + key: `content-of-${path}.key`, + cert: `content-of-${path}.cert`, + ca: [`content-of-${path}.ca`], + })); + mockReadPkcs12Truststore.mockReset(); + mockReadPkcs12Truststore.mockImplementation((path: string) => [`content-of-${path}`]); + }); + + describe('throws when config is invalid', () => { + beforeEach(() => { + const realFs = jest.requireActual('fs'); + mockReadFileSync.mockImplementation((path: string) => realFs.readFileSync(path)); + const utils = jest.requireActual('../../utils'); + mockReadPkcs12Keystore.mockImplementation((path: string, password?: string) => + utils.readPkcs12Keystore(path, password) + ); + mockReadPkcs12Truststore.mockImplementation((path: string, password?: string) => + utils.readPkcs12Truststore(path, password) + ); + }); + + test('throws if `key` is invalid', () => { + const obj = { key: '/invalid/key', certificate: '/valid/certificate' }; + expect(() => createConfig(obj)).toThrowErrorMatchingInlineSnapshot( + `"ENOENT: no such file or directory, open '/invalid/key'"` + ); + }); + + test('throws if `certificate` is invalid', () => { + mockReadFileSync.mockImplementationOnce((path: string) => `content-of-${path}`); + const obj = { key: '/valid/key', certificate: '/invalid/certificate' }; + expect(() => createConfig(obj)).toThrowErrorMatchingInlineSnapshot( + `"ENOENT: no such file or directory, open '/invalid/certificate'"` + ); + }); + + test('throws if `certificateAuthorities` is invalid', () => { + const obj = { certificateAuthorities: '/invalid/ca' }; + expect(() => createConfig(obj)).toThrowErrorMatchingInlineSnapshot( + `"ENOENT: no such file or directory, open '/invalid/ca'"` + ); + }); + + test('throws if `keystore.path` is invalid', () => { + const obj = { keystore: { path: '/invalid/keystore' } }; + expect(() => createConfig(obj)).toThrowErrorMatchingInlineSnapshot( + `"ENOENT: no such file or directory, open '/invalid/keystore'"` + ); + }); + + test('throws if `keystore.path` does not contain a private key', () => { + mockReadPkcs12Keystore.mockImplementation((path: string, password?: string) => ({ + key: undefined, + certificate: 'foo', + })); + const obj = { keystore: { path: 'some-path' } }; + expect(() => createConfig(obj)).toThrowErrorMatchingInlineSnapshot( + `"Did not find private key in keystore at [keystore.path]."` + ); + }); + + test('throws if `keystore.path` does not contain a certificate', () => { + mockReadPkcs12Keystore.mockImplementation((path: string, password?: string) => ({ + key: 'foo', + certificate: undefined, + })); + const obj = { keystore: { path: 'some-path' } }; + expect(() => createConfig(obj)).toThrowErrorMatchingInlineSnapshot( + `"Did not find certificate in keystore at [keystore.path]."` + ); + }); + + test('throws if `truststore.path` is invalid', () => { + const obj = { truststore: { path: '/invalid/truststore' } }; + expect(() => createConfig(obj)).toThrowErrorMatchingInlineSnapshot( + `"ENOENT: no such file or directory, open '/invalid/truststore'"` + ); + }); + }); + + describe('reads files', () => { + it('reads certificate authorities when `keystore.path` is specified', () => { + const configValue = createConfig({ keystore: { path: 'some-path' } }); + expect(mockReadPkcs12Keystore).toHaveBeenCalledTimes(1); + expect(configValue.certificateAuthorities).toEqual(['content-of-some-path.ca']); + }); + + it('reads certificate authorities when `truststore.path` is specified', () => { + const configValue = createConfig({ truststore: { path: 'some-path' } }); + expect(mockReadPkcs12Truststore).toHaveBeenCalledTimes(1); + expect(configValue.certificateAuthorities).toEqual(['content-of-some-path']); + }); + + it('reads certificate authorities when `certificateAuthorities` is specified', () => { + let configValue = createConfig({ certificateAuthorities: 'some-path' }); + expect(mockReadFileSync).toHaveBeenCalledTimes(1); + expect(configValue.certificateAuthorities).toEqual(['content-of-some-path']); + + mockReadFileSync.mockClear(); + configValue = createConfig({ certificateAuthorities: ['some-path'] }); + expect(mockReadFileSync).toHaveBeenCalledTimes(1); + expect(configValue.certificateAuthorities).toEqual(['content-of-some-path']); + + mockReadFileSync.mockClear(); + configValue = createConfig({ certificateAuthorities: ['some-path', 'another-path'] }); + expect(mockReadFileSync).toHaveBeenCalledTimes(2); + expect(configValue.certificateAuthorities).toEqual([ + 'content-of-some-path', + 'content-of-another-path', + ]); + }); + + it('reads certificate authorities when `keystore.path`, `truststore.path`, and `certificateAuthorities` are specified', () => { + const configValue = createConfig({ + keystore: { path: 'some-path' }, + truststore: { path: 'another-path' }, + certificateAuthorities: 'yet-another-path', + }); + expect(mockReadPkcs12Keystore).toHaveBeenCalledTimes(1); + expect(mockReadPkcs12Truststore).toHaveBeenCalledTimes(1); + expect(mockReadFileSync).toHaveBeenCalledTimes(1); + expect(configValue.certificateAuthorities).toEqual([ + 'content-of-some-path.ca', + 'content-of-another-path', + 'content-of-yet-another-path', + ]); + }); + + it('reads a private key and certificate when `keystore.path` is specified', () => { + const configValue = createConfig({ keystore: { path: 'some-path' } }); + expect(mockReadPkcs12Keystore).toHaveBeenCalledTimes(1); + expect(configValue.key).toEqual('content-of-some-path.key'); + expect(configValue.certificate).toEqual('content-of-some-path.cert'); + }); + + it('reads a private key and certificate when `key` and `certificate` are specified', () => { + const configValue = createConfig({ key: 'some-path', certificate: 'another-path' }); + expect(mockReadFileSync).toHaveBeenCalledTimes(2); + expect(configValue.key).toEqual('content-of-some-path'); + expect(configValue.certificate).toEqual('content-of-another-path'); + }); + }); +}); + +describe('#sslSchema', () => { + describe('throws when config is invalid', () => { + test('throws if both `key` and `keystore.path` are specified', () => { + const obj = { + key: '/path/to/key', + keystore: { + path: 'path/to/keystore', + }, + }; + expect(() => sslSchema.validate(obj)).toThrowErrorMatchingInlineSnapshot( + `"cannot use [key] when [keystore.path] is specified"` + ); + }); + + test('throws if both `certificate` and `keystore.path` are specified', () => { + const obj = { + certificate: '/path/to/certificate', + keystore: { + path: 'path/to/keystore', + }, + }; + expect(() => sslSchema.validate(obj)).toThrowErrorMatchingInlineSnapshot( + `"cannot use [certificate] when [keystore.path] is specified"` + ); + }); + + test('throws if TLS is enabled but `certificate` is specified and `key` is not', () => { + const obj = { + certificate: '/path/to/certificate', + enabled: true, + }; + expect(() => sslSchema.validate(obj)).toThrowErrorMatchingInlineSnapshot( + `"must specify [certificate] and [key] -- or [keystore.path] -- when ssl is enabled"` + ); + }); + + test('throws if TLS is enabled but `key` is specified and `certificate` is not', () => { + const obj = { + enabled: true, + key: '/path/to/key', + }; + expect(() => sslSchema.validate(obj)).toThrowErrorMatchingInlineSnapshot( + `"must specify [certificate] and [key] -- or [keystore.path] -- when ssl is enabled"` + ); + }); + + test('throws if TLS is enabled but `key`, `certificate`, and `keystore.path` are not specified', () => { + const obj = { + enabled: true, + }; + expect(() => sslSchema.validate(obj)).toThrowErrorMatchingInlineSnapshot( + `"must specify [certificate] and [key] -- or [keystore.path] -- when ssl is enabled"` + ); + }); + + test('throws if TLS is not enabled but `clientAuthentication` is `optional`', () => { + const obj = { + enabled: false, + clientAuthentication: 'optional', + }; + expect(() => sslSchema.validate(obj)).toThrowErrorMatchingInlineSnapshot( + `"must enable ssl to use [clientAuthentication]"` + ); + }); + + test('throws if TLS is not enabled but `clientAuthentication` is `required`', () => { + const obj = { + enabled: false, + clientAuthentication: 'required', + }; + expect(() => sslSchema.validate(obj)).toThrowErrorMatchingInlineSnapshot( + `"must enable ssl to use [clientAuthentication]"` + ); + }); + }); + + describe('#supportedProtocols', () => { + test('accepts known protocols`', () => { + const singleKnownProtocol = { + certificate: '/path/to/certificate', + enabled: true, + key: '/path/to/key', + supportedProtocols: ['TLSv1'], + }; + + const allKnownProtocols = { + certificate: '/path/to/certificate', + enabled: true, + key: '/path/to/key', + supportedProtocols: ['TLSv1', 'TLSv1.1', 'TLSv1.2'], + }; + + const singleKnownProtocolConfig = sslSchema.validate(singleKnownProtocol); + expect(singleKnownProtocolConfig.supportedProtocols).toEqual(['TLSv1']); + + const allKnownProtocolsConfig = sslSchema.validate(allKnownProtocols); + expect(allKnownProtocolsConfig.supportedProtocols).toEqual(['TLSv1', 'TLSv1.1', 'TLSv1.2']); + }); + + test('rejects unknown protocols`', () => { + const singleUnknownProtocol = { + certificate: '/path/to/certificate', + enabled: true, + key: '/path/to/key', + supportedProtocols: ['SOMEv100500'], + }; + + const allKnownWithOneUnknownProtocols = { + certificate: '/path/to/certificate', + enabled: true, + key: '/path/to/key', + supportedProtocols: ['TLSv1', 'TLSv1.1', 'TLSv1.2', 'SOMEv100500'], + }; + + expect(() => sslSchema.validate(singleUnknownProtocol)).toThrowErrorMatchingInlineSnapshot(` +"[supportedProtocols.0]: types that failed validation: +- [supportedProtocols.0.0]: expected value to equal [TLSv1] but got [SOMEv100500] +- [supportedProtocols.0.1]: expected value to equal [TLSv1.1] but got [SOMEv100500] +- [supportedProtocols.0.2]: expected value to equal [TLSv1.2] but got [SOMEv100500]" +`); + expect(() => sslSchema.validate(allKnownWithOneUnknownProtocols)) + .toThrowErrorMatchingInlineSnapshot(` +"[supportedProtocols.3]: types that failed validation: +- [supportedProtocols.3.0]: expected value to equal [TLSv1] but got [SOMEv100500] +- [supportedProtocols.3.1]: expected value to equal [TLSv1.1] but got [SOMEv100500] +- [supportedProtocols.3.2]: expected value to equal [TLSv1.2] but got [SOMEv100500]" +`); + }); + }); + + describe('#clientAuthentication', () => { + test('can specify `none` client authentication when ssl is not enabled', () => { + const obj = { + enabled: false, + clientAuthentication: 'none', + }; + + const configValue = sslSchema.validate(obj); + expect(configValue.clientAuthentication).toBe('none'); + }); + + test('should properly interpret `none` client authentication when ssl is enabled', () => { + const sslConfig = new SslConfig( + sslSchema.validate({ + enabled: true, + key: 'some-key-path', + certificate: 'some-certificate-path', + clientAuthentication: 'none', + }) + ); + + expect(sslConfig.requestCert).toBe(false); + expect(sslConfig.rejectUnauthorized).toBe(false); + }); + + test('should properly interpret `optional` client authentication when ssl is enabled', () => { + const sslConfig = new SslConfig( + sslSchema.validate({ + enabled: true, + key: 'some-key-path', + certificate: 'some-certificate-path', + clientAuthentication: 'optional', + }) + ); + + expect(sslConfig.requestCert).toBe(true); + expect(sslConfig.rejectUnauthorized).toBe(false); + }); + + test('should properly interpret `required` client authentication when ssl is enabled', () => { + const sslConfig = new SslConfig( + sslSchema.validate({ + enabled: true, + key: 'some-key-path', + certificate: 'some-certificate-path', + clientAuthentication: 'required', + }) + ); + + expect(sslConfig.requestCert).toBe(true); + expect(sslConfig.rejectUnauthorized).toBe(true); + }); + }); +}); diff --git a/src/core/server/http/ssl_config.ts b/src/core/server/http/ssl_config.ts index 55d6ebff93ce7..0096eeb092565 100644 --- a/src/core/server/http/ssl_config.ts +++ b/src/core/server/http/ssl_config.ts @@ -19,6 +19,8 @@ import { schema, TypeOf } from '@kbn/config-schema'; import crypto from 'crypto'; +import { readFileSync } from 'fs'; +import { readPkcs12Keystore, readPkcs12Truststore } from '../../utils'; // `crypto` type definitions doesn't currently include `crypto.constants`, see // https://github.com/DefinitelyTyped/DefinitelyTyped/blob/fa5baf1733f49cf26228a4e509914572c1b74adf/types/node/v6/index.d.ts#L3412 @@ -44,6 +46,14 @@ export const sslSchema = schema.object( }), key: schema.maybe(schema.string()), keyPassphrase: schema.maybe(schema.string()), + keystore: schema.object({ + path: schema.maybe(schema.string()), + password: schema.maybe(schema.string()), + }), + truststore: schema.object({ + path: schema.maybe(schema.string()), + password: schema.maybe(schema.string()), + }), redirectHttpFromPort: schema.maybe(schema.number()), supportedProtocols: schema.arrayOf( schema.oneOf([schema.literal('TLSv1'), schema.literal('TLSv1.1'), schema.literal('TLSv1.2')]), @@ -56,8 +66,16 @@ export const sslSchema = schema.object( }, { validate: ssl => { - if (ssl.enabled && (!ssl.key || !ssl.certificate)) { - return 'must specify [certificate] and [key] when ssl is enabled'; + if (ssl.key && ssl.keystore.path) { + return 'cannot use [key] when [keystore.path] is specified'; + } + + if (ssl.certificate && ssl.keystore.path) { + return 'cannot use [certificate] when [keystore.path] is specified'; + } + + if (ssl.enabled && (!ssl.key || !ssl.certificate) && !ssl.keystore.path) { + return 'must specify [certificate] and [key] -- or [keystore.path] -- when ssl is enabled'; } if (!ssl.enabled && ssl.clientAuthentication !== 'none') { @@ -88,14 +106,49 @@ export class SslConfig { constructor(config: SslConfigType) { this.enabled = config.enabled; this.redirectHttpFromPort = config.redirectHttpFromPort; - this.key = config.key; - this.certificate = config.certificate; - this.certificateAuthorities = this.initCertificateAuthorities(config.certificateAuthorities); - this.keyPassphrase = config.keyPassphrase; this.cipherSuites = config.cipherSuites; this.supportedProtocols = config.supportedProtocols; this.requestCert = config.clientAuthentication !== 'none'; this.rejectUnauthorized = config.clientAuthentication === 'required'; + + const addCAs = (ca: string[] | undefined) => { + if (ca && ca.length) { + this.certificateAuthorities = [...(this.certificateAuthorities || []), ...ca]; + } + }; + + if (config.keystore?.path) { + const { key, cert, ca } = readPkcs12Keystore(config.keystore.path, config.keystore.password); + if (!key) { + throw new Error(`Did not find private key in keystore at [keystore.path].`); + } else if (!cert) { + throw new Error(`Did not find certificate in keystore at [keystore.path].`); + } + this.key = key; + this.certificate = cert; + addCAs(ca); + } else if (config.key && config.certificate) { + this.key = readFile(config.key); + this.keyPassphrase = config.keyPassphrase; + this.certificate = readFile(config.certificate); + } + + if (config.truststore?.path) { + const ca = readPkcs12Truststore(config.truststore.path, config.truststore.password); + addCAs(ca); + } + + const ca = config.certificateAuthorities; + if (ca) { + const parsed: string[] = []; + const paths = Array.isArray(ca) ? ca : [ca]; + if (paths.length > 0) { + for (const path of paths) { + parsed.push(readFile(path)); + } + addCAs(parsed); + } + } } /** @@ -117,12 +170,8 @@ export class SslConfig { : secureOptions | secureOption; // eslint-disable-line no-bitwise }, 0); } - - private initCertificateAuthorities(certificateAuthorities?: string[] | string) { - if (certificateAuthorities === undefined || Array.isArray(certificateAuthorities)) { - return certificateAuthorities; - } - - return [certificateAuthorities]; - } } + +const readFile = (file: string) => { + return readFileSync(file, 'utf8'); +}; diff --git a/src/core/server/http/test_utils.ts b/src/core/server/http/test_utils.ts index e0a15cdc6e839..ffdc04d156ca0 100644 --- a/src/core/server/http/test_utils.ts +++ b/src/core/server/http/test_utils.ts @@ -41,6 +41,10 @@ configService.atPath.mockReturnValue( enabled: false, }, compression: { enabled: true }, + xsrf: { + disableProtection: true, + whitelist: [], + }, } as any) ); diff --git a/src/core/server/http/types.ts b/src/core/server/http/types.ts index 92217515a22a1..9c8bfc073a524 100644 --- a/src/core/server/http/types.ts +++ b/src/core/server/http/types.ts @@ -250,16 +250,6 @@ export interface InternalHttpServiceSetup contextName: T, provider: RequestHandlerContextProvider ) => RequestHandlerContextContainer; - config: { - /** - * @internalRemarks - * Deprecated part of the server config, provided until - * https://github.com/elastic/kibana/issues/40255 - * - * @deprecated - * */ - defaultRoute?: string; - }; } /** @public */ 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/internal_types.ts b/src/core/server/internal_types.ts index be4d830c55eab..ff68d1544d119 100644 --- a/src/core/server/internal_types.ts +++ b/src/core/server/internal_types.ts @@ -17,7 +17,10 @@ * under the License. */ +import { Type } from '@kbn/config-schema'; + import { CapabilitiesSetup, CapabilitiesStart } from './capabilities'; +import { ConfigDeprecationProvider } from './config'; import { ContextSetup } from './context'; import { InternalElasticsearchServiceSetup } from './elasticsearch'; import { InternalHttpServiceSetup } from './http'; @@ -47,3 +50,18 @@ export interface InternalCoreStart { savedObjects: InternalSavedObjectsServiceStart; uiSettings: InternalUiSettingsServiceStart; } + +/** + * @internal + */ +export interface ServiceConfigDescriptor { + path: string; + /** + * Schema to use to validate the configuration. + */ + schema: Type; + /** + * Provider for the {@link ConfigDeprecation} to apply to the plugin configuration. + */ + deprecations?: ConfigDeprecationProvider; +} diff --git a/src/core/server/legacy/config/__snapshots__/legacy_object_to_config_adapter.test.ts.snap b/src/core/server/legacy/config/__snapshots__/legacy_object_to_config_adapter.test.ts.snap index 0ebd8b8371628..74ecaa9f09c0e 100644 --- a/src/core/server/legacy/config/__snapshots__/legacy_object_to_config_adapter.test.ts.snap +++ b/src/core/server/legacy/config/__snapshots__/legacy_object_to_config_adapter.test.ts.snap @@ -8,10 +8,13 @@ Object { "enabled": true, }, "cors": false, - "defaultRoute": undefined, + "customResponseHeaders": Object { + "custom-header": "custom-value", + }, "host": "host", "keepaliveTimeout": 5000, "maxPayload": 1000, + "name": "kibana-hostname", "port": 1234, "rewriteBasePath": false, "socketTimeout": 2000, @@ -21,6 +24,10 @@ Object { "someNewValue": "new", }, "uuid": undefined, + "xsrf": Object { + "disableProtection": false, + "whitelist": Array [], + }, } `; @@ -32,10 +39,13 @@ Object { "enabled": true, }, "cors": false, - "defaultRoute": undefined, + "customResponseHeaders": Object { + "custom-header": "custom-value", + }, "host": "host", "keepaliveTimeout": 5000, "maxPayload": 1000, + "name": "kibana-hostname", "port": 1234, "rewriteBasePath": false, "socketTimeout": 2000, @@ -45,6 +55,10 @@ Object { "key": "key", }, "uuid": undefined, + "xsrf": Object { + "disableProtection": false, + "whitelist": Array [], + }, } `; diff --git a/src/core/server/legacy/config/legacy_object_to_config_adapter.test.ts b/src/core/server/legacy/config/legacy_object_to_config_adapter.test.ts index db2bc117280ca..1c51564187442 100644 --- a/src/core/server/legacy/config/legacy_object_to_config_adapter.test.ts +++ b/src/core/server/legacy/config/legacy_object_to_config_adapter.test.ts @@ -80,9 +80,11 @@ describe('#get', () => { test('correctly handles server config.', () => { const configAdapter = new LegacyObjectToConfigAdapter({ server: { + name: 'kibana-hostname', autoListen: true, basePath: '/abc', cors: false, + customResponseHeaders: { 'custom-header': 'custom-value' }, host: 'host', maxPayloadBytes: 1000, keepaliveTimeout: 5000, @@ -92,14 +94,20 @@ describe('#get', () => { ssl: { enabled: true, keyPassphrase: 'some-phrase', someNewValue: 'new' }, compression: { enabled: true }, someNotSupportedValue: 'val', + xsrf: { + disableProtection: false, + whitelist: [], + }, }, }); const configAdapterWithDisabledSSL = new LegacyObjectToConfigAdapter({ server: { + name: 'kibana-hostname', autoListen: true, basePath: '/abc', cors: false, + customResponseHeaders: { 'custom-header': 'custom-value' }, host: 'host', maxPayloadBytes: 1000, keepaliveTimeout: 5000, @@ -109,6 +117,10 @@ describe('#get', () => { ssl: { enabled: false, certificate: 'cert', key: 'key' }, compression: { enabled: true }, someNotSupportedValue: 'val', + xsrf: { + disableProtection: false, + whitelist: [], + }, }, }); diff --git a/src/core/server/legacy/config/legacy_object_to_config_adapter.ts b/src/core/server/legacy/config/legacy_object_to_config_adapter.ts index bdcde8262ef98..30bb150e6c15a 100644 --- a/src/core/server/legacy/config/legacy_object_to_config_adapter.ts +++ b/src/core/server/legacy/config/legacy_object_to_config_adapter.ts @@ -60,14 +60,15 @@ export class LegacyObjectToConfigAdapter extends ObjectToConfigAdapter { private static transformServer(configValue: any = {}) { // TODO: New platform uses just a subset of `server` config from the legacy platform, - // new values will be exposed once we need them (eg. customResponseHeaders or xsrf). + // new values will be exposed once we need them return { autoListen: configValue.autoListen, basePath: configValue.basePath, - defaultRoute: configValue.defaultRoute, cors: configValue.cors, + customResponseHeaders: configValue.customResponseHeaders, host: configValue.host, maxPayload: configValue.maxPayloadBytes, + name: configValue.name, port: configValue.port, rewriteBasePath: configValue.rewriteBasePath, ssl: configValue.ssl, @@ -75,6 +76,7 @@ export class LegacyObjectToConfigAdapter extends ObjectToConfigAdapter { socketTimeout: configValue.socketTimeout, compression: configValue.compression, uuid: configValue.uuid, + xsrf: configValue.xsrf, }; } diff --git a/src/core/server/legacy/legacy_service.ts b/src/core/server/legacy/legacy_service.ts index 2ed87f4c6d488..cc36b90ec526d 100644 --- a/src/core/server/legacy/legacy_service.ts +++ b/src/core/server/legacy/legacy_service.ts @@ -84,7 +84,7 @@ export class LegacyService implements CoreService { private settings?: LegacyVars; constructor(private readonly coreContext: CoreContext) { - const { logger, configService, env } = coreContext; + const { logger, configService } = coreContext; this.log = logger.get('legacy-service'); this.devConfig$ = configService @@ -93,7 +93,7 @@ export class LegacyService implements CoreService { this.httpConfig$ = combineLatest( configService.atPath(httpConfig.path), configService.atPath(cspConfig.path) - ).pipe(map(([http, csp]) => new HttpConfig(http, csp, env))); + ).pipe(map(([http, csp]) => new HttpConfig(http, csp))); } public async discoverPlugins(): Promise { @@ -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..c7082d46313ae 100644 --- a/src/core/server/mocks.ts +++ b/src/core/server/mocks.ts @@ -37,6 +37,7 @@ export { elasticsearchServiceMock } from './elasticsearch/elasticsearch_service. export { httpServiceMock } from './http/http_service.mock'; export { loggingServiceMock } from './logging/logging_service.mock'; export { savedObjectsClientMock } from './saved_objects/service/saved_objects_client.mock'; +export { savedObjectsRepositoryMock } from './saved_objects/service/lib/repository.mock'; export { uiSettingsServiceMock } from './ui_settings/ui_settings_service.mock'; import { uuidServiceMock } from './uuid/uuid_service.mock'; @@ -107,7 +108,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 +132,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/discovery/plugins_discovery.test.ts b/src/core/server/plugins/discovery/plugins_discovery.test.ts index bf55fc7caae4c..2902aafdbf146 100644 --- a/src/core/server/plugins/discovery/plugins_discovery.test.ts +++ b/src/core/server/plugins/discovery/plugins_discovery.test.ts @@ -18,13 +18,14 @@ */ import { mockPackage, mockReaddir, mockReadFile, mockStat } from './plugins_discovery.test.mocks'; +import { rawConfigServiceMock } from '../../config/raw_config_service.mock'; +import { loggingServiceMock } from '../../logging/logging_service.mock'; import { resolve } from 'path'; import { first, map, toArray } from 'rxjs/operators'; + import { ConfigService, Env } from '../../config'; -import { rawConfigServiceMock } from '../../config/raw_config_service.mock'; import { getEnvOptions } from '../../config/__mocks__/env'; -import { loggingServiceMock } from '../../logging/logging_service.mock'; import { PluginWrapper } from '../plugin'; import { PluginsConfig, PluginsConfigType, config } from '../plugins_config'; import { discover } from './plugins_discovery'; @@ -37,6 +38,7 @@ const TEST_PLUGIN_SEARCH_PATHS = { const TEST_EXTRA_PLUGIN_PATH = resolve(process.cwd(), 'my-extra-plugin'); const logger = loggingServiceMock.create(); + beforeEach(() => { mockReaddir.mockImplementation((path, cb) => { if (path === TEST_PLUGIN_SEARCH_PATHS.nonEmptySrcPlugins) { @@ -182,12 +184,84 @@ test('properly iterates through plugin search locations', async () => { 'kibana.json' )})`, ]); +}); + +test('logs a warning about --plugin-path when used in development', async () => { + mockPackage.raw = { + branch: 'master', + version: '1.2.3', + build: { + distributable: true, + number: 1, + sha: '', + }, + }; + + const env = Env.createDefault( + getEnvOptions({ + cliArgs: { dev: false, envName: 'development' }, + }) + ); + const configService = new ConfigService( + rawConfigServiceMock.create({ rawConfig: { plugins: { paths: [TEST_EXTRA_PLUGIN_PATH] } } }), + env, + logger + ); + await configService.setSchema(config.path, config.schema); + + const rawConfig = await configService + .atPath('plugins') + .pipe(first()) + .toPromise(); + + discover(new PluginsConfig(rawConfig, env), { + coreId: Symbol(), + configService, + env, + logger, + }); + + expect(loggingServiceMock.collect(logger).warn).toEqual([ + [ + `Explicit plugin paths [${TEST_EXTRA_PLUGIN_PATH}] should only be used in development. Relative imports may not work properly in production.`, + ], + ]); +}); + +test('does not log a warning about --plugin-path when used in production', async () => { + mockPackage.raw = { + branch: 'master', + version: '1.2.3', + build: { + distributable: true, + number: 1, + sha: '', + }, + }; + + const env = Env.createDefault( + getEnvOptions({ + cliArgs: { dev: false, envName: 'production' }, + }) + ); + const configService = new ConfigService( + rawConfigServiceMock.create({ rawConfig: { plugins: { paths: [TEST_EXTRA_PLUGIN_PATH] } } }), + env, + logger + ); + await configService.setSchema(config.path, config.schema); + + const rawConfig = await configService + .atPath('plugins') + .pipe(first()) + .toPromise(); + + discover(new PluginsConfig(rawConfig, env), { + coreId: Symbol(), + configService, + env, + logger, + }); - expect(loggingServiceMock.collect(logger).warn).toMatchInlineSnapshot(` -Array [ - Array [ - "Explicit plugin paths [${TEST_EXTRA_PLUGIN_PATH}] are only supported in development. Relative imports will not work in production.", - ], -] -`); + expect(loggingServiceMock.collect(logger).warn).toEqual([]); }); diff --git a/src/core/server/plugins/discovery/plugins_discovery.ts b/src/core/server/plugins/discovery/plugins_discovery.ts index 521d02e487df6..79238afdf5c81 100644 --- a/src/core/server/plugins/discovery/plugins_discovery.ts +++ b/src/core/server/plugins/discovery/plugins_discovery.ts @@ -46,9 +46,9 @@ export function discover(config: PluginsConfig, coreContext: CoreContext) { const log = coreContext.logger.get('plugins-discovery'); log.debug('Discovering plugins...'); - if (config.additionalPluginPaths.length) { + if (config.additionalPluginPaths.length && coreContext.env.mode.dev) { log.warn( - `Explicit plugin paths [${config.additionalPluginPaths}] are only supported in development. Relative imports will not work in production.` + `Explicit plugin paths [${config.additionalPluginPaths}] should only be used in development. Relative imports may not work properly in production.` ); } 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_config.test.ts b/src/core/server/plugins/plugins_config.test.ts new file mode 100644 index 0000000000000..180d6093e0404 --- /dev/null +++ b/src/core/server/plugins/plugins_config.test.ts @@ -0,0 +1,44 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { PluginsConfig, PluginsConfigType } from './plugins_config'; +import { Env } from '../config'; +import { getEnvOptions } from '../config/__mocks__/env'; + +describe('PluginsConfig', () => { + it('retrieves additionalPluginPaths from config.paths when in production mode', () => { + const env = Env.createDefault(getEnvOptions({ cliArgs: { dev: false } })); + const rawConfig: PluginsConfigType = { + initialize: true, + paths: ['some-path', 'another-path'], + }; + const config = new PluginsConfig(rawConfig, env); + expect(config.additionalPluginPaths).toEqual(['some-path', 'another-path']); + }); + + it('retrieves additionalPluginPaths from config.paths when in development mode', () => { + const env = Env.createDefault(getEnvOptions({ cliArgs: { dev: true } })); + const rawConfig: PluginsConfigType = { + initialize: true, + paths: ['some-path', 'another-path'], + }; + const config = new PluginsConfig(rawConfig, env); + expect(config.additionalPluginPaths).toEqual(['some-path', 'another-path']); + }); +}); diff --git a/src/core/server/plugins/plugins_config.ts b/src/core/server/plugins/plugins_config.ts index 93ae229737fae..8ebcc92672dde 100644 --- a/src/core/server/plugins/plugins_config.ts +++ b/src/core/server/plugins/plugins_config.ts @@ -29,7 +29,6 @@ export const config = { /** * Defines an array of directories where another plugin should be loaded from. - * Should only be used in a development environment. */ paths: schema.arrayOf(schema.string(), { defaultValue: [] }), }), @@ -55,7 +54,6 @@ export class PluginsConfig { constructor(rawConfig: PluginsConfigType, env: Env) { this.initialize = rawConfig.initialize; this.pluginSearchPaths = env.pluginSearchPaths; - // Only allow custom plugin paths in dev. - this.additionalPluginPaths = env.mode.dev ? rawConfig.paths : []; + this.additionalPluginPaths = rawConfig.paths; } } 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/__snapshots__/rendering_service.test.ts.snap b/src/core/server/rendering/__snapshots__/rendering_service.test.ts.snap index edde1dee85f4f..5e6e977663bc4 100644 --- a/src/core/server/rendering/__snapshots__/rendering_service.test.ts.snap +++ b/src/core/server/rendering/__snapshots__/rendering_service.test.ts.snap @@ -18,6 +18,7 @@ Object { "oss": false, "quiet": false, "repl": false, + "runExamples": false, "silent": false, "watch": false, }, @@ -39,7 +40,6 @@ Object { "version": Any, }, "pluginSearchPaths": Any, - "staticFilesDir": Any, }, "i18n": Object { "translationsUrl": "/mock-server-basepath/translations/en.json", @@ -89,6 +89,7 @@ Object { "oss": false, "quiet": false, "repl": false, + "runExamples": false, "silent": false, "watch": false, }, @@ -110,7 +111,6 @@ Object { "version": Any, }, "pluginSearchPaths": Any, - "staticFilesDir": Any, }, "i18n": Object { "translationsUrl": "/mock-server-basepath/translations/en.json", @@ -160,6 +160,7 @@ Object { "oss": false, "quiet": false, "repl": false, + "runExamples": false, "silent": false, "watch": false, }, @@ -181,7 +182,6 @@ Object { "version": Any, }, "pluginSearchPaths": Any, - "staticFilesDir": Any, }, "i18n": Object { "translationsUrl": "/mock-server-basepath/translations/en.json", @@ -235,6 +235,7 @@ Object { "oss": false, "quiet": false, "repl": false, + "runExamples": false, "silent": false, "watch": false, }, @@ -256,7 +257,6 @@ Object { "version": Any, }, "pluginSearchPaths": Any, - "staticFilesDir": Any, }, "i18n": Object { "translationsUrl": "/translations/en.json", @@ -306,6 +306,7 @@ Object { "oss": false, "quiet": false, "repl": false, + "runExamples": false, "silent": false, "watch": false, }, @@ -327,7 +328,6 @@ Object { "version": Any, }, "pluginSearchPaths": Any, - "staticFilesDir": Any, }, "i18n": Object { "translationsUrl": "/mock-server-basepath/translations/en.json", @@ -377,6 +377,7 @@ Object { "oss": false, "quiet": false, "repl": false, + "runExamples": false, "silent": false, "watch": false, }, @@ -398,7 +399,6 @@ Object { "version": Any, }, "pluginSearchPaths": Any, - "staticFilesDir": Any, }, "i18n": Object { "translationsUrl": "/mock-server-basepath/translations/en.json", @@ -448,6 +448,7 @@ Object { "oss": false, "quiet": false, "repl": false, + "runExamples": false, "silent": false, "watch": false, }, @@ -469,7 +470,6 @@ Object { "version": Any, }, "pluginSearchPaths": Any, - "staticFilesDir": Any, }, "i18n": Object { "translationsUrl": "/translations/en.json", @@ -519,6 +519,7 @@ Object { "oss": false, "quiet": false, "repl": false, + "runExamples": false, "silent": false, "watch": false, }, @@ -540,7 +541,6 @@ Object { "version": Any, }, "pluginSearchPaths": Any, - "staticFilesDir": Any, }, "i18n": Object { "translationsUrl": "/mock-server-basepath/translations/en.json", @@ -592,6 +592,7 @@ Object { "oss": false, "quiet": false, "repl": false, + "runExamples": false, "silent": false, "watch": false, }, @@ -613,7 +614,6 @@ Object { "version": Any, }, "pluginSearchPaths": Any, - "staticFilesDir": Any, }, "i18n": Object { "translationsUrl": "/mock-server-basepath/translations/en.json", @@ -663,6 +663,7 @@ Object { "oss": false, "quiet": false, "repl": false, + "runExamples": false, "silent": false, "watch": false, }, @@ -684,7 +685,6 @@ Object { "version": Any, }, "pluginSearchPaths": Any, - "staticFilesDir": Any, }, "i18n": Object { "translationsUrl": "/mock-server-basepath/translations/en.json", diff --git a/src/core/server/rendering/rendering_service.test.ts b/src/core/server/rendering/rendering_service.test.ts index 63145f2b30573..43ff4f633085c 100644 --- a/src/core/server/rendering/rendering_service.test.ts +++ b/src/core/server/rendering/rendering_service.test.ts @@ -41,7 +41,6 @@ const INJECTED_METADATA = { version: expect.any(String), }, pluginSearchPaths: expect.any(Array), - staticFilesDir: expect.any(String), }, legacyMetadata: { branch: expect.any(String), @@ -50,6 +49,7 @@ const INJECTED_METADATA = { version: expect.any(String), }, }; + const { createKibanaRequest, createRawRequest } = httpServerMock; const legacyApp = { getId: () => 'legacy' }; 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 ( \ No newline at end of file diff --git a/src/legacy/core_plugins/kibana/public/home/tutorial_resources/logos/ibmmq.svg b/src/legacy/core_plugins/kibana/public/home/tutorial_resources/logos/ibmmq.svg new file mode 100644 index 0000000000000..ad0cb64b161dd --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/home/tutorial_resources/logos/ibmmq.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/legacy/core_plugins/kibana/public/home/tutorial_resources/logos/stan.svg b/src/legacy/core_plugins/kibana/public/home/tutorial_resources/logos/stan.svg new file mode 100644 index 0000000000000..5a1d6e9a52f17 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/home/tutorial_resources/logos/stan.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/legacy/core_plugins/kibana/public/local_application_service/local_application_service.ts b/src/legacy/core_plugins/kibana/public/local_application_service/local_application_service.ts index cd3e0d2fd9f89..c09995caab669 100644 --- a/src/legacy/core_plugins/kibana/public/local_application_service/local_application_service.ts +++ b/src/legacy/core_plugins/kibana/public/local_application_service/local_application_service.ts @@ -68,7 +68,7 @@ export class LocalApplicationService { isUnmounted = true; }); (async () => { - const params = { element, appBasePath: '' }; + const params = { element, appBasePath: '', onAppLeave: () => undefined }; unmountHandler = isAppMountDeprecated(app.mount) ? await app.mount({ core: npStart.core }, params) : await app.mount(params); diff --git a/src/legacy/core_plugins/kibana/public/management/index.js b/src/legacy/core_plugins/kibana/public/management/index.js index 5323fb2dac2d2..d62770956b88e 100644 --- a/src/legacy/core_plugins/kibana/public/management/index.js +++ b/src/legacy/core_plugins/kibana/public/management/index.js @@ -28,7 +28,8 @@ import { I18nContext } from 'ui/i18n'; import { uiModules } from 'ui/modules'; import appTemplate from './app.html'; import landingTemplate from './landing.html'; -import { management, SidebarNav, MANAGEMENT_BREADCRUMB } from 'ui/management'; +import { management, MANAGEMENT_BREADCRUMB } from 'ui/management'; +import { ManagementSidebarNav } from '../../../../../plugins/management/public'; import { FeatureCatalogueRegistryProvider, FeatureCatalogueCategory, @@ -42,6 +43,7 @@ import { EuiIcon, EuiHorizontalRule, } from '@elastic/eui'; +import { npStart } from 'ui/new_platform'; const SIDENAV_ID = 'management-sidenav'; const LANDING_ID = 'management-landing'; @@ -102,7 +104,7 @@ export function updateLandingPage(version) { ); } -export function updateSidebar(items, id) { +export function updateSidebar(legacySections, id) { const node = document.getElementById(SIDENAV_ID); if (!node) { return; @@ -110,7 +112,12 @@ export function updateSidebar(items, id) { render( - + , node ); diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/__jest__/render.test.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/__jest__/render.test.js index be4866f9dfdd4..375e80028f317 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/__jest__/render.test.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/__jest__/render.test.js @@ -22,12 +22,6 @@ const unmountComponentAtNode = jest.fn(); jest.doMock('react-dom', () => ({ render, unmountComponentAtNode })); -// If we don't mock this, Jest fails with the error `TypeError: Cannot redefine property: prototype -// at Function.defineProperties`. -jest.mock('ui/index_patterns', () => ({ - INDEX_PATTERN_ILLEGAL_CHARACTERS: ['\\', '/', '?', '"', '<', '>', '|', ' '], -})); - jest.mock('ui/chrome', () => ({ getUiSettingsClient: () => ({ get: () => '', diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_index_pattern/__jest__/step_index_pattern.test.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_index_pattern/__jest__/step_index_pattern.test.js index 1797fc203ccd8..85e610bbbf993 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_index_pattern/__jest__/step_index_pattern.test.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_index_pattern/__jest__/step_index_pattern.test.js @@ -32,11 +32,6 @@ const mockIndexPatternCreationType = { checkIndicesForErrors: () => false, getShowSystemIndices: () => false, }; -// If we don't mock this, Jest fails with the error `TypeError: Cannot redefine property: prototype -// at Function.defineProperties`. -jest.mock('ui/index_patterns', () => ({ - INDEX_PATTERN_ILLEGAL_CHARACTERS: ['\\', '/', '?', '"', '<', '>', '|', ' '], -})); jest.mock('ui/chrome', () => ({ getUiSettingsClient: () => ({ diff --git a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.js b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.js index 4764b516dffec..c990efaf43547 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/index_patterns/create_index_pattern_wizard/components/step_index_pattern/step_index_pattern.js @@ -19,7 +19,7 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; -import { INDEX_PATTERN_ILLEGAL_CHARACTERS as ILLEGAL_CHARACTERS } from 'ui/index_patterns'; +import { indexPatterns } from '../../../../../../../../../../plugins/data/public'; import { MAX_SEARCH_SIZE } from '../../constants'; import { getIndices, @@ -71,7 +71,7 @@ export class StepIndexPattern extends Component { indexPatternName: indexPatternCreationType.getIndexPatternName(), }; - this.ILLEGAL_CHARACTERS = [...ILLEGAL_CHARACTERS]; + this.ILLEGAL_CHARACTERS = [...indexPatterns.ILLEGAL_CHARACTERS]; this.lastQuery = null; } @@ -243,7 +243,7 @@ export class StepIndexPattern extends Component { if (!query || !query.length || query === '.' || query === '..') { // This is an error scenario but do not report an error containsErrors = true; - } else if (containsIllegalCharacters(query, ILLEGAL_CHARACTERS)) { + } else if (containsIllegalCharacters(query, indexPatterns.ILLEGAL_CHARACTERS)) { const errorMessage = i18n.translate( 'kbn.management.createIndexPattern.step.invalidCharactersErrorMessage', { diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/_view.js b/src/legacy/core_plugins/kibana/public/management/sections/objects/_view.js index 0909b6947895c..f7e654fd3c76d 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/_view.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/_view.js @@ -29,7 +29,7 @@ import { uiModules } from 'ui/modules'; import { fatalError, toastNotifications } from 'ui/notify'; import 'ui/accessibility/kbn_ui_ace_keyboard_mode'; import { SavedObjectsClientProvider } from 'ui/saved_objects'; -import { isNumeric } from 'ui/utils/numeric'; +import { isNumeric } from './lib/numeric'; import { canViewInApp } from './lib/in_app_url'; import { castEsToKbnFieldTypeName } from '../../../../../../../plugins/data/public'; diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/case_conversion.test.ts b/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/case_conversion.test.ts new file mode 100644 index 0000000000000..bb749de8dcb71 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/case_conversion.test.ts @@ -0,0 +1,36 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { keysToCamelCaseShallow } from './case_conversion'; + +describe('keysToCamelCaseShallow', () => { + test("should convert all of an object's keys to camel case", () => { + const data = { + camelCase: 'camelCase', + 'kebab-case': 'kebabCase', + snake_case: 'snakeCase', + }; + + const result = keysToCamelCaseShallow(data); + + expect(result.camelCase).toBe('camelCase'); + expect(result.kebabCase).toBe('kebabCase'); + expect(result.snakeCase).toBe('snakeCase'); + }); +}); diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/case_conversion.ts b/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/case_conversion.ts new file mode 100644 index 0000000000000..718530eb3b602 --- /dev/null +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/case_conversion.ts @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { mapKeys, camelCase } from 'lodash'; + +export function keysToCamelCaseShallow(object: Record) { + return mapKeys(object, (value, key) => camelCase(key)); +} diff --git a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/find_objects.js b/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/find_objects.js index f982966d1e314..caf2b5f503440 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/find_objects.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/find_objects.js @@ -18,7 +18,7 @@ */ import { kfetch } from 'ui/kfetch'; -import { keysToCamelCaseShallow } from 'ui/utils/case_conversion'; +import { keysToCamelCaseShallow } from './case_conversion'; export async function findObjects(findOptions) { const response = await kfetch({ @@ -26,5 +26,6 @@ export async function findObjects(findOptions) { pathname: '/api/kibana/management/saved_objects/_find', query: findOptions, }); + return keysToCamelCaseShallow(response); } diff --git a/src/legacy/ui/public/utils/numeric.ts b/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/numeric.ts similarity index 86% rename from src/legacy/ui/public/utils/numeric.ts rename to src/legacy/core_plugins/kibana/public/management/sections/objects/lib/numeric.ts index 1342498cf5dc3..c7bc6c26a378f 100644 --- a/src/legacy/ui/public/utils/numeric.ts +++ b/src/legacy/core_plugins/kibana/public/management/sections/objects/lib/numeric.ts @@ -17,8 +17,8 @@ * under the License. */ -import _ from 'lodash'; +import { isNaN } from 'lodash'; export function isNumeric(v: any): boolean { - return !_.isNaN(v) && (typeof v === 'number' || (!Array.isArray(v) && !_.isNaN(parseFloat(v)))); + return !isNaN(v) && (typeof v === 'number' || (!Array.isArray(v) && !isNaN(parseFloat(v)))); } diff --git a/src/legacy/core_plugins/kibana/public/management/sections/settings/__snapshots__/advanced_settings.test.js.snap b/src/legacy/core_plugins/kibana/public/management/sections/settings/__snapshots__/advanced_settings.test.js.snap index 10d165d0d69c4..eef8f3fc93d90 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/settings/__snapshots__/advanced_settings.test.js.snap +++ b/src/legacy/core_plugins/kibana/public/management/sections/settings/__snapshots__/advanced_settings.test.js.snap @@ -60,6 +60,7 @@ exports[`AdvancedSettings should render normally 1`] = ` "defVal": Array [ "default_value", ], + "deprecation": undefined, "description": "Description for Test array setting", "displayName": "Test array setting", "isCustom": undefined, @@ -79,6 +80,7 @@ exports[`AdvancedSettings should render normally 1`] = ` "elasticsearch", ], "defVal": true, + "deprecation": undefined, "description": "Description for Test boolean setting", "displayName": "Test boolean setting", "isCustom": undefined, @@ -100,6 +102,7 @@ exports[`AdvancedSettings should render normally 1`] = ` "general", ], "defVal": null, + "deprecation": undefined, "description": "Description for Test custom string setting", "displayName": "Test custom string setting", "isCustom": undefined, @@ -119,6 +122,7 @@ exports[`AdvancedSettings should render normally 1`] = ` "general", ], "defVal": null, + "deprecation": undefined, "description": "Description for Test image setting", "displayName": "Test image setting", "isCustom": undefined, @@ -140,6 +144,7 @@ exports[`AdvancedSettings should render normally 1`] = ` "defVal": "{ \\"foo\\": \\"bar\\" }", + "deprecation": undefined, "description": "Description for overridden json", "displayName": "An overridden json", "isCustom": undefined, @@ -159,6 +164,7 @@ exports[`AdvancedSettings should render normally 1`] = ` "general", ], "defVal": 1234, + "deprecation": undefined, "description": "Description for overridden number", "displayName": "An overridden number", "isCustom": undefined, @@ -178,6 +184,7 @@ exports[`AdvancedSettings should render normally 1`] = ` "general", ], "defVal": "orange", + "deprecation": undefined, "description": "Description for overridden select setting", "displayName": "Test overridden select setting", "isCustom": undefined, @@ -201,6 +208,7 @@ exports[`AdvancedSettings should render normally 1`] = ` "general", ], "defVal": "foo", + "deprecation": undefined, "description": "Description for overridden string", "displayName": "An overridden string", "isCustom": undefined, @@ -220,6 +228,7 @@ exports[`AdvancedSettings should render normally 1`] = ` "general", ], "defVal": "{\\"foo\\": \\"bar\\"}", + "deprecation": undefined, "description": "Description for Test json setting", "displayName": "Test json setting", "isCustom": undefined, @@ -239,6 +248,7 @@ exports[`AdvancedSettings should render normally 1`] = ` "general", ], "defVal": "", + "deprecation": undefined, "description": "Description for Test markdown setting", "displayName": "Test markdown setting", "isCustom": undefined, @@ -258,6 +268,7 @@ exports[`AdvancedSettings should render normally 1`] = ` "general", ], "defVal": 5, + "deprecation": undefined, "description": "Description for Test number setting", "displayName": "Test number setting", "isCustom": undefined, @@ -277,6 +288,7 @@ exports[`AdvancedSettings should render normally 1`] = ` "general", ], "defVal": "orange", + "deprecation": undefined, "description": "Description for Test select setting", "displayName": "Test select setting", "isCustom": undefined, @@ -300,6 +312,7 @@ exports[`AdvancedSettings should render normally 1`] = ` "general", ], "defVal": null, + "deprecation": undefined, "description": "Description for Test string setting", "displayName": "Test string setting", "isCustom": undefined, @@ -345,6 +358,7 @@ exports[`AdvancedSettings should render normally 1`] = ` "defVal": Array [ "default_value", ], + "deprecation": undefined, "description": "Description for Test array setting", "displayName": "Test array setting", "isCustom": undefined, @@ -364,6 +378,7 @@ exports[`AdvancedSettings should render normally 1`] = ` "elasticsearch", ], "defVal": true, + "deprecation": undefined, "description": "Description for Test boolean setting", "displayName": "Test boolean setting", "isCustom": undefined, @@ -385,6 +400,7 @@ exports[`AdvancedSettings should render normally 1`] = ` "general", ], "defVal": null, + "deprecation": undefined, "description": "Description for Test custom string setting", "displayName": "Test custom string setting", "isCustom": undefined, @@ -404,6 +420,7 @@ exports[`AdvancedSettings should render normally 1`] = ` "general", ], "defVal": null, + "deprecation": undefined, "description": "Description for Test image setting", "displayName": "Test image setting", "isCustom": undefined, @@ -425,6 +442,7 @@ exports[`AdvancedSettings should render normally 1`] = ` "defVal": "{ \\"foo\\": \\"bar\\" }", + "deprecation": undefined, "description": "Description for overridden json", "displayName": "An overridden json", "isCustom": undefined, @@ -444,6 +462,7 @@ exports[`AdvancedSettings should render normally 1`] = ` "general", ], "defVal": 1234, + "deprecation": undefined, "description": "Description for overridden number", "displayName": "An overridden number", "isCustom": undefined, @@ -463,6 +482,7 @@ exports[`AdvancedSettings should render normally 1`] = ` "general", ], "defVal": "orange", + "deprecation": undefined, "description": "Description for overridden select setting", "displayName": "Test overridden select setting", "isCustom": undefined, @@ -486,6 +506,7 @@ exports[`AdvancedSettings should render normally 1`] = ` "general", ], "defVal": "foo", + "deprecation": undefined, "description": "Description for overridden string", "displayName": "An overridden string", "isCustom": undefined, @@ -505,6 +526,7 @@ exports[`AdvancedSettings should render normally 1`] = ` "general", ], "defVal": "{\\"foo\\": \\"bar\\"}", + "deprecation": undefined, "description": "Description for Test json setting", "displayName": "Test json setting", "isCustom": undefined, @@ -524,6 +546,7 @@ exports[`AdvancedSettings should render normally 1`] = ` "general", ], "defVal": "", + "deprecation": undefined, "description": "Description for Test markdown setting", "displayName": "Test markdown setting", "isCustom": undefined, @@ -543,6 +566,7 @@ exports[`AdvancedSettings should render normally 1`] = ` "general", ], "defVal": 5, + "deprecation": undefined, "description": "Description for Test number setting", "displayName": "Test number setting", "isCustom": undefined, @@ -562,6 +586,7 @@ exports[`AdvancedSettings should render normally 1`] = ` "general", ], "defVal": "orange", + "deprecation": undefined, "description": "Description for Test select setting", "displayName": "Test select setting", "isCustom": undefined, @@ -585,6 +610,7 @@ exports[`AdvancedSettings should render normally 1`] = ` "general", ], "defVal": null, + "deprecation": undefined, "description": "Description for Test string setting", "displayName": "Test string setting", "isCustom": undefined, @@ -705,6 +731,7 @@ exports[`AdvancedSettings should render read-only when saving is disabled 1`] = "general", ], "defVal": null, + "deprecation": undefined, "description": "Description for Test string setting", "displayName": "Test string setting", "isCustom": undefined, @@ -748,6 +775,7 @@ exports[`AdvancedSettings should render read-only when saving is disabled 1`] = "general", ], "defVal": null, + "deprecation": undefined, "description": "Description for Test string setting", "displayName": "Test string setting", "isCustom": undefined, @@ -886,6 +914,7 @@ exports[`AdvancedSettings should render specific setting if given setting key 1` "general", ], "defVal": null, + "deprecation": undefined, "description": "Description for Test string setting", "displayName": "Test string setting", "isCustom": undefined, @@ -929,6 +958,7 @@ exports[`AdvancedSettings should render specific setting if given setting key 1` "general", ], "defVal": null, + "deprecation": undefined, "description": "Description for Test string setting", "displayName": "Test string setting", "isCustom": undefined, diff --git a/src/legacy/core_plugins/kibana/public/management/sections/settings/components/field/field.js b/src/legacy/core_plugins/kibana/public/management/sections/settings/components/field/field.js index 939dc8c20e465..a2f201cf757f5 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/settings/components/field/field.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/settings/components/field/field.js @@ -19,12 +19,14 @@ import React, { PureComponent, Fragment } from 'react'; import PropTypes from 'prop-types'; +import { npStart } from 'ui/new_platform'; import 'brace/theme/textmate'; import 'brace/mode/markdown'; import { toastNotifications } from 'ui/notify'; import { + EuiBadge, EuiButton, EuiButtonEmpty, EuiCode, @@ -41,6 +43,7 @@ import { EuiImage, EuiLink, EuiSpacer, + EuiToolTip, EuiText, EuiSelect, EuiSwitch, @@ -224,7 +227,7 @@ export class Field extends PureComponent { } const file = files[0]; - const { maxSize } = this.props.setting.options; + const { maxSize } = this.props.setting.validation; try { const base64Image = await this.getImageAsBase64(file); const isInvalid = !!(maxSize && maxSize.length && base64Image.length > maxSize.length); @@ -565,6 +568,36 @@ export class Field extends PureComponent { renderDescription(setting) { let description; + let deprecation; + + if (setting.deprecation) { + const { links } = npStart.core.docLinks; + + deprecation = ( + <> + + { + window.open(links.management[setting.deprecation.docLinksKey], '_blank'); + }} + onClickAriaLabel={i18n.translate( + 'kbn.management.settings.field.deprecationClickAreaLabel', + { + defaultMessage: 'Click to view deprecation documentation for {settingName}.', + values: { + settingName: setting.name, + }, + } + )} + > + Deprecated + + + + + ); + } if (React.isValidElement(setting.description)) { description = setting.description; @@ -582,6 +615,7 @@ export class Field extends PureComponent { return ( + {deprecation} {description} {this.renderDefaultValue(setting)} diff --git a/src/legacy/core_plugins/kibana/public/management/sections/settings/components/field/field.test.js b/src/legacy/core_plugins/kibana/public/management/sections/settings/components/field/field.test.js index 74bb0e25ff52e..07ce6f84d2bb6 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/settings/components/field/field.test.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/settings/components/field/field.test.js @@ -72,10 +72,9 @@ const settings = { defVal: null, isCustom: false, isOverridden: false, - options: { + validation: { maxSize: { length: 1000, - displayName: '1 kB', description: 'Description for 1 kB', }, }, diff --git a/src/legacy/core_plugins/kibana/public/management/sections/settings/lib/get_category_name.js b/src/legacy/core_plugins/kibana/public/management/sections/settings/lib/get_category_name.js index 0155b10f60218..c64b332e8ebee 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/settings/lib/get_category_name.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/settings/lib/get_category_name.js @@ -17,9 +17,10 @@ * under the License. */ -import { StringUtils } from 'ui/utils/string_utils'; import { i18n } from '@kbn/i18n'; +const upperFirst = (str = '') => str.replace(/^./, str => str.toUpperCase()); + const names = { general: i18n.translate('kbn.management.settings.categoryNames.generalLabel', { defaultMessage: 'General', @@ -51,5 +52,5 @@ const names = { }; export function getCategoryName(category) { - return category ? names[category] || StringUtils.upperFirst(category) : ''; + return category ? names[category] || upperFirst(category) : ''; } diff --git a/src/legacy/core_plugins/kibana/public/management/sections/settings/lib/to_editable_config.js b/src/legacy/core_plugins/kibana/public/management/sections/settings/lib/to_editable_config.js index 791f9e400b407..6efb89cfba2b2 100644 --- a/src/legacy/core_plugins/kibana/public/management/sections/settings/lib/to_editable_config.js +++ b/src/legacy/core_plugins/kibana/public/management/sections/settings/lib/to_editable_config.js @@ -43,12 +43,14 @@ export function toEditableConfig({ def, name, value, isCustom, isOverridden }) { defVal: def.value, type: getValType(def, value), description: def.description, - validation: def.validation - ? { - regex: new RegExp(def.validation.regexString), - message: def.validation.message, - } - : undefined, + deprecation: def.deprecation, + validation: + def.validation && def.validation.regexString + ? { + regex: new RegExp(def.validation.regexString), + message: def.validation.message, + } + : def.validation, options: def.options, optionLabels: def.optionLabels, requiresPageReload: !!def.requiresPageReload, diff --git a/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/editor.js b/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/editor.js index ed9bec9db4112..64653730473cd 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/editor.js +++ b/src/legacy/core_plugins/kibana/public/visualize/np_ready/editor/editor.js @@ -35,8 +35,8 @@ import { unhashUrl } from '../../../../../../../plugins/kibana_utils/public'; import { initVisEditorDirective } from './visualization_editor'; import { initVisualizationDirective } from './visualization'; - import { + VISUALIZE_EMBEDDABLE_TYPE, subscribeWithScope, absoluteToParsedUrl, KibanaParsedUrl, @@ -588,7 +588,11 @@ function VisualizeAppController( getBasePath() ); dashboardParsedUrl.addQueryParameter( - DashboardConstants.NEW_VISUALIZATION_ID_PARAM, + DashboardConstants.ADD_EMBEDDABLE_TYPE, + VISUALIZE_EMBEDDABLE_TYPE + ); + dashboardParsedUrl.addQueryParameter( + DashboardConstants.ADD_EMBEDDABLE_ID, savedVis.id ); kbnUrl.change(dashboardParsedUrl.appPath); diff --git a/src/legacy/core_plugins/kibana/public/visualize/np_ready/listing/visualize_listing_table.js b/src/legacy/core_plugins/kibana/public/visualize/np_ready/listing/visualize_listing_table.js index 840e647edcc86..b770625cd3d70 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/np_ready/listing/visualize_listing_table.js +++ b/src/legacy/core_plugins/kibana/public/visualize/np_ready/listing/visualize_listing_table.js @@ -36,6 +36,7 @@ class VisualizeListingTable extends Component { const { visualizeCapabilities, uiSettings, toastNotifications } = getServices(); return ( +

-

+ } />
@@ -130,12 +131,12 @@ class VisualizeListingTable extends Component { +

-

+ } body={ diff --git a/src/legacy/core_plugins/kibana/public/visualize/np_ready/wizard/new_vis_modal.test.tsx b/src/legacy/core_plugins/kibana/public/visualize/np_ready/wizard/new_vis_modal.test.tsx index 2005133e6d03e..0ef1b711eafc8 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/np_ready/wizard/new_vis_modal.test.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/np_ready/wizard/new_vis_modal.test.tsx @@ -144,7 +144,7 @@ describe('NewVisModal', () => { expect(window.location.assign).toBeCalledWith('#/visualize/create?type=vis&foo=true&bar=42'); }); - it('closes if visualization with aliasUrl and addToDashboard in editorParams', () => { + it('closes and redirects properly if visualization with aliasUrl and addToDashboard in editorParams', () => { const onClose = jest.fn(); window.location.assign = jest.fn(); const wrapper = mountWithIntl( @@ -160,7 +160,7 @@ describe('NewVisModal', () => { ); const visButton = wrapper.find('button[data-test-subj="visType-visWithAliasUrl"]'); visButton.simulate('click'); - expect(window.location.assign).toBeCalledWith('testbasepath/aliasUrl'); + expect(window.location.assign).toBeCalledWith('testbasepath/aliasUrl?addToDashboard'); expect(onClose).toHaveBeenCalled(); }); }); diff --git a/src/legacy/core_plugins/kibana/public/visualize/np_ready/wizard/new_vis_modal.tsx b/src/legacy/core_plugins/kibana/public/visualize/np_ready/wizard/new_vis_modal.tsx index 9e8f46407f591..082fc3bc36b6b 100644 --- a/src/legacy/core_plugins/kibana/public/visualize/np_ready/wizard/new_vis_modal.tsx +++ b/src/legacy/core_plugins/kibana/public/visualize/np_ready/wizard/new_vis_modal.tsx @@ -143,15 +143,18 @@ class NewVisModal extends React.Component; + +export async function getTranslationCount(loader: any, locale: string): Promise { + const translations = await loader.getTranslationsByLocale(locale); + return size(translations.messages); +} + +export function createCollectorFetch(server: Server) { + return async function fetchUsageStats(): Promise { + const internalRepo = server.newPlatform.setup.core.savedObjects.createInternalRepository(); + const uiSettingsClient = server.newPlatform.start.core.uiSettings.asScopedToClient( + new SavedObjectsClient(internalRepo) + ); + + const user = await uiSettingsClient.getUserProvided(); + const modifiedEntries = Object.keys(user) + .filter((key: string) => key !== 'buildNum') + .reduce((obj: any, key: string) => { + obj[key] = user[key].userValue; + return obj; + }, {}); + + return modifiedEntries; + }; +} + +export function registerManagementUsageCollector( + usageCollection: UsageCollectionSetup, + server: any +) { + const collector = usageCollection.makeUsageCollector({ + type: KIBANA_MANAGEMENT_STATS_TYPE, + isReady: () => true, + fetch: createCollectorFetch(server), + }); + + usageCollection.registerCollector(collector); +} diff --git a/src/legacy/core_plugins/telemetry/server/plugin.ts b/src/legacy/core_plugins/telemetry/server/plugin.ts index 06a974f473498..b5b53b1daba55 100644 --- a/src/legacy/core_plugins/telemetry/server/plugin.ts +++ b/src/legacy/core_plugins/telemetry/server/plugin.ts @@ -27,6 +27,7 @@ import { registerTelemetryUsageCollector, registerLocalizationUsageCollector, registerTelemetryPluginUsageCollector, + registerManagementUsageCollector, } from './collectors'; export interface PluginsSetup { @@ -50,5 +51,6 @@ export class TelemetryPlugin { registerLocalizationUsageCollector(usageCollection, server); registerTelemetryUsageCollector(usageCollection, server); registerUiMetricUsageCollector(usageCollection, server); + registerManagementUsageCollector(usageCollection, server); } } diff --git a/src/legacy/core_plugins/tile_map/public/components/tile_map_options.tsx b/src/legacy/core_plugins/tile_map/public/components/tile_map_options.tsx index a3ebda8b3df0c..15c92f4617497 100644 --- a/src/legacy/core_plugins/tile_map/public/components/tile_map_options.tsx +++ b/src/legacy/core_plugins/tile_map/public/components/tile_map_options.tsx @@ -41,7 +41,7 @@ function TileMapOptions(props: TileMapOptionsProps) { if (!stateParams.mapType) { setValue('mapType', vis.type.editorConfig.collections.mapTypes[0]); } - }, []); + }, [setValue, stateParams.mapType, vis.type.editorConfig.collections.mapTypes]); return ( <> diff --git a/src/legacy/core_plugins/tile_map/public/css_filters.js b/src/legacy/core_plugins/tile_map/public/css_filters.js new file mode 100644 index 0000000000000..63d6a358059b3 --- /dev/null +++ b/src/legacy/core_plugins/tile_map/public/css_filters.js @@ -0,0 +1,42 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import _ from 'lodash'; + +/** + * just a place to put feature detection checks + */ +export const supportsCssFilters = (function() { + const e = document.createElement('img'); + const rules = ['webkitFilter', 'mozFilter', 'msFilter', 'filter']; + const test = 'grayscale(1)'; + + rules.forEach(function(rule) { + e.style[rule] = test; + }); + + document.body.appendChild(e); + const styles = window.getComputedStyle(e); + const can = _(styles) + .pick(rules) + .includes(test); + document.body.removeChild(e); + + return can; +})(); diff --git a/src/legacy/core_plugins/tile_map/public/tile_map_type.js b/src/legacy/core_plugins/tile_map/public/tile_map_type.js index f2354614ac41a..f2e6469e768e7 100644 --- a/src/legacy/core_plugins/tile_map/public/tile_map_type.js +++ b/src/legacy/core_plugins/tile_map/public/tile_map_type.js @@ -20,7 +20,6 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; -import { supports } from 'ui/utils/supports'; import { Schemas } from 'ui/vis/editors/default/schemas'; import { colorSchemas } from 'ui/vislib/components/color/truncated_colormaps'; import { convertToGeoJson } from 'ui/vis/map/convert_to_geojson'; @@ -29,6 +28,7 @@ import { createTileMapVisualization } from './tile_map_visualization'; import { Status } from '../../visualizations/public'; import { TileMapOptions } from './components/tile_map_options'; import { MapTypes } from './map_types'; +import { supportsCssFilters } from './css_filters'; export function createTileMapTypeDefinition(dependencies) { const CoordinateMapsVisualization = createTileMapVisualization(dependencies); @@ -44,7 +44,7 @@ export function createTileMapTypeDefinition(dependencies) { defaultMessage: 'Plot latitude and longitude coordinates on a map', }), visConfig: { - canDesaturate: !!supports.cssFilters, + canDesaturate: Boolean(supportsCssFilters), defaults: { colorSchema: 'Yellow to Red', mapType: 'Scaled Circle Markers', diff --git a/src/legacy/core_plugins/timelion/common/types.ts b/src/legacy/core_plugins/timelion/common/types.ts new file mode 100644 index 0000000000000..f7084948a14f7 --- /dev/null +++ b/src/legacy/core_plugins/timelion/common/types.ts @@ -0,0 +1,46 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +type TimelionFunctionArgsTypes = 'seriesList' | 'number' | 'string' | 'boolean' | 'null'; + +interface TimelionFunctionArgsSuggestion { + name: string; + help: string; +} + +export interface TimelionFunctionArgs { + name: string; + help?: string; + multi?: boolean; + types: TimelionFunctionArgsTypes[]; + suggestions?: TimelionFunctionArgsSuggestion[]; +} + +export interface ITimelionFunction { + aliases: string[]; + args: TimelionFunctionArgs[]; + name: string; + help: string; + chainable: boolean; + extended: boolean; + isAlias: boolean; + argsByName: { + [key: string]: TimelionFunctionArgs[]; + }; +} diff --git a/src/legacy/core_plugins/timelion/public/components/_index.scss b/src/legacy/core_plugins/timelion/public/components/_index.scss new file mode 100644 index 0000000000000..f2458a367e176 --- /dev/null +++ b/src/legacy/core_plugins/timelion/public/components/_index.scss @@ -0,0 +1 @@ +@import './timelion_expression_input'; diff --git a/src/legacy/core_plugins/timelion/public/components/_timelion_expression_input.scss b/src/legacy/core_plugins/timelion/public/components/_timelion_expression_input.scss new file mode 100644 index 0000000000000..b1c0b5514ff7a --- /dev/null +++ b/src/legacy/core_plugins/timelion/public/components/_timelion_expression_input.scss @@ -0,0 +1,18 @@ +.timExpressionInput { + flex: 1 1 auto; + display: flex; + flex-direction: column; + margin-top: $euiSize; +} + +.timExpressionInput__editor { + height: 100%; + padding-top: $euiSizeS; +} + +@include euiBreakpoint('xs', 's', 'm') { + .timExpressionInput__editor { + height: $euiSize * 15; + max-height: $euiSize * 15; + } +} diff --git a/src/legacy/core_plugins/timelion/public/components/index.ts b/src/legacy/core_plugins/timelion/public/components/index.ts new file mode 100644 index 0000000000000..8d7d32a3ba262 --- /dev/null +++ b/src/legacy/core_plugins/timelion/public/components/index.ts @@ -0,0 +1,21 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export * from './timelion_expression_input'; +export * from './timelion_interval'; diff --git a/src/legacy/core_plugins/timelion/public/components/timelion_expression_input.tsx b/src/legacy/core_plugins/timelion/public/components/timelion_expression_input.tsx new file mode 100644 index 0000000000000..c695d09ca822b --- /dev/null +++ b/src/legacy/core_plugins/timelion/public/components/timelion_expression_input.tsx @@ -0,0 +1,146 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React, { useEffect, useCallback, useRef, useMemo } from 'react'; +import { EuiFormLabel } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api'; + +import { CodeEditor, useKibana } from '../../../../../plugins/kibana_react/public'; +import { suggest, getSuggestion } from './timelion_expression_input_helpers'; +import { ITimelionFunction, TimelionFunctionArgs } from '../../common/types'; +import { getArgValueSuggestions } from '../services/arg_value_suggestions'; + +const LANGUAGE_ID = 'timelion_expression'; +monacoEditor.languages.register({ id: LANGUAGE_ID }); + +interface TimelionExpressionInputProps { + value: string; + setValue(value: string): void; +} + +function TimelionExpressionInput({ value, setValue }: TimelionExpressionInputProps) { + const functionList = useRef([]); + const kibana = useKibana(); + const argValueSuggestions = useMemo(getArgValueSuggestions, []); + + const provideCompletionItems = useCallback( + async (model: monacoEditor.editor.ITextModel, position: monacoEditor.Position) => { + const text = model.getValue(); + const wordUntil = model.getWordUntilPosition(position); + const wordRange = new monacoEditor.Range( + position.lineNumber, + wordUntil.startColumn, + position.lineNumber, + wordUntil.endColumn + ); + + const suggestions = await suggest( + text, + functionList.current, + // it's important to offset the cursor position on 1 point left + // because of PEG parser starts the line with 0, but monaco with 1 + position.column - 1, + argValueSuggestions + ); + + return { + suggestions: suggestions + ? suggestions.list.map((s: ITimelionFunction | TimelionFunctionArgs) => + getSuggestion(s, suggestions.type, wordRange) + ) + : [], + }; + }, + [argValueSuggestions] + ); + + const provideHover = useCallback( + async (model: monacoEditor.editor.ITextModel, position: monacoEditor.Position) => { + const suggestions = await suggest( + model.getValue(), + functionList.current, + // it's important to offset the cursor position on 1 point left + // because of PEG parser starts the line with 0, but monaco with 1 + position.column - 1, + argValueSuggestions + ); + + return { + contents: suggestions + ? suggestions.list.map((s: ITimelionFunction | TimelionFunctionArgs) => ({ + value: s.help, + })) + : [], + }; + }, + [argValueSuggestions] + ); + + useEffect(() => { + if (kibana.services.http) { + kibana.services.http.get('../api/timelion/functions').then(data => { + functionList.current = data; + }); + } + }, [kibana.services.http]); + + return ( +
+ + + +
+ +
+
+ ); +} + +export { TimelionExpressionInput }; diff --git a/src/legacy/core_plugins/timelion/public/components/timelion_expression_input_helpers.ts b/src/legacy/core_plugins/timelion/public/components/timelion_expression_input_helpers.ts new file mode 100644 index 0000000000000..fc90c276eeca2 --- /dev/null +++ b/src/legacy/core_plugins/timelion/public/components/timelion_expression_input_helpers.ts @@ -0,0 +1,287 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { get, startsWith } from 'lodash'; +import PEG from 'pegjs'; +import * as monacoEditor from 'monaco-editor/esm/vs/editor/editor.api'; + +// @ts-ignore +import grammar from 'raw-loader!../chain.peg'; + +import { i18n } from '@kbn/i18n'; +import { ITimelionFunction, TimelionFunctionArgs } from '../../common/types'; +import { ArgValueSuggestions, FunctionArg, Location } from '../services/arg_value_suggestions'; + +const Parser = PEG.generate(grammar); + +export enum SUGGESTION_TYPE { + ARGUMENTS = 'arguments', + ARGUMENT_VALUE = 'argument_value', + FUNCTIONS = 'functions', +} + +function inLocation(cursorPosition: number, location: Location) { + return cursorPosition >= location.min && cursorPosition <= location.max; +} + +function getArgumentsHelp( + functionHelp: ITimelionFunction | undefined, + functionArgs: FunctionArg[] = [] +) { + if (!functionHelp) { + return []; + } + + // Do not provide 'inputSeries' as argument suggestion for chainable functions + const argsHelp = functionHelp.chainable ? functionHelp.args.slice(1) : functionHelp.args.slice(0); + + // ignore arguments that are already provided in function declaration + const functionArgNames = functionArgs.map(arg => arg.name); + return argsHelp.filter(arg => !functionArgNames.includes(arg.name)); +} + +async function extractSuggestionsFromParsedResult( + result: ReturnType, + cursorPosition: number, + functionList: ITimelionFunction[], + argValueSuggestions: ArgValueSuggestions +) { + const activeFunc = result.functions.find(({ location }: { location: Location }) => + inLocation(cursorPosition, location) + ); + + if (!activeFunc) { + return; + } + + const functionHelp = functionList.find(({ name }) => name === activeFunc.function); + + if (!functionHelp) { + return; + } + + // return function suggestion when cursor is outside of parentheses + // location range includes '.', function name, and '('. + const openParen = activeFunc.location.min + activeFunc.function.length + 2; + if (cursorPosition < openParen) { + return { list: [functionHelp], type: SUGGESTION_TYPE.FUNCTIONS }; + } + + // return argument value suggestions when cursor is inside argument value + const activeArg = activeFunc.arguments.find((argument: FunctionArg) => { + return inLocation(cursorPosition, argument.location); + }); + if ( + activeArg && + activeArg.type === 'namedArg' && + inLocation(cursorPosition, activeArg.value.location) + ) { + const { function: functionName, arguments: functionArgs } = activeFunc; + + const { + name: argName, + value: { text: partialInput }, + } = activeArg; + + let valueSuggestions; + if (argValueSuggestions.hasDynamicSuggestionsForArgument(functionName, argName)) { + valueSuggestions = await argValueSuggestions.getDynamicSuggestionsForArgument( + functionName, + argName, + functionArgs, + partialInput + ); + } else { + const { suggestions: staticSuggestions } = + functionHelp.args.find(arg => arg.name === activeArg.name) || {}; + valueSuggestions = argValueSuggestions.getStaticSuggestionsForInput( + partialInput, + staticSuggestions + ); + } + return { + list: valueSuggestions, + type: SUGGESTION_TYPE.ARGUMENT_VALUE, + }; + } + + // return argument suggestions + const argsHelp = getArgumentsHelp(functionHelp, activeFunc.arguments); + const argumentSuggestions = argsHelp.filter(arg => { + if (get(activeArg, 'type') === 'namedArg') { + return startsWith(arg.name, activeArg.name); + } else if (activeArg) { + return startsWith(arg.name, activeArg.text); + } + return true; + }); + return { list: argumentSuggestions, type: SUGGESTION_TYPE.ARGUMENTS }; +} + +export async function suggest( + expression: string, + functionList: ITimelionFunction[], + cursorPosition: number, + argValueSuggestions: ArgValueSuggestions +) { + try { + const result = await Parser.parse(expression); + + return await extractSuggestionsFromParsedResult( + result, + cursorPosition, + functionList, + argValueSuggestions + ); + } catch (err) { + let message: any; + try { + // The grammar will throw an error containing a message if the expression is formatted + // correctly and is prepared to accept suggestions. If the expression is not formatted + // correctly the grammar will just throw a regular PEG SyntaxError, and this JSON.parse + // attempt will throw an error. + message = JSON.parse(err.message); + } catch (e) { + // The expression isn't correctly formatted, so JSON.parse threw an error. + return; + } + + switch (message.type) { + case 'incompleteFunction': { + let list; + if (message.function) { + // The user has start typing a function name, so we'll filter the list down to only + // possible matches. + list = functionList.filter(func => startsWith(func.name, message.function)); + } else { + // The user hasn't typed anything yet, so we'll just return the entire list. + list = functionList; + } + return { list, type: SUGGESTION_TYPE.FUNCTIONS }; + } + case 'incompleteArgument': { + const { currentFunction: functionName, currentArgs: functionArgs } = message; + const functionHelp = functionList.find(func => func.name === functionName); + return { + list: getArgumentsHelp(functionHelp, functionArgs), + type: SUGGESTION_TYPE.ARGUMENTS, + }; + } + case 'incompleteArgumentValue': { + const { name: argName, currentFunction: functionName, currentArgs: functionArgs } = message; + let valueSuggestions = []; + if (argValueSuggestions.hasDynamicSuggestionsForArgument(functionName, argName)) { + valueSuggestions = await argValueSuggestions.getDynamicSuggestionsForArgument( + functionName, + argName, + functionArgs + ); + } else { + const functionHelp = functionList.find(func => func.name === functionName); + if (functionHelp) { + const argHelp = functionHelp.args.find(arg => arg.name === argName); + if (argHelp && argHelp.suggestions) { + valueSuggestions = argHelp.suggestions; + } + } + } + return { + list: valueSuggestions, + type: SUGGESTION_TYPE.ARGUMENT_VALUE, + }; + } + } + } +} + +export function getSuggestion( + suggestion: ITimelionFunction | TimelionFunctionArgs, + type: SUGGESTION_TYPE, + range: monacoEditor.Range +): monacoEditor.languages.CompletionItem { + let kind: monacoEditor.languages.CompletionItemKind = + monacoEditor.languages.CompletionItemKind.Method; + let insertText: string = suggestion.name; + let insertTextRules: monacoEditor.languages.CompletionItem['insertTextRules']; + let detail: string = ''; + let command: monacoEditor.languages.CompletionItem['command']; + + switch (type) { + case SUGGESTION_TYPE.ARGUMENTS: + command = { + title: 'Trigger Suggestion Dialog', + id: 'editor.action.triggerSuggest', + }; + kind = monacoEditor.languages.CompletionItemKind.Property; + insertText = `${insertText}=`; + detail = `${i18n.translate( + 'timelion.expressionSuggestions.argument.description.acceptsText', + { + defaultMessage: 'Accepts', + } + )}: ${(suggestion as TimelionFunctionArgs).types}`; + + break; + case SUGGESTION_TYPE.FUNCTIONS: + command = { + title: 'Trigger Suggestion Dialog', + id: 'editor.action.triggerSuggest', + }; + kind = monacoEditor.languages.CompletionItemKind.Function; + insertText = `${insertText}($0)`; + insertTextRules = monacoEditor.languages.CompletionItemInsertTextRule.InsertAsSnippet; + detail = `(${ + (suggestion as ITimelionFunction).chainable + ? i18n.translate('timelion.expressionSuggestions.func.description.chainableHelpText', { + defaultMessage: 'Chainable', + }) + : i18n.translate('timelion.expressionSuggestions.func.description.dataSourceHelpText', { + defaultMessage: 'Data source', + }) + })`; + + break; + case SUGGESTION_TYPE.ARGUMENT_VALUE: + const param = suggestion.name.split(':'); + + if (param.length === 1 || param[1]) { + insertText = `${param.length === 1 ? insertText : param[1]},`; + } + + command = { + title: 'Trigger Suggestion Dialog', + id: 'editor.action.triggerSuggest', + }; + kind = monacoEditor.languages.CompletionItemKind.Property; + detail = suggestion.help || ''; + + break; + } + + return { + detail, + insertText, + insertTextRules, + kind, + label: suggestion.name, + documentation: suggestion.help, + command, + range, + }; +} diff --git a/src/legacy/core_plugins/timelion/public/components/timelion_interval.tsx b/src/legacy/core_plugins/timelion/public/components/timelion_interval.tsx new file mode 100644 index 0000000000000..6294e51e54788 --- /dev/null +++ b/src/legacy/core_plugins/timelion/public/components/timelion_interval.tsx @@ -0,0 +1,144 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React, { useMemo, useCallback } from 'react'; +import { EuiFormRow, EuiComboBox, EuiComboBoxOptionProps } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; + +import { useValidation } from 'ui/vis/editors/default/controls/agg_utils'; +import { isValidEsInterval } from '../../../../core_plugins/data/common'; + +const intervalOptions = [ + { + label: i18n.translate('timelion.vis.interval.auto', { + defaultMessage: 'Auto', + }), + value: 'auto', + }, + { + label: i18n.translate('timelion.vis.interval.second', { + defaultMessage: '1 second', + }), + value: '1s', + }, + { + label: i18n.translate('timelion.vis.interval.minute', { + defaultMessage: '1 minute', + }), + value: '1m', + }, + { + label: i18n.translate('timelion.vis.interval.hour', { + defaultMessage: '1 hour', + }), + value: '1h', + }, + { + label: i18n.translate('timelion.vis.interval.day', { + defaultMessage: '1 day', + }), + value: '1d', + }, + { + label: i18n.translate('timelion.vis.interval.week', { + defaultMessage: '1 week', + }), + value: '1w', + }, + { + label: i18n.translate('timelion.vis.interval.month', { + defaultMessage: '1 month', + }), + value: '1M', + }, + { + label: i18n.translate('timelion.vis.interval.year', { + defaultMessage: '1 year', + }), + value: '1y', + }, +]; + +interface TimelionIntervalProps { + value: string; + setValue(value: string): void; + setValidity(valid: boolean): void; +} + +function TimelionInterval({ value, setValue, setValidity }: TimelionIntervalProps) { + const onCustomInterval = useCallback( + (customValue: string) => { + setValue(customValue.trim()); + }, + [setValue] + ); + + const onChange = useCallback( + (opts: Array>) => { + setValue((opts[0] && opts[0].value) || ''); + }, + [setValue] + ); + + const selectedOptions = useMemo( + () => [intervalOptions.find(op => op.value === value) || { label: value, value }], + [value] + ); + + const isValid = intervalOptions.some(int => int.value === value) || isValidEsInterval(value); + + useValidation(setValidity, isValid); + + return ( + + + + ); +} + +export { TimelionInterval }; diff --git a/src/legacy/core_plugins/timelion/public/directives/__tests__/timelion_expression_input_helpers.js b/src/legacy/core_plugins/timelion/public/directives/__tests__/timelion_expression_input_helpers.js index b90f5932b5b09..231330b898edb 100644 --- a/src/legacy/core_plugins/timelion/public/directives/__tests__/timelion_expression_input_helpers.js +++ b/src/legacy/core_plugins/timelion/public/directives/__tests__/timelion_expression_input_helpers.js @@ -21,9 +21,15 @@ import expect from '@kbn/expect'; import PEG from 'pegjs'; import grammar from 'raw-loader!../../chain.peg'; import { SUGGESTION_TYPE, suggest } from '../timelion_expression_input_helpers'; -import { ArgValueSuggestionsProvider } from '../timelion_expression_suggestions/arg_value_suggestions'; +import { getArgValueSuggestions } from '../../services/arg_value_suggestions'; +import { setIndexPatterns, setSavedObjectsClient } from '../../services/plugin_services'; describe('Timelion expression suggestions', () => { + setIndexPatterns({}); + setSavedObjectsClient({}); + + const argValueSuggestions = getArgValueSuggestions(); + describe('getSuggestions', () => { const func1 = { name: 'func1', @@ -44,11 +50,6 @@ describe('Timelion expression suggestions', () => { }; const functionList = [func1, myFunc2]; let Parser; - const privateStub = () => { - return {}; - }; - const indexPatternsStub = {}; - const argValueSuggestions = ArgValueSuggestionsProvider(privateStub, indexPatternsStub); // eslint-disable-line new-cap beforeEach(function() { Parser = PEG.generate(grammar); }); diff --git a/src/legacy/core_plugins/timelion/public/directives/saved_object_finder.js b/src/legacy/core_plugins/timelion/public/directives/saved_object_finder.js index c0092ca49c4f3..111db0a83ffc4 100644 --- a/src/legacy/core_plugins/timelion/public/directives/saved_object_finder.js +++ b/src/legacy/core_plugins/timelion/public/directives/saved_object_finder.js @@ -19,12 +19,12 @@ import _ from 'lodash'; import rison from 'rison-node'; -import { keyMap } from 'ui/utils/key_map'; import { uiModules } from 'ui/modules'; import 'ui/directives/input_focus'; import 'ui/directives/paginate'; import savedObjectFinderTemplate from './saved_object_finder.html'; import { savedSheetLoader } from '../services/saved_sheets'; +import { keyMap } from 'ui/directives/key_map'; const module = uiModules.get('kibana'); diff --git a/src/legacy/core_plugins/timelion/public/directives/timelion_expression_input.js b/src/legacy/core_plugins/timelion/public/directives/timelion_expression_input.js index 137dd6b82046d..449c0489fea25 100644 --- a/src/legacy/core_plugins/timelion/public/directives/timelion_expression_input.js +++ b/src/legacy/core_plugins/timelion/public/directives/timelion_expression_input.js @@ -52,11 +52,11 @@ import { insertAtLocation, } from './timelion_expression_input_helpers'; import { comboBoxKeyCodes } from '@elastic/eui'; -import { ArgValueSuggestionsProvider } from './timelion_expression_suggestions/arg_value_suggestions'; +import { getArgValueSuggestions } from '../services/arg_value_suggestions'; const Parser = PEG.generate(grammar); -export function TimelionExpInput($http, $timeout, Private) { +export function TimelionExpInput($http, $timeout) { return { restrict: 'E', scope: { @@ -68,7 +68,7 @@ export function TimelionExpInput($http, $timeout, Private) { replace: true, template: timelionExpressionInputTemplate, link: function(scope, elem) { - const argValueSuggestions = Private(ArgValueSuggestionsProvider); + const argValueSuggestions = getArgValueSuggestions(); const expressionInput = elem.find('[data-expression-input]'); const functionReference = {}; let suggestibleFunctionLocation = {}; diff --git a/src/legacy/core_plugins/timelion/public/directives/timelion_expression_suggestions/arg_value_suggestions.js b/src/legacy/core_plugins/timelion/public/directives/timelion_expression_suggestions/arg_value_suggestions.js deleted file mode 100644 index e698a69401a37..0000000000000 --- a/src/legacy/core_plugins/timelion/public/directives/timelion_expression_suggestions/arg_value_suggestions.js +++ /dev/null @@ -1,188 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; -import { npStart } from 'ui/new_platform'; - -export function ArgValueSuggestionsProvider() { - const { indexPatterns } = npStart.plugins.data; - const { client: savedObjectsClient } = npStart.core.savedObjects; - - async function getIndexPattern(functionArgs) { - const indexPatternArg = functionArgs.find(argument => { - return argument.name === 'index'; - }); - if (!indexPatternArg) { - // index argument not provided - return; - } - const indexPatternTitle = _.get(indexPatternArg, 'value.text'); - - const resp = await savedObjectsClient.find({ - type: 'index-pattern', - fields: ['title'], - search: `"${indexPatternTitle}"`, - search_fields: ['title'], - perPage: 10, - }); - const indexPatternSavedObject = resp.savedObjects.find(savedObject => { - return savedObject.attributes.title === indexPatternTitle; - }); - if (!indexPatternSavedObject) { - // index argument does not match an index pattern - return; - } - - return await indexPatterns.get(indexPatternSavedObject.id); - } - - function containsFieldName(partial, field) { - if (!partial) { - return true; - } - return field.name.includes(partial); - } - - // Argument value suggestion handlers requiring custom client side code - // Could not put with function definition since functions are defined on server - const customHandlers = { - es: { - index: async function(partial) { - const search = partial ? `${partial}*` : '*'; - const resp = await savedObjectsClient.find({ - type: 'index-pattern', - fields: ['title', 'type'], - search: `${search}`, - search_fields: ['title'], - perPage: 25, - }); - return resp.savedObjects - .filter(savedObject => !savedObject.get('type')) - .map(savedObject => { - return { name: savedObject.attributes.title }; - }); - }, - metric: async function(partial, functionArgs) { - if (!partial || !partial.includes(':')) { - return [ - { name: 'avg:' }, - { name: 'cardinality:' }, - { name: 'count' }, - { name: 'max:' }, - { name: 'min:' }, - { name: 'percentiles:' }, - { name: 'sum:' }, - ]; - } - - const indexPattern = await getIndexPattern(functionArgs); - if (!indexPattern) { - return []; - } - - const valueSplit = partial.split(':'); - return indexPattern.fields - .filter(field => { - return ( - field.aggregatable && - 'number' === field.type && - containsFieldName(valueSplit[1], field) - ); - }) - .map(field => { - return { name: `${valueSplit[0]}:${field.name}`, help: field.type }; - }); - }, - split: async function(partial, functionArgs) { - const indexPattern = await getIndexPattern(functionArgs); - if (!indexPattern) { - return []; - } - - return indexPattern.fields - .filter(field => { - return ( - field.aggregatable && - ['number', 'boolean', 'date', 'ip', 'string'].includes(field.type) && - containsFieldName(partial, field) - ); - }) - .map(field => { - return { name: field.name, help: field.type }; - }); - }, - timefield: async function(partial, functionArgs) { - const indexPattern = await getIndexPattern(functionArgs); - if (!indexPattern) { - return []; - } - - return indexPattern.fields - .filter(field => { - return 'date' === field.type && containsFieldName(partial, field); - }) - .map(field => { - return { name: field.name }; - }); - }, - }, - }; - - return { - /** - * @param {string} functionName - user provided function name containing argument - * @param {string} argName - user provided argument name - * @return {boolean} true when dynamic suggestion handler provided for function argument - */ - hasDynamicSuggestionsForArgument: (functionName, argName) => { - return customHandlers[functionName] && customHandlers[functionName][argName]; - }, - - /** - * @param {string} functionName - user provided function name containing argument - * @param {string} argName - user provided argument name - * @param {object} functionArgs - user provided function arguments parsed ahead of current argument - * @param {string} partial - user provided argument value - * @return {array} array of dynamic suggestions matching partial - */ - getDynamicSuggestionsForArgument: async ( - functionName, - argName, - functionArgs, - partialInput = '' - ) => { - return await customHandlers[functionName][argName](partialInput, functionArgs); - }, - - /** - * @param {string} partial - user provided argument value - * @param {array} staticSuggestions - argument value suggestions - * @return {array} array of static suggestions matching partial - */ - getStaticSuggestionsForInput: (partialInput = '', staticSuggestions = []) => { - if (partialInput) { - return staticSuggestions.filter(suggestion => { - return suggestion.name.includes(partialInput); - }); - } - - return staticSuggestions; - }, - }; -} diff --git a/src/legacy/core_plugins/timelion/public/index.scss b/src/legacy/core_plugins/timelion/public/index.scss index f6123f4052156..7ccc6c300bc40 100644 --- a/src/legacy/core_plugins/timelion/public/index.scss +++ b/src/legacy/core_plugins/timelion/public/index.scss @@ -11,5 +11,6 @@ // timChart__legend-isLoading @import './app'; +@import './components/index'; @import './directives/index'; @import './vis/index'; diff --git a/src/legacy/core_plugins/timelion/public/legacy.ts b/src/legacy/core_plugins/timelion/public/legacy.ts index d989a68d40eeb..1cf6bb65cdc02 100644 --- a/src/legacy/core_plugins/timelion/public/legacy.ts +++ b/src/legacy/core_plugins/timelion/public/legacy.ts @@ -37,4 +37,4 @@ const setupPlugins: Readonly = { const pluginInstance = plugin({} as PluginInitializerContext); export const setup = pluginInstance.setup(npSetup.core, setupPlugins); -export const start = pluginInstance.start(npStart.core); +export const start = pluginInstance.start(npStart.core, npStart.plugins); diff --git a/src/legacy/core_plugins/timelion/public/panels/timechart/schema.ts b/src/legacy/core_plugins/timelion/public/panels/timechart/schema.ts index 04b27c4020ce3..0bbda4bf3646f 100644 --- a/src/legacy/core_plugins/timelion/public/panels/timechart/schema.ts +++ b/src/legacy/core_plugins/timelion/public/panels/timechart/schema.ts @@ -35,6 +35,7 @@ const DEBOUNCE_DELAY = 50; export function timechartFn(dependencies: TimelionVisualizationDependencies) { const { $rootScope, $compile, uiSettings } = dependencies; + return function() { return { help: 'Draw a timeseries chart', diff --git a/src/legacy/core_plugins/timelion/public/plugin.ts b/src/legacy/core_plugins/timelion/public/plugin.ts index ba8c25c20abea..42f0ee3ad4725 100644 --- a/src/legacy/core_plugins/timelion/public/plugin.ts +++ b/src/legacy/core_plugins/timelion/public/plugin.ts @@ -26,12 +26,14 @@ import { } from 'kibana/public'; import { Plugin as ExpressionsPlugin } from 'src/plugins/expressions/public'; import { DataPublicPluginSetup, TimefilterContract } from 'src/plugins/data/public'; +import { PluginsStart } from 'ui/new_platform/new_platform'; import { VisualizationsSetup } from '../../visualizations/public/np_ready/public'; import { getTimelionVisualizationConfig } from './timelion_vis_fn'; import { getTimelionVisualization } from './vis'; import { getTimeChart } from './panels/timechart/timechart'; import { Panel } from './panels/panel'; import { LegacyDependenciesPlugin, LegacyDependenciesPluginSetup } from './shim'; +import { setIndexPatterns, setSavedObjectsClient } from './services/plugin_services'; /** @internal */ export interface TimelionVisualizationDependencies extends LegacyDependenciesPluginSetup { @@ -85,12 +87,15 @@ export class TimelionPlugin implements Plugin, void> { dependencies.timelionPanels.set(timeChartPanel.name, timeChartPanel); } - public start(core: CoreStart) { + public start(core: CoreStart, plugins: PluginsStart) { const timelionUiEnabled = core.injectedMetadata.getInjectedVar('timelionUiEnabled'); if (timelionUiEnabled === false) { core.chrome.navLinks.update('timelion', { hidden: true }); } + + setIndexPatterns(plugins.data.indexPatterns); + setSavedObjectsClient(core.savedObjects.client); } public stop(): void {} diff --git a/src/legacy/core_plugins/timelion/public/services/arg_value_suggestions.ts b/src/legacy/core_plugins/timelion/public/services/arg_value_suggestions.ts new file mode 100644 index 0000000000000..8d133de51f6d9 --- /dev/null +++ b/src/legacy/core_plugins/timelion/public/services/arg_value_suggestions.ts @@ -0,0 +1,215 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { get } from 'lodash'; +import { TimelionFunctionArgs } from '../../common/types'; +import { getIndexPatterns, getSavedObjectsClient } from './plugin_services'; + +export interface Location { + min: number; + max: number; +} + +export interface FunctionArg { + function: string; + location: Location; + name: string; + text: string; + type: string; + value: { + location: Location; + text: string; + type: string; + value: string; + }; +} + +export function getArgValueSuggestions() { + const indexPatterns = getIndexPatterns(); + const savedObjectsClient = getSavedObjectsClient(); + + async function getIndexPattern(functionArgs: FunctionArg[]) { + const indexPatternArg = functionArgs.find(({ name }) => name === 'index'); + if (!indexPatternArg) { + // index argument not provided + return; + } + const indexPatternTitle = get(indexPatternArg, 'value.text'); + + const { savedObjects } = await savedObjectsClient.find({ + type: 'index-pattern', + fields: ['title'], + search: `"${indexPatternTitle}"`, + searchFields: ['title'], + perPage: 10, + }); + const indexPatternSavedObject = savedObjects.find( + ({ attributes }) => attributes.title === indexPatternTitle + ); + if (!indexPatternSavedObject) { + // index argument does not match an index pattern + return; + } + + return await indexPatterns.get(indexPatternSavedObject.id); + } + + function containsFieldName(partial: string, field: { name: string }) { + if (!partial) { + return true; + } + return field.name.includes(partial); + } + + // Argument value suggestion handlers requiring custom client side code + // Could not put with function definition since functions are defined on server + const customHandlers = { + es: { + async index(partial: string) { + const search = partial ? `${partial}*` : '*'; + const resp = await savedObjectsClient.find({ + type: 'index-pattern', + fields: ['title', 'type'], + search: `${search}`, + searchFields: ['title'], + perPage: 25, + }); + return resp.savedObjects + .filter(savedObject => !savedObject.get('type')) + .map(savedObject => { + return { name: savedObject.attributes.title }; + }); + }, + async metric(partial: string, functionArgs: FunctionArg[]) { + if (!partial || !partial.includes(':')) { + return [ + { name: 'avg:' }, + { name: 'cardinality:' }, + { name: 'count' }, + { name: 'max:' }, + { name: 'min:' }, + { name: 'percentiles:' }, + { name: 'sum:' }, + ]; + } + + const indexPattern = await getIndexPattern(functionArgs); + if (!indexPattern) { + return []; + } + + const valueSplit = partial.split(':'); + return indexPattern.fields + .filter(field => { + return ( + field.aggregatable && + 'number' === field.type && + containsFieldName(valueSplit[1], field) + ); + }) + .map(field => { + return { name: `${valueSplit[0]}:${field.name}`, help: field.type }; + }); + }, + async split(partial: string, functionArgs: FunctionArg[]) { + const indexPattern = await getIndexPattern(functionArgs); + if (!indexPattern) { + return []; + } + + return indexPattern.fields + .filter(field => { + return ( + field.aggregatable && + ['number', 'boolean', 'date', 'ip', 'string'].includes(field.type) && + containsFieldName(partial, field) + ); + }) + .map(field => { + return { name: field.name, help: field.type }; + }); + }, + async timefield(partial: string, functionArgs: FunctionArg[]) { + const indexPattern = await getIndexPattern(functionArgs); + if (!indexPattern) { + return []; + } + + return indexPattern.fields + .filter(field => { + return 'date' === field.type && containsFieldName(partial, field); + }) + .map(field => { + return { name: field.name }; + }); + }, + }, + }; + + return { + /** + * @param {string} functionName - user provided function name containing argument + * @param {string} argName - user provided argument name + * @return {boolean} true when dynamic suggestion handler provided for function argument + */ + hasDynamicSuggestionsForArgument: ( + functionName: T, + argName: keyof typeof customHandlers[T] + ) => { + return customHandlers[functionName] && customHandlers[functionName][argName]; + }, + + /** + * @param {string} functionName - user provided function name containing argument + * @param {string} argName - user provided argument name + * @param {object} functionArgs - user provided function arguments parsed ahead of current argument + * @param {string} partial - user provided argument value + * @return {array} array of dynamic suggestions matching partial + */ + getDynamicSuggestionsForArgument: async ( + functionName: T, + argName: keyof typeof customHandlers[T], + functionArgs: FunctionArg[], + partialInput = '' + ) => { + // @ts-ignore + return await customHandlers[functionName][argName](partialInput, functionArgs); + }, + + /** + * @param {string} partial - user provided argument value + * @param {array} staticSuggestions - argument value suggestions + * @return {array} array of static suggestions matching partial + */ + getStaticSuggestionsForInput: ( + partialInput = '', + staticSuggestions: TimelionFunctionArgs['suggestions'] = [] + ) => { + if (partialInput) { + return staticSuggestions.filter(suggestion => { + return suggestion.name.includes(partialInput); + }); + } + + return staticSuggestions; + }, + }; +} + +export type ArgValueSuggestions = ReturnType; diff --git a/src/legacy/core_plugins/timelion/public/services/plugin_services.ts b/src/legacy/core_plugins/timelion/public/services/plugin_services.ts new file mode 100644 index 0000000000000..5ba4ee5e47983 --- /dev/null +++ b/src/legacy/core_plugins/timelion/public/services/plugin_services.ts @@ -0,0 +1,30 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { IndexPatternsContract } from 'src/plugins/data/public'; +import { SavedObjectsClientContract } from 'kibana/public'; +import { createGetterSetter } from '../../../../../plugins/kibana_utils/public'; + +export const [getIndexPatterns, setIndexPatterns] = createGetterSetter( + 'IndexPatterns' +); + +export const [getSavedObjectsClient, setSavedObjectsClient] = createGetterSetter< + SavedObjectsClientContract +>('SavedObjectsClient'); diff --git a/src/legacy/core_plugins/timelion/public/timelion_vis_fn.ts b/src/legacy/core_plugins/timelion/public/timelion_vis_fn.ts index 474f464a550cd..206f9f5d8368d 100644 --- a/src/legacy/core_plugins/timelion/public/timelion_vis_fn.ts +++ b/src/legacy/core_plugins/timelion/public/timelion_vis_fn.ts @@ -28,7 +28,7 @@ const name = 'timelion_vis'; interface Arguments { expression: string; - interval: any; + interval: string; } interface RenderValue { @@ -38,7 +38,7 @@ interface RenderValue { } type Context = KibanaContext | null; -type VisParams = Arguments; +export type VisParams = Arguments; type Return = Promise>; export const getTimelionVisualizationConfig = ( @@ -60,7 +60,7 @@ export const getTimelionVisualizationConfig = ( help: '', }, interval: { - types: ['string', 'null'], + types: ['string'], default: 'auto', help: '', }, diff --git a/src/legacy/core_plugins/timelion/public/vis/_index.scss b/src/legacy/core_plugins/timelion/public/vis/_index.scss index e44b6336d33c1..17a2018f7a56a 100644 --- a/src/legacy/core_plugins/timelion/public/vis/_index.scss +++ b/src/legacy/core_plugins/timelion/public/vis/_index.scss @@ -1 +1,2 @@ @import './timelion_vis'; +@import './timelion_editor'; diff --git a/src/legacy/core_plugins/timelion/public/vis/_timelion_editor.scss b/src/legacy/core_plugins/timelion/public/vis/_timelion_editor.scss new file mode 100644 index 0000000000000..a9331930a86ff --- /dev/null +++ b/src/legacy/core_plugins/timelion/public/vis/_timelion_editor.scss @@ -0,0 +1,15 @@ +.visEditor--timelion { + vis-options-react-wrapper, + .visEditorSidebar__options, + .visEditorSidebar__timelionOptions { + flex: 1 1 auto; + display: flex; + flex-direction: column; + } + + .visEditor__sidebar { + @include euiBreakpoint('xs', 's', 'm') { + width: 100%; + } + } +} diff --git a/src/legacy/core_plugins/timelion/public/vis/index.ts b/src/legacy/core_plugins/timelion/public/vis/index.ts deleted file mode 100644 index 7b82553a24e5b..0000000000000 --- a/src/legacy/core_plugins/timelion/public/vis/index.ts +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { i18n } from '@kbn/i18n'; -// @ts-ignore -import { DefaultEditorSize } from 'ui/vis/editor_size'; -import { getTimelionRequestHandler } from './timelion_request_handler'; -import visConfigTemplate from './timelion_vis.html'; -import editorConfigTemplate from './timelion_vis_params.html'; -import { TimelionVisualizationDependencies } from '../plugin'; -// @ts-ignore -import { AngularVisController } from '../../../../ui/public/vis/vis_types/angular_vis_type'; - -export const TIMELION_VIS_NAME = 'timelion'; - -export function getTimelionVisualization(dependencies: TimelionVisualizationDependencies) { - const timelionRequestHandler = getTimelionRequestHandler(dependencies); - - // return the visType object, which kibana will use to display and configure new - // Vis object of this type. - return { - name: TIMELION_VIS_NAME, - title: 'Timelion', - icon: 'visTimelion', - description: i18n.translate('timelion.timelionDescription', { - defaultMessage: 'Build time-series using functional expressions', - }), - visualization: AngularVisController, - visConfig: { - defaults: { - expression: '.es(*)', - interval: 'auto', - }, - template: visConfigTemplate, - }, - editorConfig: { - optionsTemplate: editorConfigTemplate, - defaultSize: DefaultEditorSize.MEDIUM, - }, - requestHandler: timelionRequestHandler, - responseHandler: 'none', - options: { - showIndexSelection: false, - showQueryBar: false, - showFilterBar: false, - }, - }; -} diff --git a/src/legacy/core_plugins/timelion/public/vis/index.tsx b/src/legacy/core_plugins/timelion/public/vis/index.tsx new file mode 100644 index 0000000000000..1edcb0a5ce71c --- /dev/null +++ b/src/legacy/core_plugins/timelion/public/vis/index.tsx @@ -0,0 +1,73 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { i18n } from '@kbn/i18n'; +// @ts-ignore +import { DefaultEditorSize } from 'ui/vis/editor_size'; +import { VisOptionsProps } from 'ui/vis/editors/default'; +import { KibanaContextProvider } from '../../../../../plugins/kibana_react/public'; +import { getTimelionRequestHandler } from './timelion_request_handler'; +import visConfigTemplate from './timelion_vis.html'; +import { TimelionVisualizationDependencies } from '../plugin'; +// @ts-ignore +import { AngularVisController } from '../../../../ui/public/vis/vis_types/angular_vis_type'; +import { TimelionOptions } from './timelion_options'; +import { VisParams } from '../timelion_vis_fn'; + +export const TIMELION_VIS_NAME = 'timelion'; + +export function getTimelionVisualization(dependencies: TimelionVisualizationDependencies) { + const { http, uiSettings } = dependencies; + const timelionRequestHandler = getTimelionRequestHandler(dependencies); + + // return the visType object, which kibana will use to display and configure new + // Vis object of this type. + return { + name: TIMELION_VIS_NAME, + title: 'Timelion', + icon: 'visTimelion', + description: i18n.translate('timelion.timelionDescription', { + defaultMessage: 'Build time-series using functional expressions', + }), + visualization: AngularVisController, + visConfig: { + defaults: { + expression: '.es(*)', + interval: 'auto', + }, + template: visConfigTemplate, + }, + editorConfig: { + optionsTemplate: (props: VisOptionsProps) => ( + + + + ), + defaultSize: DefaultEditorSize.MEDIUM, + }, + requestHandler: timelionRequestHandler, + responseHandler: 'none', + options: { + showIndexSelection: false, + showQueryBar: false, + showFilterBar: false, + }, + }; +} diff --git a/src/legacy/core_plugins/timelion/public/vis/timelion_options.tsx b/src/legacy/core_plugins/timelion/public/vis/timelion_options.tsx new file mode 100644 index 0000000000000..527fcc3bc6ce8 --- /dev/null +++ b/src/legacy/core_plugins/timelion/public/vis/timelion_options.tsx @@ -0,0 +1,48 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React, { useCallback } from 'react'; +import { EuiPanel } from '@elastic/eui'; + +import { VisOptionsProps } from 'ui/vis/editors/default'; +import { VisParams } from '../timelion_vis_fn'; +import { TimelionInterval, TimelionExpressionInput } from '../components'; + +function TimelionOptions({ stateParams, setValue, setValidity }: VisOptionsProps) { + const setInterval = useCallback((value: VisParams['interval']) => setValue('interval', value), [ + setValue, + ]); + const setExpressionInput = useCallback( + (value: VisParams['expression']) => setValue('expression', value), + [setValue] + ); + + return ( + + + + + ); +} + +export { TimelionOptions }; diff --git a/src/legacy/core_plugins/timelion/public/vis/timelion_vis_params.html b/src/legacy/core_plugins/timelion/public/vis/timelion_vis_params.html deleted file mode 100644 index 9f2d2094fb1f7..0000000000000 --- a/src/legacy/core_plugins/timelion/public/vis/timelion_vis_params.html +++ /dev/null @@ -1,27 +0,0 @@ -
-
- -
- -
-
- -
-
- -
- - -
- -
diff --git a/src/legacy/core_plugins/timelion/server/lib/classes/timelion_function.d.ts b/src/legacy/core_plugins/timelion/server/lib/classes/timelion_function.d.ts index 6e32a4454e707..798902aa133de 100644 --- a/src/legacy/core_plugins/timelion/server/lib/classes/timelion_function.d.ts +++ b/src/legacy/core_plugins/timelion/server/lib/classes/timelion_function.d.ts @@ -17,6 +17,8 @@ * under the License. */ +import { TimelionFunctionArgs } from '../../../common/types'; + export interface TimelionFunctionInterface extends TimelionFunctionConfig { chainable: boolean; originalFn: Function; @@ -32,21 +34,6 @@ export interface TimelionFunctionConfig { args: TimelionFunctionArgs[]; } -export interface TimelionFunctionArgs { - name: string; - help?: string; - multi?: boolean; - types: TimelionFunctionArgsTypes[]; - suggestions?: TimelionFunctionArgsSuggestion[]; -} - -export type TimelionFunctionArgsTypes = 'seriesList' | 'number' | 'string' | 'boolean' | 'null'; - -export interface TimelionFunctionArgsSuggestion { - name: string; - help: string; -} - // eslint-disable-next-line import/no-default-export export default class TimelionFunction { constructor(name: string, config: TimelionFunctionConfig); diff --git a/src/legacy/core_plugins/timelion/server/types.ts b/src/legacy/core_plugins/timelion/server/types.ts index e612bc14a0daa..a035d64f764f1 100644 --- a/src/legacy/core_plugins/timelion/server/types.ts +++ b/src/legacy/core_plugins/timelion/server/types.ts @@ -17,12 +17,5 @@ * under the License. */ -export { - TimelionFunctionInterface, - TimelionFunctionConfig, - TimelionFunctionArgs, - TimelionFunctionArgsSuggestion, - TimelionFunctionArgsTypes, -} from './lib/classes/timelion_function'; - +export { TimelionFunctionInterface, TimelionFunctionConfig } from './lib/classes/timelion_function'; export { TimelionRequestQuery } from './routes/run'; diff --git a/src/legacy/core_plugins/vis_type_markdown/public/markdown_options.tsx b/src/legacy/core_plugins/vis_type_markdown/public/markdown_options.tsx index 53a7b1caef2a4..c70b6561c3101 100644 --- a/src/legacy/core_plugins/vis_type_markdown/public/markdown_options.tsx +++ b/src/legacy/core_plugins/vis_type_markdown/public/markdown_options.tsx @@ -36,7 +36,7 @@ import { MarkdownVisParams } from './types'; function MarkdownOptions({ stateParams, setValue }: VisOptionsProps) { const onMarkdownUpdate = useCallback( (value: MarkdownVisParams['markdown']) => setValue('markdown', value), - [] + [setValue] ); return ( diff --git a/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_value.js b/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_value.js index ca5ab20957281..0776dd13a9868 100644 --- a/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_value.js +++ b/src/legacy/core_plugins/vis_type_metric/public/components/metric_vis_value.js @@ -21,13 +21,19 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import classNames from 'classnames'; -import { EuiKeyboardAccessible } from '@elastic/eui'; +import { EuiKeyboardAccessible, keyCodes } from '@elastic/eui'; class MetricVisValue extends Component { onClick = () => { this.props.onFilter(this.props.metric); }; + onKeyPress = e => { + if (e.keyCode === keyCodes.ENTER) { + this.onClick(); + } + }; + render() { const { fontSize, metric, onFilter, showLabel } = this.props; const hasFilter = !!onFilter; @@ -47,6 +53,7 @@ class MetricVisValue extends Component { className={containerClassName} style={{ backgroundColor: metric.bgColor }} onClick={hasFilter ? this.onClick : null} + onKeyPress={hasFilter ? this.onKeyPress : null} tabIndex={hasFilter ? 0 : null} role={hasFilter ? 'button' : null} > diff --git a/src/legacy/core_plugins/vis_type_table/public/agg_table/agg_table.js b/src/legacy/core_plugins/vis_type_table/public/agg_table/agg_table.js index 07870379265c5..83d7ca4084a20 100644 --- a/src/legacy/core_plugins/vis_type_table/public/agg_table/agg_table.js +++ b/src/legacy/core_plugins/vis_type_table/public/agg_table/agg_table.js @@ -74,7 +74,11 @@ export function KbnAggTable(config, RecursionHelper) { // escape each cell in each row const csvRows = rows.map(function(row) { return Object.entries(row).map(([k, v]) => { - return escape(formatted ? columns.find(c => c.id === k).formatter.convert(v) : v); + const column = columns.find(c => c.id === k); + if (formatted && column) { + return escape(column.formatter.convert(v)); + } + return escape(v); }); }); @@ -110,12 +114,16 @@ export function KbnAggTable(config, RecursionHelper) { if (typeof $scope.dimensions === 'undefined') return; - const { buckets, metrics } = $scope.dimensions; + const { buckets, metrics, splitColumn } = $scope.dimensions; $scope.formattedColumns = table.columns .map(function(col, i) { const isBucket = buckets.find(bucket => bucket.accessor === i); - const dimension = isBucket || metrics.find(metric => metric.accessor === i); + const isSplitColumn = splitColumn + ? splitColumn.find(splitColumn => splitColumn.accessor === i) + : undefined; + const dimension = + isBucket || isSplitColumn || metrics.find(metric => metric.accessor === i); if (!dimension) return; @@ -135,18 +143,15 @@ export function KbnAggTable(config, RecursionHelper) { } const isDate = - _.get(dimension, 'format.id') === 'date' || - _.get(dimension, 'format.params.id') === 'date'; - const isNumeric = - _.get(dimension, 'format.id') === 'number' || - _.get(dimension, 'format.params.id') === 'number'; + dimension.format?.id === 'date' || dimension.format?.params?.id === 'date'; + const allowsNumericalAggregations = formatter?.allowsNumericalAggregations; let { totalFunc } = $scope; if (typeof totalFunc === 'undefined' && showPercentage) { totalFunc = 'sum'; } - if (isNumeric || isDate || totalFunc === 'count') { + if (allowsNumericalAggregations || isDate || totalFunc === 'count') { const sum = tableRows => { return _.reduce( tableRows, @@ -161,7 +166,6 @@ export function KbnAggTable(config, RecursionHelper) { }; formattedColumn.sumTotal = sum(table.rows); - switch (totalFunc) { case 'sum': { if (!isDate) { diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/timeseries/vis.js b/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/timeseries/vis.js index 982aca8d3b813..d269d7c3546ec 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/timeseries/vis.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/components/vis_types/timeseries/vis.js @@ -79,10 +79,14 @@ export class TimeseriesVisualization extends Component { static getYAxisDomain = model => { const axisMin = get(model, 'axis_min', '').toString(); const axisMax = get(model, 'axis_max', '').toString(); + const fit = model.series + ? model.series.filter(({ hidden }) => !hidden).every(({ fill }) => fill === '0') + : model.fill === '0'; return { min: axisMin.length ? Number(axisMin) : undefined, max: axisMax.length ? Number(axisMax) : undefined, + fit, }; }; diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/lib/validate_interval.js b/src/legacy/core_plugins/vis_type_timeseries/public/lib/validate_interval.js index 46dab92f3b245..2dbcdc4749a66 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/lib/validate_interval.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/lib/validate_interval.js @@ -17,9 +17,9 @@ * under the License. */ -import { parseInterval } from 'ui/utils/parse_interval'; import { GTE_INTERVAL_RE } from '../../common/interval_regexp'; import { i18n } from '@kbn/i18n'; +import { parseInterval } from '../../../../../plugins/data/public'; export function validateInterval(bounds, panel, maxBuckets) { const { interval } = panel; diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/__mocks__/@elastic/charts.js b/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/__mocks__/@elastic/charts.js index cb59bef63681b..d7f96cf4354b5 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/__mocks__/@elastic/charts.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/__mocks__/@elastic/charts.js @@ -38,8 +38,5 @@ export const ScaleType = { Time: 'time', }; -export const getSpecId = x => `id:${x}`; -export const getGroupId = x => `groupId:${x}`; - export const BarSeries = () => null; export const AreaSeries = () => null; diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/decorators/__snapshots__/area_decorator.test.js.snap b/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/decorators/__snapshots__/area_decorator.test.js.snap index 822de4cef0813..56504ca11ca39 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/decorators/__snapshots__/area_decorator.test.js.snap +++ b/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/decorators/__snapshots__/area_decorator.test.js.snap @@ -24,12 +24,9 @@ exports[`src/legacy/core_plugins/metrics/public/visualizations/views/timeseries/ } curve={9} customSeriesColors={ - Map { - Object { - "colorValues": Array [], - "specId": "id:61ca57f1-469d-11e7-af02-69e470af7417:Rome", - } => "rgb(0, 156, 224)", - } + Array [ + "rgb(0, 156, 224)", + ] } data={ Array [ @@ -44,10 +41,10 @@ exports[`src/legacy/core_plugins/metrics/public/visualizations/views/timeseries/ ] } enableHistogramMode={true} - groupId="groupId:yaxis_main_group" + groupId="yaxis_main_group" hideInLegend={false} histogramModeAlignment="center" - id="id:61ca57f1-469d-11e7-af02-69e470af7417:Rome" + id="61ca57f1-469d-11e7-af02-69e470af7417:Rome" name="Rome" stackAsPercentage={false} timeZone="local" diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/decorators/__snapshots__/bar_decorator.test.js.snap b/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/decorators/__snapshots__/bar_decorator.test.js.snap index 78133f2dda7cc..6317973cef536 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/decorators/__snapshots__/bar_decorator.test.js.snap +++ b/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/decorators/__snapshots__/bar_decorator.test.js.snap @@ -16,12 +16,9 @@ exports[`src/legacy/core_plugins/metrics/public/visualizations/views/timeseries/ } } customSeriesColors={ - Map { - Object { - "colorValues": Array [], - "specId": "id:61ca57f1-469d-11e7-af02-69e470af7417:Rome", - } => "rgb(0, 156, 224)", - } + Array [ + "rgb(0, 156, 224)", + ] } data={ Array [ @@ -36,10 +33,10 @@ exports[`src/legacy/core_plugins/metrics/public/visualizations/views/timeseries/ ] } enableHistogramMode={true} - groupId="groupId:yaxis_main_group" + groupId="yaxis_main_group" hideInLegend={false} histogramModeAlignment="center" - id="id:61ca57f1-469d-11e7-af02-69e470af7417:Rome" + id="61ca57f1-469d-11e7-af02-69e470af7417:Rome" name="Rome" stackAsPercentage={false} timeZone="local" diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/decorators/area_decorator.js b/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/decorators/area_decorator.js index 536064139e6ea..411c0813cad7c 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/decorators/area_decorator.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/decorators/area_decorator.js @@ -18,8 +18,8 @@ */ import React from 'react'; -import { getSpecId, getGroupId, ScaleType, AreaSeries } from '@elastic/charts'; -import { getSeriesColors, getAreaStyles } from '../utils/series_styles'; +import { ScaleType, AreaSeries } from '@elastic/charts'; +import { getAreaStyles } from '../utils/series_styles'; import { ChartsEntities } from '../model/charts'; import { X_ACCESSOR_INDEX, Y_ACCESSOR_INDEXES } from '../../../constants'; @@ -41,9 +41,9 @@ export function AreaSeriesDecorator({ useDefaultGroupDomain, sortIndex, }) { - const id = getSpecId(seriesId); - const groupId = getGroupId(seriesGroupId); - const customSeriesColors = getSeriesColors(color, id); + const id = seriesId; + const groupId = seriesGroupId; + const customSeriesColors = [color]; const areaSeriesStyle = getAreaStyles({ points, lines, color }); const seriesSettings = { diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/decorators/bar_decorator.js b/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/decorators/bar_decorator.js index 3dbe04dca06b8..9cc8931b48d9f 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/decorators/bar_decorator.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/decorators/bar_decorator.js @@ -18,8 +18,8 @@ */ import React from 'react'; -import { getSpecId, getGroupId, ScaleType, BarSeries } from '@elastic/charts'; -import { getSeriesColors, getBarStyles } from '../utils/series_styles'; +import { ScaleType, BarSeries } from '@elastic/charts'; +import { getBarStyles } from '../utils/series_styles'; import { ChartsEntities } from '../model/charts'; import { X_ACCESSOR_INDEX, Y_ACCESSOR_INDEXES } from '../../../constants'; @@ -40,9 +40,9 @@ export function BarSeriesDecorator({ useDefaultGroupDomain, sortIndex, }) { - const id = getSpecId(seriesId); - const groupId = getGroupId(seriesGroupId); - const customSeriesColors = getSeriesColors(color, id); + const id = seriesId; + const groupId = seriesGroupId; + const customSeriesColors = [color]; const barSeriesStyle = getBarStyles(bars, color); const seriesSettings = { diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/index.js b/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/index.js index a02ea83e5104b..bcd0b6314cef1 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/index.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/index.js @@ -25,11 +25,8 @@ import { Chart, Position, Settings, - getAxisId, - getGroupId, DARK_THEME, LIGHT_THEME, - getAnnotationId, AnnotationDomainTypes, LineAnnotation, TooltipType, @@ -75,7 +72,7 @@ export const TimeSeries = ({ const chartRef = useRef(); const updateCursor = (_, cursor) => { if (chartRef.current) { - chartRef.current.dispatchExternalCursorEvent(cursor); + chartRef.current.dispatchExternalPointerEvent(cursor); } }; @@ -99,7 +96,7 @@ export const TimeSeries = ({ legendPosition={legendPosition} onBrushEnd={onBrush} animateData={false} - onCursorUpdate={handleCursorUpdate} + onPointerUpdate={handleCursorUpdate} theme={ hasBarChart ? {} @@ -126,7 +123,7 @@ export const TimeSeries = ({ return ( } @@ -213,8 +210,8 @@ export const TimeSeries = ({ {yAxis.map(({ id, groupId, position, tickFormatter, domain, hide }) => ( "rgb(224, 0, 221)", -} -`; diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/utils/series_styles.js b/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/utils/series_styles.js index 63be14790c6c5..2891751f121ca 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/utils/series_styles.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/utils/series_styles.js @@ -56,12 +56,3 @@ export const getBarStyles = ({ show = true, lineWidth = 0, fill = 1 }, color) => }, }, }); - -export const getSeriesColors = (color, specId) => { - const map = new Map(); - const seriesColorsValues = { specId, colorValues: [] }; - - map.set(seriesColorsValues, color); - - return map; -}; diff --git a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/utils/series_styles.test.js b/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/utils/series_styles.test.js index ac0a7610f2660..c04ec457d1523 100644 --- a/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/utils/series_styles.test.js +++ b/src/legacy/core_plugins/vis_type_timeseries/public/visualizations/views/timeseries/utils/series_styles.test.js @@ -17,12 +17,11 @@ * under the License. */ -import { getBarStyles, getSeriesColors, getAreaStyles } from './series_styles'; +import { getBarStyles, getAreaStyles } from './series_styles'; describe('src/legacy/core_plugins/metrics/public/visualizations/views/timeseries/utils/series_styles.js', () => { let bars; let color; - let specId; let points; let lines; @@ -33,7 +32,6 @@ describe('src/legacy/core_plugins/metrics/public/visualizations/views/timeseries show: true, }; color = 'rgb(224, 0, 221)'; - specId = 'IT'; points = { lineWidth: 1, show: true, @@ -60,12 +58,6 @@ describe('src/legacy/core_plugins/metrics/public/visualizations/views/timeseries }); }); - describe('getSeriesColors()', () => { - test('should match a snapshot', () => { - expect(getSeriesColors(color, specId)).toMatchSnapshot(); - }); - }); - describe('getAreaStyles()', () => { test('should match a snapshot', () => { expect(getAreaStyles({ points, lines, color })).toMatchSnapshot(); diff --git a/src/legacy/core_plugins/vis_type_vega/public/components/vega_actions_menu.tsx b/src/legacy/core_plugins/vis_type_vega/public/components/vega_actions_menu.tsx index 71a88b47a8be3..3d7fda990b2ae 100644 --- a/src/legacy/core_plugins/vis_type_vega/public/components/vega_actions_menu.tsx +++ b/src/legacy/core_plugins/vis_type_vega/public/components/vega_actions_menu.tsx @@ -34,12 +34,12 @@ function VegaActionsMenu({ formatHJson, formatJson }: VegaActionsMenuProps) { const onHJsonCLick = useCallback(() => { formatHJson(); setIsPopoverOpen(false); - }, [isPopoverOpen, formatHJson]); + }, [formatHJson]); const onJsonCLick = useCallback(() => { formatJson(); setIsPopoverOpen(false); - }, [isPopoverOpen, formatJson]); + }, [formatJson]); const closePopover = useCallback(() => setIsPopoverOpen(false), []); diff --git a/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/build_pipeline.ts b/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/build_pipeline.ts index 59c6bddb64521..cc2ab133941db 100644 --- a/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/build_pipeline.ts +++ b/src/legacy/core_plugins/visualizations/public/np_ready/public/legacy/build_pipeline.ts @@ -445,6 +445,8 @@ export const buildVislibDimensions = async ( dimensions.x.params.date = true; const { esUnit, esValue } = xAgg.buckets.getInterval(); dimensions.x.params.interval = moment.duration(esValue, esUnit); + dimensions.x.params.intervalESValue = esValue; + dimensions.x.params.intervalESUnit = esUnit; dimensions.x.params.format = xAgg.buckets.getScaledDateFormat(); dimensions.x.params.bounds = xAgg.buckets.getBounds(); } else if (xAgg.type.name === 'histogram') { diff --git a/src/legacy/server/config/schema.js b/src/legacy/server/config/schema.js index f886fd598f5c9..a53e8e0498c42 100644 --- a/src/legacy/server/config/schema.js +++ b/src/legacy/server/config/schema.js @@ -70,20 +70,6 @@ export default () => server: Joi.object({ name: Joi.string().default(os.hostname()), - defaultRoute: Joi.string().regex(/^\//, `start with a slash`), - customResponseHeaders: Joi.object() - .unknown(true) - .default({}), - xsrf: Joi.object({ - disableProtection: Joi.boolean().default(false), - whitelist: Joi.array() - .items(Joi.string().regex(/^\//, 'start with a slash')) - .default([]), - token: Joi.string() - .optional() - .notes('Deprecated'), - }).default(), - // keep them for BWC, remove when not used in Legacy. // validation should be in sync with one in New platform. // https://github.com/elastic/kibana/blob/master/src/core/server/http/http_config.ts @@ -103,12 +89,14 @@ export default () => autoListen: HANDLED_IN_NEW_PLATFORM, cors: HANDLED_IN_NEW_PLATFORM, + customResponseHeaders: HANDLED_IN_NEW_PLATFORM, keepaliveTimeout: HANDLED_IN_NEW_PLATFORM, maxPayloadBytes: HANDLED_IN_NEW_PLATFORM, socketTimeout: HANDLED_IN_NEW_PLATFORM, ssl: HANDLED_IN_NEW_PLATFORM, compression: HANDLED_IN_NEW_PLATFORM, uuid: HANDLED_IN_NEW_PLATFORM, + xsrf: HANDLED_IN_NEW_PLATFORM, }).default(), uiSettings: HANDLED_IN_NEW_PLATFORM, @@ -193,7 +181,7 @@ export default () => .default('localhost'), watchPrebuild: Joi.boolean().default(false), watchProxyTimeout: Joi.number().default(10 * 60000), - useBundleCache: Joi.boolean().default(Joi.ref('$prod')), + useBundleCache: Joi.boolean().default(!!process.env.CODE_COVERAGE ? true : Joi.ref('$prod')), sourceMaps: Joi.when('$prod', { is: true, then: Joi.boolean().valid(false), diff --git a/src/legacy/server/config/schema.test.js b/src/legacy/server/config/schema.test.js index 1207a05a47497..03d2fe53c2ce7 100644 --- a/src/legacy/server/config/schema.test.js +++ b/src/legacy/server/config/schema.test.js @@ -19,7 +19,6 @@ import schemaProvider from './schema'; import Joi from 'joi'; -import { set } from 'lodash'; describe('Config schema', function() { let schema; @@ -100,60 +99,5 @@ describe('Config schema', function() { expect(error.details[0]).toHaveProperty('path', ['server', 'rewriteBasePath']); }); }); - - describe('xsrf', () => { - it('disableProtection is `false` by default.', () => { - const { - error, - value: { - server: { - xsrf: { disableProtection }, - }, - }, - } = validate({}); - expect(error).toBe(null); - expect(disableProtection).toBe(false); - }); - - it('whitelist is empty by default.', () => { - const { - value: { - server: { - xsrf: { whitelist }, - }, - }, - } = validate({}); - expect(whitelist).toBeInstanceOf(Array); - expect(whitelist).toHaveLength(0); - }); - - it('whitelist rejects paths that do not start with a slash.', () => { - const config = {}; - set(config, 'server.xsrf.whitelist', ['path/to']); - - const { error } = validate(config); - expect(error).toBeInstanceOf(Object); - expect(error).toHaveProperty('details'); - expect(error.details[0]).toHaveProperty('path', ['server', 'xsrf', 'whitelist', 0]); - }); - - it('whitelist accepts paths that start with a slash.', () => { - const config = {}; - set(config, 'server.xsrf.whitelist', ['/path/to']); - - const { - error, - value: { - server: { - xsrf: { whitelist }, - }, - }, - } = validate(config); - expect(error).toBe(null); - expect(whitelist).toBeInstanceOf(Array); - expect(whitelist).toHaveLength(1); - expect(whitelist).toContain('/path/to'); - }); - }); }); }); diff --git a/src/legacy/server/http/index.js b/src/legacy/server/http/index.js index 9b5ce2046c5d3..265d71e95b301 100644 --- a/src/legacy/server/http/index.js +++ b/src/legacy/server/http/index.js @@ -22,11 +22,9 @@ import { resolve } from 'path'; import _ from 'lodash'; import Boom from 'boom'; -import { setupVersionCheck } from './version_check'; import { registerHapiPlugins } from './register_hapi_plugins'; import { setupBasePathProvider } from './setup_base_path_provider'; import { setupDefaultRouteProvider } from './setup_default_route_provider'; -import { setupXsrf } from './xsrf'; export default async function(kbnServer, server, config) { server = kbnServer.server; @@ -62,29 +60,6 @@ export default async function(kbnServer, server, config) { }); }); - // attach the app name to the server, so we can be sure we are actually talking to kibana - server.ext('onPreResponse', function onPreResponse(req, h) { - const response = req.response; - - const customHeaders = { - ...config.get('server.customResponseHeaders'), - 'kbn-name': kbnServer.name, - }; - - if (response.isBoom) { - response.output.headers = { - ...response.output.headers, - ...customHeaders, - }; - } else { - Object.keys(customHeaders).forEach(name => { - response.header(name, customHeaders[name]); - }); - } - - return h.continue; - }); - server.route({ path: '/', method: 'GET', @@ -116,7 +91,4 @@ export default async function(kbnServer, server, config) { // Expose static assets server.exposeStaticDir('/ui/{path*}', resolve(__dirname, '../../ui/public/assets')); - - setupVersionCheck(server, config); - setupXsrf(server, config); } diff --git a/src/legacy/server/http/integration_tests/version_check.test.js b/src/legacy/server/http/integration_tests/version_check.test.js deleted file mode 100644 index 8d71c98d64969..0000000000000 --- a/src/legacy/server/http/integration_tests/version_check.test.js +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { resolve } from 'path'; -import * as kbnTestServer from '../../../../test_utils/kbn_server'; - -const src = resolve.bind(null, __dirname, '../../../../../src'); - -const versionHeader = 'kbn-version'; -const version = require(src('../package.json')).version; // eslint-disable-line import/no-dynamic-require - -describe('version_check request filter', function() { - let root; - beforeAll(async () => { - root = kbnTestServer.createRoot(); - - await root.setup(); - await root.start(); - - kbnTestServer.getKbnServer(root).server.route({ - path: '/version_check/test/route', - method: 'GET', - handler: function() { - return 'ok'; - }, - }); - }, 30000); - - afterAll(async () => await root.shutdown()); - - it('accepts requests with the correct version passed in the version header', async function() { - await kbnTestServer.request - .get(root, '/version_check/test/route') - .set(versionHeader, version) - .expect(200, 'ok'); - }); - - it('rejects requests with an incorrect version passed in the version header', async function() { - await kbnTestServer.request - .get(root, '/version_check/test/route') - .set(versionHeader, `invalid:${version}`) - .expect(400, /"Browser client is out of date/); - }); - - it('accepts requests that do not include a version header', async function() { - await kbnTestServer.request.get(root, '/version_check/test/route').expect(200, 'ok'); - }); -}); diff --git a/src/legacy/server/http/integration_tests/xsrf.test.js b/src/legacy/server/http/integration_tests/xsrf.test.js deleted file mode 100644 index a06f4eec4fd5c..0000000000000 --- a/src/legacy/server/http/integration_tests/xsrf.test.js +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { resolve } from 'path'; -import * as kbnTestServer from '../../../../test_utils/kbn_server'; - -const destructiveMethods = ['POST', 'PUT', 'DELETE']; -const src = resolve.bind(null, __dirname, '../../../../../src'); - -const xsrfHeader = 'kbn-xsrf'; -const versionHeader = 'kbn-version'; -const testPath = '/xsrf/test/route'; -const whitelistedTestPath = '/xsrf/test/route/whitelisted'; -const actualVersion = require(src('../package.json')).version; // eslint-disable-line import/no-dynamic-require - -describe('xsrf request filter', () => { - let root; - beforeAll(async () => { - root = kbnTestServer.createRoot({ - server: { - xsrf: { disableProtection: false, whitelist: [whitelistedTestPath] }, - }, - }); - - await root.setup(); - await root.start(); - - const kbnServer = kbnTestServer.getKbnServer(root); - kbnServer.server.route({ - path: testPath, - method: 'GET', - handler: async function() { - return 'ok'; - }, - }); - - kbnServer.server.route({ - path: testPath, - method: destructiveMethods, - config: { - // Disable payload parsing to make HapiJS server accept any content-type header. - payload: { - parse: false, - }, - validate: { payload: null }, - }, - handler: async function() { - return 'ok'; - }, - }); - - kbnServer.server.route({ - path: whitelistedTestPath, - method: destructiveMethods, - config: { - // Disable payload parsing to make HapiJS server accept any content-type header. - payload: { - parse: false, - }, - validate: { payload: null }, - }, - handler: async function() { - return 'ok'; - }, - }); - }, 30000); - - afterAll(async () => await root.shutdown()); - - describe(`nonDestructiveMethod: GET`, function() { - it('accepts requests without a token', async function() { - await kbnTestServer.request.get(root, testPath).expect(200, 'ok'); - }); - - it('accepts requests with the xsrf header', async function() { - await kbnTestServer.request - .get(root, testPath) - .set(xsrfHeader, 'anything') - .expect(200, 'ok'); - }); - }); - - describe(`nonDestructiveMethod: HEAD`, function() { - it('accepts requests without a token', async function() { - await kbnTestServer.request.head(root, testPath).expect(200, undefined); - }); - - it('accepts requests with the xsrf header', async function() { - await kbnTestServer.request - .head(root, testPath) - .set(xsrfHeader, 'anything') - .expect(200, undefined); - }); - }); - - for (const method of destructiveMethods) { - // eslint-disable-next-line no-loop-func - describe(`destructiveMethod: ${method}`, function() { - it('accepts requests with the xsrf header', async function() { - await kbnTestServer.request[method.toLowerCase()](root, testPath) - .set(xsrfHeader, 'anything') - .expect(200, 'ok'); - }); - - // this is still valid for existing csrf protection support - // it does not actually do any validation on the version value itself - it('accepts requests with the version header', async function() { - await kbnTestServer.request[method.toLowerCase()](root, testPath) - .set(versionHeader, actualVersion) - .expect(200, 'ok'); - }); - - it('rejects requests without either an xsrf or version header', async function() { - await kbnTestServer.request[method.toLowerCase()](root, testPath).expect(400, { - statusCode: 400, - error: 'Bad Request', - message: 'Request must contain a kbn-xsrf header.', - }); - }); - - it('accepts whitelisted requests without either an xsrf or version header', async function() { - await kbnTestServer.request[method.toLowerCase()](root, whitelistedTestPath).expect( - 200, - 'ok' - ); - }); - }); - } -}); diff --git a/src/legacy/server/http/setup_default_route_provider.ts b/src/legacy/server/http/setup_default_route_provider.ts index 0e7bcf1f56f6f..9a580dd1c59bd 100644 --- a/src/legacy/server/http/setup_default_route_provider.ts +++ b/src/legacy/server/http/setup_default_route_provider.ts @@ -29,7 +29,7 @@ export function setupDefaultRouteProvider(server: Legacy.Server) { const uiSettings = request.getUiSettingsService(); - const defaultRoute = await uiSettings.get('defaultRoute'); + const defaultRoute = await uiSettings.get('defaultRoute'); const qualifiedDefaultRoute = `${request.getBasePath()}${defaultRoute}`; if (isRelativePath(qualifiedDefaultRoute, serverBasePath)) { diff --git a/src/legacy/server/http/version_check.js b/src/legacy/server/http/version_check.js deleted file mode 100644 index 12666c9a0f3f6..0000000000000 --- a/src/legacy/server/http/version_check.js +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { badRequest } from 'boom'; - -export function setupVersionCheck(server, config) { - const versionHeader = 'kbn-version'; - const actualVersion = config.get('pkg.version'); - - server.ext('onPostAuth', function onPostAuthVersionCheck(req, h) { - const versionRequested = req.headers[versionHeader]; - - if (versionRequested && versionRequested !== actualVersion) { - throw badRequest( - `Browser client is out of date, \ - please refresh the page ("${versionHeader}" header was "${versionRequested}" but should be "${actualVersion}")`, - { expected: actualVersion, got: versionRequested } - ); - } - - return h.continue; - }); -} diff --git a/src/legacy/server/http/xsrf.js b/src/legacy/server/http/xsrf.js deleted file mode 100644 index 79ac3af6d9f90..0000000000000 --- a/src/legacy/server/http/xsrf.js +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { badRequest } from 'boom'; - -export function setupXsrf(server, config) { - const disabled = config.get('server.xsrf.disableProtection'); - const whitelist = config.get('server.xsrf.whitelist'); - const versionHeader = 'kbn-version'; - const xsrfHeader = 'kbn-xsrf'; - - server.ext('onPostAuth', function onPostAuthXsrf(req, h) { - if (disabled) { - return h.continue; - } - - if (whitelist.includes(req.path)) { - return h.continue; - } - - const isSafeMethod = req.method === 'get' || req.method === 'head'; - const hasVersionHeader = versionHeader in req.headers; - const hasXsrfHeader = xsrfHeader in req.headers; - - if (!isSafeMethod && !hasVersionHeader && !hasXsrfHeader) { - throw badRequest(`Request must contain a ${xsrfHeader} header.`); - } - - return h.continue; - }); -} diff --git a/src/legacy/server/kbn_server.d.ts b/src/legacy/server/kbn_server.d.ts index 0af6dacee59c8..8da1b3b05fa76 100644 --- a/src/legacy/server/kbn_server.d.ts +++ b/src/legacy/server/kbn_server.d.ts @@ -129,7 +129,7 @@ export interface KibanaCore { plugins: PluginsSetup; }; startDeps: { - core: CoreSetup; + core: CoreStart; plugins: Record; }; logger: LoggerFactory; diff --git a/src/legacy/server/saved_objects/saved_objects_mixin.test.js b/src/legacy/server/saved_objects/saved_objects_mixin.test.js index 0e96189db4650..691878cf66d27 100644 --- a/src/legacy/server/saved_objects/saved_objects_mixin.test.js +++ b/src/legacy/server/saved_objects/saved_objects_mixin.test.js @@ -106,12 +106,7 @@ describe('Saved Objects Mixin', () => { newPlatform: { __internals: { elasticsearch: { - adminClient$: { - pipe: jest.fn().mockImplementation(() => ({ - toPromise: () => - Promise.resolve({ adminClient: { callAsInternalUser: mockCallCluster } }), - })), - }, + adminClient: { callAsInternalUser: mockCallCluster }, }, }, }, diff --git a/src/legacy/server/status/lib/case_conversion.test.ts b/src/legacy/server/status/lib/case_conversion.test.ts new file mode 100644 index 0000000000000..a231ee0ba4b0f --- /dev/null +++ b/src/legacy/server/status/lib/case_conversion.test.ts @@ -0,0 +1,36 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { keysToSnakeCaseShallow } from './case_conversion'; + +describe('keysToSnakeCaseShallow', () => { + test("should convert all of an object's keys to snake case", () => { + const data = { + camelCase: 'camel_case', + 'kebab-case': 'kebab_case', + snake_case: 'snake_case', + }; + + const result = keysToSnakeCaseShallow(data); + + expect(result.camel_case).toBe('camel_case'); + expect(result.kebab_case).toBe('kebab_case'); + expect(result.snake_case).toBe('snake_case'); + }); +}); diff --git a/src/legacy/server/status/lib/case_conversion.ts b/src/legacy/server/status/lib/case_conversion.ts new file mode 100644 index 0000000000000..a3ae15028daeb --- /dev/null +++ b/src/legacy/server/status/lib/case_conversion.ts @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { mapKeys, snakeCase } from 'lodash'; + +export function keysToSnakeCaseShallow(object: Record) { + return mapKeys(object, (value, key) => snakeCase(key)); +} diff --git a/src/legacy/server/status/lib/metrics.js b/src/legacy/server/status/lib/metrics.js index 322d8d8bd9ab4..2631b245e72ab 100644 --- a/src/legacy/server/status/lib/metrics.js +++ b/src/legacy/server/status/lib/metrics.js @@ -20,7 +20,7 @@ import os from 'os'; import v8 from 'v8'; import { get, isObject, merge } from 'lodash'; -import { keysToSnakeCaseShallow } from '../../../utils/case_conversion'; +import { keysToSnakeCaseShallow } from './case_conversion'; import { getAllStats as cGroupStats } from './cgroup'; import { getOSInfo } from './get_os_info'; diff --git a/src/legacy/ui/public/_index.scss b/src/legacy/ui/public/_index.scss index 98675402b43cc..747ad025ef691 100644 --- a/src/legacy/ui/public/_index.scss +++ b/src/legacy/ui/public/_index.scss @@ -20,6 +20,7 @@ @import './saved_objects/index'; @import './share/index'; @import './style_compile/index'; +@import '../../../plugins/management/public/components/index'; // The following are prefixed with "vis" diff --git a/src/legacy/ui/public/agg_response/point_series/__tests__/_init_x_axis.js b/src/legacy/ui/public/agg_response/point_series/__tests__/_init_x_axis.js index 929aa4d5a7a9f..a8512edee658b 100644 --- a/src/legacy/ui/public/agg_response/point_series/__tests__/_init_x_axis.js +++ b/src/legacy/ui/public/agg_response/point_series/__tests__/_init_x_axis.js @@ -104,6 +104,8 @@ describe('initXAxis', function() { it('reads the date interval param from the x agg', function() { chart.aspects.x[0].params.interval = 'P1D'; + chart.aspects.x[0].params.intervalESValue = 1; + chart.aspects.x[0].params.intervalESUnit = 'd'; chart.aspects.x[0].params.date = true; initXAxis(chart, table); expect(chart) @@ -113,6 +115,8 @@ describe('initXAxis', function() { expect(moment.isDuration(chart.ordered.interval)).to.be(true); expect(chart.ordered.interval.toISOString()).to.eql('P1D'); + expect(chart.ordered.intervalESValue).to.be(1); + expect(chart.ordered.intervalESUnit).to.be('d'); }); it('reads the numeric interval param from the x agg', function() { diff --git a/src/legacy/ui/public/agg_response/point_series/_init_x_axis.js b/src/legacy/ui/public/agg_response/point_series/_init_x_axis.js index 531c564ea19d6..4a81486783b08 100644 --- a/src/legacy/ui/public/agg_response/point_series/_init_x_axis.js +++ b/src/legacy/ui/public/agg_response/point_series/_init_x_axis.js @@ -28,9 +28,19 @@ export function initXAxis(chart, table) { chart.xAxisFormat = format; chart.xAxisLabel = title; - if (params.interval) { - chart.ordered = { - interval: params.date ? moment.duration(params.interval) : params.interval, - }; + const { interval, date } = params; + if (interval) { + if (date) { + const { intervalESUnit, intervalESValue } = params; + chart.ordered = { + interval: moment.duration(interval), + intervalESUnit: intervalESUnit, + intervalESValue: intervalESValue, + }; + } else { + chart.ordered = { + interval, + }; + } } } diff --git a/src/legacy/ui/public/agg_types/agg_configs.ts b/src/legacy/ui/public/agg_types/agg_configs.ts index ece9c90d09b68..6e811afb1849d 100644 --- a/src/legacy/ui/public/agg_types/agg_configs.ts +++ b/src/legacy/ui/public/agg_types/agg_configs.ts @@ -31,7 +31,7 @@ import { TimeRange } from 'src/plugins/data/public'; import { Schema } from '../vis/editors/default/schemas'; import { AggConfig, AggConfigOptions } from './agg_config'; import { AggGroupNames } from '../vis/editors/default/agg_groups'; -import { IndexPattern } from '../../../core_plugins/data/public'; +import { IndexPattern } from '../../../../plugins/data/public'; import { ISearchSource, FetchOptions } from '../courier/types'; type Schemas = Record; diff --git a/src/legacy/ui/public/agg_types/filter/agg_type_filters.test.ts b/src/legacy/ui/public/agg_types/filter/agg_type_filters.test.ts index f470ebbc90b48..517cee23e6be1 100644 --- a/src/legacy/ui/public/agg_types/filter/agg_type_filters.test.ts +++ b/src/legacy/ui/public/agg_types/filter/agg_type_filters.test.ts @@ -17,7 +17,7 @@ * under the License. */ -import { IndexPattern } from 'ui/index_patterns'; +import { IndexPattern } from '../../../../../plugins/data/public'; import { AggTypeFilters } from './agg_type_filters'; import { AggType } from '..'; import { AggConfig } from '../../vis'; diff --git a/src/legacy/ui/public/agg_types/filter/agg_type_filters.ts b/src/legacy/ui/public/agg_types/filter/agg_type_filters.ts index 4d99575e4ae38..2388d458d4abb 100644 --- a/src/legacy/ui/public/agg_types/filter/agg_type_filters.ts +++ b/src/legacy/ui/public/agg_types/filter/agg_type_filters.ts @@ -16,7 +16,7 @@ * specific language governing permissions and limitations * under the License. */ -import { IndexPattern } from '../../index_patterns'; +import { IndexPattern } from '../../../../../plugins/data/public'; import { AggConfig } from '../../vis'; import { AggType } from '..'; diff --git a/src/legacy/ui/public/agg_types/param_types/field.ts b/src/legacy/ui/public/agg_types/param_types/field.ts index 4fda86bd1379f..0ca60267becec 100644 --- a/src/legacy/ui/public/agg_types/param_types/field.ts +++ b/src/legacy/ui/public/agg_types/param_types/field.ts @@ -25,7 +25,7 @@ import { FieldParamEditor } from '../../vis/editors/default/controls/field'; import { BaseParamType } from './base'; import { toastNotifications } from '../../notify'; import { propFilter } from '../filter'; -import { Field, IFieldList } from '../../index_patterns'; +import { Field, IFieldList } from '../../../../../plugins/data/public'; const filterByType = propFilter('type'); diff --git a/src/legacy/ui/public/agg_types/param_types/filter/field_filters.test.ts b/src/legacy/ui/public/agg_types/param_types/filter/field_filters.test.ts index e8d53754f6682..978b7edaa83ff 100644 --- a/src/legacy/ui/public/agg_types/param_types/filter/field_filters.test.ts +++ b/src/legacy/ui/public/agg_types/param_types/filter/field_filters.test.ts @@ -17,10 +17,10 @@ * under the License. */ -import { Field } from 'ui/index_patterns'; import { IndexedArray } from 'ui/indexed_array'; import { AggTypeFieldFilters } from './field_filters'; import { AggConfig } from 'ui/vis'; +import { Field } from '../../../../../../plugins/data/public'; describe('AggTypeFieldFilters', () => { let registry: AggTypeFieldFilters; diff --git a/src/legacy/ui/public/agg_types/param_types/filter/field_filters.ts b/src/legacy/ui/public/agg_types/param_types/filter/field_filters.ts index b3dbcf05681db..e5cb5226435ff 100644 --- a/src/legacy/ui/public/agg_types/param_types/filter/field_filters.ts +++ b/src/legacy/ui/public/agg_types/param_types/filter/field_filters.ts @@ -16,8 +16,8 @@ * specific language governing permissions and limitations * under the License. */ -import { Field } from 'ui/index_patterns'; import { AggConfig } from '../../../vis'; +import { Field } from '../../../../../../plugins/data/public'; type AggTypeFieldFilter = (field: Field, aggConfig: AggConfig) => boolean; diff --git a/src/legacy/ui/public/utils/key_map.ts b/src/legacy/ui/public/directives/key_map.ts similarity index 100% rename from src/legacy/ui/public/utils/key_map.ts rename to src/legacy/ui/public/directives/key_map.ts diff --git a/src/legacy/ui/public/directives/watch_multi/watch_multi.js b/src/legacy/ui/public/directives/watch_multi/watch_multi.js index 2a2ffe24cba9c..54b5cf08a9397 100644 --- a/src/legacy/ui/public/directives/watch_multi/watch_multi.js +++ b/src/legacy/ui/public/directives/watch_multi/watch_multi.js @@ -19,7 +19,6 @@ import _ from 'lodash'; import { uiModules } from '../../modules'; -import { callEach } from '../../utils/function'; export function watchMultiDecorator($provide) { $provide.decorator('$rootScope', function($delegate) { @@ -112,7 +111,9 @@ export function watchMultiDecorator($provide) { ) ); - return _.partial(callEach, unwatchers); + return function() { + unwatchers.forEach(listener => listener()); + }; }; function normalizeExpression($scope, expr) { diff --git a/src/legacy/ui/public/field_editor/components/scripting_help/test_script.js b/src/legacy/ui/public/field_editor/components/scripting_help/test_script.js index 942f39fc98dec..12bf5c1cce004 100644 --- a/src/legacy/ui/public/field_editor/components/scripting_help/test_script.js +++ b/src/legacy/ui/public/field_editor/components/scripting_help/test_script.js @@ -30,6 +30,8 @@ import { EuiTitle, EuiCallOut, } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; import { npStart } from 'ui/new_platform'; const { SearchBar } = npStart.plugins.data.ui; @@ -116,7 +118,13 @@ export class TestScript extends Component { if (previewData.error) { return ( - + -

First 10 results

+

+ +

{ - return !field.name.startsWith('_'); + const isMultiField = field.subType && field.subType.multi; + return !field.name.startsWith('_') && !isMultiField && !field.scripted; }) .forEach(field => { if (fieldsByTypeMap.has(field.type)) { @@ -180,9 +194,16 @@ export class TestScript extends Component { return ( - + - Run script + } /> @@ -219,11 +243,19 @@ export class TestScript extends Component { -

Preview results

+

+ +

- Run your script to preview the first 10 results. You can also select some additional - fields to include in your results to gain more context or add a query to filter on - specific documents. +

diff --git a/src/legacy/ui/public/index_patterns/__mocks__/index.ts b/src/legacy/ui/public/index_patterns/__mocks__/index.ts deleted file mode 100644 index 0cb1cf897b166..0000000000000 --- a/src/legacy/ui/public/index_patterns/__mocks__/index.ts +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { indexPatterns as npIndexPatterns } from '../../../../../plugins/data/public'; - -export const flattenHitWrapper = npIndexPatterns.flattenHitWrapper; - -// static code -export { getFromSavedObject, getRoutes } from '../../../../core_plugins/data/public'; diff --git a/src/legacy/ui/public/index_patterns/index.ts b/src/legacy/ui/public/index_patterns/index.ts deleted file mode 100644 index 47f6e697c54e9..0000000000000 --- a/src/legacy/ui/public/index_patterns/index.ts +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -/** - * Nothing to see here! - * - * Index Patterns have moved to the data plugin, and are being re-exported - * from ui/index_patterns for backwards compatibility. - */ - -import { indexPatterns } from '../../../../plugins/data/public'; - -// static code -export const INDEX_PATTERN_ILLEGAL_CHARACTERS = indexPatterns.ILLEGAL_CHARACTERS; -export const INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE = indexPatterns.ILLEGAL_CHARACTERS_VISIBLE; -export const ILLEGAL_CHARACTERS = indexPatterns.ILLEGAL_CHARACTERS_KEY; -export const CONTAINS_SPACES = indexPatterns.CONTAINS_SPACES_KEY; -export const validateIndexPattern = indexPatterns.validate; -export const flattenHitWrapper = indexPatterns.flattenHitWrapper; -export const getFromSavedObject = indexPatterns.getFromSavedObject; -export const getRoutes = indexPatterns.getRoutes; - -// types -export { Field, FieldType, IFieldList, IndexPattern } from '../../../core_plugins/data/public'; diff --git a/src/legacy/ui/public/indexed_array/helpers/organize_by.test.ts b/src/legacy/ui/public/indexed_array/helpers/organize_by.test.ts new file mode 100644 index 0000000000000..fc4ca8469382a --- /dev/null +++ b/src/legacy/ui/public/indexed_array/helpers/organize_by.test.ts @@ -0,0 +1,63 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { groupBy } from 'lodash'; +import { organizeBy } from './organize_by'; + +describe('organizeBy', () => { + test('it works', () => { + const col = [ + { + name: 'one', + roles: ['user', 'admin', 'owner'], + }, + { + name: 'two', + roles: ['user'], + }, + { + name: 'three', + roles: ['user'], + }, + { + name: 'four', + roles: ['user', 'admin'], + }, + ]; + + const resp = organizeBy(col, 'roles'); + expect(resp).toHaveProperty('user'); + expect(resp.user.length).toBe(4); + + expect(resp).toHaveProperty('admin'); + expect(resp.admin.length).toBe(2); + + expect(resp).toHaveProperty('owner'); + expect(resp.owner.length).toBe(1); + }); + + test('behaves just like groupBy in normal scenarios', () => { + const col = [{ name: 'one' }, { name: 'two' }, { name: 'three' }, { name: 'four' }]; + + const orgs = organizeBy(col, 'name'); + const groups = groupBy(col, 'name'); + + expect(orgs).toEqual(groups); + }); +}); diff --git a/src/legacy/ui/public/indexed_array/helpers/organize_by.ts b/src/legacy/ui/public/indexed_array/helpers/organize_by.ts new file mode 100644 index 0000000000000..e923767c892cd --- /dev/null +++ b/src/legacy/ui/public/indexed_array/helpers/organize_by.ts @@ -0,0 +1,61 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { each, isFunction } from 'lodash'; + +/** + * Like _.groupBy, but allows specifying multiple groups for a + * single object. + * + * organizeBy([{ a: [1, 2, 3] }, { b: true, a: [1, 4] }], 'a') + * // Object {1: Array[2], 2: Array[1], 3: Array[1], 4: Array[1]} + * + * _.groupBy([{ a: [1, 2, 3] }, { b: true, a: [1, 4] }], 'a') + * // Object {'1,2,3': Array[1], '1,4': Array[1]} + * + * @param {array} collection - the list of values to organize + * @param {Function} callback - either a property name, or a callback. + * @return {object} + */ +export function organizeBy(collection: object[], callback: ((obj: object) => string) | string) { + const buckets: { [key: string]: object[] } = {}; + + function add(key: string, obj: object) { + if (!buckets[key]) { + buckets[key] = []; + } + buckets[key].push(obj); + } + + each(collection, (obj: Record) => { + const keys = isFunction(callback) ? callback(obj) : obj[callback]; + + if (!Array.isArray(keys)) { + add(keys, obj); + return; + } + + let length = keys.length; + while (length-- > 0) { + add(keys[length], obj); + } + }); + + return buckets; +} diff --git a/src/legacy/ui/public/indexed_array/indexed_array.js b/src/legacy/ui/public/indexed_array/indexed_array.js index 96b37a1423be0..39c79b2f021a3 100644 --- a/src/legacy/ui/public/indexed_array/indexed_array.js +++ b/src/legacy/ui/public/indexed_array/indexed_array.js @@ -19,7 +19,7 @@ import _ from 'lodash'; import { inflector } from './inflector'; -import { organizeBy } from '../utils/collection'; +import { organizeBy } from './helpers/organize_by'; const pathGetter = _(_.get) .rearg(1, 0) diff --git a/src/legacy/ui/public/indices/constants/index.js b/src/legacy/ui/public/indices/constants/index.js index 87d36d1c6d14a..72ecc2e4c87de 100644 --- a/src/legacy/ui/public/indices/constants/index.js +++ b/src/legacy/ui/public/indices/constants/index.js @@ -17,11 +17,11 @@ * under the License. */ -import { INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE } from '../../index_patterns'; +import { indexPatterns } from '../../../../../plugins/data/public'; -export const INDEX_ILLEGAL_CHARACTERS_VISIBLE = [...INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE, '*']; +export const INDEX_ILLEGAL_CHARACTERS_VISIBLE = [...indexPatterns.ILLEGAL_CHARACTERS_VISIBLE, '*']; // Insert the comma into the middle, so it doesn't look as if it has grammatical meaning when // these characters are rendered in the UI. -const insertionIndex = Math.floor(INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE.length / 2); +const insertionIndex = Math.floor(indexPatterns.ILLEGAL_CHARACTERS_VISIBLE.length / 2); INDEX_ILLEGAL_CHARACTERS_VISIBLE.splice(insertionIndex, 0, ','); diff --git a/src/legacy/ui/public/management/components/__snapshots__/sidebar_nav.test.ts.snap b/src/legacy/ui/public/management/components/__snapshots__/sidebar_nav.test.ts.snap deleted file mode 100644 index 3364bee33a544..0000000000000 --- a/src/legacy/ui/public/management/components/__snapshots__/sidebar_nav.test.ts.snap +++ /dev/null @@ -1,24 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Management filters and filters and maps section objects into SidebarNav items 1`] = ` -Array [ - Object { - "data-test-subj": "activeSection", - "href": undefined, - "icon": null, - "id": "activeSection", - "isSelected": false, - "items": Array [ - Object { - "data-test-subj": "item", - "href": undefined, - "icon": null, - "id": "item", - "isSelected": false, - "name": "item", - }, - ], - "name": "activeSection", - }, -] -`; diff --git a/src/legacy/ui/public/management/components/index.ts b/src/legacy/ui/public/management/components/index.ts deleted file mode 100644 index e3a18ec4e2698..0000000000000 --- a/src/legacy/ui/public/management/components/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -export { SidebarNav } from './sidebar_nav'; diff --git a/src/legacy/ui/public/management/components/sidebar_nav.test.ts b/src/legacy/ui/public/management/components/sidebar_nav.test.ts deleted file mode 100644 index e02cc7d2901b6..0000000000000 --- a/src/legacy/ui/public/management/components/sidebar_nav.test.ts +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { IndexedArray } from '../../indexed_array'; -import { sideNavItems } from '../components/sidebar_nav'; - -const toIndexedArray = (initialSet: any[]) => - new IndexedArray({ - index: ['id'], - order: ['order'], - initialSet, - }); - -const activeProps = { visible: true, disabled: false }; -const disabledProps = { visible: true, disabled: true }; -const notVisibleProps = { visible: false, disabled: false }; - -const visibleItem = { display: 'item', id: 'item', ...activeProps }; - -const notVisibleSection = { - display: 'Not visible', - id: 'not-visible', - visibleItems: toIndexedArray([visibleItem]), - ...notVisibleProps, -}; -const disabledSection = { - display: 'Disabled', - id: 'disabled', - visibleItems: toIndexedArray([visibleItem]), - ...disabledProps, -}; -const noItemsSection = { - display: 'No items', - id: 'no-items', - visibleItems: toIndexedArray([]), - ...activeProps, -}; -const noActiveItemsSection = { - display: 'No active items', - id: 'no-active-items', - visibleItems: toIndexedArray([ - { display: 'disabled', id: 'disabled', ...disabledProps }, - { display: 'notVisible', id: 'notVisible', ...notVisibleProps }, - ]), - ...activeProps, -}; -const activeSection = { - display: 'activeSection', - id: 'activeSection', - visibleItems: toIndexedArray([visibleItem]), - ...activeProps, -}; - -const managementSections = [ - notVisibleSection, - disabledSection, - noItemsSection, - noActiveItemsSection, - activeSection, -]; - -describe('Management', () => { - it('filters and filters and maps section objects into SidebarNav items', () => { - expect(sideNavItems(managementSections, 'active-item-id')).toMatchSnapshot(); - }); -}); diff --git a/src/legacy/ui/public/management/components/sidebar_nav.tsx b/src/legacy/ui/public/management/components/sidebar_nav.tsx deleted file mode 100644 index cd3d85090dce0..0000000000000 --- a/src/legacy/ui/public/management/components/sidebar_nav.tsx +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { EuiIcon, EuiSideNav, IconType, EuiScreenReaderOnly } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { i18n } from '@kbn/i18n'; -import React from 'react'; -import { IndexedArray } from 'ui/indexed_array'; - -interface Subsection { - disabled: boolean; - visible: boolean; - id: string; - display: string; - url?: string; - icon?: IconType; -} -interface Section extends Subsection { - visibleItems: IndexedArray; -} - -const sectionVisible = (section: Subsection) => !section.disabled && section.visible; -const sectionToNav = (selectedId: string) => ({ display, id, url, icon }: Subsection) => ({ - id, - name: display, - icon: icon ? : null, - isSelected: selectedId === id, - href: url, - 'data-test-subj': id, -}); - -export const sideNavItems = (sections: Section[], selectedId: string) => - sections - .filter(sectionVisible) - .filter(section => section.visibleItems.filter(sectionVisible).length) - .map(section => ({ - items: section.visibleItems.filter(sectionVisible).map(sectionToNav(selectedId)), - ...sectionToNav(selectedId)(section), - })); - -interface SidebarNavProps { - sections: Section[]; - selectedId: string; -} - -interface SidebarNavState { - isSideNavOpenOnMobile: boolean; -} - -export class SidebarNav extends React.Component { - constructor(props: SidebarNavProps) { - super(props); - this.state = { - isSideNavOpenOnMobile: false, - }; - } - - public render() { - const HEADER_ID = 'management-nav-header'; - - return ( - <> - -

- {i18n.translate('common.ui.management.nav.label', { - defaultMessage: 'Management', - })} -

-
- - - ); - } - - private renderMobileTitle() { - return ; - } - - private toggleOpenOnMobile = () => { - this.setState({ - isSideNavOpenOnMobile: !this.state.isSideNavOpenOnMobile, - }); - }; -} diff --git a/src/legacy/ui/public/management/index.js b/src/legacy/ui/public/management/index.js index ed8ddb65315e2..b2f1946dbc59c 100644 --- a/src/legacy/ui/public/management/index.js +++ b/src/legacy/ui/public/management/index.js @@ -23,8 +23,6 @@ export { PAGE_FOOTER_COMPONENT, } from '../../../core_plugins/kibana/public/management/sections/settings/components/default_component_registry'; export { registerSettingsComponent } from '../../../core_plugins/kibana/public/management/sections/settings/components/component_registry'; -export { SidebarNav } from './components'; export { MANAGEMENT_BREADCRUMB } from './breadcrumbs'; - import { npStart } from 'ui/new_platform'; export const management = npStart.plugins.management.legacy; diff --git a/src/legacy/ui/public/new_platform/new_platform.karma_mock.js b/src/legacy/ui/public/new_platform/new_platform.karma_mock.js index 3d4292cef27f4..06424ea48a40f 100644 --- a/src/legacy/ui/public/new_platform/new_platform.karma_mock.js +++ b/src/legacy/ui/public/new_platform/new_platform.karma_mock.js @@ -148,6 +148,8 @@ export const npStart = { legacy: { getSection: () => ({ register: sinon.fake(), + deregister: sinon.fake(), + hasItem: sinon.fake(), }), }, }, diff --git a/src/legacy/ui/public/new_platform/new_platform.test.ts b/src/legacy/ui/public/new_platform/new_platform.test.ts index cd1af311d4eff..e050ffd5b530c 100644 --- a/src/legacy/ui/public/new_platform/new_platform.test.ts +++ b/src/legacy/ui/public/new_platform/new_platform.test.ts @@ -62,6 +62,7 @@ describe('ui/new_platform', () => { expect(mountMock).toHaveBeenCalledWith({ element: elementMock[0], appBasePath: '/test/base/path/app/test', + onAppLeave: expect.any(Function), }); }); @@ -82,6 +83,7 @@ describe('ui/new_platform', () => { expect(mountMock).toHaveBeenCalledWith(expect.any(Object), { element: elementMock[0], appBasePath: '/test/base/path/app/test', + onAppLeave: expect.any(Function), }); }); diff --git a/src/legacy/ui/public/new_platform/new_platform.ts b/src/legacy/ui/public/new_platform/new_platform.ts index df3fa7c6ea466..c948565bb0835 100644 --- a/src/legacy/ui/public/new_platform/new_platform.ts +++ b/src/legacy/ui/public/new_platform/new_platform.ts @@ -124,7 +124,11 @@ export const legacyAppRegister = (app: App) => { // Root controller cannot return a Promise so use an internal async function and call it immediately (async () => { - const params = { element, appBasePath: npSetup.core.http.basePath.prepend(`/app/${app.id}`) }; + const params = { + element, + appBasePath: npSetup.core.http.basePath.prepend(`/app/${app.id}`), + onAppLeave: () => undefined, + }; const unmount = isAppMountDeprecated(app.mount) ? await app.mount({ core: npStart.core }, params) : await app.mount(params); diff --git a/src/legacy/ui/public/registry/doc_views.ts b/src/legacy/ui/public/registry/doc_views.ts deleted file mode 100644 index bf1e8416ae66d..0000000000000 --- a/src/legacy/ui/public/registry/doc_views.ts +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ -import { convertDirectiveToRenderFn } from './doc_views_helpers'; -import { DocView, DocViewInput, ElasticSearchHit, DocViewInputFn } from './doc_views_types'; - -export { DocViewRenderProps, DocView, DocViewRenderFn } from './doc_views_types'; - -export interface DocViewsRegistry { - docViews: DocView[]; - addDocView: (docView: DocViewInput) => void; - getDocViewsSorted: (hit: ElasticSearchHit) => DocView[]; -} - -export const docViews: DocView[] = []; - -/** - * Extends and adds the given doc view to the registry array - */ -export function addDocView(docView: DocViewInput) { - if (docView.directive) { - // convert angular directive to render function for backwards compatibility - docView.render = convertDirectiveToRenderFn(docView.directive); - } - if (typeof docView.shouldShow !== 'function') { - docView.shouldShow = () => true; - } - docViews.push(docView as DocView); -} - -/** - * Empty array of doc views for testing - */ -export function emptyDocViews() { - docViews.length = 0; -} - -/** - * Returns a sorted array of doc_views for rendering tabs - */ -export function getDocViewsSorted(hit: ElasticSearchHit): DocView[] { - return docViews - .filter(docView => docView.shouldShow(hit)) - .sort((a, b) => (Number(a.order) > Number(b.order) ? 1 : -1)); -} -/** - * Provider for compatibility with 3rd Party plugins - */ -export const DocViewsRegistryProvider = { - register: (docViewRaw: DocViewInput | DocViewInputFn) => { - const docView = typeof docViewRaw === 'function' ? docViewRaw() : docViewRaw; - addDocView(docView); - }, -}; diff --git a/src/legacy/ui/public/saved_objects/helpers/apply_es_resp.ts b/src/legacy/ui/public/saved_objects/helpers/apply_es_resp.ts index bec0a1d96ba92..ee49b7495f2af 100644 --- a/src/legacy/ui/public/saved_objects/helpers/apply_es_resp.ts +++ b/src/legacy/ui/public/saved_objects/helpers/apply_es_resp.ts @@ -20,7 +20,7 @@ import _ from 'lodash'; import { EsResponse, SavedObject, SavedObjectConfig } from 'ui/saved_objects/types'; import { parseSearchSource } from 'ui/saved_objects/helpers/parse_search_source'; import { expandShorthand, SavedObjectNotFound } from '../../../../../plugins/kibana_utils/public'; -import { IndexPattern } from '../../../../core_plugins/data/public'; +import { IndexPattern } from '../../../../../plugins/data/public'; /** * A given response of and ElasticSearch containing a plain saved object is applied to the given diff --git a/src/legacy/ui/public/saved_objects/helpers/string_utils.test.ts b/src/legacy/ui/public/saved_objects/helpers/string_utils.test.ts new file mode 100644 index 0000000000000..5b108a4cc0180 --- /dev/null +++ b/src/legacy/ui/public/saved_objects/helpers/string_utils.test.ts @@ -0,0 +1,32 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { StringUtils } from './string_utils'; + +describe('StringUtils class', () => { + describe('static upperFirst', () => { + test('should converts the first character of string to upper case', () => { + expect(StringUtils.upperFirst()).toBe(''); + expect(StringUtils.upperFirst('')).toBe(''); + + expect(StringUtils.upperFirst('Fred')).toBe('Fred'); + expect(StringUtils.upperFirst('fred')).toBe('Fred'); + expect(StringUtils.upperFirst('FRED')).toBe('FRED'); + }); + }); +}); diff --git a/src/legacy/ui/public/utils/string_utils.ts b/src/legacy/ui/public/saved_objects/helpers/string_utils.ts similarity index 94% rename from src/legacy/ui/public/utils/string_utils.ts rename to src/legacy/ui/public/saved_objects/helpers/string_utils.ts index 22a57aeb07933..fb10b792b7e69 100644 --- a/src/legacy/ui/public/utils/string_utils.ts +++ b/src/legacy/ui/public/saved_objects/helpers/string_utils.ts @@ -23,7 +23,7 @@ export class StringUtils { * @param str {string} * @returns {string} */ - public static upperFirst(str: string): string { + public static upperFirst(str: string = ''): string { return str ? str.charAt(0).toUpperCase() + str.slice(1) : ''; } } diff --git a/src/legacy/ui/public/saved_objects/saved_object_loader.ts b/src/legacy/ui/public/saved_objects/saved_object_loader.ts index eb880ce5380c0..7784edc9ad528 100644 --- a/src/legacy/ui/public/saved_objects/saved_object_loader.ts +++ b/src/legacy/ui/public/saved_objects/saved_object_loader.ts @@ -18,7 +18,7 @@ */ import { SavedObject } from 'ui/saved_objects/types'; import { ChromeStart, SavedObjectsClientContract, SavedObjectsFindOptions } from 'kibana/public'; -import { StringUtils } from '../utils/string_utils'; +import { StringUtils } from './helpers/string_utils'; /** * The SavedObjectLoader class provides some convenience functions diff --git a/src/legacy/ui/public/state_management/app_state.js b/src/legacy/ui/public/state_management/app_state.js index 4bf386febf836..e253d49c04131 100644 --- a/src/legacy/ui/public/state_management/app_state.js +++ b/src/legacy/ui/public/state_management/app_state.js @@ -31,7 +31,6 @@ import { uiModules } from '../modules'; import { StateProvider } from './state'; import '../persisted_state'; import { createLegacyClass } from '../utils/legacy_class'; -import { callEach } from '../utils/function'; const urlParam = '_a'; @@ -62,7 +61,8 @@ export function AppStateProvider(Private, $location, $injector) { AppState.prototype.destroy = function() { AppState.Super.prototype.destroy.call(this); AppState.getAppState._set(null); - callEach(eventUnsubscribers); + + eventUnsubscribers.forEach(listener => listener()); }; /** diff --git a/src/legacy/ui/public/state_management/state.js b/src/legacy/ui/public/state_management/state.js index a9898303fa5be..289d4b8006cba 100644 --- a/src/legacy/ui/public/state_management/state.js +++ b/src/legacy/ui/public/state_management/state.js @@ -29,12 +29,11 @@ import _ from 'lodash'; import { i18n } from '@kbn/i18n'; import angular from 'angular'; import rison from 'rison-node'; -import { applyDiff } from '../utils/diff_object'; +import { applyDiff } from './utils/diff_object'; import { EventsProvider } from '../events'; import { fatalError, toastNotifications } from '../notify'; import './config_provider'; import { createLegacyClass } from '../utils/legacy_class'; -import { callEach } from '../utils/function'; import { hashedItemStore, isStateHash, @@ -66,7 +65,7 @@ export function StateProvider( this._hashedItemStore = _hashedItemStore; // When the URL updates we need to fetch the values from the URL - this._cleanUpListeners = _.partial(callEach, [ + this._cleanUpListeners = [ // partial route update, no app reload $rootScope.$on('$routeUpdate', () => { this.fetch(); @@ -85,7 +84,7 @@ export function StateProvider( this.fetch(); } }), - ]); + ]; // Initialize the State with fetch this.fetch(); @@ -242,7 +241,9 @@ export function StateProvider( */ State.prototype.destroy = function() { this.off(); // removes all listeners - this._cleanUpListeners(); // Removes the $routeUpdate listener + + // Removes the $routeUpdate listener + this._cleanUpListeners.forEach(listener => listener(this)); }; State.prototype.setDefaults = function(defaults) { diff --git a/src/legacy/ui/public/state_management/utils/diff_object.test.ts b/src/legacy/ui/public/state_management/utils/diff_object.test.ts new file mode 100644 index 0000000000000..d93fa0fe5a169 --- /dev/null +++ b/src/legacy/ui/public/state_management/utils/diff_object.test.ts @@ -0,0 +1,104 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { cloneDeep } from 'lodash'; +import { applyDiff } from './diff_object'; + +describe('diff_object', () => { + test('should list the removed keys', () => { + const target = { test: 'foo' }; + const source = { foo: 'test' }; + const results = applyDiff(target, source); + + expect(results).toHaveProperty('removed'); + expect(results.removed).toEqual(['test']); + }); + + test('should list the changed keys', () => { + const target = { foo: 'bar' }; + const source = { foo: 'test' }; + const results = applyDiff(target, source); + + expect(results).toHaveProperty('changed'); + expect(results.changed).toEqual(['foo']); + }); + + test('should list the added keys', () => { + const target = {}; + const source = { foo: 'test' }; + const results = applyDiff(target, source); + + expect(results).toHaveProperty('added'); + expect(results.added).toEqual(['foo']); + }); + + test('should list all the keys that are change or removed', () => { + const target = { foo: 'bar', test: 'foo' }; + const source = { foo: 'test' }; + const results = applyDiff(target, source); + + expect(results).toHaveProperty('keys'); + expect(results.keys).toEqual(['foo', 'test']); + }); + + test('should ignore functions', () => { + const target = { foo: 'bar', test: 'foo' }; + const source = { foo: 'test', fn: () => {} }; + + applyDiff(target, source); + + expect(target).not.toHaveProperty('fn'); + }); + + test('should ignore underscores', () => { + const target = { foo: 'bar', test: 'foo' }; + const source = { foo: 'test', _private: 'foo' }; + + applyDiff(target, source); + + expect(target).not.toHaveProperty('_private'); + }); + + test('should ignore dollar signs', () => { + const target = { foo: 'bar', test: 'foo' }; + const source = { foo: 'test', $private: 'foo' }; + + applyDiff(target, source); + + expect(target).not.toHaveProperty('$private'); + }); + + test('should not list any changes for similar objects', () => { + const target = { foo: 'bar', test: 'foo' }; + const source = { foo: 'bar', test: 'foo', $private: 'foo' }; + const results = applyDiff(target, source); + + expect(results.changed).toEqual([]); + }); + + test('should only change keys that actually changed', () => { + const obj = { message: 'foo' }; + const target = { obj, message: 'foo' }; + const source = { obj: cloneDeep(obj), message: 'test' }; + + applyDiff(target, source); + + expect(target.obj).toBe(obj); + }); +}); diff --git a/src/legacy/ui/public/state_management/utils/diff_object.ts b/src/legacy/ui/public/state_management/utils/diff_object.ts new file mode 100644 index 0000000000000..2590e2271f771 --- /dev/null +++ b/src/legacy/ui/public/state_management/utils/diff_object.ts @@ -0,0 +1,75 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import { keys, isFunction, difference, filter, union, pick, each, assign, isEqual } from 'lodash'; + +export interface IDiffObject { + removed: string[]; + added: string[]; + changed: string[]; + keys: string[]; +} + +/** + * Filter the private vars + * @param {string} key The keys + * @returns {boolean} + */ +const filterPrivateAndMethods = function(obj: Record) { + return function(key: string) { + if (isFunction(obj[key])) return false; + if (key.charAt(0) === '$') return false; + return key.charAt(0) !== '_'; + }; +}; + +export function applyDiff(target: Record, source: Record) { + const diff: IDiffObject = { + removed: [], + added: [], + changed: [], + keys: [], + }; + + const targetKeys = keys(target).filter(filterPrivateAndMethods(target)); + const sourceKeys = keys(source).filter(filterPrivateAndMethods(source)); + + // Find the keys to be removed + diff.removed = difference(targetKeys, sourceKeys); + + // Find the keys to be added + diff.added = difference(sourceKeys, targetKeys); + + // Find the keys that will be changed + diff.changed = filter(sourceKeys, key => !isEqual(target[key], source[key])); + + // Make a list of all the keys that are changing + diff.keys = union(diff.changed, diff.removed, diff.added); + + // Remove all the keys + each(diff.removed, key => { + delete target[key]; + }); + + // Assign the changed to the source to the target + assign(target, pick(source, diff.changed)); + // Assign the added to the source to the target + assign(target, pick(source, diff.added)); + + return diff; +} diff --git a/src/legacy/ui/public/time_buckets/time_buckets.js b/src/legacy/ui/public/time_buckets/time_buckets.js index 92de88b47e3c3..96ba4bfb2ac2c 100644 --- a/src/legacy/ui/public/time_buckets/time_buckets.js +++ b/src/legacy/ui/public/time_buckets/time_buckets.js @@ -20,13 +20,12 @@ import _ from 'lodash'; import moment from 'moment'; import { npStart } from 'ui/new_platform'; -import { parseInterval } from '../utils/parse_interval'; import { calcAutoIntervalLessThan, calcAutoIntervalNear } from './calc_auto_interval'; import { convertDurationToNormalizedEsInterval, convertIntervalToEsInterval, } from './calc_es_interval'; -import { FIELD_FORMAT_IDS } from '../../../../plugins/data/public'; +import { FIELD_FORMAT_IDS, parseInterval } from '../../../../plugins/data/public'; const getConfig = (...args) => npStart.core.uiSettings.get(...args); diff --git a/src/legacy/ui/public/utils/__tests__/collection.js b/src/legacy/ui/public/utils/__tests__/collection.js deleted file mode 100644 index 402f0387e53ce..0000000000000 --- a/src/legacy/ui/public/utils/__tests__/collection.js +++ /dev/null @@ -1,200 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import expect from '@kbn/expect'; -import { groupBy } from 'lodash'; -import { move, pushAll, organizeBy } from '../collection'; - -describe('collection', () => { - describe('move', function() { - it('accepts previous from->to syntax', function() { - const list = [1, 1, 1, 1, 1, 1, 1, 1, 8, 1, 1]; - - expect(list[3]).to.be(1); - expect(list[8]).to.be(8); - - move(list, 8, 3); - - expect(list[8]).to.be(1); - expect(list[3]).to.be(8); - }); - - it('moves an object up based on a function callback', function() { - const list = [1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1]; - - expect(list[4]).to.be(0); - expect(list[5]).to.be(1); - expect(list[6]).to.be(0); - - move(list, 5, false, function(v) { - return v === 0; - }); - - expect(list[4]).to.be(1); - expect(list[5]).to.be(0); - expect(list[6]).to.be(0); - }); - - it('moves an object down based on a function callback', function() { - const list = [1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1]; - - expect(list[4]).to.be(0); - expect(list[5]).to.be(1); - expect(list[6]).to.be(0); - - move(list, 5, true, function(v) { - return v === 0; - }); - - expect(list[4]).to.be(0); - expect(list[5]).to.be(0); - expect(list[6]).to.be(1); - }); - - it('moves an object up based on a where callback', function() { - const list = [ - { v: 1 }, - { v: 1 }, - { v: 1 }, - { v: 1 }, - { v: 0 }, - { v: 1 }, - { v: 0 }, - { v: 1 }, - { v: 1 }, - { v: 1 }, - { v: 1 }, - ]; - - expect(list[4]).to.have.property('v', 0); - expect(list[5]).to.have.property('v', 1); - expect(list[6]).to.have.property('v', 0); - - move(list, 5, false, { v: 0 }); - - expect(list[4]).to.have.property('v', 1); - expect(list[5]).to.have.property('v', 0); - expect(list[6]).to.have.property('v', 0); - }); - - it('moves an object down based on a where callback', function() { - const list = [ - { v: 1 }, - { v: 1 }, - { v: 1 }, - { v: 1 }, - { v: 0 }, - { v: 1 }, - { v: 0 }, - { v: 1 }, - { v: 1 }, - { v: 1 }, - { v: 1 }, - ]; - - expect(list[4]).to.have.property('v', 0); - expect(list[5]).to.have.property('v', 1); - expect(list[6]).to.have.property('v', 0); - - move(list, 5, true, { v: 0 }); - - expect(list[4]).to.have.property('v', 0); - expect(list[5]).to.have.property('v', 0); - expect(list[6]).to.have.property('v', 1); - }); - - it('moves an object down based on a pluck callback', function() { - const list = [ - { id: 0, normal: true }, - { id: 1, normal: true }, - { id: 2, normal: true }, - { id: 3, normal: true }, - { id: 4, normal: true }, - { id: 5, normal: false }, - { id: 6, normal: true }, - { id: 7, normal: true }, - { id: 8, normal: true }, - { id: 9, normal: true }, - ]; - - expect(list[4]).to.have.property('id', 4); - expect(list[5]).to.have.property('id', 5); - expect(list[6]).to.have.property('id', 6); - - move(list, 5, true, 'normal'); - - expect(list[4]).to.have.property('id', 4); - expect(list[5]).to.have.property('id', 6); - expect(list[6]).to.have.property('id', 5); - }); - }); - - describe('pushAll', function() { - it('pushes an entire array into another', function() { - const a = [1, 2, 3, 4]; - const b = [5, 6, 7, 8]; - - const output = pushAll(b, a); - expect(output).to.be(a); - expect(a).to.eql([1, 2, 3, 4, 5, 6, 7, 8]); - expect(b).to.eql([5, 6, 7, 8]); - }); - }); - - describe('organizeBy', function() { - it('it works', function() { - const col = [ - { - name: 'one', - roles: ['user', 'admin', 'owner'], - }, - { - name: 'two', - roles: ['user'], - }, - { - name: 'three', - roles: ['user'], - }, - { - name: 'four', - roles: ['user', 'admin'], - }, - ]; - - const resp = organizeBy(col, 'roles'); - expect(resp).to.have.property('user'); - expect(resp.user).to.have.length(4); - - expect(resp).to.have.property('admin'); - expect(resp.admin).to.have.length(2); - - expect(resp).to.have.property('owner'); - expect(resp.owner).to.have.length(1); - }); - - it('behaves just like groupBy in normal scenarios', function() { - const col = [{ name: 'one' }, { name: 'two' }, { name: 'three' }, { name: 'four' }]; - - const orgs = organizeBy(col, 'name'); - const groups = groupBy(col, 'name'); - expect(orgs).to.eql(groups); - }); - }); -}); diff --git a/src/legacy/ui/public/utils/__tests__/diff_object.js b/src/legacy/ui/public/utils/__tests__/diff_object.js deleted file mode 100644 index 8459aa60436b1..0000000000000 --- a/src/legacy/ui/public/utils/__tests__/diff_object.js +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import expect from '@kbn/expect'; -import _ from 'lodash'; -import { applyDiff } from '../diff_object'; - -describe('ui/utils/diff_object', function() { - it('should list the removed keys', function() { - const target = { test: 'foo' }; - const source = { foo: 'test' }; - const results = applyDiff(target, source); - expect(results).to.have.property('removed'); - expect(results.removed).to.eql(['test']); - }); - - it('should list the changed keys', function() { - const target = { foo: 'bar' }; - const source = { foo: 'test' }; - const results = applyDiff(target, source); - expect(results).to.have.property('changed'); - expect(results.changed).to.eql(['foo']); - }); - - it('should list the added keys', function() { - const target = {}; - const source = { foo: 'test' }; - const results = applyDiff(target, source); - expect(results).to.have.property('added'); - expect(results.added).to.eql(['foo']); - }); - - it('should list all the keys that are change or removed', function() { - const target = { foo: 'bar', test: 'foo' }; - const source = { foo: 'test' }; - const results = applyDiff(target, source); - expect(results).to.have.property('keys'); - expect(results.keys).to.eql(['foo', 'test']); - }); - - it('should ignore functions', function() { - const target = { foo: 'bar', test: 'foo' }; - const source = { foo: 'test', fn: _.noop }; - applyDiff(target, source); - expect(target).to.not.have.property('fn'); - }); - - it('should ignore underscores', function() { - const target = { foo: 'bar', test: 'foo' }; - const source = { foo: 'test', _private: 'foo' }; - applyDiff(target, source); - expect(target).to.not.have.property('_private'); - }); - - it('should ignore dollar signs', function() { - const target = { foo: 'bar', test: 'foo' }; - const source = { foo: 'test', $private: 'foo' }; - applyDiff(target, source); - expect(target).to.not.have.property('$private'); - }); - - it('should not list any changes for similar objects', function() { - const target = { foo: 'bar', test: 'foo' }; - const source = { foo: 'bar', test: 'foo', $private: 'foo' }; - const results = applyDiff(target, source); - expect(results.changed).to.be.empty(); - }); - - it('should only change keys that actually changed', function() { - const obj = { message: 'foo' }; - const target = { obj: obj, message: 'foo' }; - const source = { obj: _.cloneDeep(obj), message: 'test' }; - applyDiff(target, source); - expect(target.obj).to.be(obj); - }); -}); diff --git a/src/legacy/ui/public/utils/__tests__/parse_interval.js b/src/legacy/ui/public/utils/__tests__/parse_interval.js deleted file mode 100644 index a33b0ab958072..0000000000000 --- a/src/legacy/ui/public/utils/__tests__/parse_interval.js +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { parseInterval } from '../parse_interval'; -import expect from '@kbn/expect'; - -describe('parseInterval', function() { - it('should correctly parse an interval containing unit and value', function() { - let duration = parseInterval('1d'); - expect(duration.as('d')).to.be(1); - - duration = parseInterval('2y'); - expect(duration.as('y')).to.be(2); - - duration = parseInterval('5M'); - expect(duration.as('M')).to.be(5); - - duration = parseInterval('5m'); - expect(duration.as('m')).to.be(5); - - duration = parseInterval('250ms'); - expect(duration.as('ms')).to.be(250); - - duration = parseInterval('100s'); - expect(duration.as('s')).to.be(100); - - duration = parseInterval('23d'); - expect(duration.as('d')).to.be(23); - - duration = parseInterval('52w'); - expect(duration.as('w')).to.be(52); - }); - - it('should correctly parse fractional intervals containing unit and value', function() { - let duration = parseInterval('1.5w'); - expect(duration.as('w')).to.be(1.5); - - duration = parseInterval('2.35y'); - expect(duration.as('y')).to.be(2.35); - }); - - it('should correctly bubble up intervals which are less than 1', function() { - let duration = parseInterval('0.5y'); - expect(duration.as('d')).to.be(183); - - duration = parseInterval('0.5d'); - expect(duration.as('h')).to.be(12); - }); - - it('should correctly parse a unit in an interval only', function() { - let duration = parseInterval('ms'); - expect(duration.as('ms')).to.be(1); - - duration = parseInterval('d'); - expect(duration.as('d')).to.be(1); - - duration = parseInterval('m'); - expect(duration.as('m')).to.be(1); - - duration = parseInterval('y'); - expect(duration.as('y')).to.be(1); - - duration = parseInterval('M'); - expect(duration.as('M')).to.be(1); - }); - - it('should return null for an invalid interval', function() { - let duration = parseInterval(''); - expect(duration).to.not.be.ok(); - - duration = parseInterval(null); - expect(duration).to.not.be.ok(); - - duration = parseInterval('234asdf'); - expect(duration).to.not.be.ok(); - }); -}); diff --git a/src/legacy/ui/public/utils/__tests__/sort_prefix_first.js b/src/legacy/ui/public/utils/__tests__/sort_prefix_first.js deleted file mode 100644 index 721de95cbd27d..0000000000000 --- a/src/legacy/ui/public/utils/__tests__/sort_prefix_first.js +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import expect from '@kbn/expect'; -import { sortPrefixFirst } from '../sort_prefix_first'; - -describe('sortPrefixFirst', function() { - it('should return the original unmodified array if no prefix is provided', function() { - const array = ['foo', 'bar', 'baz']; - const result = sortPrefixFirst(array); - expect(result).to.be(array); - expect(result).to.eql(['foo', 'bar', 'baz']); - }); - - it('should sort items that match the prefix first without modifying the original array', function() { - const array = ['foo', 'bar', 'baz']; - const result = sortPrefixFirst(array, 'b'); - expect(result).to.not.be(array); - expect(result).to.eql(['bar', 'baz', 'foo']); - expect(array).to.eql(['foo', 'bar', 'baz']); - }); - - it('should not modify the order of the array other than matching prefix without modifying the original array', function() { - const array = ['foo', 'bar', 'baz', 'qux', 'quux']; - const result = sortPrefixFirst(array, 'b'); - expect(result).to.not.be(array); - expect(result).to.eql(['bar', 'baz', 'foo', 'qux', 'quux']); - expect(array).to.eql(['foo', 'bar', 'baz', 'qux', 'quux']); - }); - - it('should sort objects by property if provided', function() { - const array = [ - { name: 'foo' }, - { name: 'bar' }, - { name: 'baz' }, - { name: 'qux' }, - { name: 'quux' }, - ]; - const result = sortPrefixFirst(array, 'b', 'name'); - expect(result).to.not.be(array); - expect(result).to.eql([ - { name: 'bar' }, - { name: 'baz' }, - { name: 'foo' }, - { name: 'qux' }, - { name: 'quux' }, - ]); - expect(array).to.eql([ - { name: 'foo' }, - { name: 'bar' }, - { name: 'baz' }, - { name: 'qux' }, - { name: 'quux' }, - ]); - }); - - it('should handle numbers', function() { - const array = [1, 50, 5]; - const result = sortPrefixFirst(array, 5); - expect(result).to.not.be(array); - expect(result).to.eql([50, 5, 1]); - }); - - it('should handle mixed case', function() { - const array = ['Date Histogram', 'Histogram']; - const prefix = 'histo'; - const result = sortPrefixFirst(array, prefix); - expect(result).to.not.be(array); - expect(result).to.eql(['Histogram', 'Date Histogram']); - }); -}); diff --git a/src/legacy/ui/public/utils/case_conversion.ts b/src/legacy/ui/public/utils/case_conversion.ts deleted file mode 100644 index e0c4cad6b4e94..0000000000000 --- a/src/legacy/ui/public/utils/case_conversion.ts +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -// TODO: This file is copied from src/legacy/utils/case_conversion.ts -// because TS-imports from utils in ui are currently not possible. -// When the build process is updated, this file can be removed - -import _ from 'lodash'; - -export function keysToSnakeCaseShallow(object: Record) { - return _.mapKeys(object, (value, key) => { - return _.snakeCase(key); - }); -} - -export function keysToCamelCaseShallow(object: Record) { - return _.mapKeys(object, (value, key) => { - return _.camelCase(key); - }); -} diff --git a/src/legacy/ui/public/utils/collection.test.ts b/src/legacy/ui/public/utils/collection.test.ts new file mode 100644 index 0000000000000..0841e3554c0d0 --- /dev/null +++ b/src/legacy/ui/public/utils/collection.test.ts @@ -0,0 +1,141 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { move } from './collection'; + +describe('collection', () => { + describe('move', () => { + test('accepts previous from->to syntax', () => { + const list = [1, 1, 1, 1, 1, 1, 1, 1, 8, 1, 1]; + + expect(list[3]).toBe(1); + expect(list[8]).toBe(8); + + move(list, 8, 3); + + expect(list[8]).toBe(1); + expect(list[3]).toBe(8); + }); + + test('moves an object up based on a function callback', () => { + const list = [1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1]; + + expect(list[4]).toBe(0); + expect(list[5]).toBe(1); + expect(list[6]).toBe(0); + + move(list, 5, false, (v: any) => v === 0); + + expect(list[4]).toBe(1); + expect(list[5]).toBe(0); + expect(list[6]).toBe(0); + }); + + test('moves an object down based on a function callback', () => { + const list = [1, 1, 1, 1, 0, 1, 0, 1, 1, 1, 1]; + + expect(list[4]).toBe(0); + expect(list[5]).toBe(1); + expect(list[6]).toBe(0); + + move(list, 5, true, (v: any) => v === 0); + + expect(list[4]).toBe(0); + expect(list[5]).toBe(0); + expect(list[6]).toBe(1); + }); + + test('moves an object up based on a where callback', () => { + const list = [ + { v: 1 }, + { v: 1 }, + { v: 1 }, + { v: 1 }, + { v: 0 }, + { v: 1 }, + { v: 0 }, + { v: 1 }, + { v: 1 }, + { v: 1 }, + { v: 1 }, + ]; + + expect(list[4]).toHaveProperty('v', 0); + expect(list[5]).toHaveProperty('v', 1); + expect(list[6]).toHaveProperty('v', 0); + + move(list, 5, false, { v: 0 }); + + expect(list[4]).toHaveProperty('v', 1); + expect(list[5]).toHaveProperty('v', 0); + expect(list[6]).toHaveProperty('v', 0); + }); + + test('moves an object down based on a where callback', () => { + const list = [ + { v: 1 }, + { v: 1 }, + { v: 1 }, + { v: 1 }, + { v: 0 }, + { v: 1 }, + { v: 0 }, + { v: 1 }, + { v: 1 }, + { v: 1 }, + { v: 1 }, + ]; + + expect(list[4]).toHaveProperty('v', 0); + expect(list[5]).toHaveProperty('v', 1); + expect(list[6]).toHaveProperty('v', 0); + + move(list, 5, true, { v: 0 }); + + expect(list[4]).toHaveProperty('v', 0); + expect(list[5]).toHaveProperty('v', 0); + expect(list[6]).toHaveProperty('v', 1); + }); + + test('moves an object down based on a pluck callback', () => { + const list = [ + { id: 0, normal: true }, + { id: 1, normal: true }, + { id: 2, normal: true }, + { id: 3, normal: true }, + { id: 4, normal: true }, + { id: 5, normal: false }, + { id: 6, normal: true }, + { id: 7, normal: true }, + { id: 8, normal: true }, + { id: 9, normal: true }, + ]; + + expect(list[4]).toHaveProperty('id', 4); + expect(list[5]).toHaveProperty('id', 5); + expect(list[6]).toHaveProperty('id', 6); + + move(list, 5, true, 'normal'); + + expect(list[4]).toHaveProperty('id', 4); + expect(list[5]).toHaveProperty('id', 6); + expect(list[6]).toHaveProperty('id', 5); + }); + }); +}); diff --git a/src/legacy/ui/public/utils/collection.ts b/src/legacy/ui/public/utils/collection.ts index 61a7388575c93..45e5a0704c37b 100644 --- a/src/legacy/ui/public/utils/collection.ts +++ b/src/legacy/ui/public/utils/collection.ts @@ -33,10 +33,10 @@ import _ from 'lodash'; * @return {array} - the objs argument */ export function move( - objs: object[], + objs: any[], obj: object | number, below: number | boolean, - qualifier: (object: object, index: number) => any + qualifier?: ((object: object, index: number) => any) | Record | string ): object[] { const origI = _.isNumber(obj) ? obj : objs.indexOf(obj); if (origI === -1) { @@ -50,7 +50,7 @@ export function move( } below = !!below; - qualifier = _.callback(qualifier); + qualifier = qualifier && _.callback(qualifier); const above = !below; const finder = below ? _.findIndex : _.findLastIndex; @@ -63,7 +63,7 @@ export function move( if (above && otherI >= origI) { return; } - return !!qualifier(otherAgg, otherI); + return Boolean(_.isFunction(qualifier) && qualifier(otherAgg, otherI)); }); if (targetI === -1) { @@ -74,68 +74,3 @@ export function move( objs.splice(targetI, 0, objs.splice(origI, 1)[0]); return objs; } - -/** - * Like _.groupBy, but allows specifying multiple groups for a - * single object. - * - * organizeBy([{ a: [1, 2, 3] }, { b: true, a: [1, 4] }], 'a') - * // Object {1: Array[2], 2: Array[1], 3: Array[1], 4: Array[1]} - * - * _.groupBy([{ a: [1, 2, 3] }, { b: true, a: [1, 4] }], 'a') - * // Object {'1,2,3': Array[1], '1,4': Array[1]} - * - * @param {array} collection - the list of values to organize - * @param {Function} callback - either a property name, or a callback. - * @return {object} - */ -export function organizeBy(collection: object[], callback: (obj: object) => string | string) { - const buckets: { [key: string]: object[] } = {}; - const prop = typeof callback === 'function' ? false : callback; - - function add(key: string, obj: object) { - if (!buckets[key]) { - buckets[key] = []; - } - buckets[key].push(obj); - } - - _.each(collection, (obj: object) => { - const keys = prop === false ? callback(obj) : obj[prop]; - - if (!Array.isArray(keys)) { - add(keys, obj); - return; - } - - let length = keys.length; - while (length-- > 0) { - add(keys[length], obj); - } - }); - - return buckets; -} - -/** - * Efficient and safe version of [].push(dest, source); - * - * @param {Array} source - the array to pull values from - * @param {Array} dest - the array to push values into - * @return {Array} dest - */ -export function pushAll(source: any[], dest: any[]): any[] { - const start = dest.length; - const adding = source.length; - - // allocate - http://goo.gl/e2i0S0 - dest.length = start + adding; - - // fill sparse positions - let i = -1; - while (++i < adding) { - dest[start + i] = source[i]; - } - - return dest; -} diff --git a/src/legacy/ui/public/utils/diff_object.js b/src/legacy/ui/public/utils/diff_object.js deleted file mode 100644 index ddad5e0ae42a0..0000000000000 --- a/src/legacy/ui/public/utils/diff_object.js +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; -import angular from 'angular'; - -export function applyDiff(target, source) { - const diff = {}; - - /** - * Filter the private vars - * @param {string} key The keys - * @returns {boolean} - */ - const filterPrivateAndMethods = function(obj) { - return function(key) { - if (_.isFunction(obj[key])) return false; - if (key.charAt(0) === '$') return false; - return key.charAt(0) !== '_'; - }; - }; - - const targetKeys = _.keys(target).filter(filterPrivateAndMethods(target)); - const sourceKeys = _.keys(source).filter(filterPrivateAndMethods(source)); - - // Find the keys to be removed - diff.removed = _.difference(targetKeys, sourceKeys); - - // Find the keys to be added - diff.added = _.difference(sourceKeys, targetKeys); - - // Find the keys that will be changed - diff.changed = _.filter(sourceKeys, function(key) { - return !angular.equals(target[key], source[key]); - }); - - // Make a list of all the keys that are changing - diff.keys = _.union(diff.changed, diff.removed, diff.added); - - // Remove all the keys - _.each(diff.removed, function(key) { - delete target[key]; - }); - - // Assign the changed to the source to the target - _.assign(target, _.pick(source, diff.changed)); - // Assign the added to the source to the target - _.assign(target, _.pick(source, diff.added)); - - return diff; -} diff --git a/src/legacy/ui/public/utils/function.js b/src/legacy/ui/public/utils/function.js deleted file mode 100644 index f9e7777d08bfe..0000000000000 --- a/src/legacy/ui/public/utils/function.js +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; - -/** - * Call all of the function in an array - * - * @param {array[functions]} arr - * @return {undefined} - */ -export function callEach(arr) { - return _.map(arr, function(fn) { - return _.isFunction(fn) ? fn() : undefined; - }); -} diff --git a/src/legacy/ui/public/utils/math.test.ts b/src/legacy/ui/public/utils/math.test.ts deleted file mode 100644 index 13f090e77647b..0000000000000 --- a/src/legacy/ui/public/utils/math.test.ts +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { greatestCommonDivisor, leastCommonMultiple } from './math'; - -describe('math utils', () => { - describe('greatestCommonDivisor', () => { - const tests: Array<[number, number, number]> = [ - [3, 5, 1], - [30, 36, 6], - [5, 1, 1], - [9, 9, 9], - [40, 20, 20], - [3, 0, 3], - [0, 5, 5], - [0, 0, 0], - [-9, -3, 3], - [-24, 8, 8], - [22, -7, 1], - ]; - - tests.map(([a, b, expected]) => { - it(`should return ${expected} for greatestCommonDivisor(${a}, ${b})`, () => { - expect(greatestCommonDivisor(a, b)).toBe(expected); - }); - }); - }); - - describe('leastCommonMultiple', () => { - const tests: Array<[number, number, number]> = [ - [3, 5, 15], - [1, 1, 1], - [5, 6, 30], - [3, 9, 9], - [8, 20, 40], - [5, 5, 5], - [0, 5, 0], - [-4, -5, 20], - [-2, -3, 6], - [-8, 2, 8], - [-8, 5, 40], - ]; - - tests.map(([a, b, expected]) => { - it(`should return ${expected} for leastCommonMultiple(${a}, ${b})`, () => { - expect(leastCommonMultiple(a, b)).toBe(expected); - }); - }); - }); -}); diff --git a/src/legacy/ui/public/utils/math.ts b/src/legacy/ui/public/utils/math.ts deleted file mode 100644 index e0cab4236e2b8..0000000000000 --- a/src/legacy/ui/public/utils/math.ts +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -/** - * Calculates the greates common divisor of two numbers. This will be the - * greatest positive integer number, that both input values share as a divisor. - * - * This method does not properly work for fractional (non integer) numbers. If you - * pass in fractional numbers there usually will be an output, but that's not necessarily - * the greatest common divisor of those two numbers. - */ -export function greatestCommonDivisor(a: number, b: number): number { - return a === 0 ? Math.abs(b) : greatestCommonDivisor(b % a, a); -} - -/** - * Calculates the least common multiple of two numbers. The least common multiple - * is the smallest positive integer number, that is divisible by both input parameters. - * - * Since this calculation suffers from rounding issues in decimal values, this method - * won't work for passing in fractional (non integer) numbers. It will return a value, - * but that value won't necessarily be the mathematical correct least common multiple. - */ -export function leastCommonMultiple(a: number, b: number): number { - return Math.abs((a * b) / greatestCommonDivisor(a, b)); -} diff --git a/src/legacy/ui/public/utils/parse_interval.d.ts b/src/legacy/ui/public/utils/parse_interval.d.ts deleted file mode 100644 index 9d78b4ef6cddf..0000000000000 --- a/src/legacy/ui/public/utils/parse_interval.d.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import moment from 'moment'; - -export function parseInterval(interval: string): moment.Duration | null; diff --git a/src/legacy/ui/public/utils/parse_interval.js b/src/legacy/ui/public/utils/parse_interval.js deleted file mode 100644 index fe484985ef101..0000000000000 --- a/src/legacy/ui/public/utils/parse_interval.js +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; -import moment from 'moment'; -import dateMath from '@elastic/datemath'; - -// Assume interval is in the form (value)(unit), such as "1h" -const INTERVAL_STRING_RE = new RegExp('^([0-9\\.]*)\\s*(' + dateMath.units.join('|') + ')$'); - -export function parseInterval(interval) { - const matches = String(interval) - .trim() - .match(INTERVAL_STRING_RE); - - if (!matches) return null; - - try { - const value = parseFloat(matches[1]) || 1; - const unit = matches[2]; - - const duration = moment.duration(value, unit); - - // There is an error with moment, where if you have a fractional interval between 0 and 1, then when you add that - // interval to an existing moment object, it will remain unchanged, which causes problems in the ordered_x_keys - // code. To counteract this, we find the first unit that doesn't result in a value between 0 and 1. - // For example, if you have '0.5d', then when calculating the x-axis series, we take the start date and begin - // adding 0.5 days until we hit the end date. However, since there is a bug in moment, when you add 0.5 days to - // the start date, you get the same exact date (instead of being ahead by 12 hours). So instead of returning - // a duration corresponding to 0.5 hours, we return a duration corresponding to 12 hours. - const selectedUnit = _.find(dateMath.units, function(unit) { - return Math.abs(duration.as(unit)) >= 1; - }); - - return moment.duration(duration.as(selectedUnit), selectedUnit); - } catch (e) { - return null; - } -} diff --git a/src/legacy/ui/public/utils/sort_prefix_first.ts b/src/legacy/ui/public/utils/sort_prefix_first.ts deleted file mode 100644 index 4d1a8d7f39866..0000000000000 --- a/src/legacy/ui/public/utils/sort_prefix_first.ts +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { partition } from 'lodash'; - -export function sortPrefixFirst(array: any[], prefix?: string | number, property?: string): any[] { - if (!prefix) { - return array; - } - const lowerCasePrefix = ('' + prefix).toLowerCase(); - - const partitions = partition(array, entry => { - const value = ('' + (property ? entry[property] : entry)).toLowerCase(); - return value.startsWith(lowerCasePrefix); - }); - return [...partitions[0], ...partitions[1]]; -} diff --git a/src/legacy/ui/public/utils/supports.js b/src/legacy/ui/public/utils/supports.js deleted file mode 100644 index 19c5f34d97f36..0000000000000 --- a/src/legacy/ui/public/utils/supports.js +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; - -/** - * just a place to put feature detection checks - */ -export const supports = { - cssFilters: (function() { - const e = document.createElement('img'); - const rules = ['webkitFilter', 'mozFilter', 'msFilter', 'filter']; - const test = 'grayscale(1)'; - rules.forEach(function(rule) { - e.style[rule] = test; - }); - - document.body.appendChild(e); - const styles = window.getComputedStyle(e); - const can = _(styles) - .pick(rules) - .includes(test); - document.body.removeChild(e); - - return can; - })(), -}; diff --git a/src/legacy/ui/public/vis/editors/config/editor_config_providers.ts b/src/legacy/ui/public/vis/editors/config/editor_config_providers.ts index 3dd911efc324c..c7fb937b97424 100644 --- a/src/legacy/ui/public/vis/editors/config/editor_config_providers.ts +++ b/src/legacy/ui/public/vis/editors/config/editor_config_providers.ts @@ -20,8 +20,8 @@ import { TimeIntervalParam } from 'ui/vis/editors/config/types'; import { AggConfig } from '../..'; import { AggType } from '../../../agg_types'; -import { IndexPattern } from '../../../index_patterns'; -import { leastCommonMultiple } from '../../../utils/math'; +import { IndexPattern } from '../../../../../../plugins/data/public'; +import { leastCommonMultiple } from '../../lib/least_common_multiple'; import { parseEsInterval } from '../../../../../core_plugins/data/public'; import { leastCommonInterval } from '../../lib/least_common_interval'; import { EditorConfig, EditorParamConfig, FixedParam, NumericIntervalParam } from './types'; diff --git a/src/legacy/ui/public/vis/editors/default/components/agg.test.tsx b/src/legacy/ui/public/vis/editors/default/components/agg.test.tsx index 661ece5944fa3..c19f101fa2c32 100644 --- a/src/legacy/ui/public/vis/editors/default/components/agg.test.tsx +++ b/src/legacy/ui/public/vis/editors/default/components/agg.test.tsx @@ -24,8 +24,8 @@ import { AggGroupNames } from '../agg_groups'; import { DefaultEditorAgg, DefaultEditorAggProps } from './agg'; import { act } from 'react-dom/test-utils'; import { DefaultEditorAggParams } from './agg_params'; -import { IndexPattern } from 'ui/index_patterns'; import { AggType } from 'ui/agg_types'; +import { IndexPattern } from '../../../../../../../plugins/data/public'; jest.mock('./agg_params', () => ({ DefaultEditorAggParams: () => null, diff --git a/src/legacy/ui/public/vis/editors/default/components/agg_param_props.ts b/src/legacy/ui/public/vis/editors/default/components/agg_param_props.ts index aafba008f97c5..df77ea9e43eab 100644 --- a/src/legacy/ui/public/vis/editors/default/components/agg_param_props.ts +++ b/src/legacy/ui/public/vis/editors/default/components/agg_param_props.ts @@ -17,13 +17,13 @@ * under the License. */ -import { Field } from 'ui/index_patterns'; import { AggParam } from 'ui/agg_types'; import { AggConfig } from '../../../../agg_types/agg_config'; import { ComboBoxGroupedOptions } from '../utils'; import { EditorConfig } from '../../config/types'; import { VisState } from '../../..'; import { SubAggParamsProp } from './agg_params'; +import { Field } from '../../../../../../../plugins/data/public'; // NOTE: we cannot export the interface with export { InterfaceName } // as there is currently a bug on babel typescript transform plugin for it diff --git a/src/legacy/ui/public/vis/editors/default/components/agg_params.test.tsx b/src/legacy/ui/public/vis/editors/default/components/agg_params.test.tsx index 25e2db8ef1836..9f3f5bff3f56e 100644 --- a/src/legacy/ui/public/vis/editors/default/components/agg_params.test.tsx +++ b/src/legacy/ui/public/vis/editors/default/components/agg_params.test.tsx @@ -19,9 +19,9 @@ import React from 'react'; import { mount, shallow } from 'enzyme'; -import { IndexPattern } from 'ui/index_patterns'; import { AggConfig, VisState } from '../../..'; import { DefaultEditorAggParams, DefaultEditorAggParamsProps } from './agg_params'; +import { IndexPattern } from '../../../../../../../plugins/data/public'; const mockEditorConfig = { useNormalizedEsInterval: { hidden: false, fixedValue: false }, diff --git a/src/legacy/ui/public/vis/editors/default/components/agg_params.tsx b/src/legacy/ui/public/vis/editors/default/components/agg_params.tsx index 1515ed86db070..c62e2837908c7 100644 --- a/src/legacy/ui/public/vis/editors/default/components/agg_params.tsx +++ b/src/legacy/ui/public/vis/editors/default/components/agg_params.tsx @@ -23,8 +23,8 @@ import { i18n } from '@kbn/i18n'; import useUnmount from 'react-use/lib/useUnmount'; import { VisState } from 'ui/vis'; -import { IndexPattern } from 'ui/index_patterns'; import { aggTypes, AggType, AggParam, AggConfig } from 'ui/agg_types/'; +import { IndexPattern } from '../../../../../../../plugins/data/public'; import { DefaultEditorAggSelect } from './agg_select'; import { DefaultEditorAggParam } from './agg_param'; diff --git a/src/legacy/ui/public/vis/editors/default/components/agg_params_helper.test.ts b/src/legacy/ui/public/vis/editors/default/components/agg_params_helper.test.ts index eb6bef4887642..fb37dd5d94618 100644 --- a/src/legacy/ui/public/vis/editors/default/components/agg_params_helper.test.ts +++ b/src/legacy/ui/public/vis/editors/default/components/agg_params_helper.test.ts @@ -19,7 +19,6 @@ import { AggConfig, VisState } from '../../..'; import { AggType } from 'ui/agg_types'; -import { IndexPattern, Field } from 'ui/index_patterns'; import { IndexedArray } from 'ui/indexed_array'; import { getAggParamsToRender, @@ -28,6 +27,7 @@ import { isInvalidParamsTouched, } from './agg_params_helper'; import { EditorConfig } from '../../config/types'; +import { IndexPattern, Field } from '../../../../../../../plugins/data/public'; jest.mock('ui/agg_types', () => ({ aggTypes: { diff --git a/src/legacy/ui/public/vis/editors/default/components/agg_params_helper.ts b/src/legacy/ui/public/vis/editors/default/components/agg_params_helper.ts index d233f561940da..e0e014f69ef3f 100644 --- a/src/legacy/ui/public/vis/editors/default/components/agg_params_helper.ts +++ b/src/legacy/ui/public/vis/editors/default/components/agg_params_helper.ts @@ -20,7 +20,6 @@ import { get, isEmpty } from 'lodash'; import { i18n } from '@kbn/i18n'; import { aggTypeFilters } from 'ui/agg_types/filter'; -import { IndexPattern, Field } from 'ui/index_patterns'; import { aggTypes, AggParam, FieldParamType, AggType } from 'ui/agg_types'; import { aggTypeFieldFilters } from 'ui/agg_types/param_types/filter'; import { AggConfig, VisState } from '../../..'; @@ -28,6 +27,7 @@ import { groupAndSortBy, ComboBoxGroupedOptions } from '../utils'; import { EditorConfig } from '../../config/types'; import { AggTypeState, AggParamsState } from './agg_params_state'; import { AggParamEditorProps } from './agg_param_props'; +import { IndexPattern, Field } from '../../../../../../../plugins/data/public'; interface ParamInstanceBase { agg: AggConfig; diff --git a/src/legacy/ui/public/vis/editors/default/components/agg_select.tsx b/src/legacy/ui/public/vis/editors/default/components/agg_select.tsx index 2934711b2357a..443c655912a55 100644 --- a/src/legacy/ui/public/vis/editors/default/components/agg_select.tsx +++ b/src/legacy/ui/public/vis/editors/default/components/agg_select.tsx @@ -23,9 +23,9 @@ import { EuiComboBox, EuiComboBoxOptionProps, EuiFormRow, EuiLink, EuiText } fro import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { AggType } from 'ui/agg_types'; -import { IndexPattern } from 'ui/index_patterns'; import { documentationLinks } from '../../../../documentation_links/documentation_links'; import { ComboBoxGroupedOptions } from '../utils'; +import { IndexPattern } from '../../../../../../../plugins/data/public'; interface DefaultEditorAggSelectProps { aggError?: string; diff --git a/src/legacy/ui/public/vis/editors/default/controls/field.test.tsx b/src/legacy/ui/public/vis/editors/default/controls/field.test.tsx index aac32e28c80a5..4d15ac8e80e63 100644 --- a/src/legacy/ui/public/vis/editors/default/controls/field.test.tsx +++ b/src/legacy/ui/public/vis/editors/default/controls/field.test.tsx @@ -21,7 +21,7 @@ import React from 'react'; import { act } from 'react-dom/test-utils'; import { mount, shallow, ReactWrapper } from 'enzyme'; import { EuiComboBoxProps, EuiComboBox } from '@elastic/eui'; -import { Field } from '../../../../index_patterns'; +import { Field } from '../../../../../../../plugins/data/public'; import { ComboBoxGroupedOptions, SubAggParamsProp } from '..'; import { FieldParamEditor, FieldParamEditorProps } from './field'; import { AggConfig, VisState } from '../../..'; diff --git a/src/legacy/ui/public/vis/editors/default/controls/field.tsx b/src/legacy/ui/public/vis/editors/default/controls/field.tsx index a96be3a14b7ef..75a9e24cd0dee 100644 --- a/src/legacy/ui/public/vis/editors/default/controls/field.tsx +++ b/src/legacy/ui/public/vis/editors/default/controls/field.tsx @@ -23,7 +23,7 @@ import React, { useEffect } from 'react'; import { EuiComboBox, EuiComboBoxOptionProps, EuiFormRow } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { AggConfig } from '../../..'; -import { Field } from '../../../../index_patterns'; +import { Field } from '../../../../../../../plugins/data/public'; import { formatListAsProse, parseCommaSeparatedList } from '../../../../../../utils'; import { AggParam, FieldParamType } from '../../../../agg_types'; import { AggParamEditorProps, ComboBoxGroupedOptions } from '..'; diff --git a/src/legacy/ui/public/vis/editors/default/controls/top_field.tsx b/src/legacy/ui/public/vis/editors/default/controls/top_field.tsx index 5a5f2ceee3a56..e15187621954f 100644 --- a/src/legacy/ui/public/vis/editors/default/controls/top_field.tsx +++ b/src/legacy/ui/public/vis/editors/default/controls/top_field.tsx @@ -19,7 +19,7 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; -import { Field } from '../../../../index_patterns'; +import { Field } from '../../../../../../../plugins/data/public'; import { FieldParamEditor } from './field'; import { getCompatibleAggs } from './top_aggregate'; import { AggParamEditorProps } from '..'; diff --git a/src/legacy/ui/public/vis/editors/default/controls/top_sort_field.tsx b/src/legacy/ui/public/vis/editors/default/controls/top_sort_field.tsx index 4116ae878209e..905bd052ec695 100644 --- a/src/legacy/ui/public/vis/editors/default/controls/top_sort_field.tsx +++ b/src/legacy/ui/public/vis/editors/default/controls/top_sort_field.tsx @@ -19,7 +19,7 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; -import { Field } from '../../../../index_patterns'; +import { Field } from '../../../../../../../plugins/data/public'; import { FieldParamEditor } from './field'; import { AggParamEditorProps } from '..'; diff --git a/src/legacy/ui/public/vis/editors/default/vis_type_agg_filter.ts b/src/legacy/ui/public/vis/editors/default/vis_type_agg_filter.ts index 5842d7579f60a..c64907fff58a1 100644 --- a/src/legacy/ui/public/vis/editors/default/vis_type_agg_filter.ts +++ b/src/legacy/ui/public/vis/editors/default/vis_type_agg_filter.ts @@ -18,7 +18,7 @@ */ import { AggType } from '../../../agg_types'; import { aggTypeFilters, propFilter } from '../../../agg_types/filter'; -import { IndexPattern } from '../../../index_patterns'; +import { IndexPattern } from '../../../../../../plugins/data/public'; import { AggConfig } from '../../../vis'; const filterByName = propFilter('name'); diff --git a/src/legacy/ui/public/vis/lib/least_common_interval.ts b/src/legacy/ui/public/vis/lib/least_common_interval.ts index dfdb099249228..244bc1d0111e3 100644 --- a/src/legacy/ui/public/vis/lib/least_common_interval.ts +++ b/src/legacy/ui/public/vis/lib/least_common_interval.ts @@ -18,7 +18,7 @@ */ import dateMath from '@elastic/datemath'; -import { leastCommonMultiple } from '../../utils/math'; +import { leastCommonMultiple } from './least_common_multiple'; import { parseEsInterval } from '../../../../core_plugins/data/public'; /** diff --git a/src/legacy/ui/public/vis/lib/least_common_multiple.test.ts b/src/legacy/ui/public/vis/lib/least_common_multiple.test.ts new file mode 100644 index 0000000000000..d07ac96b5f4d5 --- /dev/null +++ b/src/legacy/ui/public/vis/lib/least_common_multiple.test.ts @@ -0,0 +1,42 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { leastCommonMultiple } from './least_common_multiple'; + +describe('leastCommonMultiple', () => { + const tests: Array<[number, number, number]> = [ + [3, 5, 15], + [1, 1, 1], + [5, 6, 30], + [3, 9, 9], + [8, 20, 40], + [5, 5, 5], + [0, 5, 0], + [-4, -5, 20], + [-2, -3, 6], + [-8, 2, 8], + [-8, 5, 40], + ]; + + tests.map(([a, b, expected]) => { + test(`should return ${expected} for leastCommonMultiple(${a}, ${b})`, () => { + expect(leastCommonMultiple(a, b)).toBe(expected); + }); + }); +}); diff --git a/src/legacy/ui/public/vis/lib/least_common_multiple.ts b/src/legacy/ui/public/vis/lib/least_common_multiple.ts new file mode 100644 index 0000000000000..dedddbf22ab44 --- /dev/null +++ b/src/legacy/ui/public/vis/lib/least_common_multiple.ts @@ -0,0 +1,46 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +/** + * Calculates the greates common divisor of two numbers. This will be the + * greatest positive integer number, that both input values share as a divisor. + * + * This method does not properly work for fractional (non integer) numbers. If you + * pass in fractional numbers there usually will be an output, but that's not necessarily + * the greatest common divisor of those two numbers. + * + * @private + */ +function greatestCommonDivisor(a: number, b: number): number { + return a === 0 ? Math.abs(b) : greatestCommonDivisor(b % a, a); +} + +/** + * Calculates the least common multiple of two numbers. The least common multiple + * is the smallest positive integer number, that is divisible by both input parameters. + * + * Since this calculation suffers from rounding issues in decimal values, this method + * won't work for passing in fractional (non integer) numbers. It will return a value, + * but that value won't necessarily be the mathematical correct least common multiple. + * + * @internal + */ +export function leastCommonMultiple(a: number, b: number): number { + return Math.abs((a * b) / greatestCommonDivisor(a, b)); +} diff --git a/src/legacy/ui/ui_render/bootstrap/template.js.hbs b/src/legacy/ui/ui_render/bootstrap/template.js.hbs index d8a55935b705a..85b6de26b9516 100644 --- a/src/legacy/ui/ui_render/bootstrap/template.js.hbs +++ b/src/legacy/ui/ui_render/bootstrap/template.js.hbs @@ -14,6 +14,7 @@ if (window.__kbnStrictCsp__ && window.__kbnCspNotEnforced__) { window.onload = function () { var files = [ '{{dllBundlePath}}/vendors.bundle.dll.js', + '{{regularBundlePath}}/kbn-ui-shared-deps/{{sharedDepsFilename}}', '{{regularBundlePath}}/commons.bundle.js', '{{regularBundlePath}}/{{appId}}.bundle.js' ]; diff --git a/src/legacy/ui/ui_render/ui_render_mixin.js b/src/legacy/ui/ui_render/ui_render_mixin.js index 0b266b8b62726..a935270d23fce 100644 --- a/src/legacy/ui/ui_render/ui_render_mixin.js +++ b/src/legacy/ui/ui_render/ui_render_mixin.js @@ -21,6 +21,7 @@ import { createHash } from 'crypto'; import Boom from 'boom'; import { resolve } from 'path'; import { i18n } from '@kbn/i18n'; +import * as UiSharedDeps from '@kbn/ui-shared-deps'; import { AppBootstrap } from './bootstrap'; // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { fromRoot } from '../../../core/server/utils'; @@ -41,18 +42,10 @@ export function uiRenderMixin(kbnServer, server, config) { // render all views from ./views server.setupViews(resolve(__dirname, 'views')); - server.exposeStaticDir( - '/node_modules/@elastic/eui/dist/{path*}', - fromRoot('node_modules/@elastic/eui/dist') - ); server.exposeStaticDir( '/node_modules/@kbn/ui-framework/dist/{path*}', fromRoot('node_modules/@kbn/ui-framework/dist') ); - server.exposeStaticDir( - '/node_modules/@elastic/charts/dist/{path*}', - fromRoot('node_modules/@elastic/charts/dist') - ); const translationsCache = { translations: null, hash: null }; server.route({ @@ -114,14 +107,12 @@ export function uiRenderMixin(kbnServer, server, config) { `${dllBundlePath}/vendors.style.dll.css`, ...(darkMode ? [ - `${basePath}/node_modules/@elastic/eui/dist/eui_theme_dark.css`, + `${basePath}/bundles/kbn-ui-shared-deps/${UiSharedDeps.darkCssDistFilename}`, `${basePath}/node_modules/@kbn/ui-framework/dist/kui_dark.css`, - `${basePath}/node_modules/@elastic/charts/dist/theme_only_dark.css`, ] : [ - `${basePath}/node_modules/@elastic/eui/dist/eui_theme_light.css`, + `${basePath}/bundles/kbn-ui-shared-deps/${UiSharedDeps.lightCssDistFilename}`, `${basePath}/node_modules/@kbn/ui-framework/dist/kui_light.css`, - `${basePath}/node_modules/@elastic/charts/dist/theme_only_light.css`, ]), `${regularBundlePath}/${darkMode ? 'dark' : 'light'}_theme.style.css`, `${regularBundlePath}/commons.style.css`, @@ -142,6 +133,7 @@ export function uiRenderMixin(kbnServer, server, config) { regularBundlePath, dllBundlePath, styleSheetPaths, + sharedDepsFilename: UiSharedDeps.distFilename, }, }); diff --git a/src/legacy/utils/case_conversion.test.ts b/src/legacy/utils/case_conversion.test.ts deleted file mode 100644 index 27498f3878980..0000000000000 --- a/src/legacy/utils/case_conversion.test.ts +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { keysToCamelCaseShallow, keysToSnakeCaseShallow } from './case_conversion'; - -describe('keysToSnakeCaseShallow', () => { - it("should convert all of an object's keys to snake case", () => { - const result = keysToSnakeCaseShallow({ - camelCase: 'camel_case', - 'kebab-case': 'kebab_case', - snake_case: 'snake_case', - }); - - expect(result).toMatchInlineSnapshot(` -Object { - "camel_case": "camel_case", - "kebab_case": "kebab_case", - "snake_case": "snake_case", -} -`); - }); -}); - -describe('keysToCamelCaseShallow', () => { - it("should convert all of an object's keys to camel case", () => { - const result = keysToCamelCaseShallow({ - camelCase: 'camelCase', - 'kebab-case': 'kebabCase', - snake_case: 'snakeCase', - }); - - expect(result).toMatchInlineSnapshot(` -Object { - "camelCase": "camelCase", - "kebabCase": "kebabCase", - "snakeCase": "snakeCase", -} -`); - }); -}); diff --git a/src/legacy/utils/case_conversion.ts b/src/legacy/utils/case_conversion.ts deleted file mode 100644 index e1c1317b26b14..0000000000000 --- a/src/legacy/utils/case_conversion.ts +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import _ from 'lodash'; - -export function keysToSnakeCaseShallow(object: Record) { - return _.mapKeys(object, (value, key) => { - return _.snakeCase(key); - }); -} - -export function keysToCamelCaseShallow(object: Record) { - return _.mapKeys(object, (value, key) => { - return _.camelCase(key); - }); -} diff --git a/src/optimize/base_optimizer.js b/src/optimize/base_optimizer.js index 9a21a4b1d5439..efff7f0aa2b46 100644 --- a/src/optimize/base_optimizer.js +++ b/src/optimize/base_optimizer.js @@ -19,6 +19,7 @@ import { writeFile } from 'fs'; import os from 'os'; + import Boom from 'boom'; import MiniCssExtractPlugin from 'mini-css-extract-plugin'; import TerserPlugin from 'terser-webpack-plugin'; @@ -26,10 +27,10 @@ import webpack from 'webpack'; import Stats from 'webpack/lib/Stats'; import * as threadLoader from 'thread-loader'; import webpackMerge from 'webpack-merge'; -import { DynamicDllPlugin } from './dynamic_dll_plugin'; import WrapperPlugin from 'wrapper-webpack-plugin'; +import * as UiSharedDeps from '@kbn/ui-shared-deps'; -import { defaults } from 'lodash'; +import { DynamicDllPlugin } from './dynamic_dll_plugin'; import { IS_KIBANA_DISTRIBUTABLE } from '../legacy/utils'; import { fromRoot } from '../core/server/utils'; @@ -403,6 +404,10 @@ export default class BaseOptimizer { // and not for the webpack compilations performance itself hints: false, }, + + externals: { + ...UiSharedDeps.externals, + }, }; // when running from the distributable define an environment variable we can use @@ -417,17 +422,6 @@ export default class BaseOptimizer { ], }; - // We need to add react-addons (and a few other bits) for enzyme to work. - // https://github.com/airbnb/enzyme/blob/master/docs/guides/webpack.md - const supportEnzymeConfig = { - externals: { - mocha: 'mocha', - 'react/lib/ExecutionEnvironment': true, - 'react/addons': true, - 'react/lib/ReactContext': true, - }, - }; - const watchingConfig = { plugins: [ new webpack.WatchIgnorePlugin([ @@ -482,9 +476,7 @@ export default class BaseOptimizer { IS_CODE_COVERAGE ? coverageConfig : {}, commonConfig, IS_KIBANA_DISTRIBUTABLE ? isDistributableConfig : {}, - this.uiBundles.isDevMode() - ? webpackMerge(watchingConfig, supportEnzymeConfig) - : productionConfig + this.uiBundles.isDevMode() ? watchingConfig : productionConfig ) ); } @@ -515,22 +507,19 @@ export default class BaseOptimizer { } failedStatsToError(stats) { - const details = stats.toString( - defaults( - { colors: true, warningsFilter: STATS_WARNINGS_FILTER }, - Stats.presetToOptions('minimal') - ) - ); + const details = stats.toString({ + ...Stats.presetToOptions('minimal'), + colors: true, + warningsFilter: STATS_WARNINGS_FILTER, + }); return Boom.internal( `Optimizations failure.\n${details.split('\n').join('\n ')}\n`, - stats.toJson( - defaults({ - warningsFilter: STATS_WARNINGS_FILTER, - ...Stats.presetToOptions('detailed'), - maxModules: 1000, - }) - ) + stats.toJson({ + warningsFilter: STATS_WARNINGS_FILTER, + ...Stats.presetToOptions('detailed'), + maxModules: 1000, + }) ); } diff --git a/src/optimize/bundles_route/bundles_route.js b/src/optimize/bundles_route/bundles_route.js index d3c08fae92264..f0261d44e0347 100644 --- a/src/optimize/bundles_route/bundles_route.js +++ b/src/optimize/bundles_route/bundles_route.js @@ -19,6 +19,7 @@ import { isAbsolute, extname } from 'path'; import LruCache from 'lru-cache'; +import * as UiSharedDeps from '@kbn/ui-shared-deps'; import { createDynamicAssetResponse } from './dynamic_asset_response'; /** @@ -66,6 +67,12 @@ export function createBundlesRoute({ } return [ + buildRouteForBundles( + `${basePublicPath}/bundles/kbn-ui-shared-deps/`, + '/bundles/kbn-ui-shared-deps/', + UiSharedDeps.distDir, + fileHashCache + ), buildRouteForBundles( `${basePublicPath}/bundles/`, '/bundles/', diff --git a/src/optimize/dynamic_dll_plugin/dll_config_model.js b/src/optimize/dynamic_dll_plugin/dll_config_model.js index 2a3d3dd659c67..ecf5def5aa6ca 100644 --- a/src/optimize/dynamic_dll_plugin/dll_config_model.js +++ b/src/optimize/dynamic_dll_plugin/dll_config_model.js @@ -23,6 +23,7 @@ import webpack from 'webpack'; import webpackMerge from 'webpack-merge'; import MiniCssExtractPlugin from 'mini-css-extract-plugin'; import TerserPlugin from 'terser-webpack-plugin'; +import * as UiSharedDeps from '@kbn/ui-shared-deps'; function generateDLL(config) { const { @@ -145,6 +146,9 @@ function generateDLL(config) { // and not for the webpack compilations performance itself hints: false, }, + externals: { + ...UiSharedDeps.externals, + }, }; } diff --git a/src/optimize/watch/watch_cache.ts b/src/optimize/watch/watch_cache.ts index ab11a8c5d2f11..15957210b3d43 100644 --- a/src/optimize/watch/watch_cache.ts +++ b/src/optimize/watch/watch_cache.ts @@ -18,17 +18,18 @@ */ import { createHash } from 'crypto'; -import { readFile, writeFile } from 'fs'; +import { readFile, writeFile, readdir, unlink, rmdir } from 'fs'; import { resolve } from 'path'; import { promisify } from 'util'; - +import path from 'path'; import del from 'del'; -import deleteEmpty from 'delete-empty'; -import globby from 'globby'; import normalizePosixPath from 'normalize-path'; const readAsync = promisify(readFile); const writeAsync = promisify(writeFile); +const readdirAsync = promisify(readdir); +const unlinkAsync = promisify(unlink); +const rmdirAsync = promisify(rmdir); interface Params { logWithMetadata: (tags: string[], message: string, metadata?: { [key: string]: any }) => void; @@ -95,11 +96,7 @@ export class WatchCache { await del(this.statePath, { force: true }); // delete everything in optimize/.cache directory - await del(await globby([normalizePosixPath(this.cachePath)], { dot: true })); - - // delete some empty folder that could be left - // from the previous cache path reset action - await deleteEmpty(this.cachePath); + await recursiveDelete(normalizePosixPath(this.cachePath)); // delete dlls await del(this.dllsPath); @@ -167,3 +164,28 @@ export class WatchCache { } } } + +/** + * Recursively deletes a folder. This is a workaround for a bug in `del` where + * very large folders (with 84K+ files) cause a stack overflow. + */ +async function recursiveDelete(directory: string) { + const entries = await readdirAsync(directory, { withFileTypes: true }); + await Promise.all( + entries.map(entry => { + const absolutePath = path.join(directory, entry.name); + const result = entry.isDirectory() + ? recursiveDelete(absolutePath) + : unlinkAsync(absolutePath); + + // Ignore errors, if the file or directory doesn't exist. + return result.catch(e => { + if (e.code !== 'ENOENT') { + throw e; + } + }); + }) + ); + + return rmdirAsync(directory); +} diff --git a/src/plugins/dashboard_embeddable_container/public/embeddable/viewport/_dashboard_viewport.scss b/src/plugins/dashboard_embeddable_container/public/embeddable/viewport/_dashboard_viewport.scss index b446f1e57a895..bb95840676969 100644 --- a/src/plugins/dashboard_embeddable_container/public/embeddable/viewport/_dashboard_viewport.scss +++ b/src/plugins/dashboard_embeddable_container/public/embeddable/viewport/_dashboard_viewport.scss @@ -1,9 +1,7 @@ .dshDashboardViewport { - height: 100%; width: 100%; } .dshDashboardViewport-withMargins { width: 100%; - height: 100%; } diff --git a/src/plugins/data/common/es_query/kuery/kuery_syntax_error.ts b/src/plugins/data/common/es_query/kuery/kuery_syntax_error.ts index 7c90119fcc1bc..0d5cd6ea17f16 100644 --- a/src/plugins/data/common/es_query/kuery/kuery_syntax_error.ts +++ b/src/plugins/data/common/es_query/kuery/kuery_syntax_error.ts @@ -41,7 +41,7 @@ const grammarRuleTranslations: Record = { interface KQLSyntaxErrorData extends Error { found: string; - expected: KQLSyntaxErrorExpected[]; + expected: KQLSyntaxErrorExpected[] | null; location: any; } @@ -53,19 +53,22 @@ export class KQLSyntaxError extends Error { shortMessage: string; constructor(error: KQLSyntaxErrorData, expression: any) { - const translatedExpectations = error.expected.map(expected => { - return grammarRuleTranslations[expected.description] || expected.description; - }); + let message = error.message; + if (error.expected) { + const translatedExpectations = error.expected.map(expected => { + return grammarRuleTranslations[expected.description] || expected.description; + }); - const translatedExpectationText = translatedExpectations.join(', '); + const translatedExpectationText = translatedExpectations.join(', '); - const message = i18n.translate('data.common.esQuery.kql.errors.syntaxError', { - defaultMessage: 'Expected {expectedList} but {foundInput} found.', - values: { - expectedList: translatedExpectationText, - foundInput: error.found ? `"${error.found}"` : endOfInputText, - }, - }); + message = i18n.translate('data.common.esQuery.kql.errors.syntaxError', { + defaultMessage: 'Expected {expectedList} but {foundInput} found.', + values: { + expectedList: translatedExpectationText, + foundInput: error.found ? `"${error.found}"` : endOfInputText, + }, + }); + } const fullMessage = [message, expression, repeat('-', error.location.start.offset) + '^'].join( '\n' diff --git a/src/plugins/data/common/field_formats/converters/bytes.ts b/src/plugins/data/common/field_formats/converters/bytes.ts index 6c6df5eb7367d..f1110add3e7de 100644 --- a/src/plugins/data/common/field_formats/converters/bytes.ts +++ b/src/plugins/data/common/field_formats/converters/bytes.ts @@ -26,4 +26,5 @@ export class BytesFormat extends NumeralFormat { id = BytesFormat.id; title = BytesFormat.title; + allowsNumericalAggregations = true; } diff --git a/src/plugins/data/common/field_formats/converters/duration.ts b/src/plugins/data/common/field_formats/converters/duration.ts index d02de1a2fd889..8caa11be5ca79 100644 --- a/src/plugins/data/common/field_formats/converters/duration.ts +++ b/src/plugins/data/common/field_formats/converters/duration.ts @@ -171,6 +171,7 @@ export class DurationFormat extends FieldFormat { static fieldType = KBN_FIELD_TYPES.NUMBER; static inputFormats = inputFormats; static outputFormats = outputFormats; + allowsNumericalAggregations = true; isHuman() { return this.param('outputFormat') === HUMAN_FRIENDLY; diff --git a/src/plugins/data/common/field_formats/converters/number.ts b/src/plugins/data/common/field_formats/converters/number.ts index 6969c1551e1cc..686329e887682 100644 --- a/src/plugins/data/common/field_formats/converters/number.ts +++ b/src/plugins/data/common/field_formats/converters/number.ts @@ -26,4 +26,5 @@ export class NumberFormat extends NumeralFormat { id = NumberFormat.id; title = NumberFormat.title; + allowsNumericalAggregations = true; } diff --git a/src/plugins/data/common/field_formats/converters/percent.ts b/src/plugins/data/common/field_formats/converters/percent.ts index 2ae32c7c77f07..d839a54dd0c2c 100644 --- a/src/plugins/data/common/field_formats/converters/percent.ts +++ b/src/plugins/data/common/field_formats/converters/percent.ts @@ -26,6 +26,7 @@ export class PercentFormat extends NumeralFormat { id = PercentFormat.id; title = PercentFormat.title; + allowsNumericalAggregations = true; getParamDefaults = () => ({ pattern: this.getConfig!('format:percent:defaultPattern'), diff --git a/src/plugins/data/common/utils/index.ts b/src/plugins/data/common/utils/index.ts index 7196c96989e97..c5f1276feb81d 100644 --- a/src/plugins/data/common/utils/index.ts +++ b/src/plugins/data/common/utils/index.ts @@ -18,3 +18,4 @@ */ export { shortenDottedString } from './shorten_dotted_string'; +export { parseInterval } from './parse_interval'; diff --git a/src/plugins/data/common/utils/parse_interval.test.ts b/src/plugins/data/common/utils/parse_interval.test.ts new file mode 100644 index 0000000000000..0c02b02a25af0 --- /dev/null +++ b/src/plugins/data/common/utils/parse_interval.test.ts @@ -0,0 +1,119 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Duration, unitOfTime } from 'moment'; +import { parseInterval } from './parse_interval'; + +const validateDuration = (duration: Duration | null, unit: unitOfTime.Base, value: number) => { + expect(duration).toBeDefined(); + + if (duration) { + expect(duration.as(unit)).toBe(value); + } +}; + +describe('parseInterval', () => { + describe('integer', () => { + test('should correctly parse 1d interval', () => { + validateDuration(parseInterval('1d'), 'd', 1); + }); + + test('should correctly parse 2y interval', () => { + validateDuration(parseInterval('2y'), 'y', 2); + }); + + test('should correctly parse 5M interval', () => { + validateDuration(parseInterval('5M'), 'M', 5); + }); + + test('should correctly parse 5m interval', () => { + validateDuration(parseInterval('5m'), 'm', 5); + }); + + test('should correctly parse 250ms interval', () => { + validateDuration(parseInterval('250ms'), 'ms', 250); + }); + + test('should correctly parse 100s interval', () => { + validateDuration(parseInterval('100s'), 's', 100); + }); + + test('should correctly parse 23d interval', () => { + validateDuration(parseInterval('23d'), 'd', 23); + }); + + test('should correctly parse 52w interval', () => { + validateDuration(parseInterval('52w'), 'w', 52); + }); + }); + + describe('fractional interval', () => { + test('should correctly parse fractional 2.35y interval', () => { + validateDuration(parseInterval('2.35y'), 'y', 2.35); + }); + + test('should correctly parse fractional 1.5w interval', () => { + validateDuration(parseInterval('1.5w'), 'w', 1.5); + }); + }); + + describe('less than 1', () => { + test('should correctly bubble up 0.5h interval which are less than 1', () => { + validateDuration(parseInterval('0.5h'), 'm', 30); + }); + + test('should correctly bubble up 0.5d interval which are less than 1', () => { + validateDuration(parseInterval('0.5d'), 'h', 12); + }); + }); + + describe('unit in an interval only', () => { + test('should correctly parse ms interval', () => { + validateDuration(parseInterval('ms'), 'ms', 1); + }); + + test('should correctly parse d interval', () => { + validateDuration(parseInterval('d'), 'd', 1); + }); + + test('should correctly parse m interval', () => { + validateDuration(parseInterval('m'), 'm', 1); + }); + + test('should correctly parse y interval', () => { + validateDuration(parseInterval('y'), 'y', 1); + }); + + test('should correctly parse M interval', () => { + validateDuration(parseInterval('M'), 'M', 1); + }); + }); + + test('should return null for an invalid interval', () => { + let duration = parseInterval(''); + expect(duration).toBeNull(); + + // @ts-ignore + duration = parseInterval(null); + expect(duration).toBeNull(); + + duration = parseInterval('234asdf'); + expect(duration).toBeNull(); + }); +}); diff --git a/src/plugins/data/common/utils/parse_interval.ts b/src/plugins/data/common/utils/parse_interval.ts new file mode 100644 index 0000000000000..ef1d89e400b72 --- /dev/null +++ b/src/plugins/data/common/utils/parse_interval.ts @@ -0,0 +1,56 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { find } from 'lodash'; +import moment, { unitOfTime } from 'moment'; +import dateMath from '@elastic/datemath'; + +// Assume interval is in the form (value)(unit), such as "1h" +const INTERVAL_STRING_RE = new RegExp('^([0-9\\.]*)\\s*(' + dateMath.units.join('|') + ')$'); + +export function parseInterval(interval: string): moment.Duration | null { + const matches = String(interval) + .trim() + .match(INTERVAL_STRING_RE); + + if (!matches) return null; + + try { + const value = parseFloat(matches[1]) || 1; + const unit = matches[2] as unitOfTime.Base; + + const duration = moment.duration(value, unit); + + // There is an error with moment, where if you have a fractional interval between 0 and 1, then when you add that + // interval to an existing moment object, it will remain unchanged, which causes problems in the ordered_x_keys + // code. To counteract this, we find the first unit that doesn't result in a value between 0 and 1. + // For example, if you have '0.5d', then when calculating the x-axis series, we take the start date and begin + // adding 0.5 days until we hit the end date. However, since there is a bug in moment, when you add 0.5 days to + // the start date, you get the same exact date (instead of being ahead by 12 hours). So instead of returning + // a duration corresponding to 0.5 hours, we return a duration corresponding to 12 hours. + const selectedUnit = find( + dateMath.units, + u => Math.abs(duration.as(u)) >= 1 + ) as unitOfTime.Base; + + return moment.duration(duration.as(selectedUnit), selectedUnit); + } catch (e) { + return null; + } +} diff --git a/src/plugins/data/public/index.ts b/src/plugins/data/public/index.ts index 967887764237d..4b330600417e7 100644 --- a/src/plugins/data/public/index.ts +++ b/src/plugins/data/public/index.ts @@ -90,6 +90,8 @@ export { castEsToKbnFieldTypeName, getKbnFieldType, getKbnTypeNames, + // utils + parseInterval, } from '../common'; // Export plugin after all other imports diff --git a/src/plugins/data/public/query/timefilter/timefilter.ts b/src/plugins/data/public/query/timefilter/timefilter.ts index 639f3f4a66f18..58806a9328b1c 100644 --- a/src/plugins/data/public/query/timefilter/timefilter.ts +++ b/src/plugins/data/public/query/timefilter/timefilter.ts @@ -20,13 +20,13 @@ import _ from 'lodash'; import { Subject, BehaviorSubject } from 'rxjs'; import moment from 'moment'; -import { IndexPattern } from 'src/legacy/core_plugins/data/public'; import { areRefreshIntervalsDifferent, areTimeRangesDifferent } from './lib/diff_time_picker_vals'; import { parseQueryString } from './lib/parse_querystring'; import { calculateBounds, getTime } from './get_time'; import { TimefilterConfig, InputTimeRange, TimeRangeBounds } from './types'; import { RefreshInterval, TimeRange } from '../../../common'; import { TimeHistoryContract } from './time_history'; +import { IndexPattern } from '../../index_patterns'; // TODO: remove! diff --git a/src/plugins/data/public/ui/query_string_input/__snapshots__/language_switcher.test.tsx.snap b/src/plugins/data/public/ui/query_string_input/__snapshots__/language_switcher.test.tsx.snap index 7ab7d7653eb5e..4ec29ca409b80 100644 --- a/src/plugins/data/public/ui/query_string_input/__snapshots__/language_switcher.test.tsx.snap +++ b/src/plugins/data/public/ui/query_string_input/__snapshots__/language_switcher.test.tsx.snap @@ -170,6 +170,9 @@ exports[`LanguageSwitcher should toggle off if language is lucene 1`] = ` "logstash": Object { "base": "https://www.elastic.co/guide/en/logstash/mocked-test-branch", }, + "management": Object { + "kibanaSearchSettings": "https://www.elastic.co/guide/en/kibana/mocked-test-branch/advanced-options.html#kibana-search-settings", + }, "metricbeat": Object { "base": "https://www.elastic.co/guide/en/beats/metricbeat/mocked-test-branch", }, @@ -460,6 +463,9 @@ exports[`LanguageSwitcher should toggle on if language is kuery 1`] = ` "logstash": Object { "base": "https://www.elastic.co/guide/en/logstash/mocked-test-branch", }, + "management": Object { + "kibanaSearchSettings": "https://www.elastic.co/guide/en/kibana/mocked-test-branch/advanced-options.html#kibana-search-settings", + }, "metricbeat": Object { "base": "https://www.elastic.co/guide/en/beats/metricbeat/mocked-test-branch", }, diff --git a/src/plugins/data/public/ui/query_string_input/__snapshots__/query_string_input.test.tsx.snap b/src/plugins/data/public/ui/query_string_input/__snapshots__/query_string_input.test.tsx.snap index 2fce33793cd46..15e74e98920e2 100644 --- a/src/plugins/data/public/ui/query_string_input/__snapshots__/query_string_input.test.tsx.snap +++ b/src/plugins/data/public/ui/query_string_input/__snapshots__/query_string_input.test.tsx.snap @@ -276,6 +276,9 @@ exports[`QueryStringInput Should disable autoFocus on EuiFieldText when disableA "logstash": Object { "base": "https://www.elastic.co/guide/en/logstash/mocked-test-branch", }, + "management": Object { + "kibanaSearchSettings": "https://www.elastic.co/guide/en/kibana/mocked-test-branch/advanced-options.html#kibana-search-settings", + }, "metricbeat": Object { "base": "https://www.elastic.co/guide/en/beats/metricbeat/mocked-test-branch", }, @@ -346,6 +349,7 @@ exports[`QueryStringInput Should disable autoFocus on EuiFieldText when disableA "remove": [MockFunction], "replace": [MockFunction], }, + "openConfirm": [MockFunction], "openFlyout": [MockFunction], "openModal": [MockFunction], }, @@ -895,6 +899,9 @@ exports[`QueryStringInput Should disable autoFocus on EuiFieldText when disableA "logstash": Object { "base": "https://www.elastic.co/guide/en/logstash/mocked-test-branch", }, + "management": Object { + "kibanaSearchSettings": "https://www.elastic.co/guide/en/kibana/mocked-test-branch/advanced-options.html#kibana-search-settings", + }, "metricbeat": Object { "base": "https://www.elastic.co/guide/en/beats/metricbeat/mocked-test-branch", }, @@ -965,6 +972,7 @@ exports[`QueryStringInput Should disable autoFocus on EuiFieldText when disableA "remove": [MockFunction], "replace": [MockFunction], }, + "openConfirm": [MockFunction], "openFlyout": [MockFunction], "openModal": [MockFunction], }, @@ -1502,6 +1510,9 @@ exports[`QueryStringInput Should pass the query language to the language switche "logstash": Object { "base": "https://www.elastic.co/guide/en/logstash/mocked-test-branch", }, + "management": Object { + "kibanaSearchSettings": "https://www.elastic.co/guide/en/kibana/mocked-test-branch/advanced-options.html#kibana-search-settings", + }, "metricbeat": Object { "base": "https://www.elastic.co/guide/en/beats/metricbeat/mocked-test-branch", }, @@ -1572,6 +1583,7 @@ exports[`QueryStringInput Should pass the query language to the language switche "remove": [MockFunction], "replace": [MockFunction], }, + "openConfirm": [MockFunction], "openFlyout": [MockFunction], "openModal": [MockFunction], }, @@ -2118,6 +2130,9 @@ exports[`QueryStringInput Should pass the query language to the language switche "logstash": Object { "base": "https://www.elastic.co/guide/en/logstash/mocked-test-branch", }, + "management": Object { + "kibanaSearchSettings": "https://www.elastic.co/guide/en/kibana/mocked-test-branch/advanced-options.html#kibana-search-settings", + }, "metricbeat": Object { "base": "https://www.elastic.co/guide/en/beats/metricbeat/mocked-test-branch", }, @@ -2188,6 +2203,7 @@ exports[`QueryStringInput Should pass the query language to the language switche "remove": [MockFunction], "replace": [MockFunction], }, + "openConfirm": [MockFunction], "openFlyout": [MockFunction], "openModal": [MockFunction], }, @@ -2725,6 +2741,9 @@ exports[`QueryStringInput Should render the given query 1`] = ` "logstash": Object { "base": "https://www.elastic.co/guide/en/logstash/mocked-test-branch", }, + "management": Object { + "kibanaSearchSettings": "https://www.elastic.co/guide/en/kibana/mocked-test-branch/advanced-options.html#kibana-search-settings", + }, "metricbeat": Object { "base": "https://www.elastic.co/guide/en/beats/metricbeat/mocked-test-branch", }, @@ -2795,6 +2814,7 @@ exports[`QueryStringInput Should render the given query 1`] = ` "remove": [MockFunction], "replace": [MockFunction], }, + "openConfirm": [MockFunction], "openFlyout": [MockFunction], "openModal": [MockFunction], }, @@ -3341,6 +3361,9 @@ exports[`QueryStringInput Should render the given query 1`] = ` "logstash": Object { "base": "https://www.elastic.co/guide/en/logstash/mocked-test-branch", }, + "management": Object { + "kibanaSearchSettings": "https://www.elastic.co/guide/en/kibana/mocked-test-branch/advanced-options.html#kibana-search-settings", + }, "metricbeat": Object { "base": "https://www.elastic.co/guide/en/beats/metricbeat/mocked-test-branch", }, @@ -3411,6 +3434,7 @@ exports[`QueryStringInput Should render the given query 1`] = ` "remove": [MockFunction], "replace": [MockFunction], }, + "openConfirm": [MockFunction], "openFlyout": [MockFunction], "openModal": [MockFunction], }, diff --git a/src/plugins/data/server/index.ts b/src/plugins/data/server/index.ts index fe96c494bd9ff..3cd088744a439 100644 --- a/src/plugins/data/server/index.ts +++ b/src/plugins/data/server/index.ts @@ -18,7 +18,7 @@ */ import { PluginInitializerContext } from '../../../core/server'; -import { DataServerPlugin } from './plugin'; +import { DataServerPlugin, DataPluginSetup } from './plugin'; export function plugin(initializerContext: PluginInitializerContext) { return new DataServerPlugin(initializerContext); @@ -47,6 +47,8 @@ export { // timefilter RefreshInterval, TimeRange, + // utils + parseInterval, } from '../common'; /** @@ -91,4 +93,4 @@ export { getKbnTypeNames, } from '../common'; -export { DataServerPlugin as Plugin }; +export { DataServerPlugin as Plugin, DataPluginSetup as PluginSetup }; diff --git a/src/plugins/dev_tools/public/application.tsx b/src/plugins/dev_tools/public/application.tsx index be142f2cc74e6..a179be6946c76 100644 --- a/src/plugins/dev_tools/public/application.tsx +++ b/src/plugins/dev_tools/public/application.tsx @@ -91,7 +91,7 @@ function DevToolsWrapper({ if (mountedTool.current) { mountedTool.current.unmountHandler(); } - const params = { element, appBasePath: '' }; + const params = { element, appBasePath: '', onAppLeave: () => undefined }; const unmountHandler = isAppMountDeprecated(activeDevTool.mount) ? await activeDevTool.mount(appMountContext, params) : await activeDevTool.mount(params); diff --git a/src/plugins/es_ui_shared/static/forms/components/field.tsx b/src/plugins/es_ui_shared/static/forms/components/field.tsx index 89dea53d75b38..5b9a6dc9de002 100644 --- a/src/plugins/es_ui_shared/static/forms/components/field.tsx +++ b/src/plugins/es_ui_shared/static/forms/components/field.tsx @@ -37,6 +37,7 @@ import { RadioGroupField, RangeField, SelectField, + SuperSelectField, ToggleField, } from './fields'; @@ -50,6 +51,7 @@ const mapTypeToFieldComponent = { [FIELD_TYPES.RADIO_GROUP]: RadioGroupField, [FIELD_TYPES.RANGE]: RangeField, [FIELD_TYPES.SELECT]: SelectField, + [FIELD_TYPES.SUPER_SELECT]: SuperSelectField, [FIELD_TYPES.TOGGLE]: ToggleField, }; diff --git a/src/plugins/es_ui_shared/static/forms/components/fields/index.ts b/src/plugins/es_ui_shared/static/forms/components/fields/index.ts index f973bb7b04d34..35635d0e8530c 100644 --- a/src/plugins/es_ui_shared/static/forms/components/fields/index.ts +++ b/src/plugins/es_ui_shared/static/forms/components/fields/index.ts @@ -25,5 +25,6 @@ export * from './multi_select_field'; export * from './radio_group_field'; export * from './range_field'; export * from './select_field'; +export * from './super_select_field'; export * from './toggle_field'; export * from './text_area_field'; diff --git a/src/plugins/es_ui_shared/static/forms/components/fields/super_select_field.tsx b/src/plugins/es_ui_shared/static/forms/components/fields/super_select_field.tsx new file mode 100644 index 0000000000000..9b29d75230d7a --- /dev/null +++ b/src/plugins/es_ui_shared/static/forms/components/fields/super_select_field.tsx @@ -0,0 +1,58 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { EuiFormRow, EuiSuperSelect } from '@elastic/eui'; + +import { FieldHook, getFieldValidityAndErrorMessage } from '../../hook_form_lib'; + +interface Props { + field: FieldHook; + euiFieldProps?: Record; + idAria?: string; + [key: string]: any; +} + +export const SuperSelectField = ({ field, euiFieldProps = {}, ...rest }: Props) => { + const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage(field); + + return ( + + { + field.setValue(value); + }} + options={[]} + isInvalid={isInvalid} + data-test-subj="select" + {...euiFieldProps} + /> + + ); +}; diff --git a/src/plugins/es_ui_shared/static/forms/helpers/field_validators/index_pattern_field.ts b/src/plugins/es_ui_shared/static/forms/helpers/field_validators/index_pattern_field.ts index 7f178eabdcd19..48702a32bfde8 100644 --- a/src/plugins/es_ui_shared/static/forms/helpers/field_validators/index_pattern_field.ts +++ b/src/plugins/es_ui_shared/static/forms/helpers/field_validators/index_pattern_field.ts @@ -16,12 +16,13 @@ * specific language governing permissions and limitations * under the License. */ -import { ILLEGAL_CHARACTERS, validateIndexPattern } from 'ui/index_patterns'; import { ValidationFunc } from '../../hook_form_lib'; import { containsChars } from '../../../validators/string'; import { ERROR_CODE } from './types'; +import { indexPatterns } from '../../../../../data/public'; + export const indexPatternField = (i18n: any) => ( ...args: Parameters ): ReturnType> => { @@ -45,9 +46,9 @@ export const indexPatternField = (i18n: any) => ( } // Validate illegal characters - const errors = validateIndexPattern(value); + const errors = indexPatterns.validate(value); - if (errors[ILLEGAL_CHARACTERS]) { + if (errors[indexPatterns.ILLEGAL_CHARACTERS_KEY]) { return { code: 'ERR_FIELD_FORMAT', formatType: 'INDEX_PATTERN', @@ -55,8 +56,8 @@ export const indexPatternField = (i18n: any) => ( defaultMessage: 'The index pattern contains the invalid {characterListLength, plural, one {character} other {characters}} { characterList }.', values: { - characterList: errors[ILLEGAL_CHARACTERS].join(' '), - characterListLength: errors[ILLEGAL_CHARACTERS].length, + characterList: errors[indexPatterns.ILLEGAL_CHARACTERS_KEY].join(' '), + characterListLength: errors[indexPatterns.ILLEGAL_CHARACTERS_KEY].length, }, }), }; diff --git a/src/plugins/es_ui_shared/static/forms/hook_form_lib/constants.ts b/src/plugins/es_ui_shared/static/forms/hook_form_lib/constants.ts index df2807e59ab46..4056947483107 100644 --- a/src/plugins/es_ui_shared/static/forms/hook_form_lib/constants.ts +++ b/src/plugins/es_ui_shared/static/forms/hook_form_lib/constants.ts @@ -28,6 +28,7 @@ export const FIELD_TYPES = { RADIO_GROUP: 'radioGroup', RANGE: 'range', SELECT: 'select', + SUPER_SELECT: 'superSelect', MULTI_SELECT: 'multiSelect', }; diff --git a/src/plugins/inspector/public/views/data/components/download_options.tsx b/src/plugins/inspector/public/views/data/components/download_options.tsx index 6d21dcdafa84d..e7bfbed23c074 100644 --- a/src/plugins/inspector/public/views/data/components/download_options.tsx +++ b/src/plugins/inspector/public/views/data/components/download_options.tsx @@ -20,6 +20,7 @@ import React, { Component } from 'react'; import PropTypes from 'prop-types'; import { FormattedMessage } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; import { EuiButton, EuiContextMenuItem, EuiContextMenuPanel, EuiPopover } from '@elastic/eui'; import { DataViewColumn, DataViewRow } from '../types'; @@ -66,8 +67,14 @@ class DataDownloadOptions extends Component { + let filename = this.props.title; + if (!filename || filename.length === 0) { + filename = i18n.translate('inspector.data.downloadOptionsUnsavedFilename', { + defaultMessage: 'unsaved', + }); + } exportAsCsv({ - filename: `${this.props.title}.csv`, + filename: `${filename}.csv`, columns: this.props.columns, rows: this.props.rows, csvSeparator: this.props.csvSeparator, diff --git a/src/plugins/kibana_react/public/code_editor/code_editor.tsx b/src/plugins/kibana_react/public/code_editor/code_editor.tsx index 0ae77995c0502..62440f12c6d84 100644 --- a/src/plugins/kibana_react/public/code_editor/code_editor.tsx +++ b/src/plugins/kibana_react/public/code_editor/code_editor.tsx @@ -78,6 +78,13 @@ export interface Props { */ hoverProvider?: monacoEditor.languages.HoverProvider; + /** + * Language config provider for bracket + * Documentation for the provider can be found here: + * https://microsoft.github.io/monaco-editor/api/interfaces/monaco.languages.languageconfiguration.html + */ + languageConfiguration?: monacoEditor.languages.LanguageConfiguration; + /** * Function called before the editor is mounted in the view */ @@ -130,6 +137,13 @@ export class CodeEditor extends React.Component { if (this.props.hoverProvider) { monaco.languages.registerHoverProvider(this.props.languageId, this.props.hoverProvider); } + + if (this.props.languageConfiguration) { + monaco.languages.setLanguageConfiguration( + this.props.languageId, + this.props.languageConfiguration + ); + } }); // Register the theme diff --git a/src/plugins/kibana_react/public/index.ts b/src/plugins/kibana_react/public/index.ts index 10b7dd2b4da44..cfe89f16e99dd 100644 --- a/src/plugins/kibana_react/public/index.ts +++ b/src/plugins/kibana_react/public/index.ts @@ -25,4 +25,5 @@ export * from './overlays'; export * from './ui_settings'; export * from './field_icon'; export * from './table_list_view'; +export { useUrlTracker } from './use_url_tracker'; export { toMountPoint } from './util'; diff --git a/src/plugins/kibana_react/public/saved_objects/saved_object_finder.tsx b/src/plugins/kibana_react/public/saved_objects/saved_object_finder.tsx index bd2beaf77a305..1522c6b42824c 100644 --- a/src/plugins/kibana_react/public/saved_objects/saved_object_finder.tsx +++ b/src/plugins/kibana_react/public/saved_objects/saved_object_finder.tsx @@ -346,6 +346,9 @@ class SavedObjectFinderUi extends React.Component< placeholder={i18n.translate('kibana-react.savedObjects.finder.searchPlaceholder', { defaultMessage: 'Search…', })} + aria-label={i18n.translate('kibana-react.savedObjects.finder.searchPlaceholder', { + defaultMessage: 'Search…', + })} fullWidth value={this.state.query} onChange={e => { diff --git a/src/plugins/kibana_react/public/table_list_view/table_list_view.tsx b/src/plugins/kibana_react/public/table_list_view/table_list_view.tsx index 2e7b22a14fb0e..4c2dac4f39134 100644 --- a/src/plugins/kibana_react/public/table_list_view/table_list_view.tsx +++ b/src/plugins/kibana_react/public/table_list_view/table_list_view.tsx @@ -67,6 +67,11 @@ export interface TableListViewProps { tableListTitle: string; toastNotifications: ToastsStart; uiSettings: IUiSettingsClient; + /** + * Id of the heading element describing the table. This id will be used as `aria-labelledby` of the wrapper element. + * If the table is not empty, this component renders its own h1 element using the same id. + */ + headingId?: string; } export interface TableListViewState { @@ -463,7 +468,7 @@ class TableListView extends React.Component -

{this.props.tableListTitle}

+

{this.props.tableListTitle}

@@ -498,7 +503,11 @@ class TableListView extends React.Component - {this.renderPageContent()} + + {this.renderPageContent()} + ); } diff --git a/src/plugins/kibana_react/public/use_url_tracker/index.ts b/src/plugins/kibana_react/public/use_url_tracker/index.ts new file mode 100644 index 0000000000000..fdceaf34e04ee --- /dev/null +++ b/src/plugins/kibana_react/public/use_url_tracker/index.ts @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { useUrlTracker } from './use_url_tracker'; diff --git a/src/plugins/kibana_react/public/use_url_tracker/use_url_tracker.test.tsx b/src/plugins/kibana_react/public/use_url_tracker/use_url_tracker.test.tsx new file mode 100644 index 0000000000000..d1425a09b2f9c --- /dev/null +++ b/src/plugins/kibana_react/public/use_url_tracker/use_url_tracker.test.tsx @@ -0,0 +1,70 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { renderHook } from '@testing-library/react-hooks'; +import { useUrlTracker } from './use_url_tracker'; +import { StubBrowserStorage } from 'test_utils/stub_browser_storage'; +import { createMemoryHistory } from 'history'; + +describe('useUrlTracker', () => { + const key = 'key'; + let storage = new StubBrowserStorage(); + let history = createMemoryHistory(); + beforeEach(() => { + storage = new StubBrowserStorage(); + history = createMemoryHistory(); + }); + + it('should track history changes and save them to storage', () => { + expect(storage.getItem(key)).toBeNull(); + const { unmount } = renderHook(() => { + useUrlTracker(key, history, () => false, storage); + }); + expect(storage.getItem(key)).toBe('/'); + history.push('/change'); + expect(storage.getItem(key)).toBe('/change'); + unmount(); + history.push('/other-change'); + expect(storage.getItem(key)).toBe('/change'); + }); + + it('by default should restore initial url', () => { + storage.setItem(key, '/change'); + renderHook(() => { + useUrlTracker(key, history, undefined, storage); + }); + expect(history.location.pathname).toBe('/change'); + }); + + it('should restore initial url if shouldRestoreUrl cb returns true', () => { + storage.setItem(key, '/change'); + renderHook(() => { + useUrlTracker(key, history, () => true, storage); + }); + expect(history.location.pathname).toBe('/change'); + }); + + it('should not restore initial url if shouldRestoreUrl cb returns false', () => { + storage.setItem(key, '/change'); + renderHook(() => { + useUrlTracker(key, history, () => false, storage); + }); + expect(history.location.pathname).toBe('/'); + }); +}); diff --git a/src/plugins/kibana_react/public/use_url_tracker/use_url_tracker.tsx b/src/plugins/kibana_react/public/use_url_tracker/use_url_tracker.tsx new file mode 100644 index 0000000000000..97e69fe22a842 --- /dev/null +++ b/src/plugins/kibana_react/public/use_url_tracker/use_url_tracker.tsx @@ -0,0 +1,52 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { History } from 'history'; +import { useLayoutEffect } from 'react'; +import { createUrlTracker } from '../../../kibana_utils/public/'; + +/** + * State management url_tracker in react hook form + * + * Replicates what src/legacy/ui/public/chrome/api/nav.ts did + * Persists the url in sessionStorage so it could be restored if navigated back to the app + * + * @param key - key to use in storage + * @param history - history instance to use + * @param shouldRestoreUrl - cb if url should be restored + * @param storage - storage to use. window.sessionStorage is default + */ +export function useUrlTracker( + key: string, + history: History, + shouldRestoreUrl: (urlToRestore: string) => boolean = () => true, + storage: Storage = sessionStorage +) { + useLayoutEffect(() => { + const urlTracker = createUrlTracker(key, storage); + const urlToRestore = urlTracker.getTrackedUrl(); + if (urlToRestore && shouldRestoreUrl(urlToRestore)) { + history.replace(urlToRestore); + } + const stopTrackingUrl = urlTracker.startTrackingUrl(history); + return () => { + stopTrackingUrl(); + }; + }, [key, history]); +} diff --git a/src/plugins/kibana_utils/common/distinct_until_changed_with_initial_value.test.ts b/src/plugins/kibana_utils/common/distinct_until_changed_with_initial_value.test.ts new file mode 100644 index 0000000000000..24f8f13f21478 --- /dev/null +++ b/src/plugins/kibana_utils/common/distinct_until_changed_with_initial_value.test.ts @@ -0,0 +1,75 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Subject } from 'rxjs'; +import { distinctUntilChangedWithInitialValue } from './distinct_until_changed_with_initial_value'; +import { toArray } from 'rxjs/operators'; +import deepEqual from 'fast-deep-equal'; + +describe('distinctUntilChangedWithInitialValue', () => { + it('should skip updates with the same value', async () => { + const subject = new Subject(); + const result = subject.pipe(distinctUntilChangedWithInitialValue(1), toArray()).toPromise(); + + subject.next(2); + subject.next(3); + subject.next(3); + subject.next(3); + subject.complete(); + + expect(await result).toEqual([2, 3]); + }); + + it('should accept promise as initial value', async () => { + const subject = new Subject(); + const result = subject + .pipe( + distinctUntilChangedWithInitialValue( + new Promise(resolve => { + resolve(1); + setTimeout(() => { + subject.next(2); + subject.next(3); + subject.next(3); + subject.next(3); + subject.complete(); + }); + }) + ), + toArray() + ) + .toPromise(); + expect(await result).toEqual([2, 3]); + }); + + it('should accept custom comparator', async () => { + const subject = new Subject(); + const result = subject + .pipe(distinctUntilChangedWithInitialValue({ test: 1 }, deepEqual), toArray()) + .toPromise(); + + subject.next({ test: 1 }); + subject.next({ test: 2 }); + subject.next({ test: 2 }); + subject.next({ test: 3 }); + subject.complete(); + + expect(await result).toEqual([{ test: 2 }, { test: 3 }]); + }); +}); diff --git a/src/plugins/kibana_utils/common/distinct_until_changed_with_initial_value.ts b/src/plugins/kibana_utils/common/distinct_until_changed_with_initial_value.ts new file mode 100644 index 0000000000000..6af9cc1e8ac3a --- /dev/null +++ b/src/plugins/kibana_utils/common/distinct_until_changed_with_initial_value.ts @@ -0,0 +1,42 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { MonoTypeOperatorFunction, queueScheduler, scheduled, from } from 'rxjs'; +import { concatAll, distinctUntilChanged, skip } from 'rxjs/operators'; + +export function distinctUntilChangedWithInitialValue( + initialValue: T | Promise, + compare?: (x: T, y: T) => boolean +): MonoTypeOperatorFunction { + return input$ => + scheduled( + [isPromise(initialValue) ? from(initialValue) : [initialValue], input$], + queueScheduler + ).pipe(concatAll(), distinctUntilChanged(compare), skip(1)); +} + +function isPromise(value: T | Promise): value is Promise { + return ( + !!value && + typeof value === 'object' && + 'then' in value && + typeof value.then === 'function' && + !('subscribe' in value) + ); +} diff --git a/src/plugins/kibana_utils/common/index.ts b/src/plugins/kibana_utils/common/index.ts index d13a250cedf2e..eb3bb96c8e874 100644 --- a/src/plugins/kibana_utils/common/index.ts +++ b/src/plugins/kibana_utils/common/index.ts @@ -18,3 +18,4 @@ */ export * from './defer'; +export { distinctUntilChangedWithInitialValue } from './distinct_until_changed_with_initial_value'; diff --git a/src/plugins/kibana_utils/demos/demos.test.ts b/src/plugins/kibana_utils/demos/demos.test.ts index 4e792ceef117a..b905aeff41f1f 100644 --- a/src/plugins/kibana_utils/demos/demos.test.ts +++ b/src/plugins/kibana_utils/demos/demos.test.ts @@ -19,6 +19,7 @@ import { result as counterResult } from './state_containers/counter'; import { result as todomvcResult } from './state_containers/todomvc'; +import { result as urlSyncResult } from './state_sync/url'; describe('demos', () => { describe('state containers', () => { @@ -33,4 +34,12 @@ describe('demos', () => { ]); }); }); + + describe('state sync', () => { + test('url sync demo works', async () => { + expect(await urlSyncResult).toMatchInlineSnapshot( + `"http://localhost/#?_s=(todos:!((completed:!f,id:0,text:'Learning%20state%20containers'),(completed:!f,id:2,text:test)))"` + ); + }); + }); }); diff --git a/src/plugins/kibana_utils/demos/state_containers/counter.ts b/src/plugins/kibana_utils/demos/state_containers/counter.ts index 643763cc4cee9..4ddf532c1506d 100644 --- a/src/plugins/kibana_utils/demos/state_containers/counter.ts +++ b/src/plugins/kibana_utils/demos/state_containers/counter.ts @@ -19,14 +19,24 @@ import { createStateContainer } from '../../public/state_containers'; -const container = createStateContainer(0, { - increment: (cnt: number) => (by: number) => cnt + by, - double: (cnt: number) => () => cnt * 2, -}); +interface State { + count: number; +} + +const container = createStateContainer( + { count: 0 }, + { + increment: (state: State) => (by: number) => ({ count: state.count + by }), + double: (state: State) => () => ({ count: state.count * 2 }), + }, + { + count: (state: State) => () => state.count, + } +); container.transitions.increment(5); container.transitions.double(); -console.log(container.get()); // eslint-disable-line +console.log(container.selectors.count()); // eslint-disable-line -export const result = container.get(); +export const result = container.selectors.count(); diff --git a/src/plugins/kibana_utils/demos/state_containers/todomvc.ts b/src/plugins/kibana_utils/demos/state_containers/todomvc.ts index 6d0c960e2a5b2..e807783a56f31 100644 --- a/src/plugins/kibana_utils/demos/state_containers/todomvc.ts +++ b/src/plugins/kibana_utils/demos/state_containers/todomvc.ts @@ -25,15 +25,19 @@ export interface TodoItem { id: number; } -export type TodoState = TodoItem[]; +export interface TodoState { + todos: TodoItem[]; +} -export const defaultState: TodoState = [ - { - id: 0, - text: 'Learning state containers', - completed: false, - }, -]; +export const defaultState: TodoState = { + todos: [ + { + id: 0, + text: 'Learning state containers', + completed: false, + }, + ], +}; export interface TodoActions { add: PureTransition; @@ -44,17 +48,34 @@ export interface TodoActions { clearCompleted: PureTransition; } +export interface TodosSelectors { + todos: (state: TodoState) => () => TodoItem[]; + todo: (state: TodoState) => (id: number) => TodoItem | null; +} + export const pureTransitions: TodoActions = { - add: state => todo => [...state, todo], - edit: state => todo => state.map(item => (item.id === todo.id ? { ...item, ...todo } : item)), - delete: state => id => state.filter(item => item.id !== id), - complete: state => id => - state.map(item => (item.id === id ? { ...item, completed: true } : item)), - completeAll: state => () => state.map(item => ({ ...item, completed: true })), - clearCompleted: state => () => state.filter(({ completed }) => !completed), + add: state => todo => ({ todos: [...state.todos, todo] }), + edit: state => todo => ({ + todos: state.todos.map(item => (item.id === todo.id ? { ...item, ...todo } : item)), + }), + delete: state => id => ({ todos: state.todos.filter(item => item.id !== id) }), + complete: state => id => ({ + todos: state.todos.map(item => (item.id === id ? { ...item, completed: true } : item)), + }), + completeAll: state => () => ({ todos: state.todos.map(item => ({ ...item, completed: true })) }), + clearCompleted: state => () => ({ todos: state.todos.filter(({ completed }) => !completed) }), +}; + +export const pureSelectors: TodosSelectors = { + todos: state => () => state.todos, + todo: state => id => state.todos.find(todo => todo.id === id) ?? null, }; -const container = createStateContainer(defaultState, pureTransitions); +const container = createStateContainer( + defaultState, + pureTransitions, + pureSelectors +); container.transitions.add({ id: 1, @@ -64,6 +85,6 @@ container.transitions.add({ container.transitions.complete(0); container.transitions.complete(1); -console.log(container.get()); // eslint-disable-line +console.log(container.selectors.todos()); // eslint-disable-line -export const result = container.get(); +export const result = container.selectors.todos(); diff --git a/src/plugins/kibana_utils/demos/state_sync/url.ts b/src/plugins/kibana_utils/demos/state_sync/url.ts new file mode 100644 index 0000000000000..2c426cae6733a --- /dev/null +++ b/src/plugins/kibana_utils/demos/state_sync/url.ts @@ -0,0 +1,70 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { defaultState, pureTransitions, TodoActions, TodoState } from '../state_containers/todomvc'; +import { BaseState, BaseStateContainer, createStateContainer } from '../../public/state_containers'; +import { + createKbnUrlStateStorage, + syncState, + INullableBaseStateContainer, +} from '../../public/state_sync'; + +const tick = () => new Promise(resolve => setTimeout(resolve)); + +const stateContainer = createStateContainer(defaultState, pureTransitions); +const { start, stop } = syncState({ + stateContainer: withDefaultState(stateContainer, defaultState), + storageKey: '_s', + stateStorage: createKbnUrlStateStorage(), +}); + +start(); +export const result = Promise.resolve() + .then(() => { + // http://localhost/#?_s=!((completed:!f,id:0,text:'Learning+state+containers')" + + stateContainer.transitions.add({ + id: 2, + text: 'test', + completed: false, + }); + + // http://localhost/#?_s=!((completed:!f,id:0,text:'Learning+state+containers'),(completed:!f,id:2,text:test))" + + /* actual url updates happens async */ + return tick(); + }) + .then(() => { + stop(); + return window.location.href; + }); + +function withDefaultState( + // eslint-disable-next-line no-shadow + stateContainer: BaseStateContainer, + // eslint-disable-next-line no-shadow + defaultState: State +): INullableBaseStateContainer { + return { + ...stateContainer, + set: (state: State | null) => { + stateContainer.set(state || defaultState); + }, + }; +} diff --git a/src/plugins/kibana_utils/docs/state_containers/README.md b/src/plugins/kibana_utils/docs/state_containers/README.md index 3b7a8b8bd4621..583f8f65ce6b6 100644 --- a/src/plugins/kibana_utils/docs/state_containers/README.md +++ b/src/plugins/kibana_utils/docs/state_containers/README.md @@ -18,14 +18,21 @@ your services or apps. ```ts import { createStateContainer } from 'src/plugins/kibana_utils'; -const container = createStateContainer(0, { - increment: (cnt: number) => (by: number) => cnt + by, - double: (cnt: number) => () => cnt * 2, -}); +const container = createStateContainer( + { count: 0 }, + { + increment: (state: {count: number}) => (by: number) => ({ count: state.count + by }), + double: (state: {count: number}) => () => ({ count: state.count * 2 }), + }, + { + count: (state: {count: number}) => () => state.count, + } +); container.transitions.increment(5); container.transitions.double(); -console.log(container.get()); // 10 + +console.log(container.selectors.count()); // 10 ``` diff --git a/src/plugins/kibana_utils/docs/state_containers/creation.md b/src/plugins/kibana_utils/docs/state_containers/creation.md index 66d28bbd8603f..f8ded75ed3f45 100644 --- a/src/plugins/kibana_utils/docs/state_containers/creation.md +++ b/src/plugins/kibana_utils/docs/state_containers/creation.md @@ -32,7 +32,7 @@ Create your a state container. ```ts import { createStateContainer } from 'src/plugins/kibana_utils'; -const container = createStateContainer(defaultState, {}); +const container = createStateContainer(defaultState); console.log(container.get()); ``` diff --git a/src/plugins/kibana_utils/docs/state_containers/no_react.md b/src/plugins/kibana_utils/docs/state_containers/no_react.md index 7a15483d83b44..a72995f4f1eae 100644 --- a/src/plugins/kibana_utils/docs/state_containers/no_react.md +++ b/src/plugins/kibana_utils/docs/state_containers/no_react.md @@ -1,13 +1,13 @@ # Consuming state in non-React setting -To read the current `state` of the store use `.get()` method. +To read the current `state` of the store use `.get()` method or `getState()` alias method. ```ts -store.get(); +stateContainer.get(); ``` To listen for latest state changes use `.state$` observable. ```ts -store.state$.subscribe(state => { ... }); +stateContainer.state$.subscribe(state => { ... }); ``` diff --git a/src/plugins/kibana_utils/docs/state_containers/react.md b/src/plugins/kibana_utils/docs/state_containers/react.md index 363fd9253d44f..1bab1af1d5f68 100644 --- a/src/plugins/kibana_utils/docs/state_containers/react.md +++ b/src/plugins/kibana_utils/docs/state_containers/react.md @@ -9,7 +9,7 @@ ```ts import { createStateContainer, createStateContainerReactHelpers } from 'src/plugins/kibana_utils'; -const container = createStateContainer({}, {}); +const container = createStateContainer({}); export const { Provider, Consumer, diff --git a/src/plugins/kibana_utils/public/field_mapping/mapping_setup.ts b/src/plugins/kibana_utils/public/field_mapping/mapping_setup.ts index 72f3716147efa..99b49b401a8b8 100644 --- a/src/plugins/kibana_utils/public/field_mapping/mapping_setup.ts +++ b/src/plugins/kibana_utils/public/field_mapping/mapping_setup.ts @@ -19,7 +19,9 @@ import { mapValues, isString } from 'lodash'; import { FieldMappingSpec, MappingObject } from './types'; -import { ES_FIELD_TYPES } from '../../../data/public'; + +// import from ./common/types to prevent circular dependency of kibana_utils <-> data plugin +import { ES_FIELD_TYPES } from '../../../data/common/types'; /** @private */ type ShorthandFieldMapObject = FieldMappingSpec | ES_FIELD_TYPES | 'json'; diff --git a/src/plugins/kibana_utils/public/index.ts b/src/plugins/kibana_utils/public/index.ts index af2fc9e31b21b..0ba444c4e9395 100644 --- a/src/plugins/kibana_utils/public/index.ts +++ b/src/plugins/kibana_utils/public/index.ts @@ -27,6 +27,34 @@ export * from './render_complete'; export * from './resize_checker'; export * from './state_containers'; export * from './storage'; -export * from './storage/hashed_item_store'; -export * from './state_management/state_hash'; -export * from './state_management/url'; +export { hashedItemStore, HashedItemStore } from './storage/hashed_item_store'; +export { + createStateHash, + persistState, + retrieveState, + isStateHash, +} from './state_management/state_hash'; +export { + hashQuery, + hashUrl, + unhashUrl, + unhashQuery, + createUrlTracker, + createKbnUrlControls, + getStateFromKbnUrl, + getStatesFromKbnUrl, + setStateToKbnUrl, +} from './state_management/url'; +export { + syncState, + syncStates, + createKbnUrlStateStorage, + createSessionStorageStateStorage, + IStateSyncConfig, + ISyncStateRef, + IKbnUrlStateStorage, + INullableBaseStateContainer, + ISessionStorageStateStorage, + StartSyncStateFnType, + StopSyncStateFnType, +} from './state_sync'; diff --git a/src/plugins/kibana_utils/public/state_containers/create_state_container.test.ts b/src/plugins/kibana_utils/public/state_containers/create_state_container.test.ts index 9165181299a90..d4877acaa5ca0 100644 --- a/src/plugins/kibana_utils/public/state_containers/create_state_container.test.ts +++ b/src/plugins/kibana_utils/public/state_containers/create_state_container.test.ts @@ -19,18 +19,9 @@ import { createStateContainer } from './create_state_container'; -const create = (state: S, transitions: T = {} as T) => { - const pureTransitions = { - set: () => (newState: S) => newState, - ...transitions, - }; - const store = createStateContainer(state, pureTransitions); - return { store, mutators: store.transitions }; -}; - -test('can create store', () => { - const { store } = create({}); - expect(store).toMatchObject({ +test('can create state container', () => { + const stateContainer = createStateContainer({}); + expect(stateContainer).toMatchObject({ getState: expect.any(Function), state$: expect.any(Object), transitions: expect.any(Object), @@ -45,9 +36,9 @@ test('can set default state', () => { const defaultState = { foo: 'bar', }; - const { store } = create(defaultState); - expect(store.get()).toEqual(defaultState); - expect(store.getState()).toEqual(defaultState); + const stateContainer = createStateContainer(defaultState); + expect(stateContainer.get()).toEqual(defaultState); + expect(stateContainer.getState()).toEqual(defaultState); }); test('can set state', () => { @@ -57,12 +48,12 @@ test('can set state', () => { const newState = { foo: 'baz', }; - const { store, mutators } = create(defaultState); + const stateContainer = createStateContainer(defaultState); - mutators.set(newState); + stateContainer.set(newState); - expect(store.get()).toEqual(newState); - expect(store.getState()).toEqual(newState); + expect(stateContainer.get()).toEqual(newState); + expect(stateContainer.getState()).toEqual(newState); }); test('does not shallow merge states', () => { @@ -72,22 +63,22 @@ test('does not shallow merge states', () => { const newState = { foo2: 'baz', }; - const { store, mutators } = create(defaultState); + const stateContainer = createStateContainer(defaultState); - mutators.set(newState as any); + stateContainer.set(newState as any); - expect(store.get()).toEqual(newState); - expect(store.getState()).toEqual(newState); + expect(stateContainer.get()).toEqual(newState); + expect(stateContainer.getState()).toEqual(newState); }); test('can subscribe and unsubscribe to state changes', () => { - const { store, mutators } = create({}); + const stateContainer = createStateContainer({}); const spy = jest.fn(); - const subscription = store.state$.subscribe(spy); - mutators.set({ a: 1 }); - mutators.set({ a: 2 }); + const subscription = stateContainer.state$.subscribe(spy); + stateContainer.set({ a: 1 }); + stateContainer.set({ a: 2 }); subscription.unsubscribe(); - mutators.set({ a: 3 }); + stateContainer.set({ a: 3 }); expect(spy).toHaveBeenCalledTimes(2); expect(spy.mock.calls[0][0]).toEqual({ a: 1 }); @@ -95,16 +86,16 @@ test('can subscribe and unsubscribe to state changes', () => { }); test('multiple subscribers can subscribe', () => { - const { store, mutators } = create({}); + const stateContainer = createStateContainer({}); const spy1 = jest.fn(); const spy2 = jest.fn(); - const subscription1 = store.state$.subscribe(spy1); - const subscription2 = store.state$.subscribe(spy2); - mutators.set({ a: 1 }); + const subscription1 = stateContainer.state$.subscribe(spy1); + const subscription2 = stateContainer.state$.subscribe(spy2); + stateContainer.set({ a: 1 }); subscription1.unsubscribe(); - mutators.set({ a: 2 }); + stateContainer.set({ a: 2 }); subscription2.unsubscribe(); - mutators.set({ a: 3 }); + stateContainer.set({ a: 3 }); expect(spy1).toHaveBeenCalledTimes(1); expect(spy2).toHaveBeenCalledTimes(2); @@ -113,19 +104,26 @@ test('multiple subscribers can subscribe', () => { expect(spy2.mock.calls[1][0]).toEqual({ a: 2 }); }); -test('creates impure mutators from pure mutators', () => { - const { mutators } = create( +test('can create state container without transitions', () => { + const state = { foo: 'bar' }; + const stateContainer = createStateContainer(state); + expect(stateContainer.transitions).toEqual({}); + expect(stateContainer.get()).toEqual(state); +}); + +test('creates transitions', () => { + const stateContainer = createStateContainer( {}, { setFoo: () => (bar: any) => ({ foo: bar }), } ); - expect(typeof mutators.setFoo).toBe('function'); + expect(typeof stateContainer.transitions.setFoo).toBe('function'); }); -test('mutators can update state', () => { - const { store, mutators } = create( +test('transitions can update state', () => { + const stateContainer = createStateContainer( { value: 0, foo: 'bar', @@ -136,30 +134,30 @@ test('mutators can update state', () => { } ); - expect(store.get()).toEqual({ + expect(stateContainer.get()).toEqual({ value: 0, foo: 'bar', }); - mutators.add(11); - mutators.setFoo('baz'); + stateContainer.transitions.add(11); + stateContainer.transitions.setFoo('baz'); - expect(store.get()).toEqual({ + expect(stateContainer.get()).toEqual({ value: 11, foo: 'baz', }); - mutators.add(-20); - mutators.setFoo('bazooka'); + stateContainer.transitions.add(-20); + stateContainer.transitions.setFoo('bazooka'); - expect(store.get()).toEqual({ + expect(stateContainer.get()).toEqual({ value: -9, foo: 'bazooka', }); }); -test('mutators methods are not bound', () => { - const { store, mutators } = create( +test('transitions methods are not bound', () => { + const stateContainer = createStateContainer( { value: -3 }, { add: (state: { value: number }) => (increment: number) => ({ @@ -169,13 +167,13 @@ test('mutators methods are not bound', () => { } ); - expect(store.get()).toEqual({ value: -3 }); - mutators.add(4); - expect(store.get()).toEqual({ value: 1 }); + expect(stateContainer.get()).toEqual({ value: -3 }); + stateContainer.transitions.add(4); + expect(stateContainer.get()).toEqual({ value: 1 }); }); -test('created mutators are saved in store object', () => { - const { store, mutators } = create( +test('created transitions are saved in stateContainer object', () => { + const stateContainer = createStateContainer( { value: -3 }, { add: (state: { value: number }) => (increment: number) => ({ @@ -185,55 +183,57 @@ test('created mutators are saved in store object', () => { } ); - expect(typeof store.transitions.add).toBe('function'); - mutators.add(5); - expect(store.get()).toEqual({ value: 2 }); + expect(typeof stateContainer.transitions.add).toBe('function'); + stateContainer.transitions.add(5); + expect(stateContainer.get()).toEqual({ value: 2 }); }); -test('throws when state is modified inline - 1', () => { - const container = createStateContainer({ a: 'b' }, {}); +test('throws when state is modified inline', () => { + const container = createStateContainer({ a: 'b', array: [{ a: 'b' }] }); - let error: TypeError | null = null; - try { + expect(() => { (container.get().a as any) = 'c'; - } catch (err) { - error = err; - } + }).toThrowErrorMatchingInlineSnapshot( + `"Cannot assign to read only property 'a' of object '#'"` + ); - expect(error).toBeInstanceOf(TypeError); -}); + expect(() => { + (container.getState().a as any) = 'c'; + }).toThrowErrorMatchingInlineSnapshot( + `"Cannot assign to read only property 'a' of object '#'"` + ); -test('throws when state is modified inline - 2', () => { - const container = createStateContainer({ a: 'b' }, {}); + expect(() => { + (container.getState().array as any).push('c'); + }).toThrowErrorMatchingInlineSnapshot(`"Cannot add property 1, object is not extensible"`); - let error: TypeError | null = null; - try { - (container.getState().a as any) = 'c'; - } catch (err) { - error = err; - } + expect(() => { + (container.getState().array[0] as any).c = 'b'; + }).toThrowErrorMatchingInlineSnapshot(`"Cannot add property c, object is not extensible"`); - expect(error).toBeInstanceOf(TypeError); + expect(() => { + container.set(null as any); + expect(container.getState()).toBeNull(); + }).not.toThrow(); }); -test('throws when state is modified inline in subscription', done => { +test('throws when state is modified inline in subscription', () => { const container = createStateContainer({ a: 'b' }, { set: () => (newState: any) => newState }); container.subscribe(value => { - let error: TypeError | null = null; - try { + expect(() => { (value.a as any) = 'd'; - } catch (err) { - error = err; - } - expect(error).toBeInstanceOf(TypeError); - done(); + }).toThrowErrorMatchingInlineSnapshot( + `"Cannot assign to read only property 'a' of object '#'"` + ); }); + container.transitions.set({ a: 'c' }); }); describe('selectors', () => { test('can specify no selectors, or can skip them', () => { + createStateContainer({}); createStateContainer({}, {}); createStateContainer({}, {}, {}); }); diff --git a/src/plugins/kibana_utils/public/state_containers/create_state_container.ts b/src/plugins/kibana_utils/public/state_containers/create_state_container.ts index 1ef4a1c012817..d420aec30f068 100644 --- a/src/plugins/kibana_utils/public/state_containers/create_state_container.ts +++ b/src/plugins/kibana_utils/public/state_containers/create_state_container.ts @@ -20,34 +20,52 @@ import { BehaviorSubject } from 'rxjs'; import { skip } from 'rxjs/operators'; import { RecursiveReadonly } from '@kbn/utility-types'; +import deepFreeze from 'deep-freeze-strict'; import { PureTransitionsToTransitions, PureTransition, ReduxLikeStateContainer, PureSelectorsToSelectors, + BaseState, } from './types'; const $$observable = (typeof Symbol === 'function' && (Symbol as any).observable) || '@@observable'; +const $$setActionType = '@@SET'; const freeze: (value: T) => RecursiveReadonly = process.env.NODE_ENV !== 'production' ? (value: T): RecursiveReadonly => { - if (!value) return value as RecursiveReadonly; - if (value instanceof Array) return value as RecursiveReadonly; - if (typeof value === 'object') return Object.freeze({ ...value }) as RecursiveReadonly; - else return value as RecursiveReadonly; + const isFreezable = value !== null && typeof value === 'object'; + if (isFreezable) return deepFreeze(value) as RecursiveReadonly; + return value as RecursiveReadonly; } : (value: T) => value as RecursiveReadonly; -export const createStateContainer = < - State, +export function createStateContainer( + defaultState: State +): ReduxLikeStateContainer; +export function createStateContainer( + defaultState: State, + pureTransitions: PureTransitions +): ReduxLikeStateContainer; +export function createStateContainer< + State extends BaseState, PureTransitions extends object, - PureSelectors extends object = {} + PureSelectors extends object >( defaultState: State, pureTransitions: PureTransitions, + pureSelectors: PureSelectors +): ReduxLikeStateContainer; +export function createStateContainer< + State extends BaseState, + PureTransitions extends object, + PureSelectors extends object +>( + defaultState: State, + pureTransitions: PureTransitions = {} as PureTransitions, pureSelectors: PureSelectors = {} as PureSelectors -): ReduxLikeStateContainer => { +): ReduxLikeStateContainer { const data$ = new BehaviorSubject>(freeze(defaultState)); const state$ = data$.pipe(skip(1)); const get = () => data$.getValue(); @@ -56,9 +74,13 @@ export const createStateContainer = < state$, getState: () => data$.getValue(), set: (state: State) => { - data$.next(freeze(state)); + container.dispatch({ type: $$setActionType, args: [state] }); }, reducer: (state, action) => { + if (action.type === $$setActionType) { + return freeze(action.args[0] as State); + } + const pureTransition = (pureTransitions as Record>)[ action.type ]; @@ -86,4 +108,4 @@ export const createStateContainer = < [$$observable]: state$, }; return container; -}; +} diff --git a/src/plugins/kibana_utils/public/state_containers/create_state_container_react_helpers.test.tsx b/src/plugins/kibana_utils/public/state_containers/create_state_container_react_helpers.test.tsx index 8f5810f3e147d..0f25f65c30ade 100644 --- a/src/plugins/kibana_utils/public/state_containers/create_state_container_react_helpers.test.tsx +++ b/src/plugins/kibana_utils/public/state_containers/create_state_container_react_helpers.test.tsx @@ -23,15 +23,6 @@ import { act, Simulate } from 'react-dom/test-utils'; import { createStateContainer } from './create_state_container'; import { createStateContainerReactHelpers } from './create_state_container_react_helpers'; -const create = (state: S, transitions: T = {} as T) => { - const pureTransitions = { - set: () => (newState: S) => newState, - ...transitions, - }; - const store = createStateContainer(state, pureTransitions); - return { store, mutators: store.transitions }; -}; - let container: HTMLDivElement | null; beforeEach(() => { @@ -56,12 +47,12 @@ test('can create React context', () => { }); test(' passes state to ', () => { - const { store } = create({ hello: 'world' }); - const { Provider, Consumer } = createStateContainerReactHelpers(); + const stateContainer = createStateContainer({ hello: 'world' }); + const { Provider, Consumer } = createStateContainerReactHelpers(); ReactDOM.render( - - {(s: typeof store) => s.get().hello} + + {(s: typeof stateContainer) => s.get().hello} , container ); @@ -79,8 +70,8 @@ interface Props1 { } test(' passes state to connect()()', () => { - const { store } = create({ hello: 'Bob' }); - const { Provider, connect } = createStateContainerReactHelpers(); + const stateContainer = createStateContainer({ hello: 'Bob' }); + const { Provider, connect } = createStateContainerReactHelpers(); const Demo: React.FC = ({ message, stop }) => ( <> @@ -92,7 +83,7 @@ test(' passes state to connect()()', () => { const DemoConnected = connect(mergeProps)(Demo); ReactDOM.render( - + , container @@ -101,14 +92,14 @@ test(' passes state to connect()()', () => { expect(container!.innerHTML).toBe('Bob?'); }); -test('context receives Redux store', () => { - const { store } = create({ foo: 'bar' }); - const { Provider, context } = createStateContainerReactHelpers(); +test('context receives stateContainer', () => { + const stateContainer = createStateContainer({ foo: 'bar' }); + const { Provider, context } = createStateContainerReactHelpers(); ReactDOM.render( /* eslint-disable no-shadow */ - - {store => store.get().foo} + + {stateContainer => stateContainer.get().foo} , /* eslint-enable no-shadow */ container @@ -117,21 +108,21 @@ test('context receives Redux store', () => { expect(container!.innerHTML).toBe('bar'); }); -xtest('can use multiple stores in one React app', () => {}); +test.todo('can use multiple stores in one React app'); describe('hooks', () => { describe('useStore', () => { - test('can select store using useStore hook', () => { - const { store } = create({ foo: 'bar' }); - const { Provider, useContainer } = createStateContainerReactHelpers(); + test('can select store using useContainer hook', () => { + const stateContainer = createStateContainer({ foo: 'bar' }); + const { Provider, useContainer } = createStateContainerReactHelpers(); const Demo: React.FC<{}> = () => { // eslint-disable-next-line no-shadow - const store = useContainer(); - return <>{store.get().foo}; + const stateContainer = useContainer(); + return <>{stateContainer.get().foo}; }; ReactDOM.render( - + , container @@ -143,15 +134,15 @@ describe('hooks', () => { describe('useState', () => { test('can select state using useState hook', () => { - const { store } = create({ foo: 'qux' }); - const { Provider, useState } = createStateContainerReactHelpers(); + const stateContainer = createStateContainer({ foo: 'qux' }); + const { Provider, useState } = createStateContainerReactHelpers(); const Demo: React.FC<{}> = () => { const { foo } = useState(); return <>{foo}; }; ReactDOM.render( - + , container @@ -161,23 +152,20 @@ describe('hooks', () => { }); test('re-renders when state changes', () => { - const { - store, - mutators: { setFoo }, - } = create( + const stateContainer = createStateContainer( { foo: 'bar' }, { setFoo: (state: { foo: string }) => (foo: string) => ({ ...state, foo }), } ); - const { Provider, useState } = createStateContainerReactHelpers(); + const { Provider, useState } = createStateContainerReactHelpers(); const Demo: React.FC<{}> = () => { const { foo } = useState(); return <>{foo}; }; ReactDOM.render( - + , container @@ -185,7 +173,7 @@ describe('hooks', () => { expect(container!.innerHTML).toBe('bar'); act(() => { - setFoo('baz'); + stateContainer.transitions.setFoo('baz'); }); expect(container!.innerHTML).toBe('baz'); }); @@ -193,12 +181,7 @@ describe('hooks', () => { describe('useTransitions', () => { test('useTransitions hook returns mutations that can update state', () => { - const { store } = create< - { - cnt: number; - }, - any - >( + const stateContainer = createStateContainer( { cnt: 0, }, @@ -211,7 +194,7 @@ describe('hooks', () => { ); const { Provider, useState, useTransitions } = createStateContainerReactHelpers< - typeof store + typeof stateContainer >(); const Demo: React.FC<{}> = () => { const { cnt } = useState(); @@ -225,7 +208,7 @@ describe('hooks', () => { }; ReactDOM.render( - + , container @@ -245,7 +228,7 @@ describe('hooks', () => { describe('useSelector', () => { test('can select deeply nested value', () => { - const { store } = create({ + const stateContainer = createStateContainer({ foo: { bar: { baz: 'qux', @@ -253,14 +236,14 @@ describe('hooks', () => { }, }); const selector = (state: { foo: { bar: { baz: string } } }) => state.foo.bar.baz; - const { Provider, useSelector } = createStateContainerReactHelpers(); + const { Provider, useSelector } = createStateContainerReactHelpers(); const Demo: React.FC<{}> = () => { const value = useSelector(selector); return <>{value}; }; ReactDOM.render( - + , container @@ -270,7 +253,7 @@ describe('hooks', () => { }); test('re-renders when state changes', () => { - const { store, mutators } = create({ + const stateContainer = createStateContainer({ foo: { bar: { baz: 'qux', @@ -285,7 +268,7 @@ describe('hooks', () => { }; ReactDOM.render( - + , container @@ -293,7 +276,7 @@ describe('hooks', () => { expect(container!.innerHTML).toBe('qux'); act(() => { - mutators.set({ + stateContainer.set({ foo: { bar: { baz: 'quux', @@ -305,9 +288,9 @@ describe('hooks', () => { }); test("re-renders only when selector's result changes", async () => { - const { store, mutators } = create({ a: 'b', foo: 'bar' }); + const stateContainer = createStateContainer({ a: 'b', foo: 'bar' }); const selector = (state: { foo: string }) => state.foo; - const { Provider, useSelector } = createStateContainerReactHelpers(); + const { Provider, useSelector } = createStateContainerReactHelpers(); let cnt = 0; const Demo: React.FC<{}> = () => { @@ -316,7 +299,7 @@ describe('hooks', () => { return <>{value}; }; ReactDOM.render( - + , container @@ -326,14 +309,14 @@ describe('hooks', () => { expect(cnt).toBe(1); act(() => { - mutators.set({ a: 'c', foo: 'bar' }); + stateContainer.set({ a: 'c', foo: 'bar' }); }); await new Promise(r => setTimeout(r, 1)); expect(cnt).toBe(1); act(() => { - mutators.set({ a: 'd', foo: 'bar 2' }); + stateContainer.set({ a: 'd', foo: 'bar 2' }); }); await new Promise(r => setTimeout(r, 1)); @@ -341,9 +324,9 @@ describe('hooks', () => { }); test('does not re-render on same shape object', async () => { - const { store, mutators } = create({ foo: { bar: 'baz' } }); + const stateContainer = createStateContainer({ foo: { bar: 'baz' } }); const selector = (state: { foo: any }) => state.foo; - const { Provider, useSelector } = createStateContainerReactHelpers(); + const { Provider, useSelector } = createStateContainerReactHelpers(); let cnt = 0; const Demo: React.FC<{}> = () => { @@ -352,7 +335,7 @@ describe('hooks', () => { return <>{JSON.stringify(value)}; }; ReactDOM.render( - + , container @@ -362,14 +345,14 @@ describe('hooks', () => { expect(cnt).toBe(1); act(() => { - mutators.set({ foo: { bar: 'baz' } }); + stateContainer.set({ foo: { bar: 'baz' } }); }); await new Promise(r => setTimeout(r, 1)); expect(cnt).toBe(1); act(() => { - mutators.set({ foo: { bar: 'qux' } }); + stateContainer.set({ foo: { bar: 'qux' } }); }); await new Promise(r => setTimeout(r, 1)); @@ -377,7 +360,7 @@ describe('hooks', () => { }); test('can set custom comparator function to prevent re-renders on deep equality', async () => { - const { store, mutators } = create( + const stateContainer = createStateContainer( { foo: { bar: 'baz' } }, { set: () => (newState: { foo: { bar: string } }) => newState, @@ -385,7 +368,7 @@ describe('hooks', () => { ); const selector = (state: { foo: any }) => state.foo; const comparator = (prev: any, curr: any) => JSON.stringify(prev) === JSON.stringify(curr); - const { Provider, useSelector } = createStateContainerReactHelpers(); + const { Provider, useSelector } = createStateContainerReactHelpers(); let cnt = 0; const Demo: React.FC<{}> = () => { @@ -394,7 +377,7 @@ describe('hooks', () => { return <>{JSON.stringify(value)}; }; ReactDOM.render( - + , container @@ -404,13 +387,13 @@ describe('hooks', () => { expect(cnt).toBe(1); act(() => { - mutators.set({ foo: { bar: 'baz' } }); + stateContainer.set({ foo: { bar: 'baz' } }); }); await new Promise(r => setTimeout(r, 1)); expect(cnt).toBe(1); }); - xtest('unsubscribes when React un-mounts', () => {}); + test.todo('unsubscribes when React un-mounts'); }); }); diff --git a/src/plugins/kibana_utils/public/state_containers/create_state_container_react_helpers.ts b/src/plugins/kibana_utils/public/state_containers/create_state_container_react_helpers.ts index e94165cc48376..36903f2d7c90f 100644 --- a/src/plugins/kibana_utils/public/state_containers/create_state_container_react_helpers.ts +++ b/src/plugins/kibana_utils/public/state_containers/create_state_container_react_helpers.ts @@ -35,7 +35,7 @@ export const createStateContainerReactHelpers = useContainer().transitions; + const useTransitions: () => Container['transitions'] = () => useContainer().transitions; const useSelector = ( selector: (state: UnboxState) => Result, diff --git a/src/plugins/kibana_utils/public/state_containers/types.ts b/src/plugins/kibana_utils/public/state_containers/types.ts index e0a1a18972635..5f27a3d2c1dca 100644 --- a/src/plugins/kibana_utils/public/state_containers/types.ts +++ b/src/plugins/kibana_utils/public/state_containers/types.ts @@ -20,12 +20,13 @@ import { Observable } from 'rxjs'; import { Ensure, RecursiveReadonly } from '@kbn/utility-types'; +export type BaseState = object; export interface TransitionDescription { type: Type; args: Args; } -export type Transition = (...args: Args) => State; -export type PureTransition = ( +export type Transition = (...args: Args) => State; +export type PureTransition = ( state: RecursiveReadonly ) => Transition; export type EnsurePureTransition = Ensure>; @@ -34,14 +35,14 @@ export type PureTransitionsToTransitions = { [K in keyof T]: PureTransitionToTransition>; }; -export interface BaseStateContainer { +export interface BaseStateContainer { get: () => RecursiveReadonly; set: (state: State) => void; state$: Observable>; } export interface StateContainer< - State, + State extends BaseState, PureTransitions extends object, PureSelectors extends object = {} > extends BaseStateContainer { @@ -50,8 +51,8 @@ export interface StateContainer< } export interface ReduxLikeStateContainer< - State, - PureTransitions extends object, + State extends BaseState, + PureTransitions extends object = {}, PureSelectors extends object = {} > extends StateContainer { getState: () => RecursiveReadonly; @@ -63,14 +64,16 @@ export interface ReduxLikeStateContainer< } export type Dispatch = (action: T) => void; - -export type Middleware = ( +export type Middleware = ( store: Pick, 'getState' | 'dispatch'> ) => ( next: (action: TransitionDescription) => TransitionDescription | any ) => Dispatch; -export type Reducer = (state: State, action: TransitionDescription) => State; +export type Reducer = ( + state: State, + action: TransitionDescription +) => State; export type UnboxState< Container extends StateContainer @@ -80,7 +83,7 @@ export type UnboxTransitions< > = Container extends StateContainer ? T : never; export type Selector = (...args: Args) => Result; -export type PureSelector = ( +export type PureSelector = ( state: State ) => Selector; export type EnsurePureSelector = Ensure>; @@ -93,7 +96,12 @@ export type PureSelectorsToSelectors = { export type Comparator = (previous: Result, current: Result) => boolean; -export type MapStateToProps = (state: State) => StateProps; -export type Connect = ( +export type MapStateToProps = ( + state: State +) => StateProps; +export type Connect = < + Props extends object, + StatePropKeys extends keyof Props +>( mapStateToProp: MapStateToProps> ) => (component: React.ComponentType) => React.FC>; diff --git a/src/plugins/kibana_utils/public/state_management/state_encoder/encode_decode_state.ts b/src/plugins/kibana_utils/public/state_management/state_encoder/encode_decode_state.ts new file mode 100644 index 0000000000000..c535e965aa772 --- /dev/null +++ b/src/plugins/kibana_utils/public/state_management/state_encoder/encode_decode_state.ts @@ -0,0 +1,61 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import rison, { RisonValue } from 'rison-node'; +import { isStateHash, retrieveState, persistState } from '../state_hash'; + +// should be: +// export function decodeState(expandedOrHashedState: string) +// but this leads to the chain of types mismatches up to BaseStateContainer interfaces, +// as in state containers we don't have any restrictions on state shape +export function decodeState(expandedOrHashedState: string): State { + if (isStateHash(expandedOrHashedState)) { + return retrieveState(expandedOrHashedState); + } else { + return (rison.decode(expandedOrHashedState) as unknown) as State; + } +} + +// should be: +// export function encodeState(expandedOrHashedState: string) +// but this leads to the chain of types mismatches up to BaseStateContainer interfaces, +// as in state containers we don't have any restrictions on state shape +export function encodeState(state: State, useHash: boolean): string { + if (useHash) { + return persistState(state); + } else { + return rison.encode((state as unknown) as RisonValue); + } +} + +export function hashedStateToExpandedState(expandedOrHashedState: string): string { + if (isStateHash(expandedOrHashedState)) { + return encodeState(retrieveState(expandedOrHashedState), false); + } + + return expandedOrHashedState; +} + +export function expandedStateToHashedState(expandedOrHashedState: string): string { + if (isStateHash(expandedOrHashedState)) { + return expandedOrHashedState; + } + + return persistState(decodeState(expandedOrHashedState)); +} diff --git a/src/plugins/kibana_utils/public/state_management/state_encoder/index.ts b/src/plugins/kibana_utils/public/state_management/state_encoder/index.ts new file mode 100644 index 0000000000000..da1382720faff --- /dev/null +++ b/src/plugins/kibana_utils/public/state_management/state_encoder/index.ts @@ -0,0 +1,25 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { + encodeState, + decodeState, + expandedStateToHashedState, + hashedStateToExpandedState, +} from './encode_decode_state'; diff --git a/src/plugins/kibana_utils/public/state_management/state_hash/index.ts b/src/plugins/kibana_utils/public/state_management/state_hash/index.ts index 0e52c4c55872d..24c3c57613477 100644 --- a/src/plugins/kibana_utils/public/state_management/state_hash/index.ts +++ b/src/plugins/kibana_utils/public/state_management/state_hash/index.ts @@ -17,4 +17,4 @@ * under the License. */ -export * from './state_hash'; +export { isStateHash, createStateHash, persistState, retrieveState } from './state_hash'; diff --git a/src/plugins/kibana_utils/public/state_management/state_hash/state_hash.ts b/src/plugins/kibana_utils/public/state_management/state_hash/state_hash.ts index a3eb5272b112d..f56d71297c030 100644 --- a/src/plugins/kibana_utils/public/state_management/state_hash/state_hash.ts +++ b/src/plugins/kibana_utils/public/state_management/state_hash/state_hash.ts @@ -17,6 +17,7 @@ * under the License. */ +import { i18n } from '@kbn/i18n'; import { Sha256 } from '../../../../../core/public/utils'; import { hashedItemStore } from '../../storage/hashed_item_store'; @@ -52,3 +53,46 @@ export function createStateHash( export function isStateHash(str: string) { return String(str).indexOf(HASH_PREFIX) === 0; } + +export function retrieveState(stateHash: string): State { + const json = hashedItemStore.getItem(stateHash); + const throwUnableToRestoreUrlError = () => { + throw new Error( + i18n.translate('kibana_utils.stateManagement.stateHash.unableToRestoreUrlErrorMessage', { + defaultMessage: + 'Unable to completely restore the URL, be sure to use the share functionality.', + }) + ); + }; + if (json === null) { + return throwUnableToRestoreUrlError(); + } + try { + return JSON.parse(json); + } catch (e) { + return throwUnableToRestoreUrlError(); + } +} + +export function persistState(state: State): string { + const json = JSON.stringify(state); + const hash = createStateHash(json); + + const isItemSet = hashedItemStore.setItem(hash, json); + if (isItemSet) return hash; + // If we ran out of space trying to persist the state, notify the user. + const message = i18n.translate( + 'kibana_utils.stateManagement.stateHash.unableToStoreHistoryInSessionErrorMessage', + { + defaultMessage: + 'Kibana is unable to store history items in your session ' + + `because it is full and there don't seem to be items any items safe ` + + 'to delete.\n\n' + + 'This can usually be fixed by moving to a fresh tab, but could ' + + 'be caused by a larger issue. If you are seeing this message regularly, ' + + 'please file an issue at {gitHubIssuesUrl}.', + values: { gitHubIssuesUrl: 'https://github.com/elastic/kibana/issues' }, + } + ); + throw new Error(message); +} diff --git a/src/plugins/kibana_utils/public/state_management/url/format.test.ts b/src/plugins/kibana_utils/public/state_management/url/format.test.ts new file mode 100644 index 0000000000000..728f069840c72 --- /dev/null +++ b/src/plugins/kibana_utils/public/state_management/url/format.test.ts @@ -0,0 +1,41 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { replaceUrlHashQuery } from './format'; + +describe('format', () => { + describe('replaceUrlHashQuery', () => { + it('should add hash query to url without hash', () => { + const url = 'http://localhost:5601/oxf/app/kibana'; + expect(replaceUrlHashQuery(url, () => ({ test: 'test' }))).toMatchInlineSnapshot( + `"http://localhost:5601/oxf/app/kibana#?test=test"` + ); + }); + + it('should replace hash query', () => { + const url = 'http://localhost:5601/oxf/app/kibana#?test=test'; + expect( + replaceUrlHashQuery(url, query => ({ + ...query, + test1: 'test1', + })) + ).toMatchInlineSnapshot(`"http://localhost:5601/oxf/app/kibana#?test=test&test1=test1"`); + }); + }); +}); diff --git a/src/plugins/kibana_utils/public/state_management/url/format.ts b/src/plugins/kibana_utils/public/state_management/url/format.ts new file mode 100644 index 0000000000000..988ee08627382 --- /dev/null +++ b/src/plugins/kibana_utils/public/state_management/url/format.ts @@ -0,0 +1,41 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { format as formatUrl } from 'url'; +import { ParsedUrlQuery } from 'querystring'; +import { parseUrl, parseUrlHash } from './parse'; +import { stringifyQueryString } from './stringify_query_string'; + +export function replaceUrlHashQuery( + rawUrl: string, + queryReplacer: (query: ParsedUrlQuery) => ParsedUrlQuery +) { + const url = parseUrl(rawUrl); + const hash = parseUrlHash(rawUrl); + const newQuery = queryReplacer(hash?.query || {}); + const searchQueryString = stringifyQueryString(newQuery); + if ((!hash || !hash.search) && !searchQueryString) return rawUrl; // nothing to change. return original url + return formatUrl({ + ...url, + hash: formatUrl({ + pathname: hash?.pathname || '', + search: searchQueryString, + }), + }); +} diff --git a/src/plugins/kibana_utils/public/state_management/url/hash_unhash_url.test.ts b/src/plugins/kibana_utils/public/state_management/url/hash_unhash_url.test.ts index a85158acddefd..ec87b8464ac2d 100644 --- a/src/plugins/kibana_utils/public/state_management/url/hash_unhash_url.test.ts +++ b/src/plugins/kibana_utils/public/state_management/url/hash_unhash_url.test.ts @@ -29,13 +29,6 @@ describe('hash unhash url', () => { describe('hash url', () => { describe('does nothing', () => { - it('if missing input', () => { - expect(() => { - // @ts-ignore - hashUrl(); - }).not.toThrowError(); - }); - it('if url is empty', () => { const url = ''; expect(hashUrl(url)).toBe(url); diff --git a/src/plugins/kibana_utils/public/state_management/url/hash_unhash_url.ts b/src/plugins/kibana_utils/public/state_management/url/hash_unhash_url.ts index 872e7953f938b..a29f8bb9ac635 100644 --- a/src/plugins/kibana_utils/public/state_management/url/hash_unhash_url.ts +++ b/src/plugins/kibana_utils/public/state_management/url/hash_unhash_url.ts @@ -17,13 +17,8 @@ * under the License. */ -import { i18n } from '@kbn/i18n'; -import rison, { RisonObject } from 'rison-node'; -import { stringify as stringifyQueryString } from 'querystring'; -import encodeUriQuery from 'encode-uri-query'; -import { format as formatUrl, parse as parseUrl } from 'url'; -import { hashedItemStore } from '../../storage/hashed_item_store'; -import { createStateHash, isStateHash } from '../state_hash'; +import { expandedStateToHashedState, hashedStateToExpandedState } from '../state_encoder'; +import { replaceUrlHashQuery } from './format'; export type IParsedUrlQuery = Record; @@ -32,8 +27,8 @@ interface IUrlQueryMapperOptions { } export type IUrlQueryReplacerOptions = IUrlQueryMapperOptions; -export const unhashQuery = createQueryMapper(stateHashToRisonState); -export const hashQuery = createQueryMapper(risonStateToStateHash); +export const unhashQuery = createQueryMapper(hashedStateToExpandedState); +export const hashQuery = createQueryMapper(expandedStateToHashedState); export const unhashUrl = createQueryReplacer(unhashQuery); export const hashUrl = createQueryReplacer(hashQuery); @@ -61,97 +56,5 @@ function createQueryReplacer( queryMapper: (q: IParsedUrlQuery, options?: IUrlQueryMapperOptions) => IParsedUrlQuery, options?: IUrlQueryReplacerOptions ) { - return (url: string) => { - if (!url) return url; - - const parsedUrl = parseUrl(url, true); - if (!parsedUrl.hash) return url; - - const appUrl = parsedUrl.hash.slice(1); // trim the # - if (!appUrl) return url; - - const appUrlParsed = parseUrl(appUrl, true); - if (!appUrlParsed.query) return url; - - const changedAppQuery = queryMapper(appUrlParsed.query, options); - - // encodeUriQuery implements the less-aggressive encoding done naturally by - // the browser. We use it to generate the same urls the browser would - const changedAppQueryString = stringifyQueryString(changedAppQuery, undefined, undefined, { - encodeURIComponent: encodeUriQuery, - }); - - return formatUrl({ - ...parsedUrl, - hash: formatUrl({ - pathname: appUrlParsed.pathname, - search: changedAppQueryString, - }), - }); - }; -} - -// TODO: this helper should be merged with or replaced by -// src/legacy/ui/public/state_management/state_storage/hashed_item_store.ts -// maybe to become simplified stateless version -export function retrieveState(stateHash: string): RisonObject { - const json = hashedItemStore.getItem(stateHash); - const throwUnableToRestoreUrlError = () => { - throw new Error( - i18n.translate('kibana_utils.stateManagement.url.unableToRestoreUrlErrorMessage', { - defaultMessage: - 'Unable to completely restore the URL, be sure to use the share functionality.', - }) - ); - }; - if (json === null) { - return throwUnableToRestoreUrlError(); - } - try { - return JSON.parse(json); - } catch (e) { - return throwUnableToRestoreUrlError(); - } -} - -// TODO: this helper should be merged with or replaced by -// src/legacy/ui/public/state_management/state_storage/hashed_item_store.ts -// maybe to become simplified stateless version -export function persistState(state: RisonObject): string { - const json = JSON.stringify(state); - const hash = createStateHash(json); - - const isItemSet = hashedItemStore.setItem(hash, json); - if (isItemSet) return hash; - // If we ran out of space trying to persist the state, notify the user. - const message = i18n.translate( - 'kibana_utils.stateManagement.url.unableToStoreHistoryInSessionErrorMessage', - { - defaultMessage: - 'Kibana is unable to store history items in your session ' + - `because it is full and there don't seem to be items any items safe ` + - 'to delete.\n\n' + - 'This can usually be fixed by moving to a fresh tab, but could ' + - 'be caused by a larger issue. If you are seeing this message regularly, ' + - 'please file an issue at {gitHubIssuesUrl}.', - values: { gitHubIssuesUrl: 'https://github.com/elastic/kibana/issues' }, - } - ); - throw new Error(message); -} - -function stateHashToRisonState(stateHashOrRison: string): string { - if (isStateHash(stateHashOrRison)) { - return rison.encode(retrieveState(stateHashOrRison)); - } - - return stateHashOrRison; -} - -function risonStateToStateHash(stateHashOrRison: string): string | null { - if (isStateHash(stateHashOrRison)) { - return stateHashOrRison; - } - - return persistState(rison.decode(stateHashOrRison) as RisonObject); + return (url: string) => replaceUrlHashQuery(url, query => queryMapper(query, options)); } diff --git a/src/plugins/kibana_utils/public/state_management/url/index.ts b/src/plugins/kibana_utils/public/state_management/url/index.ts index 30c5696233db7..40491bf7a274b 100644 --- a/src/plugins/kibana_utils/public/state_management/url/index.ts +++ b/src/plugins/kibana_utils/public/state_management/url/index.ts @@ -17,4 +17,12 @@ * under the License. */ -export * from './hash_unhash_url'; +export { hashUrl, hashQuery, unhashUrl, unhashQuery } from './hash_unhash_url'; +export { + createKbnUrlControls, + setStateToKbnUrl, + getStateFromKbnUrl, + getStatesFromKbnUrl, + IKbnUrlControls, +} from './kbn_url_storage'; +export { createUrlTracker } from './url_tracker'; diff --git a/src/plugins/kibana_utils/public/state_management/url/kbn_url_storage.test.ts b/src/plugins/kibana_utils/public/state_management/url/kbn_url_storage.test.ts new file mode 100644 index 0000000000000..f1c527d3d5309 --- /dev/null +++ b/src/plugins/kibana_utils/public/state_management/url/kbn_url_storage.test.ts @@ -0,0 +1,246 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import '../../storage/hashed_item_store/mock'; +import { + History, + createBrowserHistory, + createHashHistory, + createMemoryHistory, + createPath, +} from 'history'; +import { + getRelativeToHistoryPath, + createKbnUrlControls, + IKbnUrlControls, + setStateToKbnUrl, + getStateFromKbnUrl, +} from './kbn_url_storage'; + +describe('kbn_url_storage', () => { + describe('getStateFromUrl & setStateToUrl', () => { + const url = 'http://localhost:5601/oxf/app/kibana#/management/kibana/index_patterns/id'; + const state1 = { + testStr: '123', + testNumber: 0, + testObj: { test: '123' }, + testNull: null, + testArray: [1, 2, {}], + }; + const state2 = { + test: '123', + }; + + it('should set expanded state to url', () => { + let newUrl = setStateToKbnUrl('_s', state1, { useHash: false }, url); + expect(newUrl).toMatchInlineSnapshot( + `"http://localhost:5601/oxf/app/kibana#/management/kibana/index_patterns/id?_s=(testArray:!(1,2,()),testNull:!n,testNumber:0,testObj:(test:'123'),testStr:'123')"` + ); + const retrievedState1 = getStateFromKbnUrl('_s', newUrl); + expect(retrievedState1).toEqual(state1); + + newUrl = setStateToKbnUrl('_s', state2, { useHash: false }, newUrl); + expect(newUrl).toMatchInlineSnapshot( + `"http://localhost:5601/oxf/app/kibana#/management/kibana/index_patterns/id?_s=(test:'123')"` + ); + const retrievedState2 = getStateFromKbnUrl('_s', newUrl); + expect(retrievedState2).toEqual(state2); + }); + + it('should set hashed state to url', () => { + let newUrl = setStateToKbnUrl('_s', state1, { useHash: true }, url); + expect(newUrl).toMatchInlineSnapshot( + `"http://localhost:5601/oxf/app/kibana#/management/kibana/index_patterns/id?_s=h@a897fac"` + ); + const retrievedState1 = getStateFromKbnUrl('_s', newUrl); + expect(retrievedState1).toEqual(state1); + + newUrl = setStateToKbnUrl('_s', state2, { useHash: true }, newUrl); + expect(newUrl).toMatchInlineSnapshot( + `"http://localhost:5601/oxf/app/kibana#/management/kibana/index_patterns/id?_s=h@40f94d5"` + ); + const retrievedState2 = getStateFromKbnUrl('_s', newUrl); + expect(retrievedState2).toEqual(state2); + }); + }); + + describe('urlControls', () => { + let history: History; + let urlControls: IKbnUrlControls; + beforeEach(() => { + history = createMemoryHistory(); + urlControls = createKbnUrlControls(history); + }); + + const getCurrentUrl = () => createPath(history.location); + it('should update url', () => { + urlControls.update('/1', false); + + expect(getCurrentUrl()).toBe('/1'); + expect(history.length).toBe(2); + + urlControls.update('/2', true); + + expect(getCurrentUrl()).toBe('/2'); + expect(history.length).toBe(2); + }); + + it('should update url async', async () => { + const pr1 = urlControls.updateAsync(() => '/1', false); + const pr2 = urlControls.updateAsync(() => '/2', false); + const pr3 = urlControls.updateAsync(() => '/3', false); + expect(getCurrentUrl()).toBe('/'); + await Promise.all([pr1, pr2, pr3]); + expect(getCurrentUrl()).toBe('/3'); + }); + + it('should push url state if at least 1 push in async chain', async () => { + const pr1 = urlControls.updateAsync(() => '/1', true); + const pr2 = urlControls.updateAsync(() => '/2', false); + const pr3 = urlControls.updateAsync(() => '/3', true); + expect(getCurrentUrl()).toBe('/'); + await Promise.all([pr1, pr2, pr3]); + expect(getCurrentUrl()).toBe('/3'); + expect(history.length).toBe(2); + }); + + it('should replace url state if all updates in async chain are replace', async () => { + const pr1 = urlControls.updateAsync(() => '/1', true); + const pr2 = urlControls.updateAsync(() => '/2', true); + const pr3 = urlControls.updateAsync(() => '/3', true); + expect(getCurrentUrl()).toBe('/'); + await Promise.all([pr1, pr2, pr3]); + expect(getCurrentUrl()).toBe('/3'); + expect(history.length).toBe(1); + }); + + it('should listen for url updates', async () => { + const cb = jest.fn(); + urlControls.listen(cb); + const pr1 = urlControls.updateAsync(() => '/1', true); + const pr2 = urlControls.updateAsync(() => '/2', true); + const pr3 = urlControls.updateAsync(() => '/3', true); + await Promise.all([pr1, pr2, pr3]); + + urlControls.update('/4', false); + urlControls.update('/5', true); + + expect(cb).toHaveBeenCalledTimes(3); + }); + + it('should flush async url updates', async () => { + const pr1 = urlControls.updateAsync(() => '/1', false); + const pr2 = urlControls.updateAsync(() => '/2', false); + const pr3 = urlControls.updateAsync(() => '/3', false); + expect(getCurrentUrl()).toBe('/'); + urlControls.flush(); + expect(getCurrentUrl()).toBe('/3'); + await Promise.all([pr1, pr2, pr3]); + expect(getCurrentUrl()).toBe('/3'); + }); + + it('flush should take priority over regular replace behaviour', async () => { + const pr1 = urlControls.updateAsync(() => '/1', true); + const pr2 = urlControls.updateAsync(() => '/2', false); + const pr3 = urlControls.updateAsync(() => '/3', true); + urlControls.flush(false); + expect(getCurrentUrl()).toBe('/3'); + await Promise.all([pr1, pr2, pr3]); + expect(getCurrentUrl()).toBe('/3'); + expect(history.length).toBe(2); + }); + + it('should cancel async url updates', async () => { + const pr1 = urlControls.updateAsync(() => '/1', true); + const pr2 = urlControls.updateAsync(() => '/2', false); + const pr3 = urlControls.updateAsync(() => '/3', true); + urlControls.cancel(); + expect(getCurrentUrl()).toBe('/'); + await Promise.all([pr1, pr2, pr3]); + expect(getCurrentUrl()).toBe('/'); + }); + }); + + describe('getRelativeToHistoryPath', () => { + it('should extract path relative to browser history without basename', () => { + const history = createBrowserHistory(); + const url = + "http://localhost:5601/oxf/app/kibana#/management/kibana/index_patterns/id?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'')"; + const relativePath = getRelativeToHistoryPath(url, history); + expect(relativePath).toEqual( + "/oxf/app/kibana#/management/kibana/index_patterns/id?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'')" + ); + }); + + it('should extract path relative to browser history with basename', () => { + const url = + "http://localhost:5601/oxf/app/kibana#/management/kibana/index_patterns/id?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'')"; + const history1 = createBrowserHistory({ basename: '/oxf/app/' }); + const relativePath1 = getRelativeToHistoryPath(url, history1); + expect(relativePath1).toEqual( + "/kibana#/management/kibana/index_patterns/id?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'')" + ); + + const history2 = createBrowserHistory({ basename: '/oxf/app/kibana/' }); + const relativePath2 = getRelativeToHistoryPath(url, history2); + expect(relativePath2).toEqual( + "#/management/kibana/index_patterns/id?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'')" + ); + }); + + it('should extract path relative to browser history with basename from relative url', () => { + const history = createBrowserHistory({ basename: '/oxf/app/' }); + const url = + "/oxf/app/kibana#/management/kibana/index_patterns/id?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'')"; + const relativePath = getRelativeToHistoryPath(url, history); + expect(relativePath).toEqual( + "/kibana#/management/kibana/index_patterns/id?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'')" + ); + }); + + it('should extract path relative to hash history without basename', () => { + const history = createHashHistory(); + const url = + "http://localhost:5601/oxf/app/kibana#/management/kibana/index_patterns/id?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'')"; + const relativePath = getRelativeToHistoryPath(url, history); + expect(relativePath).toEqual( + "/management/kibana/index_patterns/id?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'')" + ); + }); + + it('should extract path relative to hash history with basename', () => { + const history = createHashHistory({ basename: 'management' }); + const url = + "http://localhost:5601/oxf/app/kibana#/management/kibana/index_patterns/id?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'')"; + const relativePath = getRelativeToHistoryPath(url, history); + expect(relativePath).toEqual( + "/kibana/index_patterns/id?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'')" + ); + }); + + it('should extract path relative to hash history with basename from relative url', () => { + const history = createHashHistory({ basename: 'management' }); + const url = + "/oxf/app/kibana#/management/kibana/index_patterns/id?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'')"; + const relativePath = getRelativeToHistoryPath(url, history); + expect(relativePath).toEqual( + "/kibana/index_patterns/id?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'')" + ); + }); + }); +}); diff --git a/src/plugins/kibana_utils/public/state_management/url/kbn_url_storage.ts b/src/plugins/kibana_utils/public/state_management/url/kbn_url_storage.ts new file mode 100644 index 0000000000000..03c136ea3d092 --- /dev/null +++ b/src/plugins/kibana_utils/public/state_management/url/kbn_url_storage.ts @@ -0,0 +1,235 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { format as formatUrl } from 'url'; +import { createBrowserHistory, History } from 'history'; +import { decodeState, encodeState } from '../state_encoder'; +import { getCurrentUrl, parseUrl, parseUrlHash } from './parse'; +import { stringifyQueryString } from './stringify_query_string'; +import { replaceUrlHashQuery } from './format'; + +/** + * Parses a kibana url and retrieves all the states encoded into url, + * Handles both expanded rison state and hashed state (where the actual state stored in sessionStorage) + * e.g.: + * + * given an url: + * http://localhost:5601/oxf/app/kibana#/management/kibana/index_patterns/id?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'') + * will return object: + * {_a: {tab: 'indexedFields'}, _b: {f: 'test', i: '', l: ''}}; + */ +export function getStatesFromKbnUrl( + url: string = window.location.href, + keys?: string[] +): Record { + const query = parseUrlHash(url)?.query; + + if (!query) return {}; + const decoded: Record = {}; + Object.entries(query) + .filter(([key]) => (keys ? keys.includes(key) : true)) + .forEach(([q, value]) => { + decoded[q] = decodeState(value as string); + }); + + return decoded; +} + +/** + * Retrieves specific state from url by key + * e.g.: + * + * given an url: + * http://localhost:5601/oxf/app/kibana#/management/kibana/index_patterns/id?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'') + * and key '_a' + * will return object: + * {tab: 'indexedFields'} + */ +export function getStateFromKbnUrl( + key: string, + url: string = window.location.href +): State | null { + return (getStatesFromKbnUrl(url, [key])[key] as State) || null; +} + +/** + * Sets state to the url by key and returns a new url string. + * Doesn't actually updates history + * + * e.g.: + * given a url: http://localhost:5601/oxf/app/kibana#/management/kibana/index_patterns/id?_a=(tab:indexedFields)&_b=(f:test,i:'',l:'') + * key: '_a' + * and state: {tab: 'other'} + * + * will return url: + * http://localhost:5601/oxf/app/kibana#/management/kibana/index_patterns/id?_a=(tab:other)&_b=(f:test,i:'',l:'') + */ +export function setStateToKbnUrl( + key: string, + state: State, + { useHash = false }: { useHash: boolean } = { useHash: false }, + rawUrl = window.location.href +): string { + return replaceUrlHashQuery(rawUrl, query => { + const encoded = encodeState(state, useHash); + return { + ...query, + [key]: encoded, + }; + }); +} + +/** + * A tiny wrapper around history library to listen for url changes and update url + * History library handles a bunch of cross browser edge cases + */ +export interface IKbnUrlControls { + /** + * Listen for url changes + * @param cb - get's called when url has been changed + */ + listen: (cb: () => void) => () => void; + + /** + * Updates url synchronously + * @param url - url to update to + * @param replace - use replace instead of push + */ + update: (url: string, replace: boolean) => string; + + /** + * Schedules url update to next microtask, + * Useful to batch sync changes to url to cause only one browser history update + * @param updater - fn which receives current url and should return next url to update to + * @param replace - use replace instead of push + */ + updateAsync: (updater: UrlUpdaterFnType, replace?: boolean) => Promise; + + /** + * Synchronously flushes scheduled url updates + * @param replace - if replace passed in, then uses it instead of push. Otherwise push or replace is picked depending on updateQueue + */ + flush: (replace?: boolean) => string; + + /** + * Cancels any pending url updates + */ + cancel: () => void; +} +export type UrlUpdaterFnType = (currentUrl: string) => string; + +export const createKbnUrlControls = ( + history: History = createBrowserHistory() +): IKbnUrlControls => { + const updateQueue: Array<(currentUrl: string) => string> = []; + + // if we should replace or push with next async update, + // if any call in a queue asked to push, then we should push + let shouldReplace = true; + + function updateUrl(newUrl: string, replace = false): string { + const currentUrl = getCurrentUrl(); + if (newUrl === currentUrl) return currentUrl; // skip update + + const historyPath = getRelativeToHistoryPath(newUrl, history); + + if (replace) { + history.replace(historyPath); + } else { + history.push(historyPath); + } + + return getCurrentUrl(); + } + + // queue clean up + function cleanUp() { + updateQueue.splice(0, updateQueue.length); + shouldReplace = true; + } + + // runs scheduled url updates + function flush(replace = shouldReplace) { + if (updateQueue.length === 0) return getCurrentUrl(); + const resultUrl = updateQueue.reduce((url, nextUpdate) => nextUpdate(url), getCurrentUrl()); + + cleanUp(); + + const newUrl = updateUrl(resultUrl, replace); + return newUrl; + } + + return { + listen: (cb: () => void) => + history.listen(() => { + cb(); + }), + update: (newUrl: string, replace = false) => updateUrl(newUrl, replace), + updateAsync: (updater: (currentUrl: string) => string, replace = false) => { + updateQueue.push(updater); + if (shouldReplace) { + shouldReplace = replace; + } + + // Schedule url update to the next microtask + // this allows to batch synchronous url changes + return Promise.resolve().then(() => { + return flush(); + }); + }, + flush: (replace?: boolean) => { + return flush(replace); + }, + cancel: () => { + cleanUp(); + }, + }; +}; + +/** + * Depending on history configuration extracts relative path for history updates + * 4 possible cases (see tests): + * 1. Browser history with empty base path + * 2. Browser history with base path + * 3. Hash history with empty base path + * 4. Hash history with base path + */ +export function getRelativeToHistoryPath(absoluteUrl: string, history: History): History.Path { + function stripBasename(path: string = '') { + const stripLeadingHash = (_: string) => (_.charAt(0) === '#' ? _.substr(1) : _); + const stripTrailingSlash = (_: string) => + _.charAt(_.length - 1) === '/' ? _.substr(0, _.length - 1) : _; + const baseName = stripLeadingHash(stripTrailingSlash(history.createHref({}))); + return path.startsWith(baseName) ? path.substr(baseName.length) : path; + } + const isHashHistory = history.createHref({}).includes('#'); + const parsedUrl = isHashHistory ? parseUrlHash(absoluteUrl)! : parseUrl(absoluteUrl); + const parsedHash = isHashHistory ? null : parseUrlHash(absoluteUrl); + + return formatUrl({ + pathname: stripBasename(parsedUrl.pathname), + search: stringifyQueryString(parsedUrl.query), + hash: parsedHash + ? formatUrl({ + pathname: parsedHash.pathname, + search: stringifyQueryString(parsedHash.query), + }) + : parsedUrl.hash, + }); +} diff --git a/src/plugins/kibana_utils/public/state_management/url/parse.test.ts b/src/plugins/kibana_utils/public/state_management/url/parse.test.ts new file mode 100644 index 0000000000000..774f18b734514 --- /dev/null +++ b/src/plugins/kibana_utils/public/state_management/url/parse.test.ts @@ -0,0 +1,35 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { parseUrlHash } from './parse'; + +describe('parseUrlHash', () => { + it('should return null if no hash', () => { + expect(parseUrlHash('http://localhost:5601/oxf/app/kibana')).toBeNull(); + }); + + it('should return parsed hash', () => { + expect(parseUrlHash('http://localhost:5601/oxf/app/kibana/#/path?test=test')).toMatchObject({ + pathname: '/path', + query: { + test: 'test', + }, + }); + }); +}); diff --git a/src/plugins/kibana_utils/public/state_management/url/parse.ts b/src/plugins/kibana_utils/public/state_management/url/parse.ts new file mode 100644 index 0000000000000..95041d0662f56 --- /dev/null +++ b/src/plugins/kibana_utils/public/state_management/url/parse.ts @@ -0,0 +1,29 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { parse as _parseUrl } from 'url'; + +export const parseUrl = (url: string) => _parseUrl(url, true); +export const parseUrlHash = (url: string) => { + const hash = parseUrl(url).hash; + return hash ? parseUrl(hash.slice(1)) : null; +}; +export const getCurrentUrl = () => window.location.href; +export const parseCurrentUrl = () => parseUrl(getCurrentUrl()); +export const parseCurrentUrlHash = () => parseUrlHash(getCurrentUrl()); diff --git a/src/plugins/kibana_utils/public/state_management/url/stringify_query_string.test.ts b/src/plugins/kibana_utils/public/state_management/url/stringify_query_string.test.ts new file mode 100644 index 0000000000000..3ca6cb4214682 --- /dev/null +++ b/src/plugins/kibana_utils/public/state_management/url/stringify_query_string.test.ts @@ -0,0 +1,65 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { encodeUriQuery, stringifyQueryString } from './stringify_query_string'; + +describe('stringifyQueryString', () => { + it('stringifyQueryString', () => { + expect( + stringifyQueryString({ + a: 'asdf1234asdf', + b: "-_.!~*'() -_.!~*'()", + c: ':@$, :@$,', + d: "&;=+# &;=+#'", + f: ' ', + g: 'null', + }) + ).toMatchInlineSnapshot( + `"a=asdf1234asdf&b=-_.!~*'()%20-_.!~*'()&c=:@$,%20:@$,&d=%26;%3D%2B%23%20%26;%3D%2B%23'&f=%20&g=null"` + ); + }); +}); + +describe('encodeUriQuery', function() { + it('should correctly encode uri query and not encode chars defined as pchar set in rfc3986', () => { + // don't encode alphanum + expect(encodeUriQuery('asdf1234asdf')).toBe('asdf1234asdf'); + + // don't encode unreserved + expect(encodeUriQuery("-_.!~*'() -_.!~*'()")).toBe("-_.!~*'()+-_.!~*'()"); + + // don't encode the rest of pchar + expect(encodeUriQuery(':@$, :@$,')).toBe(':@$,+:@$,'); + + // encode '&', ';', '=', '+', and '#' + expect(encodeUriQuery('&;=+# &;=+#')).toBe('%26;%3D%2B%23+%26;%3D%2B%23'); + + // encode ' ' as '+' + expect(encodeUriQuery(' ')).toBe('++'); + + // encode ' ' as '%20' when a flag is used + expect(encodeUriQuery(' ', true)).toBe('%20%20'); + + // do not encode `null` as '+' when flag is used + expect(encodeUriQuery('null', true)).toBe('null'); + + // do not encode `null` with no flag + expect(encodeUriQuery('null')).toBe('null'); + }); +}); diff --git a/src/plugins/kibana_utils/public/state_management/url/stringify_query_string.ts b/src/plugins/kibana_utils/public/state_management/url/stringify_query_string.ts new file mode 100644 index 0000000000000..e951dfac29c02 --- /dev/null +++ b/src/plugins/kibana_utils/public/state_management/url/stringify_query_string.ts @@ -0,0 +1,57 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { stringify, ParsedUrlQuery } from 'querystring'; + +// encodeUriQuery implements the less-aggressive encoding done naturally by +// the browser. We use it to generate the same urls the browser would +export const stringifyQueryString = (query: ParsedUrlQuery) => + stringify(query, undefined, undefined, { + // encode spaces with %20 is needed to produce the same queries as angular does + // https://github.com/angular/angular.js/blob/51c516e7d4f2d10b0aaa4487bd0b52772022207a/src/Angular.js#L1377 + encodeURIComponent: (val: string) => encodeUriQuery(val, true), + }); + +/** + * Extracted from angular.js + * repo: https://github.com/angular/angular.js + * license: MIT - https://github.com/angular/angular.js/blob/51c516e7d4f2d10b0aaa4487bd0b52772022207a/LICENSE + * source: https://github.com/angular/angular.js/blob/51c516e7d4f2d10b0aaa4487bd0b52772022207a/src/Angular.js#L1413-L1432 + */ + +/** + * This method is intended for encoding *key* or *value* parts of query component. We need a custom + * method because encodeURIComponent is too aggressive and encodes stuff that doesn't have to be + * encoded per http://tools.ietf.org/html/rfc3986: + * query = *( pchar / "/" / "?" ) + * pchar = unreserved / pct-encoded / sub-delims / ":" / "@" + * unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~" + * pct-encoded = "%" HEXDIG HEXDIG + * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" + * / "*" / "+" / "," / ";" / "=" + */ +export function encodeUriQuery(val: string, pctEncodeSpaces: boolean = false) { + return encodeURIComponent(val) + .replace(/%40/gi, '@') + .replace(/%3A/gi, ':') + .replace(/%24/g, '$') + .replace(/%2C/gi, ',') + .replace(/%3B/gi, ';') + .replace(/%20/g, pctEncodeSpaces ? '%20' : '+'); +} diff --git a/src/plugins/kibana_utils/public/state_management/url/url_tracker.test.ts b/src/plugins/kibana_utils/public/state_management/url/url_tracker.test.ts new file mode 100644 index 0000000000000..d7e5f99ffb700 --- /dev/null +++ b/src/plugins/kibana_utils/public/state_management/url/url_tracker.test.ts @@ -0,0 +1,56 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { createUrlTracker, IUrlTracker } from './url_tracker'; +import { StubBrowserStorage } from 'test_utils/stub_browser_storage'; +import { createMemoryHistory, History } from 'history'; + +describe('urlTracker', () => { + let storage: StubBrowserStorage; + let history: History; + let urlTracker: IUrlTracker; + beforeEach(() => { + storage = new StubBrowserStorage(); + history = createMemoryHistory(); + urlTracker = createUrlTracker('test', storage); + }); + + it('should return null if no tracked url', () => { + expect(urlTracker.getTrackedUrl()).toBeNull(); + }); + + it('should return last tracked url', () => { + urlTracker.trackUrl('http://localhost:4200'); + urlTracker.trackUrl('http://localhost:4201'); + urlTracker.trackUrl('http://localhost:4202'); + expect(urlTracker.getTrackedUrl()).toBe('http://localhost:4202'); + }); + + it('should listen to history and track updates', () => { + const stop = urlTracker.startTrackingUrl(history); + expect(urlTracker.getTrackedUrl()).toBe('/'); + history.push('/1'); + history.replace('/2'); + expect(urlTracker.getTrackedUrl()).toBe('/2'); + + stop(); + history.replace('/3'); + expect(urlTracker.getTrackedUrl()).toBe('/2'); + }); +}); diff --git a/src/plugins/kibana_utils/public/state_management/url/url_tracker.ts b/src/plugins/kibana_utils/public/state_management/url/url_tracker.ts new file mode 100644 index 0000000000000..89e72e94ba6b4 --- /dev/null +++ b/src/plugins/kibana_utils/public/state_management/url/url_tracker.ts @@ -0,0 +1,49 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { createBrowserHistory, History, Location } from 'history'; +import { getRelativeToHistoryPath } from './kbn_url_storage'; + +export interface IUrlTracker { + startTrackingUrl: (history?: History) => () => void; + getTrackedUrl: () => string | null; + trackUrl: (url: string) => void; +} +/** + * Replicates what src/legacy/ui/public/chrome/api/nav.ts did + * Persists the url in sessionStorage so it could be restored if navigated back to the app + */ +export function createUrlTracker(key: string, storage: Storage = sessionStorage): IUrlTracker { + return { + startTrackingUrl(history: History = createBrowserHistory()) { + const track = (location: Location) => { + const url = getRelativeToHistoryPath(history.createHref(location), history); + storage.setItem(key, url); + }; + track(history.location); + return history.listen(track); + }, + getTrackedUrl() { + return storage.getItem(key); + }, + trackUrl(url: string) { + storage.setItem(key, url); + }, + }; +} diff --git a/src/plugins/kibana_utils/public/state_sync/index.ts b/src/plugins/kibana_utils/public/state_sync/index.ts new file mode 100644 index 0000000000000..1dfa998c5bb9d --- /dev/null +++ b/src/plugins/kibana_utils/public/state_sync/index.ts @@ -0,0 +1,33 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { + createSessionStorageStateStorage, + createKbnUrlStateStorage, + IKbnUrlStateStorage, + ISessionStorageStateStorage, +} from './state_sync_state_storage'; +export { IStateSyncConfig, INullableBaseStateContainer } from './types'; +export { + syncState, + syncStates, + StopSyncStateFnType, + StartSyncStateFnType, + ISyncStateRef, +} from './state_sync'; diff --git a/src/plugins/kibana_utils/public/state_sync/state_sync.test.ts b/src/plugins/kibana_utils/public/state_sync/state_sync.test.ts new file mode 100644 index 0000000000000..08ad1551420d2 --- /dev/null +++ b/src/plugins/kibana_utils/public/state_sync/state_sync.test.ts @@ -0,0 +1,311 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { BaseState, BaseStateContainer, createStateContainer } from '../state_containers'; +import { + defaultState, + pureTransitions, + TodoActions, + TodoState, +} from '../../demos/state_containers/todomvc'; +import { syncState, syncStates } from './state_sync'; +import { IStateStorage } from './state_sync_state_storage/types'; +import { Observable, Subject } from 'rxjs'; +import { + createSessionStorageStateStorage, + createKbnUrlStateStorage, + IKbnUrlStateStorage, + ISessionStorageStateStorage, +} from './state_sync_state_storage'; +import { StubBrowserStorage } from 'test_utils/stub_browser_storage'; +import { createBrowserHistory, History } from 'history'; +import { INullableBaseStateContainer } from './types'; + +describe('state_sync', () => { + describe('basic', () => { + const container = createStateContainer(defaultState, pureTransitions); + beforeEach(() => { + container.set(defaultState); + }); + const storageChange$ = new Subject(); + let testStateStorage: IStateStorage; + + beforeEach(() => { + testStateStorage = { + set: jest.fn(), + get: jest.fn(), + change$: (key: string) => storageChange$.asObservable() as Observable, + }; + }); + + it('should sync state to storage', () => { + const key = '_s'; + const { start, stop } = syncState({ + stateContainer: withDefaultState(container, defaultState), + storageKey: key, + stateStorage: testStateStorage, + }); + start(); + + // initial sync of state to storage is not happening + expect(testStateStorage.set).not.toBeCalled(); + + container.transitions.add({ + id: 1, + text: 'Learning transitions...', + completed: false, + }); + expect(testStateStorage.set).toBeCalledWith(key, container.getState()); + stop(); + }); + + it('should sync storage to state', () => { + const key = '_s'; + const storageState1 = [{ id: 1, text: 'todo', completed: false }]; + (testStateStorage.get as jest.Mock).mockImplementation(() => storageState1); + const { stop, start } = syncState({ + stateContainer: withDefaultState(container, defaultState), + storageKey: key, + stateStorage: testStateStorage, + }); + start(); + + // initial sync of storage to state is not happening + expect(container.getState()).toEqual(defaultState); + + const storageState2 = { todos: [{ id: 1, text: 'todo', completed: true }] }; + (testStateStorage.get as jest.Mock).mockImplementation(() => storageState2); + storageChange$.next(storageState2); + + expect(container.getState()).toEqual(storageState2); + + stop(); + }); + + it('should not update storage if no actual state change happened', () => { + const key = '_s'; + const { stop, start } = syncState({ + stateContainer: withDefaultState(container, defaultState), + storageKey: key, + stateStorage: testStateStorage, + }); + start(); + (testStateStorage.set as jest.Mock).mockClear(); + + container.set(defaultState); + expect(testStateStorage.set).not.toBeCalled(); + + stop(); + }); + + it('should not update state container if no actual storage change happened', () => { + const key = '_s'; + const { stop, start } = syncState({ + stateContainer: withDefaultState(container, defaultState), + storageKey: key, + stateStorage: testStateStorage, + }); + start(); + + const originalState = container.getState(); + const storageState = { ...originalState }; + (testStateStorage.get as jest.Mock).mockImplementation(() => storageState); + storageChange$.next(storageState); + + expect(container.getState()).toBe(originalState); + + stop(); + }); + + it('storage change to null should notify state', () => { + container.set({ todos: [{ completed: false, id: 1, text: 'changed' }] }); + const { stop, start } = syncStates([ + { + stateContainer: withDefaultState(container, defaultState), + storageKey: '_s', + stateStorage: testStateStorage, + }, + ]); + start(); + + (testStateStorage.get as jest.Mock).mockImplementation(() => null); + storageChange$.next(null); + + expect(container.getState()).toEqual(defaultState); + + stop(); + }); + }); + + describe('integration', () => { + const key = '_s'; + const container = createStateContainer(defaultState, pureTransitions); + + let sessionStorage: StubBrowserStorage; + let sessionStorageSyncStrategy: ISessionStorageStateStorage; + let history: History; + let urlSyncStrategy: IKbnUrlStateStorage; + const getCurrentUrl = () => history.createHref(history.location); + const tick = () => new Promise(resolve => setTimeout(resolve)); + + beforeEach(() => { + container.set(defaultState); + + window.location.href = '/'; + sessionStorage = new StubBrowserStorage(); + sessionStorageSyncStrategy = createSessionStorageStateStorage(sessionStorage); + history = createBrowserHistory(); + urlSyncStrategy = createKbnUrlStateStorage({ useHash: false, history }); + }); + + it('change to one storage should also update other storage', () => { + const { stop, start } = syncStates([ + { + stateContainer: withDefaultState(container, defaultState), + storageKey: key, + stateStorage: urlSyncStrategy, + }, + { + stateContainer: withDefaultState(container, defaultState), + storageKey: key, + stateStorage: sessionStorageSyncStrategy, + }, + ]); + start(); + + const newStateFromUrl = { todos: [{ completed: false, id: 1, text: 'changed' }] }; + history.replace('/#?_s=(todos:!((completed:!f,id:1,text:changed)))'); + + expect(container.getState()).toEqual(newStateFromUrl); + expect(JSON.parse(sessionStorage.getItem(key)!)).toEqual(newStateFromUrl); + + stop(); + }); + + it('KbnUrlSyncStrategy applies url updates asynchronously to trigger single history change', async () => { + const { stop, start } = syncStates([ + { + stateContainer: withDefaultState(container, defaultState), + storageKey: key, + stateStorage: urlSyncStrategy, + }, + ]); + start(); + + const startHistoryLength = history.length; + container.transitions.add({ id: 2, text: '2', completed: false }); + container.transitions.add({ id: 3, text: '3', completed: false }); + container.transitions.completeAll(); + + expect(history.length).toBe(startHistoryLength); + expect(getCurrentUrl()).toMatchInlineSnapshot(`"/"`); + + await tick(); + expect(history.length).toBe(startHistoryLength + 1); + + expect(getCurrentUrl()).toMatchInlineSnapshot( + `"/#?_s=(todos:!((completed:!t,id:0,text:'Learning%20state%20containers'),(completed:!t,id:2,text:'2'),(completed:!t,id:3,text:'3')))"` + ); + + stop(); + }); + + it('KbnUrlSyncStrategy supports flushing url updates synchronously and triggers single history change', async () => { + const { stop, start } = syncStates([ + { + stateContainer: withDefaultState(container, defaultState), + storageKey: key, + stateStorage: urlSyncStrategy, + }, + ]); + start(); + + const startHistoryLength = history.length; + container.transitions.add({ id: 2, text: '2', completed: false }); + container.transitions.add({ id: 3, text: '3', completed: false }); + container.transitions.completeAll(); + + expect(history.length).toBe(startHistoryLength); + expect(getCurrentUrl()).toMatchInlineSnapshot(`"/"`); + + urlSyncStrategy.flush(); + + expect(history.length).toBe(startHistoryLength + 1); + expect(getCurrentUrl()).toMatchInlineSnapshot( + `"/#?_s=(todos:!((completed:!t,id:0,text:'Learning%20state%20containers'),(completed:!t,id:2,text:'2'),(completed:!t,id:3,text:'3')))"` + ); + + await tick(); + + expect(history.length).toBe(startHistoryLength + 1); + expect(getCurrentUrl()).toMatchInlineSnapshot( + `"/#?_s=(todos:!((completed:!t,id:0,text:'Learning%20state%20containers'),(completed:!t,id:2,text:'2'),(completed:!t,id:3,text:'3')))"` + ); + + stop(); + }); + + it('KbnUrlSyncStrategy supports cancellation of pending updates ', async () => { + const { stop, start } = syncStates([ + { + stateContainer: withDefaultState(container, defaultState), + storageKey: key, + stateStorage: urlSyncStrategy, + }, + ]); + start(); + + const startHistoryLength = history.length; + container.transitions.add({ id: 2, text: '2', completed: false }); + container.transitions.add({ id: 3, text: '3', completed: false }); + container.transitions.completeAll(); + + expect(history.length).toBe(startHistoryLength); + expect(getCurrentUrl()).toMatchInlineSnapshot(`"/"`); + + urlSyncStrategy.cancel(); + + expect(history.length).toBe(startHistoryLength); + expect(getCurrentUrl()).toMatchInlineSnapshot(`"/"`); + + await tick(); + + expect(history.length).toBe(startHistoryLength); + expect(getCurrentUrl()).toMatchInlineSnapshot(`"/"`); + + stop(); + }); + }); +}); + +function withDefaultState( + stateContainer: BaseStateContainer, + // eslint-disable-next-line no-shadow + defaultState: State +): INullableBaseStateContainer { + return { + ...stateContainer, + set: (state: State | null) => { + stateContainer.set({ + ...defaultState, + ...state, + }); + }, + }; +} diff --git a/src/plugins/kibana_utils/public/state_sync/state_sync.ts b/src/plugins/kibana_utils/public/state_sync/state_sync.ts new file mode 100644 index 0000000000000..9c1116e5da531 --- /dev/null +++ b/src/plugins/kibana_utils/public/state_sync/state_sync.ts @@ -0,0 +1,175 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { EMPTY, Subscription } from 'rxjs'; +import { tap } from 'rxjs/operators'; +import defaultComparator from 'fast-deep-equal'; +import { IStateSyncConfig } from './types'; +import { IStateStorage } from './state_sync_state_storage'; +import { distinctUntilChangedWithInitialValue } from '../../common'; +import { BaseState } from '../state_containers'; + +/** + * Utility for syncing application state wrapped in state container + * with some kind of storage (e.g. URL) + * + * Examples: + * + * 1. the simplest use case + * const stateStorage = createKbnUrlStateStorage(); + * syncState({ + * storageKey: '_s', + * stateContainer, + * stateStorage + * }); + * + * 2. conditionally configuring sync strategy + * const stateStorage = createKbnUrlStateStorage({useHash: config.get('state:stateContainerInSessionStorage')}) + * syncState({ + * storageKey: '_s', + * stateContainer, + * stateStorage + * }); + * + * 3. implementing custom sync strategy + * const localStorageStateStorage = { + * set: (storageKey, state) => localStorage.setItem(storageKey, JSON.stringify(state)), + * get: (storageKey) => localStorage.getItem(storageKey) ? JSON.parse(localStorage.getItem(storageKey)) : null + * }; + * syncState({ + * storageKey: '_s', + * stateContainer, + * stateStorage: localStorageStateStorage + * }); + * + * 4. Transform state before serialising + * Useful for: + * * Migration / backward compatibility + * * Syncing part of state + * * Providing default values + * const stateToStorage = (s) => ({ tab: s.tab }); + * syncState({ + * storageKey: '_s', + * stateContainer: { + * get: () => stateToStorage(stateContainer.get()), + * set: stateContainer.set(({ tab }) => ({ ...stateContainer.get(), tab }), + * state$: stateContainer.state$.pipe(map(stateToStorage)) + * }, + * stateStorage + * }); + * + * Caveats: + * + * 1. It is responsibility of consumer to make sure that initial app state and storage are in sync before starting syncing + * No initial sync happens when syncState() is called + */ +export type StopSyncStateFnType = () => void; +export type StartSyncStateFnType = () => void; +export interface ISyncStateRef { + // stop syncing state with storage + stop: StopSyncStateFnType; + // start syncing state with storage + start: StartSyncStateFnType; +} +export function syncState< + State extends BaseState, + StateStorage extends IStateStorage = IStateStorage +>({ + storageKey, + stateStorage, + stateContainer, +}: IStateSyncConfig): ISyncStateRef { + const subscriptions: Subscription[] = []; + + const updateState = () => { + const newState = stateStorage.get(storageKey); + const oldState = stateContainer.get(); + if (!defaultComparator(newState, oldState)) { + stateContainer.set(newState); + } + }; + + const updateStorage = () => { + const newStorageState = stateContainer.get(); + const oldStorageState = stateStorage.get(storageKey); + if (!defaultComparator(newStorageState, oldStorageState)) { + stateStorage.set(storageKey, newStorageState); + } + }; + + const onStateChange$ = stateContainer.state$.pipe( + distinctUntilChangedWithInitialValue(stateContainer.get(), defaultComparator), + tap(() => updateStorage()) + ); + + const onStorageChange$ = stateStorage.change$ + ? stateStorage.change$(storageKey).pipe( + distinctUntilChangedWithInitialValue(stateStorage.get(storageKey), defaultComparator), + tap(() => { + updateState(); + }) + ) + : EMPTY; + + return { + stop: () => { + // if stateStorage has any cancellation logic, then run it + if (stateStorage.cancel) { + stateStorage.cancel(); + } + + subscriptions.forEach(s => s.unsubscribe()); + subscriptions.splice(0, subscriptions.length); + }, + start: () => { + if (subscriptions.length > 0) { + throw new Error("syncState: can't start syncing state, when syncing is in progress"); + } + subscriptions.push(onStateChange$.subscribe(), onStorageChange$.subscribe()); + }, + }; +} + +/** + * multiple different sync configs + * syncStates([ + * { + * storageKey: '_s1', + * stateStorage: stateStorage1, + * stateContainer: stateContainer1, + * }, + * { + * storageKey: '_s2', + * stateStorage: stateStorage2, + * stateContainer: stateContainer2, + * }, + * ]); + * @param stateSyncConfigs - Array of IStateSyncConfig to sync + */ +export function syncStates(stateSyncConfigs: Array>): ISyncStateRef { + const syncRefs = stateSyncConfigs.map(config => syncState(config)); + return { + stop: () => { + syncRefs.forEach(s => s.stop()); + }, + start: () => { + syncRefs.forEach(s => s.start()); + }, + }; +} diff --git a/src/plugins/kibana_utils/public/state_sync/state_sync_state_storage/create_kbn_url_state_storage.test.ts b/src/plugins/kibana_utils/public/state_sync/state_sync_state_storage/create_kbn_url_state_storage.test.ts new file mode 100644 index 0000000000000..826122176e061 --- /dev/null +++ b/src/plugins/kibana_utils/public/state_sync/state_sync_state_storage/create_kbn_url_state_storage.test.ts @@ -0,0 +1,120 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import '../../storage/hashed_item_store/mock'; +import { createKbnUrlStateStorage, IKbnUrlStateStorage } from './create_kbn_url_state_storage'; +import { History, createBrowserHistory } from 'history'; +import { takeUntil, toArray } from 'rxjs/operators'; +import { Subject } from 'rxjs'; + +describe('KbnUrlStateStorage', () => { + describe('useHash: false', () => { + let urlStateStorage: IKbnUrlStateStorage; + let history: History; + const getCurrentUrl = () => history.createHref(history.location); + beforeEach(() => { + history = createBrowserHistory(); + history.push('/'); + urlStateStorage = createKbnUrlStateStorage({ useHash: false, history }); + }); + + it('should persist state to url', async () => { + const state = { test: 'test', ok: 1 }; + const key = '_s'; + await urlStateStorage.set(key, state); + expect(getCurrentUrl()).toMatchInlineSnapshot(`"/#?_s=(ok:1,test:test)"`); + expect(urlStateStorage.get(key)).toEqual(state); + }); + + it('should flush state to url', () => { + const state = { test: 'test', ok: 1 }; + const key = '_s'; + urlStateStorage.set(key, state); + expect(getCurrentUrl()).toMatchInlineSnapshot(`"/"`); + urlStateStorage.flush(); + expect(getCurrentUrl()).toMatchInlineSnapshot(`"/#?_s=(ok:1,test:test)"`); + expect(urlStateStorage.get(key)).toEqual(state); + }); + + it('should cancel url updates', async () => { + const state = { test: 'test', ok: 1 }; + const key = '_s'; + const pr = urlStateStorage.set(key, state); + expect(getCurrentUrl()).toMatchInlineSnapshot(`"/"`); + urlStateStorage.cancel(); + await pr; + expect(getCurrentUrl()).toMatchInlineSnapshot(`"/"`); + expect(urlStateStorage.get(key)).toEqual(null); + }); + + it('should notify about url changes', async () => { + expect(urlStateStorage.change$).toBeDefined(); + const key = '_s'; + const destroy$ = new Subject(); + const result = urlStateStorage.change$!(key) + .pipe(takeUntil(destroy$), toArray()) + .toPromise(); + + history.push(`/#?${key}=(ok:1,test:test)`); + history.push(`/?query=test#?${key}=(ok:2,test:test)&some=test`); + history.push(`/?query=test#?some=test`); + + destroy$.next(); + destroy$.complete(); + + expect(await result).toEqual([{ test: 'test', ok: 1 }, { test: 'test', ok: 2 }, null]); + }); + }); + + describe('useHash: true', () => { + let urlStateStorage: IKbnUrlStateStorage; + let history: History; + const getCurrentUrl = () => history.createHref(history.location); + beforeEach(() => { + history = createBrowserHistory(); + history.push('/'); + urlStateStorage = createKbnUrlStateStorage({ useHash: true, history }); + }); + + it('should persist state to url', async () => { + const state = { test: 'test', ok: 1 }; + const key = '_s'; + await urlStateStorage.set(key, state); + expect(getCurrentUrl()).toMatchInlineSnapshot(`"/#?_s=h@487e077"`); + expect(urlStateStorage.get(key)).toEqual(state); + }); + + it('should notify about url changes', async () => { + expect(urlStateStorage.change$).toBeDefined(); + const key = '_s'; + const destroy$ = new Subject(); + const result = urlStateStorage.change$!(key) + .pipe(takeUntil(destroy$), toArray()) + .toPromise(); + + history.push(`/#?${key}=(ok:1,test:test)`); + history.push(`/?query=test#?${key}=(ok:2,test:test)&some=test`); + history.push(`/?query=test#?some=test`); + + destroy$.next(); + destroy$.complete(); + + expect(await result).toEqual([{ test: 'test', ok: 1 }, { test: 'test', ok: 2 }, null]); + }); + }); +}); diff --git a/src/plugins/kibana_utils/public/state_sync/state_sync_state_storage/create_kbn_url_state_storage.ts b/src/plugins/kibana_utils/public/state_sync/state_sync_state_storage/create_kbn_url_state_storage.ts new file mode 100644 index 0000000000000..245006349ad55 --- /dev/null +++ b/src/plugins/kibana_utils/public/state_sync/state_sync_state_storage/create_kbn_url_state_storage.ts @@ -0,0 +1,84 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Observable } from 'rxjs'; +import { map, share } from 'rxjs/operators'; +import { History } from 'history'; +import { IStateStorage } from './types'; +import { + createKbnUrlControls, + getStateFromKbnUrl, + setStateToKbnUrl, +} from '../../state_management/url'; + +export interface IKbnUrlStateStorage extends IStateStorage { + set: (key: string, state: State, opts?: { replace: boolean }) => Promise; + get: (key: string) => State | null; + change$: (key: string) => Observable; + + // cancels any pending url updates + cancel: () => void; + + // synchronously runs any pending url updates + flush: (opts?: { replace?: boolean }) => void; +} + +/** + * Implements syncing to/from url strategies. + * Replicates what was implemented in state (AppState, GlobalState) + * Both expanded and hashed use cases + */ +export const createKbnUrlStateStorage = ( + { useHash = false, history }: { useHash: boolean; history?: History } = { useHash: false } +): IKbnUrlStateStorage => { + const url = createKbnUrlControls(history); + return { + set: ( + key: string, + state: State, + { replace = false }: { replace: boolean } = { replace: false } + ) => { + // syncState() utils doesn't wait for this promise + return url.updateAsync( + currentUrl => setStateToKbnUrl(key, state, { useHash }, currentUrl), + replace + ); + }, + get: key => getStateFromKbnUrl(key), + change$: (key: string) => + new Observable(observer => { + const unlisten = url.listen(() => { + observer.next(); + }); + + return () => { + unlisten(); + }; + }).pipe( + map(() => getStateFromKbnUrl(key)), + share() + ), + flush: ({ replace = false }: { replace?: boolean } = {}) => { + url.flush(replace); + }, + cancel() { + url.cancel(); + }, + }; +}; diff --git a/src/plugins/kibana_utils/public/state_sync/state_sync_state_storage/create_session_storage_state_storage.test.ts b/src/plugins/kibana_utils/public/state_sync/state_sync_state_storage/create_session_storage_state_storage.test.ts new file mode 100644 index 0000000000000..f69629e755008 --- /dev/null +++ b/src/plugins/kibana_utils/public/state_sync/state_sync_state_storage/create_session_storage_state_storage.test.ts @@ -0,0 +1,44 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { + createSessionStorageStateStorage, + ISessionStorageStateStorage, +} from './create_session_storage_state_storage'; +import { StubBrowserStorage } from 'test_utils/stub_browser_storage'; + +describe('SessionStorageStateStorage', () => { + let browserStorage: StubBrowserStorage; + let stateStorage: ISessionStorageStateStorage; + beforeEach(() => { + browserStorage = new StubBrowserStorage(); + stateStorage = createSessionStorageStateStorage(browserStorage); + }); + + it('should synchronously sync to storage', () => { + const state = { state: 'state' }; + stateStorage.set('key', state); + expect(stateStorage.get('key')).toEqual(state); + expect(browserStorage.getItem('key')).not.toBeNull(); + }); + + it('should not implement change$', () => { + expect(stateStorage.change$).not.toBeDefined(); + }); +}); diff --git a/src/plugins/kibana_utils/public/state_sync/state_sync_state_storage/create_session_storage_state_storage.ts b/src/plugins/kibana_utils/public/state_sync/state_sync_state_storage/create_session_storage_state_storage.ts new file mode 100644 index 0000000000000..00edfdfd1ed61 --- /dev/null +++ b/src/plugins/kibana_utils/public/state_sync/state_sync_state_storage/create_session_storage_state_storage.ts @@ -0,0 +1,34 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { IStateStorage } from './types'; + +export interface ISessionStorageStateStorage extends IStateStorage { + set: (key: string, state: State) => void; + get: (key: string) => State | null; +} + +export const createSessionStorageStateStorage = ( + storage: Storage = window.sessionStorage +): ISessionStorageStateStorage => { + return { + set: (key: string, state: State) => storage.setItem(key, JSON.stringify(state)), + get: (key: string) => JSON.parse(storage.getItem(key)!), + }; +}; diff --git a/src/plugins/kibana_utils/public/state_sync/state_sync_state_storage/index.ts b/src/plugins/kibana_utils/public/state_sync/state_sync_state_storage/index.ts new file mode 100644 index 0000000000000..fe04333e5ef15 --- /dev/null +++ b/src/plugins/kibana_utils/public/state_sync/state_sync_state_storage/index.ts @@ -0,0 +1,25 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { IStateStorage } from './types'; +export { createKbnUrlStateStorage, IKbnUrlStateStorage } from './create_kbn_url_state_storage'; +export { + createSessionStorageStateStorage, + ISessionStorageStateStorage, +} from './create_session_storage_state_storage'; diff --git a/src/plugins/kibana_utils/public/state_sync/state_sync_state_storage/types.ts b/src/plugins/kibana_utils/public/state_sync/state_sync_state_storage/types.ts new file mode 100644 index 0000000000000..add1dc259be45 --- /dev/null +++ b/src/plugins/kibana_utils/public/state_sync/state_sync_state_storage/types.ts @@ -0,0 +1,51 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Observable } from 'rxjs'; + +/** + * Any StateStorage have to implement IStateStorage interface + * StateStorage is responsible for: + * * state serialisation / deserialization + * * persisting to and retrieving from storage + * + * For an example take a look at already implemented KbnUrl state storage + */ +export interface IStateStorage { + /** + * Take in a state object, should serialise and persist + */ + set: (key: string, state: State) => any; + + /** + * Should retrieve state from the storage and deserialize it + */ + get: (key: string) => State | null; + + /** + * Should notify when the stored state has changed + */ + change$?: (key: string) => Observable; + + /** + * Optional method to cancel any pending activity + * syncState() will call it, if it is provided by IStateStorage + */ + cancel?: () => void; +} diff --git a/src/plugins/kibana_utils/public/state_sync/types.ts b/src/plugins/kibana_utils/public/state_sync/types.ts new file mode 100644 index 0000000000000..3009c1d161a53 --- /dev/null +++ b/src/plugins/kibana_utils/public/state_sync/types.ts @@ -0,0 +1,57 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { BaseState, BaseStateContainer } from '../state_containers/types'; +import { IStateStorage } from './state_sync_state_storage'; + +export interface INullableBaseStateContainer + extends BaseStateContainer { + // State container for stateSync() have to accept "null" + // for example, set() implementation could handle null and fallback to some default state + // this is required to handle edge case, when state in storage becomes empty and syncing is in progress. + // state container will be notified about about storage becoming empty with null passed in + set: (state: State | null) => void; +} + +export interface IStateSyncConfig< + State extends BaseState, + StateStorage extends IStateStorage = IStateStorage +> { + /** + * Storage key to use for syncing, + * e.g. storageKey '_a' should sync state to ?_a query param + */ + storageKey: string; + /** + * State container to keep in sync with storage, have to implement INullableBaseStateContainer interface + * The idea is that ./state_containers/ should be used as a state container, + * but it is also possible to implement own custom container for advanced use cases + */ + stateContainer: INullableBaseStateContainer; + /** + * State storage to use, + * State storage is responsible for serialising / deserialising and persisting / retrieving stored state + * + * There are common strategies already implemented: + * './state_sync_state_storage/' + * which replicate what State (AppState, GlobalState) in legacy world did + * + */ + stateStorage: StateStorage; +} diff --git a/src/plugins/management/kibana.json b/src/plugins/management/kibana.json index 755a387afbd05..80135f1bfb6c8 100644 --- a/src/plugins/management/kibana.json +++ b/src/plugins/management/kibana.json @@ -3,5 +3,5 @@ "version": "kibana", "server": false, "ui": true, - "requiredPlugins": [] + "requiredPlugins": ["kibana_legacy"] } diff --git a/src/plugins/management/public/__snapshots__/management_app.test.tsx.snap b/src/plugins/management/public/__snapshots__/management_app.test.tsx.snap new file mode 100644 index 0000000000000..7f13472ee02ee --- /dev/null +++ b/src/plugins/management/public/__snapshots__/management_app.test.tsx.snap @@ -0,0 +1,11 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Management app can mount and unmount 1`] = ` +
+
+ Test App - Hello world! +
+
+`; + +exports[`Management app can mount and unmount 2`] = `
`; diff --git a/src/plugins/management/public/components/_index.scss b/src/plugins/management/public/components/_index.scss new file mode 100644 index 0000000000000..df0ebb48803d9 --- /dev/null +++ b/src/plugins/management/public/components/_index.scss @@ -0,0 +1 @@ +@import './management_sidebar_nav/index'; diff --git a/src/plugins/management/public/components/index.ts b/src/plugins/management/public/components/index.ts new file mode 100644 index 0000000000000..2650d23d3c25c --- /dev/null +++ b/src/plugins/management/public/components/index.ts @@ -0,0 +1,21 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { ManagementSidebarNav } from './management_sidebar_nav'; +export { ManagementChrome } from './management_chrome'; diff --git a/src/plugins/management/public/components/management_chrome/index.ts b/src/plugins/management/public/components/management_chrome/index.ts new file mode 100644 index 0000000000000..b82c1af871be7 --- /dev/null +++ b/src/plugins/management/public/components/management_chrome/index.ts @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { ManagementChrome } from './management_chrome'; diff --git a/src/plugins/management/public/components/management_chrome/management_chrome.tsx b/src/plugins/management/public/components/management_chrome/management_chrome.tsx new file mode 100644 index 0000000000000..df844e2208936 --- /dev/null +++ b/src/plugins/management/public/components/management_chrome/management_chrome.tsx @@ -0,0 +1,59 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import * as React from 'react'; +import { EuiPage, EuiPageBody, EuiPageSideBar } from '@elastic/eui'; +import { I18nProvider } from '@kbn/i18n/react'; +import { ManagementSidebarNav } from '../management_sidebar_nav'; +import { LegacySection } from '../../types'; +import { ManagementSection } from '../../management_section'; + +interface Props { + getSections: () => ManagementSection[]; + legacySections: LegacySection[]; + selectedId: string; + onMounted: (element: HTMLDivElement) => void; +} + +export class ManagementChrome extends React.Component { + private container = React.createRef(); + componentDidMount() { + if (this.container.current) { + this.props.onMounted(this.container.current); + } + } + render() { + return ( + + + + + + +
+ + + + ); + } +} diff --git a/src/plugins/management/public/components/management_sidebar_nav/__snapshots__/management_sidebar_nav.test.ts.snap b/src/plugins/management/public/components/management_sidebar_nav/__snapshots__/management_sidebar_nav.test.ts.snap new file mode 100644 index 0000000000000..e7225b356ed68 --- /dev/null +++ b/src/plugins/management/public/components/management_sidebar_nav/__snapshots__/management_sidebar_nav.test.ts.snap @@ -0,0 +1,95 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Management adds legacy apps to existing SidebarNav sections 1`] = ` +Array [ + Object { + "data-test-subj": "activeSection", + "icon": null, + "id": "activeSection", + "items": Array [ + Object { + "data-test-subj": "item", + "href": undefined, + "id": "item", + "isSelected": false, + "name": "item", + "order": undefined, + }, + ], + "name": "activeSection", + "order": 10, + }, + Object { + "data-test-subj": "no-active-items", + "icon": null, + "id": "no-active-items", + "items": Array [ + Object { + "data-test-subj": "disabled", + "href": undefined, + "id": "disabled", + "isSelected": false, + "name": "disabled", + "order": undefined, + }, + Object { + "data-test-subj": "notVisible", + "href": undefined, + "id": "notVisible", + "isSelected": false, + "name": "notVisible", + "order": undefined, + }, + ], + "name": "No active items", + "order": 10, + }, +] +`; + +exports[`Management maps legacy sections and apps into SidebarNav items 1`] = ` +Array [ + Object { + "data-test-subj": "no-active-items", + "icon": null, + "id": "no-active-items", + "items": Array [ + Object { + "data-test-subj": "disabled", + "href": undefined, + "id": "disabled", + "isSelected": false, + "name": "disabled", + "order": undefined, + }, + Object { + "data-test-subj": "notVisible", + "href": undefined, + "id": "notVisible", + "isSelected": false, + "name": "notVisible", + "order": undefined, + }, + ], + "name": "No active items", + "order": 10, + }, + Object { + "data-test-subj": "activeSection", + "icon": null, + "id": "activeSection", + "items": Array [ + Object { + "data-test-subj": "item", + "href": undefined, + "id": "item", + "isSelected": false, + "name": "item", + "order": undefined, + }, + ], + "name": "activeSection", + "order": 10, + }, +] +`; diff --git a/src/legacy/ui/public/management/components/_index.scss b/src/plugins/management/public/components/management_sidebar_nav/_index.scss similarity index 100% rename from src/legacy/ui/public/management/components/_index.scss rename to src/plugins/management/public/components/management_sidebar_nav/_index.scss diff --git a/src/legacy/ui/public/management/components/_sidebar_nav.scss b/src/plugins/management/public/components/management_sidebar_nav/_sidebar_nav.scss similarity index 88% rename from src/legacy/ui/public/management/components/_sidebar_nav.scss rename to src/plugins/management/public/components/management_sidebar_nav/_sidebar_nav.scss index 0c2b2bc228b2c..cf88ed9b0a88b 100644 --- a/src/legacy/ui/public/management/components/_sidebar_nav.scss +++ b/src/plugins/management/public/components/management_sidebar_nav/_sidebar_nav.scss @@ -1,4 +1,4 @@ -.mgtSidebarNav { +.mgtSideBarNav { width: 192px; } diff --git a/src/plugins/management/public/components/management_sidebar_nav/index.ts b/src/plugins/management/public/components/management_sidebar_nav/index.ts new file mode 100644 index 0000000000000..79142fdb69a74 --- /dev/null +++ b/src/plugins/management/public/components/management_sidebar_nav/index.ts @@ -0,0 +1,20 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export { ManagementSidebarNav } from './management_sidebar_nav'; diff --git a/src/plugins/management/public/components/management_sidebar_nav/management_sidebar_nav.test.ts b/src/plugins/management/public/components/management_sidebar_nav/management_sidebar_nav.test.ts new file mode 100644 index 0000000000000..e04e0a7572612 --- /dev/null +++ b/src/plugins/management/public/components/management_sidebar_nav/management_sidebar_nav.test.ts @@ -0,0 +1,98 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { IndexedArray } from '../../../../../legacy/ui/public/indexed_array'; +import { mergeLegacyItems } from './management_sidebar_nav'; + +const toIndexedArray = (initialSet: any[]) => + new IndexedArray({ + index: ['id'], + order: ['order'], + initialSet, + }); + +const activeProps = { visible: true, disabled: false }; +const disabledProps = { visible: true, disabled: true }; +const notVisibleProps = { visible: false, disabled: false }; +const visibleItem = { display: 'item', id: 'item', ...activeProps }; + +const notVisibleSection = { + display: 'Not visible', + id: 'not-visible', + order: 10, + visibleItems: toIndexedArray([visibleItem]), + ...notVisibleProps, +}; +const disabledSection = { + display: 'Disabled', + id: 'disabled', + order: 10, + visibleItems: toIndexedArray([visibleItem]), + ...disabledProps, +}; +const noItemsSection = { + display: 'No items', + id: 'no-items', + order: 10, + visibleItems: toIndexedArray([]), + ...activeProps, +}; +const noActiveItemsSection = { + display: 'No active items', + id: 'no-active-items', + order: 10, + visibleItems: toIndexedArray([ + { display: 'disabled', id: 'disabled', ...disabledProps }, + { display: 'notVisible', id: 'notVisible', ...notVisibleProps }, + ]), + ...activeProps, +}; +const activeSection = { + display: 'activeSection', + id: 'activeSection', + order: 10, + visibleItems: toIndexedArray([visibleItem]), + ...activeProps, +}; + +const managementSections = [ + notVisibleSection, + disabledSection, + noItemsSection, + noActiveItemsSection, + activeSection, +]; + +describe('Management', () => { + it('maps legacy sections and apps into SidebarNav items', () => { + expect(mergeLegacyItems([], managementSections, 'active-item-id')).toMatchSnapshot(); + }); + + it('adds legacy apps to existing SidebarNav sections', () => { + const navSection = { + 'data-test-subj': 'activeSection', + icon: null, + id: 'activeSection', + items: [], + name: 'activeSection', + order: 10, + }; + expect(mergeLegacyItems([navSection], managementSections, 'active-item-id')).toMatchSnapshot(); + }); +}); diff --git a/src/plugins/management/public/components/management_sidebar_nav/management_sidebar_nav.tsx b/src/plugins/management/public/components/management_sidebar_nav/management_sidebar_nav.tsx new file mode 100644 index 0000000000000..cb0b82d0f0bde --- /dev/null +++ b/src/plugins/management/public/components/management_sidebar_nav/management_sidebar_nav.tsx @@ -0,0 +1,200 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { + EuiIcon, + // @ts-ignore + EuiSideNav, + EuiScreenReaderOnly, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; +import { LegacySection, LegacyApp } from '../../types'; +import { ManagementApp } from '../../management_app'; +import { ManagementSection } from '../../management_section'; + +interface NavApp { + id: string; + name: string; + [key: string]: unknown; + order: number; // only needed while merging platform and legacy +} + +interface NavSection extends NavApp { + items: NavApp[]; +} + +interface ManagementSidebarNavProps { + getSections: () => ManagementSection[]; + legacySections: LegacySection[]; + selectedId: string; +} + +interface ManagementSidebarNavState { + isSideNavOpenOnMobile: boolean; +} + +const managementSectionOrAppToNav = (appOrSection: ManagementApp | ManagementSection) => ({ + id: appOrSection.id, + name: appOrSection.title, + 'data-test-subj': appOrSection.id, + order: appOrSection.order, +}); + +const managementSectionToNavSection = (section: ManagementSection) => { + const iconType = section.euiIconType + ? section.euiIconType + : section.icon + ? section.icon + : 'empty'; + + return { + icon: , + ...managementSectionOrAppToNav(section), + }; +}; + +const managementAppToNavItem = (selectedId?: string, parentId?: string) => ( + app: ManagementApp +) => ({ + isSelected: selectedId === app.id, + href: `#/management/${parentId}/${app.id}`, + ...managementSectionOrAppToNav(app), +}); + +const legacySectionToNavSection = (section: LegacySection) => ({ + name: section.display, + id: section.id, + icon: section.icon ? : null, + items: [], + 'data-test-subj': section.id, + // @ts-ignore + order: section.order, +}); + +const legacyAppToNavItem = (app: LegacyApp, selectedId: string) => ({ + isSelected: selectedId === app.id, + name: app.display, + id: app.id, + href: app.url, + 'data-test-subj': app.id, + // @ts-ignore + order: app.order, +}); + +const sectionVisible = (section: LegacySection | LegacyApp) => !section.disabled && section.visible; + +const sideNavItems = (sections: ManagementSection[], selectedId: string) => + sections.map(section => ({ + items: section.getAppsEnabled().map(managementAppToNavItem(selectedId, section.id)), + ...managementSectionToNavSection(section), + })); + +const findOrAddSection = (navItems: NavSection[], legacySection: LegacySection): NavSection => { + const foundSection = navItems.find(sec => sec.id === legacySection.id); + + if (foundSection) { + return foundSection; + } else { + const newSection = legacySectionToNavSection(legacySection); + navItems.push(newSection); + navItems.sort((a: NavSection, b: NavSection) => a.order - b.order); // only needed while merging platform and legacy + return newSection; + } +}; + +export const mergeLegacyItems = ( + navItems: NavSection[], + legacySections: LegacySection[], + selectedId: string +) => { + const filteredLegacySections = legacySections + .filter(sectionVisible) + .filter(section => section.visibleItems.length); + + filteredLegacySections.forEach(legacySection => { + const section = findOrAddSection(navItems, legacySection); + legacySection.visibleItems.forEach(app => { + section.items.push(legacyAppToNavItem(app, selectedId)); + return section.items.sort((a, b) => a.order - b.order); + }); + }); + + return navItems; +}; + +const sectionsToItems = ( + sections: ManagementSection[], + legacySections: LegacySection[], + selectedId: string +) => { + const navItems = sideNavItems(sections, selectedId); + return mergeLegacyItems(navItems, legacySections, selectedId); +}; + +export class ManagementSidebarNav extends React.Component< + ManagementSidebarNavProps, + ManagementSidebarNavState +> { + constructor(props: ManagementSidebarNavProps) { + super(props); + this.state = { + isSideNavOpenOnMobile: false, + }; + } + + public render() { + const HEADER_ID = 'management-nav-header'; + + return ( + <> + +

+ {i18n.translate('management.nav.label', { + defaultMessage: 'Management', + })} +

+
+ + + ); + } + + private renderMobileTitle() { + return ; + } + + private toggleOpenOnMobile = () => { + this.setState({ + isSideNavOpenOnMobile: !this.state.isSideNavOpenOnMobile, + }); + }; +} diff --git a/src/plugins/management/public/index.ts b/src/plugins/management/public/index.ts index ee3866c734f19..faec466dbd671 100644 --- a/src/plugins/management/public/index.ts +++ b/src/plugins/management/public/index.ts @@ -24,4 +24,7 @@ export function plugin(initializerContext: PluginInitializerContext) { return new ManagementPlugin(); } -export { ManagementStart } from './types'; +export { ManagementSetup, ManagementStart, RegisterManagementApp } from './types'; +export { ManagementApp } from './management_app'; +export { ManagementSection } from './management_section'; +export { ManagementSidebarNav } from './components'; // for use in legacy management apps diff --git a/src/plugins/management/public/legacy/index.js b/src/plugins/management/public/legacy/index.js index 63b9d2c6b27d7..f2e0ba89b7b59 100644 --- a/src/plugins/management/public/legacy/index.js +++ b/src/plugins/management/public/legacy/index.js @@ -17,4 +17,5 @@ * under the License. */ -export { management } from './sections_register'; +export { LegacyManagementAdapter } from './sections_register'; +export { LegacyManagementSection } from './section'; diff --git a/src/plugins/management/public/legacy/section.js b/src/plugins/management/public/legacy/section.js index f269e3fe295b7..7d733b7b3173b 100644 --- a/src/plugins/management/public/legacy/section.js +++ b/src/plugins/management/public/legacy/section.js @@ -22,7 +22,7 @@ import { IndexedArray } from '../../../../legacy/ui/public/indexed_array'; const listeners = []; -export class ManagementSection { +export class LegacyManagementSection { /** * @param {string} id * @param {object} options @@ -83,7 +83,11 @@ export class ManagementSection { */ register(id, options = {}) { - const item = new ManagementSection(id, assign(options, { parent: this }), this.capabilities); + const item = new LegacyManagementSection( + id, + assign(options, { parent: this }), + this.capabilities + ); if (this.hasItem(id)) { throw new Error(`'${id}' is already registered`); diff --git a/src/plugins/management/public/legacy/section.test.js b/src/plugins/management/public/legacy/section.test.js index 61bafd298afb3..45cc80ef80edd 100644 --- a/src/plugins/management/public/legacy/section.test.js +++ b/src/plugins/management/public/legacy/section.test.js @@ -17,7 +17,7 @@ * under the License. */ -import { ManagementSection } from './section'; +import { LegacyManagementSection } from './section'; import { IndexedArray } from '../../../../legacy/ui/public/indexed_array'; const capabilitiesMock = { @@ -29,42 +29,42 @@ const capabilitiesMock = { describe('ManagementSection', () => { describe('constructor', () => { it('defaults display to id', () => { - const section = new ManagementSection('kibana', {}, capabilitiesMock); + const section = new LegacyManagementSection('kibana', {}, capabilitiesMock); expect(section.display).toBe('kibana'); }); it('defaults visible to true', () => { - const section = new ManagementSection('kibana', {}, capabilitiesMock); + const section = new LegacyManagementSection('kibana', {}, capabilitiesMock); expect(section.visible).toBe(true); }); it('defaults disabled to false', () => { - const section = new ManagementSection('kibana', {}, capabilitiesMock); + const section = new LegacyManagementSection('kibana', {}, capabilitiesMock); expect(section.disabled).toBe(false); }); it('defaults tooltip to empty string', () => { - const section = new ManagementSection('kibana', {}, capabilitiesMock); + const section = new LegacyManagementSection('kibana', {}, capabilitiesMock); expect(section.tooltip).toBe(''); }); it('defaults url to empty string', () => { - const section = new ManagementSection('kibana', {}, capabilitiesMock); + const section = new LegacyManagementSection('kibana', {}, capabilitiesMock); expect(section.url).toBe(''); }); it('exposes items', () => { - const section = new ManagementSection('kibana', {}, capabilitiesMock); + const section = new LegacyManagementSection('kibana', {}, capabilitiesMock); expect(section.items).toHaveLength(0); }); it('exposes visibleItems', () => { - const section = new ManagementSection('kibana', {}, capabilitiesMock); + const section = new LegacyManagementSection('kibana', {}, capabilitiesMock); expect(section.visibleItems).toHaveLength(0); }); it('assigns all options', () => { - const section = new ManagementSection( + const section = new LegacyManagementSection( 'kibana', { description: 'test', url: 'foobar' }, capabilitiesMock @@ -78,11 +78,11 @@ describe('ManagementSection', () => { let section; beforeEach(() => { - section = new ManagementSection('kibana', {}, capabilitiesMock); + section = new LegacyManagementSection('kibana', {}, capabilitiesMock); }); it('returns a ManagementSection', () => { - expect(section.register('about')).toBeInstanceOf(ManagementSection); + expect(section.register('about')).toBeInstanceOf(LegacyManagementSection); }); it('provides a reference to the parent', () => { @@ -93,7 +93,7 @@ describe('ManagementSection', () => { section.register('about', { description: 'test' }); expect(section.items).toHaveLength(1); - expect(section.items[0]).toBeInstanceOf(ManagementSection); + expect(section.items[0]).toBeInstanceOf(LegacyManagementSection); expect(section.items[0].id).toBe('about'); }); @@ -126,7 +126,7 @@ describe('ManagementSection', () => { let section; beforeEach(() => { - section = new ManagementSection('kibana', {}, capabilitiesMock); + section = new LegacyManagementSection('kibana', {}, capabilitiesMock); section.register('about'); }); @@ -157,12 +157,12 @@ describe('ManagementSection', () => { let section; beforeEach(() => { - section = new ManagementSection('kibana', {}, capabilitiesMock); + section = new LegacyManagementSection('kibana', {}, capabilitiesMock); section.register('about'); }); it('returns registered section', () => { - expect(section.getSection('about')).toBeInstanceOf(ManagementSection); + expect(section.getSection('about')).toBeInstanceOf(LegacyManagementSection); }); it('returns undefined if un-registered', () => { @@ -171,7 +171,7 @@ describe('ManagementSection', () => { it('returns sub-sections specified via a /-separated path', () => { section.getSection('about').register('time'); - expect(section.getSection('about/time')).toBeInstanceOf(ManagementSection); + expect(section.getSection('about/time')).toBeInstanceOf(LegacyManagementSection); expect(section.getSection('about/time')).toBe(section.getSection('about').getSection('time')); }); @@ -184,7 +184,7 @@ describe('ManagementSection', () => { let section; beforeEach(() => { - section = new ManagementSection('kibana', {}, capabilitiesMock); + section = new LegacyManagementSection('kibana', {}, capabilitiesMock); section.register('three', { order: 3 }); section.register('one', { order: 1 }); @@ -214,7 +214,7 @@ describe('ManagementSection', () => { let section; beforeEach(() => { - section = new ManagementSection('kibana', {}, capabilitiesMock); + section = new LegacyManagementSection('kibana', {}, capabilitiesMock); }); it('hide sets visible to false', () => { @@ -233,7 +233,7 @@ describe('ManagementSection', () => { let section; beforeEach(() => { - section = new ManagementSection('kibana', {}, capabilitiesMock); + section = new LegacyManagementSection('kibana', {}, capabilitiesMock); }); it('disable sets disabled to true', () => { @@ -251,7 +251,7 @@ describe('ManagementSection', () => { let section; beforeEach(() => { - section = new ManagementSection('kibana', {}, capabilitiesMock); + section = new LegacyManagementSection('kibana', {}, capabilitiesMock); section.register('three', { order: 3 }); section.register('one', { order: 1 }); diff --git a/src/plugins/management/public/legacy/sections_register.js b/src/plugins/management/public/legacy/sections_register.js index 888b2c5bc3aeb..63d919377f89e 100644 --- a/src/plugins/management/public/legacy/sections_register.js +++ b/src/plugins/management/public/legacy/sections_register.js @@ -17,44 +17,48 @@ * under the License. */ -import { ManagementSection } from './section'; +import { LegacyManagementSection } from './section'; import { i18n } from '@kbn/i18n'; -export const management = capabilities => { - const main = new ManagementSection( - 'management', - { - display: i18n.translate('management.displayName', { - defaultMessage: 'Management', - }), - }, - capabilities - ); +export class LegacyManagementAdapter { + main = undefined; + init = capabilities => { + this.main = new LegacyManagementSection( + 'management', + { + display: i18n.translate('management.displayName', { + defaultMessage: 'Management', + }), + }, + capabilities + ); - main.register('data', { - display: i18n.translate('management.connectDataDisplayName', { - defaultMessage: 'Connect Data', - }), - order: 0, - }); + this.main.register('data', { + display: i18n.translate('management.connectDataDisplayName', { + defaultMessage: 'Connect Data', + }), + order: 0, + }); - main.register('elasticsearch', { - display: 'Elasticsearch', - order: 20, - icon: 'logoElasticsearch', - }); + this.main.register('elasticsearch', { + display: 'Elasticsearch', + order: 20, + icon: 'logoElasticsearch', + }); - main.register('kibana', { - display: 'Kibana', - order: 30, - icon: 'logoKibana', - }); + this.main.register('kibana', { + display: 'Kibana', + order: 30, + icon: 'logoKibana', + }); - main.register('logstash', { - display: 'Logstash', - order: 30, - icon: 'logoLogstash', - }); + this.main.register('logstash', { + display: 'Logstash', + order: 30, + icon: 'logoLogstash', + }); - return main; -}; + return this.main; + }; + getManagement = () => this.main; +} diff --git a/src/plugins/management/public/management_app.test.tsx b/src/plugins/management/public/management_app.test.tsx new file mode 100644 index 0000000000000..a76b234d95ef5 --- /dev/null +++ b/src/plugins/management/public/management_app.test.tsx @@ -0,0 +1,66 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import * as React from 'react'; +import * as ReactDOM from 'react-dom'; +import { coreMock } from '../../../core/public/mocks'; + +import { ManagementApp } from './management_app'; +// @ts-ignore +import { LegacyManagementSection } from './legacy'; + +function createTestApp() { + const legacySection = new LegacyManagementSection('legacy'); + return new ManagementApp( + { + id: 'test-app', + title: 'Test App', + basePath: '', + mount(params) { + params.setBreadcrumbs([{ text: 'Test App' }]); + ReactDOM.render(
Test App - Hello world!
, params.element); + + return () => { + ReactDOM.unmountComponentAtNode(params.element); + }; + }, + }, + () => [], + jest.fn(), + () => legacySection, + coreMock.createSetup().getStartServices + ); +} + +test('Management app can mount and unmount', async () => { + const testApp = createTestApp(); + const container = document.createElement('div'); + document.body.appendChild(container); + const unmount = testApp.mount({ element: container, basePath: '', setBreadcrumbs: jest.fn() }); + expect(container).toMatchSnapshot(); + (await unmount)(); + expect(container).toMatchSnapshot(); +}); + +test('Enabled by default, can disable', () => { + const testApp = createTestApp(); + expect(testApp.enabled).toBe(true); + testApp.disable(); + expect(testApp.enabled).toBe(false); +}); diff --git a/src/plugins/management/public/management_app.tsx b/src/plugins/management/public/management_app.tsx new file mode 100644 index 0000000000000..f7e8dba4f8210 --- /dev/null +++ b/src/plugins/management/public/management_app.tsx @@ -0,0 +1,102 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import * as React from 'react'; +import ReactDOM from 'react-dom'; +import { i18n } from '@kbn/i18n'; +import { CreateManagementApp, ManagementSectionMount, Unmount } from './types'; +import { KibanaLegacySetup } from '../../kibana_legacy/public'; +// @ts-ignore +import { LegacyManagementSection } from './legacy'; +import { ManagementChrome } from './components'; +import { ManagementSection } from './management_section'; +import { ChromeBreadcrumb, CoreSetup } from '../../../core/public/'; + +export class ManagementApp { + readonly id: string; + readonly title: string; + readonly basePath: string; + readonly order: number; + readonly mount: ManagementSectionMount; + protected enabledStatus: boolean = true; + + constructor( + { id, title, basePath, order = 100, mount }: CreateManagementApp, + getSections: () => ManagementSection[], + registerLegacyApp: KibanaLegacySetup['registerLegacyApp'], + getLegacyManagementSections: () => LegacyManagementSection, + getStartServices: CoreSetup['getStartServices'] + ) { + this.id = id; + this.title = title; + this.basePath = basePath; + this.order = order; + this.mount = mount; + + registerLegacyApp({ + id: basePath.substr(1), // get rid of initial slash + title, + mount: async ({}, params) => { + let appUnmount: Unmount; + async function setBreadcrumbs(crumbs: ChromeBreadcrumb[]) { + const [coreStart] = await getStartServices(); + coreStart.chrome.setBreadcrumbs([ + { + text: i18n.translate('management.breadcrumb', { + defaultMessage: 'Management', + }), + href: '#/management', + }, + ...crumbs, + ]); + } + + ReactDOM.render( + { + appUnmount = await mount({ + basePath, + element, + setBreadcrumbs, + }); + }} + />, + params.element + ); + + return async () => { + appUnmount(); + ReactDOM.unmountComponentAtNode(params.element); + }; + }, + }); + } + public enable() { + this.enabledStatus = true; + } + public disable() { + this.enabledStatus = false; + } + public get enabled() { + return this.enabledStatus; + } +} diff --git a/src/plugins/management/public/management_section.test.ts b/src/plugins/management/public/management_section.test.ts new file mode 100644 index 0000000000000..c68175ee0a678 --- /dev/null +++ b/src/plugins/management/public/management_section.test.ts @@ -0,0 +1,65 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { ManagementSection } from './management_section'; +// @ts-ignore +import { LegacyManagementSection } from './legacy'; +import { coreMock } from '../../../core/public/mocks'; + +function createSection(registerLegacyApp: () => void) { + const legacySection = new LegacyManagementSection('legacy'); + const getLegacySection = () => legacySection; + const getManagementSections: () => ManagementSection[] = () => []; + + const testSectionConfig = { id: 'test-section', title: 'Test Section' }; + return new ManagementSection( + testSectionConfig, + getManagementSections, + registerLegacyApp, + getLegacySection, + coreMock.createSetup().getStartServices + ); +} + +test('cannot register two apps with the same id', () => { + const registerLegacyApp = jest.fn(); + const section = createSection(registerLegacyApp); + + const testAppConfig = { id: 'test-app', title: 'Test App', mount: () => () => {} }; + + section.registerApp(testAppConfig); + expect(registerLegacyApp).toHaveBeenCalled(); + expect(section.apps.length).toEqual(1); + + expect(() => { + section.registerApp(testAppConfig); + }).toThrow(); +}); + +test('can enable and disable apps', () => { + const registerLegacyApp = jest.fn(); + const section = createSection(registerLegacyApp); + + const testAppConfig = { id: 'test-app', title: 'Test App', mount: () => () => {} }; + + const app = section.registerApp(testAppConfig); + expect(section.getAppsEnabled().length).toEqual(1); + app.disable(); + expect(section.getAppsEnabled().length).toEqual(0); +}); diff --git a/src/plugins/management/public/management_section.ts b/src/plugins/management/public/management_section.ts new file mode 100644 index 0000000000000..2f323c4b6a9cf --- /dev/null +++ b/src/plugins/management/public/management_section.ts @@ -0,0 +1,78 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { CreateSection, RegisterManagementAppArgs } from './types'; +import { KibanaLegacySetup } from '../../kibana_legacy/public'; +import { CoreSetup } from '../../../core/public'; +// @ts-ignore +import { LegacyManagementSection } from './legacy'; +import { ManagementApp } from './management_app'; + +export class ManagementSection { + public readonly id: string = ''; + public readonly title: string = ''; + public readonly apps: ManagementApp[] = []; + public readonly order: number; + public readonly euiIconType?: string; + public readonly icon?: string; + private readonly getSections: () => ManagementSection[]; + private readonly registerLegacyApp: KibanaLegacySetup['registerLegacyApp']; + private readonly getLegacyManagementSection: () => LegacyManagementSection; + private readonly getStartServices: CoreSetup['getStartServices']; + + constructor( + { id, title, order = 100, euiIconType, icon }: CreateSection, + getSections: () => ManagementSection[], + registerLegacyApp: KibanaLegacySetup['registerLegacyApp'], + getLegacyManagementSection: () => ManagementSection, + getStartServices: CoreSetup['getStartServices'] + ) { + this.id = id; + this.title = title; + this.order = order; + this.euiIconType = euiIconType; + this.icon = icon; + this.getSections = getSections; + this.registerLegacyApp = registerLegacyApp; + this.getLegacyManagementSection = getLegacyManagementSection; + this.getStartServices = getStartServices; + } + + registerApp({ id, title, order, mount }: RegisterManagementAppArgs) { + if (this.getApp(id)) { + throw new Error(`Management app already registered - id: ${id}, title: ${title}`); + } + + const app = new ManagementApp( + { id, title, order, mount, basePath: `/management/${this.id}/${id}` }, + this.getSections, + this.registerLegacyApp, + this.getLegacyManagementSection, + this.getStartServices + ); + this.apps.push(app); + return app; + } + getApp(id: ManagementApp['id']) { + return this.apps.find(app => app.id === id); + } + getAppsEnabled() { + return this.apps.filter(app => app.enabled).sort((a, b) => a.order - b.order); + } +} diff --git a/src/plugins/management/public/management_service.test.ts b/src/plugins/management/public/management_service.test.ts new file mode 100644 index 0000000000000..854406a10335b --- /dev/null +++ b/src/plugins/management/public/management_service.test.ts @@ -0,0 +1,55 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { ManagementService } from './management_service'; +import { coreMock } from '../../../core/public/mocks'; + +const mockKibanaLegacy = { registerLegacyApp: () => {}, forwardApp: () => {} }; + +test('Provides default sections', () => { + const service = new ManagementService().setup( + mockKibanaLegacy, + () => {}, + coreMock.createSetup().getStartServices + ); + expect(service.getAllSections().length).toEqual(3); + expect(service.getSection('kibana')).not.toBeUndefined(); + expect(service.getSection('logstash')).not.toBeUndefined(); + expect(service.getSection('elasticsearch')).not.toBeUndefined(); +}); + +test('Register section, enable and disable', () => { + const service = new ManagementService().setup( + mockKibanaLegacy, + () => {}, + coreMock.createSetup().getStartServices + ); + const testSection = service.register({ id: 'test-section', title: 'Test Section' }); + expect(service.getSection('test-section')).not.toBeUndefined(); + + const testApp = testSection.registerApp({ + id: 'test-app', + title: 'Test App', + mount: () => () => {}, + }); + expect(testSection.getApp('test-app')).not.toBeUndefined(); + expect(service.getSectionsEnabled().length).toEqual(1); + testApp.disable(); + expect(service.getSectionsEnabled().length).toEqual(0); +}); diff --git a/src/plugins/management/public/management_service.ts b/src/plugins/management/public/management_service.ts new file mode 100644 index 0000000000000..4a900345b3843 --- /dev/null +++ b/src/plugins/management/public/management_service.ts @@ -0,0 +1,103 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { ManagementSection } from './management_section'; +import { KibanaLegacySetup } from '../../kibana_legacy/public'; +// @ts-ignore +import { LegacyManagementSection } from './legacy'; +import { CreateSection } from './types'; +import { CoreSetup, CoreStart } from '../../../core/public'; + +export class ManagementService { + private sections: ManagementSection[] = []; + + private register( + registerLegacyApp: KibanaLegacySetup['registerLegacyApp'], + getLegacyManagement: () => LegacyManagementSection, + getStartServices: CoreSetup['getStartServices'] + ) { + return (section: CreateSection) => { + if (this.getSection(section.id)) { + throw Error(`ManagementSection '${section.id}' already registered`); + } + + const newSection = new ManagementSection( + section, + this.getSectionsEnabled.bind(this), + registerLegacyApp, + getLegacyManagement, + getStartServices + ); + this.sections.push(newSection); + return newSection; + }; + } + private getSection(sectionId: ManagementSection['id']) { + return this.sections.find(section => section.id === sectionId); + } + + private getAllSections() { + return this.sections; + } + + private getSectionsEnabled() { + return this.sections + .filter(section => section.getAppsEnabled().length > 0) + .sort((a, b) => a.order - b.order); + } + + private sharedInterface = { + getSection: this.getSection.bind(this), + getSectionsEnabled: this.getSectionsEnabled.bind(this), + getAllSections: this.getAllSections.bind(this), + }; + + public setup( + kibanaLegacy: KibanaLegacySetup, + getLegacyManagement: () => LegacyManagementSection, + getStartServices: CoreSetup['getStartServices'] + ) { + const register = this.register.bind(this)( + kibanaLegacy.registerLegacyApp, + getLegacyManagement, + getStartServices + ); + + register({ id: 'kibana', title: 'Kibana', order: 30, euiIconType: 'logoKibana' }); + register({ id: 'logstash', title: 'Logstash', order: 30, euiIconType: 'logoLogstash' }); + register({ + id: 'elasticsearch', + title: 'Elasticsearch', + order: 20, + euiIconType: 'logoElasticsearch', + }); + + return { + register, + ...this.sharedInterface, + }; + } + + public start(navigateToApp: CoreStart['application']['navigateToApp']) { + return { + navigateToApp, // apps are currently registered as top level apps but this may change in the future + ...this.sharedInterface, + }; + } +} diff --git a/src/plugins/management/public/plugin.ts b/src/plugins/management/public/plugin.ts index c65dfd1dc7bb4..195d96c11d8d9 100644 --- a/src/plugins/management/public/plugin.ts +++ b/src/plugins/management/public/plugin.ts @@ -18,18 +18,30 @@ */ import { CoreSetup, CoreStart, Plugin } from 'kibana/public'; -import { ManagementStart } from './types'; +import { ManagementSetup, ManagementStart } from './types'; +import { ManagementService } from './management_service'; +import { KibanaLegacySetup } from '../../kibana_legacy/public'; // @ts-ignore -import { management } from './legacy'; +import { LegacyManagementAdapter } from './legacy'; -export class ManagementPlugin implements Plugin<{}, ManagementStart> { - public setup(core: CoreSetup) { - return {}; +export class ManagementPlugin implements Plugin { + private managementSections = new ManagementService(); + private legacyManagement = new LegacyManagementAdapter(); + + public setup(core: CoreSetup, { kibana_legacy }: { kibana_legacy: KibanaLegacySetup }) { + return { + sections: this.managementSections.setup( + kibana_legacy, + this.legacyManagement.getManagement, + core.getStartServices + ), + }; } public start(core: CoreStart) { return { - legacy: management(core.application.capabilities), + sections: this.managementSections.start(core.application.navigateToApp), + legacy: this.legacyManagement.init(core.application.capabilities), }; } } diff --git a/src/plugins/management/public/types.ts b/src/plugins/management/public/types.ts index 6ca1faf338c39..4dbea30ff062d 100644 --- a/src/plugins/management/public/types.ts +++ b/src/plugins/management/public/types.ts @@ -17,6 +17,82 @@ * under the License. */ +import { IconType } from '@elastic/eui'; +import { ManagementApp } from './management_app'; +import { ManagementSection } from './management_section'; +import { ChromeBreadcrumb, ApplicationStart } from '../../../core/public/'; + +export interface ManagementSetup { + sections: SectionsServiceSetup; +} + export interface ManagementStart { + sections: SectionsServiceStart; legacy: any; } + +interface SectionsServiceSetup { + getSection: (sectionId: ManagementSection['id']) => ManagementSection | undefined; + getAllSections: () => ManagementSection[]; + register: RegisterSection; +} + +interface SectionsServiceStart { + getSection: (sectionId: ManagementSection['id']) => ManagementSection | undefined; + getAllSections: () => ManagementSection[]; + navigateToApp: ApplicationStart['navigateToApp']; +} + +export interface CreateSection { + id: string; + title: string; + order?: number; + euiIconType?: string; // takes precedence over `icon` property. + icon?: string; // URL to image file; fallback if no `euiIconType` +} + +export type RegisterSection = (section: CreateSection) => ManagementSection; + +export interface RegisterManagementAppArgs { + id: string; + title: string; + mount: ManagementSectionMount; + order?: number; +} + +export type RegisterManagementApp = (managementApp: RegisterManagementAppArgs) => ManagementApp; + +export type Unmount = () => Promise | void; + +interface ManagementAppMountParams { + basePath: string; // base path for setting up your router + element: HTMLElement; // element the section should render into + setBreadcrumbs: (crumbs: ChromeBreadcrumb[]) => void; +} + +export type ManagementSectionMount = ( + params: ManagementAppMountParams +) => Unmount | Promise; + +export interface CreateManagementApp { + id: string; + title: string; + basePath: string; + order?: number; + mount: ManagementSectionMount; +} + +export interface LegacySection extends LegacyApp { + visibleItems: LegacyApp[]; +} + +export interface LegacyApp { + disabled: boolean; + visible: boolean; + id: string; + display: string; + url?: string; + euiIconType?: IconType; + icon?: string; + order: number; +} diff --git a/src/plugins/testbed/server/index.ts b/src/plugins/testbed/server/index.ts index 586254603567b..4873fe0926472 100644 --- a/src/plugins/testbed/server/index.ts +++ b/src/plugins/testbed/server/index.ts @@ -17,7 +17,7 @@ * under the License. */ -import { map, mergeMap } from 'rxjs/operators'; +import { map } from 'rxjs/operators'; import { schema, TypeOf } from '@kbn/config-schema'; import { @@ -108,9 +108,7 @@ class Plugin { return `Some exposed data derived from config: ${configValue.secret}`; }) ), - pingElasticsearch$: core.elasticsearch.adminClient$.pipe( - mergeMap(client => client.callAsInternalUser('ping')) - ), + pingElasticsearch: () => core.elasticsearch.adminClient.callAsInternalUser('ping'), }; } diff --git a/src/test_utils/kbn_server.ts b/src/test_utils/kbn_server.ts index 40700e05bcde8..e1b4a823e7e87 100644 --- a/src/test_utils/kbn_server.ts +++ b/src/test_utils/kbn_server.ts @@ -37,7 +37,7 @@ import { Root } from '../core/server/root'; import KbnServer from '../legacy/server/kbn_server'; import { CallCluster } from '../legacy/core_plugins/elasticsearch'; -type HttpMethod = 'delete' | 'get' | 'head' | 'post' | 'put'; +export type HttpMethod = 'delete' | 'get' | 'head' | 'post' | 'put'; const DEFAULTS_SETTINGS = { server: { @@ -76,6 +76,7 @@ export function createRootWithSettings( repl: false, basePath: false, optimize: false, + runExamples: false, oss: true, ...cliArgs, }, @@ -96,7 +97,7 @@ export function createRootWithSettings( * @param method * @param path */ -function getSupertest(root: Root, method: HttpMethod, path: string) { +export function getSupertest(root: Root, method: HttpMethod, path: string) { const testUserCredentials = Buffer.from(`${kibanaTestUser.username}:${kibanaTestUser.password}`); return supertest((root as any).server.http.httpServer.server.listener) [method](path) diff --git a/src/test_utils/public/simulate_keys.js b/src/test_utils/public/simulate_keys.js index 8ee4a79e6bc7c..a876d67325c05 100644 --- a/src/test_utils/public/simulate_keys.js +++ b/src/test_utils/public/simulate_keys.js @@ -20,7 +20,7 @@ import $ from 'jquery'; import _ from 'lodash'; import Bluebird from 'bluebird'; -import { keyMap } from 'ui/utils/key_map'; +import { keyMap } from 'ui/directives/key_map'; const reverseKeyMap = _.mapValues(_.invert(keyMap), _.ary(_.parseInt, 1)); /** diff --git a/tasks/config/karma.js b/tasks/config/karma.js index c0d6074da61c5..0acd452530b30 100644 --- a/tasks/config/karma.js +++ b/tasks/config/karma.js @@ -20,6 +20,7 @@ import { dirname } from 'path'; import { times } from 'lodash'; import { makeJunitReportPath } from '@kbn/test'; +import * as UiSharedDeps from '@kbn/ui-shared-deps'; const TOTAL_CI_SHARDS = 4; const ROOT = dirname(require.resolve('../../package.json')); @@ -48,6 +49,25 @@ module.exports = function(grunt) { return ['progress']; } + function getKarmaFiles(shardNum) { + return [ + 'http://localhost:5610/test_bundle/built_css.css', + + `http://localhost:5610/bundles/kbn-ui-shared-deps/${UiSharedDeps.distFilename}`, + 'http://localhost:5610/built_assets/dlls/vendors.bundle.dll.js', + + shardNum === undefined + ? `http://localhost:5610/bundles/tests.bundle.js` + : `http://localhost:5610/bundles/tests.bundle.js?shards=${TOTAL_CI_SHARDS}&shard_num=${shardNum}`, + + // this causes tilemap tests to fail, probably because the eui styles haven't been + // included in the karma harness a long some time, if ever + // `http://localhost:5610/bundles/kbn-ui-shared-deps/${UiSharedDeps.lightCssDistFilename}`, + 'http://localhost:5610/built_assets/dlls/vendors.style.dll.css', + 'http://localhost:5610/bundles/tests.style.css', + ]; + } + const config = { options: { // base path that will be used to resolve all patterns (eg. files, exclude) @@ -90,15 +110,7 @@ module.exports = function(grunt) { }, // list of files / patterns to load in the browser - files: [ - 'http://localhost:5610/test_bundle/built_css.css', - - 'http://localhost:5610/built_assets/dlls/vendors.bundle.dll.js', - 'http://localhost:5610/bundles/tests.bundle.js', - - 'http://localhost:5610/built_assets/dlls/vendors.style.dll.css', - 'http://localhost:5610/bundles/tests.style.css', - ], + files: getKarmaFiles(), proxies: { '/tests/': 'http://localhost:5610/tests/', @@ -181,15 +193,7 @@ module.exports = function(grunt) { config[`ciShard-${n}`] = { singleRun: true, options: { - files: [ - 'http://localhost:5610/test_bundle/built_css.css', - - 'http://localhost:5610/built_assets/dlls/vendors.bundle.dll.js', - `http://localhost:5610/bundles/tests.bundle.js?shards=${TOTAL_CI_SHARDS}&shard_num=${n}`, - - 'http://localhost:5610/built_assets/dlls/vendors.style.dll.css', - 'http://localhost:5610/bundles/tests.style.css', - ], + files: getKarmaFiles(n), }, }; }); diff --git a/tasks/config/run.js b/tasks/config/run.js index a29061c9a7240..857895d75595c 100644 --- a/tasks/config/run.js +++ b/tasks/config/run.js @@ -152,6 +152,7 @@ module.exports = function(grunt) { args: [ 'nyc', '--reporter=html', + '--reporter=json-summary', '--report-dir=./target/kibana-coverage/mocha', NODE, 'scripts/mocha', diff --git a/tasks/function_test_groups.js b/tasks/function_test_groups.js index 7854e2cd49837..7b7293dc9a037 100644 --- a/tasks/function_test_groups.js +++ b/tasks/function_test_groups.js @@ -29,6 +29,21 @@ const TEST_TAGS = safeLoad(JOBS_YAML) .JOB.filter(id => id.startsWith('kibana-ciGroup')) .map(id => id.replace(/^kibana-/, '')); +const getDefaultArgs = tag => { + return [ + 'scripts/functional_tests', + '--include-tag', + tag, + '--config', + 'test/functional/config.js', + '--config', + 'test/ui_capabilities/newsfeed_err/config.ts', + // '--config', 'test/functional/config.firefox.js', + '--bail', + '--debug', + ]; +}; + export function getFunctionalTestGroupRunConfigs({ kibanaInstallDir } = {}) { return { // include a run task for each test group @@ -38,18 +53,8 @@ export function getFunctionalTestGroupRunConfigs({ kibanaInstallDir } = {}) { [`functionalTests_${tag}`]: { cmd: process.execPath, args: [ - 'scripts/functional_tests', - '--include-tag', - tag, - '--config', - 'test/functional/config.js', - '--config', - 'test/ui_capabilities/newsfeed_err/config.ts', - // '--config', 'test/functional/config.firefox.js', - '--bail', - '--debug', - '--kibana-install-dir', - kibanaInstallDir, + ...getDefaultArgs(tag), + ...(!!process.env.CODE_COVERAGE ? [] : ['--kibana-install-dir', kibanaInstallDir]), ], }, }), diff --git a/test/accessibility/apps/discover.ts b/test/accessibility/apps/discover.ts index 38ee5b7db39c4..e25d295515971 100644 --- a/test/accessibility/apps/discover.ts +++ b/test/accessibility/apps/discover.ts @@ -20,10 +20,12 @@ import { FtrProviderContext } from '../ftr_provider_context'; export default function({ getService, getPageObjects }: FtrProviderContext) { - const PageObjects = getPageObjects(['common', 'timePicker']); + const PageObjects = getPageObjects(['common', 'discover', 'header', 'share', 'timePicker']); const a11y = getService('a11y'); const esArchiver = getService('esArchiver'); const kibanaServer = getService('kibanaServer'); + const inspector = getService('inspector'); + const filterBar = getService('filterBar'); describe('Discover', () => { before(async () => { @@ -39,5 +41,73 @@ export default function({ getService, getPageObjects }: FtrProviderContext) { it('main view', async () => { await a11y.testAppSnapshot(); }); + + it('Click save button', async () => { + await PageObjects.discover.clickSaveSearchButton(); + await a11y.testAppSnapshot(); + }); + + it('Save search panel', async () => { + await PageObjects.discover.inputSavedSearchTitle('a11ySearch'); + await a11y.testAppSnapshot(); + }); + + it('Confirm saved search', async () => { + await PageObjects.discover.clickConfirmSavedSearch(); + await a11y.testAppSnapshot(); + }); + + // skipping the test for new because we can't fix it right now + it.skip('Click on new to clear the search', async () => { + await PageObjects.discover.clickNewSearchButton(); + await a11y.testAppSnapshot(); + }); + + it('Open load saved search panel', async () => { + await PageObjects.discover.openLoadSavedSearchPanel(); + await a11y.testAppSnapshot(); + await PageObjects.discover.closeLoadSavedSearchPanel(); + }); + + it('Open inspector panel', async () => { + await inspector.open(); + await a11y.testAppSnapshot(); + await inspector.close(); + }); + + it('Open add filter', async () => { + await PageObjects.discover.openAddFilterPanel(); + await a11y.testAppSnapshot(); + }); + + it('Select values for a filter', async () => { + await filterBar.addFilter('extension.raw', 'is one of', 'jpg'); + await a11y.testAppSnapshot(); + }); + + it('Load a new search from the panel', async () => { + await PageObjects.discover.clickSaveSearchButton(); + await PageObjects.discover.inputSavedSearchTitle('filterSearch'); + await PageObjects.discover.clickConfirmSavedSearch(); + await PageObjects.discover.openLoadSavedSearchPanel(); + await PageObjects.discover.loadSavedSearch('filterSearch'); + await a11y.testAppSnapshot(); + }); + + // unable to validate on EUI pop-over + it('click share button', async () => { + await PageObjects.share.clickShareTopNavButton(); + await a11y.testAppSnapshot(); + }); + + it('Open sidebar filter', async () => { + await PageObjects.discover.openSidebarFieldFilter(); + await a11y.testAppSnapshot(); + }); + + it('Close sidebar filter', async () => { + await PageObjects.discover.closeSidebarFieldFilter(); + await a11y.testAppSnapshot(); + }); }); } diff --git a/test/accessibility/services/a11y/a11y.ts b/test/accessibility/services/a11y/a11y.ts index 7adfe7ebfcc7d..72440b648e538 100644 --- a/test/accessibility/services/a11y/a11y.ts +++ b/test/accessibility/services/a11y/a11y.ts @@ -45,7 +45,6 @@ export const normalizeResult = (report: any) => { export function A11yProvider({ getService }: FtrProviderContext) { const browser = getService('browser'); const Wd = getService('__webdriver__'); - const log = getService('log'); /** * Accessibility testing service using the Axe (https://www.deque.com/axe/) @@ -78,11 +77,6 @@ export function A11yProvider({ getService }: FtrProviderContext) { private testAxeReport(report: AxeReport) { const errorMsgs = []; - for (const result of report.incomplete) { - // these items require human review and can't be definitively validated - log.warning(printResult(chalk.yellow('UNABLE TO VALIDATE'), result)); - } - for (const result of report.violations) { errorMsgs.push(printResult(chalk.red('VIOLATION'), result)); } diff --git a/test/api_integration/apis/ui_metric/ui_metric.js b/test/api_integration/apis/ui_metric/ui_metric.js index 5b02ba7e72430..5ddbd8649589c 100644 --- a/test/api_integration/apis/ui_metric/ui_metric.js +++ b/test/api_integration/apis/ui_metric/ui_metric.js @@ -25,15 +25,13 @@ export default function({ getService }) { const es = getService('legacyEs'); const createStatsMetric = eventName => ({ - key: ReportManager.createMetricKey({ appName: 'myApp', type: METRIC_TYPE.CLICK, eventName }), eventName, appName: 'myApp', type: METRIC_TYPE.CLICK, - stats: { sum: 1, avg: 1, min: 1, max: 1 }, + count: 1, }); const createUserAgentMetric = appName => ({ - key: ReportManager.createMetricKey({ appName, type: METRIC_TYPE.USER_AGENT }), appName, type: METRIC_TYPE.USER_AGENT, userAgent: @@ -42,12 +40,9 @@ export default function({ getService }) { describe('ui_metric API', () => { it('increments the count field in the document defined by the {app}/{action_type} path', async () => { + const reportManager = new ReportManager(); const uiStatsMetric = createStatsMetric('myEvent'); - const report = { - uiStatsMetrics: { - [uiStatsMetric.key]: uiStatsMetric, - }, - }; + const { report } = reportManager.assignReports([uiStatsMetric]); await supertest .post('/api/ui_metric/report') .set('kbn-xsrf', 'kibana') @@ -61,21 +56,18 @@ export default function({ getService }) { }); it('supports multiple events', async () => { + const reportManager = new ReportManager(); const userAgentMetric = createUserAgentMetric('kibana'); const uiStatsMetric1 = createStatsMetric('myEvent'); const hrTime = process.hrtime(); const nano = hrTime[0] * 1000000000 + hrTime[1]; const uniqueEventName = `myEvent${nano}`; const uiStatsMetric2 = createStatsMetric(uniqueEventName); - const report = { - userAgent: { - [userAgentMetric.key]: userAgentMetric, - }, - uiStatsMetrics: { - [uiStatsMetric1.key]: uiStatsMetric1, - [uiStatsMetric2.key]: uiStatsMetric2, - }, - }; + const { report } = reportManager.assignReports([ + userAgentMetric, + uiStatsMetric1, + uiStatsMetric2, + ]); await supertest .post('/api/ui_metric/report') .set('kbn-xsrf', 'kibana') diff --git a/test/common/services/security/role_mappings.ts b/test/common/services/security/role_mappings.ts new file mode 100644 index 0000000000000..cc2fa23825498 --- /dev/null +++ b/test/common/services/security/role_mappings.ts @@ -0,0 +1,66 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import axios, { AxiosInstance } from 'axios'; +import util from 'util'; +import { ToolingLog } from '@kbn/dev-utils'; + +export class RoleMappings { + private log: ToolingLog; + private axios: AxiosInstance; + + constructor(url: string, log: ToolingLog) { + this.log = log; + this.axios = axios.create({ + headers: { 'kbn-xsrf': 'x-pack/ftr/services/security/role_mappings' }, + baseURL: url, + maxRedirects: 0, + validateStatus: () => true, // we do our own validation below and throw better error messages + }); + } + + public async create(name: string, roleMapping: Record) { + this.log.debug(`creating role mapping ${name}`); + const { data, status, statusText } = await this.axios.post( + `/internal/security/role_mapping/${name}`, + roleMapping + ); + if (status !== 200) { + throw new Error( + `Expected status code of 200, received ${status} ${statusText}: ${util.inspect(data)}` + ); + } + this.log.debug(`created role mapping ${name}`); + } + + public async delete(name: string) { + this.log.debug(`deleting role mapping ${name}`); + const { data, status, statusText } = await this.axios.delete( + `/internal/security/role_mapping/${name}` + ); + if (status !== 200 && status !== 404) { + throw new Error( + `Expected status code of 200 or 404, received ${status} ${statusText}: ${util.inspect( + data + )}` + ); + } + this.log.debug(`deleted role mapping ${name}`); + } +} diff --git a/test/common/services/security/security.ts b/test/common/services/security/security.ts index 6649a765a9e50..4eebb7b6697e0 100644 --- a/test/common/services/security/security.ts +++ b/test/common/services/security/security.ts @@ -21,6 +21,7 @@ import { format as formatUrl } from 'url'; import { Role } from './role'; import { User } from './user'; +import { RoleMappings } from './role_mappings'; import { FtrProviderContext } from '../../ftr_provider_context'; export function SecurityServiceProvider({ getService }: FtrProviderContext) { @@ -30,6 +31,7 @@ export function SecurityServiceProvider({ getService }: FtrProviderContext) { return new (class SecurityService { role = new Role(url, log); + roleMappings = new RoleMappings(url, log); user = new User(url, log); })(); } diff --git a/test/functional/apps/dashboard/dashboard_state.js b/test/functional/apps/dashboard/dashboard_state.js index 3caab3db44cb3..3b9e404e9b94d 100644 --- a/test/functional/apps/dashboard/dashboard_state.js +++ b/test/functional/apps/dashboard/dashboard_state.js @@ -27,7 +27,14 @@ import { } from '../../../../src/plugins/dashboard_embeddable_container/public/embeddable/dashboard_constants'; export default function({ getService, getPageObjects }) { - const PageObjects = getPageObjects(['dashboard', 'visualize', 'header', 'discover']); + const PageObjects = getPageObjects([ + 'dashboard', + 'visualize', + 'header', + 'discover', + 'tileMap', + 'visChart', + ]); const testSubjects = getService('testSubjects'); const browser = getService('browser'); const queryBar = getService('queryBar'); @@ -58,14 +65,14 @@ export default function({ getService, getPageObjects }) { await PageObjects.dashboard.switchToEditMode(); - await PageObjects.visualize.openLegendOptionColors('Count'); - await PageObjects.visualize.selectNewLegendColorChoice('#EA6460'); + await PageObjects.visChart.openLegendOptionColors('Count'); + await PageObjects.visChart.selectNewLegendColorChoice('#EA6460'); await PageObjects.dashboard.saveDashboard('Overridden colors'); await PageObjects.dashboard.gotoDashboardLandingPage(); await PageObjects.dashboard.loadSavedDashboard('Overridden colors'); - const colorChoiceRetained = await PageObjects.visualize.doesSelectedLegendColorExist( + const colorChoiceRetained = await PageObjects.visChart.doesSelectedLegendColorExist( '#EA6460' ); @@ -153,10 +160,10 @@ export default function({ getService, getPageObjects }) { await dashboardPanelActions.openContextMenu(); await dashboardPanelActions.clickEdit(); - await PageObjects.visualize.clickMapZoomIn(); - await PageObjects.visualize.clickMapZoomIn(); - await PageObjects.visualize.clickMapZoomIn(); - await PageObjects.visualize.clickMapZoomIn(); + await PageObjects.tileMap.clickMapZoomIn(); + await PageObjects.tileMap.clickMapZoomIn(); + await PageObjects.tileMap.clickMapZoomIn(); + await PageObjects.tileMap.clickMapZoomIn(); await PageObjects.visualize.saveVisualizationExpectSuccess('Visualization TileMap'); @@ -225,8 +232,8 @@ export default function({ getService, getPageObjects }) { describe('for embeddable config color parameters on a visualization', () => { it('updates a pie slice color on a soft refresh', async function() { await dashboardAddPanel.addVisualization(PIE_CHART_VIS_NAME); - await PageObjects.visualize.openLegendOptionColors('80,000'); - await PageObjects.visualize.selectNewLegendColorChoice('#F9D9F9'); + await PageObjects.visChart.openLegendOptionColors('80,000'); + await PageObjects.visChart.selectNewLegendColorChoice('#F9D9F9'); const currentUrl = await browser.getCurrentUrl(); const newUrl = currentUrl.replace('F9D9F9', 'FFFFFF'); await browser.get(newUrl.toString(), false); @@ -248,7 +255,7 @@ export default function({ getService, getPageObjects }) { // Unskip once https://github.com/elastic/kibana/issues/15736 is fixed. it.skip('and updates the pie slice legend color', async function() { await retry.try(async () => { - const colorExists = await PageObjects.visualize.doesSelectedLegendColorExist('#FFFFFF'); + const colorExists = await PageObjects.visChart.doesSelectedLegendColorExist('#FFFFFF'); expect(colorExists).to.be(true); }); }); @@ -269,7 +276,7 @@ export default function({ getService, getPageObjects }) { // Unskip once https://github.com/elastic/kibana/issues/15736 is fixed. it.skip('resets the legend color as well', async function() { await retry.try(async () => { - const colorExists = await PageObjects.visualize.doesSelectedLegendColorExist('#57c17b'); + const colorExists = await PageObjects.visChart.doesSelectedLegendColorExist('#57c17b'); expect(colorExists).to.be(true); }); }); diff --git a/test/functional/apps/dashboard/empty_dashboard.js b/test/functional/apps/dashboard/empty_dashboard.js index d46daff183abf..c91b7bd1ecee0 100644 --- a/test/functional/apps/dashboard/empty_dashboard.js +++ b/test/functional/apps/dashboard/empty_dashboard.js @@ -44,13 +44,13 @@ export default function({ getService, getPageObjects }) { await PageObjects.dashboard.gotoDashboardLandingPage(); }); - it('should display add button', async () => { - const addButtonExists = await testSubjects.exists('emptyDashboardAddPanelButton'); - expect(addButtonExists).to.be(true); + it('should display empty widget', async () => { + const emptyWidgetExists = await testSubjects.exists('emptyDashboardWidget'); + expect(emptyWidgetExists).to.be(true); }); it.skip('should open add panel when add button is clicked', async () => { - await testSubjects.click('emptyDashboardAddPanelButton'); + await testSubjects.click('dashboardAddPanelButton'); const isAddPanelOpen = await dashboardAddPanel.isAddPanelOpen(); expect(isAddPanelOpen).to.be(true); }); diff --git a/test/functional/apps/discover/_discover_histogram.js b/test/functional/apps/discover/_discover_histogram.js new file mode 100644 index 0000000000000..4780f36fc27c6 --- /dev/null +++ b/test/functional/apps/discover/_discover_histogram.js @@ -0,0 +1,91 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import expect from '@kbn/expect'; + +export default function({ getService, getPageObjects }) { + const log = getService('log'); + const esArchiver = getService('esArchiver'); + const browser = getService('browser'); + const kibanaServer = getService('kibanaServer'); + const PageObjects = getPageObjects(['settings', 'common', 'discover', 'header', 'timePicker']); + const defaultSettings = { + defaultIndex: 'long-window-logstash-*', + 'dateFormat:tz': 'Europe/Berlin', + }; + + describe('discover histogram', function describeIndexTests() { + before(async function() { + log.debug('load kibana index with default index pattern'); + await PageObjects.common.navigateToApp('home'); + await esArchiver.loadIfNeeded('logstash_functional'); + await esArchiver.load('long_window_logstash'); + await esArchiver.load('visualize'); + await esArchiver.load('discover'); + + log.debug('create long_window_logstash index pattern'); + // NOTE: long_window_logstash load does NOT create index pattern + await PageObjects.settings.createIndexPattern('long-window-logstash-'); + await kibanaServer.uiSettings.replace(defaultSettings); + await browser.refresh(); + + log.debug('discover'); + await PageObjects.common.navigateToApp('discover'); + await PageObjects.discover.selectIndexPattern('long-window-logstash-*'); + // NOTE: For some reason without setting this relative time, the abs times will not fetch data. + await PageObjects.timePicker.setCommonlyUsedTime('superDatePickerCommonlyUsed_Last_1 year'); + }); + after(async () => { + await esArchiver.unload('long_window_logstash'); + await esArchiver.unload('visualize'); + await esArchiver.unload('discover'); + }); + + it('should visualize monthly data with different day intervals', async () => { + //Nov 1, 2017 @ 01:00:00.000 - Mar 21, 2018 @ 02:00:00.000 + const fromTime = '2017-11-01 00:00:00.000'; + const toTime = '2018-03-21 00:00:00.000'; + await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime); + await PageObjects.discover.setChartInterval('Monthly'); + await PageObjects.header.waitUntilLoadingHasFinished(); + const chartCanvasExist = await PageObjects.discover.chartCanvasExist(); + expect(chartCanvasExist).to.be(true); + }); + it('should visualize weekly data with within DST changes', async () => { + //Nov 1, 2017 @ 01:00:00.000 - Mar 21, 2018 @ 02:00:00.000 + const fromTime = '2018-03-01 00:00:00.000'; + const toTime = '2018-05-01 00:00:00.000'; + await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime); + await PageObjects.discover.setChartInterval('Weekly'); + await PageObjects.header.waitUntilLoadingHasFinished(); + const chartCanvasExist = await PageObjects.discover.chartCanvasExist(); + expect(chartCanvasExist).to.be(true); + }); + it('should visualize monthly data with different years Scaled to 30d', async () => { + //Nov 1, 2017 @ 01:00:00.000 - Mar 21, 2018 @ 02:00:00.000 + const fromTime = '2010-01-01 00:00:00.000'; + const toTime = '2018-03-21 00:00:00.000'; + await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime); + await PageObjects.discover.setChartInterval('Daily'); + await PageObjects.header.waitUntilLoadingHasFinished(); + const chartCanvasExist = await PageObjects.discover.chartCanvasExist(); + expect(chartCanvasExist).to.be(true); + }); + }); +} diff --git a/test/functional/apps/discover/index.js b/test/functional/apps/discover/index.js index e10e772e93ab1..64a5a61335365 100644 --- a/test/functional/apps/discover/index.js +++ b/test/functional/apps/discover/index.js @@ -34,6 +34,7 @@ export default function({ getService, loadTestFile }) { loadTestFile(require.resolve('./_saved_queries')); loadTestFile(require.resolve('./_discover')); + loadTestFile(require.resolve('./_discover_histogram')); loadTestFile(require.resolve('./_filter_editor')); loadTestFile(require.resolve('./_errors')); loadTestFile(require.resolve('./_field_data')); diff --git a/test/functional/apps/getting_started/_shakespeare.js b/test/functional/apps/getting_started/_shakespeare.js index 04d81f4b46083..5af1676cf423f 100644 --- a/test/functional/apps/getting_started/_shakespeare.js +++ b/test/functional/apps/getting_started/_shakespeare.js @@ -23,7 +23,14 @@ export default function({ getService, getPageObjects }) { const log = getService('log'); const esArchiver = getService('esArchiver'); const retry = getService('retry'); - const PageObjects = getPageObjects(['console', 'common', 'settings', 'visualize']); + const PageObjects = getPageObjects([ + 'console', + 'common', + 'settings', + 'visualize', + 'visEditor', + 'visChart', + ]); // https://www.elastic.co/guide/en/kibana/current/tutorial-load-dataset.html @@ -63,11 +70,11 @@ export default function({ getService, getPageObjects }) { await PageObjects.visualize.navigateToNewVisualization(); await PageObjects.visualize.clickVerticalBarChart(); await PageObjects.visualize.clickNewSearch('shakes*'); - await PageObjects.visualize.waitForVisualization(); + await PageObjects.visChart.waitForVisualization(); const expectedChartValues = [111396]; await retry.try(async () => { - const data = await PageObjects.visualize.getBarChartData('Count'); + const data = await PageObjects.visChart.getBarChartData('Count'); log.debug('data=' + data); log.debug('data.length=' + data.length); expect(data[0] - expectedChartValues[0]).to.be.lessThan(5); @@ -84,22 +91,22 @@ export default function({ getService, getPageObjects }) { it('should configure metric Unique Count Speaking Parts', async function() { log.debug('Metric = Unique Count, speaker, Speaking Parts'); // this first change to the YAxis metric agg uses the default aggIndex of 1 - await PageObjects.visualize.selectYAxisAggregation( + await PageObjects.visEditor.selectYAxisAggregation( 'Unique Count', 'speaker', 'Speaking Parts' ); // then increment the aggIndex for the next one we create aggIndex = aggIndex + 1; - await PageObjects.visualize.clickGo(); + await PageObjects.visEditor.clickGo(); const expectedChartValues = [935]; await retry.try(async () => { - const data = await PageObjects.visualize.getBarChartData('Speaking Parts'); + const data = await PageObjects.visChart.getBarChartData('Speaking Parts'); log.debug('data=' + data); log.debug('data.length=' + data.length); expect(data).to.eql(expectedChartValues); }); - const title = await PageObjects.visualize.getYAxisTitle(); + const title = await PageObjects.visChart.getYAxisTitle(); expect(title).to.be('Speaking Parts'); }); @@ -110,23 +117,23 @@ export default function({ getService, getPageObjects }) { 5. Click Apply changes images/apply-changes-button.png to view the results. */ it('should configure Terms aggregation on play_name', async function() { - await PageObjects.visualize.clickBucket('X-axis'); + await PageObjects.visEditor.clickBucket('X-axis'); log.debug('Aggregation = Terms'); - await PageObjects.visualize.selectAggregation('Terms'); + await PageObjects.visEditor.selectAggregation('Terms'); aggIndex = aggIndex + 1; log.debug('Field = play_name'); - await PageObjects.visualize.selectField('play_name'); - await PageObjects.visualize.clickGo(); + await PageObjects.visEditor.selectField('play_name'); + await PageObjects.visEditor.clickGo(); const expectedChartValues = [71, 65, 62, 55, 55]; await retry.try(async () => { - const data = await PageObjects.visualize.getBarChartData('Speaking Parts'); + const data = await PageObjects.visChart.getBarChartData('Speaking Parts'); log.debug('data=' + data); log.debug('data.length=' + data.length); expect(data).to.eql(expectedChartValues); }); - const labels = await PageObjects.visualize.getXAxisLabels(); + const labels = await PageObjects.visChart.getXAxisLabels(); expect(labels).to.eql([ 'Richard III', 'Henry VI Part 2', @@ -145,21 +152,21 @@ export default function({ getService, getPageObjects }) { 2. Choose the Max aggregation and select the speech_number field. */ it('should configure Max aggregation metric on speech_number', async function() { - await PageObjects.visualize.clickBucket('Y-axis', 'metrics'); + await PageObjects.visEditor.clickBucket('Y-axis', 'metrics'); log.debug('Aggregation = Max'); - await PageObjects.visualize.selectYAxisAggregation( + await PageObjects.visEditor.selectYAxisAggregation( 'Max', 'speech_number', 'Max Speaking Parts', aggIndex ); - await PageObjects.visualize.clickGo(); + await PageObjects.visEditor.clickGo(); const expectedChartValues = [71, 65, 62, 55, 55]; const expectedChartValues2 = [177, 106, 153, 132, 162]; await retry.try(async () => { - const data = await PageObjects.visualize.getBarChartData('Speaking Parts'); - const data2 = await PageObjects.visualize.getBarChartData('Max Speaking Parts'); + const data = await PageObjects.visChart.getBarChartData('Speaking Parts'); + const data2 = await PageObjects.visChart.getBarChartData('Max Speaking Parts'); log.debug('data=' + data); log.debug('data.length=' + data.length); log.debug('data2=' + data2); @@ -168,7 +175,7 @@ export default function({ getService, getPageObjects }) { expect(data2).to.eql(expectedChartValues2); }); - const labels = await PageObjects.visualize.getXAxisLabels(); + const labels = await PageObjects.visChart.getXAxisLabels(); expect(labels).to.eql([ 'Richard III', 'Henry VI Part 2', @@ -184,15 +191,15 @@ export default function({ getService, getPageObjects }) { 4. Click Apply changes images/apply-changes-button.png. Your chart should now look like this: */ it('should configure change options to normal bars', async function() { - await PageObjects.visualize.clickMetricsAndAxes(); - await PageObjects.visualize.selectChartMode('normal'); - await PageObjects.visualize.clickGo(); + await PageObjects.visEditor.clickMetricsAndAxes(); + await PageObjects.visEditor.selectChartMode('normal'); + await PageObjects.visEditor.clickGo(); const expectedChartValues = [71, 65, 62, 55, 55]; const expectedChartValues2 = [177, 106, 153, 132, 162]; await retry.try(async () => { - const data = await PageObjects.visualize.getBarChartData('Speaking Parts'); - const data2 = await PageObjects.visualize.getBarChartData('Max Speaking Parts'); + const data = await PageObjects.visChart.getBarChartData('Speaking Parts'); + const data2 = await PageObjects.visChart.getBarChartData('Max Speaking Parts'); log.debug('data=' + data); log.debug('data.length=' + data.length); log.debug('data2=' + data2); @@ -210,15 +217,15 @@ export default function({ getService, getPageObjects }) { Save this chart with the name Bar Example. */ it('should change the Y-Axis extents', async function() { - await PageObjects.visualize.setAxisExtents('50', '250'); - await PageObjects.visualize.clickGo(); + await PageObjects.visEditor.setAxisExtents('50', '250'); + await PageObjects.visEditor.clickGo(); // same values as previous test except scaled down by the 50 for Y-Axis min const expectedChartValues = [21, 15, 12, 5, 5]; const expectedChartValues2 = [127, 56, 103, 82, 112]; await retry.try(async () => { - const data = await PageObjects.visualize.getBarChartData('Speaking Parts'); - const data2 = await PageObjects.visualize.getBarChartData('Max Speaking Parts'); + const data = await PageObjects.visChart.getBarChartData('Speaking Parts'); + const data2 = await PageObjects.visChart.getBarChartData('Max Speaking Parts'); log.debug('data=' + data); log.debug('data.length=' + data.length); log.debug('data2=' + data2); diff --git a/test/functional/apps/management/_handle_alias.js b/test/functional/apps/management/_handle_alias.js index 06406bddeb009..3d9368f8d4680 100644 --- a/test/functional/apps/management/_handle_alias.js +++ b/test/functional/apps/management/_handle_alias.js @@ -74,7 +74,7 @@ export default function({ getService, getPageObjects }) { const toTime = 'Nov 19, 2016 @ 05:00:00.000'; await PageObjects.common.navigateToApp('discover'); - await PageObjects.discover.selectIndexPattern('alias2'); + await PageObjects.discover.selectIndexPattern('alias2*'); await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime); await retry.try(async function() { diff --git a/test/functional/apps/visualize/_area_chart.js b/test/functional/apps/visualize/_area_chart.js index b8fa253f72104..e52cfdf478c33 100644 --- a/test/functional/apps/visualize/_area_chart.js +++ b/test/functional/apps/visualize/_area_chart.js @@ -24,7 +24,14 @@ export default function({ getService, getPageObjects }) { const inspector = getService('inspector'); const browser = getService('browser'); const retry = getService('retry'); - const PageObjects = getPageObjects(['common', 'visualize', 'header', 'settings', 'timePicker']); + const PageObjects = getPageObjects([ + 'common', + 'visualize', + 'visEditor', + 'visChart', + 'header', + 'timePicker', + ]); describe('area charts', function indexPatternCreation() { const vizName1 = 'Visualization AreaChart Name Test'; @@ -38,17 +45,17 @@ export default function({ getService, getPageObjects }) { await PageObjects.visualize.clickNewSearch(); await PageObjects.timePicker.setDefaultAbsoluteRange(); log.debug('Click X-axis'); - await PageObjects.visualize.clickBucket('X-axis'); + await PageObjects.visEditor.clickBucket('X-axis'); log.debug('Click Date Histogram'); - await PageObjects.visualize.selectAggregation('Date Histogram'); + await PageObjects.visEditor.selectAggregation('Date Histogram'); log.debug('Check field value'); - const fieldValues = await PageObjects.visualize.getField(); + const fieldValues = await PageObjects.visEditor.getField(); log.debug('fieldValue = ' + fieldValues); expect(fieldValues[0]).to.be('@timestamp'); - const intervalValue = await PageObjects.visualize.getInterval(); + const intervalValue = await PageObjects.visEditor.getInterval(); log.debug('intervalValue = ' + intervalValue); expect(intervalValue[0]).to.be('Auto'); - return PageObjects.visualize.clickGo(); + return PageObjects.visEditor.clickGo(); }; before(initAreaChart); @@ -70,7 +77,7 @@ export default function({ getService, getPageObjects }) { it('should save and load', async function() { await PageObjects.visualize.saveVisualizationExpectSuccessAndBreadcrumb(vizName1); await PageObjects.visualize.loadSavedVisualization(vizName1); - await PageObjects.visualize.waitForVisualization(); + await PageObjects.visChart.waitForVisualization(); }); it('should have inspector enabled', async function() { @@ -113,14 +120,14 @@ export default function({ getService, getPageObjects }) { ]; await retry.try(async function tryingForTime() { - const labels = await PageObjects.visualize.getXAxisLabels(); + const labels = await PageObjects.visChart.getXAxisLabels(); log.debug('X-Axis labels = ' + labels); expect(labels).to.eql(xAxisLabels); }); - const labels = await PageObjects.visualize.getYAxisLabels(); + const labels = await PageObjects.visChart.getYAxisLabels(); log.debug('Y-Axis labels = ' + labels); expect(labels).to.eql(yAxisLabels); - const paths = await PageObjects.visualize.getAreaChartData('Count'); + const paths = await PageObjects.visChart.getAreaChartData('Count'); log.debug('expectedAreaChartData = ' + expectedAreaChartData); log.debug('actual chart data = ' + paths); expect(paths).to.eql(expectedAreaChartData); @@ -185,9 +192,9 @@ export default function({ getService, getPageObjects }) { ['2015-09-20 19:00', '55'], ]; - await PageObjects.visualize.toggleOpenEditor(2); - await PageObjects.visualize.setInterval('Second'); - await PageObjects.visualize.clickGo(); + await PageObjects.visEditor.toggleOpenEditor(2); + await PageObjects.visEditor.setInterval('Second'); + await PageObjects.visEditor.clickGo(); await inspector.open(); await inspector.expectTableData(expectedTableData); await inspector.close(); @@ -217,9 +224,9 @@ export default function({ getService, getPageObjects }) { ['2015-09-20 19:00', '0.015'], ]; - await PageObjects.visualize.toggleAdvancedParams('2'); - await PageObjects.visualize.toggleScaleMetrics(); - await PageObjects.visualize.clickGo(); + await PageObjects.visEditor.toggleAdvancedParams('2'); + await PageObjects.visEditor.toggleScaleMetrics(); + await PageObjects.visEditor.clickGo(); await inspector.open(); await inspector.expectTableData(expectedTableData); await inspector.close(); @@ -249,11 +256,11 @@ export default function({ getService, getPageObjects }) { ['2015-09-20 19:00', '55', '2.053KB'], ]; - await PageObjects.visualize.clickBucket('Y-axis', 'metrics'); - await PageObjects.visualize.selectAggregation('Top Hit', 'metrics'); - await PageObjects.visualize.selectField('bytes', 'metrics'); - await PageObjects.visualize.selectAggregateWith('average'); - await PageObjects.visualize.clickGo(); + await PageObjects.visEditor.clickBucket('Y-axis', 'metrics'); + await PageObjects.visEditor.selectAggregation('Top Hit', 'metrics'); + await PageObjects.visEditor.selectField('bytes', 'metrics'); + await PageObjects.visEditor.selectAggregateWith('average'); + await PageObjects.visEditor.clickGo(); await inspector.open(); await inspector.expectTableData(expectedTableData); await inspector.close(); @@ -285,13 +292,13 @@ export default function({ getService, getPageObjects }) { const axisId = 'ValueAxis-1'; it('should show ticks on selecting log scale', async () => { - await PageObjects.visualize.clickMetricsAndAxes(); - await PageObjects.visualize.clickYAxisOptions(axisId); - await PageObjects.visualize.selectYAxisScaleType(axisId, 'log'); - await PageObjects.visualize.clickYAxisAdvancedOptions(axisId); - await PageObjects.visualize.changeYAxisFilterLabelsCheckbox(axisId, false); - await PageObjects.visualize.clickGo(); - const labels = await PageObjects.visualize.getYAxisLabels(); + await PageObjects.visEditor.clickMetricsAndAxes(); + await PageObjects.visEditor.clickYAxisOptions(axisId); + await PageObjects.visEditor.selectYAxisScaleType(axisId, 'log'); + await PageObjects.visEditor.clickYAxisAdvancedOptions(axisId); + await PageObjects.visEditor.changeYAxisFilterLabelsCheckbox(axisId, false); + await PageObjects.visEditor.clickGo(); + const labels = await PageObjects.visChart.getYAxisLabels(); const expectedLabels = [ '2', '3', @@ -317,9 +324,9 @@ export default function({ getService, getPageObjects }) { }); it('should show filtered ticks on selecting log scale', async () => { - await PageObjects.visualize.changeYAxisFilterLabelsCheckbox(axisId, true); - await PageObjects.visualize.clickGo(); - const labels = await PageObjects.visualize.getYAxisLabels(); + await PageObjects.visEditor.changeYAxisFilterLabelsCheckbox(axisId, true); + await PageObjects.visEditor.clickGo(); + const labels = await PageObjects.visChart.getYAxisLabels(); const expectedLabels = [ '2', '3', @@ -345,10 +352,10 @@ export default function({ getService, getPageObjects }) { }); it('should show ticks on selecting square root scale', async () => { - await PageObjects.visualize.selectYAxisScaleType(axisId, 'square root'); - await PageObjects.visualize.changeYAxisFilterLabelsCheckbox(axisId, false); - await PageObjects.visualize.clickGo(); - const labels = await PageObjects.visualize.getYAxisLabels(); + await PageObjects.visEditor.selectYAxisScaleType(axisId, 'square root'); + await PageObjects.visEditor.changeYAxisFilterLabelsCheckbox(axisId, false); + await PageObjects.visEditor.clickGo(); + const labels = await PageObjects.visChart.getYAxisLabels(); const expectedLabels = [ '0', '200', @@ -364,18 +371,18 @@ export default function({ getService, getPageObjects }) { }); it('should show filtered ticks on selecting square root scale', async () => { - await PageObjects.visualize.changeYAxisFilterLabelsCheckbox(axisId, true); - await PageObjects.visualize.clickGo(); - const labels = await PageObjects.visualize.getYAxisLabels(); + await PageObjects.visEditor.changeYAxisFilterLabelsCheckbox(axisId, true); + await PageObjects.visEditor.clickGo(); + const labels = await PageObjects.visChart.getYAxisLabels(); const expectedLabels = ['200', '400', '600', '800', '1,000', '1,200', '1,400']; expect(labels).to.eql(expectedLabels); }); it('should show ticks on selecting linear scale', async () => { - await PageObjects.visualize.selectYAxisScaleType(axisId, 'linear'); - await PageObjects.visualize.changeYAxisFilterLabelsCheckbox(axisId, false); - await PageObjects.visualize.clickGo(); - const labels = await PageObjects.visualize.getYAxisLabels(); + await PageObjects.visEditor.selectYAxisScaleType(axisId, 'linear'); + await PageObjects.visEditor.changeYAxisFilterLabelsCheckbox(axisId, false); + await PageObjects.visEditor.clickGo(); + const labels = await PageObjects.visChart.getYAxisLabels(); log.debug(labels); const expectedLabels = [ '0', @@ -392,9 +399,9 @@ export default function({ getService, getPageObjects }) { }); it('should show filtered ticks on selecting linear scale', async () => { - await PageObjects.visualize.changeYAxisFilterLabelsCheckbox(axisId, true); - await PageObjects.visualize.clickGo(); - const labels = await PageObjects.visualize.getYAxisLabels(); + await PageObjects.visEditor.changeYAxisFilterLabelsCheckbox(axisId, true); + await PageObjects.visEditor.clickGo(); + const labels = await PageObjects.visChart.getYAxisLabels(); const expectedLabels = ['200', '400', '600', '800', '1,000', '1,200', '1,400']; expect(labels).to.eql(expectedLabels); }); @@ -412,16 +419,16 @@ export default function({ getService, getPageObjects }) { await PageObjects.visualize.clickNewSearch('long-window-logstash-*'); await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime); log.debug('Click X-axis'); - await PageObjects.visualize.clickBucket('X-axis'); + await PageObjects.visEditor.clickBucket('X-axis'); log.debug('Click Date Histogram'); - await PageObjects.visualize.selectAggregation('Date Histogram'); - await PageObjects.visualize.selectField('@timestamp'); - await PageObjects.visualize.setInterval('Yearly'); - await PageObjects.visualize.clickGo(); + await PageObjects.visEditor.selectAggregation('Date Histogram'); + await PageObjects.visEditor.selectField('@timestamp'); + await PageObjects.visEditor.setInterval('Yearly'); + await PageObjects.visEditor.clickGo(); // This svg area is composed by 7 years (2013 - 2019). // 7 points are used to draw the upper line (usually called y1) // 7 points compose the lower line (usually called y0) - const paths = await PageObjects.visualize.getAreaChartPaths('Count'); + const paths = await PageObjects.visChart.getAreaChartPaths('Count'); log.debug('actual chart data = ' + paths); const numberOfSegments = 7 * 2; expect(paths.length).to.eql(numberOfSegments); @@ -435,17 +442,17 @@ export default function({ getService, getPageObjects }) { await PageObjects.visualize.clickNewSearch('long-window-logstash-*'); await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime); log.debug('Click X-axis'); - await PageObjects.visualize.clickBucket('X-axis'); + await PageObjects.visEditor.clickBucket('X-axis'); log.debug('Click Date Histogram'); - await PageObjects.visualize.selectAggregation('Date Histogram'); - await PageObjects.visualize.selectField('@timestamp'); - await PageObjects.visualize.setInterval('Monthly'); - await PageObjects.visualize.clickGo(); + await PageObjects.visEditor.selectAggregation('Date Histogram'); + await PageObjects.visEditor.selectField('@timestamp'); + await PageObjects.visEditor.setInterval('Monthly'); + await PageObjects.visEditor.clickGo(); // This svg area is composed by 67 months 3 (2013) + 5 * 12 + 4 (2019) // 67 points are used to draw the upper line (usually called y1) // 67 points compose the lower line (usually called y0) const numberOfSegments = 67 * 2; - const paths = await PageObjects.visualize.getAreaChartPaths('Count'); + const paths = await PageObjects.visChart.getAreaChartPaths('Count'); log.debug('actual chart data = ' + paths); expect(paths.length).to.eql(numberOfSegments); }); diff --git a/test/functional/apps/visualize/_data_table.js b/test/functional/apps/visualize/_data_table.js index e8fe8fb656877..0a9ff1e77a2ef 100644 --- a/test/functional/apps/visualize/_data_table.js +++ b/test/functional/apps/visualize/_data_table.js @@ -22,9 +22,10 @@ import expect from '@kbn/expect'; export default function({ getService, getPageObjects }) { const log = getService('log'); const inspector = getService('inspector'); + const testSubjects = getService('testSubjects'); const retry = getService('retry'); const filterBar = getService('filterBar'); - const PageObjects = getPageObjects(['common', 'visualize', 'header', 'timePicker']); + const PageObjects = getPageObjects(['visualize', 'timePicker', 'visEditor', 'visChart']); describe('data table', function indexPatternCreation() { const vizName1 = 'Visualization DataTable'; @@ -38,27 +39,27 @@ export default function({ getService, getPageObjects }) { await PageObjects.visualize.clickNewSearch(); await PageObjects.timePicker.setDefaultAbsoluteRange(); log.debug('Bucket = Split rows'); - await PageObjects.visualize.clickBucket('Split rows'); + await PageObjects.visEditor.clickBucket('Split rows'); log.debug('Aggregation = Histogram'); - await PageObjects.visualize.selectAggregation('Histogram'); + await PageObjects.visEditor.selectAggregation('Histogram'); log.debug('Field = bytes'); - await PageObjects.visualize.selectField('bytes'); + await PageObjects.visEditor.selectField('bytes'); log.debug('Interval = 2000'); - await PageObjects.visualize.setNumericInterval('2000'); - await PageObjects.visualize.clickGo(); + await PageObjects.visEditor.setInterval('2000', { type: 'numeric' }); + await PageObjects.visEditor.clickGo(); }); it('should allow applying changed params', async () => { - await PageObjects.visualize.setNumericInterval('1', { append: true }); - const interval = await PageObjects.visualize.getNumericInterval(); + await PageObjects.visEditor.setInterval('1', { type: 'numeric', append: true }); + const interval = await PageObjects.visEditor.getNumericInterval(); expect(interval).to.be('20001'); - const isApplyButtonEnabled = await PageObjects.visualize.isApplyEnabled(); + const isApplyButtonEnabled = await PageObjects.visEditor.isApplyEnabled(); expect(isApplyButtonEnabled).to.be(true); }); it('should allow reseting changed params', async () => { - await PageObjects.visualize.clickReset(); - const interval = await PageObjects.visualize.getNumericInterval(); + await PageObjects.visEditor.clickReset(); + const interval = await PageObjects.visEditor.getNumericInterval(); expect(interval).to.be('2000'); }); @@ -66,7 +67,7 @@ export default function({ getService, getPageObjects }) { await PageObjects.visualize.saveVisualizationExpectSuccessAndBreadcrumb(vizName1); await PageObjects.visualize.loadSavedVisualization(vizName1); - await PageObjects.visualize.waitForVisualization(); + await PageObjects.visChart.waitForVisualization(); }); it('should have inspector enabled', async function() { @@ -96,7 +97,7 @@ export default function({ getService, getPageObjects }) { it('should show percentage columns', async () => { async function expectValidTableData() { - const data = await PageObjects.visualize.getTableVisData(); + const data = await PageObjects.visChart.getTableVisData(); expect(data.trim().split('\n')).to.be.eql([ '≥ 0 and < 1000', '1,351 64.7%', @@ -110,16 +111,16 @@ export default function({ getService, getPageObjects }) { await PageObjects.visualize.clickDataTable(); await PageObjects.visualize.clickNewSearch(); await PageObjects.timePicker.setDefaultAbsoluteRange(); - await PageObjects.visualize.clickBucket('Split rows'); - await PageObjects.visualize.selectAggregation('Range'); - await PageObjects.visualize.selectField('bytes'); - await PageObjects.visualize.clickGo(); - await PageObjects.visualize.clickOptionsTab(); - await PageObjects.visualize.setSelectByOptionText( + await PageObjects.visEditor.clickBucket('Split rows'); + await PageObjects.visEditor.selectAggregation('Range'); + await PageObjects.visEditor.selectField('bytes'); + await PageObjects.visEditor.clickGo(); + await PageObjects.visEditor.clickOptionsTab(); + await PageObjects.visEditor.setSelectByOptionText( 'datatableVisualizationPercentageCol', 'Count' ); - await PageObjects.visualize.clickGo(); + await PageObjects.visEditor.clickGo(); await expectValidTableData(); @@ -128,20 +129,20 @@ export default function({ getService, getPageObjects }) { await PageObjects.visualize.saveVisualizationExpectSuccessAndBreadcrumb(SAVE_NAME); await PageObjects.visualize.loadSavedVisualization(SAVE_NAME); - await PageObjects.visualize.waitForVisualization(); + await PageObjects.visChart.waitForVisualization(); await expectValidTableData(); // check that it works after selecting a column that's deleted - await PageObjects.visualize.clickData(); - await PageObjects.visualize.clickBucket('Metric', 'metrics'); - await PageObjects.visualize.selectAggregation('Average', 'metrics'); - await PageObjects.visualize.selectField('bytes', 'metrics'); - await PageObjects.visualize.removeDimension(1); - await PageObjects.visualize.clickGo(); - await PageObjects.visualize.clickOptionsTab(); - - const data = await PageObjects.visualize.getTableVisData(); + await PageObjects.visEditor.clickDataTab(); + await PageObjects.visEditor.clickBucket('Metric', 'metrics'); + await PageObjects.visEditor.selectAggregation('Average', 'metrics'); + await PageObjects.visEditor.selectField('bytes', 'metrics'); + await PageObjects.visEditor.removeDimension(1); + await PageObjects.visEditor.clickGo(); + await PageObjects.visEditor.clickOptionsTab(); + + const data = await PageObjects.visChart.getTableVisData(); expect(data.trim().split('\n')).to.be.eql([ '≥ 0 and < 1000', '344.094B', @@ -155,12 +156,12 @@ export default function({ getService, getPageObjects }) { await PageObjects.visualize.clickDataTable(); await PageObjects.visualize.clickNewSearch(); await PageObjects.timePicker.setDefaultAbsoluteRange(); - await PageObjects.visualize.clickBucket('Metric', 'metrics'); - await PageObjects.visualize.selectAggregation('Average Bucket', 'metrics'); - await PageObjects.visualize.selectAggregation('Terms', 'metrics', 'buckets'); - await PageObjects.visualize.selectField('geo.src', 'metrics', 'buckets'); - await PageObjects.visualize.clickGo(); - const data = await PageObjects.visualize.getTableVisData(); + await PageObjects.visEditor.clickBucket('Metric', 'metrics'); + await PageObjects.visEditor.selectAggregation('Average Bucket', 'metrics'); + await PageObjects.visEditor.selectAggregation('Terms', 'metrics', 'buckets'); + await PageObjects.visEditor.selectField('geo.src', 'metrics', 'buckets'); + await PageObjects.visEditor.clickGo(); + const data = await PageObjects.visChart.getTableVisData(); log.debug(data.split('\n')); expect(data.trim().split('\n')).to.be.eql(['14,004 1,412.6']); }); @@ -170,12 +171,12 @@ export default function({ getService, getPageObjects }) { await PageObjects.visualize.clickDataTable(); await PageObjects.visualize.clickNewSearch(); await PageObjects.timePicker.setDefaultAbsoluteRange(); - await PageObjects.visualize.clickBucket('Split rows'); - await PageObjects.visualize.selectAggregation('Date Histogram'); - await PageObjects.visualize.selectField('@timestamp'); - await PageObjects.visualize.setInterval('Daily'); - await PageObjects.visualize.clickGo(); - const data = await PageObjects.visualize.getTableVisData(); + await PageObjects.visEditor.clickBucket('Split rows'); + await PageObjects.visEditor.selectAggregation('Date Histogram'); + await PageObjects.visEditor.selectField('@timestamp'); + await PageObjects.visEditor.setInterval('Daily'); + await PageObjects.visEditor.clickGo(); + const data = await PageObjects.visChart.getTableVisData(); log.debug(data.split('\n')); expect(data.trim().split('\n')).to.be.eql([ '2015-09-20', @@ -192,12 +193,12 @@ export default function({ getService, getPageObjects }) { await PageObjects.visualize.clickDataTable(); await PageObjects.visualize.clickNewSearch(); await PageObjects.timePicker.setDefaultAbsoluteRange(); - await PageObjects.visualize.clickBucket('Split rows'); - await PageObjects.visualize.selectAggregation('Date Histogram'); - await PageObjects.visualize.selectField('@timestamp'); - await PageObjects.visualize.setInterval('Daily'); - await PageObjects.visualize.clickGo(); - const data = await PageObjects.visualize.getTableVisData(); + await PageObjects.visEditor.clickBucket('Split rows'); + await PageObjects.visEditor.selectAggregation('Date Histogram'); + await PageObjects.visEditor.selectField('@timestamp'); + await PageObjects.visEditor.setInterval('Daily'); + await PageObjects.visEditor.clickGo(); + const data = await PageObjects.visChart.getTableVisData(); expect(data.trim().split('\n')).to.be.eql([ '2015-09-20', '4,757', @@ -210,15 +211,15 @@ export default function({ getService, getPageObjects }) { it('should correctly filter for applied time filter on the main timefield', async () => { await filterBar.addFilter('@timestamp', 'is between', '2015-09-19', '2015-09-21'); - await PageObjects.visualize.waitForVisualizationRenderingStabilized(); - const data = await PageObjects.visualize.getTableVisData(); + await PageObjects.visChart.waitForVisualizationRenderingStabilized(); + const data = await PageObjects.visChart.getTableVisData(); expect(data.trim().split('\n')).to.be.eql(['2015-09-20', '4,757']); }); it('should correctly filter for pinned filters', async () => { await filterBar.toggleFilterPinned('@timestamp'); - await PageObjects.visualize.waitForVisualizationRenderingStabilized(); - const data = await PageObjects.visualize.getTableVisData(); + await PageObjects.visChart.waitForVisualizationRenderingStabilized(); + const data = await PageObjects.visChart.getTableVisData(); expect(data.trim().split('\n')).to.be.eql(['2015-09-20', '4,757']); }); @@ -227,11 +228,11 @@ export default function({ getService, getPageObjects }) { await PageObjects.visualize.clickDataTable(); await PageObjects.visualize.clickNewSearch(); await PageObjects.timePicker.setDefaultAbsoluteRange(); - await PageObjects.visualize.clickMetricEditor(); - await PageObjects.visualize.selectAggregation('Top Hit', 'metrics'); - await PageObjects.visualize.selectField('agent.raw', 'metrics'); - await PageObjects.visualize.clickGo(); - const data = await PageObjects.visualize.getTableVisData(); + await PageObjects.visEditor.clickMetricEditor(); + await PageObjects.visEditor.selectAggregation('Top Hit', 'metrics'); + await PageObjects.visEditor.selectField('agent.raw', 'metrics'); + await PageObjects.visEditor.clickGo(); + const data = await PageObjects.visChart.getTableVisData(); log.debug(data); expect(data.length).to.be.greaterThan(0); }); @@ -241,11 +242,11 @@ export default function({ getService, getPageObjects }) { await PageObjects.visualize.clickDataTable(); await PageObjects.visualize.clickNewSearch(); await PageObjects.timePicker.setDefaultAbsoluteRange(); - await PageObjects.visualize.clickBucket('Split rows'); - await PageObjects.visualize.selectAggregation('Range'); - await PageObjects.visualize.selectField('bytes'); - await PageObjects.visualize.clickGo(); - const data = await PageObjects.visualize.getTableVisData(); + await PageObjects.visEditor.clickBucket('Split rows'); + await PageObjects.visEditor.selectAggregation('Range'); + await PageObjects.visEditor.selectField('bytes'); + await PageObjects.visEditor.clickGo(); + const data = await PageObjects.visChart.getTableVisData(); expect(data.trim().split('\n')).to.be.eql([ '≥ 0 and < 1000', '1,351', @@ -260,19 +261,19 @@ export default function({ getService, getPageObjects }) { await PageObjects.visualize.clickDataTable(); await PageObjects.visualize.clickNewSearch(); await PageObjects.timePicker.setDefaultAbsoluteRange(); - await PageObjects.visualize.clickBucket('Split rows'); - await PageObjects.visualize.selectAggregation('Terms'); - await PageObjects.visualize.selectField('extension.raw'); - await PageObjects.visualize.setSize(2); - await PageObjects.visualize.clickGo(); - - await PageObjects.visualize.toggleOtherBucket(); - await PageObjects.visualize.toggleMissingBucket(); - await PageObjects.visualize.clickGo(); + await PageObjects.visEditor.clickBucket('Split rows'); + await PageObjects.visEditor.selectAggregation('Terms'); + await PageObjects.visEditor.selectField('extension.raw'); + await PageObjects.visEditor.setSize(2); + await PageObjects.visEditor.clickGo(); + + await PageObjects.visEditor.toggleOtherBucket(); + await PageObjects.visEditor.toggleMissingBucket(); + await PageObjects.visEditor.clickGo(); }); it('should show correct data', async () => { - const data = await PageObjects.visualize.getTableVisContent(); + const data = await PageObjects.visChart.getTableVisContent(); expect(data).to.be.eql([ ['jpg', '9,109'], ['css', '2,159'], @@ -281,9 +282,9 @@ export default function({ getService, getPageObjects }) { }); it('should apply correct filter', async () => { - await PageObjects.visualize.filterOnTableCell(1, 3); - await PageObjects.visualize.waitForVisualizationRenderingStabilized(); - const data = await PageObjects.visualize.getTableVisContent(); + await PageObjects.visChart.filterOnTableCell(1, 3); + await PageObjects.visChart.waitForVisualizationRenderingStabilized(); + const data = await PageObjects.visChart.getTableVisContent(); expect(data).to.be.eql([ ['png', '1,373'], ['gif', '918'], @@ -298,20 +299,20 @@ export default function({ getService, getPageObjects }) { await PageObjects.visualize.clickDataTable(); await PageObjects.visualize.clickNewSearch(); await PageObjects.timePicker.setDefaultAbsoluteRange(); - await PageObjects.visualize.clickBucket('Split rows'); - await PageObjects.visualize.selectAggregation('Terms'); - await PageObjects.visualize.selectField('extension.raw'); - await PageObjects.visualize.setSize(2); - await PageObjects.visualize.toggleOpenEditor(2, 'false'); - await PageObjects.visualize.clickBucket('Split rows'); - await PageObjects.visualize.selectAggregation('Terms'); - await PageObjects.visualize.selectField('geo.dest'); - await PageObjects.visualize.toggleOpenEditor(3, 'false'); - await PageObjects.visualize.clickGo(); + await PageObjects.visEditor.clickBucket('Split rows'); + await PageObjects.visEditor.selectAggregation('Terms'); + await PageObjects.visEditor.selectField('extension.raw'); + await PageObjects.visEditor.setSize(2); + await PageObjects.visEditor.toggleOpenEditor(2, 'false'); + await PageObjects.visEditor.clickBucket('Split rows'); + await PageObjects.visEditor.selectAggregation('Terms'); + await PageObjects.visEditor.selectField('geo.dest'); + await PageObjects.visEditor.toggleOpenEditor(3, 'false'); + await PageObjects.visEditor.clickGo(); }); it('should show correct data without showMetricsAtAllLevels', async () => { - const data = await PageObjects.visualize.getTableVisContent(); + const data = await PageObjects.visChart.getTableVisContent(); expect(data).to.be.eql([ ['jpg', 'CN', '1,718'], ['jpg', 'IN', '1,511'], @@ -327,10 +328,10 @@ export default function({ getService, getPageObjects }) { }); it('should show correct data without showMetricsAtAllLevels even if showPartialRows is selected', async () => { - await PageObjects.visualize.clickOptionsTab(); - await PageObjects.visualize.checkCheckbox('showPartialRows'); - await PageObjects.visualize.clickGo(); - const data = await PageObjects.visualize.getTableVisContent(); + await PageObjects.visEditor.clickOptionsTab(); + await testSubjects.setCheckbox('showPartialRows', 'check'); + await PageObjects.visEditor.clickGo(); + const data = await PageObjects.visChart.getTableVisContent(); expect(data).to.be.eql([ ['jpg', 'CN', '1,718'], ['jpg', 'IN', '1,511'], @@ -346,10 +347,10 @@ export default function({ getService, getPageObjects }) { }); it('should show metrics on each level', async () => { - await PageObjects.visualize.clickOptionsTab(); - await PageObjects.visualize.checkCheckbox('showMetricsAtAllLevels'); - await PageObjects.visualize.clickGo(); - const data = await PageObjects.visualize.getTableVisContent(); + await PageObjects.visEditor.clickOptionsTab(); + await testSubjects.setCheckbox('showMetricsAtAllLevels', 'check'); + await PageObjects.visEditor.clickGo(); + const data = await PageObjects.visChart.getTableVisContent(); expect(data).to.be.eql([ ['jpg', '9,109', 'CN', '1,718'], ['jpg', '9,109', 'IN', '1,511'], @@ -365,12 +366,12 @@ export default function({ getService, getPageObjects }) { }); it('should show metrics other than count on each level', async () => { - await PageObjects.visualize.clickData(); - await PageObjects.visualize.clickBucket('Metric', 'metrics'); - await PageObjects.visualize.selectAggregation('Average', 'metrics'); - await PageObjects.visualize.selectField('bytes', 'metrics'); - await PageObjects.visualize.clickGo(); - const data = await PageObjects.visualize.getTableVisContent(); + await PageObjects.visEditor.clickDataTab(); + await PageObjects.visEditor.clickBucket('Metric', 'metrics'); + await PageObjects.visEditor.selectAggregation('Average', 'metrics'); + await PageObjects.visEditor.selectField('bytes', 'metrics'); + await PageObjects.visEditor.clickGo(); + const data = await PageObjects.visChart.getTableVisContent(); expect(data).to.be.eql([ ['jpg', '9,109', '5.469KB', 'CN', '1,718', '5.477KB'], ['jpg', '9,109', '5.469KB', 'IN', '1,511', '5.456KB'], @@ -392,26 +393,26 @@ export default function({ getService, getPageObjects }) { await PageObjects.visualize.clickDataTable(); await PageObjects.visualize.clickNewSearch(); await PageObjects.timePicker.setDefaultAbsoluteRange(); - await PageObjects.visualize.clickBucket('Split table'); - await PageObjects.visualize.selectAggregation('Terms'); - await PageObjects.visualize.selectField('extension.raw'); - await PageObjects.visualize.setSize(2); - await PageObjects.visualize.toggleOpenEditor(2, 'false'); - await PageObjects.visualize.clickBucket('Split rows'); - await PageObjects.visualize.selectAggregation('Terms'); - await PageObjects.visualize.selectField('geo.dest'); - await PageObjects.visualize.setSize(3, 3); - await PageObjects.visualize.toggleOpenEditor(3, 'false'); - await PageObjects.visualize.clickBucket('Split rows'); - await PageObjects.visualize.selectAggregation('Terms'); - await PageObjects.visualize.selectField('geo.src'); - await PageObjects.visualize.setSize(3, 4); - await PageObjects.visualize.toggleOpenEditor(4, 'false'); - await PageObjects.visualize.clickGo(); + await PageObjects.visEditor.clickBucket('Split table'); + await PageObjects.visEditor.selectAggregation('Terms'); + await PageObjects.visEditor.selectField('extension.raw'); + await PageObjects.visEditor.setSize(2); + await PageObjects.visEditor.toggleOpenEditor(2, 'false'); + await PageObjects.visEditor.clickBucket('Split rows'); + await PageObjects.visEditor.selectAggregation('Terms'); + await PageObjects.visEditor.selectField('geo.dest'); + await PageObjects.visEditor.setSize(3, 3); + await PageObjects.visEditor.toggleOpenEditor(3, 'false'); + await PageObjects.visEditor.clickBucket('Split rows'); + await PageObjects.visEditor.selectAggregation('Terms'); + await PageObjects.visEditor.selectField('geo.src'); + await PageObjects.visEditor.setSize(3, 4); + await PageObjects.visEditor.toggleOpenEditor(4, 'false'); + await PageObjects.visEditor.clickGo(); }); it('should have a splitted table', async () => { - const data = await PageObjects.visualize.getTableVisContent(); + const data = await PageObjects.visChart.getTableVisContent(); expect(data).to.be.eql([ [ ['CN', 'CN', '330'], @@ -439,10 +440,10 @@ export default function({ getService, getPageObjects }) { }); it('should show metrics for split bucket when using showMetricsAtAllLevels', async () => { - await PageObjects.visualize.clickOptionsTab(); - await PageObjects.visualize.checkCheckbox('showMetricsAtAllLevels'); - await PageObjects.visualize.clickGo(); - const data = await PageObjects.visualize.getTableVisContent(); + await PageObjects.visEditor.clickOptionsTab(); + await testSubjects.setCheckbox('showMetricsAtAllLevels', 'check'); + await PageObjects.visEditor.clickGo(); + const data = await PageObjects.visChart.getTableVisContent(); expect(data).to.be.eql([ [ ['CN', '1,718', 'CN', '330'], diff --git a/test/functional/apps/visualize/_data_table_nontimeindex.js b/test/functional/apps/visualize/_data_table_nontimeindex.js index 77478f5d10edc..3db3cd094a81b 100644 --- a/test/functional/apps/visualize/_data_table_nontimeindex.js +++ b/test/functional/apps/visualize/_data_table_nontimeindex.js @@ -25,7 +25,7 @@ export default function({ getService, getPageObjects }) { const retry = getService('retry'); const filterBar = getService('filterBar'); const renderable = getService('renderable'); - const PageObjects = getPageObjects(['common', 'visualize', 'header']); + const PageObjects = getPageObjects(['visualize', 'visEditor', 'header', 'visChart']); describe.skip('data table with index without time filter', function indexPatternCreation() { const vizName1 = 'Visualization DataTable without time filter'; @@ -40,27 +40,27 @@ export default function({ getService, getPageObjects }) { PageObjects.visualize.index.LOGSTASH_NON_TIME_BASED ); log.debug('Bucket = Split Rows'); - await PageObjects.visualize.clickBucket('Split rows'); + await PageObjects.visEditor.clickBucket('Split rows'); log.debug('Aggregation = Histogram'); - await PageObjects.visualize.selectAggregation('Histogram'); + await PageObjects.visEditor.selectAggregation('Histogram'); log.debug('Field = bytes'); - await PageObjects.visualize.selectField('bytes'); + await PageObjects.visEditor.selectField('bytes'); log.debug('Interval = 2000'); - await PageObjects.visualize.setNumericInterval('2000'); - await PageObjects.visualize.clickGo(); + await PageObjects.visEditor.setInterval('2000', { type: 'numeric' }); + await PageObjects.visEditor.clickGo(); }); it('should allow applying changed params', async () => { - await PageObjects.visualize.setNumericInterval('1', { append: true }); - const interval = await PageObjects.visualize.getNumericInterval(); + await PageObjects.visEditor.setInterval('1', { type: 'numeric', append: true }); + const interval = await PageObjects.visEditor.getNumericInterval(); expect(interval).to.be('20001'); - const isApplyButtonEnabled = await PageObjects.visualize.isApplyEnabled(); + const isApplyButtonEnabled = await PageObjects.visEditor.isApplyEnabled(); expect(isApplyButtonEnabled).to.be(true); }); it('should allow reseting changed params', async () => { - await PageObjects.visualize.clickReset(); - const interval = await PageObjects.visualize.getNumericInterval(); + await PageObjects.visEditor.clickReset(); + const interval = await PageObjects.visEditor.getNumericInterval(); expect(interval).to.be('2000'); }); @@ -68,7 +68,7 @@ export default function({ getService, getPageObjects }) { await PageObjects.visualize.saveVisualizationExpectSuccessAndBreadcrumb(vizName1); await PageObjects.visualize.loadSavedVisualization(vizName1); - await PageObjects.visualize.waitForVisualization(); + await PageObjects.visChart.waitForVisualization(); }); it('should have inspector enabled', async function() { @@ -102,12 +102,12 @@ export default function({ getService, getPageObjects }) { await PageObjects.visualize.clickNewSearch( PageObjects.visualize.index.LOGSTASH_NON_TIME_BASED ); - await PageObjects.visualize.clickBucket('Metric', 'metrics'); - await PageObjects.visualize.selectAggregation('Average Bucket', 'metrics'); - await PageObjects.visualize.selectAggregation('Terms', 'metrics', 'buckets'); - await PageObjects.visualize.selectField('geo.src', 'metrics', 'buckets'); - await PageObjects.visualize.clickGo(); - const data = await PageObjects.visualize.getTableVisData(); + await PageObjects.visEditor.clickBucket('Metric', 'metrics'); + await PageObjects.visEditor.selectAggregation('Average Bucket', 'metrics'); + await PageObjects.visEditor.selectAggregation('Terms', 'metrics', 'buckets'); + await PageObjects.visEditor.selectField('geo.src', 'metrics', 'buckets'); + await PageObjects.visEditor.clickGo(); + const data = await PageObjects.visChart.getTableVisData(); log.debug(data.split('\n')); expect(data.trim().split('\n')).to.be.eql(['14,004 1,412.6']); }); @@ -118,12 +118,12 @@ export default function({ getService, getPageObjects }) { await PageObjects.visualize.clickNewSearch( PageObjects.visualize.index.LOGSTASH_NON_TIME_BASED ); - await PageObjects.visualize.clickBucket('Split rows'); - await PageObjects.visualize.selectAggregation('Date Histogram'); - await PageObjects.visualize.selectField('@timestamp'); - await PageObjects.visualize.setInterval('Daily'); - await PageObjects.visualize.clickGo(); - const data = await PageObjects.visualize.getTableVisData(); + await PageObjects.visEditor.clickBucket('Split rows'); + await PageObjects.visEditor.selectAggregation('Date Histogram'); + await PageObjects.visEditor.selectField('@timestamp'); + await PageObjects.visEditor.setInterval('Daily'); + await PageObjects.visEditor.clickGo(); + const data = await PageObjects.visChart.getTableVisData(); log.debug(data.split('\n')); expect(data.trim().split('\n')).to.be.eql([ '2015-09-20', @@ -141,12 +141,12 @@ export default function({ getService, getPageObjects }) { await PageObjects.visualize.clickNewSearch( PageObjects.visualize.index.LOGSTASH_NON_TIME_BASED ); - await PageObjects.visualize.clickBucket('Split rows'); - await PageObjects.visualize.selectAggregation('Date Histogram'); - await PageObjects.visualize.selectField('@timestamp'); - await PageObjects.visualize.setInterval('Daily'); - await PageObjects.visualize.clickGo(); - const data = await PageObjects.visualize.getTableVisData(); + await PageObjects.visEditor.clickBucket('Split rows'); + await PageObjects.visEditor.selectAggregation('Date Histogram'); + await PageObjects.visEditor.selectField('@timestamp'); + await PageObjects.visEditor.setInterval('Daily'); + await PageObjects.visEditor.clickGo(); + const data = await PageObjects.visChart.getTableVisData(); expect(data.trim().split('\n')).to.be.eql([ '2015-09-20', '4,757', @@ -161,7 +161,7 @@ export default function({ getService, getPageObjects }) { await filterBar.addFilter('@timestamp', 'is between', '2015-09-19', '2015-09-21'); await PageObjects.header.waitUntilLoadingHasFinished(); await renderable.waitForRender(); - const data = await PageObjects.visualize.getTableVisData(); + const data = await PageObjects.visChart.getTableVisData(); expect(data.trim().split('\n')).to.be.eql(['2015-09-20', '4,757']); }); @@ -169,7 +169,7 @@ export default function({ getService, getPageObjects }) { await filterBar.toggleFilterPinned('@timestamp'); await PageObjects.header.waitUntilLoadingHasFinished(); await renderable.waitForRender(); - const data = await PageObjects.visualize.getTableVisData(); + const data = await PageObjects.visChart.getTableVisData(); expect(data.trim().split('\n')).to.be.eql(['2015-09-20', '4,757']); }); }); diff --git a/test/functional/apps/visualize/_embedding_chart.js b/test/functional/apps/visualize/_embedding_chart.js index c16dc3b1279ff..940aa3eb5d462 100644 --- a/test/functional/apps/visualize/_embedding_chart.js +++ b/test/functional/apps/visualize/_embedding_chart.js @@ -24,7 +24,13 @@ export default function({ getService, getPageObjects }) { const log = getService('log'); const renderable = getService('renderable'); const embedding = getService('embedding'); - const PageObjects = getPageObjects(['common', 'visualize', 'header', 'timePicker']); + const PageObjects = getPageObjects([ + 'visualize', + 'visEditor', + 'visChart', + 'header', + 'timePicker', + ]); describe('embedding', () => { describe('a data table', () => { @@ -33,22 +39,22 @@ export default function({ getService, getPageObjects }) { await PageObjects.visualize.clickDataTable(); await PageObjects.visualize.clickNewSearch(); await PageObjects.timePicker.setDefaultAbsoluteRange(); - await PageObjects.visualize.clickBucket('Split rows'); - await PageObjects.visualize.selectAggregation('Date Histogram'); - await PageObjects.visualize.selectField('@timestamp'); - await PageObjects.visualize.toggleOpenEditor(2, 'false'); - await PageObjects.visualize.clickBucket('Split rows'); - await PageObjects.visualize.selectAggregation('Histogram'); - await PageObjects.visualize.selectField('bytes'); - await PageObjects.visualize.setNumericInterval('2000', undefined, 3); - await PageObjects.visualize.clickGo(); + await PageObjects.visEditor.clickBucket('Split rows'); + await PageObjects.visEditor.selectAggregation('Date Histogram'); + await PageObjects.visEditor.selectField('@timestamp'); + await PageObjects.visEditor.toggleOpenEditor(2, 'false'); + await PageObjects.visEditor.clickBucket('Split rows'); + await PageObjects.visEditor.selectAggregation('Histogram'); + await PageObjects.visEditor.selectField('bytes'); + await PageObjects.visEditor.setInterval('2000', { type: 'numeric', aggNth: 3 }); + await PageObjects.visEditor.clickGo(); }); it('should allow opening table vis in embedded mode', async () => { await embedding.openInEmbeddedMode(); await renderable.waitForRender(); - const data = await PageObjects.visualize.getTableVisData(); + const data = await PageObjects.visChart.getTableVisData(); log.debug(data.split('\n')); expect(data.trim().split('\n')).to.be.eql([ '2015-09-20 00:00', @@ -89,7 +95,7 @@ export default function({ getService, getPageObjects }) { await PageObjects.header.waitUntilLoadingHasFinished(); await renderable.waitForRender(); - const data = await PageObjects.visualize.getTableVisData(); + const data = await PageObjects.visChart.getTableVisData(); log.debug(data.split('\n')); expect(data.trim().split('\n')).to.be.eql([ '2015-09-21 00:00', @@ -126,11 +132,11 @@ export default function({ getService, getPageObjects }) { }); it('should allow to change timerange from the visualization in embedded mode', async () => { - await PageObjects.visualize.filterOnTableCell(1, 7); + await PageObjects.visChart.filterOnTableCell(1, 7); await PageObjects.header.waitUntilLoadingHasFinished(); await renderable.waitForRender(); - const data = await PageObjects.visualize.getTableVisData(); + const data = await PageObjects.visChart.getTableVisData(); log.debug(data.split('\n')); expect(data.trim().split('\n')).to.be.eql([ '03:00', diff --git a/test/functional/apps/visualize/_experimental_vis.js b/test/functional/apps/visualize/_experimental_vis.js index ae364244b4f5c..2ce15cf913eff 100644 --- a/test/functional/apps/visualize/_experimental_vis.js +++ b/test/functional/apps/visualize/_experimental_vis.js @@ -21,7 +21,7 @@ import expect from '@kbn/expect'; export default ({ getService, getPageObjects }) => { const log = getService('log'); - const PageObjects = getPageObjects(['common', 'visualize']); + const PageObjects = getPageObjects(['visualize']); describe('visualize app', function() { this.tags('smoke'); diff --git a/test/functional/apps/visualize/_gauge_chart.js b/test/functional/apps/visualize/_gauge_chart.js index 2b9033d1ae9d2..7ebb4548f967b 100644 --- a/test/functional/apps/visualize/_gauge_chart.js +++ b/test/functional/apps/visualize/_gauge_chart.js @@ -24,7 +24,7 @@ export default function({ getService, getPageObjects }) { const retry = getService('retry'); const inspector = getService('inspector'); const testSubjects = getService('testSubjects'); - const PageObjects = getPageObjects(['common', 'visualize', 'timePicker']); + const PageObjects = getPageObjects(['visualize', 'visEditor', 'visChart', 'timePicker']); // FLAKY: https://github.com/elastic/kibana/issues/45089 describe('gauge chart', function indexPatternCreation() { @@ -50,24 +50,24 @@ export default function({ getService, getPageObjects }) { // initial metric of "Count" is selected by default return retry.try(async function tryingForTime() { - const metricValue = await PageObjects.visualize.getGaugeValue(); + const metricValue = await PageObjects.visChart.getGaugeValue(); expect(expectedCount).to.eql(metricValue); }); }); it('should show Split Gauges', async function() { log.debug('Bucket = Split Group'); - await PageObjects.visualize.clickBucket('Split group'); + await PageObjects.visEditor.clickBucket('Split group'); log.debug('Aggregation = Terms'); - await PageObjects.visualize.selectAggregation('Terms'); + await PageObjects.visEditor.selectAggregation('Terms'); log.debug('Field = machine.os.raw'); - await PageObjects.visualize.selectField('machine.os.raw'); + await PageObjects.visEditor.selectField('machine.os.raw'); log.debug('Size = 4'); - await PageObjects.visualize.setSize('4'); - await PageObjects.visualize.clickGo(); + await PageObjects.visEditor.setSize('4'); + await PageObjects.visEditor.clickGo(); await retry.try(async () => { - expect(await PageObjects.visualize.getGaugeValue()).to.eql([ + expect(await PageObjects.visChart.getGaugeValue()).to.eql([ '2,904', 'win 8', '2,858', @@ -83,34 +83,34 @@ export default function({ getService, getPageObjects }) { it('should show correct values for fields with fieldFormatters', async function() { const expectedTexts = ['2,904', 'win 8: Count', '0B', 'win 8: Min bytes']; - await PageObjects.visualize.selectAggregation('Terms'); - await PageObjects.visualize.selectField('machine.os.raw'); - await PageObjects.visualize.setSize('1'); - await PageObjects.visualize.clickBucket('Metric', 'metrics'); - await PageObjects.visualize.selectAggregation('Min', 'metrics'); - await PageObjects.visualize.selectField('bytes', 'metrics'); - await PageObjects.visualize.clickGo(); + await PageObjects.visEditor.selectAggregation('Terms'); + await PageObjects.visEditor.selectField('machine.os.raw'); + await PageObjects.visEditor.setSize('1'); + await PageObjects.visEditor.clickBucket('Metric', 'metrics'); + await PageObjects.visEditor.selectAggregation('Min', 'metrics'); + await PageObjects.visEditor.selectField('bytes', 'metrics'); + await PageObjects.visEditor.clickGo(); await retry.try(async function tryingForTime() { - const metricValue = await PageObjects.visualize.getGaugeValue(); + const metricValue = await PageObjects.visChart.getGaugeValue(); expect(expectedTexts).to.eql(metricValue); }); }); it('should format the metric correctly in percentage mode', async function() { await initGaugeVis(); - await PageObjects.visualize.clickMetricEditor(); - await PageObjects.visualize.selectAggregation('Average', 'metrics'); - await PageObjects.visualize.selectField('bytes', 'metrics'); - await PageObjects.visualize.clickOptionsTab(); + await PageObjects.visEditor.clickMetricEditor(); + await PageObjects.visEditor.selectAggregation('Average', 'metrics'); + await PageObjects.visEditor.selectField('bytes', 'metrics'); + await PageObjects.visEditor.clickOptionsTab(); await testSubjects.setValue('gaugeColorRange2__to', '10000'); await testSubjects.click('gaugePercentageMode'); - await PageObjects.visualize.waitForVisualizationRenderingStabilized(); - await PageObjects.visualize.clickGo(); + await PageObjects.visChart.waitForVisualizationRenderingStabilized(); + await PageObjects.visEditor.clickGo(); await retry.try(async function tryingForTime() { const expectedTexts = ['57.273%', 'Average bytes']; - const metricValue = await PageObjects.visualize.getGaugeValue(); + const metricValue = await PageObjects.visChart.getGaugeValue(); expect(expectedTexts).to.eql(metricValue); }); }); diff --git a/test/functional/apps/visualize/_heatmap_chart.js b/test/functional/apps/visualize/_heatmap_chart.js index 226e490a6b232..2cea861d0f64d 100644 --- a/test/functional/apps/visualize/_heatmap_chart.js +++ b/test/functional/apps/visualize/_heatmap_chart.js @@ -22,7 +22,7 @@ import expect from '@kbn/expect'; export default function({ getService, getPageObjects }) { const log = getService('log'); const inspector = getService('inspector'); - const PageObjects = getPageObjects(['common', 'visualize', 'timePicker']); + const PageObjects = getPageObjects(['visualize', 'visEditor', 'visChart', 'timePicker']); describe('heatmap chart', function indexPatternCreation() { this.tags('smoke'); @@ -36,20 +36,20 @@ export default function({ getService, getPageObjects }) { await PageObjects.visualize.clickNewSearch(); await PageObjects.timePicker.setDefaultAbsoluteRange(); log.debug('Bucket = X-Axis'); - await PageObjects.visualize.clickBucket('X-axis'); + await PageObjects.visEditor.clickBucket('X-axis'); log.debug('Aggregation = Date Histogram'); - await PageObjects.visualize.selectAggregation('Date Histogram'); + await PageObjects.visEditor.selectAggregation('Date Histogram'); log.debug('Field = @timestamp'); - await PageObjects.visualize.selectField('@timestamp'); + await PageObjects.visEditor.selectField('@timestamp'); // leaving Interval set to Auto - await PageObjects.visualize.clickGo(); + await PageObjects.visEditor.clickGo(); }); it('should save and load', async function() { await PageObjects.visualize.saveVisualizationExpectSuccessAndBreadcrumb(vizName1); await PageObjects.visualize.loadSavedVisualization(vizName1); - await PageObjects.visualize.waitForVisualization(); + await PageObjects.visChart.waitForVisualization(); }); it('should have inspector enabled', async function() { @@ -87,16 +87,16 @@ export default function({ getService, getPageObjects }) { }); it('should show 4 color ranges as default colorNumbers param', async function() { - const legends = await PageObjects.visualize.getLegendEntries(); + const legends = await PageObjects.visChart.getLegendEntries(); const expectedLegends = ['0 - 400', '400 - 800', '800 - 1,200', '1,200 - 1,600']; expect(legends).to.eql(expectedLegends); }); it('should show 6 color ranges if changed on options', async function() { - await PageObjects.visualize.clickOptionsTab(); - await PageObjects.visualize.changeHeatmapColorNumbers(6); - await PageObjects.visualize.clickGo(); - const legends = await PageObjects.visualize.getLegendEntries(); + await PageObjects.visEditor.clickOptionsTab(); + await PageObjects.visEditor.changeHeatmapColorNumbers(6); + await PageObjects.visEditor.clickGo(); + const legends = await PageObjects.visChart.getLegendEntries(); const expectedLegends = [ '0 - 267', '267 - 534', @@ -108,23 +108,23 @@ export default function({ getService, getPageObjects }) { expect(legends).to.eql(expectedLegends); }); it('should show 6 custom color ranges', async function() { - await PageObjects.visualize.clickOptionsTab(); - await PageObjects.visualize.clickEnableCustomRanges(); - await PageObjects.visualize.clickAddRange(); - await PageObjects.visualize.clickAddRange(); - await PageObjects.visualize.clickAddRange(); - await PageObjects.visualize.clickAddRange(); - await PageObjects.visualize.clickAddRange(); - await PageObjects.visualize.clickAddRange(); - await PageObjects.visualize.clickAddRange(); + await PageObjects.visEditor.clickOptionsTab(); + await PageObjects.visEditor.clickEnableCustomRanges(); + await PageObjects.visEditor.clickAddRange(); + await PageObjects.visEditor.clickAddRange(); + await PageObjects.visEditor.clickAddRange(); + await PageObjects.visEditor.clickAddRange(); + await PageObjects.visEditor.clickAddRange(); + await PageObjects.visEditor.clickAddRange(); + await PageObjects.visEditor.clickAddRange(); log.debug('customize 2 last ranges'); - await PageObjects.visualize.setCustomRangeByIndex(6, '650', '720'); - await PageObjects.visualize.setCustomRangeByIndex(7, '800', '905'); + await PageObjects.visEditor.setCustomRangeByIndex(6, '650', '720'); + await PageObjects.visEditor.setCustomRangeByIndex(7, '800', '905'); - await PageObjects.visualize.waitForVisualizationRenderingStabilized(); - await PageObjects.visualize.clickGo(); - const legends = await PageObjects.visualize.getLegendEntries(); + await PageObjects.visChart.waitForVisualizationRenderingStabilized(); + await PageObjects.visEditor.clickGo(); + const legends = await PageObjects.visChart.getLegendEntries(); const expectedLegends = [ '0 - 100', '100 - 200', diff --git a/test/functional/apps/visualize/_histogram_request_start.js b/test/functional/apps/visualize/_histogram_request_start.js index a601e370a568f..a74fa8856a3b3 100644 --- a/test/functional/apps/visualize/_histogram_request_start.js +++ b/test/functional/apps/visualize/_histogram_request_start.js @@ -22,7 +22,13 @@ import expect from '@kbn/expect'; export default function({ getService, getPageObjects }) { const log = getService('log'); const retry = getService('retry'); - const PageObjects = getPageObjects(['common', 'visualize', 'timePicker']); + const PageObjects = getPageObjects([ + 'common', + 'visualize', + 'visEditor', + 'visChart', + 'timePicker', + ]); describe('histogram agg onSearchRequestStart', function() { before(async function() { @@ -33,21 +39,21 @@ export default function({ getService, getPageObjects }) { await PageObjects.visualize.clickNewSearch(); await PageObjects.timePicker.setDefaultAbsoluteRange(); log.debug('Bucket = Split Rows'); - await PageObjects.visualize.clickBucket('Split rows'); + await PageObjects.visEditor.clickBucket('Split rows'); log.debug('Aggregation = Histogram'); - await PageObjects.visualize.selectAggregation('Histogram'); + await PageObjects.visEditor.selectAggregation('Histogram'); log.debug('Field = machine.ram'); - await PageObjects.visualize.selectField('machine.ram'); + await PageObjects.visEditor.selectField('machine.ram'); }); describe('interval parameter uses autoBounds', function() { it('should use provided value when number of generated buckets is less than histogram:maxBars', async function() { const providedInterval = 2400000000; log.debug(`Interval = ${providedInterval}`); - await PageObjects.visualize.setNumericInterval(providedInterval); - await PageObjects.visualize.clickGo(); + await PageObjects.visEditor.setInterval(providedInterval, { type: 'numeric' }); + await PageObjects.visEditor.clickGo(); await retry.try(async () => { - const data = await PageObjects.visualize.getTableVisData(); + const data = await PageObjects.visChart.getTableVisData(); const dataArray = data.replace(/,/g, '').split('\n'); expect(dataArray.length).to.eql(20); const bucketStart = parseInt(dataArray[0], 10); @@ -60,11 +66,11 @@ export default function({ getService, getPageObjects }) { it('should scale value to round number when number of generated buckets is greater than histogram:maxBars', async function() { const providedInterval = 100; log.debug(`Interval = ${providedInterval}`); - await PageObjects.visualize.setNumericInterval(providedInterval); - await PageObjects.visualize.clickGo(); + await PageObjects.visEditor.setInterval(providedInterval, { type: 'numeric' }); + await PageObjects.visEditor.clickGo(); await PageObjects.common.sleep(1000); //fix this await retry.try(async () => { - const data = await PageObjects.visualize.getTableVisData(); + const data = await PageObjects.visChart.getTableVisData(); const dataArray = data.replace(/,/g, '').split('\n'); expect(dataArray.length).to.eql(20); const bucketStart = parseInt(dataArray[0], 10); diff --git a/test/functional/apps/visualize/_inspector.js b/test/functional/apps/visualize/_inspector.js index 76b75f62b5f2a..84f955d9c7879 100644 --- a/test/functional/apps/visualize/_inspector.js +++ b/test/functional/apps/visualize/_inspector.js @@ -21,7 +21,7 @@ export default function({ getService, getPageObjects }) { const log = getService('log'); const inspector = getService('inspector'); const filterBar = getService('filterBar'); - const PageObjects = getPageObjects(['common', 'visualize', 'timePicker']); + const PageObjects = getPageObjects(['visualize', 'visEditor', 'visChart', 'timePicker']); describe('inspector', function describeIndexTests() { this.tags('smoke'); @@ -39,10 +39,10 @@ export default function({ getService, getPageObjects }) { await inspector.expectTableHeaders(['Count']); log.debug('Add Average Metric on machine.ram field'); - await PageObjects.visualize.clickBucket('Y-axis', 'metrics'); - await PageObjects.visualize.selectAggregation('Average', 'metrics'); - await PageObjects.visualize.selectField('machine.ram', 'metrics'); - await PageObjects.visualize.clickGo(); + await PageObjects.visEditor.clickBucket('Y-axis', 'metrics'); + await PageObjects.visEditor.selectAggregation('Average', 'metrics'); + await PageObjects.visEditor.selectField('machine.ram', 'metrics'); + await PageObjects.visEditor.clickGo(); await inspector.open(); await inspector.expectTableHeaders(['Count', 'Average machine.ram']); }); @@ -50,23 +50,23 @@ export default function({ getService, getPageObjects }) { describe('filtering on inspector table values', function() { before(async function() { log.debug('Add X-axis terms agg on machine.os.raw'); - await PageObjects.visualize.clickBucket('X-axis'); - await PageObjects.visualize.selectAggregation('Terms'); - await PageObjects.visualize.selectField('machine.os.raw'); - await PageObjects.visualize.setSize(2); - await PageObjects.visualize.toggleOtherBucket(3); - await PageObjects.visualize.clickGo(); + await PageObjects.visEditor.clickBucket('X-axis'); + await PageObjects.visEditor.selectAggregation('Terms'); + await PageObjects.visEditor.selectField('machine.os.raw'); + await PageObjects.visEditor.setSize(2); + await PageObjects.visEditor.toggleOtherBucket(3); + await PageObjects.visEditor.clickGo(); }); beforeEach(async function() { await inspector.open(); - await PageObjects.visualize.waitForVisualizationRenderingStabilized(); + await PageObjects.visChart.waitForVisualizationRenderingStabilized(); }); afterEach(async function() { await inspector.close(); await filterBar.removeFilter('machine.os.raw'); - await PageObjects.visualize.waitForVisualizationRenderingStabilized(); + await PageObjects.visChart.waitForVisualizationRenderingStabilized(); }); it('should allow filtering for values', async function() { diff --git a/test/functional/apps/visualize/_line_chart.js b/test/functional/apps/visualize/_line_chart.js index 6a0a21e76924d..becf66f0fd5b1 100644 --- a/test/functional/apps/visualize/_line_chart.js +++ b/test/functional/apps/visualize/_line_chart.js @@ -24,7 +24,13 @@ export default function({ getService, getPageObjects }) { const inspector = getService('inspector'); const retry = getService('retry'); const testSubjects = getService('testSubjects'); - const PageObjects = getPageObjects(['common', 'visualize', 'timePicker']); + const PageObjects = getPageObjects([ + 'common', + 'visualize', + 'visEditor', + 'visChart', + 'timePicker', + ]); describe('line charts', function() { const vizName1 = 'Visualization LineChart'; @@ -37,14 +43,14 @@ export default function({ getService, getPageObjects }) { await PageObjects.visualize.clickNewSearch(); await PageObjects.timePicker.setDefaultAbsoluteRange(); log.debug('Bucket = Split chart'); - await PageObjects.visualize.clickBucket('Split chart'); + await PageObjects.visEditor.clickBucket('Split chart'); log.debug('Aggregation = Terms'); - await PageObjects.visualize.selectAggregation('Terms'); + await PageObjects.visEditor.selectAggregation('Terms'); log.debug('Field = extension'); - await PageObjects.visualize.selectField('extension.raw'); + await PageObjects.visEditor.selectField('extension.raw'); log.debug('switch from Rows to Columns'); - await PageObjects.visualize.clickSplitDirection('Columns'); - await PageObjects.visualize.clickGo(); + await PageObjects.visEditor.clickSplitDirection('Columns'); + await PageObjects.visEditor.clickGo(); }; before(initLineChart); @@ -60,7 +66,7 @@ export default function({ getService, getPageObjects }) { // sleep a bit before trying to get the chart data await PageObjects.common.sleep(3000); - const data = await PageObjects.visualize.getLineChartData(); + const data = await PageObjects.visChart.getLineChartData(); log.debug('data=' + data); const tolerance = 10; // the y-axis scale is 10000 so 10 is 0.1% for (let x = 0; x < data.length; x++) { @@ -91,10 +97,10 @@ export default function({ getService, getPageObjects }) { const expectedChartData = ['png 1,373', 'php 445', 'jpg 9,109', 'gif 918', 'css 2,159']; log.debug('Order By = Term'); - await PageObjects.visualize.selectOrderByMetric(2, '_key'); - await PageObjects.visualize.clickGo(); + await PageObjects.visEditor.selectOrderByMetric(2, '_key'); + await PageObjects.visEditor.clickGo(); await retry.try(async function() { - const data = await PageObjects.visualize.getLineChartData(); + const data = await PageObjects.visChart.getLineChartData(); log.debug('data=' + data); const tolerance = 10; // the y-axis scale is 10000 so 10 is 0.1% for (let x = 0; x < data.length; x++) { @@ -172,7 +178,7 @@ export default function({ getService, getPageObjects }) { await PageObjects.visualize.saveVisualizationExpectSuccessAndBreadcrumb(vizName1); await PageObjects.visualize.loadSavedVisualization(vizName1); - await PageObjects.visualize.waitForVisualization(); + await PageObjects.visChart.waitForVisualization(); }); describe.skip('switch between Y axis scale types', () => { @@ -180,13 +186,13 @@ export default function({ getService, getPageObjects }) { const axisId = 'ValueAxis-1'; it('should show ticks on selecting log scale', async () => { - await PageObjects.visualize.clickMetricsAndAxes(); - await PageObjects.visualize.clickYAxisOptions(axisId); - await PageObjects.visualize.selectYAxisScaleType(axisId, 'log'); - await PageObjects.visualize.clickYAxisAdvancedOptions(axisId); - await PageObjects.visualize.changeYAxisFilterLabelsCheckbox(axisId, false); - await PageObjects.visualize.clickGo(); - const labels = await PageObjects.visualize.getYAxisLabels(); + await PageObjects.visEditor.clickMetricsAndAxes(); + await PageObjects.visEditor.clickYAxisOptions(axisId); + await PageObjects.visEditor.selectYAxisScaleType(axisId, 'log'); + await PageObjects.visEditor.clickYAxisAdvancedOptions(axisId); + await PageObjects.visEditor.changeYAxisFilterLabelsCheckbox(axisId, false); + await PageObjects.visEditor.clickGo(); + const labels = await PageObjects.visChart.getYAxisLabels(); const expectedLabels = [ '2', '3', @@ -212,9 +218,9 @@ export default function({ getService, getPageObjects }) { }); it('should show filtered ticks on selecting log scale', async () => { - await PageObjects.visualize.changeYAxisFilterLabelsCheckbox(axisId, true); - await PageObjects.visualize.clickGo(); - const labels = await PageObjects.visualize.getYAxisLabels(); + await PageObjects.visEditor.changeYAxisFilterLabelsCheckbox(axisId, true); + await PageObjects.visEditor.clickGo(); + const labels = await PageObjects.visChart.getYAxisLabels(); const expectedLabels = [ '2', '3', @@ -240,36 +246,36 @@ export default function({ getService, getPageObjects }) { }); it('should show ticks on selecting square root scale', async () => { - await PageObjects.visualize.selectYAxisScaleType(axisId, 'square root'); - await PageObjects.visualize.changeYAxisFilterLabelsCheckbox(axisId, false); - await PageObjects.visualize.clickGo(); - const labels = await PageObjects.visualize.getYAxisLabels(); + await PageObjects.visEditor.selectYAxisScaleType(axisId, 'square root'); + await PageObjects.visEditor.changeYAxisFilterLabelsCheckbox(axisId, false); + await PageObjects.visEditor.clickGo(); + const labels = await PageObjects.visChart.getYAxisLabels(); const expectedLabels = ['0', '2,000', '4,000', '6,000', '8,000', '10,000']; expect(labels).to.eql(expectedLabels); }); it('should show filtered ticks on selecting square root scale', async () => { - await PageObjects.visualize.changeYAxisFilterLabelsCheckbox(axisId, true); - await PageObjects.visualize.clickGo(); - const labels = await PageObjects.visualize.getYAxisLabels(); + await PageObjects.visEditor.changeYAxisFilterLabelsCheckbox(axisId, true); + await PageObjects.visEditor.clickGo(); + const labels = await PageObjects.visChart.getYAxisLabels(); const expectedLabels = ['2,000', '4,000', '6,000', '8,000']; expect(labels).to.eql(expectedLabels); }); it('should show ticks on selecting linear scale', async () => { - await PageObjects.visualize.selectYAxisScaleType(axisId, 'linear'); - await PageObjects.visualize.changeYAxisFilterLabelsCheckbox(axisId, false); - await PageObjects.visualize.clickGo(); - const labels = await PageObjects.visualize.getYAxisLabels(); + await PageObjects.visEditor.selectYAxisScaleType(axisId, 'linear'); + await PageObjects.visEditor.changeYAxisFilterLabelsCheckbox(axisId, false); + await PageObjects.visEditor.clickGo(); + const labels = await PageObjects.visChart.getYAxisLabels(); log.debug(labels); const expectedLabels = ['0', '2,000', '4,000', '6,000', '8,000', '10,000']; expect(labels).to.eql(expectedLabels); }); it('should show filtered ticks on selecting linear scale', async () => { - await PageObjects.visualize.changeYAxisFilterLabelsCheckbox(axisId, true); - await PageObjects.visualize.clickGo(); - const labels = await PageObjects.visualize.getYAxisLabels(); + await PageObjects.visEditor.changeYAxisFilterLabelsCheckbox(axisId, true); + await PageObjects.visEditor.clickGo(); + const labels = await PageObjects.visChart.getYAxisLabels(); const expectedLabels = ['2,000', '4,000', '6,000', '8,000']; expect(labels).to.eql(expectedLabels); }); diff --git a/test/functional/apps/visualize/_linked_saved_searches.js b/test/functional/apps/visualize/_linked_saved_searches.js index 9bbe8c9d2147c..37ec3f06f2ecd 100644 --- a/test/functional/apps/visualize/_linked_saved_searches.js +++ b/test/functional/apps/visualize/_linked_saved_searches.js @@ -22,7 +22,14 @@ import expect from '@kbn/expect'; export default function({ getService, getPageObjects }) { const filterBar = getService('filterBar'); const retry = getService('retry'); - const PageObjects = getPageObjects(['common', 'discover', 'visualize', 'header', 'timePicker']); + const PageObjects = getPageObjects([ + 'common', + 'discover', + 'visualize', + 'header', + 'timePicker', + 'visChart', + ]); describe('visualize app', function describeIndexTests() { describe('linked saved searched', () => { @@ -43,7 +50,7 @@ export default function({ getService, getPageObjects }) { await PageObjects.visualize.clickSavedSearch(savedSearchName); await PageObjects.timePicker.setDefaultAbsoluteRange(); await retry.waitFor('wait for count to equal 9,109', async () => { - const data = await PageObjects.visualize.getTableVisData(); + const data = await PageObjects.visChart.getTableVisData(); return data.trim() === '9,109'; }); }); @@ -54,7 +61,7 @@ export default function({ getService, getPageObjects }) { 'Sep 21, 2015 @ 10:00:00.000' ); await retry.waitFor('wait for count to equal 3,950', async () => { - const data = await PageObjects.visualize.getTableVisData(); + const data = await PageObjects.visChart.getTableVisData(); return data.trim() === '3,950'; }); }); @@ -63,7 +70,7 @@ export default function({ getService, getPageObjects }) { await filterBar.addFilter('bytes', 'is between', '100', '3000'); await PageObjects.header.waitUntilLoadingHasFinished(); await retry.waitFor('wait for count to equal 707', async () => { - const data = await PageObjects.visualize.getTableVisData(); + const data = await PageObjects.visChart.getTableVisData(); return data.trim() === '707'; }); }); @@ -71,7 +78,7 @@ export default function({ getService, getPageObjects }) { it('should allow unlinking from a linked search', async () => { await PageObjects.visualize.clickUnlinkSavedSearch(); await retry.waitFor('wait for count to equal 707', async () => { - const data = await PageObjects.visualize.getTableVisData(); + const data = await PageObjects.visChart.getTableVisData(); return data.trim() === '707'; }); // The filter on the saved search should now be in the editor @@ -82,7 +89,7 @@ export default function({ getService, getPageObjects }) { await filterBar.toggleFilterEnabled('extension.raw'); await PageObjects.header.waitUntilLoadingHasFinished(); await retry.waitFor('wait for count to equal 1,293', async () => { - const unfilteredData = await PageObjects.visualize.getTableVisData(); + const unfilteredData = await PageObjects.visChart.getTableVisData(); return unfilteredData.trim() === '1,293'; }); }); @@ -91,7 +98,7 @@ export default function({ getService, getPageObjects }) { await PageObjects.visualize.saveVisualizationExpectSuccess('Unlinked before saved'); await PageObjects.header.waitUntilLoadingHasFinished(); await retry.waitFor('wait for count to equal 1,293', async () => { - const data = await PageObjects.visualize.getTableVisData(); + const data = await PageObjects.visChart.getTableVisData(); return data.trim() === '1,293'; }); }); diff --git a/test/functional/apps/visualize/_markdown_vis.js b/test/functional/apps/visualize/_markdown_vis.js index 870c465f2de37..51c03c90f507b 100644 --- a/test/functional/apps/visualize/_markdown_vis.js +++ b/test/functional/apps/visualize/_markdown_vis.js @@ -20,7 +20,7 @@ import expect from '@kbn/expect'; export default function({ getPageObjects, getService }) { - const PageObjects = getPageObjects(['common', 'visualize', 'header']); + const PageObjects = getPageObjects(['visualize', 'visEditor', 'visChart', 'header']); const find = getService('find'); const inspector = getService('inspector'); const markdown = ` @@ -33,8 +33,8 @@ export default function({ getPageObjects, getService }) { before(async function() { await PageObjects.visualize.navigateToNewVisualization(); await PageObjects.visualize.clickMarkdownWidget(); - await PageObjects.visualize.setMarkdownTxt(markdown); - await PageObjects.visualize.clickGo(); + await PageObjects.visEditor.setMarkdownTxt(markdown); + await PageObjects.visEditor.clickGo(); }); describe('markdown vis', () => { @@ -43,29 +43,29 @@ export default function({ getPageObjects, getService }) { }); it('should render markdown as html', async function() { - const h1Txt = await PageObjects.visualize.getMarkdownBodyDescendentText('h1'); + const h1Txt = await PageObjects.visChart.getMarkdownBodyDescendentText('h1'); expect(h1Txt).to.equal('Heading 1'); }); it('should not render html in markdown as html', async function() { const expected = 'Heading 1\n

Inline HTML that should not be rendered as html

'; - const actual = await PageObjects.visualize.getMarkdownText(); + const actual = await PageObjects.visChart.getMarkdownText(); expect(actual).to.equal(expected); }); it('should auto apply changes if auto mode is turned on', async function() { const markdown2 = '## Heading 2'; - await PageObjects.visualize.toggleAutoMode(); - await PageObjects.visualize.setMarkdownTxt(markdown2); + await PageObjects.visEditor.toggleAutoMode(); + await PageObjects.visEditor.setMarkdownTxt(markdown2); await PageObjects.header.waitUntilLoadingHasFinished(); - const h1Txt = await PageObjects.visualize.getMarkdownBodyDescendentText('h2'); + const h1Txt = await PageObjects.visChart.getMarkdownBodyDescendentText('h2'); expect(h1Txt).to.equal('Heading 2'); }); it('should resize the editor', async function() { const editorSidebar = await find.byCssSelector('.visEditor__sidebar'); const initialSize = await editorSidebar.getSize(); - await PageObjects.visualize.sizeUpEditor(); + await PageObjects.visEditor.sizeUpEditor(); const afterSize = await editorSidebar.getSize(); expect(afterSize.width).to.be.greaterThan(initialSize.width); }); diff --git a/test/functional/apps/visualize/_metric_chart.js b/test/functional/apps/visualize/_metric_chart.js index 31140a1718cfe..6a95f7553943c 100644 --- a/test/functional/apps/visualize/_metric_chart.js +++ b/test/functional/apps/visualize/_metric_chart.js @@ -24,7 +24,7 @@ export default function({ getService, getPageObjects }) { const retry = getService('retry'); const filterBar = getService('filterBar'); const inspector = getService('inspector'); - const PageObjects = getPageObjects(['common', 'visualize', 'timePicker']); + const PageObjects = getPageObjects(['visualize', 'visEditor', 'visChart', 'timePicker']); describe('metric chart', function() { before(async function() { @@ -45,21 +45,21 @@ export default function({ getService, getPageObjects }) { // initial metric of "Count" is selected by default await retry.try(async function tryingForTime() { - const metricValue = await PageObjects.visualize.getMetric(); + const metricValue = await PageObjects.visChart.getMetric(); expect(expectedCount).to.eql(metricValue); }); }); it('should show Average', async function() { const avgMachineRam = ['13,104,036,080.615', 'Average machine.ram']; - await PageObjects.visualize.clickMetricEditor(); + await PageObjects.visEditor.clickMetricEditor(); log.debug('Aggregation = Average'); - await PageObjects.visualize.selectAggregation('Average', 'metrics'); + await PageObjects.visEditor.selectAggregation('Average', 'metrics'); log.debug('Field = machine.ram'); - await PageObjects.visualize.selectField('machine.ram', 'metrics'); - await PageObjects.visualize.clickGo(); + await PageObjects.visEditor.selectField('machine.ram', 'metrics'); + await PageObjects.visEditor.clickGo(); await retry.try(async function tryingForTime() { - const metricValue = await PageObjects.visualize.getMetric(); + const metricValue = await PageObjects.visChart.getMetric(); expect(avgMachineRam).to.eql(metricValue); }); }); @@ -67,12 +67,12 @@ export default function({ getService, getPageObjects }) { it('should show Sum', async function() { const sumPhpMemory = ['85,865,880', 'Sum of phpmemory']; log.debug('Aggregation = Sum'); - await PageObjects.visualize.selectAggregation('Sum', 'metrics'); + await PageObjects.visEditor.selectAggregation('Sum', 'metrics'); log.debug('Field = phpmemory'); - await PageObjects.visualize.selectField('phpmemory', 'metrics'); - await PageObjects.visualize.clickGo(); + await PageObjects.visEditor.selectField('phpmemory', 'metrics'); + await PageObjects.visEditor.clickGo(); await retry.try(async function tryingForTime() { - const metricValue = await PageObjects.visualize.getMetric(); + const metricValue = await PageObjects.visChart.getMetric(); expect(sumPhpMemory).to.eql(metricValue); }); }); @@ -81,12 +81,12 @@ export default function({ getService, getPageObjects }) { const medianBytes = ['5,565.263', '50th percentile of bytes']; // For now, only comparing the text label part of the metric log.debug('Aggregation = Median'); - await PageObjects.visualize.selectAggregation('Median', 'metrics'); + await PageObjects.visEditor.selectAggregation('Median', 'metrics'); log.debug('Field = bytes'); - await PageObjects.visualize.selectField('bytes', 'metrics'); - await PageObjects.visualize.clickGo(); + await PageObjects.visEditor.selectField('bytes', 'metrics'); + await PageObjects.visEditor.clickGo(); await retry.try(async function tryingForTime() { - const metricValue = await PageObjects.visualize.getMetric(); + const metricValue = await PageObjects.visChart.getMetric(); // only comparing the text label! expect(medianBytes[1]).to.eql(metricValue[1]); }); @@ -95,12 +95,12 @@ export default function({ getService, getPageObjects }) { it('should show Min', async function() { const minTimestamp = ['Sep 20, 2015 @ 00:00:00.000', 'Min @timestamp']; log.debug('Aggregation = Min'); - await PageObjects.visualize.selectAggregation('Min', 'metrics'); + await PageObjects.visEditor.selectAggregation('Min', 'metrics'); log.debug('Field = @timestamp'); - await PageObjects.visualize.selectField('@timestamp', 'metrics'); - await PageObjects.visualize.clickGo(); + await PageObjects.visEditor.selectField('@timestamp', 'metrics'); + await PageObjects.visEditor.clickGo(); await retry.try(async function tryingForTime() { - const metricValue = await PageObjects.visualize.getMetric(); + const metricValue = await PageObjects.visChart.getMetric(); expect(minTimestamp).to.eql(metricValue); }); }); @@ -111,12 +111,12 @@ export default function({ getService, getPageObjects }) { 'Max relatedContent.article:modified_time', ]; log.debug('Aggregation = Max'); - await PageObjects.visualize.selectAggregation('Max', 'metrics'); + await PageObjects.visEditor.selectAggregation('Max', 'metrics'); log.debug('Field = relatedContent.article:modified_time'); - await PageObjects.visualize.selectField('relatedContent.article:modified_time', 'metrics'); - await PageObjects.visualize.clickGo(); + await PageObjects.visEditor.selectField('relatedContent.article:modified_time', 'metrics'); + await PageObjects.visEditor.clickGo(); await retry.try(async function tryingForTime() { - const metricValue = await PageObjects.visualize.getMetric(); + const metricValue = await PageObjects.visChart.getMetric(); expect(maxRelatedContentArticleModifiedTime).to.eql(metricValue); }); }); @@ -124,12 +124,12 @@ export default function({ getService, getPageObjects }) { it('should show Unique Count', async function() { const uniqueCountClientip = ['1,000', 'Unique count of clientip']; log.debug('Aggregation = Unique Count'); - await PageObjects.visualize.selectAggregation('Unique Count', 'metrics'); + await PageObjects.visEditor.selectAggregation('Unique Count', 'metrics'); log.debug('Field = clientip'); - await PageObjects.visualize.selectField('clientip', 'metrics'); - await PageObjects.visualize.clickGo(); + await PageObjects.visEditor.selectField('clientip', 'metrics'); + await PageObjects.visEditor.clickGo(); await retry.try(async function tryingForTime() { - const metricValue = await PageObjects.visualize.getMetric(); + const metricValue = await PageObjects.visChart.getMetric(); expect(uniqueCountClientip).to.eql(metricValue); }); }); @@ -153,12 +153,12 @@ export default function({ getService, getPageObjects }) { ]; log.debug('Aggregation = Percentiles'); - await PageObjects.visualize.selectAggregation('Percentiles', 'metrics'); + await PageObjects.visEditor.selectAggregation('Percentiles', 'metrics'); log.debug('Field = machine.ram'); - await PageObjects.visualize.selectField('machine.ram', 'metrics'); - await PageObjects.visualize.clickGo(); + await PageObjects.visEditor.selectField('machine.ram', 'metrics'); + await PageObjects.visEditor.clickGo(); await retry.try(async function tryingForTime() { - const metricValue = await PageObjects.visualize.getMetric(); + const metricValue = await PageObjects.visChart.getMetric(); expect(percentileMachineRam).to.eql(metricValue); }); }); @@ -166,14 +166,14 @@ export default function({ getService, getPageObjects }) { it('should show Percentile Ranks', async function() { const percentileRankBytes = ['2.036%', 'Percentile rank 99 of "memory"']; log.debug('Aggregation = Percentile Ranks'); - await PageObjects.visualize.selectAggregation('Percentile Ranks', 'metrics'); + await PageObjects.visEditor.selectAggregation('Percentile Ranks', 'metrics'); log.debug('Field = bytes'); - await PageObjects.visualize.selectField('memory', 'metrics'); + await PageObjects.visEditor.selectField('memory', 'metrics'); log.debug('Values = 99'); - await PageObjects.visualize.setValue('99'); - await PageObjects.visualize.clickGo(); + await PageObjects.visEditor.setValue('99'); + await PageObjects.visEditor.clickGo(); await retry.try(async function tryingForTime() { - const metricValue = await PageObjects.visualize.getMetric(); + const metricValue = await PageObjects.visChart.getMetric(); expect(percentileRankBytes).to.eql(metricValue); }); }); @@ -183,7 +183,7 @@ export default function({ getService, getPageObjects }) { let filterCount = 0; await retry.try(async function tryingForTime() { // click first metric bucket - await PageObjects.visualize.clickMetricByIndex(0); + await PageObjects.visEditor.clickMetricByIndex(0); filterCount = await filterBar.getFilterCount(); }); expect(filterCount).to.equal(0); @@ -191,17 +191,17 @@ export default function({ getService, getPageObjects }) { it('should allow filtering with buckets', async function() { log.debug('Bucket = Split Group'); - await PageObjects.visualize.clickBucket('Split group'); + await PageObjects.visEditor.clickBucket('Split group'); log.debug('Aggregation = Terms'); - await PageObjects.visualize.selectAggregation('Terms'); + await PageObjects.visEditor.selectAggregation('Terms'); log.debug('Field = machine.os.raw'); - await PageObjects.visualize.selectField('machine.os.raw'); - await PageObjects.visualize.clickGo(); + await PageObjects.visEditor.selectField('machine.os.raw'); + await PageObjects.visEditor.clickGo(); let filterCount = 0; await retry.try(async function tryingForTime() { // click first metric bucket - await PageObjects.visualize.clickMetricByIndex(0); + await PageObjects.visEditor.clickMetricByIndex(0); filterCount = await filterBar.getFilterCount(); }); await filterBar.removeAllFilters(); diff --git a/test/functional/apps/visualize/_pie_chart.js b/test/functional/apps/visualize/_pie_chart.js index 03067bb2182c5..313a4e39e5030 100644 --- a/test/functional/apps/visualize/_pie_chart.js +++ b/test/functional/apps/visualize/_pie_chart.js @@ -24,7 +24,14 @@ export default function({ getService, getPageObjects }) { const filterBar = getService('filterBar'); const pieChart = getService('pieChart'); const inspector = getService('inspector'); - const PageObjects = getPageObjects(['common', 'visualize', 'header', 'settings', 'timePicker']); + const PageObjects = getPageObjects([ + 'common', + 'visualize', + 'visEditor', + 'visChart', + 'header', + 'timePicker', + ]); describe('pie chart', function() { const vizName1 = 'Visualization PieChart'; @@ -36,24 +43,24 @@ export default function({ getService, getPageObjects }) { await PageObjects.visualize.clickNewSearch(); await PageObjects.timePicker.setDefaultAbsoluteRange(); log.debug('select bucket Split slices'); - await PageObjects.visualize.clickBucket('Split slices'); + await PageObjects.visEditor.clickBucket('Split slices'); log.debug('Click aggregation Histogram'); - await PageObjects.visualize.selectAggregation('Histogram'); + await PageObjects.visEditor.selectAggregation('Histogram'); log.debug('Click field memory'); - await PageObjects.visualize.selectField('memory'); + await PageObjects.visEditor.selectField('memory'); await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.common.sleep(1003); log.debug('setNumericInterval 4000'); - await PageObjects.visualize.setNumericInterval('40000'); + await PageObjects.visEditor.setInterval('40000', { type: 'numeric' }); log.debug('clickGo'); - await PageObjects.visualize.clickGo(); + await PageObjects.visEditor.clickGo(); }); it('should save and load', async function() { await PageObjects.visualize.saveVisualizationExpectSuccessAndBreadcrumb(vizName1); await PageObjects.visualize.loadSavedVisualization(vizName1); - await PageObjects.visualize.waitForVisualization(); + await PageObjects.visChart.waitForVisualization(); }); it('should have inspector enabled', async function() { @@ -93,15 +100,15 @@ export default function({ getService, getPageObjects }) { await PageObjects.visualize.clickNewSearch(); await PageObjects.timePicker.setDefaultAbsoluteRange(); log.debug('select bucket Split slices'); - await PageObjects.visualize.clickBucket('Split slices'); + await PageObjects.visEditor.clickBucket('Split slices'); log.debug('Click aggregation Terms'); - await PageObjects.visualize.selectAggregation('Terms'); + await PageObjects.visEditor.selectAggregation('Terms'); log.debug('Click field machine.os.raw'); - await PageObjects.visualize.selectField('machine.os.raw'); - await PageObjects.visualize.toggleOtherBucket(2); - await PageObjects.visualize.toggleMissingBucket(2); + await PageObjects.visEditor.selectField('machine.os.raw'); + await PageObjects.visEditor.toggleOtherBucket(2); + await PageObjects.visEditor.toggleMissingBucket(2); log.debug('clickGo'); - await PageObjects.visualize.clickGo(); + await PageObjects.visEditor.clickGo(); await pieChart.expectPieChartLabels(expectedTableData); }); @@ -109,20 +116,20 @@ export default function({ getService, getPageObjects }) { const expectedTableData = ['Missing', 'osx']; await pieChart.filterOnPieSlice('Other'); - await PageObjects.visualize.waitForVisualization(); + await PageObjects.visChart.waitForVisualization(); await pieChart.expectPieChartLabels(expectedTableData); await filterBar.removeFilter('machine.os.raw'); - await PageObjects.visualize.waitForVisualization(); + await PageObjects.visChart.waitForVisualization(); }); it('should apply correct filter on other bucket by clicking on a legend', async () => { const expectedTableData = ['Missing', 'osx']; - await PageObjects.visualize.filterLegend('Other'); - await PageObjects.visualize.waitForVisualization(); + await PageObjects.visChart.filterLegend('Other'); + await PageObjects.visChart.waitForVisualization(); await pieChart.expectPieChartLabels(expectedTableData); await filterBar.removeFilter('machine.os.raw'); - await PageObjects.visualize.waitForVisualization(); + await PageObjects.visChart.waitForVisualization(); }); it('should show two levels of other buckets', async () => { @@ -171,15 +178,15 @@ export default function({ getService, getPageObjects }) { 'Other', ]; - await PageObjects.visualize.toggleOpenEditor(2, 'false'); - await PageObjects.visualize.clickBucket('Split slices'); - await PageObjects.visualize.selectAggregation('Terms'); + await PageObjects.visEditor.toggleOpenEditor(2, 'false'); + await PageObjects.visEditor.clickBucket('Split slices'); + await PageObjects.visEditor.selectAggregation('Terms'); log.debug('Click field geo.src'); - await PageObjects.visualize.selectField('geo.src'); - await PageObjects.visualize.toggleOtherBucket(3); - await PageObjects.visualize.toggleMissingBucket(3); + await PageObjects.visEditor.selectField('geo.src'); + await PageObjects.visEditor.toggleOtherBucket(3); + await PageObjects.visEditor.toggleMissingBucket(3); log.debug('clickGo'); - await PageObjects.visualize.clickGo(); + await PageObjects.visEditor.clickGo(); await pieChart.expectPieChartLabels(expectedTableData); }); }); @@ -187,17 +194,17 @@ export default function({ getService, getPageObjects }) { describe('disabled aggs', () => { before(async () => { await PageObjects.visualize.loadSavedVisualization(vizName1); - await PageObjects.visualize.waitForRenderingCount(); + await PageObjects.visChart.waitForRenderingCount(); }); it('should show correct result with one agg disabled', async () => { const expectedTableData = ['win 8', 'win xp', 'win 7', 'ios', 'osx']; - await PageObjects.visualize.clickBucket('Split slices'); - await PageObjects.visualize.selectAggregation('Terms'); - await PageObjects.visualize.selectField('machine.os.raw'); - await PageObjects.visualize.toggleDisabledAgg(2); - await PageObjects.visualize.clickGo(); + await PageObjects.visEditor.clickBucket('Split slices'); + await PageObjects.visEditor.selectAggregation('Terms'); + await PageObjects.visEditor.selectField('machine.os.raw'); + await PageObjects.visEditor.toggleDisabledAgg(2); + await PageObjects.visEditor.clickGo(); await pieChart.expectPieChartLabels(expectedTableData); }); @@ -206,15 +213,15 @@ export default function({ getService, getPageObjects }) { await PageObjects.visualize.saveVisualizationExpectSuccessAndBreadcrumb(vizName1); await PageObjects.visualize.loadSavedVisualization(vizName1); - await PageObjects.visualize.waitForRenderingCount(); + await PageObjects.visChart.waitForRenderingCount(); const expectedTableData = ['win 8', 'win xp', 'win 7', 'ios', 'osx']; await pieChart.expectPieChartLabels(expectedTableData); }); it('should show correct result when agg is re-enabled', async () => { - await PageObjects.visualize.toggleDisabledAgg(2); - await PageObjects.visualize.clickGo(); + await PageObjects.visEditor.toggleDisabledAgg(2); + await PageObjects.visEditor.clickGo(); const expectedTableData = [ '0', @@ -291,24 +298,24 @@ export default function({ getService, getPageObjects }) { await PageObjects.visualize.clickNewSearch(); await PageObjects.timePicker.setDefaultAbsoluteRange(); log.debug('select bucket Split slices'); - await PageObjects.visualize.clickBucket('Split slices'); + await PageObjects.visEditor.clickBucket('Split slices'); log.debug('Click aggregation Filters'); - await PageObjects.visualize.selectAggregation('Filters'); + await PageObjects.visEditor.selectAggregation('Filters'); log.debug('Set the 1st filter value'); - await PageObjects.visualize.setFilterAggregationValue('geo.dest:"US"'); + await PageObjects.visEditor.setFilterAggregationValue('geo.dest:"US"'); log.debug('Add new filter'); - await PageObjects.visualize.addNewFilterAggregation(); + await PageObjects.visEditor.addNewFilterAggregation(); log.debug('Set the 2nd filter value'); - await PageObjects.visualize.setFilterAggregationValue('geo.dest:"CN"', 1); - await PageObjects.visualize.clickGo(); + await PageObjects.visEditor.setFilterAggregationValue('geo.dest:"CN"', 1); + await PageObjects.visEditor.clickGo(); const emptyFromTime = 'Sep 19, 2016 @ 06:31:44.000'; const emptyToTime = 'Sep 23, 2016 @ 18:31:44.000'; log.debug( 'Switch to a different time range from "' + emptyFromTime + '" to "' + emptyToTime + '"' ); await PageObjects.timePicker.setAbsoluteRange(emptyFromTime, emptyToTime); - await PageObjects.visualize.waitForVisualization(); - await PageObjects.visualize.expectError(); + await PageObjects.visChart.waitForVisualization(); + await PageObjects.visChart.expectError(); }); }); describe('multi series slice', () => { @@ -327,22 +334,22 @@ export default function({ getService, getPageObjects }) { ); await PageObjects.timePicker.setDefaultAbsoluteRange(); log.debug('select bucket Split slices'); - await PageObjects.visualize.clickBucket('Split slices'); + await PageObjects.visEditor.clickBucket('Split slices'); log.debug('Click aggregation Histogram'); - await PageObjects.visualize.selectAggregation('Histogram'); + await PageObjects.visEditor.selectAggregation('Histogram'); log.debug('Click field memory'); - await PageObjects.visualize.selectField('memory'); + await PageObjects.visEditor.selectField('memory'); await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.common.sleep(1003); log.debug('setNumericInterval 4000'); - await PageObjects.visualize.setNumericInterval('40000'); + await PageObjects.visEditor.setInterval('40000', { type: 'numeric' }); log.debug('Toggle previous editor'); - await PageObjects.visualize.toggleAggregationEditor(2); + await PageObjects.visEditor.toggleAggregationEditor(2); log.debug('select bucket Split slices'); - await PageObjects.visualize.clickBucket('Split slices'); - await PageObjects.visualize.selectAggregation('Terms'); - await PageObjects.visualize.selectField('geo.dest'); - await PageObjects.visualize.clickGo(); + await PageObjects.visEditor.clickBucket('Split slices'); + await PageObjects.visEditor.selectAggregation('Terms'); + await PageObjects.visEditor.selectField('geo.dest'); + await PageObjects.visEditor.clickGo(); }); it('should show correct chart', async () => { @@ -428,11 +435,11 @@ export default function({ getService, getPageObjects }) { '360,000', 'CN', ]; - await PageObjects.visualize.filterLegend('CN'); - await PageObjects.visualize.waitForVisualization(); + await PageObjects.visChart.filterLegend('CN'); + await PageObjects.visChart.waitForVisualization(); await pieChart.expectPieChartLabels(expectedTableData); await filterBar.removeFilter('geo.dest'); - await PageObjects.visualize.waitForVisualization(); + await PageObjects.visChart.waitForVisualization(); }); it('should still showing pie chart when a subseries have zero data', async function() { @@ -442,21 +449,21 @@ export default function({ getService, getPageObjects }) { await PageObjects.visualize.clickNewSearch(); await PageObjects.timePicker.setDefaultAbsoluteRange(); log.debug('select bucket Split slices'); - await PageObjects.visualize.clickBucket('Split slices'); + await PageObjects.visEditor.clickBucket('Split slices'); log.debug('Click aggregation Filters'); - await PageObjects.visualize.selectAggregation('Filters'); + await PageObjects.visEditor.selectAggregation('Filters'); log.debug('Set the 1st filter value'); - await PageObjects.visualize.setFilterAggregationValue('geo.dest:"US"'); + await PageObjects.visEditor.setFilterAggregationValue('geo.dest:"US"'); log.debug('Toggle previous editor'); - await PageObjects.visualize.toggleAggregationEditor(2); + await PageObjects.visEditor.toggleAggregationEditor(2); log.debug('Add a new series, select bucket Split slices'); - await PageObjects.visualize.clickBucket('Split slices'); + await PageObjects.visEditor.clickBucket('Split slices'); log.debug('Click aggregation Filters'); - await PageObjects.visualize.selectAggregation('Filters'); + await PageObjects.visEditor.selectAggregation('Filters'); log.debug('Set the 1st filter value of the aggregation id 3'); - await PageObjects.visualize.setFilterAggregationValue('geo.dest:"UX"', 0, 3); - await PageObjects.visualize.clickGo(); - const legends = await PageObjects.visualize.getLegendEntries(); + await PageObjects.visEditor.setFilterAggregationValue('geo.dest:"UX"', 0, 3); + await PageObjects.visEditor.clickGo(); + const legends = await PageObjects.visChart.getLegendEntries(); const expectedLegends = ['geo.dest:"US"', 'geo.dest:"UX"']; expect(legends).to.eql(expectedLegends); }); @@ -469,15 +476,15 @@ export default function({ getService, getPageObjects }) { await PageObjects.visualize.clickNewSearch(); await PageObjects.timePicker.setDefaultAbsoluteRange(); log.debug('select bucket Split chart'); - await PageObjects.visualize.clickBucket('Split chart'); - await PageObjects.visualize.selectAggregation('Terms'); - await PageObjects.visualize.selectField('machine.os.raw'); - await PageObjects.visualize.toggleAggregationEditor(2); + await PageObjects.visEditor.clickBucket('Split chart'); + await PageObjects.visEditor.selectAggregation('Terms'); + await PageObjects.visEditor.selectField('machine.os.raw'); + await PageObjects.visEditor.toggleAggregationEditor(2); log.debug('Add a new series, select bucket Split slices'); - await PageObjects.visualize.clickBucket('Split slices'); - await PageObjects.visualize.selectAggregation('Terms'); - await PageObjects.visualize.selectField('geo.src'); - await PageObjects.visualize.clickGo(); + await PageObjects.visEditor.clickBucket('Split slices'); + await PageObjects.visEditor.selectAggregation('Terms'); + await PageObjects.visEditor.selectField('geo.src'); + await PageObjects.visEditor.clickGo(); }); it('shows correct split chart', async () => { @@ -522,7 +529,7 @@ export default function({ getService, getPageObjects }) { ['ios', '478', 'CN', '478'], ['osx', '228', 'CN', '228'], ]; - await PageObjects.visualize.filterLegend('CN'); + await PageObjects.visChart.filterLegend('CN'); await PageObjects.header.waitUntilLoadingHasFinished(); await inspector.open(); await inspector.setTablePageSize(50); diff --git a/test/functional/apps/visualize/_point_series_options.js b/test/functional/apps/visualize/_point_series_options.js index 2d496cb575db7..e7ce5808554b4 100644 --- a/test/functional/apps/visualize/_point_series_options.js +++ b/test/functional/apps/visualize/_point_series_options.js @@ -25,11 +25,12 @@ export default function({ getService, getPageObjects }) { const kibanaServer = getService('kibanaServer'); const browser = getService('browser'); const PageObjects = getPageObjects([ - 'common', 'visualize', 'header', 'pointSeries', 'timePicker', + 'visEditor', + 'visChart', ]); const pointSeriesVis = PageObjects.pointSeries; const inspector = getService('inspector'); @@ -42,18 +43,18 @@ export default function({ getService, getPageObjects }) { await PageObjects.visualize.clickNewSearch(); await PageObjects.timePicker.setDefaultAbsoluteRange(); log.debug('Bucket = X-axis'); - await PageObjects.visualize.clickBucket('X-axis'); + await PageObjects.visEditor.clickBucket('X-axis'); log.debug('Aggregation = Date Histogram'); - await PageObjects.visualize.selectAggregation('Date Histogram'); + await PageObjects.visEditor.selectAggregation('Date Histogram'); log.debug('Field = @timestamp'); - await PageObjects.visualize.selectField('@timestamp'); + await PageObjects.visEditor.selectField('@timestamp'); // add another metrics log.debug('Metric = Value Axis'); - await PageObjects.visualize.clickBucket('Y-axis', 'metrics'); + await PageObjects.visEditor.clickBucket('Y-axis', 'metrics'); log.debug('Aggregation = Average'); - await PageObjects.visualize.selectAggregation('Average', 'metrics'); + await PageObjects.visEditor.selectAggregation('Average', 'metrics'); log.debug('Field = memory'); - await PageObjects.visualize.selectField('machine.ram', 'metrics'); + await PageObjects.visEditor.selectField('machine.ram', 'metrics'); // go to options page log.debug('Going to axis options'); await pointSeriesVis.clickAxisOptions(); @@ -61,11 +62,11 @@ export default function({ getService, getPageObjects }) { log.debug('adding axis'); await pointSeriesVis.clickAddAxis(); // set average count to use second value axis - await PageObjects.visualize.toggleAccordion('visEditorSeriesAccordion3'); + await PageObjects.visEditor.toggleAccordion('visEditorSeriesAccordion3'); log.debug('Average memory value axis - ValueAxis-2'); await pointSeriesVis.setSeriesAxis(1, 'ValueAxis-2'); - await PageObjects.visualize.waitForVisualizationRenderingStabilized(); - await PageObjects.visualize.clickGo(); + await PageObjects.visChart.waitForVisualizationRenderingStabilized(); + await PageObjects.visEditor.clickGo(); } describe('point series', function describeIndexTests() { @@ -129,14 +130,14 @@ export default function({ getService, getPageObjects }) { ]; await retry.try(async () => { - const data = await PageObjects.visualize.getLineChartData('Count'); + const data = await PageObjects.visChart.getLineChartData('Count'); log.debug('count data=' + data); log.debug('data.length=' + data.length); expect(data).to.eql(expectedChartValues[0]); }); await retry.try(async () => { - const avgMemoryData = await PageObjects.visualize.getLineChartData( + const avgMemoryData = await PageObjects.visChart.getLineChartData( 'Average machine.ram', 'ValueAxis-2' ); @@ -158,7 +159,7 @@ export default function({ getService, getPageObjects }) { describe('multiple chart types', function() { it('should change average series type to histogram', async function() { await pointSeriesVis.setSeriesType(1, 'histogram'); - await PageObjects.visualize.clickGo(); + await PageObjects.visEditor.clickGo(); const length = await pointSeriesVis.getHistogramSeries(); expect(length).to.be(1); }); @@ -166,12 +167,12 @@ export default function({ getService, getPageObjects }) { describe('grid lines', function() { before(async function() { - await pointSeriesVis.clickOptions(); + await PageObjects.visEditor.clickOptionsTab(); }); it('should show category grid lines', async function() { await pointSeriesVis.toggleGridCategoryLines(); - await PageObjects.visualize.clickGo(); + await PageObjects.visEditor.clickGo(); const gridLines = await pointSeriesVis.getGridLines(); expect(gridLines.length).to.be(9); gridLines.forEach(gridLine => { @@ -182,7 +183,7 @@ export default function({ getService, getPageObjects }) { it('should show value axis grid lines', async function() { await pointSeriesVis.setGridValueAxis('ValueAxis-2'); await pointSeriesVis.toggleGridCategoryLines(); - await PageObjects.visualize.clickGo(); + await PageObjects.visEditor.clickGo(); const gridLines = await pointSeriesVis.getGridLines(); expect(gridLines.length).to.be(9); gridLines.forEach(gridLine => { @@ -199,36 +200,36 @@ export default function({ getService, getPageObjects }) { await PageObjects.visualize.navigateToNewVisualization(); await PageObjects.visualize.clickLineChart(); await PageObjects.visualize.clickNewSearch(); - await PageObjects.visualize.selectYAxisAggregation('Average', 'bytes', customLabel, 1); - await PageObjects.visualize.clickGo(); - await PageObjects.visualize.clickMetricsAndAxes(); - await PageObjects.visualize.clickYAxisOptions('ValueAxis-1'); + await PageObjects.visEditor.selectYAxisAggregation('Average', 'bytes', customLabel, 1); + await PageObjects.visEditor.clickGo(); + await PageObjects.visEditor.clickMetricsAndAxes(); + await PageObjects.visEditor.clickYAxisOptions('ValueAxis-1'); }); it('should render a custom label when one is set', async function() { - const title = await PageObjects.visualize.getYAxisTitle(); + const title = await PageObjects.visChart.getYAxisTitle(); expect(title).to.be(customLabel); }); it('should render a custom axis title when one is set, overriding the custom label', async function() { await pointSeriesVis.setAxisTitle(axisTitle); - await PageObjects.visualize.clickGo(); - const title = await PageObjects.visualize.getYAxisTitle(); + await PageObjects.visEditor.clickGo(); + const title = await PageObjects.visChart.getYAxisTitle(); expect(title).to.be(axisTitle); }); it('should preserve saved axis titles after a vis is saved and reopened', async function() { await PageObjects.visualize.saveVisualizationExpectSuccess(visName); - await PageObjects.visualize.waitForVisualization(); + await PageObjects.visChart.waitForVisualization(); await PageObjects.visualize.loadSavedVisualization(visName); - await PageObjects.visualize.waitForRenderingCount(); - await PageObjects.visualize.clickData(); - await PageObjects.visualize.toggleOpenEditor(1); - await PageObjects.visualize.setCustomLabel('test', 1); - await PageObjects.visualize.clickGo(); - await PageObjects.visualize.clickMetricsAndAxes(); - await PageObjects.visualize.clickYAxisOptions('ValueAxis-1'); - const title = await PageObjects.visualize.getYAxisTitle(); + await PageObjects.visChart.waitForRenderingCount(); + await PageObjects.visEditor.clickDataTab(); + await PageObjects.visEditor.toggleOpenEditor(1); + await PageObjects.visEditor.setCustomLabel('test', 1); + await PageObjects.visEditor.clickGo(); + await PageObjects.visEditor.clickMetricsAndAxes(); + await PageObjects.visEditor.clickYAxisOptions('ValueAxis-1'); + const title = await PageObjects.visChart.getYAxisTitle(); expect(title).to.be(axisTitle); }); }); @@ -238,7 +239,7 @@ export default function({ getService, getPageObjects }) { it('should show round labels in default timezone', async function() { await initChart(); - const labels = await PageObjects.visualize.getXAxisLabels(); + const labels = await PageObjects.visChart.getXAxisLabels(); expect(labels.join()).to.contain(expectedLabels.join()); }); @@ -248,7 +249,7 @@ export default function({ getService, getPageObjects }) { await PageObjects.header.awaitKibanaChrome(); await initChart(); - const labels = await PageObjects.visualize.getXAxisLabels(); + const labels = await PageObjects.visChart.getXAxisLabels(); expect(labels.join()).to.contain(expectedLabels.join()); }); @@ -258,10 +259,10 @@ export default function({ getService, getPageObjects }) { const toTime = 'Sep 22, 2015 @ 16:08:34.554'; // note that we're setting the absolute time range while we're in 'America/Phoenix' tz await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime); - await PageObjects.visualize.waitForRenderingCount(); + await PageObjects.visChart.waitForRenderingCount(); await retry.waitFor('wait for x-axis labels to match expected for Phoenix', async () => { - const labels = await PageObjects.visualize.getXAxisLabels(); + const labels = await PageObjects.visChart.getXAxisLabels(); log.debug(`Labels: ${labels}`); return ( labels.toString() === ['10:00', '11:00', '12:00', '13:00', '14:00', '15:00'].toString() @@ -303,11 +304,11 @@ export default function({ getService, getPageObjects }) { await browser.refresh(); // wait some time before trying to check for rendering count await PageObjects.header.awaitKibanaChrome(); - await PageObjects.visualize.waitForRenderingCount(); + await PageObjects.visChart.waitForRenderingCount(); log.debug('getXAxisLabels'); await retry.waitFor('wait for x-axis labels to match expected for UTC', async () => { - const labels2 = await PageObjects.visualize.getXAxisLabels(); + const labels2 = await PageObjects.visChart.getXAxisLabels(); log.debug(`Labels: ${labels2}`); return ( labels2.toString() === ['17:00', '18:00', '19:00', '20:00', '21:00', '22:00'].toString() diff --git a/test/functional/apps/visualize/_region_map.js b/test/functional/apps/visualize/_region_map.js index f6c81dab5921f..10cbd9913c70c 100644 --- a/test/functional/apps/visualize/_region_map.js +++ b/test/functional/apps/visualize/_region_map.js @@ -24,7 +24,7 @@ export default function({ getService, getPageObjects }) { const inspector = getService('inspector'); const log = getService('log'); const find = getService('find'); - const PageObjects = getPageObjects(['common', 'visualize', 'timePicker', 'settings']); + const PageObjects = getPageObjects(['visualize', 'visEditor', 'timePicker']); before(async function() { log.debug('navigateToApp visualize'); @@ -34,12 +34,12 @@ export default function({ getService, getPageObjects }) { await PageObjects.visualize.clickNewSearch(); await PageObjects.timePicker.setDefaultAbsoluteRange(); log.debug('Bucket = Shape field'); - await PageObjects.visualize.clickBucket('Shape field'); + await PageObjects.visEditor.clickBucket('Shape field'); log.debug('Aggregation = Terms'); - await PageObjects.visualize.selectAggregation('Terms'); + await PageObjects.visEditor.selectAggregation('Terms'); log.debug('Field = geo.src'); - await PageObjects.visualize.selectField('geo.src'); - await PageObjects.visualize.clickGo(); + await PageObjects.visEditor.selectField('geo.src'); + await PageObjects.visEditor.clickGo(); }); describe('vector map', function indexPatternCreation() { @@ -60,26 +60,26 @@ export default function({ getService, getPageObjects }) { }); it('should change results after changing layer to world', async function() { - await PageObjects.visualize.clickOptions(); - await PageObjects.visualize.setSelectByOptionText( + await PageObjects.visEditor.clickOptionsTab(); + await PageObjects.visEditor.setSelectByOptionText( 'regionMapOptionsSelectLayer', 'World Countries' ); //ensure all fields are there - await PageObjects.visualize.setSelectByOptionText( + await PageObjects.visEditor.setSelectByOptionText( 'regionMapOptionsSelectJoinField', 'ISO 3166-1 alpha-2 code' ); - await PageObjects.visualize.setSelectByOptionText( + await PageObjects.visEditor.setSelectByOptionText( 'regionMapOptionsSelectJoinField', 'ISO 3166-1 alpha-3 code' ); - await PageObjects.visualize.setSelectByOptionText( + await PageObjects.visEditor.setSelectByOptionText( 'regionMapOptionsSelectJoinField', 'name' ); - await PageObjects.visualize.setSelectByOptionText( + await PageObjects.visEditor.setSelectByOptionText( 'regionMapOptionsSelectJoinField', 'ISO 3166-1 alpha-2 code' ); diff --git a/test/functional/apps/visualize/_tag_cloud.js b/test/functional/apps/visualize/_tag_cloud.js index 97b8036b30503..4f921cec1fdf1 100644 --- a/test/functional/apps/visualize/_tag_cloud.js +++ b/test/functional/apps/visualize/_tag_cloud.js @@ -26,7 +26,16 @@ export default function({ getService, getPageObjects }) { const browser = getService('browser'); const retry = getService('retry'); const find = getService('find'); - const PageObjects = getPageObjects(['common', 'visualize', 'header', 'settings', 'timePicker']); + const PageObjects = getPageObjects([ + 'common', + 'visualize', + 'visEditor', + 'visChart', + 'header', + 'settings', + 'timePicker', + 'tagCloud', + ]); describe('tag cloud chart', function() { const vizName1 = 'Visualization tagCloud'; @@ -40,15 +49,15 @@ export default function({ getService, getPageObjects }) { await PageObjects.visualize.clickNewSearch(); await PageObjects.timePicker.setDefaultAbsoluteRange(); log.debug('select Tags'); - await PageObjects.visualize.clickBucket('Tags'); + await PageObjects.visEditor.clickBucket('Tags'); log.debug('Click aggregation Terms'); - await PageObjects.visualize.selectAggregation('Terms'); + await PageObjects.visEditor.selectAggregation('Terms'); log.debug('Click field machine.ram'); await retry.try(async function tryingForTime() { - await PageObjects.visualize.selectField(termsField); + await PageObjects.visEditor.selectField(termsField); }); - await PageObjects.visualize.selectOrderByMetric(2, '_key'); - await PageObjects.visualize.clickGo(); + await PageObjects.visEditor.selectOrderByMetric(2, '_key'); + await PageObjects.visEditor.clickGo(); }); it('should have inspector enabled', async function() { @@ -56,7 +65,7 @@ export default function({ getService, getPageObjects }) { }); it('should show correct tag cloud data', async function() { - const data = await PageObjects.visualize.getTextTag(); + const data = await PageObjects.tagCloud.getTextTag(); log.debug(data); expect(data).to.eql([ '32,212,254,720', @@ -69,22 +78,22 @@ export default function({ getService, getPageObjects }) { it('should collapse the sidebar', async function() { const editorSidebar = await find.byCssSelector('.collapsible-sidebar'); - await PageObjects.visualize.clickEditorSidebarCollapse(); + await PageObjects.visEditor.clickEditorSidebarCollapse(); // Give d3 tag cloud some time to rearrange tags await PageObjects.common.sleep(1000); const afterSize = await editorSidebar.getSize(); expect(afterSize.width).to.be(0); - await PageObjects.visualize.clickEditorSidebarCollapse(); + await PageObjects.visEditor.clickEditorSidebarCollapse(); }); it('should still show all tags after sidebar has been collapsed', async function() { - await PageObjects.visualize.clickEditorSidebarCollapse(); + await PageObjects.visEditor.clickEditorSidebarCollapse(); // Give d3 tag cloud some time to rearrange tags await PageObjects.common.sleep(1000); - await PageObjects.visualize.clickEditorSidebarCollapse(); + await PageObjects.visEditor.clickEditorSidebarCollapse(); // Give d3 tag cloud some time to rearrange tags await PageObjects.common.sleep(1000); - const data = await PageObjects.visualize.getTextTag(); + const data = await PageObjects.tagCloud.getTextTag(); log.debug(data); expect(data).to.eql([ '32,212,254,720', @@ -100,7 +109,7 @@ export default function({ getService, getPageObjects }) { await PageObjects.common.sleep(1000); await browser.setWindowSize(1200, 800); await PageObjects.common.sleep(1000); - const data = await PageObjects.visualize.getTextTag(); + const data = await PageObjects.tagCloud.getTextTag(); expect(data).to.eql([ '32,212,254,720', '21,474,836,480', @@ -114,11 +123,11 @@ export default function({ getService, getPageObjects }) { await PageObjects.visualize.saveVisualizationExpectSuccessAndBreadcrumb(vizName1); await PageObjects.visualize.loadSavedVisualization(vizName1); - await PageObjects.visualize.waitForVisualization(); + await PageObjects.visChart.waitForVisualization(); }); it('should show the tags and relative size', function() { - return PageObjects.visualize.getTextSizes().then(function(results) { + return PageObjects.tagCloud.getTextSizes().then(function(results) { log.debug('results here ' + results); expect(results).to.eql(['72px', '63px', '25px', '32px', '18px']); }); @@ -153,7 +162,7 @@ export default function({ getService, getPageObjects }) { }); await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.timePicker.setDefaultAbsoluteRange(); - await PageObjects.visualize.waitForVisualization(); + await PageObjects.visChart.waitForVisualization(); }); after(async function() { @@ -168,15 +177,15 @@ export default function({ getService, getPageObjects }) { }); it('should format tags with field formatter', async function() { - const data = await PageObjects.visualize.getTextTag(); + const data = await PageObjects.tagCloud.getTextTag(); log.debug(data); expect(data).to.eql(['30GB', '20GB', '19GB', '18GB', '17GB']); }); it('should apply filter with unformatted value', async function() { - await PageObjects.visualize.selectTagCloudTag('30GB'); + await PageObjects.tagCloud.selectTagCloudTag('30GB'); await PageObjects.header.waitUntilLoadingHasFinished(); - const data = await PageObjects.visualize.getTextTag(); + const data = await PageObjects.tagCloud.getTextTag(); expect(data).to.eql(['30GB']); }); }); diff --git a/test/functional/apps/visualize/_tile_map.js b/test/functional/apps/visualize/_tile_map.js index e2946339b1f08..397eaeb0f3013 100644 --- a/test/functional/apps/visualize/_tile_map.js +++ b/test/functional/apps/visualize/_tile_map.js @@ -27,7 +27,14 @@ export default function({ getService, getPageObjects }) { const filterBar = getService('filterBar'); const testSubjects = getService('testSubjects'); const browser = getService('browser'); - const PageObjects = getPageObjects(['common', 'visualize', 'timePicker', 'settings']); + const PageObjects = getPageObjects([ + 'common', + 'visualize', + 'visEditor', + 'visChart', + 'timePicker', + 'tileMap', + ]); describe('tile map visualize app', function() { describe('incomplete config', function describeIndexTests() { @@ -45,8 +52,8 @@ export default function({ getService, getPageObjects }) { it('should be able to zoom in twice', async () => { //should not throw - await PageObjects.visualize.clickMapZoomIn(); - await PageObjects.visualize.clickMapZoomIn(); + await PageObjects.tileMap.clickMapZoomIn(); + await PageObjects.tileMap.clickMapZoomIn(); }); }); @@ -61,14 +68,14 @@ export default function({ getService, getPageObjects }) { await PageObjects.visualize.clickNewSearch(); await PageObjects.timePicker.setDefaultAbsoluteRange(); log.debug('select bucket Geo Coordinates'); - await PageObjects.visualize.clickBucket('Geo coordinates'); + await PageObjects.visEditor.clickBucket('Geo coordinates'); log.debug('Click aggregation Geohash'); - await PageObjects.visualize.selectAggregation('Geohash'); + await PageObjects.visEditor.selectAggregation('Geohash'); log.debug('Click field geo.coordinates'); await retry.try(async function tryingForTime() { - await PageObjects.visualize.selectField('geo.coordinates'); + await PageObjects.visEditor.selectField('geo.coordinates'); }); - await PageObjects.visualize.clickGo(); + await PageObjects.visEditor.clickGo(); }); /** @@ -106,9 +113,9 @@ export default function({ getService, getPageObjects }) { ['-', '8', '108', { lat: 18, lon: -157 }], ]; //level 1 - await PageObjects.visualize.clickMapZoomOut(); + await PageObjects.tileMap.clickMapZoomOut(); //level 0 - await PageObjects.visualize.clickMapZoomOut(); + await PageObjects.tileMap.clickMapZoomOut(); await inspector.open(); await inspector.setTablePageSize(50); @@ -118,8 +125,8 @@ export default function({ getService, getPageObjects }) { }); it('should not be able to zoom out beyond 0', async function() { - await PageObjects.visualize.zoomAllTheWayOut(); - const enabled = await PageObjects.visualize.getMapZoomOutEnabled(); + await PageObjects.tileMap.zoomAllTheWayOut(); + const enabled = await PageObjects.tileMap.getMapZoomOutEnabled(); expect(enabled).to.be(false); }); @@ -148,7 +155,7 @@ export default function({ getService, getPageObjects }) { ['-', 'b7', '167', { lat: 64, lon: -163 }], ]; - await PageObjects.visualize.clickMapFitDataBounds(); + await PageObjects.tileMap.clickMapFitDataBounds(); await inspector.open(); const data = await inspector.getTableData(); await inspector.close(); @@ -164,8 +171,8 @@ export default function({ getService, getPageObjects }) { await filterBar.addFilter('bytes', 'is between', '19980', '19990'); await filterBar.toggleFilterPinned('bytes'); - await PageObjects.visualize.zoomAllTheWayOut(); - await PageObjects.visualize.clickMapFitDataBounds(); + await PageObjects.tileMap.zoomAllTheWayOut(); + await PageObjects.tileMap.clickMapFitDataBounds(); await inspector.open(); const data = await inspector.getTableData(); @@ -178,15 +185,15 @@ export default function({ getService, getPageObjects }) { it('Newly saved visualization retains map bounds', async () => { const vizName1 = 'Visualization TileMap'; - await PageObjects.visualize.clickMapZoomIn(); - await PageObjects.visualize.clickMapZoomIn(); + await PageObjects.tileMap.clickMapZoomIn(); + await PageObjects.tileMap.clickMapZoomIn(); - const mapBounds = await PageObjects.visualize.getMapBounds(); + const mapBounds = await PageObjects.tileMap.getMapBounds(); await inspector.close(); await PageObjects.visualize.saveVisualizationExpectSuccess(vizName1); - const afterSaveMapBounds = await PageObjects.visualize.getMapBounds(); + const afterSaveMapBounds = await PageObjects.tileMap.getMapBounds(); await inspector.close(); // For some reason the values are slightly different, so we can't check that they are equal. But we did @@ -207,17 +214,17 @@ export default function({ getService, getPageObjects }) { }); it('when not checked does not add filters to aggregation', async () => { - await PageObjects.visualize.toggleOpenEditor(2); - await PageObjects.visualize.setIsFilteredByCollarCheckbox(false); - await PageObjects.visualize.clickGo(); + await PageObjects.visEditor.toggleOpenEditor(2); + await PageObjects.visEditor.setIsFilteredByCollarCheckbox(false); + await PageObjects.visEditor.clickGo(); await inspector.open(); await inspector.expectTableHeaders(['geohash_grid', 'Count', 'Geo Centroid']); await inspector.close(); }); after(async () => { - await PageObjects.visualize.setIsFilteredByCollarCheckbox(true); - await PageObjects.visualize.clickGo(); + await PageObjects.visEditor.setIsFilteredByCollarCheckbox(true); + await PageObjects.visEditor.clickGo(); }); }); }); @@ -245,18 +252,18 @@ export default function({ getService, getPageObjects }) { const zoomLevel = 9; for (let i = 0; i < zoomLevel; i++) { - await PageObjects.visualize.clickMapZoomIn(); + await PageObjects.tileMap.clickMapZoomIn(); } }); beforeEach(async function() { - await PageObjects.visualize.clickMapZoomIn(waitForLoading); + await PageObjects.tileMap.clickMapZoomIn(waitForLoading); }); afterEach(async function() { if (!last) { await PageObjects.common.sleep(toastDefaultLife); - await PageObjects.visualize.clickMapZoomOut(waitForLoading); + await PageObjects.tileMap.clickMapZoomOut(waitForLoading); } }); @@ -270,11 +277,11 @@ export default function({ getService, getPageObjects }) { it('should suppress zoom warning if suppress warnings button clicked', async () => { last = true; - await PageObjects.visualize.waitForVisualization(); + await PageObjects.visChart.waitForVisualization(); await find.clickByCssSelector('[data-test-subj="suppressZoomWarnings"]'); - await PageObjects.visualize.clickMapZoomOut(waitForLoading); + await PageObjects.tileMap.clickMapZoomOut(waitForLoading); await testSubjects.waitForDeleted('suppressZoomWarnings'); - await PageObjects.visualize.clickMapZoomIn(waitForLoading); + await PageObjects.tileMap.clickMapZoomIn(waitForLoading); await testSubjects.missingOrFail('maxZoomWarning'); }); diff --git a/test/functional/apps/visualize/_tsvb_chart.ts b/test/functional/apps/visualize/_tsvb_chart.ts index dc4b9a786eaa4..8dbe356889568 100644 --- a/test/functional/apps/visualize/_tsvb_chart.ts +++ b/test/functional/apps/visualize/_tsvb_chart.ts @@ -25,7 +25,7 @@ export default function({ getService, getPageObjects }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const log = getService('log'); const inspector = getService('inspector'); - const PageObjects = getPageObjects(['visualize', 'visualBuilder', 'timePicker']); + const PageObjects = getPageObjects(['visualize', 'visualBuilder', 'timePicker', 'visChart']); describe('visual builder', function describeIndexTests() { this.tags('smoke'); @@ -80,7 +80,7 @@ export default function({ getService, getPageObjects }: FtrProviderContext) { }); it('should verify gauge label and count display', async () => { - await PageObjects.visualize.waitForVisualizationRenderingStabilized(); + await PageObjects.visChart.waitForVisualizationRenderingStabilized(); const labelString = await PageObjects.visualBuilder.getGaugeLabel(); expect(labelString).to.be('Count'); const gaugeCount = await PageObjects.visualBuilder.getGaugeCount(); @@ -96,7 +96,7 @@ export default function({ getService, getPageObjects }: FtrProviderContext) { }); it('should verify topN label and count display', async () => { - await PageObjects.visualize.waitForVisualizationRenderingStabilized(); + await PageObjects.visChart.waitForVisualizationRenderingStabilized(); const labelString = await PageObjects.visualBuilder.getTopNLabel(); expect(labelString).to.be('Count'); const gaugeCount = await PageObjects.visualBuilder.getTopNCount(); diff --git a/test/functional/apps/visualize/_tsvb_table.ts b/test/functional/apps/visualize/_tsvb_table.ts index a36b6facb3039..5808212559b18 100644 --- a/test/functional/apps/visualize/_tsvb_table.ts +++ b/test/functional/apps/visualize/_tsvb_table.ts @@ -21,7 +21,11 @@ import expect from '@kbn/expect'; import { FtrProviderContext } from '../../ftr_provider_context'; export default function({ getPageObjects }: FtrProviderContext) { - const { visualBuilder, visualize } = getPageObjects(['visualBuilder', 'visualize']); + const { visualBuilder, visualize, visChart } = getPageObjects([ + 'visualBuilder', + 'visualize', + 'visChart', + ]); describe('visual builder', function describeIndexTests() { describe('table', () => { @@ -32,7 +36,7 @@ export default function({ getPageObjects }: FtrProviderContext) { await visualBuilder.checkTableTabIsPresent(); await visualBuilder.selectGroupByField('machine.os.raw'); await visualBuilder.setColumnLabelValue('OS'); - await visualize.waitForVisualizationRenderingStabilized(); + await visChart.waitForVisualizationRenderingStabilized(); }); it('should display correct values on changing group by field and column name', async () => { diff --git a/test/functional/apps/visualize/_vega_chart.js b/test/functional/apps/visualize/_vega_chart.js index 224dec7ef2a71..df0603c7f95f5 100644 --- a/test/functional/apps/visualize/_vega_chart.js +++ b/test/functional/apps/visualize/_vega_chart.js @@ -20,7 +20,7 @@ import expect from '@kbn/expect'; export default function({ getService, getPageObjects }) { - const PageObjects = getPageObjects(['common', 'header', 'timePicker', 'visualize']); + const PageObjects = getPageObjects(['timePicker', 'visualize', 'visChart', 'vegaChart']); const filterBar = getService('filterBar'); const inspector = getService('inspector'); const log = getService('log'); @@ -40,7 +40,7 @@ export default function({ getService, getPageObjects }) { }); it.skip('should have some initial vega spec text', async function() { - const vegaSpec = await PageObjects.visualize.getVegaSpec(); + const vegaSpec = await PageObjects.vegaChart.getSpec(); expect(vegaSpec) .to.contain('{') .and.to.contain('data'); @@ -48,7 +48,7 @@ export default function({ getService, getPageObjects }) { }); it('should have view and control containers', async function() { - const view = await PageObjects.visualize.getVegaViewContainer(); + const view = await PageObjects.vegaChart.getViewContainer(); expect(view).to.be.ok(); const size = await view.getSize(); expect(size) @@ -57,7 +57,7 @@ export default function({ getService, getPageObjects }) { expect(size.width).to.be.above(0); expect(size.height).to.be.above(0); - const controls = await PageObjects.visualize.getVegaControlContainer(); + const controls = await PageObjects.vegaChart.getControlContainer(); expect(controls).to.be.ok(); }); }); @@ -73,9 +73,9 @@ export default function({ getService, getPageObjects }) { }); it.skip('should render different data in response to filter change', async function() { - await PageObjects.visualize.expectVisToMatchScreenshot('vega_chart'); + await PageObjects.vegaChart.expectVisToMatchScreenshot('vega_chart'); await filterBar.addFilter('@tags.raw', 'is', 'error'); - await PageObjects.visualize.expectVisToMatchScreenshot('vega_chart_filtered'); + await PageObjects.vegaChart.expectVisToMatchScreenshot('vega_chart_filtered'); }); }); }); diff --git a/test/functional/apps/visualize/_vertical_bar_chart.js b/test/functional/apps/visualize/_vertical_bar_chart.js index 1153cc12e23ed..2efa812c7a734 100644 --- a/test/functional/apps/visualize/_vertical_bar_chart.js +++ b/test/functional/apps/visualize/_vertical_bar_chart.js @@ -23,8 +23,9 @@ export default function({ getService, getPageObjects }) { const log = getService('log'); const retry = getService('retry'); const inspector = getService('inspector'); + const testSubjects = getService('testSubjects'); const filterBar = getService('filterBar'); - const PageObjects = getPageObjects(['common', 'visualize', 'header', 'timePicker']); + const PageObjects = getPageObjects(['visualize', 'visEditor', 'visChart', 'timePicker']); // FLAKY: https://github.com/elastic/kibana/issues/22322 describe('vertical bar chart', function() { @@ -38,13 +39,13 @@ export default function({ getService, getPageObjects }) { await PageObjects.visualize.clickNewSearch(); await PageObjects.timePicker.setDefaultAbsoluteRange(); log.debug('Bucket = X-Axis'); - await PageObjects.visualize.clickBucket('X-axis'); + await PageObjects.visEditor.clickBucket('X-axis'); log.debug('Aggregation = Date Histogram'); - await PageObjects.visualize.selectAggregation('Date Histogram'); + await PageObjects.visEditor.selectAggregation('Date Histogram'); log.debug('Field = @timestamp'); - await PageObjects.visualize.selectField('@timestamp'); + await PageObjects.visEditor.selectField('@timestamp'); // leaving Interval set to Auto - await PageObjects.visualize.clickGo(); + await PageObjects.visEditor.clickGo(); }; before(initBarChart); @@ -53,7 +54,7 @@ export default function({ getService, getPageObjects }) { await PageObjects.visualize.saveVisualizationExpectSuccessAndBreadcrumb(vizName1); await PageObjects.visualize.loadSavedVisualization(vizName1); - await PageObjects.visualize.waitForVisualization(); + await PageObjects.visChart.waitForVisualization(); }); it('should have inspector enabled', async function() { @@ -92,7 +93,7 @@ export default function({ getService, getPageObjects }) { // return arguments[0].getAttribute(arguments[1]);","args":[{"ELEMENT":"592"},"fill"]}] arguments[0].getAttribute is not a function // try sleeping a bit before getting that data await retry.try(async () => { - const data = await PageObjects.visualize.getBarChartData(); + const data = await PageObjects.visChart.getBarChartData(); log.debug('data=' + data); log.debug('data.length=' + data.length); expect(data).to.eql(expectedChartValues); @@ -203,15 +204,15 @@ export default function({ getService, getPageObjects }) { // return arguments[0].getAttribute(arguments[1]);","args":[{"ELEMENT":"592"},"fill"]}] arguments[0].getAttribute is not a function // try sleeping a bit before getting that data await retry.try(async () => { - const data = await PageObjects.visualize.getBarChartData(); + const data = await PageObjects.visChart.getBarChartData(); log.debug('data=' + data); log.debug('data.length=' + data.length); expect(data).to.eql(expectedChartValues); }); - await PageObjects.visualize.toggleOpenEditor(2); - await PageObjects.visualize.clickDropPartialBuckets(); - await PageObjects.visualize.clickGo(); + await PageObjects.visEditor.toggleOpenEditor(2); + await PageObjects.visEditor.clickDropPartialBuckets(); + await PageObjects.visEditor.clickGo(); expectedChartValues = [ 218, @@ -279,7 +280,7 @@ export default function({ getService, getPageObjects }) { // return arguments[0].getAttribute(arguments[1]);","args":[{"ELEMENT":"592"},"fill"]}] arguments[0].getAttribute is not a function // try sleeping a bit before getting that data await retry.try(async () => { - const data = await PageObjects.visualize.getBarChartData(); + const data = await PageObjects.visChart.getBarChartData(); log.debug('data=' + data); log.debug('data.length=' + data.length); expect(data).to.eql(expectedChartValues); @@ -291,13 +292,13 @@ export default function({ getService, getPageObjects }) { const axisId = 'ValueAxis-1'; it('should show ticks on selecting log scale', async () => { - await PageObjects.visualize.clickMetricsAndAxes(); - await PageObjects.visualize.clickYAxisOptions(axisId); - await PageObjects.visualize.selectYAxisScaleType(axisId, 'log'); - await PageObjects.visualize.clickYAxisAdvancedOptions(axisId); - await PageObjects.visualize.changeYAxisFilterLabelsCheckbox(axisId, false); - await PageObjects.visualize.clickGo(); - const labels = await PageObjects.visualize.getYAxisLabels(); + await PageObjects.visEditor.clickMetricsAndAxes(); + await PageObjects.visEditor.clickYAxisOptions(axisId); + await PageObjects.visEditor.selectYAxisScaleType(axisId, 'log'); + await PageObjects.visEditor.clickYAxisAdvancedOptions(axisId); + await PageObjects.visEditor.changeYAxisFilterLabelsCheckbox(axisId, false); + await PageObjects.visEditor.clickGo(); + const labels = await PageObjects.visChart.getYAxisLabels(); const expectedLabels = [ '2', '3', @@ -323,9 +324,9 @@ export default function({ getService, getPageObjects }) { }); it('should show filtered ticks on selecting log scale', async () => { - await PageObjects.visualize.changeYAxisFilterLabelsCheckbox(axisId, true); - await PageObjects.visualize.clickGo(); - const labels = await PageObjects.visualize.getYAxisLabels(); + await PageObjects.visEditor.changeYAxisFilterLabelsCheckbox(axisId, true); + await PageObjects.visEditor.clickGo(); + const labels = await PageObjects.visChart.getYAxisLabels(); const expectedLabels = [ '2', '3', @@ -351,10 +352,10 @@ export default function({ getService, getPageObjects }) { }); it('should show ticks on selecting square root scale', async () => { - await PageObjects.visualize.selectYAxisScaleType(axisId, 'square root'); - await PageObjects.visualize.changeYAxisFilterLabelsCheckbox(axisId, false); - await PageObjects.visualize.clickGo(); - const labels = await PageObjects.visualize.getYAxisLabels(); + await PageObjects.visEditor.selectYAxisScaleType(axisId, 'square root'); + await PageObjects.visEditor.changeYAxisFilterLabelsCheckbox(axisId, false); + await PageObjects.visEditor.clickGo(); + const labels = await PageObjects.visChart.getYAxisLabels(); const expectedLabels = [ '0', '200', @@ -370,18 +371,18 @@ export default function({ getService, getPageObjects }) { }); it('should show filtered ticks on selecting square root scale', async () => { - await PageObjects.visualize.changeYAxisFilterLabelsCheckbox(axisId, true); - await PageObjects.visualize.clickGo(); - const labels = await PageObjects.visualize.getYAxisLabels(); + await PageObjects.visEditor.changeYAxisFilterLabelsCheckbox(axisId, true); + await PageObjects.visEditor.clickGo(); + const labels = await PageObjects.visChart.getYAxisLabels(); const expectedLabels = ['200', '400', '600', '800', '1,000', '1,200', '1,400']; expect(labels).to.eql(expectedLabels); }); it('should show ticks on selecting linear scale', async () => { - await PageObjects.visualize.selectYAxisScaleType(axisId, 'linear'); - await PageObjects.visualize.changeYAxisFilterLabelsCheckbox(axisId, false); - await PageObjects.visualize.clickGo(); - const labels = await PageObjects.visualize.getYAxisLabels(); + await PageObjects.visEditor.selectYAxisScaleType(axisId, 'linear'); + await PageObjects.visEditor.changeYAxisFilterLabelsCheckbox(axisId, false); + await PageObjects.visEditor.clickGo(); + const labels = await PageObjects.visChart.getYAxisLabels(); log.debug(labels); const expectedLabels = [ '0', @@ -398,9 +399,9 @@ export default function({ getService, getPageObjects }) { }); it('should show filtered ticks on selecting linear scale', async () => { - await PageObjects.visualize.changeYAxisFilterLabelsCheckbox(axisId, true); - await PageObjects.visualize.clickGo(); - const labels = await PageObjects.visualize.getYAxisLabels(); + await PageObjects.visEditor.changeYAxisFilterLabelsCheckbox(axisId, true); + await PageObjects.visEditor.clickGo(); + const labels = await PageObjects.visChart.getYAxisLabels(); const expectedLabels = ['200', '400', '600', '800', '1,000', '1,200', '1,400']; expect(labels).to.eql(expectedLabels); }); @@ -409,11 +410,11 @@ export default function({ getService, getPageObjects }) { describe('vertical bar in percent mode', async () => { it('should show ticks with percentage values', async function() { const axisId = 'ValueAxis-1'; - await PageObjects.visualize.clickMetricsAndAxes(); - await PageObjects.visualize.clickYAxisOptions(axisId); - await PageObjects.visualize.selectYAxisMode('percentage'); - await PageObjects.visualize.clickGo(); - const labels = await PageObjects.visualize.getYAxisLabels(); + await PageObjects.visEditor.clickMetricsAndAxes(); + await PageObjects.visEditor.clickYAxisOptions(axisId); + await PageObjects.visEditor.selectYAxisMode('percentage'); + await PageObjects.visEditor.clickGo(); + const labels = await PageObjects.visChart.getYAxisLabels(); expect(labels[0]).to.eql('0%'); expect(labels[labels.length - 1]).to.eql('100%'); }); @@ -423,36 +424,36 @@ export default function({ getService, getPageObjects }) { before(initBarChart); it('should show correct series', async function() { - await PageObjects.visualize.toggleOpenEditor(2, 'false'); - await PageObjects.visualize.clickBucket('Split series'); - await PageObjects.visualize.selectAggregation('Terms'); - await PageObjects.visualize.selectField('response.raw'); - await PageObjects.visualize.waitForVisualizationRenderingStabilized(); - await PageObjects.visualize.clickGo(); + await PageObjects.visEditor.toggleOpenEditor(2, 'false'); + await PageObjects.visEditor.clickBucket('Split series'); + await PageObjects.visEditor.selectAggregation('Terms'); + await PageObjects.visEditor.selectField('response.raw'); + await PageObjects.visChart.waitForVisualizationRenderingStabilized(); + await PageObjects.visEditor.clickGo(); const expectedEntries = ['200', '404', '503']; - const legendEntries = await PageObjects.visualize.getLegendEntries(); + const legendEntries = await PageObjects.visChart.getLegendEntries(); expect(legendEntries).to.eql(expectedEntries); }); it('should allow custom sorting of series', async () => { - await PageObjects.visualize.toggleOpenEditor(1, 'false'); - await PageObjects.visualize.selectCustomSortMetric(3, 'Min', 'bytes'); - await PageObjects.visualize.clickGo(); + await PageObjects.visEditor.toggleOpenEditor(1, 'false'); + await PageObjects.visEditor.selectCustomSortMetric(3, 'Min', 'bytes'); + await PageObjects.visEditor.clickGo(); const expectedEntries = ['404', '200', '503']; - const legendEntries = await PageObjects.visualize.getLegendEntries(); + const legendEntries = await PageObjects.visChart.getLegendEntries(); expect(legendEntries).to.eql(expectedEntries); }); it('should correctly filter by legend', async () => { - await PageObjects.visualize.filterLegend('200'); - await PageObjects.visualize.waitForVisualization(); - const legendEntries = await PageObjects.visualize.getLegendEntries(); + await PageObjects.visChart.filterLegend('200'); + await PageObjects.visChart.waitForVisualization(); + const legendEntries = await PageObjects.visChart.getLegendEntries(); const expectedEntries = ['200']; expect(legendEntries).to.eql(expectedEntries); await filterBar.removeFilter('response.raw'); - await PageObjects.visualize.waitForVisualization(); + await PageObjects.visChart.waitForVisualization(); }); }); @@ -460,18 +461,18 @@ export default function({ getService, getPageObjects }) { before(initBarChart); it('should show correct series', async function() { - await PageObjects.visualize.toggleOpenEditor(2, 'false'); - await PageObjects.visualize.clickBucket('Split series'); - await PageObjects.visualize.selectAggregation('Terms'); - await PageObjects.visualize.selectField('response.raw'); - await PageObjects.visualize.waitForVisualizationRenderingStabilized(); - - await PageObjects.visualize.toggleOpenEditor(3, 'false'); - await PageObjects.visualize.clickBucket('Split series'); - await PageObjects.visualize.selectAggregation('Terms'); - await PageObjects.visualize.selectField('machine.os'); - await PageObjects.visualize.waitForVisualizationRenderingStabilized(); - await PageObjects.visualize.clickGo(); + await PageObjects.visEditor.toggleOpenEditor(2, 'false'); + await PageObjects.visEditor.clickBucket('Split series'); + await PageObjects.visEditor.selectAggregation('Terms'); + await PageObjects.visEditor.selectField('response.raw'); + await PageObjects.visChart.waitForVisualizationRenderingStabilized(); + + await PageObjects.visEditor.toggleOpenEditor(3, 'false'); + await PageObjects.visEditor.clickBucket('Split series'); + await PageObjects.visEditor.selectAggregation('Terms'); + await PageObjects.visEditor.selectField('machine.os'); + await PageObjects.visChart.waitForVisualizationRenderingStabilized(); + await PageObjects.visEditor.clickGo(); const expectedEntries = [ '200 - win 8', @@ -490,18 +491,18 @@ export default function({ getService, getPageObjects }) { '404 - win 8', '404 - win xp', ]; - const legendEntries = await PageObjects.visualize.getLegendEntries(); + const legendEntries = await PageObjects.visChart.getLegendEntries(); expect(legendEntries).to.eql(expectedEntries); }); it('should show correct series when disabling first agg', async function() { // this will avoid issues with the play tooltip covering the disable agg button - await PageObjects.visualize.scrollSubjectIntoView('metricsAggGroup'); - await PageObjects.visualize.toggleDisabledAgg(3); - await PageObjects.visualize.clickGo(); + await testSubjects.scrollIntoView('metricsAggGroup'); + await PageObjects.visEditor.toggleDisabledAgg(3); + await PageObjects.visEditor.clickGo(); const expectedEntries = ['win 8', 'win xp', 'ios', 'osx', 'win 7']; - const legendEntries = await PageObjects.visualize.getLegendEntries(); + const legendEntries = await PageObjects.visChart.getLegendEntries(); expect(legendEntries).to.eql(expectedEntries); }); }); @@ -510,24 +511,24 @@ export default function({ getService, getPageObjects }) { before(initBarChart); it('should show correct series', async function() { - await PageObjects.visualize.toggleOpenEditor(2, 'false'); - await PageObjects.visualize.toggleOpenEditor(1); - await PageObjects.visualize.selectAggregation('Derivative', 'metrics'); - await PageObjects.visualize.waitForVisualizationRenderingStabilized(); - await PageObjects.visualize.clickGo(); + await PageObjects.visEditor.toggleOpenEditor(2, 'false'); + await PageObjects.visEditor.toggleOpenEditor(1); + await PageObjects.visEditor.selectAggregation('Derivative', 'metrics'); + await PageObjects.visChart.waitForVisualizationRenderingStabilized(); + await PageObjects.visEditor.clickGo(); const expectedEntries = ['Derivative of Count']; - const legendEntries = await PageObjects.visualize.getLegendEntries(); + const legendEntries = await PageObjects.visChart.getLegendEntries(); expect(legendEntries).to.eql(expectedEntries); }); it('should show an error if last bucket aggregation is terms', async () => { - await PageObjects.visualize.toggleOpenEditor(2, 'false'); - await PageObjects.visualize.clickBucket('Split series'); - await PageObjects.visualize.selectAggregation('Terms'); - await PageObjects.visualize.selectField('response.raw'); + await PageObjects.visEditor.toggleOpenEditor(2, 'false'); + await PageObjects.visEditor.clickBucket('Split series'); + await PageObjects.visEditor.selectAggregation('Terms'); + await PageObjects.visEditor.selectField('response.raw'); - const errorMessage = await PageObjects.visualize.getBucketErrorMessage(); + const errorMessage = await PageObjects.visEditor.getBucketErrorMessage(); expect(errorMessage).to.contain('Last bucket aggregation must be "Date Histogram"'); }); }); diff --git a/test/functional/apps/visualize/_vertical_bar_chart_nontimeindex.js b/test/functional/apps/visualize/_vertical_bar_chart_nontimeindex.js index e796f281b0689..2371df6e92476 100644 --- a/test/functional/apps/visualize/_vertical_bar_chart_nontimeindex.js +++ b/test/functional/apps/visualize/_vertical_bar_chart_nontimeindex.js @@ -23,7 +23,7 @@ export default function({ getService, getPageObjects }) { const log = getService('log'); const retry = getService('retry'); const inspector = getService('inspector'); - const PageObjects = getPageObjects(['common', 'visualize', 'header']); + const PageObjects = getPageObjects(['common', 'visualize', 'header', 'visEditor', 'visChart']); // FLAKY: https://github.com/elastic/kibana/issues/22322 describe.skip('vertical bar chart with index without time filter', function() { @@ -39,13 +39,13 @@ export default function({ getService, getPageObjects }) { ); await PageObjects.common.sleep(500); log.debug('Bucket = X-Axis'); - await PageObjects.visualize.clickBucket('X-axis'); + await PageObjects.visEditor.clickBucket('X-axis'); log.debug('Aggregation = Date Histogram'); - await PageObjects.visualize.selectAggregation('Date Histogram'); + await PageObjects.visEditor.selectAggregation('Date Histogram'); log.debug('Field = @timestamp'); - await PageObjects.visualize.selectField('@timestamp'); - await PageObjects.visualize.setCustomInterval('3h'); - await PageObjects.visualize.waitForVisualizationRenderingStabilized(); + await PageObjects.visEditor.selectField('@timestamp'); + await PageObjects.visEditor.setInterval('3h', { type: 'custom' }); + await PageObjects.visChart.waitForVisualizationRenderingStabilized(); }; before(initBarChart); @@ -54,7 +54,7 @@ export default function({ getService, getPageObjects }) { await PageObjects.visualize.saveVisualizationExpectSuccessAndBreadcrumb(vizName1); await PageObjects.visualize.loadSavedVisualization(vizName1); - await PageObjects.visualize.waitForVisualization(); + await PageObjects.visChart.waitForVisualization(); }); it('should have inspector enabled', async function() { @@ -93,7 +93,7 @@ export default function({ getService, getPageObjects }) { // return arguments[0].getAttribute(arguments[1]);","args":[{"ELEMENT":"592"},"fill"]}] arguments[0].getAttribute is not a function // try sleeping a bit before getting that data await retry.try(async () => { - const data = await PageObjects.visualize.getBarChartData(); + const data = await PageObjects.visChart.getBarChartData(); log.debug('data=' + data); log.debug('data.length=' + data.length); expect(data).to.eql(expectedChartValues); @@ -134,13 +134,13 @@ export default function({ getService, getPageObjects }) { const axisId = 'ValueAxis-1'; it('should show ticks on selecting log scale', async () => { - await PageObjects.visualize.clickMetricsAndAxes(); - await PageObjects.visualize.clickYAxisOptions(axisId); - await PageObjects.visualize.selectYAxisScaleType(axisId, 'log'); - await PageObjects.visualize.clickYAxisAdvancedOptions(axisId); - await PageObjects.visualize.changeYAxisFilterLabelsCheckbox(axisId, false); - await PageObjects.visualize.clickGo(); - const labels = await PageObjects.visualize.getYAxisLabels(); + await PageObjects.visEditor.clickMetricsAndAxes(); + await PageObjects.visEditor.clickYAxisOptions(axisId); + await PageObjects.visEditor.selectYAxisScaleType(axisId, 'log'); + await PageObjects.visEditor.clickYAxisAdvancedOptions(axisId); + await PageObjects.visEditor.changeYAxisFilterLabelsCheckbox(axisId, false); + await PageObjects.visEditor.clickGo(); + const labels = await PageObjects.visChart.getYAxisLabels(); const expectedLabels = [ '2', '3', @@ -166,9 +166,9 @@ export default function({ getService, getPageObjects }) { }); it('should show filtered ticks on selecting log scale', async () => { - await PageObjects.visualize.changeYAxisFilterLabelsCheckbox(axisId, true); - await PageObjects.visualize.clickGo(); - const labels = await PageObjects.visualize.getYAxisLabels(); + await PageObjects.visEditor.changeYAxisFilterLabelsCheckbox(axisId, true); + await PageObjects.visEditor.clickGo(); + const labels = await PageObjects.visChart.getYAxisLabels(); const expectedLabels = [ '2', '3', @@ -194,10 +194,10 @@ export default function({ getService, getPageObjects }) { }); it('should show ticks on selecting square root scale', async () => { - await PageObjects.visualize.selectYAxisScaleType(axisId, 'square root'); - await PageObjects.visualize.changeYAxisFilterLabelsCheckbox(axisId, false); - await PageObjects.visualize.clickGo(); - const labels = await PageObjects.visualize.getYAxisLabels(); + await PageObjects.visEditor.selectYAxisScaleType(axisId, 'square root'); + await PageObjects.visEditor.changeYAxisFilterLabelsCheckbox(axisId, false); + await PageObjects.visEditor.clickGo(); + const labels = await PageObjects.visChart.getYAxisLabels(); const expectedLabels = [ '0', '200', @@ -213,18 +213,18 @@ export default function({ getService, getPageObjects }) { }); it('should show filtered ticks on selecting square root scale', async () => { - await PageObjects.visualize.changeYAxisFilterLabelsCheckbox(axisId, true); - await PageObjects.visualize.clickGo(); - const labels = await PageObjects.visualize.getYAxisLabels(); + await PageObjects.visEditor.changeYAxisFilterLabelsCheckbox(axisId, true); + await PageObjects.visEditor.clickGo(); + const labels = await PageObjects.visChart.getYAxisLabels(); const expectedLabels = ['200', '400', '600', '800', '1,000', '1,200', '1,400']; expect(labels).to.eql(expectedLabels); }); it('should show ticks on selecting linear scale', async () => { - await PageObjects.visualize.selectYAxisScaleType(axisId, 'linear'); - await PageObjects.visualize.changeYAxisFilterLabelsCheckbox(axisId, false); - await PageObjects.visualize.clickGo(); - const labels = await PageObjects.visualize.getYAxisLabels(); + await PageObjects.visEditor.selectYAxisScaleType(axisId, 'linear'); + await PageObjects.visEditor.changeYAxisFilterLabelsCheckbox(axisId, false); + await PageObjects.visEditor.clickGo(); + const labels = await PageObjects.visChart.getYAxisLabels(); log.debug(labels); const expectedLabels = [ '0', @@ -241,9 +241,9 @@ export default function({ getService, getPageObjects }) { }); it('should show filtered ticks on selecting linear scale', async () => { - await PageObjects.visualize.changeYAxisFilterLabelsCheckbox(axisId, true); - await PageObjects.visualize.clickGo(); - const labels = await PageObjects.visualize.getYAxisLabels(); + await PageObjects.visEditor.changeYAxisFilterLabelsCheckbox(axisId, true); + await PageObjects.visEditor.clickGo(); + const labels = await PageObjects.visChart.getYAxisLabels(); const expectedLabels = ['200', '400', '600', '800', '1,000', '1,200', '1,400']; expect(labels).to.eql(expectedLabels); }); @@ -253,18 +253,18 @@ export default function({ getService, getPageObjects }) { before(initBarChart); it('should show correct series', async function() { - await PageObjects.visualize.toggleOpenEditor(2, 'false'); - await PageObjects.visualize.clickBucket('Split series'); - await PageObjects.visualize.selectAggregation('Terms'); - await PageObjects.visualize.selectField('response.raw'); + await PageObjects.visEditor.toggleOpenEditor(2, 'false'); + await PageObjects.visEditor.clickBucket('Split series'); + await PageObjects.visEditor.selectAggregation('Terms'); + await PageObjects.visEditor.selectField('response.raw'); await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.common.sleep(1003); - await PageObjects.visualize.clickGo(); + await PageObjects.visEditor.clickGo(); await PageObjects.header.waitUntilLoadingHasFinished(); const expectedEntries = ['200', '404', '503']; - const legendEntries = await PageObjects.visualize.getLegendEntries(); + const legendEntries = await PageObjects.visChart.getLegendEntries(); expect(legendEntries).to.eql(expectedEntries); }); }); @@ -273,20 +273,20 @@ export default function({ getService, getPageObjects }) { before(initBarChart); it('should show correct series', async function() { - await PageObjects.visualize.toggleOpenEditor(2, 'false'); - await PageObjects.visualize.clickBucket('Split series'); - await PageObjects.visualize.selectAggregation('Terms'); - await PageObjects.visualize.selectField('response.raw'); + await PageObjects.visEditor.toggleOpenEditor(2, 'false'); + await PageObjects.visEditor.clickBucket('Split series'); + await PageObjects.visEditor.selectAggregation('Terms'); + await PageObjects.visEditor.selectField('response.raw'); await PageObjects.header.waitUntilLoadingHasFinished(); - await PageObjects.visualize.toggleOpenEditor(3, 'false'); - await PageObjects.visualize.clickBucket('Split series'); - await PageObjects.visualize.selectAggregation('Terms'); - await PageObjects.visualize.selectField('machine.os'); + await PageObjects.visEditor.toggleOpenEditor(3, 'false'); + await PageObjects.visEditor.clickBucket('Split series'); + await PageObjects.visEditor.selectAggregation('Terms'); + await PageObjects.visEditor.selectField('machine.os'); await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.common.sleep(1003); - await PageObjects.visualize.clickGo(); + await PageObjects.visEditor.clickGo(); await PageObjects.header.waitUntilLoadingHasFinished(); const expectedEntries = [ @@ -306,17 +306,17 @@ export default function({ getService, getPageObjects }) { '404 - win 8', '404 - win xp', ]; - const legendEntries = await PageObjects.visualize.getLegendEntries(); + const legendEntries = await PageObjects.visChart.getLegendEntries(); expect(legendEntries).to.eql(expectedEntries); }); it('should show correct series when disabling first agg', async function() { - await PageObjects.visualize.toggleDisabledAgg(3); - await PageObjects.visualize.clickGo(); + await PageObjects.visEditor.toggleDisabledAgg(3); + await PageObjects.visEditor.clickGo(); await PageObjects.header.waitUntilLoadingHasFinished(); const expectedEntries = ['win 8', 'win xp', 'ios', 'osx', 'win 7']; - const legendEntries = await PageObjects.visualize.getLegendEntries(); + const legendEntries = await PageObjects.visChart.getLegendEntries(); expect(legendEntries).to.eql(expectedEntries); }); }); @@ -325,17 +325,17 @@ export default function({ getService, getPageObjects }) { before(initBarChart); it('should show correct series', async function() { - await PageObjects.visualize.toggleOpenEditor(2, 'false'); - await PageObjects.visualize.toggleOpenEditor(1); - await PageObjects.visualize.selectAggregation('Derivative', 'metrics'); + await PageObjects.visEditor.toggleOpenEditor(2, 'false'); + await PageObjects.visEditor.toggleOpenEditor(1); + await PageObjects.visEditor.selectAggregation('Derivative', 'metrics'); await PageObjects.header.waitUntilLoadingHasFinished(); await PageObjects.common.sleep(1003); - await PageObjects.visualize.clickGo(); + await PageObjects.visEditor.clickGo(); await PageObjects.header.waitUntilLoadingHasFinished(); const expectedEntries = ['Derivative of Count']; - const legendEntries = await PageObjects.visualize.getLegendEntries(); + const legendEntries = await PageObjects.visChart.getLegendEntries(); expect(legendEntries).to.eql(expectedEntries); }); }); diff --git a/test/functional/apps/visualize/_visualize_listing.js b/test/functional/apps/visualize/_visualize_listing.js index 63a8a8310d2c1..e277c3c7d104d 100644 --- a/test/functional/apps/visualize/_visualize_listing.js +++ b/test/functional/apps/visualize/_visualize_listing.js @@ -19,8 +19,9 @@ import expect from '@kbn/expect'; -export default function({ getPageObjects }) { - const PageObjects = getPageObjects(['visualize', 'header', 'common']); +export default function({ getService, getPageObjects }) { + const PageObjects = getPageObjects(['visualize', 'visEditor']); + const listingTable = getService('listingTable'); // FLAKY: https://github.com/elastic/kibana/issues/40912 describe.skip('visualize listing page', function describeIndexTests() { @@ -36,7 +37,7 @@ export default function({ getPageObjects }) { // type markdown is used for simplicity await PageObjects.visualize.createSimpleMarkdownViz(vizName); await PageObjects.visualize.gotoVisualizationLandingPage(); - const visCount = await PageObjects.visualize.getCountOfItemsInListingTable(); + const visCount = await listingTable.getItemsCount('visualize'); expect(visCount).to.equal(1); }); @@ -45,11 +46,11 @@ export default function({ getPageObjects }) { await PageObjects.visualize.createSimpleMarkdownViz(vizName + '2'); await PageObjects.visualize.gotoVisualizationLandingPage(); - let visCount = await PageObjects.visualize.getCountOfItemsInListingTable(); + let visCount = await listingTable.getItemsCount('visualize'); expect(visCount).to.equal(3); await PageObjects.visualize.deleteAllVisualizations(); - visCount = await PageObjects.visualize.getCountOfItemsInListingTable(); + visCount = await listingTable.getItemsCount('visualize'); expect(visCount).to.equal(0); }); }); @@ -60,45 +61,45 @@ export default function({ getPageObjects }) { await PageObjects.visualize.gotoVisualizationLandingPage(); await PageObjects.visualize.navigateToNewVisualization(); await PageObjects.visualize.clickMarkdownWidget(); - await PageObjects.visualize.setMarkdownTxt('HELLO'); - await PageObjects.visualize.clickGo(); + await PageObjects.visEditor.setMarkdownTxt('HELLO'); + await PageObjects.visEditor.clickGo(); await PageObjects.visualize.saveVisualization('Hello World'); await PageObjects.visualize.gotoVisualizationLandingPage(); }); it('matches on the first word', async function() { - await PageObjects.visualize.searchForItemWithName('Hello'); - const itemCount = await PageObjects.visualize.getCountOfItemsInListingTable(); + await listingTable.searchForItemWithName('Hello'); + const itemCount = await listingTable.getItemsCount('visualize'); expect(itemCount).to.equal(1); }); it('matches the second word', async function() { - await PageObjects.visualize.searchForItemWithName('World'); - const itemCount = await PageObjects.visualize.getCountOfItemsInListingTable(); + await listingTable.searchForItemWithName('World'); + const itemCount = await listingTable.getItemsCount('visualize'); expect(itemCount).to.equal(1); }); it('matches the second word prefix', async function() { - await PageObjects.visualize.searchForItemWithName('Wor'); - const itemCount = await PageObjects.visualize.getCountOfItemsInListingTable(); + await listingTable.searchForItemWithName('Wor'); + const itemCount = await listingTable.getItemsCount('visualize'); expect(itemCount).to.equal(1); }); it('does not match mid word', async function() { - await PageObjects.visualize.searchForItemWithName('orld'); - const itemCount = await PageObjects.visualize.getCountOfItemsInListingTable(); + await listingTable.searchForItemWithName('orld'); + const itemCount = await listingTable.getItemsCount('visualize'); expect(itemCount).to.equal(0); }); it('is case insensitive', async function() { - await PageObjects.visualize.searchForItemWithName('hello world'); - const itemCount = await PageObjects.visualize.getCountOfItemsInListingTable(); + await listingTable.searchForItemWithName('hello world'); + const itemCount = await listingTable.getItemsCount('visualize'); expect(itemCount).to.equal(1); }); it('is using AND operator', async function() { - await PageObjects.visualize.searchForItemWithName('hello banana'); - const itemCount = await PageObjects.visualize.getCountOfItemsInListingTable(); + await listingTable.searchForItemWithName('hello banana'); + const itemCount = await listingTable.getItemsCount('visualize'); expect(itemCount).to.equal(0); }); }); diff --git a/test/functional/apps/visualize/input_control_vis/chained_controls.js b/test/functional/apps/visualize/input_control_vis/chained_controls.js index 96d9dae519b51..b56a37218aba5 100644 --- a/test/functional/apps/visualize/input_control_vis/chained_controls.js +++ b/test/functional/apps/visualize/input_control_vis/chained_controls.js @@ -21,7 +21,7 @@ import expect from '@kbn/expect'; export default function({ getService, getPageObjects }) { const filterBar = getService('filterBar'); - const PageObjects = getPageObjects(['common', 'visualize', 'header', 'timePicker']); + const PageObjects = getPageObjects(['common', 'visualize', 'visEditor', 'header', 'timePicker']); const testSubjects = getService('testSubjects'); const find = getService('find'); const comboBox = getService('comboBox'); @@ -65,7 +65,7 @@ export default function({ getService, getPageObjects }) { it('should create a seperate filter pill for parent control and child control', async () => { await comboBox.set('listControlSelect1', '14.61.182.136'); - await PageObjects.visualize.inputControlSubmit(); + await PageObjects.visEditor.inputControlSubmit(); const hasParentControlFilter = await filterBar.hasFilter('geo.src', 'BR'); expect(hasParentControlFilter).to.equal(true); diff --git a/test/functional/apps/visualize/input_control_vis/dynamic_options.js b/test/functional/apps/visualize/input_control_vis/dynamic_options.js index 2354855f12079..d78c780a728a7 100644 --- a/test/functional/apps/visualize/input_control_vis/dynamic_options.js +++ b/test/functional/apps/visualize/input_control_vis/dynamic_options.js @@ -20,7 +20,7 @@ import expect from '@kbn/expect'; export default function({ getService, getPageObjects }) { - const PageObjects = getPageObjects(['common', 'visualize', 'header', 'timePicker']); + const PageObjects = getPageObjects(['common', 'visualize', 'visEditor', 'header', 'timePicker']); const comboBox = getService('comboBox'); describe('dynamic options', () => { @@ -55,7 +55,7 @@ export default function({ getService, getPageObjects }) { it('should not fetch new options when non-string is filtered', async () => { await comboBox.set('fieldSelect-0', 'clientip'); - await PageObjects.visualize.clickGo(); + await PageObjects.visEditor.clickGo(); const initialOptions = await comboBox.getOptionsList('listControlSelect0'); expect( diff --git a/test/functional/apps/visualize/input_control_vis/input_control_options.js b/test/functional/apps/visualize/input_control_vis/input_control_options.js index 1c3f63e94ae75..8e8891ac585b3 100644 --- a/test/functional/apps/visualize/input_control_vis/input_control_options.js +++ b/test/functional/apps/visualize/input_control_vis/input_control_options.js @@ -21,7 +21,7 @@ import expect from '@kbn/expect'; export default function({ getService, getPageObjects }) { const filterBar = getService('filterBar'); - const PageObjects = getPageObjects(['common', 'visualize', 'header', 'timePicker']); + const PageObjects = getPageObjects(['common', 'visualize', 'visEditor', 'header', 'timePicker']); const testSubjects = getService('testSubjects'); const inspector = getService('inspector'); const find = getService('find'); @@ -38,11 +38,11 @@ export default function({ getService, getPageObjects }) { 'Jan 1, 2017 @ 00:00:00.000', 'Jan 1, 2017 @ 00:00:00.000' ); - await PageObjects.visualize.clickVisEditorTab('controls'); - await PageObjects.visualize.addInputControl(); + await PageObjects.visEditor.clickVisEditorTab('controls'); + await PageObjects.visEditor.addInputControl(); await comboBox.set('indexPatternSelect-0', 'logstash- '); await comboBox.set('fieldSelect-0', FIELD_NAME); - await PageObjects.visualize.clickGo(); + await PageObjects.visEditor.clickGo(); }); it('should not have inspector enabled', async function() { @@ -89,7 +89,7 @@ export default function({ getService, getPageObjects }) { }); it('should add filter pill when submit button is clicked', async () => { - await PageObjects.visualize.inputControlSubmit(); + await PageObjects.visEditor.inputControlSubmit(); const hasFilter = await filterBar.hasFilter(FIELD_NAME, 'ios'); expect(hasFilter).to.equal(true); @@ -98,7 +98,7 @@ export default function({ getService, getPageObjects }) { it('should replace existing filter pill(s) when new item is selected', async () => { await comboBox.clear('listControlSelect0'); await comboBox.set('listControlSelect0', 'osx'); - await PageObjects.visualize.inputControlSubmit(); + await PageObjects.visEditor.inputControlSubmit(); await PageObjects.common.sleep(1000); const hasOldFilter = await filterBar.hasFilter(FIELD_NAME, 'ios'); @@ -117,11 +117,11 @@ export default function({ getService, getPageObjects }) { it('should clear form when Clear button is clicked but not remove filter pill', async () => { await comboBox.set('listControlSelect0', 'ios'); - await PageObjects.visualize.inputControlSubmit(); + await PageObjects.visEditor.inputControlSubmit(); const hasFilterBeforeClearBtnClicked = await filterBar.hasFilter(FIELD_NAME, 'ios'); expect(hasFilterBeforeClearBtnClicked).to.equal(true); - await PageObjects.visualize.inputControlClear(); + await PageObjects.visEditor.inputControlClear(); const hasValue = await comboBox.doesComboBoxHaveSelectedOptions('listControlSelect0'); expect(hasValue).to.equal(false); @@ -130,7 +130,7 @@ export default function({ getService, getPageObjects }) { }); it('should remove filter pill when cleared form is submitted', async () => { - await PageObjects.visualize.inputControlSubmit(); + await PageObjects.visEditor.inputControlSubmit(); const hasFilter = await filterBar.hasFilter(FIELD_NAME, 'ios'); expect(hasFilter).to.equal(false); }); @@ -138,17 +138,17 @@ export default function({ getService, getPageObjects }) { describe('updateFiltersOnChange is true', () => { before(async () => { - await PageObjects.visualize.clickVisEditorTab('options'); - await PageObjects.visualize.checkSwitch('inputControlEditorUpdateFiltersOnChangeCheckbox'); - await PageObjects.visualize.clickGo(); + await PageObjects.visEditor.clickVisEditorTab('options'); + await PageObjects.visEditor.checkSwitch('inputControlEditorUpdateFiltersOnChangeCheckbox'); + await PageObjects.visEditor.clickGo(); }); after(async () => { - await PageObjects.visualize.clickVisEditorTab('options'); - await PageObjects.visualize.uncheckSwitch( + await PageObjects.visEditor.clickVisEditorTab('options'); + await PageObjects.visEditor.uncheckSwitch( 'inputControlEditorUpdateFiltersOnChangeCheckbox' ); - await PageObjects.visualize.clickGo(); + await PageObjects.visEditor.clickGo(); }); it('should not display staging control buttons', async () => { @@ -173,9 +173,9 @@ export default function({ getService, getPageObjects }) { describe('useTimeFilter', () => { it('should use global time filter when getting terms', async () => { - await PageObjects.visualize.clickVisEditorTab('options'); - await PageObjects.visualize.checkCheckbox('inputControlEditorUseTimeFilterCheckbox'); - await PageObjects.visualize.clickGo(); + await PageObjects.visEditor.clickVisEditorTab('options'); + await testSubjects.setCheckbox('inputControlEditorUseTimeFilterCheckbox', 'check'); + await PageObjects.visEditor.clickGo(); // Expect control to be disabled because no terms could be gathered with time filter applied const input = await find.byCssSelector('[data-test-subj="inputControl0"] input'); diff --git a/test/functional/apps/visualize/input_control_vis/input_control_range.ts b/test/functional/apps/visualize/input_control_vis/input_control_range.ts index 2d6550de5dec9..f48ba7b54daf1 100644 --- a/test/functional/apps/visualize/input_control_vis/input_control_range.ts +++ b/test/functional/apps/visualize/input_control_vis/input_control_range.ts @@ -25,7 +25,7 @@ export default function({ getService, getPageObjects }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const kibanaServer = getService('kibanaServer'); const find = getService('find'); - const { visualize } = getPageObjects(['visualize']); + const { visualize, visEditor } = getPageObjects(['visualize', 'visEditor']); describe('input control range', () => { before(async () => { @@ -35,36 +35,22 @@ export default function({ getService, getPageObjects }: FtrProviderContext) { }); it('should add filter with scripted field', async () => { - await visualize.addInputControl('range'); - await visualize.setFilterParams({ - indexPattern: 'kibana_sample_data_flights', - field: 'hour_of_day', - }); - await visualize.clickGo(); - await visualize.setFilterRange({ - min: '7', - max: '10', - }); - await visualize.inputControlSubmit(); + await visEditor.addInputControl('range'); + await visEditor.setFilterParams(0, 'kibana_sample_data_flights', 'hour_of_day'); + await visEditor.clickGo(); + await visEditor.setFilterRange(0, '7', '10'); + await visEditor.inputControlSubmit(); const controlFilters = await find.allByCssSelector('[data-test-subj^="filter"]'); expect(controlFilters).to.have.length(1); expect(await controlFilters[0].getVisibleText()).to.equal('hour_of_day: 7 to 10'); }); it('should add filter with price field', async () => { - await visualize.addInputControl('range'); - await visualize.setFilterParams({ - aggNth: 1, - indexPattern: 'kibana_sample_data_flights', - field: 'AvgTicketPrice', - }); - await visualize.clickGo(); - await visualize.setFilterRange({ - aggNth: 1, - min: '400', - max: '999', - }); - await visualize.inputControlSubmit(); + await visEditor.addInputControl('range'); + await visEditor.setFilterParams(1, 'kibana_sample_data_flights', 'AvgTicketPrice'); + await visEditor.clickGo(); + await visEditor.setFilterRange(1, '400', '999'); + await visEditor.inputControlSubmit(); const controlFilters = await find.allByCssSelector('[data-test-subj^="filter"]'); expect(controlFilters).to.have.length(2); expect(await controlFilters[1].getVisibleText()).to.equal('AvgTicketPrice: $400 to $999'); diff --git a/test/functional/page_objects/discover_page.js b/test/functional/page_objects/discover_page.js index 7029fbf9e1350..85d8cff675f2d 100644 --- a/test/functional/page_objects/discover_page.js +++ b/test/functional/page_objects/discover_page.js @@ -63,6 +63,18 @@ export function DiscoverPageProvider({ getService, getPageObjects }) { }); } + async inputSavedSearchTitle(searchName) { + await testSubjects.setValue('savedObjectTitle', searchName); + } + + async clickConfirmSavedSearch() { + await testSubjects.click('confirmSaveSavedObjectButton'); + } + + async openAddFilterPanel() { + await testSubjects.click('addFilter'); + } + async waitUntilSearchingHasFinished() { const spinner = await testSubjects.find('loadingSpinner'); await find.waitForElementHidden(spinner, defaultFindTimeout * 10); @@ -117,8 +129,20 @@ export function DiscoverPageProvider({ getService, getPageObjects }) { await testSubjects.click('discoverOpenButton'); } + async closeLoadSavedSearchPanel() { + await testSubjects.click('euiFlyoutCloseButton'); + } + + async getChartCanvas() { + return await find.byCssSelector('.echChart canvas:last-of-type'); + } + + async chartCanvasExist() { + return await find.existsByCssSelector('.echChart canvas:last-of-type'); + } + async clickHistogramBar() { - const el = await find.byCssSelector('.echChart canvas:last-of-type'); + const el = await this.getChartCanvas(); await browser .getActions() @@ -128,7 +152,8 @@ export function DiscoverPageProvider({ getService, getPageObjects }) { } async brushHistogram() { - const el = await find.byCssSelector('.echChart canvas:last-of-type'); + const el = await this.getChartCanvas(); + await browser.dragAndDrop( { location: el, offset: { x: 200, y: 20 } }, { location: el, offset: { x: 400, y: 30 } } @@ -279,7 +304,7 @@ export function DiscoverPageProvider({ getService, getPageObjects }) { async selectIndexPattern(indexPattern) { await testSubjects.click('indexPattern-switch-link'); await find.clickByCssSelector( - `[data-test-subj="indexPattern-switcher"] [title="${indexPattern}*"]` + `[data-test-subj="indexPattern-switcher"] [title="${indexPattern}"]` ); await PageObjects.header.waitUntilLoadingHasFinished(); } diff --git a/test/functional/page_objects/index.ts b/test/functional/page_objects/index.ts index 5a104c8d17bf2..5526243ea2bbd 100644 --- a/test/functional/page_objects/index.ts +++ b/test/functional/page_objects/index.ts @@ -39,7 +39,6 @@ import { NewsfeedPageProvider } from './newsfeed_page'; import { PointSeriesPageProvider } from './point_series_page'; // @ts-ignore not TS yet import { SettingsPageProvider } from './settings_page'; -// @ts-ignore not TS yet import { SharePageProvider } from './share_page'; // @ts-ignore not TS yet import { ShieldPageProvider } from './shield_page'; @@ -48,8 +47,12 @@ import { TimePickerPageProvider } from './time_picker'; // @ts-ignore not TS yet import { TimelionPageProvider } from './timelion_page'; import { VisualBuilderPageProvider } from './visual_builder_page'; -// @ts-ignore not TS yet import { VisualizePageProvider } from './visualize_page'; +import { VisualizeEditorPageProvider } from './visualize_editor_page'; +import { VisualizeChartPageProvider } from './visualize_chart_page'; +import { TileMapPageProvider } from './tile_map_page'; +import { TagCloudPageProvider } from './tag_cloud_page'; +import { VegaChartPageProvider } from './vega_chart_page'; export const pageObjects = { common: CommonPageProvider, @@ -70,4 +73,9 @@ export const pageObjects = { timePicker: TimePickerPageProvider, visualBuilder: VisualBuilderPageProvider, visualize: VisualizePageProvider, + visEditor: VisualizeEditorPageProvider, + visChart: VisualizeChartPageProvider, + tileMap: TileMapPageProvider, + tagCloud: TagCloudPageProvider, + vegaChart: VegaChartPageProvider, }; diff --git a/test/functional/page_objects/point_series_page.js b/test/functional/page_objects/point_series_page.js index 9172809eb73e5..74bf07b59bc38 100644 --- a/test/functional/page_objects/point_series_page.js +++ b/test/functional/page_objects/point_series_page.js @@ -23,10 +23,6 @@ export function PointSeriesPageProvider({ getService }) { const find = getService('find'); class PointSeriesVis { - async clickOptions() { - return await testSubjects.click('visEditorTaboptions'); - } - async clickAxisOptions() { return await testSubjects.click('visEditorTabadvanced'); } diff --git a/test/functional/page_objects/share_page.ts b/test/functional/page_objects/share_page.ts index 906effcb54a26..fc8db9b78a03f 100644 --- a/test/functional/page_objects/share_page.ts +++ b/test/functional/page_objects/share_page.ts @@ -19,10 +19,9 @@ import { FtrProviderContext } from '../ftr_provider_context'; -export function SharePageProvider({ getService, getPageObjects }: FtrProviderContext) { +export function SharePageProvider({ getService }: FtrProviderContext) { const testSubjects = getService('testSubjects'); const find = getService('find'); - const PageObjects = getPageObjects(['visualize', 'common']); const log = getService('log'); class SharePage { @@ -78,7 +77,7 @@ export function SharePageProvider({ getService, getPageObjects }: FtrProviderCon async checkShortenUrl() { const shareForm = await testSubjects.find('shareUrlForm'); - await PageObjects.visualize.checkCheckbox('useShortUrl'); + await testSubjects.setCheckbox('useShortUrl', 'check'); await shareForm.waitForDeletedByCssSelector('.euiLoadingSpinner'); } diff --git a/test/functional/page_objects/tag_cloud_page.ts b/test/functional/page_objects/tag_cloud_page.ts new file mode 100644 index 0000000000000..7d87caa39b2fb --- /dev/null +++ b/test/functional/page_objects/tag_cloud_page.ts @@ -0,0 +1,52 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { FtrProviderContext } from '../ftr_provider_context'; +import { WebElementWrapper } from '../services/lib/web_element_wrapper'; + +export function TagCloudPageProvider({ getService, getPageObjects }: FtrProviderContext) { + const find = getService('find'); + const testSubjects = getService('testSubjects'); + const { header, visChart } = getPageObjects(['header', 'visChart']); + + class TagCloudPage { + public async selectTagCloudTag(tagDisplayText: string) { + await testSubjects.click(tagDisplayText); + await header.waitUntilLoadingHasFinished(); + } + + public async getTextTag() { + await visChart.waitForVisualization(); + const elements = await find.allByCssSelector('text'); + return await Promise.all(elements.map(async element => await element.getVisibleText())); + } + + public async getTextSizes() { + const tags = await find.allByCssSelector('text'); + async function returnTagSize(tag: WebElementWrapper) { + const style = await tag.getAttribute('style'); + const fontSize = style.match(/font-size: ([^;]*);/); + return fontSize ? fontSize[1] : ''; + } + return await Promise.all(tags.map(returnTagSize)); + } + } + + return new TagCloudPage(); +} diff --git a/test/functional/page_objects/tile_map_page.ts b/test/functional/page_objects/tile_map_page.ts new file mode 100644 index 0000000000000..b41578f782af4 --- /dev/null +++ b/test/functional/page_objects/tile_map_page.ts @@ -0,0 +1,102 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { FtrProviderContext } from '../ftr_provider_context'; + +export function TileMapPageProvider({ getService, getPageObjects }: FtrProviderContext) { + const find = getService('find'); + const testSubjects = getService('testSubjects'); + const retry = getService('retry'); + const log = getService('log'); + const inspector = getService('inspector'); + const { header } = getPageObjects(['header']); + + class TileMapPage { + public async getZoomSelectors(zoomSelector: string) { + return await find.allByCssSelector(zoomSelector); + } + + public async clickMapButton(zoomSelector: string, waitForLoading?: boolean) { + await retry.try(async () => { + const zooms = await this.getZoomSelectors(zoomSelector); + await Promise.all(zooms.map(async zoom => await zoom.click())); + if (waitForLoading) { + await header.waitUntilLoadingHasFinished(); + } + }); + } + + public async getVisualizationRequest() { + log.debug('getVisualizationRequest'); + await inspector.open(); + await testSubjects.click('inspectorViewChooser'); + await testSubjects.click('inspectorViewChooserRequests'); + await testSubjects.click('inspectorRequestDetailRequest'); + return await testSubjects.getVisibleText('inspectorRequestBody'); + } + + public async getMapBounds(): Promise { + const request = await this.getVisualizationRequest(); + const requestObject = JSON.parse(request); + return requestObject.aggs.filter_agg.filter.geo_bounding_box['geo.coordinates']; + } + + public async clickMapZoomIn(waitForLoading = true) { + await this.clickMapButton('a.leaflet-control-zoom-in', waitForLoading); + } + + public async clickMapZoomOut(waitForLoading = true) { + await this.clickMapButton('a.leaflet-control-zoom-out', waitForLoading); + } + + public async getMapZoomEnabled(zoomSelector: string): Promise { + const zooms = await this.getZoomSelectors(zoomSelector); + const classAttributes = await Promise.all( + zooms.map(async zoom => await zoom.getAttribute('class')) + ); + return !classAttributes.join('').includes('leaflet-disabled'); + } + + public async zoomAllTheWayOut(): Promise { + // we can tell we're at level 1 because zoom out is disabled + return await retry.try(async () => { + await this.clickMapZoomOut(); + const enabled = await this.getMapZoomOutEnabled(); + // should be able to zoom more as current config has 0 as min level. + if (enabled) { + throw new Error('Not fully zoomed out yet'); + } + }); + } + + public async getMapZoomInEnabled() { + return await this.getMapZoomEnabled('a.leaflet-control-zoom-in'); + } + + public async getMapZoomOutEnabled() { + return await this.getMapZoomEnabled('a.leaflet-control-zoom-out'); + } + + public async clickMapFitDataBounds() { + return await this.clickMapButton('a.fa-crop'); + } + } + + return new TileMapPage(); +} diff --git a/test/functional/page_objects/vega_chart_page.ts b/test/functional/page_objects/vega_chart_page.ts new file mode 100644 index 0000000000000..9931ebebef6ef --- /dev/null +++ b/test/functional/page_objects/vega_chart_page.ts @@ -0,0 +1,91 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../ftr_provider_context'; + +export function VegaChartPageProvider({ + getService, + getPageObjects, + updateBaselines, +}: FtrProviderContext & { updateBaselines: boolean }) { + const find = getService('find'); + const testSubjects = getService('testSubjects'); + const browser = getService('browser'); + const screenshot = getService('screenshots'); + const log = getService('log'); + const { visEditor, visChart } = getPageObjects(['visEditor', 'visChart']); + + class VegaChartPage { + public async getSpec() { + // Adapted from console_page.js:getVisibleTextFromAceEditor(). Is there a common utilities file? + const editor = await testSubjects.find('vega-editor'); + const lines = await editor.findAllByClassName('ace_line_group'); + const linesText = await Promise.all( + lines.map(async line => { + return await line.getVisibleText(); + }) + ); + return linesText.join('\n'); + } + + public async getViewContainer() { + return await find.byCssSelector('div.vgaVis__view'); + } + + public async getControlContainer() { + return await find.byCssSelector('div.vgaVis__controls'); + } + + /** + * Removes chrome and takes a small screenshot of a vis to compare against a baseline. + * @param {string} name The name of the baseline image. + * @param {object} opts Options object. + * @param {number} opts.threshold Threshold for allowed variance when comparing images. + */ + public async expectVisToMatchScreenshot(name: string, opts = { threshold: 0.05 }) { + log.debug(`expectVisToMatchScreenshot(${name})`); + + // Collapse sidebar and inject some CSS to hide the nav so we have a focused screenshot + await visEditor.clickEditorSidebarCollapse(); + await visChart.waitForVisualizationRenderingStabilized(); + await browser.execute(` + var el = document.createElement('style'); + el.id = '__data-test-style'; + el.innerHTML = '[data-test-subj="headerGlobalNav"] { display: none; } '; + el.innerHTML += '[data-test-subj="top-nav"] { display: none; } '; + el.innerHTML += '[data-test-subj="experimentalVisInfo"] { display: none; } '; + document.body.appendChild(el); + `); + + const percentDifference = await screenshot.compareAgainstBaseline(name, updateBaselines); + + // Reset the chart to its original state + await browser.execute(` + var el = document.getElementById('__data-test-style'); + document.body.removeChild(el); + `); + await visEditor.clickEditorSidebarCollapse(); + await visChart.waitForVisualizationRenderingStabilized(); + expect(percentDifference).to.be.lessThan(opts.threshold); + } + } + + return new VegaChartPage(); +} diff --git a/test/functional/page_objects/visual_builder_page.ts b/test/functional/page_objects/visual_builder_page.ts index 97d5787350376..2fa59d5fd89d8 100644 --- a/test/functional/page_objects/visual_builder_page.ts +++ b/test/functional/page_objects/visual_builder_page.ts @@ -26,7 +26,7 @@ export function VisualBuilderPageProvider({ getService, getPageObjects }: FtrPro const retry = getService('retry'); const testSubjects = getService('testSubjects'); const comboBox = getService('comboBox'); - const PageObjects = getPageObjects(['common', 'header', 'visualize', 'timePicker']); + const PageObjects = getPageObjects(['common', 'header', 'visualize', 'timePicker', 'visChart']); type Duration = | 'Milliseconds' @@ -101,7 +101,7 @@ export function VisualBuilderPageProvider({ getService, getPageObjects }: FtrPro } public async getMetricValue() { - await PageObjects.visualize.waitForVisualizationRenderingStabilized(); + await PageObjects.visChart.waitForVisualizationRenderingStabilized(); const metricValue = await find.byCssSelector('.tvbVisMetric__value--primary'); return metricValue.getVisibleText(); } @@ -110,7 +110,7 @@ export function VisualBuilderPageProvider({ getService, getPageObjects }: FtrPro const input = await find.byCssSelector('.tvbMarkdownEditor__editor textarea'); await this.clearMarkdown(); await input.type(markdown, { charByChar: true }); - await PageObjects.visualize.waitForVisualizationRenderingStabilized(); + await PageObjects.visChart.waitForVisualizationRenderingStabilized(); } public async clearMarkdown() { @@ -304,7 +304,7 @@ export function VisualBuilderPageProvider({ getService, getPageObjects }: FtrPro } public async getRhythmChartLegendValue(nth = 0) { - await PageObjects.visualize.waitForVisualizationRenderingStabilized(); + await PageObjects.visChart.waitForVisualizationRenderingStabilized(); const metricValue = ( await find.allByCssSelector(`.echLegendItem .echLegendItem__displayValue`) )[nth]; @@ -348,7 +348,7 @@ export function VisualBuilderPageProvider({ getService, getPageObjects }: FtrPro const prevAggs = await testSubjects.findAll('aggSelector'); const elements = await testSubjects.findAll('addMetricAddBtn'); await elements[nth].click(); - await PageObjects.visualize.waitForVisualizationRenderingStabilized(); + await PageObjects.visChart.waitForVisualizationRenderingStabilized(); await retry.waitFor('new agg is added', async () => { const currentAggs = await testSubjects.findAll('aggSelector'); return currentAggs.length > prevAggs.length; @@ -485,7 +485,7 @@ export function VisualBuilderPageProvider({ getService, getPageObjects }: FtrPro await this.checkColorPickerPopUpIsPresent(); await find.setValue('.tvbColorPickerPopUp input', colorHex); await this.clickColorPicker(); - await PageObjects.visualize.waitForVisualizationRenderingStabilized(); + await PageObjects.visChart.waitForVisualizationRenderingStabilized(); } public async checkColorPickerPopUpIsPresent(): Promise { @@ -494,10 +494,10 @@ export function VisualBuilderPageProvider({ getService, getPageObjects }: FtrPro } public async changePanelPreview(nth: number = 0): Promise { - const prevRenderingCount = await PageObjects.visualize.getVisualizationRenderingCount(); + const prevRenderingCount = await PageObjects.visChart.getVisualizationRenderingCount(); const changePreviewBtnArray = await testSubjects.findAll('AddActivatePanelBtn'); await changePreviewBtnArray[nth].click(); - await PageObjects.visualize.waitForRenderingCount(prevRenderingCount + 1); + await PageObjects.visChart.waitForRenderingCount(prevRenderingCount + 1); } public async checkPreviewIsDisabled(): Promise { @@ -508,7 +508,7 @@ export function VisualBuilderPageProvider({ getService, getPageObjects }: FtrPro public async cloneSeries(nth: number = 0): Promise { const cloneBtnArray = await testSubjects.findAll('AddCloneBtn'); await cloneBtnArray[nth].click(); - await PageObjects.visualize.waitForVisualizationRenderingStabilized(); + await PageObjects.visChart.waitForVisualizationRenderingStabilized(); } /** @@ -525,10 +525,10 @@ export function VisualBuilderPageProvider({ getService, getPageObjects }: FtrPro } public async deleteSeries(nth: number = 0): Promise { - const prevRenderingCount = await PageObjects.visualize.getVisualizationRenderingCount(); + const prevRenderingCount = await PageObjects.visChart.getVisualizationRenderingCount(); const cloneBtnArray = await testSubjects.findAll('AddDeleteBtn'); await cloneBtnArray[nth].click(); - await PageObjects.visualize.waitForRenderingCount(prevRenderingCount + 1); + await PageObjects.visChart.waitForRenderingCount(prevRenderingCount + 1); } public async getLegendItems(): Promise { diff --git a/test/functional/page_objects/visualize_chart_page.ts b/test/functional/page_objects/visualize_chart_page.ts new file mode 100644 index 0000000000000..138e5758ede7c --- /dev/null +++ b/test/functional/page_objects/visualize_chart_page.ts @@ -0,0 +1,386 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { FtrProviderContext } from '../ftr_provider_context'; + +export function VisualizeChartPageProvider({ getService, getPageObjects }: FtrProviderContext) { + const testSubjects = getService('testSubjects'); + const config = getService('config'); + const find = getService('find'); + const log = getService('log'); + const retry = getService('retry'); + const table = getService('table'); + const defaultFindTimeout = config.get('timeouts.find'); + const { common } = getPageObjects(['common']); + + class VisualizeChart { + public async getYAxisTitle() { + const title = await find.byCssSelector('.y-axis-div .y-axis-title text'); + return await title.getVisibleText(); + } + + public async getXAxisLabels() { + const xAxis = await find.byCssSelector('.visAxis--x.visAxis__column--bottom'); + const $ = await xAxis.parseDomContent(); + return $('.x > g > text') + .toArray() + .map(tick => + $(tick) + .text() + .trim() + ); + } + + public async getYAxisLabels() { + const yAxis = await find.byCssSelector('.visAxis__column--y.visAxis__column--left'); + const $ = await yAxis.parseDomContent(); + return $('.y > g > text') + .toArray() + .map(tick => + $(tick) + .text() + .trim() + ); + } + + /** + * Gets the chart data and scales it based on chart height and label. + * @param dataLabel data-label value + * @param axis axis value, 'ValueAxis-1' by default + * + * Returns an array of height values + */ + public async getAreaChartData(dataLabel: string, axis = 'ValueAxis-1') { + const yAxisRatio = await this.getChartYAxisRatio(axis); + + const rectangle = await find.byCssSelector('rect.background'); + const yAxisHeight = Number(await rectangle.getAttribute('height')); + log.debug(`height --------- ${yAxisHeight}`); + + const path = await retry.try( + async () => + await find.byCssSelector(`path[data-label="${dataLabel}"]`, defaultFindTimeout * 2) + ); + const data = await path.getAttribute('d'); + log.debug(data); + // This area chart data starts with a 'M'ove to a x,y location, followed + // by a bunch of 'L'ines from that point to the next. Those points are + // the values we're going to use to calculate the data values we're testing. + // So git rid of the one 'M' and split the rest on the 'L's. + const tempArray = data + .replace('M ', '') + .replace('M', '') + .replace(/ L /g, 'L') + .replace(/ /g, ',') + .split('L'); + const chartSections = tempArray.length / 2; + const chartData = []; + for (let i = 0; i < chartSections; i++) { + chartData[i] = Math.round((yAxisHeight - Number(tempArray[i].split(',')[1])) * yAxisRatio); + log.debug('chartData[i] =' + chartData[i]); + } + return chartData; + } + + /** + * Returns the paths that compose an area chart. + * @param dataLabel data-label value + */ + public async getAreaChartPaths(dataLabel: string) { + const path = await retry.try( + async () => + await find.byCssSelector(`path[data-label="${dataLabel}"]`, defaultFindTimeout * 2) + ); + const data = await path.getAttribute('d'); + log.debug(data); + // This area chart data starts with a 'M'ove to a x,y location, followed + // by a bunch of 'L'ines from that point to the next. Those points are + // the values we're going to use to calculate the data values we're testing. + // So git rid of the one 'M' and split the rest on the 'L's. + return data.split('L'); + } + + /** + * Gets the dots and normalizes their height. + * @param dataLabel data-label value + * @param axis axis value, 'ValueAxis-1' by default + */ + public async getLineChartData(dataLabel = 'Count', axis = 'ValueAxis-1') { + // 1). get the range/pixel ratio + const yAxisRatio = await this.getChartYAxisRatio(axis); + // 2). find and save the y-axis pixel size (the chart height) + const rectangle = await find.byCssSelector('clipPath rect'); + const yAxisHeight = Number(await rectangle.getAttribute('height')); + // 3). get the visWrapper__chart elements + const chartTypes = await retry.try( + async () => + await find.allByCssSelector( + `.visWrapper__chart circle[data-label="${dataLabel}"][fill-opacity="1"]`, + defaultFindTimeout * 2 + ) + ); + // 4). for each chart element, find the green circle, then the cy position + const chartData = await Promise.all( + chartTypes.map(async chart => { + const cy = Number(await chart.getAttribute('cy')); + // the point_series_options test has data in the billions range and + // getting 11 digits of precision with these calculations is very hard + return Math.round(Number(((yAxisHeight - cy) * yAxisRatio).toPrecision(6))); + }) + ); + + return chartData; + } + + /** + * Returns bar chart data in pixels + * @param dataLabel data-label value + * @param axis axis value, 'ValueAxis-1' by default + */ + public async getBarChartData(dataLabel = 'Count', axis = 'ValueAxis-1') { + const yAxisRatio = await this.getChartYAxisRatio(axis); + const svg = await find.byCssSelector('div.chart'); + const $ = await svg.parseDomContent(); + const chartData = $(`g > g.series > rect[data-label="${dataLabel}"]`) + .toArray() + .map(chart => { + const barHeight = Number($(chart).attr('height')); + return Math.round(barHeight * yAxisRatio); + }); + + return chartData; + } + + /** + * Returns the range/pixel ratio + * @param axis axis value, 'ValueAxis-1' by default + */ + private async getChartYAxisRatio(axis = 'ValueAxis-1') { + // 1). get the maximum chart Y-Axis marker value and Y position + const maxYAxisChartMarker = await retry.try( + async () => + await find.byCssSelector( + `div.visAxis__splitAxes--y > div > svg > g.${axis} > g:last-of-type.tick` + ) + ); + const maxYLabel = (await maxYAxisChartMarker.getVisibleText()).replace(/,/g, ''); + const maxYLabelYPosition = (await maxYAxisChartMarker.getPosition()).y; + log.debug(`maxYLabel = ${maxYLabel}, maxYLabelYPosition = ${maxYLabelYPosition}`); + + // 2). get the minimum chart Y-Axis marker value and Y position + const minYAxisChartMarker = await find.byCssSelector( + 'div.visAxis__column--y.visAxis__column--left > div > div > svg:nth-child(2) > g > g:nth-child(1).tick' + ); + const minYLabel = (await minYAxisChartMarker.getVisibleText()).replace(',', ''); + const minYLabelYPosition = (await minYAxisChartMarker.getPosition()).y; + return (Number(maxYLabel) - Number(minYLabel)) / (minYLabelYPosition - maxYLabelYPosition); + } + + public async toggleLegend(show = true) { + await retry.try(async () => { + const isVisible = find.byCssSelector('.visLegend'); + if ((show && !isVisible) || (!show && isVisible)) { + await testSubjects.click('vislibToggleLegend'); + } + }); + } + + public async filterLegend(name: string) { + await this.toggleLegend(); + await testSubjects.click(`legend-${name}`); + const filters = await testSubjects.find(`legend-${name}-filters`); + const [filterIn] = await filters.findAllByCssSelector(`input`); + await filterIn.click(); + await this.waitForVisualizationRenderingStabilized(); + } + + public async doesLegendColorChoiceExist(color: string) { + return await testSubjects.exists(`legendSelectColor-${color}`); + } + + public async selectNewLegendColorChoice(color: string) { + await testSubjects.click(`legendSelectColor-${color}`); + } + + public async doesSelectedLegendColorExist(color: string) { + return await testSubjects.exists(`legendSelectedColor-${color}`); + } + + public async expectError() { + await testSubjects.existOrFail('visLibVisualizeError'); + } + + public async getVisualizationRenderingCount() { + const visualizationLoader = await testSubjects.find('visualizationLoader'); + const renderingCount = await visualizationLoader.getAttribute('data-rendering-count'); + return Number(renderingCount); + } + + public async waitForRenderingCount(minimumCount = 1) { + await retry.waitFor( + `rendering count to be greater than or equal to [${minimumCount}]`, + async () => { + const currentRenderingCount = await this.getVisualizationRenderingCount(); + log.debug(`-- currentRenderingCount=${currentRenderingCount}`); + return currentRenderingCount >= minimumCount; + } + ); + } + + public async waitForVisualizationRenderingStabilized() { + // assuming rendering is done when data-rendering-count is constant within 1000 ms + await retry.waitFor('rendering count to stabilize', async () => { + const firstCount = await this.getVisualizationRenderingCount(); + log.debug(`-- firstCount=${firstCount}`); + + await common.sleep(1000); + + const secondCount = await this.getVisualizationRenderingCount(); + log.debug(`-- secondCount=${secondCount}`); + + return firstCount === secondCount; + }); + } + + public async waitForVisualization() { + await this.waitForVisualizationRenderingStabilized(); + await find.byCssSelector('.visualization'); + } + + public async getLegendEntries() { + const legendEntries = await find.allByCssSelector( + '.visLegend__button', + defaultFindTimeout * 2 + ); + return await Promise.all( + legendEntries.map(async chart => await chart.getAttribute('data-label')) + ); + } + + public async openLegendOptionColors(name: string) { + await this.waitForVisualizationRenderingStabilized(); + await retry.try(async () => { + // This click has been flaky in opening the legend, hence the retry. See + // https://github.com/elastic/kibana/issues/17468 + await testSubjects.click(`legend-${name}`); + await this.waitForVisualizationRenderingStabilized(); + // arbitrary color chosen, any available would do + const isOpen = await this.doesLegendColorChoiceExist('#EF843C'); + if (!isOpen) { + throw new Error('legend color selector not open'); + } + }); + } + + public async filterOnTableCell(column: string, row: string) { + await retry.try(async () => { + const tableVis = await testSubjects.find('tableVis'); + const cell = await tableVis.findByCssSelector( + `tbody tr:nth-child(${row}) td:nth-child(${column})` + ); + await cell.moveMouseTo(); + const filterBtn = await testSubjects.findDescendant('filterForCellValue', cell); + await filterBtn.click(); + }); + } + + public async getMarkdownText() { + const markdownContainer = await testSubjects.find('markdownBody'); + return markdownContainer.getVisibleText(); + } + + public async getMarkdownBodyDescendentText(selector: string) { + const markdownContainer = await testSubjects.find('markdownBody'); + const element = await find.descendantDisplayedByCssSelector(selector, markdownContainer); + return element.getVisibleText(); + } + + /** + * If you are writing new tests, you should rather look into getTableVisContent method instead. + */ + public async getTableVisData() { + return await testSubjects.getVisibleText('paginated-table-body'); + } + + /** + * This function is the newer function to retrieve data from within a table visualization. + * It uses a better return format, than the old getTableVisData, by properly splitting + * cell values into arrays. Please use this function for newer tests. + */ + public async getTableVisContent({ stripEmptyRows = true } = {}) { + return await retry.try(async () => { + const container = await testSubjects.find('tableVis'); + const allTables = await testSubjects.findAllDescendant('paginated-table-body', container); + + if (allTables.length === 0) { + return []; + } + + const allData = await Promise.all( + allTables.map(async t => { + let data = await table.getDataFromElement(t); + if (stripEmptyRows) { + data = data.filter(row => row.length > 0 && row.some(cell => cell.trim().length > 0)); + } + return data; + }) + ); + + if (allTables.length === 1) { + // If there was only one table we return only the data for that table + // This prevents an unnecessary array around that single table, which + // is the case we have in most tests. + return allData[0]; + } + + return allData; + }); + } + + public async getMetric() { + const elements = await find.allByCssSelector( + '[data-test-subj="visualizationLoader"] .mtrVis__container' + ); + const values = await Promise.all( + elements.map(async element => { + const text = await element.getVisibleText(); + return text; + }) + ); + return values + .filter(item => item.length > 0) + .reduce((arr: string[], item) => arr.concat(item.split('\n')), []); + } + + public async getGaugeValue() { + const elements = await find.allByCssSelector( + '[data-test-subj="visualizationLoader"] .chart svg text' + ); + const values = await Promise.all( + elements.map(async element => { + const text = await element.getVisibleText(); + return text; + }) + ); + return values.filter(item => item.length > 0); + } + } + + return new VisualizeChart(); +} diff --git a/test/functional/page_objects/visualize_editor_page.ts b/test/functional/page_objects/visualize_editor_page.ts new file mode 100644 index 0000000000000..7e512975356f3 --- /dev/null +++ b/test/functional/page_objects/visualize_editor_page.ts @@ -0,0 +1,462 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import expect from '@kbn/expect/expect.js'; +import { FtrProviderContext } from '../ftr_provider_context'; + +export function VisualizeEditorPageProvider({ getService, getPageObjects }: FtrProviderContext) { + const find = getService('find'); + const log = getService('log'); + const retry = getService('retry'); + const browser = getService('browser'); + const testSubjects = getService('testSubjects'); + const comboBox = getService('comboBox'); + const { common, header, visChart } = getPageObjects(['common', 'header', 'visChart']); + + interface IntervalOptions { + type?: 'default' | 'numeric' | 'custom'; + aggNth?: number; + append?: boolean; + } + + class VisualizeEditorPage { + public async clickDataTab() { + await testSubjects.click('visualizeEditDataLink'); + } + + public async clickOptionsTab() { + await testSubjects.click('visEditorTaboptions'); + } + + public async clickMetricsAndAxes() { + await testSubjects.click('visEditorTabadvanced'); + } + + public async clickVisEditorTab(tabName: string) { + await testSubjects.click('visEditorTab' + tabName); + await header.waitUntilLoadingHasFinished(); + } + + public async addInputControl(type?: string) { + if (type) { + const selectInput = await testSubjects.find('selectControlType'); + await selectInput.type(type); + } + await testSubjects.click('inputControlEditorAddBtn'); + await header.waitUntilLoadingHasFinished(); + } + + public async inputControlClear() { + await testSubjects.click('inputControlClearBtn'); + await header.waitUntilLoadingHasFinished(); + } + + public async inputControlSubmit() { + await testSubjects.clickWhenNotDisabled('inputControlSubmitBtn'); + await visChart.waitForVisualizationRenderingStabilized(); + } + + public async clickGo() { + const prevRenderingCount = await visChart.getVisualizationRenderingCount(); + log.debug(`Before Rendering count ${prevRenderingCount}`); + await testSubjects.clickWhenNotDisabled('visualizeEditorRenderButton'); + await visChart.waitForRenderingCount(prevRenderingCount + 1); + } + + public async removeDimension(aggNth: number) { + await testSubjects.click(`visEditorAggAccordion${aggNth} > removeDimensionBtn`); + } + + public async setFilterParams(aggNth: number, indexPattern: string, field: string) { + await comboBox.set(`indexPatternSelect-${aggNth}`, indexPattern); + await comboBox.set(`fieldSelect-${aggNth}`, field); + } + + public async setFilterRange(aggNth: number, min: string, max: string) { + const control = await testSubjects.find(`inputControl${aggNth}`); + const inputMin = await control.findByCssSelector('[name$="minValue"]'); + await inputMin.type(min); + const inputMax = await control.findByCssSelector('[name$="maxValue"]'); + await inputMax.type(max); + } + + public async clickSplitDirection(direction: string) { + const control = await testSubjects.find('visEditorSplitBy'); + const radioBtn = await control.findByCssSelector(`[title="${direction}"]`); + await radioBtn.click(); + } + + /** + * Adds new bucket + * @param bucketName bucket name, like 'X-axis', 'Split rows', 'Split series' + * @param type aggregation type, like 'buckets', 'metrics' + */ + public async clickBucket(bucketName: string, type = 'buckets') { + await testSubjects.click(`visEditorAdd_${type}`); + await find.clickByCssSelector(`[data-test-subj="visEditorAdd_${type}_${bucketName}"`); + } + + public async clickEnableCustomRanges() { + await testSubjects.click('heatmapUseCustomRanges'); + } + + public async clickAddRange() { + await testSubjects.click(`heatmapColorRange__addRangeButton`); + } + + public async setCustomRangeByIndex(index: string, from: string, to: string) { + await testSubjects.setValue(`heatmapColorRange${index}__from`, from); + await testSubjects.setValue(`heatmapColorRange${index}__to`, to); + } + + public async changeHeatmapColorNumbers(value = 6) { + const input = await testSubjects.find(`heatmapColorsNumber`); + await input.clearValueWithKeyboard(); + await input.type(`${value}`); + } + + public async getBucketErrorMessage() { + const error = await find.byCssSelector( + '[group-name="buckets"] [data-test-subj="defaultEditorAggSelect"] + .euiFormErrorText' + ); + const errorMessage = await error.getAttribute('innerText'); + log.debug(errorMessage); + return errorMessage; + } + + public async addNewFilterAggregation() { + await testSubjects.click('visEditorAddFilterButton'); + } + + public async selectField( + fieldValue: string, + groupName = 'buckets', + childAggregationType = false + ) { + log.debug(`selectField ${fieldValue}`); + const selector = ` + [group-name="${groupName}"] + [data-test-subj^="visEditorAggAccordion"].euiAccordion-isOpen + [data-test-subj="visAggEditorParams"] + ${childAggregationType ? '.visEditorAgg__subAgg' : ''} + [data-test-subj="visDefaultEditorField"] + `; + const fieldEl = await find.byCssSelector(selector); + await comboBox.setElement(fieldEl, fieldValue); + } + + public async selectOrderByMetric(aggNth: number, metric: string) { + const sortSelect = await testSubjects.find(`visEditorOrderBy${aggNth}`); + const sortMetric = await sortSelect.findByCssSelector(`option[value="${metric}"]`); + await sortMetric.click(); + } + + public async selectCustomSortMetric(aggNth: number, metric: string, field: string) { + await this.selectOrderByMetric(aggNth, 'custom'); + await this.selectAggregation(metric, 'buckets', true); + await this.selectField(field, 'buckets', true); + } + + public async selectAggregation( + aggValue: string, + groupName = 'buckets', + childAggregationType = false + ) { + const comboBoxElement = await find.byCssSelector(` + [group-name="${groupName}"] + [data-test-subj^="visEditorAggAccordion"].euiAccordion-isOpen + ${childAggregationType ? '.visEditorAgg__subAgg' : ''} + [data-test-subj="defaultEditorAggSelect"] + `); + + await comboBox.setElement(comboBoxElement, aggValue); + await common.sleep(500); + } + + /** + * Set the test for a filter aggregation. + * @param {*} filterValue the string value of the filter + * @param {*} filterIndex used when multiple filters are configured on the same aggregation + * @param {*} aggregationId the ID if the aggregation. On Tests, it start at from 2 + */ + public async setFilterAggregationValue( + filterValue: string, + filterIndex = 0, + aggregationId = 2 + ) { + await testSubjects.setValue( + `visEditorFilterInput_${aggregationId}_${filterIndex}`, + filterValue + ); + } + + public async setValue(newValue: string) { + const input = await find.byCssSelector('[data-test-subj="visEditorPercentileRanks"] input'); + await input.clearValue(); + await input.type(newValue); + } + + public async clickEditorSidebarCollapse() { + await testSubjects.click('collapseSideBarButton'); + } + + public async clickDropPartialBuckets() { + await testSubjects.click('dropPartialBucketsCheckbox'); + } + + public async setMarkdownTxt(markdownTxt: string) { + const input = await testSubjects.find('markdownTextarea'); + await input.clearValue(); + await input.type(markdownTxt); + } + + public async isSwitchChecked(selector: string) { + const checkbox = await testSubjects.find(selector); + const isChecked = await checkbox.getAttribute('aria-checked'); + return isChecked === 'true'; + } + + public async checkSwitch(selector: string) { + const isChecked = await this.isSwitchChecked(selector); + if (!isChecked) { + log.debug(`checking switch ${selector}`); + await testSubjects.click(selector); + } + } + + public async uncheckSwitch(selector: string) { + const isChecked = await this.isSwitchChecked(selector); + if (isChecked) { + log.debug(`unchecking switch ${selector}`); + await testSubjects.click(selector); + } + } + + public async setIsFilteredByCollarCheckbox(value = true) { + await retry.try(async () => { + const isChecked = await this.isSwitchChecked('isFilteredByCollarCheckbox'); + if (isChecked !== value) { + await testSubjects.click('isFilteredByCollarCheckbox'); + throw new Error('isFilteredByCollar not set correctly'); + } + }); + } + + public async setCustomLabel(label: string, index = 1) { + const customLabel = await testSubjects.find(`visEditorStringInput${index}customLabel`); + customLabel.type(label); + } + + public async selectYAxisAggregation(agg: string, field: string, label: string, index = 1) { + // index starts on the first "count" metric at 1 + // Each new metric or aggregation added to a visualization gets the next index. + // So to modify a metric or aggregation tests need to keep track of the + // order they are added. + await this.toggleOpenEditor(index); + + // select our agg + const aggSelect = await find.byCssSelector( + `#visEditorAggAccordion${index} [data-test-subj="defaultEditorAggSelect"]` + ); + await comboBox.setElement(aggSelect, agg); + + const fieldSelect = await find.byCssSelector( + `#visEditorAggAccordion${index} [data-test-subj="visDefaultEditorField"]` + ); + // select our field + await comboBox.setElement(fieldSelect, field); + // enter custom label + await this.setCustomLabel(label, index); + } + + public async getField() { + return await comboBox.getComboBoxSelectedOptions('visDefaultEditorField'); + } + + public async sizeUpEditor() { + await testSubjects.click('visualizeEditorResizer'); + await browser.pressKeys(browser.keys.ARROW_RIGHT); + } + + public async toggleDisabledAgg(agg: string) { + await testSubjects.click(`visEditorAggAccordion${agg} > ~toggleDisableAggregationBtn`); + await header.waitUntilLoadingHasFinished(); + } + + public async toggleAggregationEditor(agg: string) { + await find.clickByCssSelector( + `[data-test-subj="visEditorAggAccordion${agg}"] .euiAccordion__button` + ); + await header.waitUntilLoadingHasFinished(); + } + + public async toggleOtherBucket(agg = 2) { + await testSubjects.click(`visEditorAggAccordion${agg} > otherBucketSwitch`); + } + + public async toggleMissingBucket(agg = 2) { + await testSubjects.click(`visEditorAggAccordion${agg} > missingBucketSwitch`); + } + + public async toggleScaleMetrics() { + await testSubjects.click('scaleMetricsSwitch'); + } + + public async toggleAutoMode() { + await testSubjects.click('visualizeEditorAutoButton'); + } + + public async isApplyEnabled() { + const applyButton = await testSubjects.find('visualizeEditorRenderButton'); + return await applyButton.isEnabled(); + } + + public async toggleAccordion(id: string, toState = 'true') { + const toggle = await find.byCssSelector(`button[aria-controls="${id}"]`); + const toggleOpen = await toggle.getAttribute('aria-expanded'); + log.debug(`toggle ${id} expand = ${toggleOpen}`); + if (toggleOpen !== toState) { + log.debug(`toggle ${id} click()`); + await toggle.click(); + } + } + + public async toggleOpenEditor(index: number, toState = 'true') { + // index, see selectYAxisAggregation + await this.toggleAccordion(`visEditorAggAccordion${index}`, toState); + } + + public async toggleAdvancedParams(aggId: string) { + const accordion = await testSubjects.find(`advancedParams-${aggId}`); + const accordionButton = await find.descendantDisplayedByCssSelector('button', accordion); + await accordionButton.click(); + } + + public async clickReset() { + await testSubjects.click('visualizeEditorResetButton'); + await visChart.waitForVisualization(); + } + + public async clickYAxisOptions(axisId: string) { + await testSubjects.click(`toggleYAxisOptions-${axisId}`); + } + + public async clickYAxisAdvancedOptions(axisId: string) { + await testSubjects.click(`toggleYAxisAdvancedOptions-${axisId}`); + } + + public async changeYAxisFilterLabelsCheckbox(axisId: string, enabled: boolean) { + const selector = `yAxisFilterLabelsCheckbox-${axisId}`; + await testSubjects.setCheckbox(selector, enabled ? 'check' : 'uncheck'); + } + + public async setSize(newValue: string, aggId: string) { + const dataTestSubj = aggId + ? `visEditorAggAccordion${aggId} > sizeParamEditor` + : 'sizeParamEditor'; + await testSubjects.setValue(dataTestSubj, String(newValue)); + } + + public async selectChartMode(mode: string) { + const selector = await find.byCssSelector(`#seriesMode0 > option[value="${mode}"]`); + await selector.click(); + } + + public async selectYAxisScaleType(axisId: string, scaleType: string) { + const selectElement = await testSubjects.find(`scaleSelectYAxis-${axisId}`); + const selector = await selectElement.findByCssSelector(`option[value="${scaleType}"]`); + await selector.click(); + } + + public async selectYAxisMode(mode: string) { + const selector = await find.byCssSelector(`#valueAxisMode0 > option[value="${mode}"]`); + await selector.click(); + } + + public async setAxisExtents(min: string, max: string, axisId = 'ValueAxis-1') { + await this.toggleAccordion(`yAxisAccordion${axisId}`); + await this.toggleAccordion(`yAxisOptionsAccordion${axisId}`); + + await testSubjects.click('yAxisSetYExtents'); + await testSubjects.setValue('yAxisYExtentsMax', max); + await testSubjects.setValue('yAxisYExtentsMin', min); + } + + public async selectAggregateWith(fieldValue: string) { + await testSubjects.selectValue('visDefaultEditorAggregateWith', fieldValue); + } + + public async setInterval(newValue: string, options: IntervalOptions = {}) { + const { type = 'default', aggNth = 2, append = false } = options; + log.debug(`visEditor.setInterval(${newValue}, {${type}, ${aggNth}, ${append}})`); + if (type === 'default') { + await comboBox.set('visEditorInterval', newValue); + } else if (type === 'custom') { + await comboBox.setCustom('visEditorInterval', newValue); + } else { + if (append) { + await testSubjects.append(`visEditorInterval${aggNth}`, String(newValue)); + } else { + await testSubjects.setValue(`visEditorInterval${aggNth}`, String(newValue)); + } + } + } + + public async getInterval() { + return await comboBox.getComboBoxSelectedOptions('visEditorInterval'); + } + + public async getNumericInterval(agg = 2) { + return await testSubjects.getAttribute(`visEditorInterval${agg}`, 'value'); + } + + public async clickMetricEditor() { + await find.clickByCssSelector('[group-name="metrics"] .euiAccordion__button'); + } + + public async clickMetricByIndex(index: number) { + const metrics = await find.allByCssSelector( + '[data-test-subj="visualizationLoader"] .mtrVis .mtrVis__container' + ); + expect(metrics.length).greaterThan(index); + await metrics[index].click(); + } + + public async setSelectByOptionText(selectId: string, optionText: string) { + const selectField = await find.byCssSelector(`#${selectId}`); + const options = await find.allByCssSelector(`#${selectId} > option`); + const $ = await selectField.parseDomContent(); + const optionsText = $('option') + .toArray() + .map(option => $(option).text()); + const optionIndex = optionsText.indexOf(optionText); + + if (optionIndex === -1) { + throw new Error( + `Unable to find option '${optionText}' in select ${selectId}. Available options: ${optionsText.join( + ',' + )}` + ); + } + await options[optionIndex].click(); + } + } + + return new VisualizeEditorPage(); +} diff --git a/test/functional/page_objects/visualize_page.js b/test/functional/page_objects/visualize_page.js deleted file mode 100644 index c1ea8be9be98b..0000000000000 --- a/test/functional/page_objects/visualize_page.js +++ /dev/null @@ -1,1402 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { VisualizeConstants } from '../../../src/legacy/core_plugins/kibana/public/visualize/np_ready/visualize_constants'; -import Bluebird from 'bluebird'; -import expect from '@kbn/expect'; - -export function VisualizePageProvider({ getService, getPageObjects, updateBaselines }) { - const browser = getService('browser'); - const config = getService('config'); - const testSubjects = getService('testSubjects'); - const retry = getService('retry'); - const find = getService('find'); - const log = getService('log'); - const inspector = getService('inspector'); - const screenshot = getService('screenshots'); - const table = getService('table'); - const globalNav = getService('globalNav'); - const PageObjects = getPageObjects(['common', 'header']); - const defaultFindTimeout = config.get('timeouts.find'); - const comboBox = getService('comboBox'); - - class VisualizePage { - get index() { - return { - LOGSTASH_TIME_BASED: 'logstash-*', - LOGSTASH_NON_TIME_BASED: 'logstash*', - }; - } - - async gotoVisualizationLandingPage() { - log.debug('gotoVisualizationLandingPage'); - await PageObjects.common.navigateToApp('visualize'); - } - - async checkListingSelectAllCheckbox() { - const element = await testSubjects.find('checkboxSelectAll'); - const isSelected = await element.isSelected(); - if (!isSelected) { - log.debug(`checking checkbox "checkboxSelectAll"`); - await testSubjects.click('checkboxSelectAll'); - } - } - - async navigateToNewVisualization() { - log.debug('navigateToApp visualize'); - await PageObjects.common.navigateToApp('visualize'); - await this.clickNewVisualization(); - await this.waitForVisualizationSelectPage(); - } - - async clickNewVisualization() { - // newItemButton button is only visible when there are items in the listing table is displayed. - let exists = await testSubjects.exists('newItemButton'); - if (exists) { - return await testSubjects.click('newItemButton'); - } - - exists = await testSubjects.exists('createVisualizationPromptButton'); - // no viz exist, click createVisualizationPromptButton to create new dashboard - return await this.createVisualizationPromptButton(); - } - - /* - This method should use retry loop to delete visualizations from multiple pages until we find the createVisualizationPromptButton. - Perhaps it *could* set the page size larger than the default 10, but it might still need to loop anyway. - */ - async deleteAllVisualizations() { - await retry.try(async () => { - await this.checkListingSelectAllCheckbox(); - await this.clickDeleteSelected(); - await PageObjects.common.clickConfirmOnModal(); - await testSubjects.find('createVisualizationPromptButton'); - }); - } - - async createSimpleMarkdownViz(vizName) { - await this.gotoVisualizationLandingPage(); - await this.navigateToNewVisualization(); - await this.clickMarkdownWidget(); - await this.setMarkdownTxt(vizName); - await this.clickGo(); - await this.saveVisualization(vizName); - } - - async createVisualizationPromptButton() { - await testSubjects.click('createVisualizationPromptButton'); - } - - async getSearchFilter() { - const searchFilter = await find.allByCssSelector('.euiFieldSearch'); - return searchFilter[0]; - } - - async clearFilter() { - const searchFilter = await this.getSearchFilter(); - await searchFilter.clearValue(); - await searchFilter.click(); - } - - async searchForItemWithName(name) { - log.debug(`searchForItemWithName: ${name}`); - - await retry.try(async () => { - const searchFilter = await this.getSearchFilter(); - await searchFilter.clearValue(); - await searchFilter.click(); - // Note: this replacement of - to space is to preserve original logic but I'm not sure why or if it's needed. - await searchFilter.type(name.replace('-', ' ')); - await PageObjects.common.pressEnterKey(); - }); - - await PageObjects.header.waitUntilLoadingHasFinished(); - } - - async clickDeleteSelected() { - await testSubjects.click('deleteSelectedItems'); - } - - async getCreatePromptExists() { - log.debug('getCreatePromptExists'); - return await testSubjects.exists('createVisualizationPromptButton'); - } - - async getCountOfItemsInListingTable() { - const elements = await find.allByCssSelector('[data-test-subj^="visListingTitleLink"]'); - return elements.length; - } - - async waitForVisualizationSelectPage() { - await retry.try(async () => { - const visualizeSelectTypePage = await testSubjects.find('visNewDialogTypes'); - if (!(await visualizeSelectTypePage.isDisplayed())) { - throw new Error('wait for visualization select page'); - } - }); - } - - async clickVisType(type) { - await testSubjects.click(`visType-${type}`); - await PageObjects.header.waitUntilLoadingHasFinished(); - } - - async clickAreaChart() { - await this.clickVisType('area'); - } - - async clickDataTable() { - await this.clickVisType('table'); - } - - async clickLineChart() { - await this.clickVisType('line'); - } - - async clickRegionMap() { - await this.clickVisType('region_map'); - } - - async clickMarkdownWidget() { - await this.clickVisType('markdown'); - } - - // clickBucket(bucketName) 'X-axis', 'Split area', 'Split chart' - async clickBucket(bucketName, type = 'buckets') { - await testSubjects.click(`visEditorAdd_${type}`); - await find.clickByCssSelector(`[data-test-subj="visEditorAdd_${type}_${bucketName}"`); - } - - async clickMetric() { - await this.clickVisType('metric'); - } - - async clickGauge() { - await this.clickVisType('gauge'); - } - - async clickPieChart() { - await this.clickVisType('pie'); - } - - async clickTileMap() { - await this.clickVisType('tile_map'); - } - - async clickTagCloud() { - await this.clickVisType('tagcloud'); - } - - async clickVega() { - await this.clickVisType('vega'); - } - - async clickVisualBuilder() { - await this.clickVisType('metrics'); - } - - async clickEditorSidebarCollapse() { - await testSubjects.click('collapseSideBarButton'); - } - - async selectTagCloudTag(tagDisplayText) { - await testSubjects.click(tagDisplayText); - await PageObjects.header.waitUntilLoadingHasFinished(); - } - - async getTextTag() { - await this.waitForVisualization(); - const elements = await find.allByCssSelector('text'); - return await Promise.all(elements.map(async element => await element.getVisibleText())); - } - - async getTextSizes() { - const tags = await find.allByCssSelector('text'); - async function returnTagSize(tag) { - const style = await tag.getAttribute('style'); - return style.match(/font-size: ([^;]*);/)[1]; - } - return await Promise.all(tags.map(returnTagSize)); - } - - async clickVerticalBarChart() { - await this.clickVisType('histogram'); - } - - async clickHeatmapChart() { - await this.clickVisType('heatmap'); - } - - async clickInputControlVis() { - await this.clickVisType('input_control_vis'); - } - - async getChartTypes() { - const chartTypeField = await testSubjects.find('visNewDialogTypes'); - const chartTypes = await chartTypeField.findAllByTagName('button'); - async function getChartType(chart) { - const label = await testSubjects.findDescendant('visTypeTitle', chart); - return await label.getVisibleText(); - } - const getChartTypesPromises = chartTypes.map(getChartType); - return await Promise.all(getChartTypesPromises); - } - - async selectVisSourceIfRequired() { - log.debug('selectVisSourceIfRequired'); - const selectPage = await testSubjects.findAll('visualizeSelectSearch'); - if (selectPage.length) { - log.debug('a search is required for this visualization'); - await this.clickNewSearch(); - } - } - - async isBetaInfoShown() { - return await testSubjects.exists('betaVisInfo'); - } - - async getBetaTypeLinks() { - return await find.allByCssSelector('[data-vis-stage="beta"]'); - } - - async getExperimentalTypeLinks() { - return await find.allByCssSelector('[data-vis-stage="experimental"]'); - } - - async isExperimentalInfoShown() { - return await testSubjects.exists('experimentalVisInfo'); - } - - async getExperimentalInfo() { - return await testSubjects.find('experimentalVisInfo'); - } - - async clickAbsoluteButton() { - await find.clickByCssSelector( - 'ul.nav.nav-pills.nav-stacked.kbn-timepicker-modes:contains("absolute")', - defaultFindTimeout * 2 - ); - } - - async clickDropPartialBuckets() { - return await testSubjects.click('dropPartialBucketsCheckbox'); - } - - async setMarkdownTxt(markdownTxt) { - const input = await testSubjects.find('markdownTextarea'); - await input.clearValue(); - await input.type(markdownTxt); - } - - async getMarkdownText() { - const markdownContainer = await testSubjects.find('markdownBody'); - return markdownContainer.getVisibleText(); - } - - async getMarkdownBodyDescendentText(selector) { - const markdownContainer = await testSubjects.find('markdownBody'); - const element = await find.descendantDisplayedByCssSelector(selector, markdownContainer); - return element.getVisibleText(); - } - - async getVegaSpec() { - // Adapted from console_page.js:getVisibleTextFromAceEditor(). Is there a common utilities file? - const editor = await testSubjects.find('vega-editor'); - const lines = await editor.findAllByClassName('ace_line_group'); - const linesText = await Bluebird.map(lines, l => l.getVisibleText()); - return linesText.join('\n'); - } - - async getVegaViewContainer() { - return await find.byCssSelector('div.vgaVis__view'); - } - - async getVegaControlContainer() { - return await find.byCssSelector('div.vgaVis__controls'); - } - - async addInputControl(type) { - if (type) { - const selectInput = await testSubjects.find('selectControlType'); - await selectInput.type(type); - } - await testSubjects.click('inputControlEditorAddBtn'); - await PageObjects.header.waitUntilLoadingHasFinished(); - } - - async inputControlSubmit() { - await testSubjects.clickWhenNotDisabled('inputControlSubmitBtn'); - await this.waitForVisualizationRenderingStabilized(); - } - - async inputControlClear() { - await testSubjects.click('inputControlClearBtn'); - await PageObjects.header.waitUntilLoadingHasFinished(); - } - - async isChecked(selector) { - const checkbox = await testSubjects.find(selector); - return await checkbox.isSelected(); - } - - async checkCheckbox(selector) { - const isChecked = await this.isChecked(selector); - if (!isChecked) { - log.debug(`checking checkbox ${selector}`); - await testSubjects.click(selector); - } - } - - async uncheckCheckbox(selector) { - const isChecked = await this.isChecked(selector); - if (isChecked) { - log.debug(`unchecking checkbox ${selector}`); - await testSubjects.click(selector); - } - } - - async isSwitchChecked(selector) { - const checkbox = await testSubjects.find(selector); - const isChecked = await checkbox.getAttribute('aria-checked'); - return isChecked === 'true'; - } - - async checkSwitch(selector) { - const isChecked = await this.isSwitchChecked(selector); - if (!isChecked) { - log.debug(`checking switch ${selector}`); - await testSubjects.click(selector); - } - } - - async uncheckSwitch(selector) { - const isChecked = await this.isSwitchChecked(selector); - if (isChecked) { - log.debug(`unchecking switch ${selector}`); - await testSubjects.click(selector); - } - } - - async setSelectByOptionText(selectId, optionText) { - const selectField = await find.byCssSelector(`#${selectId}`); - const options = await find.allByCssSelector(`#${selectId} > option`); - const $ = await selectField.parseDomContent(); - const optionsText = $('option') - .toArray() - .map(option => $(option).text()); - const optionIndex = optionsText.indexOf(optionText); - - if (optionIndex === -1) { - throw new Error( - `Unable to find option '${optionText}' in select ${selectId}. Available options: ${optionsText.join( - ',' - )}` - ); - } - await options[optionIndex].click(); - } - - async getSideEditorExists() { - return await find.existsByCssSelector('.collapsible-sidebar'); - } - - async setInspectorTablePageSize(size) { - const panel = await testSubjects.find('inspectorPanel'); - await find.clickByButtonText('Rows per page: 20', panel); - // The buttons for setting table page size are in a popover element. This popover - // element appears as if it's part of the inspectorPanel but it's really attached - // to the body element by a portal. - const tableSizesPopover = await find.byCssSelector('.euiPanel'); - await find.clickByButtonText(`${size} rows`, tableSizesPopover); - } - - async getMetric() { - const elements = await find.allByCssSelector( - '[data-test-subj="visualizationLoader"] .mtrVis__container' - ); - const values = await Promise.all( - elements.map(async element => { - const text = await element.getVisibleText(); - return text; - }) - ); - return values - .filter(item => item.length > 0) - .reduce((arr, item) => arr.concat(item.split('\n')), []); - } - - async getGaugeValue() { - const elements = await find.allByCssSelector( - '[data-test-subj="visualizationLoader"] .chart svg text' - ); - const values = await Promise.all( - elements.map(async element => { - const text = await element.getVisibleText(); - return text; - }) - ); - return values.filter(item => item.length > 0); - } - - async clickMetricEditor() { - await find.clickByCssSelector('[group-name="metrics"] .euiAccordion__button'); - } - - async clickMetricByIndex(index) { - log.debug(`clickMetricByIndex(${index})`); - const metrics = await find.allByCssSelector( - '[data-test-subj="visualizationLoader"] .mtrVis .mtrVis__container' - ); - expect(metrics.length).greaterThan(index); - await metrics[index].click(); - } - - async clickNewSearch(indexPattern = this.index.LOGSTASH_TIME_BASED) { - await testSubjects.click(`savedObjectTitle${indexPattern.split(' ').join('-')}`); - await PageObjects.header.waitUntilLoadingHasFinished(); - } - - async clickSavedSearch(savedSearchName) { - await testSubjects.click(`savedObjectTitle${savedSearchName.split(' ').join('-')}`); - await PageObjects.header.waitUntilLoadingHasFinished(); - } - - async clickUnlinkSavedSearch() { - await testSubjects.doubleClick('unlinkSavedSearch'); - await PageObjects.header.waitUntilLoadingHasFinished(); - } - - async setValue(newValue) { - const input = await find.byCssSelector('[data-test-subj="visEditorPercentileRanks"] input'); - await input.clearValue(); - await input.type(newValue); - } - - async selectSearch(searchName) { - await find.clickByLinkText(searchName); - } - - async getErrorMessage() { - const element = await find.byCssSelector('.item>h4'); - return await element.getVisibleText(); - } - - async selectAggregation(myString, groupName = 'buckets', childAggregationType = null) { - const comboBoxElement = await find.byCssSelector(` - [group-name="${groupName}"] - [data-test-subj^="visEditorAggAccordion"].euiAccordion-isOpen - ${childAggregationType ? '.visEditorAgg__subAgg' : ''} - [data-test-subj="defaultEditorAggSelect"] - `); - - await comboBox.setElement(comboBoxElement, myString); - await PageObjects.common.sleep(500); - } - - async applyFilters() { - return await testSubjects.click('filterBarApplyFilters'); - } - /** - * Set the test for a filter aggregation. - * @param {*} filterValue the string value of the filter - * @param {*} filterIndex used when multiple filters are configured on the same aggregation - * @param {*} aggregationId the ID if the aggregation. On Tests, it start at from 2 - */ - async setFilterAggregationValue(filterValue, filterIndex = 0, aggregationId = 2) { - await testSubjects.setValue( - `visEditorFilterInput_${aggregationId}_${filterIndex}`, - filterValue - ); - } - - async addNewFilterAggregation() { - return await testSubjects.click('visEditorAddFilterButton'); - } - - async toggleOpenEditor(index, toState = 'true') { - // index, see selectYAxisAggregation - await this.toggleAccordion(`visEditorAggAccordion${index}`, toState); - } - - async toggleAccordion(id, toState = 'true') { - const toggle = await find.byCssSelector(`button[aria-controls="${id}"]`); - const toggleOpen = await toggle.getAttribute('aria-expanded'); - log.debug(`toggle ${id} expand = ${toggleOpen}`); - if (toggleOpen !== toState) { - log.debug(`toggle ${id} click()`); - await toggle.click(); - } - } - - async toggleAdvancedParams(aggId) { - const accordion = await testSubjects.find(`advancedParams-${aggId}`); - const accordionButton = await find.descendantDisplayedByCssSelector('button', accordion); - await accordionButton.click(); - } - - async selectYAxisAggregation(agg, field, label, index = 1) { - // index starts on the first "count" metric at 1 - // Each new metric or aggregation added to a visualization gets the next index. - // So to modify a metric or aggregation tests need to keep track of the - // order they are added. - await this.toggleOpenEditor(index); - - // select our agg - const aggSelect = await find.byCssSelector( - `#visEditorAggAccordion${index} [data-test-subj="defaultEditorAggSelect"]` - ); - await comboBox.setElement(aggSelect, agg); - - const fieldSelect = await find.byCssSelector( - `#visEditorAggAccordion${index} [data-test-subj="visDefaultEditorField"]` - ); - // select our field - await comboBox.setElement(fieldSelect, field); - // enter custom label - await this.setCustomLabel(label, index); - } - - async setCustomLabel(label, index = 1) { - const customLabel = await testSubjects.find(`visEditorStringInput${index}customLabel`); - customLabel.type(label); - } - - async setAxisExtents(min, max, axisId = 'ValueAxis-1') { - await this.toggleAccordion(`yAxisAccordion${axisId}`); - await this.toggleAccordion(`yAxisOptionsAccordion${axisId}`); - - await testSubjects.click('yAxisSetYExtents'); - await testSubjects.setValue('yAxisYExtentsMax', max); - await testSubjects.setValue('yAxisYExtentsMin', min); - } - - async getField() { - return await comboBox.getComboBoxSelectedOptions('visDefaultEditorField'); - } - - async selectField(fieldValue, groupName = 'buckets', childAggregationType = null) { - log.debug(`selectField ${fieldValue}`); - const selector = ` - [group-name="${groupName}"] - [data-test-subj^="visEditorAggAccordion"].euiAccordion-isOpen - [data-test-subj="visAggEditorParams"] - ${childAggregationType ? '.visEditorAgg__subAgg' : ''} - [data-test-subj="visDefaultEditorField"] - `; - const fieldEl = await find.byCssSelector(selector); - await comboBox.setElement(fieldEl, fieldValue); - } - - async selectAggregateWith(fieldValue) { - await testSubjects.selectValue('visDefaultEditorAggregateWith', fieldValue); - } - - async getInterval() { - return await comboBox.getComboBoxSelectedOptions('visEditorInterval'); - } - - async setInterval(newValue) { - log.debug(`Visualize.setInterval(${newValue})`); - return await comboBox.set('visEditorInterval', newValue); - } - - async setCustomInterval(newValue) { - log.debug(`Visualize.setCustomInterval(${newValue})`); - return await comboBox.setCustom('visEditorInterval', newValue); - } - - async getNumericInterval(agg = 2) { - return await testSubjects.getAttribute(`visEditorInterval${agg}`, 'value'); - } - - async setNumericInterval(newValue, { append } = {}, agg = 2) { - if (append) { - await testSubjects.append(`visEditorInterval${agg}`, String(newValue)); - } else { - await testSubjects.setValue(`visEditorInterval${agg}`, String(newValue)); - } - } - - async setSize(newValue, aggId) { - const dataTestSubj = aggId - ? `visEditorAggAccordion${aggId} > sizeParamEditor` - : 'sizeParamEditor'; - await testSubjects.setValue(dataTestSubj, String(newValue)); - } - - async toggleDisabledAgg(agg) { - await testSubjects.click(`visEditorAggAccordion${agg} > ~toggleDisableAggregationBtn`); - await PageObjects.header.waitUntilLoadingHasFinished(); - } - - async toggleAggregationEditor(agg) { - await find.clickByCssSelector( - `[data-test-subj="visEditorAggAccordion${agg}"] .euiAccordion__button` - ); - await PageObjects.header.waitUntilLoadingHasFinished(); - } - - async toggleOtherBucket(agg = 2) { - return await testSubjects.click(`visEditorAggAccordion${agg} > otherBucketSwitch`); - } - - async toggleMissingBucket(agg = 2) { - return await testSubjects.click(`visEditorAggAccordion${agg} > missingBucketSwitch`); - } - - async toggleScaleMetrics() { - return await testSubjects.click('scaleMetricsSwitch'); - } - - async isApplyEnabled() { - const applyButton = await testSubjects.find('visualizeEditorRenderButton'); - return await applyButton.isEnabled(); - } - - async clickGo() { - const prevRenderingCount = await this.getVisualizationRenderingCount(); - log.debug(`Before Rendering count ${prevRenderingCount}`); - await testSubjects.clickWhenNotDisabled('visualizeEditorRenderButton'); - await this.waitForRenderingCount(prevRenderingCount + 1); - } - - async clickReset() { - await testSubjects.click('visualizeEditorResetButton'); - await this.waitForVisualization(); - } - - async toggleAutoMode() { - await testSubjects.click('visualizeEditorAutoButton'); - } - - async sizeUpEditor() { - await testSubjects.click('visualizeEditorResizer'); - await browser.pressKeys(browser.keys.ARROW_RIGHT); - } - - async clickOptions() { - await find.clickByPartialLinkText('Options'); - await PageObjects.header.waitUntilLoadingHasFinished(); - } - - async changeHeatmapColorNumbers(value = 6) { - const input = await testSubjects.find(`heatmapColorsNumber`); - await input.clearValueWithKeyboard(); - await input.type(`${value}`); - } - - async clickMetricsAndAxes() { - await testSubjects.click('visEditorTabadvanced'); - } - - async clickOptionsTab() { - await testSubjects.click('visEditorTaboptions'); - } - - async clickEnableCustomRanges() { - await testSubjects.click('heatmapUseCustomRanges'); - } - - async clickAddRange() { - await testSubjects.click(`heatmapColorRange__addRangeButton`); - } - - async setCustomRangeByIndex(index, from, to) { - await testSubjects.setValue(`heatmapColorRange${index}__from`, from); - await testSubjects.setValue(`heatmapColorRange${index}__to`, to); - } - - async clickYAxisOptions(axisId) { - await testSubjects.click(`toggleYAxisOptions-${axisId}`); - } - - async clickYAxisAdvancedOptions(axisId) { - await testSubjects.click(`toggleYAxisAdvancedOptions-${axisId}`); - } - - async changeYAxisFilterLabelsCheckbox(axisId, enabled) { - const selector = `yAxisFilterLabelsCheckbox-${axisId}`; - enabled ? await this.checkCheckbox(selector) : await this.uncheckCheckbox(selector); - } - - async selectChartMode(mode) { - const selector = await find.byCssSelector(`#seriesMode0 > option[value="${mode}"]`); - await selector.click(); - } - - async selectYAxisScaleType(axisId, scaleType) { - const selectElement = await testSubjects.find(`scaleSelectYAxis-${axisId}`); - const selector = await selectElement.findByCssSelector(`option[value="${scaleType}"]`); - await selector.click(); - } - - async selectYAxisMode(mode) { - const selector = await find.byCssSelector(`#valueAxisMode0 > option[value="${mode}"]`); - await selector.click(); - } - - async clickData() { - await testSubjects.click('visualizeEditDataLink'); - } - - async clickVisEditorTab(tabName) { - await testSubjects.click('visEditorTab' + tabName); - await PageObjects.header.waitUntilLoadingHasFinished(); - } - - async selectWMS() { - await find.clickByCssSelector('input[name="wms.enabled"]'); - await PageObjects.header.waitUntilLoadingHasFinished(); - } - - async ensureSavePanelOpen() { - log.debug('ensureSavePanelOpen'); - await PageObjects.header.waitUntilLoadingHasFinished(); - const isOpen = await testSubjects.exists('savedObjectSaveModal', { timeout: 5000 }); - if (!isOpen) { - await testSubjects.click('visualizeSaveButton'); - } - } - - async saveVisualization(vizName, { saveAsNew = false } = {}) { - await this.ensureSavePanelOpen(); - await testSubjects.setValue('savedObjectTitle', vizName); - if (saveAsNew) { - log.debug('Check save as new visualization'); - await testSubjects.click('saveAsNewCheckbox'); - } - log.debug('Click Save Visualization button'); - - await testSubjects.click('confirmSaveSavedObjectButton'); - - // Confirm that the Visualization has actually been saved - await testSubjects.existOrFail('saveVisualizationSuccess'); - const message = await PageObjects.common.closeToast(); - await PageObjects.header.waitUntilLoadingHasFinished(); - await PageObjects.common.waitForSaveModalToClose(); - - return message; - } - - async saveVisualizationExpectSuccess(vizName, { saveAsNew = false } = {}) { - const saveMessage = await this.saveVisualization(vizName, { saveAsNew }); - if (!saveMessage) { - throw new Error( - `Expected saveVisualization to respond with the saveMessage from the toast, got ${saveMessage}` - ); - } - } - - async saveVisualizationExpectSuccessAndBreadcrumb(vizName, { saveAsNew = false } = {}) { - await this.saveVisualizationExpectSuccess(vizName, { saveAsNew }); - await retry.waitFor( - 'last breadcrumb to have new vis name', - async () => (await globalNav.getLastBreadcrumb()) === vizName - ); - } - - async clickLoadSavedVisButton() { - // TODO: Use a test subject selector once we rewrite breadcrumbs to accept each breadcrumb - // element as a child instead of building the breadcrumbs dynamically. - await find.clickByCssSelector('[href="#/visualize"]'); - } - - async filterVisByName(vizName) { - const input = await find.byCssSelector('input[name="filter"]'); - await input.click(); - // can't uses dashes in saved visualizations when filtering - // or extended character sets - // https://github.com/elastic/kibana/issues/6300 - await input.type(vizName.replace('-', ' ')); - await PageObjects.header.waitUntilLoadingHasFinished(); - } - - async clickVisualizationByName(vizName) { - log.debug('clickVisualizationByLinkText(' + vizName + ')'); - return find.clickByPartialLinkText(vizName); - } - - async loadSavedVisualization(vizName, { navigateToVisualize = true } = {}) { - if (navigateToVisualize) { - await this.clickLoadSavedVisButton(); - } - await this.openSavedVisualization(vizName); - } - - async openSavedVisualization(vizName) { - await this.clickVisualizationByName(vizName); - await PageObjects.header.waitUntilLoadingHasFinished(); - } - - async getXAxisLabels() { - const xAxis = await find.byCssSelector('.visAxis--x.visAxis__column--bottom'); - const $ = await xAxis.parseDomContent(); - return $('.x > g > text') - .toArray() - .map(tick => - $(tick) - .text() - .trim() - ); - } - - async getYAxisLabels() { - const yAxis = await find.byCssSelector('.visAxis__column--y.visAxis__column--left'); - const $ = await yAxis.parseDomContent(); - return $('.y > g > text') - .toArray() - .map(tick => - $(tick) - .text() - .trim() - ); - } - - /** - * Removes chrome and takes a small screenshot of a vis to compare against a baseline. - * @param {string} name The name of the baseline image. - * @param {object} opts Options object. - * @param {number} opts.threshold Threshold for allowed variance when comparing images. - */ - async expectVisToMatchScreenshot(name, opts = { threshold: 0.05 }) { - log.debug(`expectVisToMatchScreenshot(${name})`); - - // Collapse sidebar and inject some CSS to hide the nav so we have a focused screenshot - await this.clickEditorSidebarCollapse(); - await this.waitForVisualizationRenderingStabilized(); - await browser.execute(` - var el = document.createElement('style'); - el.id = '__data-test-style'; - el.innerHTML = '[data-test-subj="headerGlobalNav"] { display: none; } '; - el.innerHTML += '[data-test-subj="top-nav"] { display: none; } '; - el.innerHTML += '[data-test-subj="experimentalVisInfo"] { display: none; } '; - document.body.appendChild(el); - `); - - const percentDifference = await screenshot.compareAgainstBaseline(name, updateBaselines); - - // Reset the chart to its original state - await browser.execute(` - var el = document.getElementById('__data-test-style'); - document.body.removeChild(el); - `); - await this.clickEditorSidebarCollapse(); - await this.waitForVisualizationRenderingStabilized(); - expect(percentDifference).to.be.lessThan(opts.threshold); - } - - /* - ** This method gets the chart data and scales it based on chart height and label. - ** Returns an array of height values - */ - async getAreaChartData(dataLabel, axis = 'ValueAxis-1') { - const yAxisRatio = await this.getChartYAxisRatio(axis); - - const rectangle = await find.byCssSelector('rect.background'); - const yAxisHeight = await rectangle.getAttribute('height'); - log.debug(`height --------- ${yAxisHeight}`); - - const path = await retry.try( - async () => - await find.byCssSelector(`path[data-label="${dataLabel}"]`, defaultFindTimeout * 2) - ); - const data = await path.getAttribute('d'); - log.debug(data); - // This area chart data starts with a 'M'ove to a x,y location, followed - // by a bunch of 'L'ines from that point to the next. Those points are - // the values we're going to use to calculate the data values we're testing. - // So git rid of the one 'M' and split the rest on the 'L's. - const tempArray = data - .replace('M ', '') - .replace('M', '') - .replace(/ L /g, 'L') - .replace(/ /g, ',') - .split('L'); - const chartSections = tempArray.length / 2; - // log.debug('chartSections = ' + chartSections + ' height = ' + yAxisHeight + ' yAxisLabel = ' + yAxisLabel); - const chartData = []; - for (let i = 0; i < chartSections; i++) { - chartData[i] = Math.round((yAxisHeight - tempArray[i].split(',')[1]) * yAxisRatio); - log.debug('chartData[i] =' + chartData[i]); - } - return chartData; - } - - /* - ** This method returns the paths that compose an area chart. - */ - async getAreaChartPaths(dataLabel) { - const path = await retry.try( - async () => - await find.byCssSelector(`path[data-label="${dataLabel}"]`, defaultFindTimeout * 2) - ); - const data = await path.getAttribute('d'); - log.debug(data); - // This area chart data starts with a 'M'ove to a x,y location, followed - // by a bunch of 'L'ines from that point to the next. Those points are - // the values we're going to use to calculate the data values we're testing. - // So git rid of the one 'M' and split the rest on the 'L's. - return data.split('L'); - } - - // The current test shows dots, not a line. This function gets the dots and normalizes their height. - async getLineChartData(dataLabel = 'Count', axis = 'ValueAxis-1') { - // 1). get the range/pixel ratio - const yAxisRatio = await this.getChartYAxisRatio(axis); - // 2). find and save the y-axis pixel size (the chart height) - const rectangle = await find.byCssSelector('clipPath rect'); - const yAxisHeight = await rectangle.getAttribute('height'); - // 3). get the visWrapper__chart elements - const chartTypes = await retry.try( - async () => - await find.allByCssSelector( - `.visWrapper__chart circle[data-label="${dataLabel}"][fill-opacity="1"]`, - defaultFindTimeout * 2 - ) - ); - // 4). for each chart element, find the green circle, then the cy position - const chartData = await Promise.all( - chartTypes.map(async chart => { - const cy = await chart.getAttribute('cy'); - // the point_series_options test has data in the billions range and - // getting 11 digits of precision with these calculations is very hard - return Math.round(((yAxisHeight - cy) * yAxisRatio).toPrecision(6)); - }) - ); - - return chartData; - } - - // this is ALMOST identical to DiscoverPage.getBarChartData - async getBarChartData(dataLabel = 'Count', axis = 'ValueAxis-1') { - // 1). get the range/pixel ratio - const yAxisRatio = await this.getChartYAxisRatio(axis); - // 3). get the visWrapper__chart elements - const svg = await find.byCssSelector('div.chart'); - const $ = await svg.parseDomContent(); - const chartData = $(`g > g.series > rect[data-label="${dataLabel}"]`) - .toArray() - .map(chart => { - const barHeight = $(chart).attr('height'); - return Math.round(barHeight * yAxisRatio); - }); - - return chartData; - } - - // Returns value per pixel - async getChartYAxisRatio(axis = 'ValueAxis-1') { - // 1). get the maximum chart Y-Axis marker value and Y position - const maxYAxisChartMarker = await retry.try( - async () => - await find.byCssSelector( - `div.visAxis__splitAxes--y > div > svg > g.${axis} > g:last-of-type.tick` - ) - ); - const maxYLabel = (await maxYAxisChartMarker.getVisibleText()).replace(/,/g, ''); - const maxYLabelYPosition = (await maxYAxisChartMarker.getPosition()).y; - log.debug(`maxYLabel = ${maxYLabel}, maxYLabelYPosition = ${maxYLabelYPosition}`); - - // 2). get the minimum chart Y-Axis marker value and Y position - const minYAxisChartMarker = await find.byCssSelector( - 'div.visAxis__column--y.visAxis__column--left > div > div > svg:nth-child(2) > g > g:nth-child(1).tick' - ); - const minYLabel = (await minYAxisChartMarker.getVisibleText()).replace(',', ''); - const minYLabelYPosition = (await minYAxisChartMarker.getPosition()).y; - return (maxYLabel - minYLabel) / (minYLabelYPosition - maxYLabelYPosition); - } - - async getHeatmapData() { - const chartTypes = await retry.try( - async () => await find.allByCssSelector('svg > g > g.series rect', defaultFindTimeout * 2) - ); - log.debug('rects=' + chartTypes); - async function getChartType(chart) { - return await chart.getAttribute('data-label'); - } - const getChartTypesPromises = chartTypes.map(getChartType); - return await Promise.all(getChartTypesPromises); - } - - async expectError() { - return await testSubjects.existOrFail('visLibVisualizeError'); - } - - async getChartAreaWidth() { - const rect = await retry.try(async () => find.byCssSelector('clipPath rect')); - return await rect.getAttribute('width'); - } - - async getChartAreaHeight() { - const rect = await retry.try(async () => find.byCssSelector('clipPath rect')); - return await rect.getAttribute('height'); - } - - /** - * If you are writing new tests, you should rather look into getTableVisContent method instead. - */ - async getTableVisData() { - return await testSubjects.getVisibleText('paginated-table-body'); - } - - /** - * This function is the newer function to retrieve data from within a table visualization. - * It uses a better return format, than the old getTableVisData, by properly splitting - * cell values into arrays. Please use this function for newer tests. - */ - async getTableVisContent({ stripEmptyRows = true } = {}) { - return await retry.try(async () => { - const container = await testSubjects.find('tableVis'); - const allTables = await testSubjects.findAllDescendant('paginated-table-body', container); - - if (allTables.length === 0) { - return []; - } - - const allData = await Promise.all( - allTables.map(async t => { - let data = await table.getDataFromElement(t); - if (stripEmptyRows) { - data = data.filter(row => row.length > 0 && row.some(cell => cell.trim().length > 0)); - } - return data; - }) - ); - - if (allTables.length === 1) { - // If there was only one table we return only the data for that table - // This prevents an unnecessary array around that single table, which - // is the case we have in most tests. - return allData[0]; - } - - return allData; - }); - } - - async toggleIsFilteredByCollarCheckbox() { - await testSubjects.click('isFilteredByCollarCheckbox'); - } - - async setIsFilteredByCollarCheckbox(value = true) { - await retry.try(async () => { - const isChecked = await this.isSwitchChecked('isFilteredByCollarCheckbox'); - if (isChecked !== value) { - await testSubjects.click('isFilteredByCollarCheckbox'); - throw new Error('isFilteredByCollar not set correctly'); - } - }); - } - - async getMarkdownData() { - const markdown = await retry.try(async () => find.byCssSelector('visualize')); - return await markdown.getVisibleText(); - } - - async getVisualizationRenderingCount() { - const visualizationLoader = await testSubjects.find('visualizationLoader'); - const renderingCount = await visualizationLoader.getAttribute('data-rendering-count'); - return Number(renderingCount); - } - - async waitForRenderingCount(minimumCount = 1) { - await retry.waitFor( - `rendering count to be greater than or equal to [${minimumCount}]`, - async () => { - const currentRenderingCount = await this.getVisualizationRenderingCount(); - log.debug(`-- currentRenderingCount=${currentRenderingCount}`); - return currentRenderingCount >= minimumCount; - } - ); - } - - async waitForVisualizationRenderingStabilized() { - //assuming rendering is done when data-rendering-count is constant within 1000 ms - await retry.waitFor('rendering count to stabilize', async () => { - const firstCount = await this.getVisualizationRenderingCount(); - log.debug(`-- firstCount=${firstCount}`); - - await PageObjects.common.sleep(1000); - - const secondCount = await this.getVisualizationRenderingCount(); - log.debug(`-- secondCount=${secondCount}`); - - return firstCount === secondCount; - }); - } - - async waitForVisualization() { - await this.waitForVisualizationRenderingStabilized(); - return await find.byCssSelector('.visualization'); - } - - async waitForVisualizationSavedToastGone() { - return await testSubjects.waitForDeleted('saveVisualizationSuccess'); - } - - async getZoomSelectors(zoomSelector) { - return await find.allByCssSelector(zoomSelector); - } - - async clickMapButton(zoomSelector, waitForLoading) { - await retry.try(async () => { - const zooms = await this.getZoomSelectors(zoomSelector); - await Promise.all(zooms.map(async zoom => await zoom.click())); - if (waitForLoading) { - await PageObjects.header.waitUntilLoadingHasFinished(); - } - }); - } - - async getVisualizationRequest() { - log.debug('getVisualizationRequest'); - await inspector.open(); - await testSubjects.click('inspectorViewChooser'); - await testSubjects.click('inspectorViewChooserRequests'); - await testSubjects.click('inspectorRequestDetailRequest'); - return await testSubjects.getVisibleText('inspectorRequestBody'); - } - - async getVisualizationResponse() { - log.debug('getVisualizationResponse'); - await inspector.open(); - await testSubjects.click('inspectorViewChooser'); - await testSubjects.click('inspectorViewChooserRequests'); - await testSubjects.click('inspectorRequestDetailResponse'); - return await testSubjects.getVisibleText('inspectorResponseBody'); - } - - async getMapBounds() { - const request = await this.getVisualizationRequest(); - const requestObject = JSON.parse(request); - return requestObject.aggs.filter_agg.filter.geo_bounding_box['geo.coordinates']; - } - - async clickMapZoomIn(waitForLoading = true) { - await this.clickMapButton('a.leaflet-control-zoom-in', waitForLoading); - } - - async clickMapZoomOut(waitForLoading = true) { - await this.clickMapButton('a.leaflet-control-zoom-out', waitForLoading); - } - - async getMapZoomEnabled(zoomSelector) { - const zooms = await this.getZoomSelectors(zoomSelector); - const classAttributes = await Promise.all( - zooms.map(async zoom => await zoom.getAttribute('class')) - ); - return !classAttributes.join('').includes('leaflet-disabled'); - } - - async zoomAllTheWayOut() { - // we can tell we're at level 1 because zoom out is disabled - return await retry.try(async () => { - await this.clickMapZoomOut(); - const enabled = await this.getMapZoomOutEnabled(); - //should be able to zoom more as current config has 0 as min level. - if (enabled) { - throw new Error('Not fully zoomed out yet'); - } - }); - } - - async getMapZoomInEnabled() { - return await this.getMapZoomEnabled('a.leaflet-control-zoom-in'); - } - - async getMapZoomOutEnabled() { - return await this.getMapZoomEnabled('a.leaflet-control-zoom-out'); - } - - async clickMapFitDataBounds() { - return await this.clickMapButton('a.fa-crop'); - } - - async clickLandingPageBreadcrumbLink() { - log.debug('clickLandingPageBreadcrumbLink'); - await find.clickByCssSelector(`a[href="#${VisualizeConstants.LANDING_PAGE_PATH}"]`); - } - - /** - * Returns true if already on the landing page (that page doesn't have a link to itself). - * @returns {Promise} - */ - async onLandingPage() { - log.debug(`VisualizePage.onLandingPage`); - const exists = await testSubjects.exists('visualizeLandingPage'); - return exists; - } - - async gotoLandingPage() { - log.debug('VisualizePage.gotoLandingPage'); - const onPage = await this.onLandingPage(); - if (!onPage) { - await retry.try(async () => { - await this.clickLandingPageBreadcrumbLink(); - const onLandingPage = await this.onLandingPage(); - if (!onLandingPage) throw new Error('Not on the landing page.'); - }); - } - } - - async getLegendEntries() { - const legendEntries = await find.allByCssSelector( - '.visLegend__button', - defaultFindTimeout * 2 - ); - return await Promise.all( - legendEntries.map(async chart => await chart.getAttribute('data-label')) - ); - } - - async openLegendOptionColors(name) { - await this.waitForVisualizationRenderingStabilized(); - await retry.try(async () => { - // This click has been flaky in opening the legend, hence the retry. See - // https://github.com/elastic/kibana/issues/17468 - await testSubjects.click(`legend-${name}`); - await this.waitForVisualizationRenderingStabilized(); - // arbitrary color chosen, any available would do - const isOpen = await this.doesLegendColorChoiceExist('#EF843C'); - if (!isOpen) { - throw new Error('legend color selector not open'); - } - }); - } - - async filterOnTableCell(column, row) { - await retry.try(async () => { - const table = await testSubjects.find('tableVis'); - const cell = await table.findByCssSelector( - `tbody tr:nth-child(${row}) td:nth-child(${column})` - ); - await cell.moveMouseTo(); - const filterBtn = await testSubjects.findDescendant('filterForCellValue', cell); - await filterBtn.click(); - }); - } - - async toggleLegend(show = true) { - await retry.try(async () => { - const isVisible = find.byCssSelector('.visLegend'); - if ((show && !isVisible) || (!show && isVisible)) { - await testSubjects.click('vislibToggleLegend'); - } - }); - } - - async filterLegend(name) { - await this.toggleLegend(); - await testSubjects.click(`legend-${name}`); - const filters = await testSubjects.find(`legend-${name}-filters`); - const [filterIn] = await filters.findAllByCssSelector(`input`); - await filterIn.click(); - await this.waitForVisualizationRenderingStabilized(); - } - - async doesLegendColorChoiceExist(color) { - return await testSubjects.exists(`legendSelectColor-${color}`); - } - - async selectNewLegendColorChoice(color) { - await testSubjects.click(`legendSelectColor-${color}`); - } - - async doesSelectedLegendColorExist(color) { - return await testSubjects.exists(`legendSelectedColor-${color}`); - } - - async getYAxisTitle() { - const title = await find.byCssSelector('.y-axis-div .y-axis-title text'); - return await title.getVisibleText(); - } - - async selectBucketType(type) { - const bucketType = await find.byCssSelector(`[data-test-subj="${type}"]`); - return await bucketType.click(); - } - - async getBucketErrorMessage() { - const error = await find.byCssSelector( - '[group-name="buckets"] [data-test-subj="defaultEditorAggSelect"] + .euiFormErrorText' - ); - const errorMessage = await error.getAttribute('innerText'); - log.debug(errorMessage); - return errorMessage; - } - - async selectOrderByMetric(agg, metric) { - const sortSelect = await testSubjects.find(`visEditorOrderBy${agg}`); - const sortMetric = await sortSelect.findByCssSelector(`option[value="${metric}"]`); - await sortMetric.click(); - } - - async selectCustomSortMetric(agg, metric, field) { - await this.selectOrderByMetric(agg, 'custom'); - await this.selectAggregation(metric, 'buckets', true); - await this.selectField(field, 'buckets', true); - } - - async clickSplitDirection(direction) { - const control = await testSubjects.find('visEditorSplitBy'); - const radioBtn = await control.findByCssSelector(`[title="${direction}"]`); - await radioBtn.click(); - } - - async countNestedTables() { - const vis = await testSubjects.find('tableVis'); - const result = []; - - for (let i = 1; true; i++) { - const selector = new Array(i).fill('.kbnAggTable__group').join(' '); - const tables = await vis.findAllByCssSelector(selector); - if (tables.length === 0) { - break; - } - result.push(tables.length); - } - - return result; - } - - async removeDimension(agg) { - await testSubjects.click(`visEditorAggAccordion${agg} > removeDimensionBtn`); - } - - async setFilterParams({ aggNth = 0, indexPattern, field }) { - await comboBox.set(`indexPatternSelect-${aggNth}`, indexPattern); - await comboBox.set(`fieldSelect-${aggNth}`, field); - } - - async setFilterRange({ aggNth = 0, min, max }) { - const control = await testSubjects.find(`inputControl${aggNth}`); - const inputMin = await control.findByCssSelector('[name$="minValue"]'); - await inputMin.type(min); - const inputMax = await control.findByCssSelector('[name$="maxValue"]'); - await inputMax.type(max); - } - - async scrollSubjectIntoView(subject) { - const element = await testSubjects.find(subject); - await element.scrollIntoViewIfNecessary(); - } - } - - return new VisualizePage(); -} diff --git a/test/functional/page_objects/visualize_page.ts b/test/functional/page_objects/visualize_page.ts new file mode 100644 index 0000000000000..4ba64ea771eff --- /dev/null +++ b/test/functional/page_objects/visualize_page.ts @@ -0,0 +1,332 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { FtrProviderContext } from '../ftr_provider_context'; +import { VisualizeConstants } from '../../../src/legacy/core_plugins/kibana/public/visualize/np_ready/visualize_constants'; + +export function VisualizePageProvider({ getService, getPageObjects }: FtrProviderContext) { + const testSubjects = getService('testSubjects'); + const retry = getService('retry'); + const find = getService('find'); + const log = getService('log'); + const globalNav = getService('globalNav'); + const listingTable = getService('listingTable'); + const { common, header, visEditor } = getPageObjects(['common', 'header', 'visEditor']); + + /** + * This page object contains the visualization type selection, the landing page, + * and the open/save dialog functions + */ + class VisualizePage { + index = { + LOGSTASH_TIME_BASED: 'logstash-*', + LOGSTASH_NON_TIME_BASED: 'logstash*', + }; + + public async gotoVisualizationLandingPage() { + await common.navigateToApp('visualize'); + } + + public async clickNewVisualization() { + // newItemButton button is only visible when there are items in the listing table is displayed. + let exists = await testSubjects.exists('newItemButton'); + if (exists) { + return await testSubjects.click('newItemButton'); + } + + exists = await testSubjects.exists('createVisualizationPromptButton'); + // no viz exist, click createVisualizationPromptButton to create new dashboard + return await this.createVisualizationPromptButton(); + } + + public async createVisualizationPromptButton() { + await testSubjects.click('createVisualizationPromptButton'); + } + + public async getChartTypes() { + const chartTypeField = await testSubjects.find('visNewDialogTypes'); + const $ = await chartTypeField.parseDomContent(); + return $('button') + .toArray() + .map(chart => + $(chart) + .findTestSubject('visTypeTitle') + .text() + .trim() + ); + } + + public async waitForVisualizationSelectPage() { + await retry.try(async () => { + const visualizeSelectTypePage = await testSubjects.find('visNewDialogTypes'); + if (!(await visualizeSelectTypePage.isDisplayed())) { + throw new Error('wait for visualization select page'); + } + }); + } + + public async navigateToNewVisualization() { + await common.navigateToApp('visualize'); + await this.clickNewVisualization(); + await this.waitForVisualizationSelectPage(); + } + + public async clickVisType(type: string) { + await testSubjects.click(`visType-${type}`); + await header.waitUntilLoadingHasFinished(); + } + + public async clickAreaChart() { + await this.clickVisType('area'); + } + + public async clickDataTable() { + await this.clickVisType('table'); + } + + public async clickLineChart() { + await this.clickVisType('line'); + } + + public async clickRegionMap() { + await this.clickVisType('region_map'); + } + + public async clickMarkdownWidget() { + await this.clickVisType('markdown'); + } + + public async clickMetric() { + await this.clickVisType('metric'); + } + + public async clickGauge() { + await this.clickVisType('gauge'); + } + + public async clickPieChart() { + await this.clickVisType('pie'); + } + + public async clickTileMap() { + await this.clickVisType('tile_map'); + } + + public async clickTagCloud() { + await this.clickVisType('tagcloud'); + } + + public async clickVega() { + await this.clickVisType('vega'); + } + + public async clickVisualBuilder() { + await this.clickVisType('metrics'); + } + + public async clickVerticalBarChart() { + await this.clickVisType('histogram'); + } + + public async clickHeatmapChart() { + await this.clickVisType('heatmap'); + } + + public async clickInputControlVis() { + await this.clickVisType('input_control_vis'); + } + + public async createSimpleMarkdownViz(vizName: string) { + await this.gotoVisualizationLandingPage(); + await this.navigateToNewVisualization(); + await this.clickMarkdownWidget(); + await visEditor.setMarkdownTxt(vizName); + await visEditor.clickGo(); + await this.saveVisualization(vizName); + } + + public async clickNewSearch(indexPattern = this.index.LOGSTASH_TIME_BASED) { + await testSubjects.click(`savedObjectTitle${indexPattern.split(' ').join('-')}`); + await header.waitUntilLoadingHasFinished(); + } + + public async selectVisSourceIfRequired() { + log.debug('selectVisSourceIfRequired'); + const selectPage = await testSubjects.findAll('visualizeSelectSearch'); + if (selectPage.length) { + log.debug('a search is required for this visualization'); + await this.clickNewSearch(); + } + } + + /** + * Deletes all existing visualizations + */ + public async deleteAllVisualizations() { + await retry.try(async () => { + await listingTable.checkListingSelectAllCheckbox(); + await listingTable.clickDeleteSelected(); + await common.clickConfirmOnModal(); + await testSubjects.find('createVisualizationPromptButton'); + }); + } + + public async isBetaInfoShown() { + return await testSubjects.exists('betaVisInfo'); + } + + public async getBetaTypeLinks() { + return await find.allByCssSelector('[data-vis-stage="beta"]'); + } + + public async getExperimentalTypeLinks() { + return await find.allByCssSelector('[data-vis-stage="experimental"]'); + } + + public async isExperimentalInfoShown() { + return await testSubjects.exists('experimentalVisInfo'); + } + + public async getExperimentalInfo() { + return await testSubjects.find('experimentalVisInfo'); + } + + public async getSideEditorExists() { + return await find.existsByCssSelector('.collapsible-sidebar'); + } + + public async clickSavedSearch(savedSearchName: string) { + await testSubjects.click(`savedObjectTitle${savedSearchName.split(' ').join('-')}`); + await header.waitUntilLoadingHasFinished(); + } + + public async clickUnlinkSavedSearch() { + await testSubjects.doubleClick('unlinkSavedSearch'); + await header.waitUntilLoadingHasFinished(); + } + + public async ensureSavePanelOpen() { + log.debug('ensureSavePanelOpen'); + await header.waitUntilLoadingHasFinished(); + const isOpen = await testSubjects.exists('savedObjectSaveModal', { timeout: 5000 }); + if (!isOpen) { + await testSubjects.click('visualizeSaveButton'); + } + } + + public async clickLoadSavedVisButton() { + // TODO: Use a test subject selector once we rewrite breadcrumbs to accept each breadcrumb + // element as a child instead of building the breadcrumbs dynamically. + await find.clickByCssSelector('[href="#/visualize"]'); + } + + public async clickVisualizationByName(vizName: string) { + log.debug('clickVisualizationByLinkText(' + vizName + ')'); + await find.clickByPartialLinkText(vizName); + } + + public async loadSavedVisualization(vizName: string, { navigateToVisualize = true } = {}) { + if (navigateToVisualize) { + await this.clickLoadSavedVisButton(); + } + await this.openSavedVisualization(vizName); + } + + public async openSavedVisualization(vizName: string) { + await this.clickVisualizationByName(vizName); + await header.waitUntilLoadingHasFinished(); + } + + public async waitForVisualizationSavedToastGone() { + await testSubjects.waitForDeleted('saveVisualizationSuccess'); + } + + public async clickLandingPageBreadcrumbLink() { + log.debug('clickLandingPageBreadcrumbLink'); + await find.clickByCssSelector(`a[href="#${VisualizeConstants.LANDING_PAGE_PATH}"]`); + } + + /** + * Returns true if already on the landing page (that page doesn't have a link to itself). + * @returns {Promise} + */ + public async onLandingPage() { + log.debug(`VisualizePage.onLandingPage`); + return await testSubjects.exists('visualizeLandingPage'); + } + + public async gotoLandingPage() { + log.debug('VisualizePage.gotoLandingPage'); + const onPage = await this.onLandingPage(); + if (!onPage) { + await retry.try(async () => { + await this.clickLandingPageBreadcrumbLink(); + const onLandingPage = await this.onLandingPage(); + if (!onLandingPage) throw new Error('Not on the landing page.'); + }); + } + } + + public async saveVisualization(vizName: string, { saveAsNew = false } = {}) { + await this.ensureSavePanelOpen(); + await testSubjects.setValue('savedObjectTitle', vizName); + if (saveAsNew) { + log.debug('Check save as new visualization'); + await testSubjects.click('saveAsNewCheckbox'); + } + log.debug('Click Save Visualization button'); + + await testSubjects.click('confirmSaveSavedObjectButton'); + + // Confirm that the Visualization has actually been saved + await testSubjects.existOrFail('saveVisualizationSuccess'); + const message = await common.closeToast(); + await header.waitUntilLoadingHasFinished(); + await common.waitForSaveModalToClose(); + + return message; + } + + public async saveVisualizationExpectSuccess(vizName: string, { saveAsNew = false } = {}) { + const saveMessage = await this.saveVisualization(vizName, { saveAsNew }); + if (!saveMessage) { + throw new Error( + `Expected saveVisualization to respond with the saveMessage from the toast, got ${saveMessage}` + ); + } + } + + public async saveVisualizationExpectSuccessAndBreadcrumb( + vizName: string, + { saveAsNew = false } = {} + ) { + await this.saveVisualizationExpectSuccess(vizName, { saveAsNew }); + await retry.waitFor( + 'last breadcrumb to have new vis name', + async () => (await globalNav.getLastBreadcrumb()) === vizName + ); + } + + public async clickLensWidget() { + await this.clickVisType('lens'); + } + } + + return new VisualizePage(); +} diff --git a/test/functional/services/apps_menu.ts b/test/functional/services/apps_menu.ts index a4cd98b2a06ec..fe17532f6a41a 100644 --- a/test/functional/services/apps_menu.ts +++ b/test/functional/services/apps_menu.ts @@ -25,7 +25,7 @@ export function AppsMenuProvider({ getService }: FtrProviderContext) { return new (class AppsMenu { /** - * Get the text and href from each of the links in the apps menu + * Get the attributes from each of the links in the apps menu */ public async readLinks() { const appMenu = await testSubjects.find('navDrawer'); @@ -37,12 +37,21 @@ export function AppsMenuProvider({ getService }: FtrProviderContext) { return { text: $(link).text(), href: $(link).attr('href'), + disabled: $(link).attr('disabled') != null, }; }); return links; } + /** + * Get the attributes from the link with the given name. + * @param name + */ + public async getLink(name: string) { + return (await this.readLinks()).find(nl => nl.text === name); + } + /** * Determine if an app link with the given name exists * @param name diff --git a/test/functional/services/dashboard/visualizations.js b/test/functional/services/dashboard/visualizations.js index c4e7fe6ad3bd9..5e722ccce8970 100644 --- a/test/functional/services/dashboard/visualizations.js +++ b/test/functional/services/dashboard/visualizations.js @@ -24,7 +24,7 @@ export function DashboardVisualizationProvider({ getService, getPageObjects }) { const queryBar = getService('queryBar'); const testSubjects = getService('testSubjects'); const dashboardAddPanel = getService('dashboardAddPanel'); - const PageObjects = getPageObjects(['dashboard', 'visualize', 'header', 'discover']); + const PageObjects = getPageObjects(['dashboard', 'visualize', 'visEditor', 'header', 'discover']); return new (class DashboardVisualizations { async createAndAddTSVBVisualization(name) { @@ -107,8 +107,8 @@ export function DashboardVisualizationProvider({ getService, getPageObjects }) { } await this.ensureNewVisualizationDialogIsShowing(); await PageObjects.visualize.clickMarkdownWidget(); - await PageObjects.visualize.setMarkdownTxt(markdown); - await PageObjects.visualize.clickGo(); + await PageObjects.visEditor.setMarkdownTxt(markdown); + await PageObjects.visEditor.clickGo(); await PageObjects.visualize.saveVisualizationExpectSuccess(name); } })(); diff --git a/test/functional/services/elastic_chart.ts b/test/functional/services/elastic_chart.ts index 4f4dbcba5f0b8..afae3f830b3bf 100644 --- a/test/functional/services/elastic_chart.ts +++ b/test/functional/services/elastic_chart.ts @@ -37,7 +37,7 @@ export function ElasticChartProvider({ getService }: FtrProviderContext) { public async getVisualizationRenderingCount(dataTestSubj: string) { const chart = await testSubjects.find(dataTestSubj); - const visContainer = await chart.findByCssSelector('.echChart'); + const visContainer = await chart.findByCssSelector('.echChartStatus'); const renderingCount = await visContainer.getAttribute('data-ech-render-count'); return Number(renderingCount); } diff --git a/test/functional/services/index.ts b/test/functional/services/index.ts index ea47ccf1d2704..a10bb013b3af4 100644 --- a/test/functional/services/index.ts +++ b/test/functional/services/index.ts @@ -49,7 +49,7 @@ import { TestSubjectsProvider } from './test_subjects'; import { ToastsProvider } from './toasts'; // @ts-ignore not TS yet import { PieChartProvider } from './visualizations'; -import { VisualizeListingTableProvider } from './visualize_listing_table'; +import { ListingTableProvider } from './listing_table'; import { SavedQueryManagementComponentProvider } from './saved_query_management_component'; export const services = { @@ -66,7 +66,7 @@ export const services = { dashboardVisualizations: DashboardVisualizationProvider, dashboardExpect: DashboardExpectProvider, failureDebugging: FailureDebuggingProvider, - visualizeListingTable: VisualizeListingTableProvider, + listingTable: ListingTableProvider, dashboardAddPanel: DashboardAddPanelProvider, dashboardReplacePanel: DashboardReplacePanelProvider, dashboardPanelActions: DashboardPanelActionsProvider, diff --git a/test/functional/services/listing_table.ts b/test/functional/services/listing_table.ts new file mode 100644 index 0000000000000..ec886cf694f2e --- /dev/null +++ b/test/functional/services/listing_table.ts @@ -0,0 +1,106 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { FtrProviderContext } from '../ftr_provider_context'; + +export function ListingTableProvider({ getService, getPageObjects }: FtrProviderContext) { + const testSubjects = getService('testSubjects'); + const find = getService('find'); + const log = getService('log'); + const retry = getService('retry'); + const { common, header } = getPageObjects(['common', 'header']); + + class ListingTable { + public async getSearchFilter() { + const searchFilter = await find.allByCssSelector('.euiFieldSearch'); + return searchFilter[0]; + } + + public async clearFilter() { + const searchFilter = await this.getSearchFilter(); + await searchFilter.clearValue(); + await searchFilter.click(); + } + + public async getAllVisualizationNamesOnCurrentPage(): Promise { + const visualizationNames = []; + const links = await find.allByCssSelector('.kuiLink'); + for (let i = 0; i < links.length; i++) { + visualizationNames.push(await links[i].getVisibleText()); + } + log.debug(`Found ${visualizationNames.length} visualizations on current page`); + return visualizationNames; + } + + public async getItemsCount(appName: 'visualize' | 'dashboard'): Promise { + const prefixMap = { visualize: 'vis', dashboard: 'dashboard' }; + const elements = await find.allByCssSelector( + `[data-test-subj^="${prefixMap[appName]}ListingTitleLink"]` + ); + return elements.length; + } + + public async searchForItemWithName(name: string) { + log.debug(`searchForItemWithName: ${name}`); + + await retry.try(async () => { + const searchFilter = await this.getSearchFilter(); + await searchFilter.clearValue(); + await searchFilter.click(); + // Note: this replacement of - to space is to preserve original logic but I'm not sure why or if it's needed. + await searchFilter.type(name.replace('-', ' ')); + await common.pressEnterKey(); + }); + + await header.waitUntilLoadingHasFinished(); + } + + public async clickDeleteSelected() { + await testSubjects.click('deleteSelectedItems'); + } + + public async checkListingSelectAllCheckbox() { + const element = await testSubjects.find('checkboxSelectAll'); + const isSelected = await element.isSelected(); + if (!isSelected) { + log.debug(`checking checkbox "checkboxSelectAll"`); + await testSubjects.click('checkboxSelectAll'); + } + } + + public async getAllVisualizationNames(): Promise { + log.debug('ListingTable.getAllVisualizationNames'); + let morePages = true; + let visualizationNames: string[] = []; + while (morePages) { + visualizationNames = visualizationNames.concat( + await this.getAllVisualizationNamesOnCurrentPage() + ); + morePages = !((await testSubjects.getAttribute('pagerNextButton', 'disabled')) === 'true'); + if (morePages) { + await testSubjects.click('pagerNextButton'); + await header.waitUntilLoadingHasFinished(); + } + } + return visualizationNames; + } + } + + return new ListingTable(); +} diff --git a/test/functional/services/remote/remote.ts b/test/functional/services/remote/remote.ts index 69c2793621095..afe8499a1c2ea 100644 --- a/test/functional/services/remote/remote.ts +++ b/test/functional/services/remote/remote.ts @@ -100,7 +100,9 @@ export async function RemoteProvider({ getService }: FtrProviderContext) { .subscribe({ next({ message, level }) { const msg = message.replace(/\\n/g, '\n'); - log[level === 'SEVERE' ? 'error' : 'debug'](`browser[${level}] ${msg}`); + log[level === 'SEVERE' || level === 'error' ? 'error' : 'debug']( + `browser[${level}] ${msg}` + ); }, }); diff --git a/test/functional/services/screenshots.ts b/test/functional/services/screenshots.ts index 9e673fe919a74..4c5728174cf99 100644 --- a/test/functional/services/screenshots.ts +++ b/test/functional/services/screenshots.ts @@ -51,7 +51,7 @@ export async function ScreenshotsProvider({ getService }: FtrProviderContext) { * @param updateBaselines {boolean} optional, pass true to update the baseline snapshot. * @return {Promise.} Percentage difference between the baseline and the current snapshot. */ - async compareAgainstBaseline(name: string, updateBaselines: boolean, el: WebElementWrapper) { + async compareAgainstBaseline(name: string, updateBaselines: boolean, el?: WebElementWrapper) { log.debug('compareAgainstBaseline'); const sessionPath = resolve(SESSION_DIRECTORY, `${name}.png`); await this._take(sessionPath, el); diff --git a/test/functional/services/test_subjects.ts b/test/functional/services/test_subjects.ts index a3f64e6f96cc8..8ef008d5dee50 100644 --- a/test/functional/services/test_subjects.ts +++ b/test/functional/services/test_subjects.ts @@ -295,6 +295,25 @@ export function TestSubjectsProvider({ getService }: FtrProviderContext) { public getCssSelector(selector: string): string { return testSubjSelector(selector); } + + public async scrollIntoView(selector: string) { + const element = await this.find(selector); + await element.scrollIntoViewIfNecessary(); + } + + public async isChecked(selector: string) { + const checkbox = await this.find(selector); + return await checkbox.isSelected(); + } + + public async setCheckbox(selector: string, state: 'check' | 'uncheck') { + const isChecked = await this.isChecked(selector); + const states = { check: true, uncheck: false }; + if (isChecked !== states[state]) { + log.debug(`updating checkbox ${selector}`); + await this.click(selector); + } + } } return new TestSubjects(); diff --git a/test/functional/services/visualize_listing_table.ts b/test/functional/services/visualize_listing_table.ts deleted file mode 100644 index 8c4640ada1c05..0000000000000 --- a/test/functional/services/visualize_listing_table.ts +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -import { FtrProviderContext } from '../ftr_provider_context'; - -export function VisualizeListingTableProvider({ getService, getPageObjects }: FtrProviderContext) { - const testSubjects = getService('testSubjects'); - const find = getService('find'); - const log = getService('log'); - const { header } = getPageObjects(['header']); - - class VisualizeListingTable { - public async getAllVisualizationNamesOnCurrentPage(): Promise { - const visualizationNames = []; - const links = await find.allByCssSelector('.kuiLink'); - for (let i = 0; i < links.length; i++) { - visualizationNames.push(await links[i].getVisibleText()); - } - log.debug(`Found ${visualizationNames.length} visualizations on current page`); - return visualizationNames; - } - - public async getAllVisualizationNames(): Promise { - log.debug('VisualizeListingTable.getAllVisualizationNames'); - let morePages = true; - let visualizationNames: string[] = []; - while (morePages) { - visualizationNames = visualizationNames.concat( - await this.getAllVisualizationNamesOnCurrentPage() - ); - morePages = !((await testSubjects.getAttribute('pagerNextButton', 'disabled')) === 'true'); - if (morePages) { - await testSubjects.click('pagerNextButton'); - await header.waitUntilLoadingHasFinished(); - } - } - return visualizationNames; - } - } - - return new VisualizeListingTable(); -} diff --git a/test/plugin_functional/config.js b/test/plugin_functional/config.js index 87026ce25d9aa..e9a4f3bcc4b1a 100644 --- a/test/plugin_functional/config.js +++ b/test/plugin_functional/config.js @@ -37,6 +37,7 @@ export default async function({ readConfigFile }) { require.resolve('./test_suites/panel_actions'), require.resolve('./test_suites/embeddable_explorer'), require.resolve('./test_suites/core_plugins'), + require.resolve('./test_suites/management'), ], services: { ...functionalConfig.get('services'), diff --git a/test/plugin_functional/plugins/core_app_status/kibana.json b/test/plugin_functional/plugins/core_app_status/kibana.json new file mode 100644 index 0000000000000..91d8e6fd8f9e1 --- /dev/null +++ b/test/plugin_functional/plugins/core_app_status/kibana.json @@ -0,0 +1,8 @@ +{ + "id": "core_app_status", + "version": "0.0.1", + "kibanaVersion": "kibana", + "configPath": ["core_app_status"], + "server": false, + "ui": true +} diff --git a/test/plugin_functional/plugins/core_app_status/package.json b/test/plugin_functional/plugins/core_app_status/package.json new file mode 100644 index 0000000000000..61655487c6acb --- /dev/null +++ b/test/plugin_functional/plugins/core_app_status/package.json @@ -0,0 +1,17 @@ +{ + "name": "core_app_status", + "version": "1.0.0", + "main": "target/test/plugin_functional/plugins/core_app_status", + "kibana": { + "version": "kibana", + "templateVersion": "1.0.0" + }, + "license": "Apache-2.0", + "scripts": { + "kbn": "node ../../../../scripts/kbn.js", + "build": "rm -rf './target' && tsc" + }, + "devDependencies": { + "typescript": "3.5.3" + } +} diff --git a/test/plugin_functional/plugins/core_app_status/public/application.tsx b/test/plugin_functional/plugins/core_app_status/public/application.tsx new file mode 100644 index 0000000000000..323774392a6d7 --- /dev/null +++ b/test/plugin_functional/plugins/core_app_status/public/application.tsx @@ -0,0 +1,64 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { render, unmountComponentAtNode } from 'react-dom'; +import { + EuiPage, + EuiPageBody, + EuiPageContent, + EuiPageContentBody, + EuiPageContentHeader, + EuiPageContentHeaderSection, + EuiPageHeader, + EuiPageHeaderSection, + EuiTitle, +} from '@elastic/eui'; + +import { AppMountContext, AppMountParameters } from 'kibana/public'; + +const AppStatusApp = () => ( + + + + + +

Welcome to App Status Test App!

+
+
+
+ + + + +

App Status Test App home page section title

+
+
+
+ App Status Test App content +
+
+
+); + +export const renderApp = (context: AppMountContext, { element }: AppMountParameters) => { + render(, element); + + return () => unmountComponentAtNode(element); +}; diff --git a/test/plugin_functional/plugins/core_app_status/public/index.ts b/test/plugin_functional/plugins/core_app_status/public/index.ts new file mode 100644 index 0000000000000..e0ad7c25a54b8 --- /dev/null +++ b/test/plugin_functional/plugins/core_app_status/public/index.ts @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { PluginInitializer } from 'kibana/public'; +import { CoreAppStatusPlugin, CoreAppStatusPluginSetup, CoreAppStatusPluginStart } from './plugin'; + +export const plugin: PluginInitializer = () => + new CoreAppStatusPlugin(); diff --git a/test/plugin_functional/plugins/core_app_status/public/plugin.tsx b/test/plugin_functional/plugins/core_app_status/public/plugin.tsx new file mode 100644 index 0000000000000..85caaaf5f9090 --- /dev/null +++ b/test/plugin_functional/plugins/core_app_status/public/plugin.tsx @@ -0,0 +1,56 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Plugin, CoreSetup, AppUpdater, AppUpdatableFields, CoreStart } from 'kibana/public'; +import { BehaviorSubject } from 'rxjs'; + +export class CoreAppStatusPlugin + implements Plugin { + private appUpdater = new BehaviorSubject(() => ({})); + + public setup(core: CoreSetup, deps: {}) { + core.application.register({ + id: 'app_status', + title: 'App Status', + euiIconType: 'snowflake', + updater$: this.appUpdater, + async mount(context, params) { + const { renderApp } = await import('./application'); + return renderApp(context, params); + }, + }); + + return {}; + } + + public start(core: CoreStart) { + return { + setAppStatus: (status: Partial) => { + this.appUpdater.next(() => status); + }, + navigateToApp: async (appId: string) => { + return core.application.navigateToApp(appId); + }, + }; + } + public stop() {} +} + +export type CoreAppStatusPluginSetup = ReturnType; +export type CoreAppStatusPluginStart = ReturnType; diff --git a/test/plugin_functional/plugins/core_app_status/tsconfig.json b/test/plugin_functional/plugins/core_app_status/tsconfig.json new file mode 100644 index 0000000000000..5fcaeafbb0d85 --- /dev/null +++ b/test/plugin_functional/plugins/core_app_status/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "../../../../tsconfig.json", + "compilerOptions": { + "outDir": "./target", + "skipLibCheck": true + }, + "include": [ + "index.ts", + "public/**/*.ts", + "public/**/*.tsx", + "../../../../typings/**/*", + ], + "exclude": [] +} diff --git a/test/plugin_functional/plugins/core_plugin_appleave/kibana.json b/test/plugin_functional/plugins/core_plugin_appleave/kibana.json new file mode 100644 index 0000000000000..95343cbcf2804 --- /dev/null +++ b/test/plugin_functional/plugins/core_plugin_appleave/kibana.json @@ -0,0 +1,8 @@ +{ + "id": "core_plugin_appleave", + "version": "0.0.1", + "kibanaVersion": "kibana", + "configPath": ["core_plugin_appleave"], + "server": false, + "ui": true +} diff --git a/test/plugin_functional/plugins/core_plugin_appleave/package.json b/test/plugin_functional/plugins/core_plugin_appleave/package.json new file mode 100644 index 0000000000000..e0488655a1723 --- /dev/null +++ b/test/plugin_functional/plugins/core_plugin_appleave/package.json @@ -0,0 +1,17 @@ +{ + "name": "core_plugin_appleave", + "version": "1.0.0", + "main": "target/test/plugin_functional/plugins/core_plugin_appleave", + "kibana": { + "version": "kibana", + "templateVersion": "1.0.0" + }, + "license": "Apache-2.0", + "scripts": { + "kbn": "node ../../../../scripts/kbn.js", + "build": "rm -rf './target' && tsc" + }, + "devDependencies": { + "typescript": "3.5.3" + } +} diff --git a/test/plugin_functional/plugins/core_plugin_appleave/public/application.tsx b/test/plugin_functional/plugins/core_plugin_appleave/public/application.tsx new file mode 100644 index 0000000000000..d0b024f90c737 --- /dev/null +++ b/test/plugin_functional/plugins/core_plugin_appleave/public/application.tsx @@ -0,0 +1,63 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import React from 'react'; +import { render, unmountComponentAtNode } from 'react-dom'; +import { + EuiPage, + EuiPageBody, + EuiPageContent, + EuiPageContentBody, + EuiPageContentHeader, + EuiPageContentHeaderSection, + EuiPageHeader, + EuiPageHeaderSection, + EuiTitle, +} from '@elastic/eui'; + +import { AppMountParameters } from 'kibana/public'; + +const App = ({ appName }: { appName: string }) => ( + + + + + +

Welcome to {appName}!

+
+
+
+ + + + +

{appName} home page section title

+
+
+
+ {appName} page content +
+
+
+); + +export const renderApp = (appName: string, { element }: AppMountParameters) => { + render(, element); + return () => unmountComponentAtNode(element); +}; diff --git a/test/plugin_functional/plugins/core_plugin_appleave/public/index.ts b/test/plugin_functional/plugins/core_plugin_appleave/public/index.ts new file mode 100644 index 0000000000000..3eb2279aa9166 --- /dev/null +++ b/test/plugin_functional/plugins/core_plugin_appleave/public/index.ts @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { PluginInitializer } from 'kibana/public'; +import { CoreAppLeavePlugin, CoreAppLeavePluginSetup, CoreAppLeavePluginStart } from './plugin'; + +export const plugin: PluginInitializer = () => + new CoreAppLeavePlugin(); diff --git a/test/plugin_functional/plugins/core_plugin_appleave/public/plugin.tsx b/test/plugin_functional/plugins/core_plugin_appleave/public/plugin.tsx new file mode 100644 index 0000000000000..336bb9d787895 --- /dev/null +++ b/test/plugin_functional/plugins/core_plugin_appleave/public/plugin.tsx @@ -0,0 +1,52 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { Plugin, CoreSetup } from 'kibana/public'; + +export class CoreAppLeavePlugin + implements Plugin { + public setup(core: CoreSetup, deps: {}) { + core.application.register({ + id: 'appleave1', + title: 'AppLeave 1', + async mount(context, params) { + const { renderApp } = await import('./application'); + params.onAppLeave(actions => actions.confirm('confirm-message', 'confirm-title')); + return renderApp('AppLeave 1', params); + }, + }); + core.application.register({ + id: 'appleave2', + title: 'AppLeave 2', + async mount(context, params) { + const { renderApp } = await import('./application'); + params.onAppLeave(actions => actions.default()); + return renderApp('AppLeave 2', params); + }, + }); + + return {}; + } + + public start() {} + public stop() {} +} + +export type CoreAppLeavePluginSetup = ReturnType; +export type CoreAppLeavePluginStart = ReturnType; diff --git a/test/plugin_functional/plugins/core_plugin_appleave/tsconfig.json b/test/plugin_functional/plugins/core_plugin_appleave/tsconfig.json new file mode 100644 index 0000000000000..5fcaeafbb0d85 --- /dev/null +++ b/test/plugin_functional/plugins/core_plugin_appleave/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "../../../../tsconfig.json", + "compilerOptions": { + "outDir": "./target", + "skipLibCheck": true + }, + "include": [ + "index.ts", + "public/**/*.ts", + "public/**/*.tsx", + "../../../../typings/**/*", + ], + "exclude": [] +} diff --git a/test/plugin_functional/plugins/management_test_plugin/kibana.json b/test/plugin_functional/plugins/management_test_plugin/kibana.json new file mode 100644 index 0000000000000..e52b60b3a4e31 --- /dev/null +++ b/test/plugin_functional/plugins/management_test_plugin/kibana.json @@ -0,0 +1,9 @@ +{ + "id": "management_test_plugin", + "version": "0.0.1", + "kibanaVersion": "kibana", + "configPath": ["management_test_plugin"], + "server": false, + "ui": true, + "requiredPlugins": ["management"] +} diff --git a/test/plugin_functional/plugins/management_test_plugin/package.json b/test/plugin_functional/plugins/management_test_plugin/package.json new file mode 100644 index 0000000000000..656d92e9eb1f7 --- /dev/null +++ b/test/plugin_functional/plugins/management_test_plugin/package.json @@ -0,0 +1,17 @@ +{ + "name": "management_test_plugin", + "version": "1.0.0", + "main": "target/test/plugin_functional/plugins/management_test_plugin", + "kibana": { + "version": "kibana", + "templateVersion": "1.0.0" + }, + "license": "Apache-2.0", + "scripts": { + "kbn": "node ../../../../scripts/kbn.js", + "build": "rm -rf './target' && tsc" + }, + "devDependencies": { + "typescript": "3.7.2" + } +} diff --git a/test/plugin_functional/plugins/management_test_plugin/public/index.ts b/test/plugin_functional/plugins/management_test_plugin/public/index.ts new file mode 100644 index 0000000000000..1efcc6cd3bbd6 --- /dev/null +++ b/test/plugin_functional/plugins/management_test_plugin/public/index.ts @@ -0,0 +1,28 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { PluginInitializer } from 'kibana/public'; +import { + ManagementTestPlugin, + ManagementTestPluginSetup, + ManagementTestPluginStart, +} from './plugin'; + +export const plugin: PluginInitializer = () => + new ManagementTestPlugin(); diff --git a/test/plugin_functional/plugins/management_test_plugin/public/plugin.tsx b/test/plugin_functional/plugins/management_test_plugin/public/plugin.tsx new file mode 100644 index 0000000000000..8b7cdd653ed8c --- /dev/null +++ b/test/plugin_functional/plugins/management_test_plugin/public/plugin.tsx @@ -0,0 +1,73 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import * as React from 'react'; +import ReactDOM from 'react-dom'; +import { HashRouter as Router, Switch, Route, Link } from 'react-router-dom'; +import { CoreSetup, Plugin } from 'kibana/public'; +import { ManagementSetup } from '../../../../../src/plugins/management/public'; + +export class ManagementTestPlugin + implements Plugin { + public setup(core: CoreSetup, { management }: { management: ManagementSetup }) { + const testSection = management.sections.register({ + id: 'test-section', + title: 'Test Section', + euiIconType: 'logoKibana', + order: 25, + }); + + testSection!.registerApp({ + id: 'test-management', + title: 'Management Test', + mount(params) { + params.setBreadcrumbs([{ text: 'Management Test' }]); + ReactDOM.render( + +

Hello from management test plugin

+ + + + Link to /one + + + + + Link to basePath + + + +
, + params.element + ); + + return () => { + ReactDOM.unmountComponentAtNode(params.element); + }; + }, + }); + return {}; + } + + public start() {} + public stop() {} +} + +export type ManagementTestPluginSetup = ReturnType; +export type ManagementTestPluginStart = ReturnType; diff --git a/test/plugin_functional/plugins/management_test_plugin/tsconfig.json b/test/plugin_functional/plugins/management_test_plugin/tsconfig.json new file mode 100644 index 0000000000000..5fcaeafbb0d85 --- /dev/null +++ b/test/plugin_functional/plugins/management_test_plugin/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "../../../../tsconfig.json", + "compilerOptions": { + "outDir": "./target", + "skipLibCheck": true + }, + "include": [ + "index.ts", + "public/**/*.ts", + "public/**/*.tsx", + "../../../../typings/**/*", + ], + "exclude": [] +} diff --git a/test/plugin_functional/test_suites/core_plugins/application_leave_confirm.ts b/test/plugin_functional/test_suites/core_plugins/application_leave_confirm.ts new file mode 100644 index 0000000000000..d164c2e0bc369 --- /dev/null +++ b/test/plugin_functional/test_suites/core_plugins/application_leave_confirm.ts @@ -0,0 +1,80 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +import url from 'url'; +import expect from '@kbn/expect'; +import { PluginFunctionalProviderContext } from '../../services'; + +const getKibanaUrl = (pathname?: string, search?: string) => + url.format({ + protocol: 'http:', + hostname: process.env.TEST_KIBANA_HOST || 'localhost', + port: process.env.TEST_KIBANA_PORT || '5620', + pathname, + search, + }); + +// eslint-disable-next-line import/no-default-export +export default function({ getService, getPageObjects }: PluginFunctionalProviderContext) { + const PageObjects = getPageObjects(['common']); + const browser = getService('browser'); + const appsMenu = getService('appsMenu'); + const testSubjects = getService('testSubjects'); + + describe('application using leave confirmation', () => { + describe('when navigating to another app', () => { + it('prevents navigation if user click cancel on the confirmation dialog', async () => { + await PageObjects.common.navigateToApp('appleave1'); + await appsMenu.clickLink('AppLeave 2'); + + await testSubjects.existOrFail('appLeaveConfirmModal'); + await PageObjects.common.clickCancelOnModal(false); + expect(await browser.getCurrentUrl()).to.eql(getKibanaUrl('/app/appleave1')); + }); + it('allows navigation if user click confirm on the confirmation dialog', async () => { + await PageObjects.common.navigateToApp('appleave1'); + await appsMenu.clickLink('AppLeave 2'); + + await testSubjects.existOrFail('appLeaveConfirmModal'); + await PageObjects.common.clickConfirmOnModal(); + expect(await browser.getCurrentUrl()).to.eql(getKibanaUrl('/app/appleave2')); + }); + }); + + describe('when navigating to a legacy app', () => { + it('prevents navigation if user click cancel on the alert dialog', async () => { + await PageObjects.common.navigateToApp('appleave1'); + await appsMenu.clickLink('Core Legacy Compat'); + + const alert = await browser.getAlert(); + expect(alert).not.to.eql(undefined); + alert!.dismiss(); + expect(await browser.getCurrentUrl()).to.eql(getKibanaUrl('/app/appleave1')); + }); + it('allows navigation if user click leave on the alert dialog', async () => { + await PageObjects.common.navigateToApp('appleave1'); + await appsMenu.clickLink('Core Legacy Compat'); + + const alert = await browser.getAlert(); + expect(alert).not.to.eql(undefined); + alert!.accept(); + expect(await browser.getCurrentUrl()).to.eql(getKibanaUrl('/app/core_plugin_legacy')); + }); + }); + }); +} diff --git a/test/plugin_functional/test_suites/core_plugins/application_status.ts b/test/plugin_functional/test_suites/core_plugins/application_status.ts new file mode 100644 index 0000000000000..703ae30533bae --- /dev/null +++ b/test/plugin_functional/test_suites/core_plugins/application_status.ts @@ -0,0 +1,116 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import expect from '@kbn/expect'; +import { + AppNavLinkStatus, + AppStatus, + AppUpdatableFields, +} from '../../../../src/core/public/application/types'; +import { PluginFunctionalProviderContext } from '../../services'; +import { CoreAppStatusPluginStart } from '../../plugins/core_app_status/public/plugin'; +import '../../plugins/core_provider_plugin/types'; + +// eslint-disable-next-line import/no-default-export +export default function({ getService, getPageObjects }: PluginFunctionalProviderContext) { + const PageObjects = getPageObjects(['common']); + const browser = getService('browser'); + const appsMenu = getService('appsMenu'); + + const setAppStatus = async (s: Partial) => { + await browser.executeAsync(async (status: Partial, cb: Function) => { + const plugin = window.__coreProvider.start.plugins + .core_app_status as CoreAppStatusPluginStart; + plugin.setAppStatus(status); + cb(); + }, s); + }; + + const navigateToApp = async (i: string): Promise<{ error?: string }> => { + return (await browser.executeAsync(async (appId, cb: Function) => { + // navigating in legacy mode performs a page refresh + // and webdriver seems to re-execute the script after the reload + // as it considers it didn't end on the previous session. + // however when testing navigation to NP app, __coreProvider is not accessible + // so we need to check for existence. + if (!window.__coreProvider) { + cb({}); + } + const plugin = window.__coreProvider.start.plugins + .core_app_status as CoreAppStatusPluginStart; + try { + await plugin.navigateToApp(appId); + cb({}); + } catch (e) { + cb({ + error: e.message, + }); + } + }, i)) as any; + }; + + describe('application status management', () => { + beforeEach(async () => { + await PageObjects.common.navigateToApp('settings'); + }); + + it('can change the navLink status at runtime', async () => { + await setAppStatus({ + navLinkStatus: AppNavLinkStatus.disabled, + }); + let link = await appsMenu.getLink('App Status'); + expect(link).not.to.eql(undefined); + expect(link!.disabled).to.eql(true); + + await setAppStatus({ + navLinkStatus: AppNavLinkStatus.hidden, + }); + link = await appsMenu.getLink('App Status'); + expect(link).to.eql(undefined); + + await setAppStatus({ + navLinkStatus: AppNavLinkStatus.visible, + tooltip: 'Some tooltip', + }); + link = await appsMenu.getLink('Some tooltip'); // the tooltip replaces the name in the selector we use. + expect(link).not.to.eql(undefined); + expect(link!.disabled).to.eql(false); + }); + + it('shows an error when navigating to an inaccessible app', async () => { + await setAppStatus({ + status: AppStatus.inaccessible, + }); + + const result = await navigateToApp('app_status'); + expect(result.error).to.contain( + 'Trying to navigate to an inaccessible application: app_status' + ); + }); + + it('allows to navigate to an accessible app', async () => { + await setAppStatus({ + status: AppStatus.accessible, + }); + + const result = await navigateToApp('app_status'); + expect(result.error).to.eql(undefined); + }); + }); +} diff --git a/test/plugin_functional/test_suites/core_plugins/index.ts b/test/plugin_functional/test_suites/core_plugins/index.ts index bf33f37694c3a..d66e2e7dc5da7 100644 --- a/test/plugin_functional/test_suites/core_plugins/index.ts +++ b/test/plugin_functional/test_suites/core_plugins/index.ts @@ -27,5 +27,7 @@ export default function({ loadTestFile }: PluginFunctionalProviderContext) { loadTestFile(require.resolve('./ui_plugins')); loadTestFile(require.resolve('./ui_settings')); loadTestFile(require.resolve('./top_nav')); + loadTestFile(require.resolve('./application_leave_confirm')); + loadTestFile(require.resolve('./application_status')); }); } diff --git a/test/plugin_functional/test_suites/custom_visualizations/self_changing_vis.js b/test/plugin_functional/test_suites/custom_visualizations/self_changing_vis.js index 30b9dbe0fe80a..ef6f0a626bd15 100644 --- a/test/plugin_functional/test_suites/custom_visualizations/self_changing_vis.js +++ b/test/plugin_functional/test_suites/custom_visualizations/self_changing_vis.js @@ -22,7 +22,7 @@ import expect from '@kbn/expect'; export default function({ getService, getPageObjects }) { const testSubjects = getService('testSubjects'); const renderable = getService('renderable'); - const PageObjects = getPageObjects(['common', 'visualize']); + const PageObjects = getPageObjects(['common', 'visualize', 'visEditor']); async function getCounterValue() { return await testSubjects.getVisibleText('counter'); @@ -42,9 +42,9 @@ export default function({ getService, getPageObjects }) { const editor = await testSubjects.find('counterEditor'); await editor.clearValue(); await editor.type('10'); - const isApplyEnabled = await PageObjects.visualize.isApplyEnabled(); + const isApplyEnabled = await PageObjects.visEditor.isApplyEnabled(); expect(isApplyEnabled).to.be(true); - await PageObjects.visualize.clickGo(); + await PageObjects.visEditor.clickGo(); const counter = await getCounterValue(); expect(counter).to.be('10'); }); @@ -57,7 +57,7 @@ export default function({ getService, getPageObjects }) { const editorValue = await getEditorValue(); expect(editorValue).to.be('11'); // If changing a param from within the vis it should immediately apply and not bring editor in an unchanged state - const isApplyEnabled = await PageObjects.visualize.isApplyEnabled(); + const isApplyEnabled = await PageObjects.visEditor.isApplyEnabled(); expect(isApplyEnabled).to.be(false); }); }); diff --git a/test/plugin_functional/test_suites/management/index.js b/test/plugin_functional/test_suites/management/index.js new file mode 100644 index 0000000000000..2bfc05547b292 --- /dev/null +++ b/test/plugin_functional/test_suites/management/index.js @@ -0,0 +1,24 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export default function({ loadTestFile }) { + describe('management plugin', () => { + loadTestFile(require.resolve('./management_plugin')); + }); +} diff --git a/test/plugin_functional/test_suites/management/management_plugin.js b/test/plugin_functional/test_suites/management/management_plugin.js new file mode 100644 index 0000000000000..d65fb1dcd3a7e --- /dev/null +++ b/test/plugin_functional/test_suites/management/management_plugin.js @@ -0,0 +1,40 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export default function({ getService, getPageObjects }) { + const testSubjects = getService('testSubjects'); + const PageObjects = getPageObjects(['common']); + + describe('management plugin', function describeIndexTests() { + before(async () => { + await PageObjects.common.navigateToActualUrl('kibana', 'management'); + }); + + it('should be able to navigate to management test app', async () => { + await testSubjects.click('test-management'); + await testSubjects.existOrFail('test-management-header'); + }); + + it('should be able to navigate within management test app', async () => { + await testSubjects.click('test-management-link-one'); + await testSubjects.click('test-management-link-basepath'); + await testSubjects.existOrFail('test-management-link-one'); + }); + }); +} diff --git a/test/scripts/jenkins_build_kibana.sh b/test/scripts/jenkins_build_kibana.sh index f79fe98e07bef..2605655ed7e7a 100755 --- a/test/scripts/jenkins_build_kibana.sh +++ b/test/scripts/jenkins_build_kibana.sh @@ -8,5 +8,8 @@ node scripts/es snapshot --license=oss --download-only; echo " -> Ensuring all functional tests are in a ciGroup" yarn run grunt functionalTests:ensureAllTestsInCiGroup; -echo " -> building and extracting OSS Kibana distributable for use in functional tests" -node scripts/build --debug --oss +# Do not build kibana for code coverage run +if [[ -z "$CODE_COVERAGE" ]] ; then + echo " -> building and extracting OSS Kibana distributable for use in functional tests" + node scripts/build --debug --oss +fi diff --git a/test/scripts/jenkins_ci_group.sh b/test/scripts/jenkins_ci_group.sh index 1cb566c908dbf..fccdb29ff512b 100755 --- a/test/scripts/jenkins_ci_group.sh +++ b/test/scripts/jenkins_ci_group.sh @@ -2,22 +2,30 @@ source test/scripts/jenkins_test_setup.sh -if [[ -z "$IS_PIPELINE_JOB" ]] ; then - yarn run grunt functionalTests:ensureAllTestsInCiGroup; - node scripts/build --debug --oss; -else - installDir="$(realpath $PARENT_DIR/kibana/build/oss/kibana-*-SNAPSHOT-linux-x86_64)" - destDir=${installDir}-${CI_WORKER_NUMBER} - cp -R "$installDir" "$destDir" +if [[ -z "$CODE_COVERAGE" ]] ; then + if [[ -z "$IS_PIPELINE_JOB" ]] ; then + yarn run grunt functionalTests:ensureAllTestsInCiGroup; + node scripts/build --debug --oss; + else + installDir="$(realpath $PARENT_DIR/kibana/build/oss/kibana-*-SNAPSHOT-linux-x86_64)" + destDir=${installDir}-${CI_WORKER_NUMBER} + cp -R "$installDir" "$destDir" - export KIBANA_INSTALL_DIR="$destDir" -fi + export KIBANA_INSTALL_DIR="$destDir" + fi + + checks-reporter-with-killswitch "Functional tests / Group ${CI_GROUP}" yarn run grunt "run:functionalTests_ciGroup${CI_GROUP}"; + + if [ "$CI_GROUP" == "1" ]; then + source test/scripts/jenkins_build_kbn_tp_sample_panel_action.sh + yarn run grunt run:pluginFunctionalTestsRelease --from=source; + yarn run grunt run:exampleFunctionalTestsRelease --from=source; + yarn run grunt run:interpreterFunctionalTestsRelease; + fi +else + echo " -> Running Functional tests with code coverage" -checks-reporter-with-killswitch "Functional tests / Group ${CI_GROUP}" yarn run grunt "run:functionalTests_ciGroup${CI_GROUP}"; + export NODE_OPTIONS=--max_old_space_size=8192 -if [ "$CI_GROUP" == "1" ]; then - source test/scripts/jenkins_build_kbn_tp_sample_panel_action.sh - yarn run grunt run:pluginFunctionalTestsRelease --from=source; - yarn run grunt run:exampleFunctionalTestsRelease --from=source; - yarn run grunt run:interpreterFunctionalTestsRelease; + yarn run grunt "run:functionalTests_ciGroup${CI_GROUP}"; fi diff --git a/test/scripts/jenkins_unit.sh b/test/scripts/jenkins_unit.sh index 75610884b542f..a8b5e8e4fdf97 100755 --- a/test/scripts/jenkins_unit.sh +++ b/test/scripts/jenkins_unit.sh @@ -4,4 +4,16 @@ set -e export TEST_BROWSER_HEADLESS=1 -"$(FORCE_COLOR=0 yarn bin)/grunt" jenkins:unit --dev; +if [[ -z "$CODE_COVERAGE" ]] ; then + "$(FORCE_COLOR=0 yarn bin)/grunt" jenkins:unit --dev; +else + echo "NODE_ENV=$NODE_ENV" + echo " -> Running jest tests with coverage" + node scripts/jest --ci --verbose --coverage + echo "" + echo "" + echo " -> Running mocha tests with coverage" + yarn run grunt "test:mochaCoverage"; + echo "" + echo "" +fi diff --git a/test/scripts/jenkins_xpack.sh b/test/scripts/jenkins_xpack.sh index 27f73c0b6e20d..e0055085d9b37 100755 --- a/test/scripts/jenkins_xpack.sh +++ b/test/scripts/jenkins_xpack.sh @@ -4,33 +4,48 @@ set -e export TEST_BROWSER_HEADLESS=1 -echo " -> Running mocha tests" -cd "$XPACK_DIR" -checks-reporter-with-killswitch "X-Pack Karma Tests" yarn test:browser -echo "" -echo "" - -echo " -> Running jest tests" -cd "$XPACK_DIR" -checks-reporter-with-killswitch "X-Pack Jest" node scripts/jest --ci --verbose -echo "" -echo "" - -echo " -> Running SIEM cyclic dependency test" -cd "$XPACK_DIR" -checks-reporter-with-killswitch "X-Pack SIEM cyclic dependency test" node legacy/plugins/siem/scripts/check_circular_deps -echo "" -echo "" - -# FAILING: https://github.com/elastic/kibana/issues/44250 -# echo " -> Running jest contracts tests" -# cd "$XPACK_DIR" -# SLAPSHOT_ONLINE=true CONTRACT_ONLINE=true node scripts/jest_contract.js --ci --verbose -# echo "" -# echo "" - -# echo " -> Running jest integration tests" -# cd "$XPACK_DIR" -# node scripts/jest_integration --ci --verbose -# echo "" -# echo "" +if [[ -z "$CODE_COVERAGE" ]] ; then + echo " -> Running mocha tests" + cd "$XPACK_DIR" + checks-reporter-with-killswitch "X-Pack Karma Tests" yarn test:browser + echo "" + echo "" + + echo " -> Running jest tests" + cd "$XPACK_DIR" + checks-reporter-with-killswitch "X-Pack Jest" node scripts/jest --ci --verbose + echo "" + echo "" + + echo " -> Running SIEM cyclic dependency test" + cd "$XPACK_DIR" + checks-reporter-with-killswitch "X-Pack SIEM cyclic dependency test" node legacy/plugins/siem/scripts/check_circular_deps + echo "" + echo "" + + # FAILING: https://github.com/elastic/kibana/issues/44250 + # echo " -> Running jest contracts tests" + # cd "$XPACK_DIR" + # SLAPSHOT_ONLINE=true CONTRACT_ONLINE=true node scripts/jest_contract.js --ci --verbose + # echo "" + # echo "" + + # echo " -> Running jest integration tests" + # cd "$XPACK_DIR" + # node scripts/jest_integration --ci --verbose + # echo "" + # echo "" +else + echo " -> Running jest tests with coverage" + cd "$XPACK_DIR" + # build runtime for canvas + echo "NODE_ENV=$NODE_ENV" + node ./legacy/plugins/canvas/scripts/shareable_runtime + node scripts/jest --ci --verbose --coverage + # rename file in order to be unique one + test -f ../target/kibana-coverage/jest/coverage-final.json \ + && mv ../target/kibana-coverage/jest/coverage-final.json \ + ../target/kibana-coverage/jest/xpack-coverage-final.json + echo "" + echo "" +fi \ No newline at end of file diff --git a/test/scripts/jenkins_xpack_build_kibana.sh b/test/scripts/jenkins_xpack_build_kibana.sh index 9f2bafc863f41..20b12b302cb39 100755 --- a/test/scripts/jenkins_xpack_build_kibana.sh +++ b/test/scripts/jenkins_xpack_build_kibana.sh @@ -20,10 +20,13 @@ node scripts/functional_tests --assert-none-excluded \ --include-tag ciGroup9 \ --include-tag ciGroup10 -echo " -> building and extracting default Kibana distributable for use in functional tests" -cd "$KIBANA_DIR" -node scripts/build --debug --no-oss -linuxBuild="$(find "$KIBANA_DIR/target" -name 'kibana-*-linux-x86_64.tar.gz')" -installDir="$PARENT_DIR/install/kibana" -mkdir -p "$installDir" -tar -xzf "$linuxBuild" -C "$installDir" --strip=1 +# Do not build kibana for code coverage run +if [[ -z "$CODE_COVERAGE" ]] ; then + echo " -> building and extracting default Kibana distributable for use in functional tests" + cd "$KIBANA_DIR" + node scripts/build --debug --no-oss + linuxBuild="$(find "$KIBANA_DIR/target" -name 'kibana-*-linux-x86_64.tar.gz')" + installDir="$PARENT_DIR/install/kibana" + mkdir -p "$installDir" + tar -xzf "$linuxBuild" -C "$installDir" --strip=1 +fi diff --git a/test/scripts/jenkins_xpack_ci_group.sh b/test/scripts/jenkins_xpack_ci_group.sh index fba05f8f252d7..58c407a848ae3 100755 --- a/test/scripts/jenkins_xpack_ci_group.sh +++ b/test/scripts/jenkins_xpack_ci_group.sh @@ -2,59 +2,60 @@ source test/scripts/jenkins_test_setup.sh -if [[ -z "$IS_PIPELINE_JOB" ]] ; then - echo " -> Ensuring all functional tests are in a ciGroup" +if [[ -z "$CODE_COVERAGE" ]] ; then + if [[ -z "$IS_PIPELINE_JOB" ]] ; then + echo " -> Ensuring all functional tests are in a ciGroup" + cd "$XPACK_DIR" + node scripts/functional_tests --assert-none-excluded \ + --include-tag ciGroup1 \ + --include-tag ciGroup2 \ + --include-tag ciGroup3 \ + --include-tag ciGroup4 \ + --include-tag ciGroup5 \ + --include-tag ciGroup6 \ + --include-tag ciGroup7 \ + --include-tag ciGroup8 \ + --include-tag ciGroup9 \ + --include-tag ciGroup10 + fi + + cd "$KIBANA_DIR" + + if [[ -z "$IS_PIPELINE_JOB" ]] ; then + echo " -> building and extracting default Kibana distributable for use in functional tests" + node scripts/build --debug --no-oss + + linuxBuild="$(find "$KIBANA_DIR/target" -name 'kibana-*-linux-x86_64.tar.gz')" + installDir="$PARENT_DIR/install/kibana" + + mkdir -p "$installDir" + tar -xzf "$linuxBuild" -C "$installDir" --strip=1 + + export KIBANA_INSTALL_DIR="$installDir" + else + installDir="$PARENT_DIR/install/kibana" + destDir="${installDir}-${CI_WORKER_NUMBER}" + cp -R "$installDir" "$destDir" + + export KIBANA_INSTALL_DIR="$destDir" + fi + + echo " -> Running functional and api tests" cd "$XPACK_DIR" - node scripts/functional_tests --assert-none-excluded \ - --include-tag ciGroup1 \ - --include-tag ciGroup2 \ - --include-tag ciGroup3 \ - --include-tag ciGroup4 \ - --include-tag ciGroup5 \ - --include-tag ciGroup6 \ - --include-tag ciGroup7 \ - --include-tag ciGroup8 \ - --include-tag ciGroup9 \ - --include-tag ciGroup10 -fi - -cd "$KIBANA_DIR" - -if [[ -z "$IS_PIPELINE_JOB" ]] ; then - echo " -> building and extracting default Kibana distributable for use in functional tests" - node scripts/build --debug --no-oss - - linuxBuild="$(find "$KIBANA_DIR/target" -name 'kibana-*-linux-x86_64.tar.gz')" - installDir="$PARENT_DIR/install/kibana" - mkdir -p "$installDir" - tar -xzf "$linuxBuild" -C "$installDir" --strip=1 + checks-reporter-with-killswitch "X-Pack Chrome Functional tests / Group ${CI_GROUP}" \ + node scripts/functional_tests \ + --debug --bail \ + --kibana-install-dir "$KIBANA_INSTALL_DIR" \ + --include-tag "ciGroup$CI_GROUP" - export KIBANA_INSTALL_DIR="$installDir" + echo "" + echo "" else - installDir="$PARENT_DIR/install/kibana" - destDir="${installDir}-${CI_WORKER_NUMBER}" - cp -R "$installDir" "$destDir" + echo " -> Running X-Pack functional tests with code coverage" + cd "$XPACK_DIR" - export KIBANA_INSTALL_DIR="$destDir" -fi + export NODE_OPTIONS=--max_old_space_size=8192 -echo " -> Running functional and api tests" -cd "$XPACK_DIR" - -checks-reporter-with-killswitch "X-Pack Chrome Functional tests / Group ${CI_GROUP}" \ - node scripts/functional_tests \ - --debug --bail \ - --kibana-install-dir "$KIBANA_INSTALL_DIR" \ - --include-tag "ciGroup$CI_GROUP" - -echo "" -echo "" - -# checks-reporter-with-killswitch "X-Pack Firefox Functional tests / Group ${CI_GROUP}" \ -# node scripts/functional_tests --debug --bail \ -# --kibana-install-dir "$installDir" \ -# --include-tag "ciGroup$CI_GROUP" \ -# --config "test/functional/config.firefox.js" -# echo "" -# echo "" + node scripts/functional_tests --debug --include-tag "ciGroup$CI_GROUP" +fi diff --git a/test/server_integration/__fixtures__/README.md b/test/server_integration/__fixtures__/README.md new file mode 100644 index 0000000000000..faf881202e55e --- /dev/null +++ b/test/server_integration/__fixtures__/README.md @@ -0,0 +1,79 @@ +# HTTP SSL Test Fixtures + +These PKCS12 files are used to test SSL with a root CA and an intermediate CA. + +The files that are provided by `@kbn/dev-utils` only use a root CA, so we need additional test files for this. + +To generate these additional test files, see the steps below. + +## Step 1. Set environment variables + +```sh +CA1='test_root_ca' +CA2='test_intermediate_ca' +EE='localhost' +``` + +## Step 2. Generate PKCS12 key stores + +Using [elasticsearch-certutil](https://www.elastic.co/guide/en/elasticsearch/reference/current/certutil.html): + +```sh +bin/elasticsearch-certutil ca --ca-dn "CN=Test Root CA" -days 18250 --out $CA1.p12 --pass castorepass +bin/elasticsearch-certutil ca --ca-dn "CN=Test Intermediate CA" -days 18250 --out $CA2.p12 --pass castorepass +bin/elasticsearch-certutil cert --ca $CA2.p12 --ca-pass castorepass --name $EE --dns $EE --out $EE.p12 --pass storepass +``` + +## Step 3. Convert PKCS12 key stores + +Using OpenSSL on macOS: + +```sh +### CONVERT P12 KEYSTORES TO PEM FILES +openssl pkcs12 -in $CA1.p12 -out $CA1.crt -nokeys -passin pass:"castorepass" -passout pass: +openssl pkcs12 -in $CA1.p12 -nocerts -passin pass:"castorepass" -passout pass:"keypass" | openssl rsa -passin pass:"keypass" -out $CA1.key + +openssl pkcs12 -in $CA2.p12 -out $CA2.crt -nokeys -passin pass:"castorepass" -passout pass: +openssl pkcs12 -in $CA2.p12 -nocerts -passin pass:"castorepass" -passout pass:"keypass" | openssl rsa -passin pass:"keypass" -out $CA2.key + +openssl pkcs12 -in $EE.p12 -out $EE.crt -clcerts -passin pass:"storepass" -passout pass: +openssl pkcs12 -in $EE.p12 -nocerts -passin pass:"storepass" -passout pass:"keypass" | openssl rsa -passin pass:"keypass" -out $EE.key + +### RE-SIGN INTERMEDIATE CA CERT +mkdir -p ./tmp +openssl x509 -x509toreq -in $CA2.crt -signkey $CA2.key -out ./tmp/$CA2.csr +dd if=/dev/urandom of=./tmp/rand bs=256 count=1 +touch ./tmp/index.txt +echo "01" > ./tmp/serial +cp /System/Library/OpenSSL/openssl.cnf ./tmp/ +echo " +[ tmpcnf ] +dir = ./ +certs = ./ +new_certs_dir = ./tmp +crl_dir = ./tmp/crl +database = ./tmp/index.txt +unique_subject = no +certificate = ./$CA1.crt +serial = ./tmp/serial +crlnumber = ./tmp/crlnumber +crl = ./tmp/crl.pem +private_key = ./$CA1.key +RANDFILE = ./tmp/rand +x509_extensions = v3_ca +name_opt = ca_default +cert_opt = ca_default +default_days = 18250 +default_crl_days= 30 +default_md = sha256 +preserve = no +policy = policy_anything +" >> ./tmp/openssl.cnf + +# The next command requires user input +openssl ca -config ./tmp/openssl.cnf -name tmpcnf -in ./tmp/$CA2.csr -out $CA2.crt -verbose + +### CONVERT PEM FILES BACK TO P12 KEYSTORES +cat $CA2.key $CA2.crt $CA1.crt | openssl pkcs12 -export -name $CA2 -passout pass:"castorepass" -out $CA2.p12 +cat $EE.key $EE.crt $CA1.crt $CA2.crt | openssl pkcs12 -export -name $EE -passout pass:"storepass" -out $EE.p12 +``` diff --git a/test/server_integration/__fixtures__/index.ts b/test/server_integration/__fixtures__/index.ts new file mode 100644 index 0000000000000..40f1ddb7fa0ba --- /dev/null +++ b/test/server_integration/__fixtures__/index.ts @@ -0,0 +1,25 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { resolve } from 'path'; + +export const CA1_CERT_PATH = resolve(__dirname, './test_root_ca.crt'); +export const CA2_CERT_PATH = resolve(__dirname, './test_intermediate_ca.crt'); +export const EE_P12_PATH = resolve(__dirname, './localhost.p12'); +export const EE_P12_PASSWORD = 'storepass'; diff --git a/test/server_integration/__fixtures__/localhost.p12 b/test/server_integration/__fixtures__/localhost.p12 new file mode 100644 index 0000000000000..1b0d11fb88098 Binary files /dev/null and b/test/server_integration/__fixtures__/localhost.p12 differ diff --git a/test/server_integration/__fixtures__/test_intermediate_ca.crt b/test/server_integration/__fixtures__/test_intermediate_ca.crt new file mode 100644 index 0000000000000..2e143200d290a --- /dev/null +++ b/test/server_integration/__fixtures__/test_intermediate_ca.crt @@ -0,0 +1,79 @@ +Certificate: + Data: + Version: 3 (0x2) + Serial Number: 1 (0x1) + Signature Algorithm: sha256WithRSAEncryption + Issuer: CN=Test Root CA + Validity + Not Before: Jan 9 15:50:00 2020 GMT + Not After : Dec 27 15:50:00 2069 GMT + Subject: CN=Test Intermediate CA + Subject Public Key Info: + Public Key Algorithm: rsaEncryption + Public-Key: (2048 bit) + Modulus: + 00:ba:bb:d5:d7:5a:0a:b0:95:43:63:73:bc:1f:3f: + a4:71:3c:3f:69:96:8c:5d:e1:5b:82:95:2c:d1:b3: + 3b:7a:5c:f0:54:c9:d2:be:37:c7:81:ce:db:90:fa: + c0:0c:e8:b7:e5:51:18:29:0b:47:89:0f:b1:e3:7f: + 06:f0:fe:f6:a7:e8:42:36:58:4b:c7:04:81:48:5b: + 20:11:be:95:6b:bd:8c:0b:1b:21:2d:26:47:0b:c5: + 98:59:d7:a2:35:09:4f:1a:eb:74:d4:bc:fd:df:41: + 45:5d:fd:a6:0e:dd:02:7e:52:a4:21:9d:ac:c7:0e: + 73:50:2d:7b:6e:30:05:20:a2:ee:60:fa:0e:80:7d: + d0:0c:fd:24:ae:ef:96:70:7e:a3:bc:87:e4:fc:50: + 43:a7:a6:ef:dc:0d:7d:9e:02:73:3d:6b:b1:b3:e9: + d5:98:42:2b:ed:63:c1:a2:bb:49:19:a4:5b:d6:6e: + 33:54:44:19:f3:51:db:a4:ea:92:67:13:5e:80:bf: + 6d:1f:59:e4:f0:8c:93:10:38:54:37:8f:a6:4a:42: + 56:5f:db:d6:d5:2c:12:58:4c:42:aa:2c:19:8c:f7: + 30:51:b2:2c:29:c1:6b:29:73:bc:c6:45:63:41:90: + 80:0a:84:d5:02:0c:9c:67:cf:73:4e:62:40:51:ee: + 67:03 + Exponent: 65537 (0x10001) + X509v3 extensions: + X509v3 Subject Key Identifier: + B4:48:6A:C8:21:77:A5:03:CF:48:C4:62:74:26:03:3F:BC:88:8C:92 + X509v3 Authority Key Identifier: + keyid:03:9B:FF:88:CA:33:A2:71:C5:31:51:A6:DA:15:EF:44:C2:CB:D3:9F + DirName:/CN=Test Root CA + serial:78:77:49:60:3B:E7:73:18:06:75:45:A7:8E:8E:B4:4E:E4:9A:E3:B0 + + X509v3 Basic Constraints: + CA:TRUE + Signature Algorithm: sha256WithRSAEncryption + a3:10:97:ab:dd:43:8a:5b:c7:a6:b9:33:92:7b:61:fb:f0:3f: + 54:05:50:46:9e:62:11:d3:60:59:77:93:53:48:0b:c9:cf:bc: + c0:3c:b4:47:f4:f6:66:2c:86:76:38:3b:5d:13:77:41:ce:d7: + 16:ca:5e:29:33:1f:a7:ea:82:e4:0c:ad:f8:50:1d:54:cd:28: + a9:22:59:a8:e1:3f:05:b8:fb:5e:54:72:58:fa:a1:3e:f8:99: + bf:d6:50:99:8b:12:52:37:41:be:5f:c9:7d:04:46:8b:fd:8f: + 7f:64:a1:0d:b8:2b:ca:e9:4a:54:e2:bb:8b:39:b7:87:6f:8b: + 17:46:b4:5d:16:aa:75:5c:fb:33:29:52:51:24:7b:f2:d9:b3: + 9b:99:bf:08:6c:2c:43:8a:74:63:c1:32:ed:6b:4a:53:88:51: + c2:10:dd:92:f2:6f:af:65:f1:08:5a:cc:a6:2b:54:95:2b:2a: + a1:90:f2:eb:08:91:26:18:44:b7:49:11:09:c1:1c:aa:2d:b2: + d6:56:02:34:7a:97:fb:60:c5:1e:66:84:c0:40:6f:26:52:77: + 85:a3:ab:d5:8e:f0:d0:d0:2e:e0:6f:8a:de:72:e0:ee:96:e5: + 5d:4a:e9:c1:4c:c6:45:c7:36:6b:7a:1a:a6:64:71:9b:7c:7e: + 59:93:bd:b6 +-----BEGIN CERTIFICATE----- +MIIDODCCAiCgAwIBAgIBATANBgkqhkiG9w0BAQsFADAXMRUwEwYDVQQDEwxUZXN0 +IFJvb3QgQ0EwIBcNMjAwMTA5MTU1MDAwWhgPMjA2OTEyMjcxNTUwMDBaMB8xHTAb +BgNVBAMTFFRlc3QgSW50ZXJtZWRpYXRlIENBMIIBIjANBgkqhkiG9w0BAQEFAAOC +AQ8AMIIBCgKCAQEAurvV11oKsJVDY3O8Hz+kcTw/aZaMXeFbgpUs0bM7elzwVMnS +vjfHgc7bkPrADOi35VEYKQtHiQ+x438G8P72p+hCNlhLxwSBSFsgEb6Va72MCxsh +LSZHC8WYWdeiNQlPGut01Lz930FFXf2mDt0CflKkIZ2sxw5zUC17bjAFIKLuYPoO +gH3QDP0kru+WcH6jvIfk/FBDp6bv3A19ngJzPWuxs+nVmEIr7WPBortJGaRb1m4z +VEQZ81HbpOqSZxNegL9tH1nk8IyTEDhUN4+mSkJWX9vW1SwSWExCqiwZjPcwUbIs +KcFrKXO8xkVjQZCACoTVAgycZ89zTmJAUe5nAwIDAQABo4GEMIGBMB0GA1UdDgQW +BBS0SGrIIXelA89IxGJ0JgM/vIiMkjBSBgNVHSMESzBJgBQDm/+IyjOiccUxUaba +Fe9EwsvTn6EbpBkwFzEVMBMGA1UEAxMMVGVzdCBSb290IENBghR4d0lgO+dzGAZ1 +RaeOjrRO5JrjsDAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBCwUAA4IBAQCjEJer +3UOKW8emuTOSe2H78D9UBVBGnmIR02BZd5NTSAvJz7zAPLRH9PZmLIZ2ODtdE3dB +ztcWyl4pMx+n6oLkDK34UB1UzSipIlmo4T8FuPteVHJY+qE++Jm/1lCZixJSN0G+ +X8l9BEaL/Y9/ZKENuCvK6UpU4ruLObeHb4sXRrRdFqp1XPszKVJRJHvy2bObmb8I +bCxDinRjwTLta0pTiFHCEN2S8m+vZfEIWsymK1SVKyqhkPLrCJEmGES3SREJwRyq +LbLWVgI0epf7YMUeZoTAQG8mUneFo6vVjvDQ0C7gb4recuDuluVdSunBTMZFxzZr +ehqmZHGbfH5Zk722 +-----END CERTIFICATE----- diff --git a/test/server_integration/__fixtures__/test_root_ca.crt b/test/server_integration/__fixtures__/test_root_ca.crt new file mode 100644 index 0000000000000..678c9a7467a22 --- /dev/null +++ b/test/server_integration/__fixtures__/test_root_ca.crt @@ -0,0 +1,24 @@ +Bag Attributes + friendlyName: ca + localKeyID: 54 69 6D 65 20 31 35 37 38 35 38 34 39 34 35 33 30 37 +subject=/CN=Test Root CA +issuer=/CN=Test Root CA +-----BEGIN CERTIFICATE----- +MIIDETCCAfmgAwIBAgIUeHdJYDvncxgGdUWnjo60TuSa47AwDQYJKoZIhvcNAQEL +BQAwFzEVMBMGA1UEAxMMVGVzdCBSb290IENBMCAXDTIwMDEwOTE1NDkwNVoYDzIw +NjkxMjI3MTU0OTA1WjAXMRUwEwYDVQQDEwxUZXN0IFJvb3QgQ0EwggEiMA0GCSqG +SIb3DQEBAQUAA4IBDwAwggEKAoIBAQC2G9Bmax5yFvdWEMleXcFK7G0ir04/sd4v +pRuqYhg+LhxlDOnd7HFtSsI2GGZaBktpL4eWOA8sAZ+eL89P3JV5WFDAuvlK8RZt +ECnPzl7Yar3nhPjNO5F1xbyHCPNSiQVYx7avkLJu3sv/okA65ON+BHYijbNNwS0/ +YtZYZWF7qR6rygXiLHcCIwWwZntBAKHGsBzxZv+28xRMUGsYWHq1PI25CRfDuVub +jC3LpAiJUTkrN5cE8Mpy6R9EH3c/qCk1I2daUKJVJhIzrUrsyNYwpCpbtrE605lK +qsRVkxoAK5i3zZRqiQ/m4FEmr0rTmbLJw09u+jIzfye2ivNiC7PZAgMBAAGjUzBR +MB0GA1UdDgQWBBQDm/+IyjOiccUxUabaFe9EwsvTnzAfBgNVHSMEGDAWgBQDm/+I +yjOiccUxUabaFe9EwsvTnzAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3DQEBCwUA +A4IBAQBclwdYhVNp3I4gTzxl0kbjya28auokp1+NUhAJ++eGGeTySEzbggHWkSSw +jXhzbwri1T+80smvj4XkbSvLzPurSxT1if4kmUDh+XApx/pQb/2l88lRdLqBCcSn +UxrdeDGPGMAYsxH/+/s2rMoaagBb4n8dayBesqIa+Nt+Mf8cqfM30pRGqk5HtoI/ +ZUZDOQ7JJc3mg1usjA3mtgsUQ88zAH9C3fMpf/I3sr2UQqaXYZlzmh3r5U9yNYyw +SV0NnaDd3BVJ4qOumTjlYalRJFDrvn+aNkyPN6XiwxA1qd/uVW5mIJhkoxPYWkG4 +M1b0sea/9IVucYYyXI+GyFNI7B5N +-----END CERTIFICATE----- diff --git a/test/server_integration/config.js b/test/server_integration/config.js index 6928dedb9fb6f..26e00e5fce294 100644 --- a/test/server_integration/config.js +++ b/test/server_integration/config.js @@ -18,7 +18,7 @@ */ import { - KibanaSupertestProvider, + createKibanaSupertestProvider, KibanaSupertestWithoutAuthProvider, ElasticsearchSupertestProvider, } from './services'; @@ -30,7 +30,7 @@ export default async function({ readConfigFile }) { return { services: { ...commonConfig.get('services'), - supertest: KibanaSupertestProvider, + supertest: createKibanaSupertestProvider(), supertestWithoutAuth: KibanaSupertestWithoutAuthProvider, esSupertest: ElasticsearchSupertestProvider, }, diff --git a/test/server_integration/http/ssl/config.js b/test/server_integration/http/ssl/config.js index 1cf4cdf6064c1..2f2e7b778d361 100644 --- a/test/server_integration/http/ssl/config.js +++ b/test/server_integration/http/ssl/config.js @@ -17,12 +17,21 @@ * under the License. */ +import { readFileSync } from 'fs'; +import { CA_CERT_PATH, KBN_CERT_PATH, KBN_KEY_PATH } from '@kbn/dev-utils'; +import { createKibanaSupertestProvider } from '../../services'; + export default async function({ readConfigFile }) { const httpConfig = await readConfigFile(require.resolve('../../config')); return { testFiles: [require.resolve('./')], - services: httpConfig.get('services'), + services: { + ...httpConfig.get('services'), + supertest: createKibanaSupertestProvider({ + certificateAuthorities: [readFileSync(CA_CERT_PATH)], + }), + }, servers: { ...httpConfig.get('servers'), kibana: { @@ -39,8 +48,8 @@ export default async function({ readConfigFile }) { serverArgs: [ ...httpConfig.get('kbnTestServer.serverArgs'), '--server.ssl.enabled=true', - `--server.ssl.key=${require.resolve('../../../dev_certs/server.key')}`, - `--server.ssl.certificate=${require.resolve('../../../dev_certs/server.crt')}`, + `--server.ssl.key=${KBN_KEY_PATH}`, + `--server.ssl.certificate=${KBN_CERT_PATH}`, ], }, }; diff --git a/test/server_integration/http/ssl_redirect/config.js b/test/server_integration/http/ssl_redirect/config.js index 36e68eaf8e345..20ab4a210cc7b 100644 --- a/test/server_integration/http/ssl_redirect/config.js +++ b/test/server_integration/http/ssl_redirect/config.js @@ -17,7 +17,10 @@ * under the License. */ -import { KibanaSupertestProvider } from '../../services'; +import { readFileSync } from 'fs'; +import { CA_CERT_PATH, KBN_CERT_PATH, KBN_KEY_PATH } from '@kbn/dev-utils'; + +import { createKibanaSupertestProvider } from '../../services'; export default async function({ readConfigFile }) { const httpConfig = await readConfigFile(require.resolve('../../config')); @@ -34,8 +37,10 @@ export default async function({ readConfigFile }) { testFiles: [require.resolve('./')], services: { ...httpConfig.get('services'), - //eslint-disable-next-line new-cap - supertest: arg => KibanaSupertestProvider(arg, supertestOptions), + supertest: createKibanaSupertestProvider({ + certificateAuthorities: [readFileSync(CA_CERT_PATH)], + options: supertestOptions, + }), }, servers: { ...httpConfig.get('servers'), @@ -54,8 +59,8 @@ export default async function({ readConfigFile }) { serverArgs: [ ...httpConfig.get('kbnTestServer.serverArgs'), '--server.ssl.enabled=true', - `--server.ssl.key=${require.resolve('../../../dev_certs/server.key')}`, - `--server.ssl.certificate=${require.resolve('../../../dev_certs/server.crt')}`, + `--server.ssl.key=${KBN_KEY_PATH}`, + `--server.ssl.certificate=${KBN_CERT_PATH}`, `--server.ssl.redirectHttpFromPort=${redirectPort}`, ], }, diff --git a/test/server_integration/http/ssl_with_p12/config.js b/test/server_integration/http/ssl_with_p12/config.js new file mode 100644 index 0000000000000..e220914af54f4 --- /dev/null +++ b/test/server_integration/http/ssl_with_p12/config.js @@ -0,0 +1,56 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { readFileSync } from 'fs'; +import { CA_CERT_PATH, KBN_P12_PATH, KBN_P12_PASSWORD } from '@kbn/dev-utils'; +import { createKibanaSupertestProvider } from '../../services'; + +export default async function({ readConfigFile }) { + const httpConfig = await readConfigFile(require.resolve('../../config')); + + return { + testFiles: [require.resolve('./')], + services: { + ...httpConfig.get('services'), + supertest: createKibanaSupertestProvider({ + certificateAuthorities: [readFileSync(CA_CERT_PATH)], + }), + }, + servers: { + ...httpConfig.get('servers'), + kibana: { + ...httpConfig.get('servers.kibana'), + protocol: 'https', + }, + }, + junit: { + reportName: 'Http SSL Integration Tests', + }, + esTestCluster: httpConfig.get('esTestCluster'), + kbnTestServer: { + ...httpConfig.get('kbnTestServer'), + serverArgs: [ + ...httpConfig.get('kbnTestServer.serverArgs'), + '--server.ssl.enabled=true', + `--server.ssl.keystore.path=${KBN_P12_PATH}`, + `--server.ssl.keystore.password=${KBN_P12_PASSWORD}`, + ], + }, + }; +} diff --git a/test/server_integration/http/ssl_with_p12/index.js b/test/server_integration/http/ssl_with_p12/index.js new file mode 100644 index 0000000000000..700f30ddc21a9 --- /dev/null +++ b/test/server_integration/http/ssl_with_p12/index.js @@ -0,0 +1,28 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export default function({ getService }) { + const supertest = getService('supertest'); + + describe('kibana server with ssl', () => { + it('handles requests using ssl with a P12 keystore', async () => { + await supertest.get('/').expect(302); + }); + }); +} diff --git a/test/server_integration/http/ssl_with_p12_intermediate/config.js b/test/server_integration/http/ssl_with_p12_intermediate/config.js new file mode 100644 index 0000000000000..73a77425ec774 --- /dev/null +++ b/test/server_integration/http/ssl_with_p12_intermediate/config.js @@ -0,0 +1,56 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +import { readFileSync } from 'fs'; +import { CA1_CERT_PATH, CA2_CERT_PATH, EE_P12_PATH, EE_P12_PASSWORD } from '../../__fixtures__'; +import { createKibanaSupertestProvider } from '../../services'; + +export default async function({ readConfigFile }) { + const httpConfig = await readConfigFile(require.resolve('../../config')); + + return { + testFiles: [require.resolve('./')], + services: { + ...httpConfig.get('services'), + supertest: createKibanaSupertestProvider({ + certificateAuthorities: [readFileSync(CA1_CERT_PATH), readFileSync(CA2_CERT_PATH)], + }), + }, + servers: { + ...httpConfig.get('servers'), + kibana: { + ...httpConfig.get('servers.kibana'), + protocol: 'https', + }, + }, + junit: { + reportName: 'Http SSL Integration Tests', + }, + esTestCluster: httpConfig.get('esTestCluster'), + kbnTestServer: { + ...httpConfig.get('kbnTestServer'), + serverArgs: [ + ...httpConfig.get('kbnTestServer.serverArgs'), + '--server.ssl.enabled=true', + `--server.ssl.keystore.path=${EE_P12_PATH}`, + `--server.ssl.keystore.password=${EE_P12_PASSWORD}`, + ], + }, + }; +} diff --git a/test/server_integration/http/ssl_with_p12_intermediate/index.js b/test/server_integration/http/ssl_with_p12_intermediate/index.js new file mode 100644 index 0000000000000..fb079a4e091c3 --- /dev/null +++ b/test/server_integration/http/ssl_with_p12_intermediate/index.js @@ -0,0 +1,28 @@ +/* + * Licensed to Elasticsearch B.V. under one or more contributor + * license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright + * ownership. Elasticsearch B.V. licenses this file to you under + * the Apache License, Version 2.0 (the "License"); you may + * not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +export default function({ getService }) { + const supertest = getService('supertest'); + + describe('kibana server with ssl', () => { + it('handles requests using ssl with a P12 keystore that uses an intermediate CA', async () => { + await supertest.get('/').expect(302); + }); + }); +} diff --git a/test/server_integration/services/index.js b/test/server_integration/services/index.js index 32a11f30b8683..4904bfc9eeef6 100644 --- a/test/server_integration/services/index.js +++ b/test/server_integration/services/index.js @@ -18,7 +18,7 @@ */ export { - KibanaSupertestProvider, + createKibanaSupertestProvider, KibanaSupertestWithoutAuthProvider, ElasticsearchSupertestProvider, } from './supertest'; diff --git a/test/server_integration/services/supertest.js b/test/server_integration/services/supertest.js index c09932b0207ac..74bb5400bc299 100644 --- a/test/server_integration/services/supertest.js +++ b/test/server_integration/services/supertest.js @@ -17,25 +17,19 @@ * under the License. */ -import { readFileSync } from 'fs'; import { format as formatUrl } from 'url'; import supertestAsPromised from 'supertest-as-promised'; -export function KibanaSupertestProvider({ getService }, options) { - const config = getService('config'); - const kibanaServerUrl = options ? formatUrl(options) : formatUrl(config.get('servers.kibana')); - - const kibanaServerCert = config - .get('kbnTestServer.serverArgs') - .filter(arg => arg.startsWith('--server.ssl.certificate')) - .map(arg => arg.split('=').pop()) - .map(path => readFileSync(path)) - .shift(); +export function createKibanaSupertestProvider({ certificateAuthorities, options } = {}) { + return function({ getService }) { + const config = getService('config'); + const kibanaServerUrl = options ? formatUrl(options) : formatUrl(config.get('servers.kibana')); - return kibanaServerCert - ? supertestAsPromised.agent(kibanaServerUrl, { ca: kibanaServerCert }) - : supertestAsPromised(kibanaServerUrl); + return certificateAuthorities + ? supertestAsPromised.agent(kibanaServerUrl, { ca: certificateAuthorities }) + : supertestAsPromised(kibanaServerUrl); + }; } export function KibanaSupertestWithoutAuthProvider({ getService }) { diff --git a/test/typings/encode_uri_query.d.ts b/test/typings/encode_uri_query.d.ts deleted file mode 100644 index 4bfc554624446..0000000000000 --- a/test/typings/encode_uri_query.d.ts +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -declare module 'encode-uri-query' { - function encodeUriQuery(query: string, usePercentageSpace?: boolean): string; - // eslint-disable-next-line import/no-default-export - export default encodeUriQuery; -} diff --git a/typings/encode_uri_query.d.ts b/typings/encode_uri_query.d.ts deleted file mode 100644 index 4bfc554624446..0000000000000 --- a/typings/encode_uri_query.d.ts +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -declare module 'encode-uri-query' { - function encodeUriQuery(query: string, usePercentageSpace?: boolean): string; - // eslint-disable-next-line import/no-default-export - export default encodeUriQuery; -} diff --git a/vars/esSnapshots.groovy b/vars/esSnapshots.groovy new file mode 100644 index 0000000000000..884fbcdb17aeb --- /dev/null +++ b/vars/esSnapshots.groovy @@ -0,0 +1,50 @@ +def promote(snapshotVersion, snapshotId) { + def snapshotDestination = "${snapshotVersion}/archives/${snapshotId}" + def MANIFEST_URL = "https://storage.googleapis.com/kibana-ci-es-snapshots-daily/${snapshotDestination}/manifest.json" + + dir('verified-manifest') { + def verifiedSnapshotFilename = 'manifest-latest-verified.json' + + sh """ + curl -O '${MANIFEST_URL}' + mv manifest.json ${verifiedSnapshotFilename} + """ + + googleStorageUpload( + credentialsId: 'kibana-ci-gcs-plugin', + bucket: "gs://kibana-ci-es-snapshots-daily/${snapshotVersion}", + pattern: verifiedSnapshotFilename, + sharedPublicly: false, + showInline: false, + ) + } + + // This would probably be more efficient if we could just copy using gsutil and specifying buckets for src and dest + // But we don't currently have access to the GCS credentials in a way that can be consumed easily from here... + dir('transfer-to-permanent') { + googleStorageDownload( + credentialsId: 'kibana-ci-gcs-plugin', + bucketUri: "gs://kibana-ci-es-snapshots-daily/${snapshotDestination}/*", + localDirectory: '.', + pathPrefix: snapshotDestination, + ) + + def manifestJson = readFile file: 'manifest.json' + writeFile( + file: 'manifest.json', + text: manifestJson.replace("kibana-ci-es-snapshots-daily/${snapshotDestination}", "kibana-ci-es-snapshots-permanent/${snapshotVersion}") + ) + + // Ideally we would have some delete logic here before uploading, + // But we don't currently have access to the GCS credentials in a way that can be consumed easily from here... + googleStorageUpload( + credentialsId: 'kibana-ci-gcs-plugin', + bucket: "gs://kibana-ci-es-snapshots-permanent/${snapshotVersion}", + pattern: '*.*', + sharedPublicly: false, + showInline: false, + ) + } +} + +return this diff --git a/vars/kibanaPipeline.groovy b/vars/kibanaPipeline.groovy index 18f214554b444..5c6be70514c61 100644 --- a/vars/kibanaPipeline.groovy +++ b/vars/kibanaPipeline.groovy @@ -137,13 +137,8 @@ def jobRunner(label, useRamDisk, closure) { def scmVars // Try to clone from Github up to 8 times, waiting 15 secs between attempts - retry(8) { - try { - scmVars = checkout scm - } catch (ex) { - sleep 15 - throw ex - } + retryWithDelay(8, 15) { + scmVars = checkout scm } withEnv([ @@ -181,6 +176,18 @@ def uploadGcsArtifact(uploadPrefix, pattern) { ) } +def downloadCoverageArtifacts() { + def storageLocation = "gs://kibana-ci-artifacts/jobs/${env.JOB_NAME}/${BUILD_NUMBER}/coverage/" + def targetLocation = "/tmp/downloaded_coverage" + + sh "mkdir -p '${targetLocation}' && gsutil -m cp -r '${storageLocation}' '${targetLocation}'" +} + +def uploadCoverageArtifacts(prefix, pattern) { + def uploadPrefix = "kibana-ci-artifacts/jobs/${env.JOB_NAME}/${BUILD_NUMBER}/coverage/${prefix}" + uploadGcsArtifact(uploadPrefix, pattern) +} + def withGcsArtifactUpload(workerName, closure) { def uploadPrefix = "kibana-ci-artifacts/jobs/${env.JOB_NAME}/${BUILD_NUMBER}/${workerName}" def ARTIFACT_PATTERNS = [ @@ -206,6 +213,11 @@ def withGcsArtifactUpload(workerName, closure) { } } }) + + if (env.CODE_COVERAGE) { + sh 'tar -czf kibana-coverage.tar.gz target/kibana-coverage/**/*' + uploadGcsArtifact("kibana-ci-artifacts/jobs/${env.JOB_NAME}/${BUILD_NUMBER}/coverage/${workerName}", 'kibana-coverage.tar.gz') + } } def publishJunit() { diff --git a/vars/retryWithDelay.groovy b/vars/retryWithDelay.groovy new file mode 100644 index 0000000000000..70d6f86a63ab2 --- /dev/null +++ b/vars/retryWithDelay.groovy @@ -0,0 +1,16 @@ +def call(retryTimes, delaySecs, closure) { + retry(retryTimes) { + try { + closure() + } catch (ex) { + sleep delaySecs + throw ex + } + } +} + +def call(retryTimes, Closure closure) { + call(retryTimes, 15, closure) +} + +return this diff --git a/webpackShims/angular.js b/webpackShims/angular.js deleted file mode 100644 index 4857f0f8975bc..0000000000000 --- a/webpackShims/angular.js +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -require('jquery'); -require('../node_modules/angular/angular'); -module.exports = window.angular; diff --git a/webpackShims/jquery.js b/webpackShims/jquery.js deleted file mode 100644 index da81dd18cf71e..0000000000000 --- a/webpackShims/jquery.js +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -window.jQuery = window.$ = module.exports = require('../node_modules/jquery/dist/jquery'); diff --git a/webpackShims/moment-timezone.js b/webpackShims/moment-timezone.js deleted file mode 100644 index d5e032ff21eef..0000000000000 --- a/webpackShims/moment-timezone.js +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -var moment = (module.exports = require('../node_modules/moment-timezone/moment-timezone')); -moment.tz.load(require('../node_modules/moment-timezone/data/packed/latest.json')); diff --git a/webpackShims/moment.js b/webpackShims/moment.js deleted file mode 100644 index 31476d18c9562..0000000000000 --- a/webpackShims/moment.js +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Licensed to Elasticsearch B.V. under one or more contributor - * license agreements. See the NOTICE file distributed with - * this work for additional information regarding copyright - * ownership. Elasticsearch B.V. licenses this file to you under - * the Apache License, Version 2.0 (the "License"); you may - * not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, - * software distributed under the License is distributed on an - * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY - * KIND, either express or implied. See the License for the - * specific language governing permissions and limitations - * under the License. - */ - -module.exports = require('../node_modules/moment/min/moment-with-locales.min.js'); diff --git a/x-pack/.i18nrc.json b/x-pack/.i18nrc.json index 7e86d2f1dc435..71e3bdd6c8c84 100644 --- a/x-pack/.i18nrc.json +++ b/x-pack/.i18nrc.json @@ -4,6 +4,7 @@ "xpack.actions": "legacy/plugins/actions", "xpack.advancedUiActions": "plugins/advanced_ui_actions", "xpack.alerting": "legacy/plugins/alerting", + "xpack.triggersActionsUI": "legacy/plugins/triggers_actions_ui", "xpack.apm": "legacy/plugins/apm", "xpack.beatsManagement": "legacy/plugins/beats_management", "xpack.canvas": "legacy/plugins/canvas", diff --git a/x-pack/dev-tools/jest/create_jest_config.js b/x-pack/dev-tools/jest/create_jest_config.js index cd4414b5fdebe..f38181ce56a2f 100644 --- a/x-pack/dev-tools/jest/create_jest_config.js +++ b/x-pack/dev-tools/jest/create_jest_config.js @@ -28,9 +28,10 @@ export function createJestConfig({ kibanaDirectory, xPackKibanaDirectory }) { '\\.(css|less|scss)$': `${kibanaDirectory}/src/dev/jest/mocks/style_mock.js`, '^test_utils/enzyme_helpers': `${xPackKibanaDirectory}/test_utils/enzyme_helpers.tsx`, '^test_utils/find_test_subject': `${xPackKibanaDirectory}/test_utils/find_test_subject.ts`, + '^test_utils/stub_web_worker': `${xPackKibanaDirectory}/test_utils/stub_web_worker.ts`, }, coverageDirectory: '/../target/kibana-coverage/jest', - coverageReporters: ['html'], + coverageReporters: !!process.env.CODE_COVERAGE ? ['json'] : ['html'], setupFiles: [ `${kibanaDirectory}/src/dev/jest/setup/babel_polyfill.js`, `/dev-tools/jest/setup/polyfills.js`, diff --git a/x-pack/index.js b/x-pack/index.js index 56547f89b1e90..83a7b5540334f 100644 --- a/x-pack/index.js +++ b/x-pack/index.js @@ -42,6 +42,7 @@ import { transform } from './legacy/plugins/transform'; import { actions } from './legacy/plugins/actions'; import { alerting } from './legacy/plugins/alerting'; import { lens } from './legacy/plugins/lens'; +import { triggersActionsUI } from './legacy/plugins/triggers_actions_ui'; module.exports = function(kibana) { return [ @@ -83,5 +84,6 @@ module.exports = function(kibana) { snapshotRestore(kibana), actions(kibana), alerting(kibana), + triggersActionsUI(kibana), ]; }; diff --git a/x-pack/legacy/plugins/actions/server/action_type_registry.test.ts b/x-pack/legacy/plugins/actions/server/action_type_registry.test.ts index 98721c5675824..63f1b545179c7 100644 --- a/x-pack/legacy/plugins/actions/server/action_type_registry.test.ts +++ b/x-pack/legacy/plugins/actions/server/action_type_registry.test.ts @@ -4,13 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import { taskManagerMock } from '../../task_manager/task_manager.mock'; +import { taskManagerMock } from '../../../../plugins/task_manager/server/task_manager.mock'; import { ActionTypeRegistry } from './action_type_registry'; import { ExecutorType } from './types'; import { ActionExecutor, ExecutorError, TaskRunnerFactory } from './lib'; import { configUtilsMock } from './actions_config.mock'; -const mockTaskManager = taskManagerMock.create(); +const mockTaskManager = taskManagerMock.setup(); const actionTypeRegistryParams = { taskManager: mockTaskManager, taskRunnerFactory: new TaskRunnerFactory(new ActionExecutor()), diff --git a/x-pack/legacy/plugins/actions/server/action_type_registry.ts b/x-pack/legacy/plugins/actions/server/action_type_registry.ts index a09788e45c394..351c1add7b451 100644 --- a/x-pack/legacy/plugins/actions/server/action_type_registry.ts +++ b/x-pack/legacy/plugins/actions/server/action_type_registry.ts @@ -6,11 +6,11 @@ import Boom from 'boom'; import { i18n } from '@kbn/i18n'; -import { TaskManagerSetupContract } from './shim'; -import { RunContext } from '../../task_manager'; +import { RunContext, TaskManagerSetupContract } from '../../../../plugins/task_manager/server'; import { ExecutorError, TaskRunnerFactory } from './lib'; import { ActionType } from './types'; import { ActionsConfigurationUtilities } from './actions_config'; + interface ConstructorOptions { taskManager: TaskManagerSetupContract; taskRunnerFactory: TaskRunnerFactory; diff --git a/x-pack/legacy/plugins/actions/server/actions_client.test.ts b/x-pack/legacy/plugins/actions/server/actions_client.test.ts index 73b1de224eb32..dfbd2db4b6842 100644 --- a/x-pack/legacy/plugins/actions/server/actions_client.test.ts +++ b/x-pack/legacy/plugins/actions/server/actions_client.test.ts @@ -10,7 +10,7 @@ import { ActionTypeRegistry } from './action_type_registry'; import { ActionsClient } from './actions_client'; import { ExecutorType } from './types'; import { ActionExecutor, TaskRunnerFactory } from './lib'; -import { taskManagerMock } from '../../task_manager/task_manager.mock'; +import { taskManagerMock } from '../../../../plugins/task_manager/server/task_manager.mock'; import { configUtilsMock } from './actions_config.mock'; import { getActionsConfigurationUtilities } from './actions_config'; @@ -23,7 +23,7 @@ const defaultKibanaIndex = '.kibana'; const savedObjectsClient = savedObjectsClientMock.create(); const scopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); -const mockTaskManager = taskManagerMock.create(); +const mockTaskManager = taskManagerMock.setup(); const actionTypeRegistryParams = { taskManager: mockTaskManager, diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/email.test.ts b/x-pack/legacy/plugins/actions/server/builtin_action_types/email.test.ts index 4aaecc8e9d7df..74263c603c11e 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/email.test.ts +++ b/x-pack/legacy/plugins/actions/server/builtin_action_types/email.test.ts @@ -49,7 +49,7 @@ beforeEach(() => { describe('actionTypeRegistry.get() works', () => { test('action type static data is as expected', () => { expect(actionType.id).toEqual(ACTION_TYPE_ID); - expect(actionType.name).toEqual('email'); + expect(actionType.name).toEqual('Email'); }); }); diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/email.ts b/x-pack/legacy/plugins/actions/server/builtin_action_types/email.ts index dd2bd328ce53f..94d7852e76fad 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/email.ts +++ b/x-pack/legacy/plugins/actions/server/builtin_action_types/email.ts @@ -118,7 +118,9 @@ export function getActionType(params: GetActionTypeParams): ActionType { const { logger, configurationUtilities } = params; return { id: '.email', - name: 'email', + name: i18n.translate('xpack.actions.builtin.emailTitle', { + defaultMessage: 'Email', + }), validate: { config: schema.object(ConfigSchemaProps, { validate: curry(validateConfig)(configurationUtilities), diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/es_index.test.ts b/x-pack/legacy/plugins/actions/server/builtin_action_types/es_index.test.ts index 35d81ba74fa72..dbac84ef681f1 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/es_index.test.ts +++ b/x-pack/legacy/plugins/actions/server/builtin_action_types/es_index.test.ts @@ -37,7 +37,7 @@ beforeEach(() => { describe('actionTypeRegistry.get() works', () => { test('action type static data is as expected', () => { expect(actionType.id).toEqual(ACTION_TYPE_ID); - expect(actionType.name).toEqual('index'); + expect(actionType.name).toEqual('Index'); }); }); @@ -142,7 +142,7 @@ describe('params validation', () => { ); expect(() => { - validateParams(actionType, { refresh: 'true' }); + validateParams(actionType, { refresh: 'foo' }); }).toThrowErrorMatchingInlineSnapshot( `"error validating action params: [refresh]: expected value of type [boolean] but got [string]"` ); diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/es_index.ts b/x-pack/legacy/plugins/actions/server/builtin_action_types/es_index.ts index 0e9fe0483ee1e..ddf33ba63f71a 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/es_index.ts +++ b/x-pack/legacy/plugins/actions/server/builtin_action_types/es_index.ts @@ -38,7 +38,9 @@ const ParamsSchema = schema.object({ export function getActionType({ logger }: { logger: Logger }): ActionType { return { id: '.index', - name: 'index', + name: i18n.translate('xpack.actions.builtin.esIndexTitle', { + defaultMessage: 'Index', + }), validate: { config: ConfigSchema, params: ParamsSchema, diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/index.test.ts b/x-pack/legacy/plugins/actions/server/builtin_action_types/index.test.ts index a39aaf3a3e2d1..5fcf39c2e8fdd 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/index.test.ts +++ b/x-pack/legacy/plugins/actions/server/builtin_action_types/index.test.ts @@ -6,7 +6,7 @@ import { ActionExecutor, TaskRunnerFactory } from '../lib'; import { ActionTypeRegistry } from '../action_type_registry'; -import { taskManagerMock } from '../../../task_manager/task_manager.mock'; +import { taskManagerMock } from '../../../../../plugins/task_manager/server/task_manager.mock'; import { registerBuiltInActionTypes } from './index'; import { Logger } from '../../../../../../src/core/server'; import { loggingServiceMock } from '../../../../../../src/core/server/mocks'; @@ -20,7 +20,7 @@ export function createActionTypeRegistry(): { } { const logger = loggingServiceMock.create().get() as jest.Mocked; const actionTypeRegistry = new ActionTypeRegistry({ - taskManager: taskManagerMock.create(), + taskManager: taskManagerMock.setup(), taskRunnerFactory: new TaskRunnerFactory(new ActionExecutor()), actionsConfigUtils: configUtilsMock, }); diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/pagerduty.test.ts b/x-pack/legacy/plugins/actions/server/builtin_action_types/pagerduty.test.ts index cb3548524ebbb..f60fdf7fef95e 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/pagerduty.test.ts +++ b/x-pack/legacy/plugins/actions/server/builtin_action_types/pagerduty.test.ts @@ -38,7 +38,7 @@ beforeAll(() => { describe('get()', () => { test('should return correct action type', () => { expect(actionType.id).toEqual(ACTION_TYPE_ID); - expect(actionType.name).toEqual('pagerduty'); + expect(actionType.name).toEqual('PagerDuty'); }); }); diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/pagerduty.ts b/x-pack/legacy/plugins/actions/server/builtin_action_types/pagerduty.ts index 250c169278c57..b26621702cf5b 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/pagerduty.ts +++ b/x-pack/legacy/plugins/actions/server/builtin_action_types/pagerduty.ts @@ -96,7 +96,9 @@ export function getActionType({ }): ActionType { return { id: '.pagerduty', - name: 'pagerduty', + name: i18n.translate('xpack.actions.builtin.pagerdutyTitle', { + defaultMessage: 'PagerDuty', + }), validate: { config: schema.object(configSchemaProps, { validate: curry(valdiateActionTypeConfig)(configurationUtilities), diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/server_log.test.ts b/x-pack/legacy/plugins/actions/server/builtin_action_types/server_log.test.ts index c59ddf97017fd..8f28b9e8f5125 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/server_log.test.ts +++ b/x-pack/legacy/plugins/actions/server/builtin_action_types/server_log.test.ts @@ -25,7 +25,7 @@ beforeAll(() => { describe('get()', () => { test('returns action type', () => { expect(actionType.id).toEqual(ACTION_TYPE_ID); - expect(actionType.name).toEqual('server-log'); + expect(actionType.name).toEqual('Server log'); }); }); @@ -98,6 +98,6 @@ describe('execute()', () => { config: {}, secrets: {}, }); - expect(mockedLogger.info).toHaveBeenCalledWith('server-log: message text here'); + expect(mockedLogger.info).toHaveBeenCalledWith('Server log: message text here'); }); }); diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/server_log.ts b/x-pack/legacy/plugins/actions/server/builtin_action_types/server_log.ts index 0edf409e4d46c..34b8602eeba36 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/server_log.ts +++ b/x-pack/legacy/plugins/actions/server/builtin_action_types/server_log.ts @@ -12,7 +12,7 @@ import { Logger } from '../../../../../../src/core/server'; import { ActionType, ActionTypeExecutorOptions, ActionTypeExecutorResult } from '../types'; import { withoutControlCharacters } from './lib/string_utils'; -const ACTION_NAME = 'server-log'; +const ACTION_NAME = 'Server log'; // params definition diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/slack.test.ts b/x-pack/legacy/plugins/actions/server/builtin_action_types/slack.test.ts index a2b0db8bdb70f..aebc9c4993599 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/slack.test.ts +++ b/x-pack/legacy/plugins/actions/server/builtin_action_types/slack.test.ts @@ -29,7 +29,7 @@ beforeAll(() => { describe('action registeration', () => { test('returns action type', () => { expect(actionType.id).toEqual(ACTION_TYPE_ID); - expect(actionType.name).toEqual('slack'); + expect(actionType.name).toEqual('Slack'); }); }); diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/slack.ts b/x-pack/legacy/plugins/actions/server/builtin_action_types/slack.ts index 92611d6f162ff..b8989e59a2257 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/slack.ts +++ b/x-pack/legacy/plugins/actions/server/builtin_action_types/slack.ts @@ -49,7 +49,9 @@ export function getActionType({ }): ActionType { return { id: '.slack', - name: 'slack', + name: i18n.translate('xpack.actions.builtin.slackTitle', { + defaultMessage: 'Slack', + }), validate: { secrets: schema.object(secretsSchemaProps, { validate: curry(valdiateActionTypeConfig)(configurationUtilities), diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/webhook.test.ts b/x-pack/legacy/plugins/actions/server/builtin_action_types/webhook.test.ts index 64dd3a485f8e2..b95fef97ac7b9 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/webhook.test.ts +++ b/x-pack/legacy/plugins/actions/server/builtin_action_types/webhook.test.ts @@ -25,7 +25,7 @@ beforeAll(() => { describe('actionType', () => { test('exposes the action as `webhook` on its Id and Name', () => { expect(actionType.id).toEqual('.webhook'); - expect(actionType.name).toEqual('webhook'); + expect(actionType.name).toEqual('Webhook'); }); }); diff --git a/x-pack/legacy/plugins/actions/server/builtin_action_types/webhook.ts b/x-pack/legacy/plugins/actions/server/builtin_action_types/webhook.ts index 06fe2fb0e591c..fa88d3c72c163 100644 --- a/x-pack/legacy/plugins/actions/server/builtin_action_types/webhook.ts +++ b/x-pack/legacy/plugins/actions/server/builtin_action_types/webhook.ts @@ -56,7 +56,9 @@ export function getActionType({ }): ActionType { return { id: '.webhook', - name: 'webhook', + name: i18n.translate('xpack.actions.builtin.webhookTitle', { + defaultMessage: 'Webhook', + }), validate: { config: schema.object(configSchemaProps, { validate: curry(valdiateActionTypeConfig)(configurationUtilities), diff --git a/x-pack/legacy/plugins/actions/server/create_execute_function.test.ts b/x-pack/legacy/plugins/actions/server/create_execute_function.test.ts index c5496cd92cc9f..7dbcfce5ee335 100644 --- a/x-pack/legacy/plugins/actions/server/create_execute_function.test.ts +++ b/x-pack/legacy/plugins/actions/server/create_execute_function.test.ts @@ -4,11 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { taskManagerMock } from '../../task_manager/task_manager.mock'; +import { taskManagerMock } from '../../../../plugins/task_manager/server/task_manager.mock'; import { createExecuteFunction } from './create_execute_function'; import { savedObjectsClientMock } from '../../../../../src/core/server/mocks'; -const mockTaskManager = taskManagerMock.create(); +const mockTaskManager = taskManagerMock.start(); const savedObjectsClient = savedObjectsClientMock.create(); const getBasePath = jest.fn(); diff --git a/x-pack/legacy/plugins/actions/server/create_execute_function.ts b/x-pack/legacy/plugins/actions/server/create_execute_function.ts index 8ff12b8c3fa4b..ddd8b1df2327b 100644 --- a/x-pack/legacy/plugins/actions/server/create_execute_function.ts +++ b/x-pack/legacy/plugins/actions/server/create_execute_function.ts @@ -5,7 +5,7 @@ */ import { SavedObjectsClientContract } from 'src/core/server'; -import { TaskManagerStartContract } from './shim'; +import { TaskManagerStartContract } from '../../../../plugins/task_manager/server'; import { GetBasePathFunction } from './types'; interface CreateExecuteFunctionOptions { diff --git a/x-pack/legacy/plugins/actions/server/init.ts b/x-pack/legacy/plugins/actions/server/init.ts index 5eab3418467bc..6f221b08c4bc5 100644 --- a/x-pack/legacy/plugins/actions/server/init.ts +++ b/x-pack/legacy/plugins/actions/server/init.ts @@ -4,11 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ +import { Legacy } from 'kibana'; import { Plugin } from './plugin'; -import { shim, Server } from './shim'; +import { shim } from './shim'; import { ActionsPlugin } from './types'; -export async function init(server: Server) { +export async function init(server: Legacy.Server) { const { initializerContext, coreSetup, coreStart, pluginsSetup, pluginsStart } = shim(server); const plugin = new Plugin(initializerContext); diff --git a/x-pack/legacy/plugins/actions/server/lib/task_runner_factory.test.ts b/x-pack/legacy/plugins/actions/server/lib/task_runner_factory.test.ts index eb183f1f1d06a..ad2b74da0d7d4 100644 --- a/x-pack/legacy/plugins/actions/server/lib/task_runner_factory.test.ts +++ b/x-pack/legacy/plugins/actions/server/lib/task_runner_factory.test.ts @@ -7,7 +7,7 @@ import sinon from 'sinon'; import { ExecutorError } from './executor_error'; import { ActionExecutor } from './action_executor'; -import { ConcreteTaskInstance, TaskStatus } from '../../../task_manager'; +import { ConcreteTaskInstance, TaskStatus } from '../../../../../plugins/task_manager/server'; import { TaskRunnerFactory } from './task_runner_factory'; import { actionTypeRegistryMock } from '../action_type_registry.mock'; import { actionExecutorMock } from './action_executor.mock'; diff --git a/x-pack/legacy/plugins/actions/server/lib/task_runner_factory.ts b/x-pack/legacy/plugins/actions/server/lib/task_runner_factory.ts index c0cca22b2c3eb..2dc3d1161399e 100644 --- a/x-pack/legacy/plugins/actions/server/lib/task_runner_factory.ts +++ b/x-pack/legacy/plugins/actions/server/lib/task_runner_factory.ts @@ -6,7 +6,7 @@ import { ActionExecutorContract } from './action_executor'; import { ExecutorError } from './executor_error'; -import { RunContext } from '../../../task_manager'; +import { RunContext } from '../../../../../plugins/task_manager/server'; import { PluginStartContract as EncryptedSavedObjectsStartContract } from '../../../../../plugins/encrypted_saved_objects/server'; import { ActionTaskParams, GetBasePathFunction, SpaceIdToNamespaceFunction } from '../types'; diff --git a/x-pack/legacy/plugins/actions/server/plugin.ts b/x-pack/legacy/plugins/actions/server/plugin.ts index e8bc5d60a697b..ffc4a9cf90e54 100644 --- a/x-pack/legacy/plugins/actions/server/plugin.ts +++ b/x-pack/legacy/plugins/actions/server/plugin.ts @@ -69,7 +69,7 @@ export class Plugin { plugins: ActionsPluginsSetup ): Promise { const config = await this.config$.pipe(first()).toPromise(); - this.adminClient = await core.elasticsearch.adminClient$.pipe(first()).toPromise(); + this.adminClient = core.elasticsearch.adminClient; this.defaultKibanaIndex = (await this.kibana$.pipe(first()).toPromise()).index; this.licenseState = new LicenseState(plugins.licensing.license$); @@ -93,7 +93,7 @@ export class Plugin { const actionsConfigUtils = getActionsConfigurationUtilities(config as ActionsConfigType); const actionTypeRegistry = new ActionTypeRegistry({ taskRunnerFactory, - taskManager: plugins.task_manager, + taskManager: plugins.taskManager, actionsConfigUtils, }); this.taskRunnerFactory = taskRunnerFactory; @@ -164,7 +164,7 @@ export class Plugin { }); const executeFn = createExecuteFunction({ - taskManager: plugins.task_manager, + taskManager: plugins.taskManager, getScopedSavedObjectsClient: core.savedObjects.getScopedSavedObjectsClient, getBasePath, }); diff --git a/x-pack/legacy/plugins/actions/server/shim.ts b/x-pack/legacy/plugins/actions/server/shim.ts index 3887e62c4c40a..8077dc67c92c4 100644 --- a/x-pack/legacy/plugins/actions/server/shim.ts +++ b/x-pack/legacy/plugins/actions/server/shim.ts @@ -8,7 +8,11 @@ import Hapi from 'hapi'; import { Legacy } from 'kibana'; import * as Rx from 'rxjs'; import { ActionsConfigType } from './types'; -import { TaskManager } from '../../task_manager'; +import { + TaskManagerStartContract, + TaskManagerSetupContract, +} from '../../../../plugins/task_manager/server'; +import { getTaskManagerSetup, getTaskManagerStart } from '../../task_manager/server'; import { XPackMainPlugin } from '../../xpack_main/server/xpack_main'; import KbnServer from '../../../../../src/legacy/server/kbn_server'; import { LegacySpacesPlugin as SpacesPluginStartContract } from '../../spaces'; @@ -24,16 +28,6 @@ import { } from '../../../../../src/core/server'; import { LicensingPluginSetup } from '../../../../plugins/licensing/server'; -// Extend PluginProperties to indicate which plugins are guaranteed to exist -// due to being marked as dependencies -interface Plugins extends Hapi.PluginProperties { - task_manager: TaskManager; -} - -export interface Server extends Legacy.Server { - plugins: Plugins; -} - export interface KibanaConfig { index: string; } @@ -41,14 +35,9 @@ export interface KibanaConfig { /** * Shim what we're thinking setup and start contracts will look like */ -export type TaskManagerStartContract = Pick; export type XPackMainPluginSetupContract = Pick; export type SecurityPluginSetupContract = Pick; export type SecurityPluginStartContract = Pick; -export type TaskManagerSetupContract = Pick< - TaskManager, - 'addMiddleware' | 'registerTaskDefinitions' ->; /** * New platform interfaces @@ -74,7 +63,7 @@ export interface ActionsCoreStart { } export interface ActionsPluginsSetup { security?: SecurityPluginSetupContract; - task_manager: TaskManagerSetupContract; + taskManager: TaskManagerSetupContract; xpack_main: XPackMainPluginSetupContract; encryptedSavedObjects: EncryptedSavedObjectsSetupContract; licensing: LicensingPluginSetup; @@ -83,7 +72,7 @@ export interface ActionsPluginsStart { security?: SecurityPluginStartContract; spaces: () => SpacesPluginStartContract | undefined; encryptedSavedObjects: EncryptedSavedObjectsStartContract; - task_manager: TaskManagerStartContract; + taskManager: TaskManagerStartContract; } /** @@ -92,7 +81,7 @@ export interface ActionsPluginsStart { * @param server Hapi server instance */ export function shim( - server: Server + server: Legacy.Server ): { initializerContext: ActionsPluginInitializerContext; coreSetup: ActionsCoreSetup; @@ -132,7 +121,7 @@ export function shim( const pluginsSetup: ActionsPluginsSetup = { security: newPlatform.setup.plugins.security as SecurityPluginSetupContract | undefined, - task_manager: server.plugins.task_manager, + taskManager: getTaskManagerSetup(server)!, xpack_main: server.plugins.xpack_main, encryptedSavedObjects: newPlatform.setup.plugins .encryptedSavedObjects as EncryptedSavedObjectsSetupContract, @@ -146,7 +135,7 @@ export function shim( spaces: () => server.plugins.spaces, encryptedSavedObjects: newPlatform.start.plugins .encryptedSavedObjects as EncryptedSavedObjectsStartContract, - task_manager: server.plugins.task_manager, + taskManager: getTaskManagerStart(server)!, }; return { diff --git a/x-pack/legacy/plugins/alerting/README.md b/x-pack/legacy/plugins/alerting/README.md index 33679cf6fa422..30d34bd3b436d 100644 --- a/x-pack/legacy/plugins/alerting/README.md +++ b/x-pack/legacy/plugins/alerting/README.md @@ -63,6 +63,13 @@ This is the primary function for an alert type. Whenever the alert needs to exec |previousStartedAt|The previous date and time the alert type started a successful execution.| |params|Parameters for the execution. This is where the parameters you require will be passed in. (example threshold). Use alert type validation to ensure values are set before execution.| |state|State returned from previous execution. This is the alert level state. What the executor returns will be serialized and provided here at the next execution.| +|alertId|The id of this alert.| +|spaceId|The id of the space of this alert.| +|namespace|The namespace of the space of this alert; same as spaceId, unless spaceId === 'default', then namespace = undefined.| +|name|The name of this alert.| +|tags|The tags associated with this alert.| +|createdBy|The userid that created this alert.| +|updatedBy|The userid that last updated this alert.| ### Example diff --git a/x-pack/legacy/plugins/alerting/mappings.json b/x-pack/legacy/plugins/alerting/mappings.json index bc4a7118666ed..31733f44e7ce6 100644 --- a/x-pack/legacy/plugins/alerting/mappings.json +++ b/x-pack/legacy/plugins/alerting/mappings.json @@ -54,6 +54,9 @@ "updatedBy": { "type": "keyword" }, + "createdAt": { + "type": "date" + }, "apiKey": { "type": "binary" }, diff --git a/x-pack/legacy/plugins/alerting/server/lib/alert_instance.test.ts b/x-pack/legacy/plugins/alerting/server/alert_instance/alert_instance.test.ts similarity index 100% rename from x-pack/legacy/plugins/alerting/server/lib/alert_instance.test.ts rename to x-pack/legacy/plugins/alerting/server/alert_instance/alert_instance.test.ts diff --git a/x-pack/legacy/plugins/alerting/server/lib/alert_instance.ts b/x-pack/legacy/plugins/alerting/server/alert_instance/alert_instance.ts similarity index 97% rename from x-pack/legacy/plugins/alerting/server/lib/alert_instance.ts rename to x-pack/legacy/plugins/alerting/server/alert_instance/alert_instance.ts index 1e2cc26f364ad..a56e2077cdfd8 100644 --- a/x-pack/legacy/plugins/alerting/server/lib/alert_instance.ts +++ b/x-pack/legacy/plugins/alerting/server/alert_instance/alert_instance.ts @@ -5,7 +5,7 @@ */ import { State, Context } from '../types'; -import { parseDuration } from './parse_duration'; +import { parseDuration } from '../lib'; interface Meta { lastScheduledActions?: { diff --git a/x-pack/legacy/plugins/alerting/server/lib/create_alert_instance_factory.test.ts b/x-pack/legacy/plugins/alerting/server/alert_instance/create_alert_instance_factory.test.ts similarity index 100% rename from x-pack/legacy/plugins/alerting/server/lib/create_alert_instance_factory.test.ts rename to x-pack/legacy/plugins/alerting/server/alert_instance/create_alert_instance_factory.test.ts diff --git a/x-pack/legacy/plugins/alerting/server/lib/create_alert_instance_factory.ts b/x-pack/legacy/plugins/alerting/server/alert_instance/create_alert_instance_factory.ts similarity index 100% rename from x-pack/legacy/plugins/alerting/server/lib/create_alert_instance_factory.ts rename to x-pack/legacy/plugins/alerting/server/alert_instance/create_alert_instance_factory.ts diff --git a/x-pack/legacy/plugins/alerting/server/alert_instance/index.ts b/x-pack/legacy/plugins/alerting/server/alert_instance/index.ts new file mode 100644 index 0000000000000..40ee0874e805c --- /dev/null +++ b/x-pack/legacy/plugins/alerting/server/alert_instance/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { AlertInstance } from './alert_instance'; +export { createAlertInstanceFactory } from './create_alert_instance_factory'; diff --git a/x-pack/legacy/plugins/alerting/server/alert_type_registry.test.ts b/x-pack/legacy/plugins/alerting/server/alert_type_registry.test.ts index e03df364e78ce..e1a05d6460e25 100644 --- a/x-pack/legacy/plugins/alerting/server/alert_type_registry.test.ts +++ b/x-pack/legacy/plugins/alerting/server/alert_type_registry.test.ts @@ -4,12 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { TaskRunnerFactory } from './lib'; +import { TaskRunnerFactory } from './task_runner'; import { AlertTypeRegistry } from './alert_type_registry'; -import { taskManagerMock } from '../../task_manager/task_manager.mock'; - -const taskManager = taskManagerMock.create(); +import { taskManagerMock } from '../../../../plugins/task_manager/server/task_manager.mock'; +const taskManager = taskManagerMock.setup(); const alertTypeRegistryParams = { taskManager, taskRunnerFactory: new TaskRunnerFactory(), diff --git a/x-pack/legacy/plugins/alerting/server/alert_type_registry.ts b/x-pack/legacy/plugins/alerting/server/alert_type_registry.ts index ec875aa2181eb..1e9007202c452 100644 --- a/x-pack/legacy/plugins/alerting/server/alert_type_registry.ts +++ b/x-pack/legacy/plugins/alerting/server/alert_type_registry.ts @@ -6,9 +6,8 @@ import Boom from 'boom'; import { i18n } from '@kbn/i18n'; -import { TaskRunnerFactory } from './lib'; -import { RunContext } from '../../task_manager'; -import { TaskManagerSetupContract } from './shim'; +import { RunContext, TaskManagerSetupContract } from '../../../../plugins/task_manager/server'; +import { TaskRunnerFactory } from './task_runner'; import { AlertType } from './types'; interface ConstructorOptions { diff --git a/x-pack/legacy/plugins/alerting/server/alerts_client.test.ts b/x-pack/legacy/plugins/alerting/server/alerts_client.test.ts index d11541e9378bb..2af66059d9fed 100644 --- a/x-pack/legacy/plugins/alerting/server/alerts_client.test.ts +++ b/x-pack/legacy/plugins/alerting/server/alerts_client.test.ts @@ -7,29 +7,38 @@ import uuid from 'uuid'; import { schema } from '@kbn/config-schema'; import { AlertsClient } from './alerts_client'; import { savedObjectsClientMock, loggingServiceMock } from '../../../../../src/core/server/mocks'; -import { taskManagerMock } from '../../task_manager/task_manager.mock'; +import { taskManagerMock } from '../../../../plugins/task_manager/server/task_manager.mock'; import { alertTypeRegistryMock } from './alert_type_registry.mock'; -import { TaskStatus } from '../../task_manager'; +import { TaskStatus } from '../../../../plugins/task_manager/server'; import { IntervalSchedule } from './types'; import { resolvable } from './test_utils'; +import { encryptedSavedObjectsMock } from '../../../../plugins/encrypted_saved_objects/server/mocks'; -const taskManager = taskManagerMock.create(); +const taskManager = taskManagerMock.start(); const alertTypeRegistry = alertTypeRegistryMock.create(); const savedObjectsClient = savedObjectsClientMock.create(); +const encryptedSavedObjects = encryptedSavedObjectsMock.createStart(); const alertsClientParams = { taskManager, alertTypeRegistry, savedObjectsClient, spaceId: 'default', + namespace: 'default', getUserName: jest.fn(), createAPIKey: jest.fn(), + invalidateAPIKey: jest.fn(), logger: loggingServiceMock.create().get(), + encryptedSavedObjectsPlugin: encryptedSavedObjects, }; beforeEach(() => { jest.resetAllMocks(); - alertsClientParams.createAPIKey.mockResolvedValue({ created: false }); + alertsClientParams.createAPIKey.mockResolvedValue({ apiKeysEnabled: false }); + alertsClientParams.invalidateAPIKey.mockResolvedValue({ + apiKeysEnabled: true, + result: { error_count: 0 }, + }); alertsClientParams.getUserName.mockResolvedValue('elastic'); taskManager.runNow.mockResolvedValue({ id: '' }); }); @@ -100,6 +109,7 @@ describe('create()', () => { params: { bar: true, }, + createdAt: '2019-02-12T21:01:22.479Z', actions: [ { group: 'default', @@ -160,6 +170,7 @@ describe('create()', () => { }, ], "alertTypeId": "123", + "createdAt": 2019-02-12T21:01:22.479Z, "id": "1", "params": Object { "bar": true, @@ -168,6 +179,7 @@ describe('create()', () => { "interval": "10s", }, "scheduledTaskId": "task-123", + "updatedAt": 2019-02-12T21:01:22.479Z, } `); expect(savedObjectsClient.create).toHaveBeenCalledTimes(1); @@ -189,6 +201,7 @@ describe('create()', () => { "apiKey": null, "apiKeyOwner": null, "consumer": "bar", + "createdAt": "2019-02-12T21:01:22.479Z", "createdBy": "elastic", "enabled": true, "muteAll": false, @@ -311,6 +324,7 @@ describe('create()', () => { params: { bar: true, }, + createdAt: new Date().toISOString(), actions: [ { group: 'default', @@ -407,6 +421,7 @@ describe('create()', () => { }, ], "alertTypeId": "123", + "createdAt": 2019-02-12T21:01:22.479Z, "id": "1", "params": Object { "bar": true, @@ -415,6 +430,7 @@ describe('create()', () => { "interval": "10s", }, "scheduledTaskId": "task-123", + "updatedAt": 2019-02-12T21:01:22.479Z, } `); expect(savedObjectsClient.bulkGet).toHaveBeenCalledWith([ @@ -460,6 +476,7 @@ describe('create()', () => { params: { bar: true, }, + createdAt: new Date().toISOString(), actions: [ { group: 'default', @@ -493,6 +510,7 @@ describe('create()', () => { }, ], "alertTypeId": "123", + "createdAt": 2019-02-12T21:01:22.479Z, "enabled": false, "id": "1", "params": Object { @@ -501,6 +519,7 @@ describe('create()', () => { "schedule": Object { "interval": 10000, }, + "updatedAt": 2019-02-12T21:01:22.479Z, } `); expect(savedObjectsClient.create).toHaveBeenCalledTimes(1); @@ -715,7 +734,7 @@ describe('create()', () => { async executor() {}, }); alertsClientParams.createAPIKey.mockResolvedValueOnce({ - created: true, + apiKeysEnabled: true, result: { id: '123', api_key: 'abc' }, }); savedObjectsClient.bulkGet.mockResolvedValueOnce({ @@ -806,6 +825,7 @@ describe('create()', () => { apiKey: Buffer.from('123:abc').toString('base64'), apiKeyOwner: 'elastic', createdBy: 'elastic', + createdAt: '2019-02-12T21:01:22.479Z', updatedBy: 'elastic', enabled: true, schedule: { interval: '10s' }, @@ -830,7 +850,7 @@ describe('create()', () => { describe('enable()', () => { test('enables an alert', async () => { const alertsClient = new AlertsClient(alertsClientParams); - savedObjectsClient.get.mockResolvedValueOnce({ + encryptedSavedObjects.getDecryptedAsInternalUser.mockResolvedValueOnce({ id: '1', type: 'alert', attributes: { @@ -889,7 +909,7 @@ describe('enable()', () => { test(`doesn't enable already enabled alerts`, async () => { const alertsClient = new AlertsClient(alertsClientParams); - savedObjectsClient.get.mockResolvedValueOnce({ + encryptedSavedObjects.getDecryptedAsInternalUser.mockResolvedValueOnce({ id: '1', type: 'alert', attributes: { @@ -907,7 +927,7 @@ describe('enable()', () => { test('calls the API key function', async () => { const alertsClient = new AlertsClient(alertsClientParams); - savedObjectsClient.get.mockResolvedValueOnce({ + encryptedSavedObjects.getDecryptedAsInternalUser.mockResolvedValueOnce({ id: '1', type: 'alert', attributes: { @@ -932,7 +952,7 @@ describe('enable()', () => { ownerId: null, }); alertsClientParams.createAPIKey.mockResolvedValueOnce({ - created: true, + apiKeysEnabled: true, result: { id: '123', api_key: 'abc' }, }); @@ -967,6 +987,41 @@ describe('enable()', () => { scope: ['alerting'], }); }); + + test('swallows error when invalidate API key throws', async () => { + const alertsClient = new AlertsClient(alertsClientParams); + alertsClientParams.invalidateAPIKey.mockRejectedValueOnce(new Error('Fail')); + encryptedSavedObjects.getDecryptedAsInternalUser.mockResolvedValueOnce({ + id: '1', + type: 'alert', + attributes: { + schedule: { interval: '10s' }, + alertTypeId: '2', + enabled: false, + apiKey: Buffer.from('123:abc').toString('base64'), + }, + version: '123', + references: [], + }); + taskManager.schedule.mockResolvedValueOnce({ + id: 'task-123', + scheduledAt: new Date(), + attempts: 0, + status: TaskStatus.Idle, + runAt: new Date(), + state: {}, + params: {}, + taskType: '', + startedAt: null, + retryAt: null, + ownerId: null, + }); + + await alertsClient.enable({ id: '1' }); + expect(alertsClientParams.logger.error).toHaveBeenCalledWith( + 'Failed to invalidate API Key: Fail' + ); + }); }); describe('disable()', () => { @@ -1248,6 +1303,7 @@ describe('get()', () => { }, ], "alertTypeId": "123", + "createdAt": 2019-02-12T21:01:22.479Z, "id": "1", "params": Object { "bar": true, @@ -1255,6 +1311,7 @@ describe('get()', () => { "schedule": Object { "interval": "10s", }, + "updatedAt": 2019-02-12T21:01:22.479Z, } `); expect(savedObjectsClient.get).toHaveBeenCalledTimes(1); @@ -1347,6 +1404,7 @@ describe('find()', () => { }, ], "alertTypeId": "123", + "createdAt": 2019-02-12T21:01:22.479Z, "id": "1", "params": Object { "bar": true, @@ -1354,6 +1412,7 @@ describe('find()', () => { "schedule": Object { "interval": "10s", }, + "updatedAt": 2019-02-12T21:01:22.479Z, }, ], "page": 1, @@ -1375,7 +1434,7 @@ describe('find()', () => { describe('delete()', () => { test('successfully removes an alert', async () => { const alertsClient = new AlertsClient(alertsClientParams); - savedObjectsClient.get.mockResolvedValueOnce({ + encryptedSavedObjects.getDecryptedAsInternalUser.mockResolvedValueOnce({ id: '1', type: 'alert', attributes: { @@ -1422,6 +1481,48 @@ describe('delete()', () => { ] `); }); + + test('swallows error when invalidate API key throws', async () => { + const alertsClient = new AlertsClient(alertsClientParams); + alertsClientParams.invalidateAPIKey.mockRejectedValueOnce(new Error('Fail')); + encryptedSavedObjects.getDecryptedAsInternalUser.mockResolvedValueOnce({ + id: '1', + type: 'alert', + attributes: { + alertTypeId: '123', + schedule: { interval: '10s' }, + params: { + bar: true, + }, + apiKey: Buffer.from('123:abc').toString('base64'), + scheduledTaskId: 'task-123', + actions: [ + { + group: 'default', + actionRef: 'action_0', + params: { + foo: true, + }, + }, + ], + }, + references: [ + { + name: 'action_0', + type: 'action', + id: '1', + }, + ], + }); + savedObjectsClient.delete.mockResolvedValueOnce({ + success: true, + }); + + await alertsClient.delete({ id: '1' }); + expect(alertsClientParams.logger.error).toHaveBeenCalledWith( + 'Failed to invalidate API Key: Fail' + ); + }); }); describe('update()', () => { @@ -1433,7 +1534,7 @@ describe('update()', () => { actionGroups: ['default'], async executor() {}, }); - savedObjectsClient.get.mockResolvedValueOnce({ + encryptedSavedObjects.getDecryptedAsInternalUser.mockResolvedValueOnce({ id: '1', type: 'alert', attributes: { @@ -1476,7 +1577,9 @@ describe('update()', () => { }, ], scheduledTaskId: 'task-123', + createdAt: new Date().toISOString(), }, + updated_at: new Date().toISOString(), references: [ { name: 'action_0', @@ -1517,6 +1620,7 @@ describe('update()', () => { }, }, ], + "createdAt": 2019-02-12T21:01:22.479Z, "enabled": true, "id": "1", "params": Object { @@ -1526,6 +1630,7 @@ describe('update()', () => { "interval": "10s", }, "scheduledTaskId": "task-123", + "updatedAt": 2019-02-12T21:01:22.479Z, } `); expect(savedObjectsClient.update).toHaveBeenCalledTimes(1); @@ -1584,7 +1689,7 @@ describe('update()', () => { actionGroups: ['default'], async executor() {}, }); - savedObjectsClient.get.mockResolvedValueOnce({ + encryptedSavedObjects.getDecryptedAsInternalUser.mockResolvedValueOnce({ id: '1', type: 'alert', attributes: { @@ -1624,6 +1729,7 @@ describe('update()', () => { params: { bar: true, }, + createdAt: new Date().toISOString(), actions: [ { group: 'default', @@ -1652,6 +1758,7 @@ describe('update()', () => { ], scheduledTaskId: 'task-123', }, + updated_at: new Date().toISOString(), references: [ { name: 'action_0', @@ -1732,6 +1839,7 @@ describe('update()', () => { }, }, ], + "createdAt": 2019-02-12T21:01:22.479Z, "enabled": true, "id": "1", "params": Object { @@ -1741,6 +1849,7 @@ describe('update()', () => { "interval": "10s", }, "scheduledTaskId": "task-123", + "updatedAt": 2019-02-12T21:01:22.479Z, } `); expect(savedObjectsClient.bulkGet).toHaveBeenCalledWith([ @@ -1763,7 +1872,7 @@ describe('update()', () => { actionGroups: ['default'], async executor() {}, }); - savedObjectsClient.get.mockResolvedValueOnce({ + encryptedSavedObjects.getDecryptedAsInternalUser.mockResolvedValueOnce({ id: '1', type: 'alert', attributes: { @@ -1787,7 +1896,7 @@ describe('update()', () => { ], }); alertsClientParams.createAPIKey.mockResolvedValueOnce({ - created: true, + apiKeysEnabled: true, result: { id: '123', api_key: 'abc' }, }); savedObjectsClient.update.mockResolvedValueOnce({ @@ -1799,6 +1908,7 @@ describe('update()', () => { params: { bar: true, }, + createdAt: new Date().toISOString(), actions: [ { group: 'default', @@ -1812,6 +1922,7 @@ describe('update()', () => { apiKey: Buffer.from('123:abc').toString('base64'), scheduledTaskId: 'task-123', }, + updated_at: new Date().toISOString(), references: [ { name: 'action_0', @@ -1853,6 +1964,7 @@ describe('update()', () => { }, ], "apiKey": "MTIzOmFiYw==", + "createdAt": 2019-02-12T21:01:22.479Z, "enabled": true, "id": "1", "params": Object { @@ -1862,6 +1974,7 @@ describe('update()', () => { "interval": "10s", }, "scheduledTaskId": "task-123", + "updatedAt": 2019-02-12T21:01:22.479Z, } `); expect(savedObjectsClient.update).toHaveBeenCalledTimes(1); @@ -1925,7 +2038,7 @@ describe('update()', () => { }, async executor() {}, }); - savedObjectsClient.get.mockResolvedValueOnce({ + encryptedSavedObjects.getDecryptedAsInternalUser.mockResolvedValueOnce({ id: '1', type: 'alert', attributes: { @@ -1959,6 +2072,93 @@ describe('update()', () => { ); }); + it('swallows error when invalidate API key throws', async () => { + const alertsClient = new AlertsClient(alertsClientParams); + alertsClientParams.invalidateAPIKey.mockRejectedValueOnce(new Error('Fail')); + alertTypeRegistry.get.mockReturnValueOnce({ + id: '123', + name: 'Test', + actionGroups: ['default'], + async executor() {}, + }); + encryptedSavedObjects.getDecryptedAsInternalUser.mockResolvedValueOnce({ + id: '1', + type: 'alert', + attributes: { + enabled: true, + alertTypeId: '123', + scheduledTaskId: 'task-123', + apiKey: Buffer.from('123:abc').toString('base64'), + }, + references: [], + version: '123', + }); + savedObjectsClient.bulkGet.mockResolvedValueOnce({ + saved_objects: [ + { + id: '1', + type: 'action', + attributes: { + actionTypeId: 'test', + }, + references: [], + }, + ], + }); + savedObjectsClient.update.mockResolvedValueOnce({ + id: '1', + type: 'alert', + attributes: { + enabled: true, + schedule: { interval: '10s' }, + params: { + bar: true, + }, + actions: [ + { + group: 'default', + actionRef: 'action_0', + actionTypeId: 'test', + params: { + foo: true, + }, + }, + ], + scheduledTaskId: 'task-123', + }, + references: [ + { + name: 'action_0', + type: 'action', + id: '1', + }, + ], + }); + await alertsClient.update({ + id: '1', + data: { + schedule: { interval: '10s' }, + name: 'abc', + tags: ['foo'], + params: { + bar: true, + }, + actions: [ + { + group: 'default', + id: '1', + params: { + foo: true, + }, + }, + ], + }, + }); + expect(alertsClientParams.logger.error).toHaveBeenCalledWith( + 'Failed to invalidate API Key: Fail' + ); + }); + describe('updating an alert schedule', () => { function mockApiCalls( alertId: string, @@ -1985,7 +2185,7 @@ describe('update()', () => { }, ], }); - savedObjectsClient.get.mockResolvedValueOnce({ + encryptedSavedObjects.getDecryptedAsInternalUser.mockResolvedValueOnce({ id: alertId, type: 'alert', attributes: { @@ -2185,7 +2385,7 @@ describe('update()', () => { describe('updateApiKey()', () => { test('updates the API key for the alert', async () => { const alertsClient = new AlertsClient(alertsClientParams); - savedObjectsClient.get.mockResolvedValueOnce({ + encryptedSavedObjects.getDecryptedAsInternalUser.mockResolvedValueOnce({ id: '1', type: 'alert', attributes: { @@ -2197,7 +2397,7 @@ describe('updateApiKey()', () => { references: [], }); alertsClientParams.createAPIKey.mockResolvedValueOnce({ - created: true, + apiKeysEnabled: true, result: { id: '123', api_key: 'abc' }, }); @@ -2216,4 +2416,30 @@ describe('updateApiKey()', () => { { version: '123' } ); }); + + test('swallows error when invalidate API key throws', async () => { + const alertsClient = new AlertsClient(alertsClientParams); + alertsClientParams.invalidateAPIKey.mockRejectedValue(new Error('Fail')); + encryptedSavedObjects.getDecryptedAsInternalUser.mockResolvedValueOnce({ + id: '1', + type: 'alert', + attributes: { + schedule: { interval: '10s' }, + alertTypeId: '2', + enabled: true, + apiKey: Buffer.from('123:abc').toString('base64'), + }, + version: '123', + references: [], + }); + alertsClientParams.createAPIKey.mockResolvedValueOnce({ + apiKeysEnabled: true, + result: { id: '123', api_key: 'abc' }, + }); + + await alertsClient.updateApiKey({ id: '1' }); + expect(alertsClientParams.logger.error).toHaveBeenCalledWith( + 'Failed to invalidate API Key: Fail' + ); + }); }); diff --git a/x-pack/legacy/plugins/alerting/server/alerts_client.ts b/x-pack/legacy/plugins/alerting/server/alerts_client.ts index 70d2ff8ca3033..fe96a233b8663 100644 --- a/x-pack/legacy/plugins/alerting/server/alerts_client.ts +++ b/x-pack/legacy/plugins/alerting/server/alerts_client.ts @@ -15,34 +15,41 @@ import { } from 'src/core/server'; import { Alert, + PartialAlert, RawAlert, AlertTypeRegistry, AlertAction, AlertType, IntervalSchedule, } from './types'; -import { TaskManagerStartContract } from './shim'; import { validateAlertTypeParams } from './lib'; -import { CreateAPIKeyResult as SecurityPluginCreateAPIKeyResult } from '../../../../plugins/security/server'; +import { + InvalidateAPIKeyParams, + CreateAPIKeyResult as SecurityPluginCreateAPIKeyResult, + InvalidateAPIKeyResult as SecurityPluginInvalidateAPIKeyResult, +} from '../../../../plugins/security/server'; +import { PluginStartContract as EncryptedSavedObjectsStartContract } from '../../../../plugins/encrypted_saved_objects/server'; +import { TaskManagerStartContract } from '../../../../plugins/task_manager/server'; -interface FailedCreateAPIKeyResult { - created: false; -} -interface SuccessCreateAPIKeyResult { - created: true; - result: SecurityPluginCreateAPIKeyResult; -} -export type CreateAPIKeyResult = FailedCreateAPIKeyResult | SuccessCreateAPIKeyResult; type NormalizedAlertAction = Omit; +export type CreateAPIKeyResult = + | { apiKeysEnabled: false } + | { apiKeysEnabled: true; result: SecurityPluginCreateAPIKeyResult }; +export type InvalidateAPIKeyResult = + | { apiKeysEnabled: false } + | { apiKeysEnabled: true; result: SecurityPluginInvalidateAPIKeyResult }; interface ConstructorOptions { logger: Logger; taskManager: TaskManagerStartContract; savedObjectsClient: SavedObjectsClientContract; alertTypeRegistry: AlertTypeRegistry; + encryptedSavedObjectsPlugin: EncryptedSavedObjectsStartContract; spaceId?: string; + namespace?: string; getUserName: () => Promise; createAPIKey: () => Promise; + invalidateAPIKey: (params: InvalidateAPIKeyParams) => Promise; } export interface FindOptions { @@ -63,26 +70,26 @@ export interface FindOptions { }; } -interface FindResult { +export interface FindResult { page: number; perPage: number; total: number; - data: object[]; + data: Alert[]; } interface CreateOptions { - data: Pick< + data: Omit< Alert, - Exclude< - keyof Alert, - | 'createdBy' - | 'updatedBy' - | 'apiKey' - | 'apiKeyOwner' - | 'muteAll' - | 'mutedInstanceIds' - | 'actions' - > + | 'id' + | 'createdBy' + | 'updatedBy' + | 'createdAt' + | 'updatedAt' + | 'apiKey' + | 'apiKeyOwner' + | 'muteAll' + | 'mutedInstanceIds' + | 'actions' > & { actions: NormalizedAlertAction[] }; options?: { migrationVersion?: Record; @@ -104,10 +111,15 @@ export class AlertsClient { private readonly logger: Logger; private readonly getUserName: () => Promise; private readonly spaceId?: string; + private readonly namespace?: string; private readonly taskManager: TaskManagerStartContract; private readonly savedObjectsClient: SavedObjectsClientContract; private readonly alertTypeRegistry: AlertTypeRegistry; private readonly createAPIKey: () => Promise; + private readonly invalidateAPIKey: ( + params: InvalidateAPIKeyParams + ) => Promise; + encryptedSavedObjectsPlugin: EncryptedSavedObjectsStartContract; constructor({ alertTypeRegistry, @@ -115,19 +127,25 @@ export class AlertsClient { taskManager, logger, spaceId, + namespace, getUserName, createAPIKey, + invalidateAPIKey, + encryptedSavedObjectsPlugin, }: ConstructorOptions) { this.logger = logger; this.getUserName = getUserName; this.spaceId = spaceId; + this.namespace = namespace; this.taskManager = taskManager; this.alertTypeRegistry = alertTypeRegistry; this.savedObjectsClient = savedObjectsClient; this.createAPIKey = createAPIKey; + this.invalidateAPIKey = invalidateAPIKey; + this.encryptedSavedObjectsPlugin = encryptedSavedObjectsPlugin; } - public async create({ data, options }: CreateOptions) { + public async create({ data, options }: CreateOptions): Promise { // Throws an error if alert type isn't registered const alertType = this.alertTypeRegistry.get(data.alertTypeId); const validatedAlertTypeParams = validateAlertTypeParams(alertType, data.params); @@ -142,6 +160,7 @@ export class AlertsClient { actions, createdBy: username, updatedBy: username, + createdAt: new Date().toISOString(), params: validatedAlertTypeParams, muteAll: false, mutedInstanceIds: [], @@ -171,50 +190,63 @@ export class AlertsClient { }); createdAlert.attributes.scheduledTaskId = scheduledTask.id; } - return this.getAlertFromRaw(createdAlert.id, createdAlert.attributes, references); + return this.getAlertFromRaw( + createdAlert.id, + createdAlert.attributes, + createdAlert.updated_at, + references + ); } - public async get({ id }: { id: string }) { + public async get({ id }: { id: string }): Promise { const result = await this.savedObjectsClient.get('alert', id); - return this.getAlertFromRaw(result.id, result.attributes, result.references); + return this.getAlertFromRaw(result.id, result.attributes, result.updated_at, result.references); } public async find({ options = {} }: FindOptions = {}): Promise { - const results = await this.savedObjectsClient.find({ + const { + page, + per_page: perPage, + total, + saved_objects: data, + } = await this.savedObjectsClient.find({ ...options, type: 'alert', }); - const data = results.saved_objects.map(result => - this.getAlertFromRaw(result.id, result.attributes, result.references) - ); - return { - page: results.page, - perPage: results.per_page, - total: results.total, - data, + page, + perPage, + total, + data: data.map(({ id, attributes, updated_at, references }) => + this.getAlertFromRaw(id, attributes, updated_at, references) + ), }; } public async delete({ id }: { id: string }) { - const alertSavedObject = await this.savedObjectsClient.get('alert', id); + const decryptedAlertSavedObject = await this.encryptedSavedObjectsPlugin.getDecryptedAsInternalUser< + RawAlert + >('alert', id, { namespace: this.namespace }); const removeResult = await this.savedObjectsClient.delete('alert', id); - if (alertSavedObject.attributes.scheduledTaskId) { - await this.taskManager.remove(alertSavedObject.attributes.scheduledTaskId); + if (decryptedAlertSavedObject.attributes.scheduledTaskId) { + await this.taskManager.remove(decryptedAlertSavedObject.attributes.scheduledTaskId); } + await this.invalidateApiKey({ apiKey: decryptedAlertSavedObject.attributes.apiKey }); return removeResult; } - public async update({ id, data }: UpdateOptions) { - const alert = await this.savedObjectsClient.get('alert', id); - const updateResult = await this.updateAlert({ id, data }, alert); + public async update({ id, data }: UpdateOptions): Promise { + const decryptedAlertSavedObject = await this.encryptedSavedObjectsPlugin.getDecryptedAsInternalUser< + RawAlert + >('alert', id, { namespace: this.namespace }); + const updateResult = await this.updateAlert({ id, data }, decryptedAlertSavedObject); if ( updateResult.scheduledTaskId && - !isEqual(alert.attributes.schedule, updateResult.schedule) + !isEqual(decryptedAlertSavedObject.attributes.schedule, updateResult.schedule) ) { - this.taskManager.runNow(updateResult.scheduledTaskId).catch(err => { + this.taskManager.runNow(updateResult.scheduledTaskId).catch((err: Error) => { this.logger.error( `Alert update failed to run its underlying task. TaskManager runNow failed with Error: ${err.message}` ); @@ -227,7 +259,7 @@ export class AlertsClient { private async updateAlert( { id, data }: UpdateOptions, { attributes, version }: SavedObject - ) { + ): Promise { const alertType = this.alertTypeRegistry.get(attributes.alertTypeId); // Validate @@ -254,14 +286,22 @@ export class AlertsClient { references, } ); - return this.getAlertFromRaw(id, updatedObject.attributes, updatedObject.references); + + await this.invalidateApiKey({ apiKey: attributes.apiKey }); + + return this.getPartialAlertFromRaw( + id, + updatedObject.attributes, + updatedObject.updated_at, + updatedObject.references + ); } private apiKeyAsAlertAttributes( apiKey: CreateAPIKeyResult, username: string | null ): Pick { - return apiKey.created + return apiKey.apiKeysEnabled ? { apiKeyOwner: username, apiKey: Buffer.from(`${apiKey.result.id}:${apiKey.result.api_key}`).toString('base64'), @@ -273,7 +313,12 @@ export class AlertsClient { } public async updateApiKey({ id }: { id: string }) { - const { version, attributes } = await this.savedObjectsClient.get('alert', id); + const { + version, + attributes, + } = await this.encryptedSavedObjectsPlugin.getDecryptedAsInternalUser('alert', id, { + namespace: this.namespace, + }); const username = await this.getUserName(); await this.savedObjectsClient.update( @@ -286,10 +331,36 @@ export class AlertsClient { }, { version } ); + + await this.invalidateApiKey({ apiKey: attributes.apiKey }); + } + + private async invalidateApiKey({ apiKey }: { apiKey: string | null }): Promise { + if (!apiKey) { + return; + } + + try { + const apiKeyId = Buffer.from(apiKey, 'base64') + .toString() + .split(':')[0]; + const response = await this.invalidateAPIKey({ id: apiKeyId }); + if (response.apiKeysEnabled === true && response.result.error_count > 0) { + this.logger.error(`Failed to invalidate API Key [id="${apiKeyId}"]`); + } + } catch (e) { + this.logger.error(`Failed to invalidate API Key: ${e.message}`); + } } public async enable({ id }: { id: string }) { - const { attributes, version } = await this.savedObjectsClient.get('alert', id); + const { + version, + attributes, + } = await this.encryptedSavedObjectsPlugin.getDecryptedAsInternalUser('alert', id, { + namespace: this.namespace, + }); + if (attributes.enabled === false) { const scheduledTask = await this.scheduleAlert(id, attributes.alertTypeId); const username = await this.getUserName(); @@ -301,10 +372,12 @@ export class AlertsClient { enabled: true, ...this.apiKeyAsAlertAttributes(await this.createAPIKey(), username), updatedBy: username, + scheduledTaskId: scheduledTask.id, }, { version } ); + await this.invalidateApiKey({ apiKey: attributes.apiKey }); } } @@ -382,6 +455,7 @@ export class AlertsClient { alertId, { updatedBy: await this.getUserName(), + mutedInstanceIds: mutedInstanceIds.filter((id: string) => id !== alertInstanceId), }, { version } @@ -422,21 +496,34 @@ export class AlertsClient { } private getAlertFromRaw( + id: string, + rawAlert: RawAlert, + updatedAt: SavedObject['updated_at'], + references: SavedObjectReference[] | undefined + ): Alert { + // In order to support the partial update API of Saved Objects we have to support + // partial updates of an Alert, but when we receive an actual RawAlert, it is safe + // to cast the result to an Alert + return this.getPartialAlertFromRaw(id, rawAlert, updatedAt, references) as Alert; + } + + private getPartialAlertFromRaw( id: string, rawAlert: Partial, + updatedAt: SavedObject['updated_at'], references: SavedObjectReference[] | undefined - ) { - if (!rawAlert.actions) { - return { - id, - ...rawAlert, - }; - } - const actions = this.injectReferencesIntoActions(rawAlert.actions, references || []); + ): PartialAlert { return { id, ...rawAlert, - actions, + // we currently only support the Interval Schedule type + // Once we support additional types, this type signature will likely change + schedule: rawAlert.schedule as IntervalSchedule, + updatedAt: updatedAt ? new Date(updatedAt) : new Date(rawAlert.createdAt!), + createdAt: new Date(rawAlert.createdAt!), + actions: rawAlert.actions + ? this.injectReferencesIntoActions(rawAlert.actions, references || []) + : [], }; } diff --git a/x-pack/legacy/plugins/alerting/server/alerts_client_factory.test.ts b/x-pack/legacy/plugins/alerting/server/alerts_client_factory.test.ts new file mode 100644 index 0000000000000..754e02a3f1e5e --- /dev/null +++ b/x-pack/legacy/plugins/alerting/server/alerts_client_factory.test.ts @@ -0,0 +1,141 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Request } from 'hapi'; +import { AlertsClientFactory, ConstructorOpts } from './alerts_client_factory'; +import { alertTypeRegistryMock } from './alert_type_registry.mock'; +import { taskManagerMock } from '../../../../plugins/task_manager/server/task_manager.mock'; +import { KibanaRequest } from '../../../../../src/core/server'; +import { loggingServiceMock } from '../../../../../src/core/server/mocks'; +import { encryptedSavedObjectsMock } from '../../../../plugins/encrypted_saved_objects/server/mocks'; + +jest.mock('./alerts_client'); + +const savedObjectsClient = jest.fn(); +const securityPluginSetup = { + authc: { + createAPIKey: jest.fn(), + getCurrentUser: jest.fn(), + }, +}; +const alertsClientFactoryParams: jest.Mocked = { + logger: loggingServiceMock.create().get(), + taskManager: taskManagerMock.start(), + alertTypeRegistry: alertTypeRegistryMock.create(), + getSpaceId: jest.fn(), + spaceIdToNamespace: jest.fn(), + encryptedSavedObjectsPlugin: encryptedSavedObjectsMock.createStart(), +}; +const fakeRequest: Request = { + headers: {}, + getBasePath: () => '', + path: '/', + route: { settings: {} }, + url: { + href: '/', + }, + raw: { + req: { + url: '/', + }, + }, + getSavedObjectsClient: () => savedObjectsClient, +} as any; + +beforeEach(() => { + jest.resetAllMocks(); + alertsClientFactoryParams.getSpaceId.mockReturnValue('default'); + alertsClientFactoryParams.spaceIdToNamespace.mockReturnValue('default'); +}); + +test('creates an alerts client with proper constructor arguments', async () => { + const factory = new AlertsClientFactory(alertsClientFactoryParams); + factory.create(KibanaRequest.from(fakeRequest), fakeRequest); + + expect(jest.requireMock('./alerts_client').AlertsClient).toHaveBeenCalledWith({ + savedObjectsClient, + logger: alertsClientFactoryParams.logger, + taskManager: alertsClientFactoryParams.taskManager, + alertTypeRegistry: alertsClientFactoryParams.alertTypeRegistry, + spaceId: 'default', + namespace: 'default', + getUserName: expect.any(Function), + createAPIKey: expect.any(Function), + invalidateAPIKey: expect.any(Function), + encryptedSavedObjectsPlugin: alertsClientFactoryParams.encryptedSavedObjectsPlugin, + }); +}); + +test('getUserName() returns null when security is disabled', async () => { + const factory = new AlertsClientFactory(alertsClientFactoryParams); + factory.create(KibanaRequest.from(fakeRequest), fakeRequest); + const constructorCall = jest.requireMock('./alerts_client').AlertsClient.mock.calls[0][0]; + + const userNameResult = await constructorCall.getUserName(); + expect(userNameResult).toEqual(null); +}); + +test('getUserName() returns a name when security is enabled', async () => { + const factory = new AlertsClientFactory({ + ...alertsClientFactoryParams, + securityPluginSetup: securityPluginSetup as any, + }); + factory.create(KibanaRequest.from(fakeRequest), fakeRequest); + const constructorCall = jest.requireMock('./alerts_client').AlertsClient.mock.calls[0][0]; + + securityPluginSetup.authc.getCurrentUser.mockResolvedValueOnce({ username: 'bob' }); + const userNameResult = await constructorCall.getUserName(); + expect(userNameResult).toEqual('bob'); +}); + +test('createAPIKey() returns { apiKeysEnabled: false } when security is disabled', async () => { + const factory = new AlertsClientFactory(alertsClientFactoryParams); + factory.create(KibanaRequest.from(fakeRequest), fakeRequest); + const constructorCall = jest.requireMock('./alerts_client').AlertsClient.mock.calls[0][0]; + + const createAPIKeyResult = await constructorCall.createAPIKey(); + expect(createAPIKeyResult).toEqual({ apiKeysEnabled: false }); +}); + +test('createAPIKey() returns { apiKeysEnabled: false } when security is enabled but ES security is disabled', async () => { + const factory = new AlertsClientFactory(alertsClientFactoryParams); + factory.create(KibanaRequest.from(fakeRequest), fakeRequest); + const constructorCall = jest.requireMock('./alerts_client').AlertsClient.mock.calls[0][0]; + + securityPluginSetup.authc.createAPIKey.mockResolvedValueOnce(null); + const createAPIKeyResult = await constructorCall.createAPIKey(); + expect(createAPIKeyResult).toEqual({ apiKeysEnabled: false }); +}); + +test('createAPIKey() returns an API key when security is enabled', async () => { + const factory = new AlertsClientFactory({ + ...alertsClientFactoryParams, + securityPluginSetup: securityPluginSetup as any, + }); + factory.create(KibanaRequest.from(fakeRequest), fakeRequest); + const constructorCall = jest.requireMock('./alerts_client').AlertsClient.mock.calls[0][0]; + + securityPluginSetup.authc.createAPIKey.mockResolvedValueOnce({ api_key: '123', id: 'abc' }); + const createAPIKeyResult = await constructorCall.createAPIKey(); + expect(createAPIKeyResult).toEqual({ + apiKeysEnabled: true, + result: { api_key: '123', id: 'abc' }, + }); +}); + +test('createAPIKey() throws when security plugin createAPIKey throws an error', async () => { + const factory = new AlertsClientFactory({ + ...alertsClientFactoryParams, + securityPluginSetup: securityPluginSetup as any, + }); + factory.create(KibanaRequest.from(fakeRequest), fakeRequest); + const constructorCall = jest.requireMock('./alerts_client').AlertsClient.mock.calls[0][0]; + + securityPluginSetup.authc.createAPIKey.mockRejectedValueOnce(new Error('TLS disabled')); + await expect(constructorCall.createAPIKey()).rejects.toThrowErrorMatchingInlineSnapshot( + `"TLS disabled"` + ); +}); diff --git a/x-pack/legacy/plugins/alerting/server/alerts_client_factory.ts b/x-pack/legacy/plugins/alerting/server/alerts_client_factory.ts new file mode 100644 index 0000000000000..eab1cc3ce627b --- /dev/null +++ b/x-pack/legacy/plugins/alerting/server/alerts_client_factory.ts @@ -0,0 +1,99 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import Hapi from 'hapi'; +import uuid from 'uuid'; +import { AlertsClient } from './alerts_client'; +import { AlertTypeRegistry, SpaceIdToNamespaceFunction } from './types'; +import { SecurityPluginStartContract } from './shim'; +import { KibanaRequest, Logger } from '../../../../../src/core/server'; +import { InvalidateAPIKeyParams } from '../../../../plugins/security/server'; +import { PluginStartContract as EncryptedSavedObjectsStartContract } from '../../../../plugins/encrypted_saved_objects/server'; +import { TaskManagerStartContract } from '../../../../plugins/task_manager/server'; + +export interface ConstructorOpts { + logger: Logger; + taskManager: TaskManagerStartContract; + alertTypeRegistry: AlertTypeRegistry; + securityPluginSetup?: SecurityPluginStartContract; + getSpaceId: (request: Hapi.Request) => string | undefined; + spaceIdToNamespace: SpaceIdToNamespaceFunction; + encryptedSavedObjectsPlugin: EncryptedSavedObjectsStartContract; +} + +export class AlertsClientFactory { + private readonly logger: Logger; + private readonly taskManager: TaskManagerStartContract; + private readonly alertTypeRegistry: AlertTypeRegistry; + private readonly securityPluginSetup?: SecurityPluginStartContract; + private readonly getSpaceId: (request: Hapi.Request) => string | undefined; + private readonly spaceIdToNamespace: SpaceIdToNamespaceFunction; + private readonly encryptedSavedObjectsPlugin: EncryptedSavedObjectsStartContract; + + constructor(options: ConstructorOpts) { + this.logger = options.logger; + this.getSpaceId = options.getSpaceId; + this.taskManager = options.taskManager; + this.alertTypeRegistry = options.alertTypeRegistry; + this.securityPluginSetup = options.securityPluginSetup; + this.spaceIdToNamespace = options.spaceIdToNamespace; + this.encryptedSavedObjectsPlugin = options.encryptedSavedObjectsPlugin; + } + + public create(request: KibanaRequest, legacyRequest: Hapi.Request): AlertsClient { + const { securityPluginSetup } = this; + const spaceId = this.getSpaceId(legacyRequest); + return new AlertsClient({ + spaceId, + logger: this.logger, + taskManager: this.taskManager, + alertTypeRegistry: this.alertTypeRegistry, + savedObjectsClient: legacyRequest.getSavedObjectsClient(), + namespace: this.spaceIdToNamespace(spaceId), + encryptedSavedObjectsPlugin: this.encryptedSavedObjectsPlugin, + async getUserName() { + if (!securityPluginSetup) { + return null; + } + const user = await securityPluginSetup.authc.getCurrentUser(request); + return user ? user.username : null; + }, + async createAPIKey() { + if (!securityPluginSetup) { + return { apiKeysEnabled: false }; + } + const createAPIKeyResult = await securityPluginSetup.authc.createAPIKey(request, { + name: `source: alerting, generated uuid: "${uuid.v4()}"`, + role_descriptors: {}, + }); + if (!createAPIKeyResult) { + return { apiKeysEnabled: false }; + } + return { + apiKeysEnabled: true, + result: createAPIKeyResult, + }; + }, + async invalidateAPIKey(params: InvalidateAPIKeyParams) { + if (!securityPluginSetup) { + return { apiKeysEnabled: false }; + } + const invalidateAPIKeyResult = await securityPluginSetup.authc.invalidateAPIKey( + request, + params + ); + // Null when Elasticsearch security is disabled + if (!invalidateAPIKeyResult) { + return { apiKeysEnabled: false }; + } + return { + apiKeysEnabled: true, + result: invalidateAPIKeyResult, + }; + }, + }); + } +} diff --git a/x-pack/legacy/plugins/alerting/server/lib/alerts_client_factory.test.ts b/x-pack/legacy/plugins/alerting/server/lib/alerts_client_factory.test.ts deleted file mode 100644 index a465aebc8bd86..0000000000000 --- a/x-pack/legacy/plugins/alerting/server/lib/alerts_client_factory.test.ts +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { Request } from 'hapi'; -import { AlertsClientFactory, ConstructorOpts } from './alerts_client_factory'; -import { alertTypeRegistryMock } from '../alert_type_registry.mock'; -import { taskManagerMock } from '../../../task_manager/task_manager.mock'; -import { KibanaRequest } from '../../../../../../src/core/server'; -import { loggingServiceMock } from '../../../../../../src/core/server/mocks'; - -jest.mock('../alerts_client'); - -const savedObjectsClient = jest.fn(); -const securityPluginSetup = { - authc: { - createAPIKey: jest.fn(), - getCurrentUser: jest.fn(), - }, -}; -const alertsClientFactoryParams: jest.Mocked = { - logger: loggingServiceMock.create().get(), - taskManager: taskManagerMock.create(), - alertTypeRegistry: alertTypeRegistryMock.create(), - getSpaceId: jest.fn(), -}; -const fakeRequest: Request = { - headers: {}, - getBasePath: () => '', - path: '/', - route: { settings: {} }, - url: { - href: '/', - }, - raw: { - req: { - url: '/', - }, - }, - getSavedObjectsClient: () => savedObjectsClient, -} as any; - -beforeEach(() => { - jest.resetAllMocks(); - alertsClientFactoryParams.getSpaceId.mockReturnValue('default'); -}); - -test('creates an alerts client with proper constructor arguments', async () => { - const factory = new AlertsClientFactory(alertsClientFactoryParams); - factory.create(KibanaRequest.from(fakeRequest), fakeRequest); - - expect(jest.requireMock('../alerts_client').AlertsClient).toHaveBeenCalledWith({ - savedObjectsClient, - logger: alertsClientFactoryParams.logger, - taskManager: alertsClientFactoryParams.taskManager, - alertTypeRegistry: alertsClientFactoryParams.alertTypeRegistry, - spaceId: 'default', - getUserName: expect.any(Function), - createAPIKey: expect.any(Function), - }); -}); - -test('getUserName() returns null when security is disabled', async () => { - const factory = new AlertsClientFactory(alertsClientFactoryParams); - factory.create(KibanaRequest.from(fakeRequest), fakeRequest); - const constructorCall = jest.requireMock('../alerts_client').AlertsClient.mock.calls[0][0]; - - const userNameResult = await constructorCall.getUserName(); - expect(userNameResult).toEqual(null); -}); - -test('getUserName() returns a name when security is enabled', async () => { - const factory = new AlertsClientFactory({ - ...alertsClientFactoryParams, - securityPluginSetup: securityPluginSetup as any, - }); - factory.create(KibanaRequest.from(fakeRequest), fakeRequest); - const constructorCall = jest.requireMock('../alerts_client').AlertsClient.mock.calls[0][0]; - - securityPluginSetup.authc.getCurrentUser.mockResolvedValueOnce({ username: 'bob' }); - const userNameResult = await constructorCall.getUserName(); - expect(userNameResult).toEqual('bob'); -}); - -test('createAPIKey() returns { created: false } when security is disabled', async () => { - const factory = new AlertsClientFactory(alertsClientFactoryParams); - factory.create(KibanaRequest.from(fakeRequest), fakeRequest); - const constructorCall = jest.requireMock('../alerts_client').AlertsClient.mock.calls[0][0]; - - const createAPIKeyResult = await constructorCall.createAPIKey(); - expect(createAPIKeyResult).toEqual({ created: false }); -}); - -test('createAPIKey() returns { created: false } when security is enabled but ES security is disabled', async () => { - const factory = new AlertsClientFactory(alertsClientFactoryParams); - factory.create(KibanaRequest.from(fakeRequest), fakeRequest); - const constructorCall = jest.requireMock('../alerts_client').AlertsClient.mock.calls[0][0]; - - securityPluginSetup.authc.createAPIKey.mockResolvedValueOnce(null); - const createAPIKeyResult = await constructorCall.createAPIKey(); - expect(createAPIKeyResult).toEqual({ created: false }); -}); - -test('createAPIKey() returns an API key when security is enabled', async () => { - const factory = new AlertsClientFactory({ - ...alertsClientFactoryParams, - securityPluginSetup: securityPluginSetup as any, - }); - factory.create(KibanaRequest.from(fakeRequest), fakeRequest); - const constructorCall = jest.requireMock('../alerts_client').AlertsClient.mock.calls[0][0]; - - securityPluginSetup.authc.createAPIKey.mockResolvedValueOnce({ api_key: '123', id: 'abc' }); - const createAPIKeyResult = await constructorCall.createAPIKey(); - expect(createAPIKeyResult).toEqual({ created: true, result: { api_key: '123', id: 'abc' } }); -}); - -test('createAPIKey() throws when security plugin createAPIKey throws an error', async () => { - const factory = new AlertsClientFactory({ - ...alertsClientFactoryParams, - securityPluginSetup: securityPluginSetup as any, - }); - factory.create(KibanaRequest.from(fakeRequest), fakeRequest); - const constructorCall = jest.requireMock('../alerts_client').AlertsClient.mock.calls[0][0]; - - securityPluginSetup.authc.createAPIKey.mockRejectedValueOnce(new Error('TLS disabled')); - await expect(constructorCall.createAPIKey()).rejects.toThrowErrorMatchingInlineSnapshot( - `"TLS disabled"` - ); -}); diff --git a/x-pack/legacy/plugins/alerting/server/lib/alerts_client_factory.ts b/x-pack/legacy/plugins/alerting/server/lib/alerts_client_factory.ts deleted file mode 100644 index b75d681b6586a..0000000000000 --- a/x-pack/legacy/plugins/alerting/server/lib/alerts_client_factory.ts +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import Hapi from 'hapi'; -import uuid from 'uuid'; -import { AlertTypeRegistry } from '../types'; -import { AlertsClient } from '../alerts_client'; -import { SecurityPluginStartContract, TaskManagerStartContract } from '../shim'; -import { KibanaRequest, Logger } from '../../../../../../src/core/server'; - -export interface ConstructorOpts { - logger: Logger; - taskManager: TaskManagerStartContract; - alertTypeRegistry: AlertTypeRegistry; - securityPluginSetup?: SecurityPluginStartContract; - getSpaceId: (request: Hapi.Request) => string | undefined; -} - -export class AlertsClientFactory { - private readonly logger: Logger; - private readonly taskManager: TaskManagerStartContract; - private readonly alertTypeRegistry: AlertTypeRegistry; - private readonly securityPluginSetup?: SecurityPluginStartContract; - private readonly getSpaceId: (request: Hapi.Request) => string | undefined; - - constructor(options: ConstructorOpts) { - this.logger = options.logger; - this.getSpaceId = options.getSpaceId; - this.taskManager = options.taskManager; - this.alertTypeRegistry = options.alertTypeRegistry; - this.securityPluginSetup = options.securityPluginSetup; - } - - public create(request: KibanaRequest, legacyRequest: Hapi.Request): AlertsClient { - const { securityPluginSetup } = this; - return new AlertsClient({ - logger: this.logger, - taskManager: this.taskManager, - alertTypeRegistry: this.alertTypeRegistry, - savedObjectsClient: legacyRequest.getSavedObjectsClient(), - spaceId: this.getSpaceId(legacyRequest), - async getUserName() { - if (!securityPluginSetup) { - return null; - } - const user = await securityPluginSetup.authc.getCurrentUser(request); - return user ? user.username : null; - }, - async createAPIKey() { - if (!securityPluginSetup) { - return { created: false }; - } - const createAPIKeyResult = await securityPluginSetup.authc.createAPIKey(request, { - name: `source: alerting, generated uuid: "${uuid.v4()}"`, - role_descriptors: {}, - }); - if (!createAPIKeyResult) { - return { created: false }; - } - return { - created: true, - result: createAPIKeyResult, - }; - }, - }); - } -} diff --git a/x-pack/legacy/plugins/alerting/server/lib/index.ts b/x-pack/legacy/plugins/alerting/server/lib/index.ts index ca4ddf9e11ad2..c41ea4a5998ff 100644 --- a/x-pack/legacy/plugins/alerting/server/lib/index.ts +++ b/x-pack/legacy/plugins/alerting/server/lib/index.ts @@ -4,8 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -export { AlertInstance } from './alert_instance'; -export { validateAlertTypeParams } from './validate_alert_type_params'; export { parseDuration, getDurationSchema } from './parse_duration'; -export { AlertsClientFactory } from './alerts_client_factory'; -export { TaskRunnerFactory } from './task_runner_factory'; +export { LicenseState } from './license_state'; +export { validateAlertTypeParams } from './validate_alert_type_params'; diff --git a/x-pack/legacy/plugins/alerting/server/lib/result_type.ts b/x-pack/legacy/plugins/alerting/server/lib/result_type.ts new file mode 100644 index 0000000000000..52843f6362303 --- /dev/null +++ b/x-pack/legacy/plugins/alerting/server/lib/result_type.ts @@ -0,0 +1,62 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export interface Ok { + tag: 'ok'; + value: T; +} + +export interface Err { + tag: 'err'; + error: E; +} +export type Result = Ok | Err; + +export type Resultable = { + [P in keyof T]: Result; +}; + +export function asOk(value: T): Ok { + return { + tag: 'ok', + value, + }; +} + +export function asErr(error: T): Err { + return { + tag: 'err', + error, + }; +} + +export function isOk(result: Result): result is Ok { + return result.tag === 'ok'; +} + +export function isErr(result: Result): result is Err { + return !isOk(result); +} + +export async function promiseResult(future: Promise): Promise> { + try { + return asOk(await future); + } catch (e) { + return asErr(e); + } +} + +export function map( + result: Result, + onOk: (value: T) => Resolution, + onErr: (error: E) => Resolution +): Resolution { + return isOk(result) ? onOk(result.value) : onErr(result.error); +} + +export function resolveErr(result: Result, onErr: (error: E) => T): T { + return isOk(result) ? result.value : onErr(result.error); +} diff --git a/x-pack/legacy/plugins/alerting/server/lib/task_runner_factory.test.ts b/x-pack/legacy/plugins/alerting/server/lib/task_runner_factory.test.ts deleted file mode 100644 index 7966f98c749c8..0000000000000 --- a/x-pack/legacy/plugins/alerting/server/lib/task_runner_factory.test.ts +++ /dev/null @@ -1,345 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import sinon from 'sinon'; -import { schema } from '@kbn/config-schema'; -import { AlertExecutorOptions } from '../types'; -import { ConcreteTaskInstance, TaskStatus } from '../../../task_manager'; -import { TaskRunnerContext, TaskRunnerFactory } from './task_runner_factory'; -import { encryptedSavedObjectsMock } from '../../../../../plugins/encrypted_saved_objects/server/mocks'; -import { - savedObjectsClientMock, - loggingServiceMock, -} from '../../../../../../src/core/server/mocks'; - -const alertType = { - id: 'test', - name: 'My test alert', - actionGroups: ['default'], - executor: jest.fn(), -}; -let fakeTimer: sinon.SinonFakeTimers; -let taskRunnerFactory: TaskRunnerFactory; -let mockedTaskInstance: ConcreteTaskInstance; - -beforeAll(() => { - fakeTimer = sinon.useFakeTimers(); - mockedTaskInstance = { - id: '', - attempts: 0, - status: TaskStatus.Running, - version: '123', - runAt: new Date(), - scheduledAt: new Date(), - startedAt: new Date(), - retryAt: new Date(Date.now() + 5 * 60 * 1000), - state: { - startedAt: new Date(Date.now() - 5 * 60 * 1000), - }, - taskType: 'alerting:test', - params: { - alertId: '1', - }, - ownerId: null, - }; - taskRunnerFactory = new TaskRunnerFactory(); - taskRunnerFactory.initialize(taskRunnerFactoryInitializerParams); -}); - -afterAll(() => fakeTimer.restore()); - -const savedObjectsClient = savedObjectsClientMock.create(); -const encryptedSavedObjectsPlugin = encryptedSavedObjectsMock.createStart(); -const services = { - log: jest.fn(), - callCluster: jest.fn(), - savedObjectsClient, -}; - -const taskRunnerFactoryInitializerParams: jest.Mocked = { - getServices: jest.fn().mockReturnValue(services), - executeAction: jest.fn(), - encryptedSavedObjectsPlugin, - logger: loggingServiceMock.create().get(), - spaceIdToNamespace: jest.fn().mockReturnValue(undefined), - getBasePath: jest.fn().mockReturnValue(undefined), -}; - -const mockedAlertTypeSavedObject = { - id: '1', - type: 'alert', - attributes: { - enabled: true, - alertTypeId: '123', - schedule: { interval: '10s' }, - mutedInstanceIds: [], - params: { - bar: true, - }, - actions: [ - { - group: 'default', - actionRef: 'action_0', - params: { - foo: true, - }, - }, - ], - }, - references: [ - { - name: 'action_0', - type: 'action', - id: '1', - }, - ], -}; - -beforeEach(() => { - jest.resetAllMocks(); - taskRunnerFactoryInitializerParams.getServices.mockReturnValue(services); -}); - -test(`throws an error if factory isn't initialized`, () => { - const factory = new TaskRunnerFactory(); - expect(() => - factory.create(alertType, { taskInstance: mockedTaskInstance }) - ).toThrowErrorMatchingInlineSnapshot(`"TaskRunnerFactory not initialized"`); -}); - -test(`throws an error if factory is already initialized`, () => { - const factory = new TaskRunnerFactory(); - factory.initialize(taskRunnerFactoryInitializerParams); - expect(() => - factory.initialize(taskRunnerFactoryInitializerParams) - ).toThrowErrorMatchingInlineSnapshot(`"TaskRunnerFactory already initialized"`); -}); - -test('successfully executes the task', async () => { - const taskRunner = taskRunnerFactory.create(alertType, { - taskInstance: mockedTaskInstance, - }); - savedObjectsClient.get.mockResolvedValueOnce(mockedAlertTypeSavedObject); - encryptedSavedObjectsPlugin.getDecryptedAsInternalUser.mockResolvedValueOnce({ - id: '1', - type: 'alert', - attributes: { - apiKey: Buffer.from('123:abc').toString('base64'), - }, - references: [], - }); - const runnerResult = await taskRunner.run(); - expect(runnerResult).toMatchInlineSnapshot(` - Object { - "runAt": 1970-01-01T00:00:10.000Z, - "state": Object { - "alertInstances": Object {}, - "alertTypeState": undefined, - "previousStartedAt": 1970-01-01T00:00:00.000Z, - }, - } - `); - expect(alertType.executor).toHaveBeenCalledTimes(1); - const call = alertType.executor.mock.calls[0][0]; - expect(call.params).toMatchInlineSnapshot(` - Object { - "bar": true, - } - `); - expect(call.startedAt).toMatchInlineSnapshot(`1970-01-01T00:00:00.000Z`); - expect(call.state).toMatchInlineSnapshot(`Object {}`); - expect(call.services.alertInstanceFactory).toBeTruthy(); - expect(call.services.callCluster).toBeTruthy(); - expect(call.services).toBeTruthy(); -}); - -test('executeAction is called per alert instance that is scheduled', async () => { - alertType.executor.mockImplementation(({ services: executorServices }: AlertExecutorOptions) => { - executorServices.alertInstanceFactory('1').scheduleActions('default'); - }); - const taskRunner = taskRunnerFactory.create(alertType, { - taskInstance: mockedTaskInstance, - }); - savedObjectsClient.get.mockResolvedValueOnce(mockedAlertTypeSavedObject); - encryptedSavedObjectsPlugin.getDecryptedAsInternalUser.mockResolvedValueOnce({ - id: '1', - type: 'alert', - attributes: { - apiKey: Buffer.from('123:abc').toString('base64'), - }, - references: [], - }); - await taskRunner.run(); - expect(taskRunnerFactoryInitializerParams.executeAction).toHaveBeenCalledTimes(1); - expect(taskRunnerFactoryInitializerParams.executeAction.mock.calls[0]).toMatchInlineSnapshot(` - Array [ - Object { - "apiKey": "MTIzOmFiYw==", - "id": "1", - "params": Object { - "foo": true, - }, - "spaceId": undefined, - }, - ] - `); -}); - -test('persists alertInstances passed in from state, only if they are scheduled for execution', async () => { - alertType.executor.mockImplementation(({ services: executorServices }: AlertExecutorOptions) => { - executorServices.alertInstanceFactory('1').scheduleActions('default'); - }); - const taskRunner = taskRunnerFactory.create(alertType, { - taskInstance: { - ...mockedTaskInstance, - state: { - ...mockedTaskInstance.state, - alertInstances: { - '1': { meta: {}, state: { bar: false } }, - '2': { meta: {}, state: { bar: false } }, - }, - }, - }, - }); - savedObjectsClient.get.mockResolvedValueOnce(mockedAlertTypeSavedObject); - encryptedSavedObjectsPlugin.getDecryptedAsInternalUser.mockResolvedValueOnce({ - id: '1', - type: 'alert', - attributes: { - apiKey: Buffer.from('123:abc').toString('base64'), - }, - references: [], - }); - const runnerResult = await taskRunner.run(); - expect(runnerResult.state.alertInstances).toMatchInlineSnapshot(` - Object { - "1": Object { - "meta": Object { - "lastScheduledActions": Object { - "date": 1970-01-01T00:00:00.000Z, - "group": "default", - }, - }, - "state": Object { - "bar": false, - }, - }, - } - `); -}); - -test('validates params before executing the alert type', async () => { - const taskRunner = taskRunnerFactory.create( - { - ...alertType, - validate: { - params: schema.object({ - param1: schema.string(), - }), - }, - }, - { taskInstance: mockedTaskInstance } - ); - savedObjectsClient.get.mockResolvedValueOnce(mockedAlertTypeSavedObject); - encryptedSavedObjectsPlugin.getDecryptedAsInternalUser.mockResolvedValueOnce({ - id: '1', - type: 'alert', - attributes: { - apiKey: Buffer.from('123:abc').toString('base64'), - }, - references: [], - }); - await expect(taskRunner.run()).rejects.toThrowErrorMatchingInlineSnapshot( - `"params invalid: [param1]: expected value of type [string] but got [undefined]"` - ); -}); - -test('throws error if reference not found', async () => { - const taskRunner = taskRunnerFactory.create(alertType, { - taskInstance: mockedTaskInstance, - }); - savedObjectsClient.get.mockResolvedValueOnce({ - ...mockedAlertTypeSavedObject, - references: [], - }); - encryptedSavedObjectsPlugin.getDecryptedAsInternalUser.mockResolvedValueOnce({ - id: '1', - type: 'alert', - attributes: { - apiKey: Buffer.from('123:abc').toString('base64'), - }, - references: [], - }); - await expect(taskRunner.run()).rejects.toThrowErrorMatchingInlineSnapshot( - `"Action reference \\"action_0\\" not found in alert id: 1"` - ); -}); - -test('uses API key when provided', async () => { - const taskRunner = taskRunnerFactory.create(alertType, { - taskInstance: mockedTaskInstance, - }); - savedObjectsClient.get.mockResolvedValueOnce(mockedAlertTypeSavedObject); - encryptedSavedObjectsPlugin.getDecryptedAsInternalUser.mockResolvedValueOnce({ - id: '1', - type: 'alert', - attributes: { - apiKey: Buffer.from('123:abc').toString('base64'), - }, - references: [], - }); - - await taskRunner.run(); - expect(taskRunnerFactoryInitializerParams.getServices).toHaveBeenCalledWith({ - getBasePath: expect.anything(), - headers: { - // base64 encoded "123:abc" - authorization: 'ApiKey MTIzOmFiYw==', - }, - path: '/', - route: { settings: {} }, - url: { - href: '/', - }, - raw: { - req: { - url: '/', - }, - }, - }); -}); - -test(`doesn't use API key when not provided`, async () => { - const factory = new TaskRunnerFactory(); - factory.initialize(taskRunnerFactoryInitializerParams); - const taskRunner = factory.create(alertType, { - taskInstance: mockedTaskInstance, - }); - savedObjectsClient.get.mockResolvedValueOnce(mockedAlertTypeSavedObject); - encryptedSavedObjectsPlugin.getDecryptedAsInternalUser.mockResolvedValueOnce({ - id: '1', - type: 'alert', - attributes: {}, - references: [], - }); - - await taskRunner.run(); - - expect(taskRunnerFactoryInitializerParams.getServices).toHaveBeenCalledWith({ - getBasePath: expect.anything(), - headers: {}, - path: '/', - route: { settings: {} }, - url: { - href: '/', - }, - raw: { - req: { - url: '/', - }, - }, - }); -}); diff --git a/x-pack/legacy/plugins/alerting/server/lib/task_runner_factory.ts b/x-pack/legacy/plugins/alerting/server/lib/task_runner_factory.ts deleted file mode 100644 index fe0979538d04e..0000000000000 --- a/x-pack/legacy/plugins/alerting/server/lib/task_runner_factory.ts +++ /dev/null @@ -1,190 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { Logger } from '../../../../../../src/core/server'; -import { RunContext } from '../../../task_manager'; -import { createExecutionHandler } from './create_execution_handler'; -import { createAlertInstanceFactory } from './create_alert_instance_factory'; -import { AlertInstance } from './alert_instance'; -import { getNextRunAt } from './get_next_run_at'; -import { validateAlertTypeParams } from './validate_alert_type_params'; -import { PluginStartContract as EncryptedSavedObjectsStartContract } from '../../../../../plugins/encrypted_saved_objects/server'; -import { PluginStartContract as ActionsPluginStartContract } from '../../../actions'; -import { - AlertType, - AlertServices, - GetBasePathFunction, - GetServicesFunction, - RawAlert, - SpaceIdToNamespaceFunction, - IntervalSchedule, -} from '../types'; - -export interface TaskRunnerContext { - logger: Logger; - getServices: GetServicesFunction; - executeAction: ActionsPluginStartContract['execute']; - encryptedSavedObjectsPlugin: EncryptedSavedObjectsStartContract; - spaceIdToNamespace: SpaceIdToNamespaceFunction; - getBasePath: GetBasePathFunction; -} - -export class TaskRunnerFactory { - private isInitialized = false; - private taskRunnerContext?: TaskRunnerContext; - - public initialize(taskRunnerContext: TaskRunnerContext) { - if (this.isInitialized) { - throw new Error('TaskRunnerFactory already initialized'); - } - this.isInitialized = true; - this.taskRunnerContext = taskRunnerContext; - } - - public create(alertType: AlertType, { taskInstance }: RunContext) { - if (!this.isInitialized) { - throw new Error('TaskRunnerFactory not initialized'); - } - - const { - logger, - getServices, - executeAction, - encryptedSavedObjectsPlugin, - spaceIdToNamespace, - getBasePath, - } = this.taskRunnerContext!; - - return { - async run() { - const { alertId, spaceId } = taskInstance.params; - const requestHeaders: Record = {}; - const namespace = spaceIdToNamespace(spaceId); - // Only fetch encrypted attributes here, we'll create a saved objects client - // scoped with the API key to fetch the remaining data. - const { - attributes: { apiKey }, - } = await encryptedSavedObjectsPlugin.getDecryptedAsInternalUser( - 'alert', - alertId, - { namespace } - ); - - if (apiKey) { - requestHeaders.authorization = `ApiKey ${apiKey}`; - } - - const fakeRequest = { - headers: requestHeaders, - getBasePath: () => getBasePath(spaceId), - path: '/', - route: { settings: {} }, - url: { - href: '/', - }, - raw: { - req: { - url: '/', - }, - }, - }; - - const services = getServices(fakeRequest); - // Ensure API key is still valid and user has access - const { - attributes: { params, actions, schedule, throttle, muteAll, mutedInstanceIds }, - references, - } = await services.savedObjectsClient.get('alert', alertId); - - // Validate - const validatedAlertTypeParams = validateAlertTypeParams(alertType, params); - - // Inject ids into actions - const actionsWithIds = actions.map(action => { - const actionReference = references.find(obj => obj.name === action.actionRef); - if (!actionReference) { - throw new Error( - `Action reference "${action.actionRef}" not found in alert id: ${alertId}` - ); - } - return { - ...action, - id: actionReference.id, - }; - }); - - const executionHandler = createExecutionHandler({ - alertId, - logger, - executeAction, - apiKey, - actions: actionsWithIds, - spaceId, - alertType, - }); - const alertInstances: Record = {}; - const alertInstancesData = taskInstance.state.alertInstances || {}; - for (const id of Object.keys(alertInstancesData)) { - alertInstances[id] = new AlertInstance(alertInstancesData[id]); - } - const alertInstanceFactory = createAlertInstanceFactory(alertInstances); - - const alertTypeServices: AlertServices = { - ...services, - alertInstanceFactory, - }; - - const alertTypeState = await alertType.executor({ - alertId, - services: alertTypeServices, - params: validatedAlertTypeParams, - state: taskInstance.state.alertTypeState || {}, - startedAt: taskInstance.startedAt!, - previousStartedAt: taskInstance.state.previousStartedAt, - }); - - await Promise.all( - Object.keys(alertInstances).map(alertInstanceId => { - const alertInstance = alertInstances[alertInstanceId]; - if (alertInstance.hasScheduledActions()) { - if ( - alertInstance.isThrottled(throttle) || - muteAll || - mutedInstanceIds.includes(alertInstanceId) - ) { - return; - } - const { actionGroup, context, state } = alertInstance.getScheduledActionOptions()!; - alertInstance.updateLastScheduledActions(actionGroup); - alertInstance.unscheduleActions(); - return executionHandler({ actionGroup, context, state, alertInstanceId }); - } else { - // Cleanup alert instances that are no longer scheduling actions to avoid over populating the alertInstances object - delete alertInstances[alertInstanceId]; - } - }) - ); - - const nextRunAt = getNextRunAt( - new Date(taskInstance.startedAt!), - // we do not currently have a good way of returning the type - // from SavedObjectsClient, and as we currenrtly require a schedule - // and we only support `interval`, we can cast this safely - schedule as IntervalSchedule - ); - - return { - state: { - alertTypeState, - alertInstances, - previousStartedAt: taskInstance.startedAt!, - }, - runAt: nextRunAt, - }; - }, - }; - } -} diff --git a/x-pack/legacy/plugins/alerting/server/plugin.ts b/x-pack/legacy/plugins/alerting/server/plugin.ts index 935431c56ff30..357db9e3df97e 100644 --- a/x-pack/legacy/plugins/alerting/server/plugin.ts +++ b/x-pack/legacy/plugins/alerting/server/plugin.ts @@ -5,11 +5,12 @@ */ import Hapi from 'hapi'; -import { first } from 'rxjs/operators'; + import { Services } from './types'; import { AlertsClient } from './alerts_client'; import { AlertTypeRegistry } from './alert_type_registry'; -import { AlertsClientFactory, TaskRunnerFactory } from './lib'; +import { TaskRunnerFactory } from './task_runner'; +import { AlertsClientFactory } from './alerts_client_factory'; import { LicenseState } from './lib/license_state'; import { IClusterClient, KibanaRequest, Logger } from '../../../../../src/core/server'; import { @@ -61,7 +62,7 @@ export class Plugin { core: AlertingCoreSetup, plugins: AlertingPluginsSetup ): Promise { - this.adminClient = await core.elasticsearch.adminClient$.pipe(first()).toPromise(); + this.adminClient = core.elasticsearch.adminClient; this.licenseState = new LicenseState(plugins.licensing.license$); @@ -78,7 +79,7 @@ export class Plugin { }); const alertTypeRegistry = new AlertTypeRegistry({ - taskManager: plugins.task_manager, + taskManager: plugins.taskManager, taskRunnerFactory: this.taskRunnerFactory, }); this.alertTypeRegistry = alertTypeRegistry; @@ -107,11 +108,18 @@ export class Plugin { public start(core: AlertingCoreStart, plugins: AlertingPluginsStart): PluginStartContract { const { adminClient, serverBasePath } = this; + function spaceIdToNamespace(spaceId?: string): string | undefined { + const spacesPlugin = plugins.spaces(); + return spacesPlugin && spaceId ? spacesPlugin.spaceIdToNamespace(spaceId) : undefined; + } + const alertsClientFactory = new AlertsClientFactory({ alertTypeRegistry: this.alertTypeRegistry!, logger: this.logger, - taskManager: plugins.task_manager, + taskManager: plugins.taskManager, securityPluginSetup: plugins.security, + encryptedSavedObjectsPlugin: plugins.encryptedSavedObjects, + spaceIdToNamespace, getSpaceId(request: Hapi.Request) { const spacesPlugin = plugins.spaces(); return spacesPlugin ? spacesPlugin.getSpaceId(request) : undefined; @@ -127,12 +135,9 @@ export class Plugin { savedObjectsClient: core.savedObjects.getScopedSavedObjectsClient(request), }; }, + spaceIdToNamespace, executeAction: plugins.actions.execute, encryptedSavedObjectsPlugin: plugins.encryptedSavedObjects, - spaceIdToNamespace(spaceId?: string): string | undefined { - const spacesPlugin = plugins.spaces(); - return spacesPlugin && spaceId ? spacesPlugin.spaceIdToNamespace(spaceId) : undefined; - }, getBasePath(spaceId?: string): string { const spacesPlugin = plugins.spaces(); return spacesPlugin && spaceId ? spacesPlugin.getBasePath(spaceId) : serverBasePath!; diff --git a/x-pack/legacy/plugins/alerting/server/routes/create.test.ts b/x-pack/legacy/plugins/alerting/server/routes/create.test.ts index c41e0d068aff2..2a0ae78fd78b2 100644 --- a/x-pack/legacy/plugins/alerting/server/routes/create.test.ts +++ b/x-pack/legacy/plugins/alerting/server/routes/create.test.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { omit } from 'lodash'; import { createMockServer } from './_mock_server'; import { createAlertRoute } from './create'; @@ -19,6 +20,7 @@ const mockedAlert = { params: { bar: true, }, + throttle: '30s', actions: [ { group: 'default', @@ -39,8 +41,19 @@ test('creates an alert with proper parameters', async () => { payload: mockedAlert, }; + const createdAt = new Date(); + const updatedAt = new Date(); alertsClient.create.mockResolvedValueOnce({ ...mockedAlert, + enabled: true, + muteAll: false, + createdBy: '', + updatedBy: '', + apiKey: '', + apiKeyOwner: '', + mutedInstanceIds: [], + createdAt, + updatedAt, id: '123', actions: [ { @@ -52,7 +65,8 @@ test('creates an alert with proper parameters', async () => { const { payload, statusCode } = await server.inject(request); expect(statusCode).toBe(200); const response = JSON.parse(payload); - expect(response).toMatchInlineSnapshot(` + expect(new Date(response.createdAt)).toEqual(createdAt); + expect(omit(response, 'createdAt', 'updatedAt')).toMatchInlineSnapshot(` Object { "actions": Array [ Object { @@ -65,8 +79,14 @@ test('creates an alert with proper parameters', async () => { }, ], "alertTypeId": "1", + "apiKey": "", + "apiKeyOwner": "", "consumer": "bar", + "createdBy": "", + "enabled": true, "id": "123", + "muteAll": false, + "mutedInstanceIds": Array [], "name": "abc", "params": Object { "bar": true, @@ -77,6 +97,8 @@ test('creates an alert with proper parameters', async () => { "tags": Array [ "foo", ], + "throttle": "30s", + "updatedBy": "", } `); expect(alertsClient.create).toHaveBeenCalledTimes(1); @@ -106,7 +128,7 @@ test('creates an alert with proper parameters', async () => { "tags": Array [ "foo", ], - "throttle": null, + "throttle": "30s", }, }, ] diff --git a/x-pack/legacy/plugins/alerting/server/routes/get.test.ts b/x-pack/legacy/plugins/alerting/server/routes/get.test.ts index b97762d10c960..320e9042d87c5 100644 --- a/x-pack/legacy/plugins/alerting/server/routes/get.test.ts +++ b/x-pack/legacy/plugins/alerting/server/routes/get.test.ts @@ -17,6 +17,8 @@ const mockedAlert = { params: { bar: true, }, + createdAt: new Date(), + updatedAt: new Date(), actions: [ { group: 'default', @@ -27,6 +29,17 @@ const mockedAlert = { }, }, ], + consumer: 'bar', + name: 'abc', + tags: ['foo'], + enabled: true, + muteAll: false, + createdBy: '', + updatedBy: '', + apiKey: '', + apiKeyOwner: '', + throttle: '30s', + mutedInstanceIds: [], }; beforeEach(() => jest.resetAllMocks()); @@ -40,8 +53,10 @@ test('calls get with proper parameters', async () => { alertsClient.get.mockResolvedValueOnce(mockedAlert); const { payload, statusCode } = await server.inject(request); expect(statusCode).toBe(200); - const response = JSON.parse(payload); - expect(response).toEqual(mockedAlert); + const { createdAt, updatedAt, ...response } = JSON.parse(payload); + expect({ createdAt: new Date(createdAt), updatedAt: new Date(updatedAt), ...response }).toEqual( + mockedAlert + ); expect(alertsClient.get).toHaveBeenCalledTimes(1); expect(alertsClient.get.mock.calls[0]).toMatchInlineSnapshot(` Array [ diff --git a/x-pack/legacy/plugins/alerting/server/routes/update.test.ts b/x-pack/legacy/plugins/alerting/server/routes/update.test.ts index 8ce9d94140e6d..83b70b505234f 100644 --- a/x-pack/legacy/plugins/alerting/server/routes/update.test.ts +++ b/x-pack/legacy/plugins/alerting/server/routes/update.test.ts @@ -20,6 +20,8 @@ const mockedResponse = { params: { otherField: false, }, + createdAt: new Date(), + updatedAt: new Date(), actions: [ { group: 'default', @@ -59,8 +61,10 @@ test('calls the update function with proper parameters', async () => { alertsClient.update.mockResolvedValueOnce(mockedResponse); const { payload, statusCode } = await server.inject(request); expect(statusCode).toBe(200); - const response = JSON.parse(payload); - expect(response).toEqual(mockedResponse); + const { createdAt, updatedAt, ...response } = JSON.parse(payload); + expect({ createdAt: new Date(createdAt), updatedAt: new Date(updatedAt), ...response }).toEqual( + mockedResponse + ); expect(alertsClient.update).toHaveBeenCalledTimes(1); expect(alertsClient.update.mock.calls[0]).toMatchInlineSnapshot(` Array [ diff --git a/x-pack/legacy/plugins/alerting/server/shim.ts b/x-pack/legacy/plugins/alerting/server/shim.ts index a3d6b778b3a1f..ccc10f929e123 100644 --- a/x-pack/legacy/plugins/alerting/server/shim.ts +++ b/x-pack/legacy/plugins/alerting/server/shim.ts @@ -7,7 +7,11 @@ import Hapi from 'hapi'; import { Legacy } from 'kibana'; import { LegacySpacesPlugin as SpacesPluginStartContract } from '../../spaces'; -import { TaskManager } from '../../task_manager'; +import { + TaskManagerStartContract, + TaskManagerSetupContract, +} from '../../../../plugins/task_manager/server'; +import { getTaskManagerSetup, getTaskManagerStart } from '../../task_manager/server'; import { XPackMainPlugin } from '../../xpack_main/server/xpack_main'; import KbnServer from '../../../../../src/legacy/server/kbn_server'; import { @@ -31,7 +35,6 @@ import { LicensingPluginSetup } from '../../../../plugins/licensing/server'; // due to being marked as dependencies interface Plugins extends Hapi.PluginProperties { actions: ActionsPlugin; - task_manager: TaskManager; } export interface Server extends Legacy.Server { @@ -41,17 +44,9 @@ export interface Server extends Legacy.Server { /** * Shim what we're thinking setup and start contracts will look like */ -export type TaskManagerStartContract = Pick< - TaskManager, - 'schedule' | 'fetch' | 'remove' | 'runNow' ->; export type SecurityPluginSetupContract = Pick; export type SecurityPluginStartContract = Pick; export type XPackMainPluginSetupContract = Pick; -export type TaskManagerSetupContract = Pick< - TaskManager, - 'addMiddleware' | 'registerTaskDefinitions' ->; /** * New platform interfaces @@ -73,7 +68,7 @@ export interface AlertingCoreStart { } export interface AlertingPluginsSetup { security?: SecurityPluginSetupContract; - task_manager: TaskManagerSetupContract; + taskManager: TaskManagerSetupContract; actions: ActionsPluginSetupContract; xpack_main: XPackMainPluginSetupContract; encryptedSavedObjects: EncryptedSavedObjectsSetupContract; @@ -84,7 +79,7 @@ export interface AlertingPluginsStart { security?: SecurityPluginStartContract; spaces: () => SpacesPluginStartContract | undefined; encryptedSavedObjects: EncryptedSavedObjectsStartContract; - task_manager: TaskManagerStartContract; + taskManager: TaskManagerStartContract; } /** @@ -121,7 +116,7 @@ export function shim( const pluginsSetup: AlertingPluginsSetup = { security: newPlatform.setup.plugins.security as SecurityPluginSetupContract | undefined, - task_manager: server.plugins.task_manager, + taskManager: getTaskManagerSetup(server)!, actions: server.plugins.actions.setup, xpack_main: server.plugins.xpack_main, encryptedSavedObjects: newPlatform.setup.plugins @@ -137,7 +132,7 @@ export function shim( spaces: () => server.plugins.spaces, encryptedSavedObjects: newPlatform.start.plugins .encryptedSavedObjects as EncryptedSavedObjectsStartContract, - task_manager: server.plugins.task_manager, + taskManager: getTaskManagerStart(server)!, }; return { diff --git a/x-pack/legacy/plugins/alerting/server/lib/create_execution_handler.test.ts b/x-pack/legacy/plugins/alerting/server/task_runner/create_execution_handler.test.ts similarity index 100% rename from x-pack/legacy/plugins/alerting/server/lib/create_execution_handler.test.ts rename to x-pack/legacy/plugins/alerting/server/task_runner/create_execution_handler.test.ts diff --git a/x-pack/legacy/plugins/alerting/server/lib/create_execution_handler.ts b/x-pack/legacy/plugins/alerting/server/task_runner/create_execution_handler.ts similarity index 100% rename from x-pack/legacy/plugins/alerting/server/lib/create_execution_handler.ts rename to x-pack/legacy/plugins/alerting/server/task_runner/create_execution_handler.ts diff --git a/x-pack/legacy/plugins/alerting/server/lib/get_next_run_at.test.ts b/x-pack/legacy/plugins/alerting/server/task_runner/get_next_run_at.test.ts similarity index 100% rename from x-pack/legacy/plugins/alerting/server/lib/get_next_run_at.test.ts rename to x-pack/legacy/plugins/alerting/server/task_runner/get_next_run_at.test.ts diff --git a/x-pack/legacy/plugins/alerting/server/lib/get_next_run_at.ts b/x-pack/legacy/plugins/alerting/server/task_runner/get_next_run_at.ts similarity index 92% rename from x-pack/legacy/plugins/alerting/server/lib/get_next_run_at.ts rename to x-pack/legacy/plugins/alerting/server/task_runner/get_next_run_at.ts index f9867b5372908..cea4584e1f713 100644 --- a/x-pack/legacy/plugins/alerting/server/lib/get_next_run_at.ts +++ b/x-pack/legacy/plugins/alerting/server/task_runner/get_next_run_at.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { parseDuration } from './parse_duration'; +import { parseDuration } from '../lib'; import { IntervalSchedule } from '../types'; export function getNextRunAt(currentRunAt: Date, schedule: IntervalSchedule) { diff --git a/x-pack/legacy/plugins/alerting/server/task_runner/index.ts b/x-pack/legacy/plugins/alerting/server/task_runner/index.ts new file mode 100644 index 0000000000000..f5401fbd9cd74 --- /dev/null +++ b/x-pack/legacy/plugins/alerting/server/task_runner/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { TaskRunnerFactory } from './task_runner_factory'; diff --git a/x-pack/legacy/plugins/alerting/server/task_runner/task_runner.test.ts b/x-pack/legacy/plugins/alerting/server/task_runner/task_runner.test.ts new file mode 100644 index 0000000000000..394c13e1bd24f --- /dev/null +++ b/x-pack/legacy/plugins/alerting/server/task_runner/task_runner.test.ts @@ -0,0 +1,500 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import sinon from 'sinon'; +import { schema } from '@kbn/config-schema'; +import { AlertExecutorOptions } from '../types'; +import { ConcreteTaskInstance, TaskStatus } from '../../../../../plugins/task_manager/server'; +import { TaskRunnerContext } from './task_runner_factory'; +import { TaskRunner } from './task_runner'; +import { encryptedSavedObjectsMock } from '../../../../../plugins/encrypted_saved_objects/server/mocks'; +import { + savedObjectsClientMock, + loggingServiceMock, +} from '../../../../../../src/core/server/mocks'; + +const alertType = { + id: 'test', + name: 'My test alert', + actionGroups: ['default'], + executor: jest.fn(), +}; +let fakeTimer: sinon.SinonFakeTimers; + +describe('Task Runner', () => { + let mockedTaskInstance: ConcreteTaskInstance; + + beforeAll(() => { + fakeTimer = sinon.useFakeTimers(); + mockedTaskInstance = { + id: '', + attempts: 0, + status: TaskStatus.Running, + version: '123', + runAt: new Date(), + scheduledAt: new Date(), + startedAt: new Date(), + retryAt: new Date(Date.now() + 5 * 60 * 1000), + state: {}, + taskType: 'alerting:test', + params: { + alertId: '1', + }, + ownerId: null, + }; + }); + + afterAll(() => fakeTimer.restore()); + + const savedObjectsClient = savedObjectsClientMock.create(); + const encryptedSavedObjectsPlugin = encryptedSavedObjectsMock.createStart(); + const services = { + log: jest.fn(), + callCluster: jest.fn(), + savedObjectsClient, + }; + + const taskRunnerFactoryInitializerParams: jest.Mocked = { + getServices: jest.fn().mockReturnValue(services), + executeAction: jest.fn(), + encryptedSavedObjectsPlugin, + logger: loggingServiceMock.create().get(), + spaceIdToNamespace: jest.fn().mockReturnValue(undefined), + getBasePath: jest.fn().mockReturnValue(undefined), + }; + + const mockedAlertTypeSavedObject = { + id: '1', + type: 'alert', + attributes: { + enabled: true, + alertTypeId: '123', + schedule: { interval: '10s' }, + name: 'alert-name', + tags: ['alert-', '-tags'], + createdBy: 'alert-creator', + updatedBy: 'alert-updater', + mutedInstanceIds: [], + params: { + bar: true, + }, + actions: [ + { + group: 'default', + actionRef: 'action_0', + params: { + foo: true, + }, + }, + ], + }, + references: [ + { + name: 'action_0', + type: 'action', + id: '1', + }, + ], + }; + + beforeEach(() => { + jest.resetAllMocks(); + taskRunnerFactoryInitializerParams.getServices.mockReturnValue(services); + }); + + test('successfully executes the task', async () => { + const taskRunner = new TaskRunner( + alertType, + { + ...mockedTaskInstance, + state: { + ...mockedTaskInstance.state, + previousStartedAt: new Date(Date.now() - 5 * 60 * 1000).toISOString(), + }, + }, + taskRunnerFactoryInitializerParams + ); + savedObjectsClient.get.mockResolvedValueOnce(mockedAlertTypeSavedObject); + encryptedSavedObjectsPlugin.getDecryptedAsInternalUser.mockResolvedValueOnce({ + id: '1', + type: 'alert', + attributes: { + apiKey: Buffer.from('123:abc').toString('base64'), + }, + references: [], + }); + const runnerResult = await taskRunner.run(); + expect(runnerResult).toMatchInlineSnapshot(` + Object { + "runAt": 1970-01-01T00:00:10.000Z, + "state": Object { + "alertInstances": Object {}, + "alertTypeState": undefined, + "previousStartedAt": 1970-01-01T00:00:00.000Z, + }, + } + `); + expect(alertType.executor).toHaveBeenCalledTimes(1); + const call = alertType.executor.mock.calls[0][0]; + expect(call.params).toMatchInlineSnapshot(` + Object { + "bar": true, + } + `); + expect(call.startedAt).toMatchInlineSnapshot(`1970-01-01T00:00:00.000Z`); + expect(call.previousStartedAt).toMatchInlineSnapshot(`1969-12-31T23:55:00.000Z`); + expect(call.state).toMatchInlineSnapshot(`Object {}`); + expect(call.name).toBe('alert-name'); + expect(call.tags).toEqual(['alert-', '-tags']); + expect(call.createdBy).toBe('alert-creator'); + expect(call.updatedBy).toBe('alert-updater'); + expect(call.services.alertInstanceFactory).toBeTruthy(); + expect(call.services.callCluster).toBeTruthy(); + expect(call.services).toBeTruthy(); + }); + + test('executeAction is called per alert instance that is scheduled', async () => { + alertType.executor.mockImplementation( + ({ services: executorServices }: AlertExecutorOptions) => { + executorServices.alertInstanceFactory('1').scheduleActions('default'); + } + ); + const taskRunner = new TaskRunner( + alertType, + mockedTaskInstance, + taskRunnerFactoryInitializerParams + ); + savedObjectsClient.get.mockResolvedValueOnce(mockedAlertTypeSavedObject); + encryptedSavedObjectsPlugin.getDecryptedAsInternalUser.mockResolvedValueOnce({ + id: '1', + type: 'alert', + attributes: { + apiKey: Buffer.from('123:abc').toString('base64'), + }, + references: [], + }); + await taskRunner.run(); + expect(taskRunnerFactoryInitializerParams.executeAction).toHaveBeenCalledTimes(1); + expect(taskRunnerFactoryInitializerParams.executeAction.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Object { + "apiKey": "MTIzOmFiYw==", + "id": "1", + "params": Object { + "foo": true, + }, + "spaceId": undefined, + }, + ] + `); + }); + + test('persists alertInstances passed in from state, only if they are scheduled for execution', async () => { + alertType.executor.mockImplementation( + ({ services: executorServices }: AlertExecutorOptions) => { + executorServices.alertInstanceFactory('1').scheduleActions('default'); + } + ); + const taskRunner = new TaskRunner( + alertType, + { + ...mockedTaskInstance, + state: { + ...mockedTaskInstance.state, + alertInstances: { + '1': { meta: {}, state: { bar: false } }, + '2': { meta: {}, state: { bar: false } }, + }, + }, + }, + taskRunnerFactoryInitializerParams + ); + savedObjectsClient.get.mockResolvedValueOnce(mockedAlertTypeSavedObject); + encryptedSavedObjectsPlugin.getDecryptedAsInternalUser.mockResolvedValueOnce({ + id: '1', + type: 'alert', + attributes: { + apiKey: Buffer.from('123:abc').toString('base64'), + }, + references: [], + }); + const runnerResult = await taskRunner.run(); + expect(runnerResult.state.alertInstances).toMatchInlineSnapshot(` + Object { + "1": Object { + "meta": Object { + "lastScheduledActions": Object { + "date": 1970-01-01T00:00:00.000Z, + "group": "default", + }, + }, + "state": Object { + "bar": false, + }, + }, + } + `); + }); + + test('validates params before executing the alert type', async () => { + const taskRunner = new TaskRunner( + { + ...alertType, + validate: { + params: schema.object({ + param1: schema.string(), + }), + }, + }, + mockedTaskInstance, + taskRunnerFactoryInitializerParams + ); + savedObjectsClient.get.mockResolvedValueOnce(mockedAlertTypeSavedObject); + encryptedSavedObjectsPlugin.getDecryptedAsInternalUser.mockResolvedValueOnce({ + id: '1', + type: 'alert', + attributes: { + apiKey: Buffer.from('123:abc').toString('base64'), + }, + references: [], + }); + expect(await taskRunner.run()).toMatchInlineSnapshot(` + Object { + "runAt": 1970-01-01T00:00:10.000Z, + "state": Object { + "previousStartedAt": 1970-01-01T00:00:00.000Z, + }, + } + `); + expect(taskRunnerFactoryInitializerParams.logger.error).toHaveBeenCalledWith( + `Executing Alert \"1\" has resulted in Error: params invalid: [param1]: expected value of type [string] but got [undefined]` + ); + }); + + test('throws error if reference not found', async () => { + const taskRunner = new TaskRunner( + alertType, + mockedTaskInstance, + taskRunnerFactoryInitializerParams + ); + savedObjectsClient.get.mockResolvedValueOnce({ + ...mockedAlertTypeSavedObject, + references: [], + }); + encryptedSavedObjectsPlugin.getDecryptedAsInternalUser.mockResolvedValueOnce({ + id: '1', + type: 'alert', + attributes: { + apiKey: Buffer.from('123:abc').toString('base64'), + }, + references: [], + }); + expect(await taskRunner.run()).toMatchInlineSnapshot(` + Object { + "runAt": 1970-01-01T00:00:10.000Z, + "state": Object { + "previousStartedAt": 1970-01-01T00:00:00.000Z, + }, + } + `); + expect(taskRunnerFactoryInitializerParams.logger.error).toHaveBeenCalledWith( + `Executing Alert \"1\" has resulted in Error: Action reference \"action_0\" not found in alert id: 1` + ); + }); + + test('uses API key when provided', async () => { + const taskRunner = new TaskRunner( + alertType, + mockedTaskInstance, + taskRunnerFactoryInitializerParams + ); + savedObjectsClient.get.mockResolvedValueOnce(mockedAlertTypeSavedObject); + encryptedSavedObjectsPlugin.getDecryptedAsInternalUser.mockResolvedValueOnce({ + id: '1', + type: 'alert', + attributes: { + apiKey: Buffer.from('123:abc').toString('base64'), + }, + references: [], + }); + + await taskRunner.run(); + expect(taskRunnerFactoryInitializerParams.getServices).toHaveBeenCalledWith({ + getBasePath: expect.anything(), + headers: { + // base64 encoded "123:abc" + authorization: 'ApiKey MTIzOmFiYw==', + }, + path: '/', + route: { settings: {} }, + url: { + href: '/', + }, + raw: { + req: { + url: '/', + }, + }, + }); + }); + + test(`doesn't use API key when not provided`, async () => { + const taskRunner = new TaskRunner( + alertType, + mockedTaskInstance, + taskRunnerFactoryInitializerParams + ); + savedObjectsClient.get.mockResolvedValueOnce(mockedAlertTypeSavedObject); + encryptedSavedObjectsPlugin.getDecryptedAsInternalUser.mockResolvedValueOnce({ + id: '1', + type: 'alert', + attributes: {}, + references: [], + }); + + await taskRunner.run(); + + expect(taskRunnerFactoryInitializerParams.getServices).toHaveBeenCalledWith({ + getBasePath: expect.anything(), + headers: {}, + path: '/', + route: { settings: {} }, + url: { + href: '/', + }, + raw: { + req: { + url: '/', + }, + }, + }); + }); + + test('recovers gracefully when the AlertType executor throws an exception', async () => { + alertType.executor.mockImplementation( + ({ services: executorServices }: AlertExecutorOptions) => { + throw new Error('OMG'); + } + ); + + const taskRunner = new TaskRunner( + alertType, + mockedTaskInstance, + taskRunnerFactoryInitializerParams + ); + + savedObjectsClient.get.mockResolvedValueOnce(mockedAlertTypeSavedObject); + encryptedSavedObjectsPlugin.getDecryptedAsInternalUser.mockResolvedValueOnce({ + id: '1', + type: 'alert', + attributes: { + apiKey: Buffer.from('123:abc').toString('base64'), + }, + references: [], + }); + + const runnerResult = await taskRunner.run(); + + expect(runnerResult).toMatchInlineSnapshot(` + Object { + "runAt": 1970-01-01T00:00:10.000Z, + "state": Object { + "previousStartedAt": 1970-01-01T00:00:00.000Z, + }, + } + `); + }); + + test('recovers gracefully when the Alert Task Runner throws an exception when fetching the encrypted attributes', async () => { + encryptedSavedObjectsPlugin.getDecryptedAsInternalUser.mockImplementation(() => { + throw new Error('OMG'); + }); + + const taskRunner = new TaskRunner( + alertType, + mockedTaskInstance, + taskRunnerFactoryInitializerParams + ); + + savedObjectsClient.get.mockResolvedValueOnce(mockedAlertTypeSavedObject); + + const runnerResult = await taskRunner.run(); + + expect(runnerResult).toMatchInlineSnapshot(` + Object { + "runAt": 1970-01-01T00:05:00.000Z, + "state": Object { + "previousStartedAt": 1970-01-01T00:00:00.000Z, + }, + } + `); + }); + + test('recovers gracefully when the Alert Task Runner throws an exception when getting internal Services', async () => { + taskRunnerFactoryInitializerParams.getServices.mockImplementation(() => { + throw new Error('OMG'); + }); + + const taskRunner = new TaskRunner( + alertType, + mockedTaskInstance, + taskRunnerFactoryInitializerParams + ); + + savedObjectsClient.get.mockResolvedValueOnce(mockedAlertTypeSavedObject); + encryptedSavedObjectsPlugin.getDecryptedAsInternalUser.mockResolvedValueOnce({ + id: '1', + type: 'alert', + attributes: { + apiKey: Buffer.from('123:abc').toString('base64'), + }, + references: [], + }); + + const runnerResult = await taskRunner.run(); + + expect(runnerResult).toMatchInlineSnapshot(` + Object { + "runAt": 1970-01-01T00:05:00.000Z, + "state": Object { + "previousStartedAt": 1970-01-01T00:00:00.000Z, + }, + } + `); + }); + + test('recovers gracefully when the Alert Task Runner throws an exception when fetching attributes', async () => { + savedObjectsClient.get.mockImplementation(() => { + throw new Error('OMG'); + }); + + const taskRunner = new TaskRunner( + alertType, + mockedTaskInstance, + taskRunnerFactoryInitializerParams + ); + + encryptedSavedObjectsPlugin.getDecryptedAsInternalUser.mockResolvedValueOnce({ + id: '1', + type: 'alert', + attributes: { + apiKey: Buffer.from('123:abc').toString('base64'), + }, + references: [], + }); + + const runnerResult = await taskRunner.run(); + + expect(runnerResult).toMatchInlineSnapshot(` + Object { + "runAt": 1970-01-01T00:05:00.000Z, + "state": Object { + "previousStartedAt": 1970-01-01T00:00:00.000Z, + }, + } + `); + }); +}); diff --git a/x-pack/legacy/plugins/alerting/server/task_runner/task_runner.ts b/x-pack/legacy/plugins/alerting/server/task_runner/task_runner.ts new file mode 100644 index 0000000000000..0f643e3d3121c --- /dev/null +++ b/x-pack/legacy/plugins/alerting/server/task_runner/task_runner.ts @@ -0,0 +1,311 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { pick, mapValues, omit } from 'lodash'; +import { Logger } from '../../../../../../src/core/server'; +import { SavedObject } from '../../../../../../src/core/server'; +import { TaskRunnerContext } from './task_runner_factory'; +import { ConcreteTaskInstance } from '../../../../../plugins/task_manager/server'; +import { createExecutionHandler } from './create_execution_handler'; +import { AlertInstance, createAlertInstanceFactory } from '../alert_instance'; +import { getNextRunAt } from './get_next_run_at'; +import { validateAlertTypeParams } from '../lib'; +import { AlertType, RawAlert, IntervalSchedule, Services, State, AlertInfoParams } from '../types'; +import { promiseResult, map, Resultable, asOk, asErr, resolveErr } from '../lib/result_type'; + +type AlertInstances = Record; + +const FALLBACK_RETRY_INTERVAL: IntervalSchedule = { interval: '5m' }; + +interface AlertTaskRunResult { + state: State; + runAt: Date; +} + +export class TaskRunner { + private context: TaskRunnerContext; + private logger: Logger; + private taskInstance: ConcreteTaskInstance; + private alertType: AlertType; + + constructor( + alertType: AlertType, + taskInstance: ConcreteTaskInstance, + context: TaskRunnerContext + ) { + this.context = context; + this.logger = context.logger; + this.alertType = alertType; + this.taskInstance = taskInstance; + } + + async getApiKeyForAlertPermissions(alertId: string, spaceId: string) { + const namespace = this.context.spaceIdToNamespace(spaceId); + // Only fetch encrypted attributes here, we'll create a saved objects client + // scoped with the API key to fetch the remaining data. + const { + attributes: { apiKey }, + } = await this.context.encryptedSavedObjectsPlugin.getDecryptedAsInternalUser( + 'alert', + alertId, + { namespace } + ); + + return apiKey; + } + + async getServicesWithSpaceLevelPermissions(spaceId: string, apiKey: string | null) { + const requestHeaders: Record = {}; + + if (apiKey) { + requestHeaders.authorization = `ApiKey ${apiKey}`; + } + + const fakeRequest = { + headers: requestHeaders, + getBasePath: () => this.context.getBasePath(spaceId), + path: '/', + route: { settings: {} }, + url: { + href: '/', + }, + raw: { + req: { + url: '/', + }, + }, + }; + + return this.context.getServices(fakeRequest); + } + + getExecutionHandler( + alertId: string, + spaceId: string, + apiKey: string | null, + actions: RawAlert['actions'], + references: SavedObject['references'] + ) { + // Inject ids into actions + const actionsWithIds = actions.map(action => { + const actionReference = references.find(obj => obj.name === action.actionRef); + if (!actionReference) { + throw new Error(`Action reference "${action.actionRef}" not found in alert id: ${alertId}`); + } + return { + ...action, + id: actionReference.id, + }; + }); + + return createExecutionHandler({ + alertId, + logger: this.logger, + executeAction: this.context.executeAction, + apiKey, + actions: actionsWithIds, + spaceId, + alertType: this.alertType, + }); + } + + async executeAlertInstance( + alertInstanceId: string, + alertInstance: AlertInstance, + executionHandler: ReturnType + ) { + const { actionGroup, context, state } = alertInstance.getScheduledActionOptions()!; + alertInstance.updateLastScheduledActions(actionGroup); + alertInstance.unscheduleActions(); + return executionHandler({ actionGroup, context, state, alertInstanceId }); + } + + async executeAlertInstances( + services: Services, + alertInfoParams: AlertInfoParams, + executionHandler: ReturnType, + spaceId: string + ): Promise { + const { + params, + throttle, + muteAll, + mutedInstanceIds, + name, + tags, + createdBy, + updatedBy, + } = alertInfoParams; + const { + params: { alertId }, + state: { alertInstances: alertRawInstances = {}, alertTypeState = {}, previousStartedAt }, + } = this.taskInstance; + const namespace = this.context.spaceIdToNamespace(spaceId); + + const alertInstances = mapValues( + alertRawInstances, + alert => new AlertInstance(alert) + ); + + const updatedAlertTypeState = await this.alertType.executor({ + alertId, + services: { + ...services, + alertInstanceFactory: createAlertInstanceFactory(alertInstances), + }, + params, + state: alertTypeState, + startedAt: this.taskInstance.startedAt!, + previousStartedAt: previousStartedAt && new Date(previousStartedAt), + spaceId, + namespace, + name, + tags, + createdBy, + updatedBy, + }); + + // Cleanup alert instances that are no longer scheduling actions to avoid over populating the alertInstances object + const instancesWithScheduledActions = pick( + alertInstances, + alertInstance => alertInstance.hasScheduledActions() + ); + + if (!muteAll) { + const enabledAlertInstances = omit( + instancesWithScheduledActions, + ...mutedInstanceIds + ); + + await Promise.all( + Object.entries(enabledAlertInstances) + .filter( + ([, alertInstance]: [string, AlertInstance]) => !alertInstance.isThrottled(throttle) + ) + .map(([id, alertInstance]: [string, AlertInstance]) => + this.executeAlertInstance(id, alertInstance, executionHandler) + ) + ); + } + + return { + alertTypeState: updatedAlertTypeState, + alertInstances: instancesWithScheduledActions, + }; + } + + async validateAndExecuteAlert( + services: Services, + apiKey: string | null, + attributes: RawAlert, + references: SavedObject['references'] + ) { + const { + params: { alertId, spaceId }, + } = this.taskInstance; + + // Validate + const params = validateAlertTypeParams(this.alertType, attributes.params); + const executionHandler = this.getExecutionHandler( + alertId, + spaceId, + apiKey, + attributes.actions, + references + ); + return this.executeAlertInstances( + services, + { ...attributes, params }, + executionHandler, + spaceId + ); + } + + async loadAlertAttributesAndRun(): Promise> { + const { + params: { alertId, spaceId }, + } = this.taskInstance; + + const apiKey = await this.getApiKeyForAlertPermissions(alertId, spaceId); + const services = await this.getServicesWithSpaceLevelPermissions(spaceId, apiKey); + + // Ensure API key is still valid and user has access + const { attributes, references } = await services.savedObjectsClient.get( + 'alert', + alertId + ); + + return { + state: await promiseResult( + this.validateAndExecuteAlert(services, apiKey, attributes, references) + ), + runAt: asOk( + getNextRunAt( + new Date(this.taskInstance.startedAt!), + // we do not currently have a good way of returning the type + // from SavedObjectsClient, and as we currenrtly require a schedule + // and we only support `interval`, we can cast this safely + attributes.schedule as IntervalSchedule + ) + ), + }; + } + + async run(): Promise { + const { + params: { alertId }, + startedAt: previousStartedAt, + state: originalState, + } = this.taskInstance; + + const { state, runAt } = await errorAsAlertTaskRunResult(this.loadAlertAttributesAndRun()); + + return { + state: map( + state, + (stateUpdates: State) => { + return { + ...stateUpdates, + previousStartedAt, + }; + }, + (err: Error) => { + this.logger.error(`Executing Alert "${alertId}" has resulted in Error: ${err.message}`); + return { + ...originalState, + previousStartedAt, + }; + } + ), + runAt: resolveErr(runAt, () => + getNextRunAt( + new Date(), + // if we fail at this point we wish to recover but don't have access to the Alert's + // attributes, so we'll use a default interval to prevent the underlying task from + // falling into a failed state + FALLBACK_RETRY_INTERVAL + ) + ), + }; + } +} + +/** + * If an error is thrown, wrap it in an AlertTaskRunResult + * so that we can treat each field independantly + */ +async function errorAsAlertTaskRunResult( + future: Promise> +): Promise> { + try { + return await future; + } catch (e) { + return { + state: asErr(e), + runAt: asErr(e), + }; + } +} diff --git a/x-pack/legacy/plugins/alerting/server/task_runner/task_runner_factory.test.ts b/x-pack/legacy/plugins/alerting/server/task_runner/task_runner_factory.test.ts new file mode 100644 index 0000000000000..543b9e7d32e12 --- /dev/null +++ b/x-pack/legacy/plugins/alerting/server/task_runner/task_runner_factory.test.ts @@ -0,0 +1,87 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import sinon from 'sinon'; +import { ConcreteTaskInstance, TaskStatus } from '../../../../../plugins/task_manager/server'; +import { TaskRunnerContext, TaskRunnerFactory } from './task_runner_factory'; +import { encryptedSavedObjectsMock } from '../../../../../plugins/encrypted_saved_objects/server/mocks'; +import { + savedObjectsClientMock, + loggingServiceMock, +} from '../../../../../../src/core/server/mocks'; + +const alertType = { + id: 'test', + name: 'My test alert', + actionGroups: ['default'], + executor: jest.fn(), +}; +let fakeTimer: sinon.SinonFakeTimers; + +describe('Task Runner Factory', () => { + let mockedTaskInstance: ConcreteTaskInstance; + + beforeAll(() => { + fakeTimer = sinon.useFakeTimers(); + mockedTaskInstance = { + id: '', + attempts: 0, + status: TaskStatus.Running, + version: '123', + runAt: new Date(), + scheduledAt: new Date(), + startedAt: new Date(), + retryAt: new Date(Date.now() + 5 * 60 * 1000), + state: { + startedAt: new Date(Date.now() - 5 * 60 * 1000), + }, + taskType: 'alerting:test', + params: { + alertId: '1', + }, + ownerId: null, + }; + }); + + afterAll(() => fakeTimer.restore()); + + const savedObjectsClient = savedObjectsClientMock.create(); + const encryptedSavedObjectsPlugin = encryptedSavedObjectsMock.createStart(); + const services = { + log: jest.fn(), + callCluster: jest.fn(), + savedObjectsClient, + }; + + const taskRunnerFactoryInitializerParams: jest.Mocked = { + getServices: jest.fn().mockReturnValue(services), + executeAction: jest.fn(), + encryptedSavedObjectsPlugin, + logger: loggingServiceMock.create().get(), + spaceIdToNamespace: jest.fn().mockReturnValue(undefined), + getBasePath: jest.fn().mockReturnValue(undefined), + }; + + beforeEach(() => { + jest.resetAllMocks(); + taskRunnerFactoryInitializerParams.getServices.mockReturnValue(services); + }); + + test(`throws an error if factory isn't initialized`, () => { + const factory = new TaskRunnerFactory(); + expect(() => + factory.create(alertType, { taskInstance: mockedTaskInstance }) + ).toThrowErrorMatchingInlineSnapshot(`"TaskRunnerFactory not initialized"`); + }); + + test(`throws an error if factory is already initialized`, () => { + const factory = new TaskRunnerFactory(); + factory.initialize(taskRunnerFactoryInitializerParams); + expect(() => + factory.initialize(taskRunnerFactoryInitializerParams) + ).toThrowErrorMatchingInlineSnapshot(`"TaskRunnerFactory already initialized"`); + }); +}); diff --git a/x-pack/legacy/plugins/alerting/server/task_runner/task_runner_factory.ts b/x-pack/legacy/plugins/alerting/server/task_runner/task_runner_factory.ts new file mode 100644 index 0000000000000..7178fa4f01282 --- /dev/null +++ b/x-pack/legacy/plugins/alerting/server/task_runner/task_runner_factory.ts @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { Logger } from '../../../../../../src/core/server'; +import { RunContext } from '../../../../../plugins/task_manager/server'; +import { PluginStartContract as EncryptedSavedObjectsStartContract } from '../../../../../plugins/encrypted_saved_objects/server'; +import { PluginStartContract as ActionsPluginStartContract } from '../../../actions'; +import { + AlertType, + GetBasePathFunction, + GetServicesFunction, + SpaceIdToNamespaceFunction, +} from '../types'; +import { TaskRunner } from './task_runner'; + +export interface TaskRunnerContext { + logger: Logger; + getServices: GetServicesFunction; + executeAction: ActionsPluginStartContract['execute']; + encryptedSavedObjectsPlugin: EncryptedSavedObjectsStartContract; + spaceIdToNamespace: SpaceIdToNamespaceFunction; + getBasePath: GetBasePathFunction; +} + +export class TaskRunnerFactory { + private isInitialized = false; + private taskRunnerContext?: TaskRunnerContext; + + public initialize(taskRunnerContext: TaskRunnerContext) { + if (this.isInitialized) { + throw new Error('TaskRunnerFactory already initialized'); + } + this.isInitialized = true; + this.taskRunnerContext = taskRunnerContext; + } + + public create(alertType: AlertType, { taskInstance }: RunContext) { + if (!this.isInitialized) { + throw new Error('TaskRunnerFactory not initialized'); + } + + return new TaskRunner(alertType, taskInstance, this.taskRunnerContext!); + } +} diff --git a/x-pack/legacy/plugins/alerting/server/lib/transform_action_params.test.ts b/x-pack/legacy/plugins/alerting/server/task_runner/transform_action_params.test.ts similarity index 100% rename from x-pack/legacy/plugins/alerting/server/lib/transform_action_params.test.ts rename to x-pack/legacy/plugins/alerting/server/task_runner/transform_action_params.test.ts diff --git a/x-pack/legacy/plugins/alerting/server/lib/transform_action_params.ts b/x-pack/legacy/plugins/alerting/server/task_runner/transform_action_params.ts similarity index 100% rename from x-pack/legacy/plugins/alerting/server/lib/transform_action_params.ts rename to x-pack/legacy/plugins/alerting/server/task_runner/transform_action_params.ts diff --git a/x-pack/legacy/plugins/alerting/server/types.ts b/x-pack/legacy/plugins/alerting/server/types.ts index 7c2f3dcc918dc..def86cd46e590 100644 --- a/x-pack/legacy/plugins/alerting/server/types.ts +++ b/x-pack/legacy/plugins/alerting/server/types.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { AlertInstance } from './lib'; +import { AlertInstance } from './alert_instance'; import { AlertTypeRegistry as OrigAlertTypeRegistry } from './alert_type_registry'; import { PluginSetupContract, PluginStartContract } from './plugin'; import { SavedObjectAttributes, SavedObjectsClientContract } from '../../../../../src/core/server'; @@ -32,6 +32,12 @@ export interface AlertExecutorOptions { services: AlertServices; params: Record; state: State; + spaceId: string; + namespace?: string; + name: string; + tags: string[]; + createdBy: string | null; + updatedBy: string | null; } export interface AlertType { @@ -65,6 +71,7 @@ export interface IntervalSchedule extends SavedObjectAttributes { } export interface Alert { + id: string; enabled: boolean; name: string; tags: string[]; @@ -76,6 +83,8 @@ export interface Alert { scheduledTaskId?: string; createdBy: string | null; updatedBy: string | null; + createdAt: Date; + updatedAt: Date; apiKey: string | null; apiKeyOwner: string | null; throttle: string | null; @@ -83,6 +92,8 @@ export interface Alert { mutedInstanceIds: string[]; } +export type PartialAlert = Pick & Partial>; + export interface RawAlert extends SavedObjectAttributes { enabled: boolean; name: string; @@ -95,6 +106,7 @@ export interface RawAlert extends SavedObjectAttributes { scheduledTaskId?: string; createdBy: string | null; updatedBy: string | null; + createdAt: string; apiKey: string | null; apiKeyOwner: string | null; throttle: string | null; @@ -102,6 +114,18 @@ export interface RawAlert extends SavedObjectAttributes { mutedInstanceIds: string[]; } +export type AlertInfoParams = Pick< + RawAlert, + | 'params' + | 'throttle' + | 'muteAll' + | 'mutedInstanceIds' + | 'name' + | 'tags' + | 'createdBy' + | 'updatedBy' +>; + export interface AlertingPlugin { setup: PluginSetupContract; start: PluginStartContract; diff --git a/x-pack/legacy/plugins/apm/common/__snapshots__/elasticsearch_fieldnames.test.ts.snap b/x-pack/legacy/plugins/apm/common/__snapshots__/elasticsearch_fieldnames.test.ts.snap index e345ca3552e5a..8f87b3473b2e4 100644 --- a/x-pack/legacy/plugins/apm/common/__snapshots__/elasticsearch_fieldnames.test.ts.snap +++ b/x-pack/legacy/plugins/apm/common/__snapshots__/elasticsearch_fieldnames.test.ts.snap @@ -4,6 +4,8 @@ exports[`Error CLIENT_GEO_COUNTRY_ISO_CODE 1`] = `undefined`; exports[`Error CONTAINER_ID 1`] = `undefined`; +exports[`Error DESTINATION_ADDRESS 1`] = `undefined`; + exports[`Error ERROR_CULPRIT 1`] = `"handleOopsie"`; exports[`Error ERROR_EXC_HANDLED 1`] = `undefined`; @@ -112,6 +114,8 @@ exports[`Span CLIENT_GEO_COUNTRY_ISO_CODE 1`] = `undefined`; exports[`Span CONTAINER_ID 1`] = `undefined`; +exports[`Span DESTINATION_ADDRESS 1`] = `undefined`; + exports[`Span ERROR_CULPRIT 1`] = `undefined`; exports[`Span ERROR_EXC_HANDLED 1`] = `undefined`; @@ -220,6 +224,8 @@ exports[`Transaction CLIENT_GEO_COUNTRY_ISO_CODE 1`] = `undefined`; exports[`Transaction CONTAINER_ID 1`] = `"container1234567890abcdef"`; +exports[`Transaction DESTINATION_ADDRESS 1`] = `undefined`; + exports[`Transaction ERROR_CULPRIT 1`] = `undefined`; exports[`Transaction ERROR_EXC_HANDLED 1`] = `undefined`; diff --git a/x-pack/legacy/plugins/apm/common/elasticsearch_fieldnames.ts b/x-pack/legacy/plugins/apm/common/elasticsearch_fieldnames.ts index 0d7ff3114e73f..ce2db4964a412 100644 --- a/x-pack/legacy/plugins/apm/common/elasticsearch_fieldnames.ts +++ b/x-pack/legacy/plugins/apm/common/elasticsearch_fieldnames.ts @@ -14,6 +14,8 @@ export const HTTP_REQUEST_METHOD = 'http.request.method'; export const USER_ID = 'user.id'; export const USER_AGENT_NAME = 'user_agent.name'; +export const DESTINATION_ADDRESS = 'destination.address'; + export const OBSERVER_VERSION_MAJOR = 'observer.version_major'; export const OBSERVER_LISTENING = 'observer.listening'; export const PROCESSOR_EVENT = 'processor.event'; diff --git a/x-pack/legacy/plugins/apm/common/service_map.ts b/x-pack/legacy/plugins/apm/common/service_map.ts new file mode 100644 index 0000000000000..fbaa489c45039 --- /dev/null +++ b/x-pack/legacy/plugins/apm/common/service_map.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export interface ServiceConnectionNode { + 'service.name': string; + 'service.environment': string | null; + 'agent.name': string; +} +export interface ExternalConnectionNode { + 'destination.address': string; + 'span.type': string; + 'span.subtype': string; +} + +export type ConnectionNode = ServiceConnectionNode | ExternalConnectionNode; + +export interface Connection { + source: ConnectionNode; + destination: ConnectionNode; +} diff --git a/x-pack/legacy/plugins/apm/index.ts b/x-pack/legacy/plugins/apm/index.ts index cf2cbd2507215..0934cb0019f44 100644 --- a/x-pack/legacy/plugins/apm/index.ts +++ b/x-pack/legacy/plugins/apm/index.ts @@ -71,7 +71,8 @@ export const apm: LegacyPluginInitializer = kibana => { autocreateApmIndexPattern: Joi.boolean().default(true), // service map - serviceMapEnabled: Joi.boolean().default(false) + serviceMapEnabled: Joi.boolean().default(false), + serviceMapInitialTimeRange: Joi.number().default(60 * 1000 * 60) // last 1 hour }).default(); }, diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Controls.tsx b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Controls.tsx index f7166bd0cec5c..5b7ddc2b450d6 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Controls.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Controls.tsx @@ -12,10 +12,11 @@ import { i18n } from '@kbn/i18n'; import { CytoscapeContext } from './Cytoscape'; import { FullscreenPanel } from './FullscreenPanel'; -const Container = styled('div')` +const ControlsContainer = styled('div')` left: ${theme.gutterTypes.gutterMedium}; position: absolute; top: ${theme.gutterTypes.gutterSmall}; + z-index: 1; /* The element containing the cytoscape canvas has z-index = 0. */ `; const Button = styled(EuiButtonIcon)` @@ -83,7 +84,7 @@ export function Controls() { }); return ( - + - + ); } diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Cytoscape.tsx b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Cytoscape.tsx index 238158c5bf224..bc020815cc9cb 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Cytoscape.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/Cytoscape.tsx @@ -73,6 +73,7 @@ export function Cytoscape({ cy.on('data', event => { // Add the "primary" class to the node if its id matches the serviceName. if (cy.nodes().length > 0 && serviceName) { + cy.nodes().removeClass('primary'); cy.getElementById(serviceName).addClass('primary'); } diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/LoadingOverlay.tsx b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/LoadingOverlay.tsx new file mode 100644 index 0000000000000..efafdbcecd41c --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/LoadingOverlay.tsx @@ -0,0 +1,66 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import theme from '@elastic/eui/dist/eui_theme_light.json'; +import React from 'react'; +import { EuiProgress, EuiText, EuiSpacer } from '@elastic/eui'; +import styled from 'styled-components'; +import { i18n } from '@kbn/i18n'; + +const Container = styled.div` + position: relative; +`; + +const Overlay = styled.div` + position: absolute; + top: 0; + z-index: 1; + display: flex; + flex-direction: column; + align-items: center; + width: 100%; + padding: ${theme.gutterTypes.gutterMedium}; +`; + +const ProgressBarContainer = styled.div` + width: 50%; + max-width: 600px; +`; + +interface Props { + children: React.ReactNode; + isLoading: boolean; + percentageLoaded: number; +} + +export const LoadingOverlay = ({ + children, + isLoading, + percentageLoaded +}: Props) => ( + + {isLoading && ( + + + + + + + {i18n.translate('xpack.apm.loadingServiceMap', { + defaultMessage: + 'Loading service map... This might take a short while.' + })} + + + )} + {children} + +); diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/cytoscapeOptions.ts b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/cytoscapeOptions.ts index 03ae9d0c287e5..d4e792ccf761b 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/cytoscapeOptions.ts +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/cytoscapeOptions.ts @@ -8,17 +8,13 @@ import theme from '@elastic/eui/dist/eui_theme_light.json'; import { icons, defaultIcon } from './icons'; const layout = { - animate: true, - animationEasing: theme.euiAnimSlightBounce as cytoscape.Css.TransitionTimingFunction, - animationDuration: parseInt(theme.euiAnimSpeedFast, 10), name: 'dagre', nodeDimensionsIncludeLabels: true, - rankDir: 'LR', - spacingFactor: 2 + rankDir: 'LR' }; function isDatabaseOrExternal(agentName: string) { - return agentName === 'database' || agentName === 'external'; + return !agentName; } const style: cytoscape.Stylesheet[] = [ @@ -47,7 +43,7 @@ const style: cytoscape.Stylesheet[] = [ 'font-family': 'Inter UI, Segoe UI, Helvetica, Arial, sans-serif', 'font-size': theme.euiFontSizeXS, height: theme.avatarSizing.l.size, - label: 'data(id)', + label: 'data(label)', 'min-zoomed-font-size': theme.euiSizeL, 'overlay-opacity': 0, shape: (el: cytoscape.NodeSingular) => @@ -76,7 +72,18 @@ const style: cytoscape.Stylesheet[] = [ // // @ts-ignore 'target-distance-from-node': theme.paddingSizes.xs, - width: 2 + width: 1, + 'source-arrow-shape': 'none' + } + }, + { + selector: 'edge[bidirectional]', + style: { + 'source-arrow-shape': 'triangle', + 'target-arrow-shape': 'triangle', + // @ts-ignore + 'source-distance-from-node': theme.paddingSizes.xs, + 'target-distance-from-node': theme.paddingSizes.xs } } ]; diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/get_cytoscape_elements.ts b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/get_cytoscape_elements.ts new file mode 100644 index 0000000000000..c9caa27af41c5 --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/get_cytoscape_elements.ts @@ -0,0 +1,158 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { ValuesType } from 'utility-types'; +import { sortBy, isEqual } from 'lodash'; +import { Connection, ConnectionNode } from '../../../../common/service_map'; +import { ServiceMapAPIResponse } from '../../../../server/lib/service_map/get_service_map'; +import { getAPMHref } from '../../shared/Links/apm/APMLink'; + +function getConnectionNodeId(node: ConnectionNode): string { + if ('destination.address' in node) { + // use a prefix to distinguish exernal destination ids from services + return `>${node['destination.address']}`; + } + return node['service.name']; +} + +function getConnectionId(connection: Connection) { + return `${getConnectionNodeId(connection.source)}~${getConnectionNodeId( + connection.destination + )}`; +} +export function getCytoscapeElements( + responses: ServiceMapAPIResponse[], + search: string +) { + const discoveredServices = responses.flatMap( + response => response.discoveredServices + ); + + const serviceNodes = responses + .flatMap(response => response.services) + .map(service => ({ + ...service, + id: service['service.name'] + })); + + // maps destination.address to service.name if possible + function getConnectionNode(node: ConnectionNode) { + let mappedNode: ConnectionNode | undefined; + + if ('destination.address' in node) { + mappedNode = discoveredServices.find(map => isEqual(map.from, node))?.to; + } + + if (!mappedNode) { + mappedNode = node; + } + + return { + ...mappedNode, + id: getConnectionNodeId(mappedNode) + }; + } + + // build connections with mapped nodes + const connections = responses + .flatMap(response => response.connections) + .map(connection => { + const source = getConnectionNode(connection.source); + const destination = getConnectionNode(connection.destination); + + return { + source, + destination, + id: getConnectionId({ source, destination }) + }; + }) + .filter(connection => connection.source.id !== connection.destination.id); + + const nodes = connections + .flatMap(connection => [connection.source, connection.destination]) + .concat(serviceNodes); + + type ConnectionWithId = ValuesType; + type ConnectionNodeWithId = ValuesType; + + const connectionsById = connections.reduce((connectionMap, connection) => { + return { + ...connectionMap, + [connection.id]: connection + }; + }, {} as Record); + + const nodesById = nodes.reduce((nodeMap, node) => { + return { + ...nodeMap, + [node.id]: node + }; + }, {} as Record); + + const cyNodes = (Object.values(nodesById) as ConnectionNodeWithId[]).map( + node => { + let data = {}; + + if ('service.name' in node) { + data = { + href: getAPMHref( + `/services/${node['service.name']}/service-map`, + search + ), + agentName: node['agent.name'] || node['agent.name'] + }; + } + + return { + group: 'nodes' as const, + data: { + id: node.id, + label: + 'service.name' in node + ? node['service.name'] + : node['destination.address'], + ...data + } + }; + } + ); + + // instead of adding connections in two directions, + // we add a `bidirectional` flag to use in styling + const dedupedConnections = (sortBy( + Object.values(connectionsById), + // make sure that order is stable + 'id' + ) as ConnectionWithId[]).reduce< + Array + >((prev, connection) => { + const reversedConnection = prev.find( + c => + c.destination.id === connection.source.id && + c.source.id === connection.destination.id + ); + + if (reversedConnection) { + reversedConnection.bidirectional = true; + return prev; + } + + return prev.concat(connection); + }, []); + + const cyEdges = dedupedConnections.map(connection => { + return { + group: 'edges' as const, + data: { + id: connection.id, + source: connection.source.id, + target: connection.destination.id, + bidirectional: connection.bidirectional ? true : undefined + } + }; + }, []); + + return [...cyNodes, ...cyEdges]; +} diff --git a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/index.tsx index 16a91116ae762..d3cc2b14e2c68 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/ServiceMap/index.tsx @@ -5,13 +5,30 @@ */ import theme from '@elastic/eui/dist/eui_theme_light.json'; -import React from 'react'; -import { useFetcher } from '../../../hooks/useFetcher'; +import React, { + useMemo, + useEffect, + useState, + useRef, + useCallback +} from 'react'; +import { find, isEqual } from 'lodash'; +import { i18n } from '@kbn/i18n'; +import { EuiButton } from '@elastic/eui'; +import { ElementDefinition } from 'cytoscape'; +import { toMountPoint } from '../../../../../../../../src/plugins/kibana_react/public'; +import { ServiceMapAPIResponse } from '../../../../server/lib/service_map/get_service_map'; import { useLicense } from '../../../hooks/useLicense'; import { useUrlParams } from '../../../hooks/useUrlParams'; import { Controls } from './Controls'; import { Cytoscape } from './Cytoscape'; import { PlatinumLicensePrompt } from './PlatinumLicensePrompt'; +import { useCallApmApi } from '../../../hooks/useCallApmApi'; +import { useDeepObjectIdentity } from '../../../hooks/useDeepObjectIdentity'; +import { useLocation } from '../../../hooks/useLocation'; +import { LoadingOverlay } from './LoadingOverlay'; +import { useApmPluginContext } from '../../../hooks/useApmPluginContext'; +import { getCytoscapeElements } from './get_cytoscape_elements'; interface ServiceMapProps { serviceName?: string; @@ -37,36 +54,159 @@ ${theme.euiColorLightShade}`, margin: `-${theme.gutterTypes.gutterLarge}` }; +const MAX_REQUESTS = 5; + export function ServiceMap({ serviceName }: ServiceMapProps) { - const { - urlParams: { start, end } - } = useUrlParams(); + const callApmApi = useCallApmApi(); + const license = useLicense(); + const { search } = useLocation(); + const { urlParams, uiFilters } = useUrlParams(); + const { notifications } = useApmPluginContext().core; + const params = useDeepObjectIdentity({ + start: urlParams.start, + end: urlParams.end, + environment: urlParams.environment, + serviceName, + uiFilters: { + ...uiFilters, + environment: undefined + } + }); + + const renderedElements = useRef([]); + const openToast = useRef(null); + + const [responses, setResponses] = useState([]); + const [isLoading, setIsLoading] = useState(true); + const [percentageLoaded, setPercentageLoaded] = useState(0); + const [, _setUnusedState] = useState(false); + + const elements = useMemo(() => getCytoscapeElements(responses, search), [ + responses, + search + ]); + + const forceUpdate = useCallback(() => _setUnusedState(value => !value), []); + + const getNext = useCallback( + async (input: { reset?: boolean; after?: string | undefined }) => { + const { start, end, uiFilters: strippedUiFilters, ...query } = params; + + if (input.reset) { + renderedElements.current = []; + setResponses([]); + } - const { data } = useFetcher( - callApmApi => { if (start && end) { - return callApmApi({ - pathname: '/api/apm/service-map', - params: { query: { start, end } } - }); + setIsLoading(true); + try { + const data = await callApmApi({ + pathname: '/api/apm/service-map', + params: { + query: { + ...query, + start, + end, + uiFilters: JSON.stringify(strippedUiFilters), + after: input.after + } + } + }); + setResponses(resp => resp.concat(data)); + setIsLoading(false); + + const shouldGetNext = + responses.length + 1 < MAX_REQUESTS && data.after; + + if (shouldGetNext) { + setPercentageLoaded(value => value + 30); // increase loading bar 30% + await getNext({ after: data.after }); + } + } catch (error) { + setIsLoading(false); + notifications.toasts.addError(error, { + title: i18n.translate('xpack.apm.errorServiceMapData', { + defaultMessage: `Error loading service connections` + }) + }); + } } }, - [start, end] + [callApmApi, params, responses.length, notifications.toasts] ); - const elements = Array.isArray(data) ? data : []; - const license = useLicense(); + useEffect(() => { + const loadServiceMaps = async () => { + setPercentageLoaded(5); + await getNext({ reset: true }); + setPercentageLoaded(100); + }; + + loadServiceMaps(); + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [params]); + + useEffect(() => { + if (renderedElements.current.length === 0) { + renderedElements.current = elements; + return; + } + + const newElements = elements.filter(element => { + return !find(renderedElements.current, el => isEqual(el, element)); + }); + + const updateMap = () => { + renderedElements.current = elements; + if (openToast.current) { + notifications.toasts.remove(openToast.current); + } + forceUpdate(); + }; + + if (newElements.length > 0 && percentageLoaded === 100) { + openToast.current = notifications.toasts.add({ + title: i18n.translate('xpack.apm.newServiceMapData', { + defaultMessage: `Newly discovered connections are available.` + }), + onClose: () => { + openToast.current = null; + }, + toastLifeTimeMs: 24 * 60 * 60 * 1000, + text: toMountPoint( + + {i18n.translate('xpack.apm.updateServiceMap', { + defaultMessage: 'Update map' + })} + + ) + }).id; + } + + return () => { + if (openToast.current) { + notifications.toasts.remove(openToast.current); + } + }; + + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [elements, percentageLoaded]); + const isValidPlatinumLicense = - license?.isActive && license?.type === 'platinum'; + license?.isActive && + (license?.type === 'platinum' || license?.type === 'trial'); return isValidPlatinumLicense ? ( - - - + + + + + ) : ( ); diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/ErrorCount.tsx b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/ErrorCount.tsx new file mode 100644 index 0000000000000..ff2cb69d011fa --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/ErrorCount.tsx @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiText, EuiTextColor } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; + +interface Props { + count: number; +} + +export const ErrorCount = ({ count }: Props) => ( + +

+ { + e.stopPropagation(); + }} + > + {i18n.translate('xpack.apm.transactionDetails.errorCount', { + defaultMessage: + '{errorCount, number} {errorCount, plural, one {Error} other {Errors}}', + values: { errorCount: count } + })} + +

+
+); diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/ErrorCountBadge.tsx b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/ErrorCountBadge.tsx deleted file mode 100644 index 4c3ec3ca9f308..0000000000000 --- a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/ErrorCountBadge.tsx +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { EuiBadge } from '@elastic/eui'; -import euiThemeLight from '@elastic/eui/dist/eui_theme_light.json'; -import React from 'react'; - -type Props = React.ComponentProps; - -export const ErrorCountBadge = ({ children, ...rest }: Props) => ( - - {children} - -); diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/MaybeViewTraceLink.tsx b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/MaybeViewTraceLink.tsx index 39e52be34a415..322ec7c422571 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/MaybeViewTraceLink.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/MaybeViewTraceLink.tsx @@ -25,8 +25,9 @@ export const MaybeViewTraceLink = ({ } ); + const { rootTransaction } = waterfall; // the traceroot cannot be found, so we cannot link to it - if (!waterfall.traceRoot) { + if (!rootTransaction) { return ( {viewFullTraceButtonLabel} diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/TransactionTabs.tsx b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/TransactionTabs.tsx index e5be12509e3c9..f8318b9ae97e6 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/TransactionTabs.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/TransactionTabs.tsx @@ -77,7 +77,6 @@ export function TransactionTabs({ {currentTab.key === timelineTab.key ? ( { + it('should sort the marks by time', () => { + const transaction: Transaction = { + transaction: { + marks: { + agent: { + domInteractive: 117, + timeToFirstByte: 10, + domComplete: 118 + } + } + } + } as any; + expect(getAgentMarks(transaction)).toEqual([ + { + id: 'timeToFirstByte', + offset: 10000, + type: 'agentMark', + verticalLine: true + }, + { + id: 'domInteractive', + offset: 117000, + type: 'agentMark', + verticalLine: true + }, + { + id: 'domComplete', + offset: 118000, + type: 'agentMark', + verticalLine: true + } + ]); + }); + + it('should return empty array if marks are missing', () => { + const transaction: Transaction = { + transaction: {} + } as any; + expect(getAgentMarks(transaction)).toEqual([]); + }); + + it('should return empty array if agent marks are missing', () => { + const transaction: Transaction = { + transaction: { marks: {} } + } as any; + expect(getAgentMarks(transaction)).toEqual([]); + }); +}); diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Marks/__test__/get_error_marks.test.ts b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Marks/__test__/get_error_marks.test.ts new file mode 100644 index 0000000000000..8fd8edd7f8a72 --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Marks/__test__/get_error_marks.test.ts @@ -0,0 +1,97 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IWaterfallItem } from '../../Waterfall/waterfall_helpers/waterfall_helpers'; +import { getErrorMarks } from '../get_error_marks'; + +describe('getErrorMarks', () => { + describe('returns empty array', () => { + it('when items are missing', () => { + expect(getErrorMarks([], {})).toEqual([]); + }); + it('when any error is available', () => { + const items = [ + { docType: 'span' }, + { docType: 'transaction' } + ] as IWaterfallItem[]; + expect(getErrorMarks(items, {})).toEqual([]); + }); + }); + + it('returns error marks', () => { + const items = [ + { + docType: 'error', + offset: 10, + skew: 5, + doc: { error: { id: 1 }, service: { name: 'opbeans-java' } } + } as unknown, + { docType: 'transaction' }, + { + docType: 'error', + offset: 50, + skew: 0, + doc: { error: { id: 2 }, service: { name: 'opbeans-node' } } + } as unknown + ] as IWaterfallItem[]; + expect( + getErrorMarks(items, { 'opbeans-java': 'red', 'opbeans-node': 'blue' }) + ).toEqual([ + { + type: 'errorMark', + offset: 15, + verticalLine: false, + id: 1, + error: { error: { id: 1 }, service: { name: 'opbeans-java' } }, + serviceColor: 'red' + }, + { + type: 'errorMark', + offset: 50, + verticalLine: false, + id: 2, + error: { error: { id: 2 }, service: { name: 'opbeans-node' } }, + serviceColor: 'blue' + } + ]); + }); + + it('returns error marks without service color', () => { + const items = [ + { + docType: 'error', + offset: 10, + skew: 5, + doc: { error: { id: 1 }, service: { name: 'opbeans-java' } } + } as unknown, + { docType: 'transaction' }, + { + docType: 'error', + offset: 50, + skew: 0, + doc: { error: { id: 2 }, service: { name: 'opbeans-node' } } + } as unknown + ] as IWaterfallItem[]; + expect(getErrorMarks(items, {})).toEqual([ + { + type: 'errorMark', + offset: 15, + verticalLine: false, + id: 1, + error: { error: { id: 1 }, service: { name: 'opbeans-java' } }, + serviceColor: undefined + }, + { + type: 'errorMark', + offset: 50, + verticalLine: false, + id: 2, + error: { error: { id: 2 }, service: { name: 'opbeans-node' } }, + serviceColor: undefined + } + ]); + }); +}); diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Marks/get_agent_marks.ts b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Marks/get_agent_marks.ts new file mode 100644 index 0000000000000..7798d716cb219 --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Marks/get_agent_marks.ts @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { sortBy } from 'lodash'; +import { Transaction } from '../../../../../../../typings/es_schemas/ui/Transaction'; +import { Mark } from '.'; + +// Extends Mark without adding new properties to it. +export interface AgentMark extends Mark { + type: 'agentMark'; +} + +export function getAgentMarks(transaction?: Transaction): AgentMark[] { + const agent = transaction?.transaction.marks?.agent; + if (!agent) { + return []; + } + + return sortBy( + Object.entries(agent).map(([name, ms]) => ({ + type: 'agentMark', + id: name, + offset: ms * 1000, + verticalLine: true + })), + 'offset' + ); +} diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Marks/get_error_marks.ts b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Marks/get_error_marks.ts new file mode 100644 index 0000000000000..f1f0163a49d10 --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Marks/get_error_marks.ts @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { isEmpty } from 'lodash'; +import { ErrorRaw } from '../../../../../../../typings/es_schemas/raw/ErrorRaw'; +import { + IWaterfallItem, + IWaterfallError, + IServiceColors +} from '../Waterfall/waterfall_helpers/waterfall_helpers'; +import { Mark } from '.'; + +export interface ErrorMark extends Mark { + type: 'errorMark'; + error: ErrorRaw; + serviceColor?: string; +} + +export const getErrorMarks = ( + items: IWaterfallItem[], + serviceColors: IServiceColors +): ErrorMark[] => { + if (isEmpty(items)) { + return []; + } + + return (items.filter( + item => item.docType === 'error' + ) as IWaterfallError[]).map(error => ({ + type: 'errorMark', + offset: error.offset + error.skew, + verticalLine: false, + id: error.doc.error.id, + error: error.doc, + serviceColor: serviceColors[error.doc.service.name] + })); +}; diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Marks/index.ts b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Marks/index.ts new file mode 100644 index 0000000000000..52f811f5c3969 --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Marks/index.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export interface Mark { + type: string; + offset: number; + verticalLine: boolean; + id: string; +} diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/ServiceLegends.tsx b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/ServiceLegends.tsx index e4cb4ff62b36c..4e6a0eaf45585 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/ServiceLegends.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/ServiceLegends.tsx @@ -10,7 +10,7 @@ import React from 'react'; import styled from 'styled-components'; import { px, unit } from '../../../../../style/variables'; // @ts-ignore -import Legend from '../../../../shared/charts/Legend'; +import { Legend } from '../../../../shared/charts/Legend'; import { IServiceColors } from './Waterfall/waterfall_helpers/waterfall_helpers'; const Legends = styled.div` diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/SpanFlyout/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/SpanFlyout/index.tsx index cc1f9dd529bce..4863d6519de07 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/SpanFlyout/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/SpanFlyout/index.tsx @@ -101,7 +101,7 @@ export function SpanFlyout({ const dbContext = span.span.db; const httpContext = span.span.http; const spanTypes = getSpanTypes(span); - const spanHttpStatusCode = httpContext?.response.status_code; + const spanHttpStatusCode = httpContext?.response?.status_code; const spanHttpUrl = httpContext?.url?.original; const spanHttpMethod = httpContext?.method; diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/TransactionFlyout/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/TransactionFlyout/index.tsx index 2020b8252035b..df95577c81eff 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/TransactionFlyout/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/TransactionFlyout/index.tsx @@ -27,8 +27,8 @@ import { DroppedSpansWarning } from './DroppedSpansWarning'; interface Props { onClose: () => void; transaction?: Transaction; - errorCount: number; - traceRootDuration?: number; + errorCount?: number; + rootTransactionDuration?: number; } function TransactionPropertiesTable({ @@ -49,8 +49,8 @@ function TransactionPropertiesTable({ export function TransactionFlyout({ transaction: transactionDoc, onClose, - errorCount, - traceRootDuration + errorCount = 0, + rootTransactionDuration }: Props) { if (!transactionDoc) { return null; @@ -84,7 +84,7 @@ export function TransactionFlyout({ diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/WaterfallFlyout.tsx b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/WaterfallFlyout.tsx new file mode 100644 index 0000000000000..426088f0bb36a --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/WaterfallFlyout.tsx @@ -0,0 +1,59 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { Location } from 'history'; +import React from 'react'; +import { SpanFlyout } from './SpanFlyout'; +import { TransactionFlyout } from './TransactionFlyout'; +import { IWaterfall } from './waterfall_helpers/waterfall_helpers'; + +interface Props { + waterfallItemId?: string; + waterfall: IWaterfall; + location: Location; + toggleFlyout: ({ location }: { location: Location }) => void; +} +export const WaterfallFlyout: React.FC = ({ + waterfallItemId, + waterfall, + location, + toggleFlyout +}) => { + const currentItem = waterfall.items.find(item => item.id === waterfallItemId); + + if (!currentItem) { + return null; + } + + switch (currentItem.docType) { + case 'span': + const parentTransaction = + currentItem.parent?.docType === 'transaction' + ? currentItem.parent?.doc + : undefined; + + return ( + toggleFlyout({ location })} + /> + ); + case 'transaction': + return ( + toggleFlyout({ location })} + rootTransactionDuration={ + waterfall.rootTransaction?.transaction.duration.us + } + errorCount={waterfall.errorsPerTransaction[currentItem.id]} + /> + ); + default: + return null; + } +}; diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/WaterfallItem.tsx b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/WaterfallItem.tsx index 8d4fab4aa8dd9..8a82547d717db 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/WaterfallItem.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/WaterfallItem.tsx @@ -13,12 +13,12 @@ import { i18n } from '@kbn/i18n'; import { isRumAgentName } from '../../../../../../../common/agent_name'; import { px, unit, units } from '../../../../../../style/variables'; import { asDuration } from '../../../../../../utils/formatters'; -import { ErrorCountBadge } from '../../ErrorCountBadge'; +import { ErrorCount } from '../../ErrorCount'; import { IWaterfallItem } from './waterfall_helpers/waterfall_helpers'; import { ErrorOverviewLink } from '../../../../../shared/Links/apm/ErrorOverviewLink'; import { TRACE_ID } from '../../../../../../../common/elasticsearch_fieldnames'; -type ItemType = 'transaction' | 'span'; +type ItemType = 'transaction' | 'span' | 'error'; interface IContainerStyleProps { type: ItemType; @@ -89,24 +89,29 @@ interface IWaterfallItemProps { } function PrefixIcon({ item }: { item: IWaterfallItem }) { - if (item.docType === 'span') { - // icon for database spans - const isDbType = item.span.span.type.startsWith('db'); - if (isDbType) { - return ; + switch (item.docType) { + case 'span': { + // icon for database spans + const isDbType = item.doc.span.type.startsWith('db'); + if (isDbType) { + return ; + } + + // omit icon for other spans + return null; } - - // omit icon for other spans - return null; - } - - // icon for RUM agent transactions - if (isRumAgentName(item.transaction.agent.name)) { - return ; + case 'transaction': { + // icon for RUM agent transactions + if (isRumAgentName(item.doc.agent.name)) { + return ; + } + + // icon for other transactions + return ; + } + default: + return null; } - - // icon for other transactions - return ; } interface SpanActionToolTipProps { @@ -117,11 +122,9 @@ const SpanActionToolTip: React.FC = ({ item, children }) => { - if (item && item.docType === 'span') { + if (item?.docType === 'span') { return ( - + <>{children} ); @@ -140,9 +143,8 @@ function Duration({ item }: { item: IWaterfallItem }) { function HttpStatusCode({ item }: { item: IWaterfallItem }) { // http status code for transactions of type 'request' const httpStatusCode = - item.docType === 'transaction' && - item.transaction.transaction.type === 'request' - ? item.transaction.transaction.result + item.docType === 'transaction' && item.doc.transaction.type === 'request' + ? item.doc.transaction.result : undefined; if (!httpStatusCode) { @@ -153,14 +155,18 @@ function HttpStatusCode({ item }: { item: IWaterfallItem }) { } function NameLabel({ item }: { item: IWaterfallItem }) { - if (item.docType === 'span') { - return {item.name}; + switch (item.docType) { + case 'span': + return {item.doc.span.name}; + case 'transaction': + return ( + +
{item.doc.transaction.name}
+
+ ); + default: + return null; } - return ( - -
{item.name}
-
- ); } export function WaterfallItem({ @@ -210,24 +216,17 @@ export function WaterfallItem({ {errorCount > 0 && item.docType === 'transaction' ? ( - { - event.stopPropagation(); - }} - onClickAriaLabel={tooltipContent} - > - {errorCount} - + ) : null} diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/index.tsx index d53b4077d9759..b48fc1cf7ca27 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/index.tsx @@ -4,31 +4,26 @@ * you may not use this file except in compliance with the Elastic License. */ +import { EuiCallOut } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import { Location } from 'history'; -import React, { Component } from 'react'; +import React from 'react'; // @ts-ignore import { StickyContainer } from 'react-sticky'; import styled from 'styled-components'; -import { EuiCallOut } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; -import { IUrlParams } from '../../../../../../context/UrlParamsContext/types'; +import { px } from '../../../../../../style/variables'; +import { history } from '../../../../../../utils/history'; // @ts-ignore import Timeline from '../../../../../shared/charts/Timeline'; +import { fromQuery, toQuery } from '../../../../../shared/Links/url_helpers'; +import { getAgentMarks } from '../Marks/get_agent_marks'; +import { getErrorMarks } from '../Marks/get_error_marks'; +import { WaterfallFlyout } from './WaterfallFlyout'; +import { WaterfallItem } from './WaterfallItem'; import { - APMQueryParams, - fromQuery, - toQuery -} from '../../../../../shared/Links/url_helpers'; -import { history } from '../../../../../../utils/history'; -import { AgentMark } from '../get_agent_marks'; -import { SpanFlyout } from './SpanFlyout'; -import { TransactionFlyout } from './TransactionFlyout'; -import { - IServiceColors, IWaterfall, IWaterfallItem } from './waterfall_helpers/waterfall_helpers'; -import { WaterfallItem } from './WaterfallItem'; const Container = styled.div` transition: 0.1s padding ease; @@ -43,138 +38,105 @@ const TIMELINE_MARGINS = { bottom: 0 }; +const toggleFlyout = ({ + item, + location +}: { + item?: IWaterfallItem; + location: Location; +}) => { + history.replace({ + ...location, + search: fromQuery({ + ...toQuery(location.search), + flyoutDetailTab: undefined, + waterfallItemId: item?.id + }) + }); +}; + +const WaterfallItemsContainer = styled.div<{ + paddingTop: number; +}>` + padding-top: ${props => px(props.paddingTop)}; +`; + interface Props { - agentMarks: AgentMark[]; - urlParams: IUrlParams; + waterfallItemId?: string; waterfall: IWaterfall; location: Location; - serviceColors: IServiceColors; exceedsMax: boolean; } -export class Waterfall extends Component { - public onOpenFlyout = (item: IWaterfallItem) => { - this.setQueryParams({ - flyoutDetailTab: undefined, - waterfallItemId: String(item.id) - }); - }; +export const Waterfall: React.FC = ({ + waterfall, + exceedsMax, + waterfallItemId, + location +}) => { + const itemContainerHeight = 58; // TODO: This is a nasty way to calculate the height of the svg element. A better approach should be found + const waterfallHeight = itemContainerHeight * waterfall.items.length; - public onCloseFlyout = () => { - this.setQueryParams({ - flyoutDetailTab: undefined, - waterfallItemId: undefined - }); - }; + const { serviceColors, duration } = waterfall; - public renderWaterfallItem = (item: IWaterfallItem) => { - const { serviceColors, waterfall, urlParams }: Props = this.props; + const agentMarks = getAgentMarks(waterfall.entryTransaction); + const errorMarks = getErrorMarks(waterfall.items, serviceColors); + + const renderWaterfallItem = (item: IWaterfallItem) => { + if (item.docType === 'error') { + return null; + } const errorCount = item.docType === 'transaction' - ? waterfall.errorCountByTransactionId[item.transaction.transaction.id] + ? waterfall.errorsPerTransaction[item.doc.transaction.id] : 0; return ( this.onOpenFlyout(item)} + onClick={() => toggleFlyout({ item, location })} /> ); }; - public getFlyOut = () => { - const { waterfall, urlParams } = this.props; - - const currentItem = - urlParams.waterfallItemId && - waterfall.itemsById[urlParams.waterfallItemId]; - - if (!currentItem) { - return null; - } - - switch (currentItem.docType) { - case 'span': - const parentTransaction = waterfall.getTransactionById( - currentItem.parentId - ); - - return ( - - ); - case 'transaction': - return ( - - ); - default: - return null; - } - }; - - public render() { - const { waterfall, exceedsMax } = this.props; - const itemContainerHeight = 58; // TODO: This is a nasty way to calculate the height of the svg element. A better approach should be found - const waterfallHeight = itemContainerHeight * waterfall.orderedItems.length; - - return ( - - {exceedsMax ? ( - - ) : null} - - -
- {waterfall.orderedItems.map(this.renderWaterfallItem)} -
-
- - {this.getFlyOut()} -
- ); - } - - private setQueryParams(params: APMQueryParams) { - const { location } = this.props; - history.replace({ - ...location, - search: fromQuery({ - ...toQuery(location.search), - ...params - }) - }); - } -} + return ( + + {exceedsMax && ( + + )} + + + + {waterfall.items.map(renderWaterfallItem)} + + + + + + ); +}; diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/waterfall_helpers/__snapshots__/waterfall_helpers.test.ts.snap b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/waterfall_helpers/__snapshots__/waterfall_helpers.test.ts.snap index 6f61f62167638..ece396bc4cfc4 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/waterfall_helpers/__snapshots__/waterfall_helpers.test.ts.snap +++ b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/waterfall_helpers/__snapshots__/waterfall_helpers.test.ts.snap @@ -24,145 +24,44 @@ Object { "name": "GET /api", }, }, - "errorCountByTransactionId": Object { + "errorsCount": 1, + "errorsPerTransaction": Object { "myTransactionId1": 2, "myTransactionId2": 3, }, - "getTransactionById": [Function], - "itemsById": Object { - "mySpanIdA": Object { - "childIds": Array [ - "mySpanIdB", - "mySpanIdC", - ], - "docType": "span", - "duration": 6161, - "id": "mySpanIdA", - "name": "Api::ProductsController#index", - "offset": 40498, - "parentId": "myTransactionId2", - "serviceName": "opbeans-ruby", - "skew": 0, - "span": Object { - "parent": Object { - "id": "myTransactionId2", - }, + "items": Array [ + Object { + "doc": Object { "processor": Object { - "event": "span", + "event": "transaction", }, "service": Object { - "name": "opbeans-ruby", - }, - "span": Object { - "duration": Object { - "us": 6161, - }, - "id": "mySpanIdA", - "name": "Api::ProductsController#index", + "name": "opbeans-node", }, "timestamp": Object { - "us": 1549324795824504, + "us": 1549324795784006, }, "trace": Object { "id": "myTraceId", }, "transaction": Object { - "id": "myTransactionId2", - }, - }, - "timestamp": 1549324795824504, - }, - "mySpanIdB": Object { - "childIds": Array [], - "docType": "span", - "duration": 481, - "id": "mySpanIdB", - "name": "SELECT FROM products", - "offset": 41627, - "parentId": "mySpanIdA", - "serviceName": "opbeans-ruby", - "skew": 0, - "span": Object { - "parent": Object { - "id": "mySpanIdA", - }, - "processor": Object { - "event": "span", - }, - "service": Object { - "name": "opbeans-ruby", - }, - "span": Object { "duration": Object { - "us": 481, + "us": 49660, }, - "id": "mySpanIdB", - "name": "SELECT FROM products", - }, - "timestamp": Object { - "us": 1549324795825633, - }, - "trace": Object { - "id": "myTraceId", - }, - "transaction": Object { - "id": "myTransactionId2", + "id": "myTransactionId1", + "name": "GET /api", }, }, - "timestamp": 1549324795825633, - }, - "mySpanIdC": Object { - "childIds": Array [], - "docType": "span", - "duration": 532, - "id": "mySpanIdC", - "name": "SELECT FROM product", - "offset": 43899, - "parentId": "mySpanIdA", - "serviceName": "opbeans-ruby", + "docType": "transaction", + "duration": 49660, + "id": "myTransactionId1", + "offset": 0, + "parent": undefined, + "parentId": undefined, "skew": 0, - "span": Object { - "parent": Object { - "id": "mySpanIdA", - }, - "processor": Object { - "event": "span", - }, - "service": Object { - "name": "opbeans-ruby", - }, - "span": Object { - "duration": Object { - "us": 532, - }, - "id": "mySpanIdC", - "name": "SELECT FROM product", - }, - "timestamp": Object { - "us": 1549324795827905, - }, - "trace": Object { - "id": "myTraceId", - }, - "transaction": Object { - "id": "myTransactionId2", - }, - }, - "timestamp": 1549324795827905, }, - "mySpanIdD": Object { - "childIds": Array [ - "myTransactionId2", - ], - "docType": "span", - "duration": 47557, - "id": "mySpanIdD", - "name": "GET opbeans-ruby:3000/api/products", - "offset": 1754, - "parentId": "myTransactionId1", - "serviceName": "opbeans-node", - "skew": 0, - "span": Object { + Object { + "doc": Object { "parent": Object { "id": "myTransactionId1", }, @@ -189,59 +88,45 @@ Object { "id": "myTransactionId1", }, }, - "timestamp": 1549324795785760, - }, - "myTransactionId1": Object { - "childIds": Array [ - "mySpanIdD", - ], - "docType": "transaction", - "duration": 49660, - "errorCount": 2, - "id": "myTransactionId1", - "name": "GET /api", - "offset": 0, - "parentId": undefined, - "serviceName": "opbeans-node", - "skew": 0, - "timestamp": 1549324795784006, - "transaction": Object { - "processor": Object { - "event": "transaction", - }, - "service": Object { - "name": "opbeans-node", - }, - "timestamp": Object { - "us": 1549324795784006, - }, - "trace": Object { - "id": "myTraceId", - }, - "transaction": Object { - "duration": Object { - "us": 49660, + "docType": "span", + "duration": 47557, + "id": "mySpanIdD", + "offset": 1754, + "parent": Object { + "doc": Object { + "processor": Object { + "event": "transaction", + }, + "service": Object { + "name": "opbeans-node", + }, + "timestamp": Object { + "us": 1549324795784006, + }, + "trace": Object { + "id": "myTraceId", + }, + "transaction": Object { + "duration": Object { + "us": 49660, + }, + "id": "myTransactionId1", + "name": "GET /api", }, - "id": "myTransactionId1", - "name": "GET /api", }, + "docType": "transaction", + "duration": 49660, + "id": "myTransactionId1", + "offset": 0, + "parent": undefined, + "parentId": undefined, + "skew": 0, }, - }, - "myTransactionId2": Object { - "childIds": Array [ - "mySpanIdA", - ], - "docType": "transaction", - "duration": 8634, - "errorCount": 3, - "id": "myTransactionId2", - "name": "Api::ProductsController#index", - "offset": 39298, - "parentId": "mySpanIdD", - "serviceName": "opbeans-ruby", + "parentId": "myTransactionId1", "skew": 0, - "timestamp": 1549324795823304, - "transaction": Object { + }, + Object { + "doc": Object { "parent": Object { "id": "mySpanIdD", }, @@ -262,181 +147,403 @@ Object { "us": 8634, }, "id": "myTransactionId2", + "marks": Object { + "agent": Object { + "domComplete": 383, + "domInteractive": 382, + "timeToFirstByte": 14, + }, + }, "name": "Api::ProductsController#index", }, }, - }, - }, - "orderedItems": Array [ - Object { - "childIds": Array [ - "mySpanIdD", - ], "docType": "transaction", - "duration": 49660, - "errorCount": 2, - "id": "myTransactionId1", - "name": "GET /api", - "offset": 0, - "parentId": undefined, - "serviceName": "opbeans-node", - "skew": 0, - "timestamp": 1549324795784006, - "transaction": Object { - "processor": Object { - "event": "transaction", - }, - "service": Object { - "name": "opbeans-node", - }, - "timestamp": Object { - "us": 1549324795784006, - }, - "trace": Object { - "id": "myTraceId", + "duration": 8634, + "id": "myTransactionId2", + "offset": 39298, + "parent": Object { + "doc": Object { + "parent": Object { + "id": "myTransactionId1", + }, + "processor": Object { + "event": "span", + }, + "service": Object { + "name": "opbeans-node", + }, + "span": Object { + "duration": Object { + "us": 47557, + }, + "id": "mySpanIdD", + "name": "GET opbeans-ruby:3000/api/products", + }, + "timestamp": Object { + "us": 1549324795785760, + }, + "trace": Object { + "id": "myTraceId", + }, + "transaction": Object { + "id": "myTransactionId1", + }, }, - "transaction": Object { - "duration": Object { - "us": 49660, + "docType": "span", + "duration": 47557, + "id": "mySpanIdD", + "offset": 1754, + "parent": Object { + "doc": Object { + "processor": Object { + "event": "transaction", + }, + "service": Object { + "name": "opbeans-node", + }, + "timestamp": Object { + "us": 1549324795784006, + }, + "trace": Object { + "id": "myTraceId", + }, + "transaction": Object { + "duration": Object { + "us": 49660, + }, + "id": "myTransactionId1", + "name": "GET /api", + }, }, + "docType": "transaction", + "duration": 49660, "id": "myTransactionId1", - "name": "GET /api", + "offset": 0, + "parent": undefined, + "parentId": undefined, + "skew": 0, }, + "parentId": "myTransactionId1", + "skew": 0, }, + "parentId": "mySpanIdD", + "skew": 0, }, Object { - "childIds": Array [ - "myTransactionId2", - ], - "docType": "span", - "duration": 47557, - "id": "mySpanIdD", - "name": "GET opbeans-ruby:3000/api/products", - "offset": 1754, - "parentId": "myTransactionId1", - "serviceName": "opbeans-node", - "skew": 0, - "span": Object { + "doc": Object { "parent": Object { - "id": "myTransactionId1", + "id": "myTransactionId2", }, "processor": Object { "event": "span", }, "service": Object { - "name": "opbeans-node", + "name": "opbeans-ruby", }, "span": Object { "duration": Object { - "us": 47557, + "us": 6161, }, - "id": "mySpanIdD", - "name": "GET opbeans-ruby:3000/api/products", + "id": "mySpanIdA", + "name": "Api::ProductsController#index", }, "timestamp": Object { - "us": 1549324795785760, + "us": 1549324795824504, }, "trace": Object { "id": "myTraceId", }, "transaction": Object { - "id": "myTransactionId1", + "id": "myTransactionId2", + }, + }, + "docType": "span", + "duration": 6161, + "id": "mySpanIdA", + "offset": 40498, + "parent": Object { + "doc": Object { + "parent": Object { + "id": "mySpanIdD", + }, + "processor": Object { + "event": "transaction", + }, + "service": Object { + "name": "opbeans-ruby", + }, + "timestamp": Object { + "us": 1549324795823304, + }, + "trace": Object { + "id": "myTraceId", + }, + "transaction": Object { + "duration": Object { + "us": 8634, + }, + "id": "myTransactionId2", + "marks": Object { + "agent": Object { + "domComplete": 383, + "domInteractive": 382, + "timeToFirstByte": 14, + }, + }, + "name": "Api::ProductsController#index", + }, }, + "docType": "transaction", + "duration": 8634, + "id": "myTransactionId2", + "offset": 39298, + "parent": Object { + "doc": Object { + "parent": Object { + "id": "myTransactionId1", + }, + "processor": Object { + "event": "span", + }, + "service": Object { + "name": "opbeans-node", + }, + "span": Object { + "duration": Object { + "us": 47557, + }, + "id": "mySpanIdD", + "name": "GET opbeans-ruby:3000/api/products", + }, + "timestamp": Object { + "us": 1549324795785760, + }, + "trace": Object { + "id": "myTraceId", + }, + "transaction": Object { + "id": "myTransactionId1", + }, + }, + "docType": "span", + "duration": 47557, + "id": "mySpanIdD", + "offset": 1754, + "parent": Object { + "doc": Object { + "processor": Object { + "event": "transaction", + }, + "service": Object { + "name": "opbeans-node", + }, + "timestamp": Object { + "us": 1549324795784006, + }, + "trace": Object { + "id": "myTraceId", + }, + "transaction": Object { + "duration": Object { + "us": 49660, + }, + "id": "myTransactionId1", + "name": "GET /api", + }, + }, + "docType": "transaction", + "duration": 49660, + "id": "myTransactionId1", + "offset": 0, + "parent": undefined, + "parentId": undefined, + "skew": 0, + }, + "parentId": "myTransactionId1", + "skew": 0, + }, + "parentId": "mySpanIdD", + "skew": 0, }, - "timestamp": 1549324795785760, + "parentId": "myTransactionId2", + "skew": 0, }, Object { - "childIds": Array [ - "mySpanIdA", - ], - "docType": "transaction", - "duration": 8634, - "errorCount": 3, - "id": "myTransactionId2", - "name": "Api::ProductsController#index", - "offset": 39298, - "parentId": "mySpanIdD", - "serviceName": "opbeans-ruby", - "skew": 0, - "timestamp": 1549324795823304, - "transaction": Object { + "doc": Object { "parent": Object { - "id": "mySpanIdD", + "id": "mySpanIdA", }, "processor": Object { - "event": "transaction", + "event": "span", }, "service": Object { "name": "opbeans-ruby", }, + "span": Object { + "duration": Object { + "us": 481, + }, + "id": "mySpanIdB", + "name": "SELECT FROM products", + }, "timestamp": Object { - "us": 1549324795823304, + "us": 1549324795825633, }, "trace": Object { "id": "myTraceId", }, "transaction": Object { - "duration": Object { - "us": 8634, - }, "id": "myTransactionId2", - "name": "Api::ProductsController#index", }, }, - }, - Object { - "childIds": Array [ - "mySpanIdB", - "mySpanIdC", - ], "docType": "span", - "duration": 6161, - "id": "mySpanIdA", - "name": "Api::ProductsController#index", - "offset": 40498, - "parentId": "myTransactionId2", - "serviceName": "opbeans-ruby", - "skew": 0, - "span": Object { - "parent": Object { - "id": "myTransactionId2", - }, - "processor": Object { - "event": "span", - }, - "service": Object { - "name": "opbeans-ruby", - }, - "span": Object { - "duration": Object { - "us": 6161, + "duration": 481, + "id": "mySpanIdB", + "offset": 41627, + "parent": Object { + "doc": Object { + "parent": Object { + "id": "myTransactionId2", + }, + "processor": Object { + "event": "span", + }, + "service": Object { + "name": "opbeans-ruby", + }, + "span": Object { + "duration": Object { + "us": 6161, + }, + "id": "mySpanIdA", + "name": "Api::ProductsController#index", + }, + "timestamp": Object { + "us": 1549324795824504, + }, + "trace": Object { + "id": "myTraceId", + }, + "transaction": Object { + "id": "myTransactionId2", }, - "id": "mySpanIdA", - "name": "Api::ProductsController#index", - }, - "timestamp": Object { - "us": 1549324795824504, - }, - "trace": Object { - "id": "myTraceId", }, - "transaction": Object { + "docType": "span", + "duration": 6161, + "id": "mySpanIdA", + "offset": 40498, + "parent": Object { + "doc": Object { + "parent": Object { + "id": "mySpanIdD", + }, + "processor": Object { + "event": "transaction", + }, + "service": Object { + "name": "opbeans-ruby", + }, + "timestamp": Object { + "us": 1549324795823304, + }, + "trace": Object { + "id": "myTraceId", + }, + "transaction": Object { + "duration": Object { + "us": 8634, + }, + "id": "myTransactionId2", + "marks": Object { + "agent": Object { + "domComplete": 383, + "domInteractive": 382, + "timeToFirstByte": 14, + }, + }, + "name": "Api::ProductsController#index", + }, + }, + "docType": "transaction", + "duration": 8634, "id": "myTransactionId2", + "offset": 39298, + "parent": Object { + "doc": Object { + "parent": Object { + "id": "myTransactionId1", + }, + "processor": Object { + "event": "span", + }, + "service": Object { + "name": "opbeans-node", + }, + "span": Object { + "duration": Object { + "us": 47557, + }, + "id": "mySpanIdD", + "name": "GET opbeans-ruby:3000/api/products", + }, + "timestamp": Object { + "us": 1549324795785760, + }, + "trace": Object { + "id": "myTraceId", + }, + "transaction": Object { + "id": "myTransactionId1", + }, + }, + "docType": "span", + "duration": 47557, + "id": "mySpanIdD", + "offset": 1754, + "parent": Object { + "doc": Object { + "processor": Object { + "event": "transaction", + }, + "service": Object { + "name": "opbeans-node", + }, + "timestamp": Object { + "us": 1549324795784006, + }, + "trace": Object { + "id": "myTraceId", + }, + "transaction": Object { + "duration": Object { + "us": 49660, + }, + "id": "myTransactionId1", + "name": "GET /api", + }, + }, + "docType": "transaction", + "duration": 49660, + "id": "myTransactionId1", + "offset": 0, + "parent": undefined, + "parentId": undefined, + "skew": 0, + }, + "parentId": "myTransactionId1", + "skew": 0, + }, + "parentId": "mySpanIdD", + "skew": 0, }, + "parentId": "myTransactionId2", + "skew": 0, }, - "timestamp": 1549324795824504, - }, - Object { - "childIds": Array [], - "docType": "span", - "duration": 481, - "id": "mySpanIdB", - "name": "SELECT FROM products", - "offset": 41627, "parentId": "mySpanIdA", - "serviceName": "opbeans-ruby", "skew": 0, - "span": Object { + }, + Object { + "doc": Object { "parent": Object { "id": "mySpanIdA", }, @@ -448,13 +555,13 @@ Object { }, "span": Object { "duration": Object { - "us": 481, + "us": 532, }, - "id": "mySpanIdB", - "name": "SELECT FROM products", + "id": "mySpanIdC", + "name": "SELECT FROM product", }, "timestamp": Object { - "us": 1549324795825633, + "us": 1549324795827905, }, "trace": Object { "id": "myTraceId", @@ -463,57 +570,223 @@ Object { "id": "myTransactionId2", }, }, - "timestamp": 1549324795825633, - }, - Object { - "childIds": Array [], "docType": "span", "duration": 532, "id": "mySpanIdC", - "name": "SELECT FROM product", "offset": 43899, + "parent": Object { + "doc": Object { + "parent": Object { + "id": "myTransactionId2", + }, + "processor": Object { + "event": "span", + }, + "service": Object { + "name": "opbeans-ruby", + }, + "span": Object { + "duration": Object { + "us": 6161, + }, + "id": "mySpanIdA", + "name": "Api::ProductsController#index", + }, + "timestamp": Object { + "us": 1549324795824504, + }, + "trace": Object { + "id": "myTraceId", + }, + "transaction": Object { + "id": "myTransactionId2", + }, + }, + "docType": "span", + "duration": 6161, + "id": "mySpanIdA", + "offset": 40498, + "parent": Object { + "doc": Object { + "parent": Object { + "id": "mySpanIdD", + }, + "processor": Object { + "event": "transaction", + }, + "service": Object { + "name": "opbeans-ruby", + }, + "timestamp": Object { + "us": 1549324795823304, + }, + "trace": Object { + "id": "myTraceId", + }, + "transaction": Object { + "duration": Object { + "us": 8634, + }, + "id": "myTransactionId2", + "marks": Object { + "agent": Object { + "domComplete": 383, + "domInteractive": 382, + "timeToFirstByte": 14, + }, + }, + "name": "Api::ProductsController#index", + }, + }, + "docType": "transaction", + "duration": 8634, + "id": "myTransactionId2", + "offset": 39298, + "parent": Object { + "doc": Object { + "parent": Object { + "id": "myTransactionId1", + }, + "processor": Object { + "event": "span", + }, + "service": Object { + "name": "opbeans-node", + }, + "span": Object { + "duration": Object { + "us": 47557, + }, + "id": "mySpanIdD", + "name": "GET opbeans-ruby:3000/api/products", + }, + "timestamp": Object { + "us": 1549324795785760, + }, + "trace": Object { + "id": "myTraceId", + }, + "transaction": Object { + "id": "myTransactionId1", + }, + }, + "docType": "span", + "duration": 47557, + "id": "mySpanIdD", + "offset": 1754, + "parent": Object { + "doc": Object { + "processor": Object { + "event": "transaction", + }, + "service": Object { + "name": "opbeans-node", + }, + "timestamp": Object { + "us": 1549324795784006, + }, + "trace": Object { + "id": "myTraceId", + }, + "transaction": Object { + "duration": Object { + "us": 49660, + }, + "id": "myTransactionId1", + "name": "GET /api", + }, + }, + "docType": "transaction", + "duration": 49660, + "id": "myTransactionId1", + "offset": 0, + "parent": undefined, + "parentId": undefined, + "skew": 0, + }, + "parentId": "myTransactionId1", + "skew": 0, + }, + "parentId": "mySpanIdD", + "skew": 0, + }, + "parentId": "myTransactionId2", + "skew": 0, + }, "parentId": "mySpanIdA", - "serviceName": "opbeans-ruby", "skew": 0, - "span": Object { + }, + Object { + "doc": Object { + "agent": Object { + "name": "ruby", + "version": "2", + }, + "error": Object { + "grouping_key": "errorGroupingKey1", + "id": "error1", + "log": Object { + "message": "error message", + }, + }, "parent": Object { - "id": "mySpanIdA", + "id": "myTransactionId1", }, "processor": Object { - "event": "span", + "event": "error", }, "service": Object { "name": "opbeans-ruby", }, - "span": Object { - "duration": Object { - "us": 532, - }, - "id": "mySpanIdC", - "name": "SELECT FROM product", - }, "timestamp": Object { - "us": 1549324795827905, + "us": 1549324795810000, }, "trace": Object { "id": "myTraceId", }, "transaction": Object { - "id": "myTransactionId2", + "id": "myTransactionId1", + }, + }, + "docType": "error", + "duration": 0, + "id": "error1", + "offset": 25994, + "parent": Object { + "doc": Object { + "processor": Object { + "event": "transaction", + }, + "service": Object { + "name": "opbeans-node", + }, + "timestamp": Object { + "us": 1549324795784006, + }, + "trace": Object { + "id": "myTraceId", + }, + "transaction": Object { + "duration": Object { + "us": 49660, + }, + "id": "myTransactionId1", + "name": "GET /api", + }, }, + "docType": "transaction", + "duration": 49660, + "id": "myTransactionId1", + "offset": 0, + "parent": undefined, + "parentId": undefined, + "skew": 0, }, - "timestamp": 1549324795827905, + "parentId": "myTransactionId1", + "skew": 0, }, ], - "serviceColors": Object { - "opbeans-node": "#3185fc", - "opbeans-ruby": "#00b3a4", - }, - "services": Array [ - "opbeans-node", - "opbeans-ruby", - ], - "traceRoot": Object { + "rootTransaction": Object { "processor": Object { "event": "transaction", }, @@ -534,7 +807,10 @@ Object { "name": "GET /api", }, }, - "traceRootDuration": 49660, + "serviceColors": Object { + "opbeans-node": "#3185fc", + "opbeans-ruby": "#00b3a4", + }, } `; @@ -562,221 +838,24 @@ Object { "us": 8634, }, "id": "myTransactionId2", - "name": "Api::ProductsController#index", - }, - }, - "errorCountByTransactionId": Object { - "myTransactionId1": 2, - "myTransactionId2": 3, - }, - "getTransactionById": [Function], - "itemsById": Object { - "mySpanIdA": Object { - "childIds": Array [ - "mySpanIdB", - "mySpanIdC", - ], - "docType": "span", - "duration": 6161, - "id": "mySpanIdA", - "name": "Api::ProductsController#index", - "offset": 1200, - "parentId": "myTransactionId2", - "serviceName": "opbeans-ruby", - "skew": 0, - "span": Object { - "parent": Object { - "id": "myTransactionId2", - }, - "processor": Object { - "event": "span", - }, - "service": Object { - "name": "opbeans-ruby", - }, - "span": Object { - "duration": Object { - "us": 6161, - }, - "id": "mySpanIdA", - "name": "Api::ProductsController#index", - }, - "timestamp": Object { - "us": 1549324795824504, - }, - "trace": Object { - "id": "myTraceId", - }, - "transaction": Object { - "id": "myTransactionId2", - }, - }, - "timestamp": 1549324795824504, - }, - "mySpanIdB": Object { - "childIds": Array [], - "docType": "span", - "duration": 481, - "id": "mySpanIdB", - "name": "SELECT FROM products", - "offset": 2329, - "parentId": "mySpanIdA", - "serviceName": "opbeans-ruby", - "skew": 0, - "span": Object { - "parent": Object { - "id": "mySpanIdA", - }, - "processor": Object { - "event": "span", - }, - "service": Object { - "name": "opbeans-ruby", - }, - "span": Object { - "duration": Object { - "us": 481, - }, - "id": "mySpanIdB", - "name": "SELECT FROM products", - }, - "timestamp": Object { - "us": 1549324795825633, - }, - "trace": Object { - "id": "myTraceId", - }, - "transaction": Object { - "id": "myTransactionId2", - }, - }, - "timestamp": 1549324795825633, - }, - "mySpanIdC": Object { - "childIds": Array [], - "docType": "span", - "duration": 532, - "id": "mySpanIdC", - "name": "SELECT FROM product", - "offset": 4601, - "parentId": "mySpanIdA", - "serviceName": "opbeans-ruby", - "skew": 0, - "span": Object { - "parent": Object { - "id": "mySpanIdA", - }, - "processor": Object { - "event": "span", - }, - "service": Object { - "name": "opbeans-ruby", - }, - "span": Object { - "duration": Object { - "us": 532, - }, - "id": "mySpanIdC", - "name": "SELECT FROM product", - }, - "timestamp": Object { - "us": 1549324795827905, - }, - "trace": Object { - "id": "myTraceId", - }, - "transaction": Object { - "id": "myTransactionId2", - }, - }, - "timestamp": 1549324795827905, - }, - "mySpanIdD": Object { - "docType": "span", - "duration": 47557, - "id": "mySpanIdD", - "name": "GET opbeans-ruby:3000/api/products", - "offset": 0, - "parentId": "myTransactionId1", - "serviceName": "opbeans-node", - "skew": 0, - "span": Object { - "parent": Object { - "id": "myTransactionId1", - }, - "processor": Object { - "event": "span", - }, - "service": Object { - "name": "opbeans-node", - }, - "span": Object { - "duration": Object { - "us": 47557, - }, - "id": "mySpanIdD", - "name": "GET opbeans-ruby:3000/api/products", - }, - "timestamp": Object { - "us": 1549324795785760, - }, - "trace": Object { - "id": "myTraceId", - }, - "transaction": Object { - "id": "myTransactionId1", - }, - }, - "timestamp": 1549324795785760, - }, - "myTransactionId1": Object { - "docType": "transaction", - "duration": 49660, - "errorCount": 2, - "id": "myTransactionId1", - "name": "GET /api", - "offset": 0, - "parentId": undefined, - "serviceName": "opbeans-node", - "skew": 0, - "timestamp": 1549324795784006, - "transaction": Object { - "processor": Object { - "event": "transaction", - }, - "service": Object { - "name": "opbeans-node", - }, - "timestamp": Object { - "us": 1549324795784006, - }, - "trace": Object { - "id": "myTraceId", - }, - "transaction": Object { - "duration": Object { - "us": 49660, - }, - "id": "myTransactionId1", - "name": "GET /api", + "marks": Object { + "agent": Object { + "domComplete": 383, + "domInteractive": 382, + "timeToFirstByte": 14, }, }, - }, - "myTransactionId2": Object { - "childIds": Array [ - "mySpanIdA", - ], - "docType": "transaction", - "duration": 8634, - "errorCount": 3, - "id": "myTransactionId2", "name": "Api::ProductsController#index", - "offset": 0, - "parentId": "mySpanIdD", - "serviceName": "opbeans-ruby", - "skew": 0, - "timestamp": 1549324795823304, - "transaction": Object { + }, + }, + "errorsCount": 0, + "errorsPerTransaction": Object { + "myTransactionId1": 2, + "myTransactionId2": 3, + }, + "items": Array [ + Object { + "doc": Object { "parent": Object { "id": "mySpanIdD", }, @@ -797,65 +876,26 @@ Object { "us": 8634, }, "id": "myTransactionId2", + "marks": Object { + "agent": Object { + "domComplete": 383, + "domInteractive": 382, + "timeToFirstByte": 14, + }, + }, "name": "Api::ProductsController#index", }, }, - }, - }, - "orderedItems": Array [ - Object { - "childIds": Array [ - "mySpanIdA", - ], "docType": "transaction", "duration": 8634, - "errorCount": 3, "id": "myTransactionId2", - "name": "Api::ProductsController#index", "offset": 0, + "parent": undefined, "parentId": "mySpanIdD", - "serviceName": "opbeans-ruby", "skew": 0, - "timestamp": 1549324795823304, - "transaction": Object { - "parent": Object { - "id": "mySpanIdD", - }, - "processor": Object { - "event": "transaction", - }, - "service": Object { - "name": "opbeans-ruby", - }, - "timestamp": Object { - "us": 1549324795823304, - }, - "trace": Object { - "id": "myTraceId", - }, - "transaction": Object { - "duration": Object { - "us": 8634, - }, - "id": "myTransactionId2", - "name": "Api::ProductsController#index", - }, - }, }, Object { - "childIds": Array [ - "mySpanIdB", - "mySpanIdC", - ], - "docType": "span", - "duration": 6161, - "id": "mySpanIdA", - "name": "Api::ProductsController#index", - "offset": 1200, - "parentId": "myTransactionId2", - "serviceName": "opbeans-ruby", - "skew": 0, - "span": Object { + "doc": Object { "parent": Object { "id": "myTransactionId2", }, @@ -882,19 +922,55 @@ Object { "id": "myTransactionId2", }, }, - "timestamp": 1549324795824504, - }, - Object { - "childIds": Array [], "docType": "span", - "duration": 481, - "id": "mySpanIdB", - "name": "SELECT FROM products", - "offset": 2329, - "parentId": "mySpanIdA", - "serviceName": "opbeans-ruby", + "duration": 6161, + "id": "mySpanIdA", + "offset": 1200, + "parent": Object { + "doc": Object { + "parent": Object { + "id": "mySpanIdD", + }, + "processor": Object { + "event": "transaction", + }, + "service": Object { + "name": "opbeans-ruby", + }, + "timestamp": Object { + "us": 1549324795823304, + }, + "trace": Object { + "id": "myTraceId", + }, + "transaction": Object { + "duration": Object { + "us": 8634, + }, + "id": "myTransactionId2", + "marks": Object { + "agent": Object { + "domComplete": 383, + "domInteractive": 382, + "timeToFirstByte": 14, + }, + }, + "name": "Api::ProductsController#index", + }, + }, + "docType": "transaction", + "duration": 8634, + "id": "myTransactionId2", + "offset": 0, + "parent": undefined, + "parentId": "mySpanIdD", + "skew": 0, + }, + "parentId": "myTransactionId2", "skew": 0, - "span": Object { + }, + Object { + "doc": Object { "parent": Object { "id": "mySpanIdA", }, @@ -921,19 +997,90 @@ Object { "id": "myTransactionId2", }, }, - "timestamp": 1549324795825633, - }, - Object { - "childIds": Array [], "docType": "span", - "duration": 532, - "id": "mySpanIdC", - "name": "SELECT FROM product", - "offset": 4601, + "duration": 481, + "id": "mySpanIdB", + "offset": 2329, + "parent": Object { + "doc": Object { + "parent": Object { + "id": "myTransactionId2", + }, + "processor": Object { + "event": "span", + }, + "service": Object { + "name": "opbeans-ruby", + }, + "span": Object { + "duration": Object { + "us": 6161, + }, + "id": "mySpanIdA", + "name": "Api::ProductsController#index", + }, + "timestamp": Object { + "us": 1549324795824504, + }, + "trace": Object { + "id": "myTraceId", + }, + "transaction": Object { + "id": "myTransactionId2", + }, + }, + "docType": "span", + "duration": 6161, + "id": "mySpanIdA", + "offset": 1200, + "parent": Object { + "doc": Object { + "parent": Object { + "id": "mySpanIdD", + }, + "processor": Object { + "event": "transaction", + }, + "service": Object { + "name": "opbeans-ruby", + }, + "timestamp": Object { + "us": 1549324795823304, + }, + "trace": Object { + "id": "myTraceId", + }, + "transaction": Object { + "duration": Object { + "us": 8634, + }, + "id": "myTransactionId2", + "marks": Object { + "agent": Object { + "domComplete": 383, + "domInteractive": 382, + "timeToFirstByte": 14, + }, + }, + "name": "Api::ProductsController#index", + }, + }, + "docType": "transaction", + "duration": 8634, + "id": "myTransactionId2", + "offset": 0, + "parent": undefined, + "parentId": "mySpanIdD", + "skew": 0, + }, + "parentId": "myTransactionId2", + "skew": 0, + }, "parentId": "mySpanIdA", - "serviceName": "opbeans-ruby", "skew": 0, - "span": Object { + }, + Object { + "doc": Object { "parent": Object { "id": "mySpanIdA", }, @@ -960,16 +1107,90 @@ Object { "id": "myTransactionId2", }, }, - "timestamp": 1549324795827905, + "docType": "span", + "duration": 532, + "id": "mySpanIdC", + "offset": 4601, + "parent": Object { + "doc": Object { + "parent": Object { + "id": "myTransactionId2", + }, + "processor": Object { + "event": "span", + }, + "service": Object { + "name": "opbeans-ruby", + }, + "span": Object { + "duration": Object { + "us": 6161, + }, + "id": "mySpanIdA", + "name": "Api::ProductsController#index", + }, + "timestamp": Object { + "us": 1549324795824504, + }, + "trace": Object { + "id": "myTraceId", + }, + "transaction": Object { + "id": "myTransactionId2", + }, + }, + "docType": "span", + "duration": 6161, + "id": "mySpanIdA", + "offset": 1200, + "parent": Object { + "doc": Object { + "parent": Object { + "id": "mySpanIdD", + }, + "processor": Object { + "event": "transaction", + }, + "service": Object { + "name": "opbeans-ruby", + }, + "timestamp": Object { + "us": 1549324795823304, + }, + "trace": Object { + "id": "myTraceId", + }, + "transaction": Object { + "duration": Object { + "us": 8634, + }, + "id": "myTransactionId2", + "marks": Object { + "agent": Object { + "domComplete": 383, + "domInteractive": 382, + "timeToFirstByte": 14, + }, + }, + "name": "Api::ProductsController#index", + }, + }, + "docType": "transaction", + "duration": 8634, + "id": "myTransactionId2", + "offset": 0, + "parent": undefined, + "parentId": "mySpanIdD", + "skew": 0, + }, + "parentId": "myTransactionId2", + "skew": 0, + }, + "parentId": "mySpanIdA", + "skew": 0, }, ], - "serviceColors": Object { - "opbeans-ruby": "#3185fc", - }, - "services": Array [ - "opbeans-ruby", - ], - "traceRoot": Object { + "rootTransaction": Object { "processor": Object { "event": "transaction", }, @@ -990,30 +1211,61 @@ Object { "name": "GET /api", }, }, - "traceRootDuration": 49660, + "serviceColors": Object { + "opbeans-ruby": "#3185fc", + }, } `; exports[`waterfall_helpers getWaterfallItems should handle cyclic references 1`] = ` Array [ Object { - "childIds": Array [ - "a", - ], + "doc": Object { + "timestamp": Object { + "us": 10, + }, + "transaction": Object { + "id": "a", + }, + }, + "docType": "transaction", "id": "a", "offset": 0, + "parent": undefined, "skew": 0, - "timestamp": 10, }, Object { - "childIds": Array [ - "a", - ], - "id": "a", + "doc": Object { + "parent": Object { + "id": "a", + }, + "span": Object { + "id": "b", + }, + "timestamp": Object { + "us": 20, + }, + }, + "docType": "span", + "id": "b", "offset": 10, + "parent": Object { + "doc": Object { + "timestamp": Object { + "us": 10, + }, + "transaction": Object { + "id": "a", + }, + }, + "docType": "transaction", + "id": "a", + "offset": 0, + "parent": undefined, + "skew": 0, + }, "parentId": "a", - "skew": undefined, - "timestamp": 20, + "skew": 0, }, ] `; @@ -1021,89 +1273,280 @@ Array [ exports[`waterfall_helpers getWaterfallItems should order items correctly 1`] = ` Array [ Object { - "childIds": Array [ - "b2", - "b", - ], + "doc": Object { + "service": Object { + "name": "opbeans-java", + }, + "timestamp": Object { + "us": 1536763736366000, + }, + "transaction": Object { + "id": "a", + "name": "APIRestController#products", + }, + }, "docType": "transaction", "duration": 9480, - "errorCount": 0, "id": "a", - "name": "APIRestController#products", "offset": 0, - "serviceName": "opbeans-java", + "parent": undefined, "skew": 0, - "timestamp": 1536763736366000, - "transaction": Object {}, }, Object { - "childIds": Array [], + "doc": Object { + "parent": Object { + "id": "a", + }, + "service": Object { + "name": "opbeans-java", + }, + "span": Object { + "id": "b2", + "name": "GET [0:0:0:0:0:0:0:1]", + }, + "timestamp": Object { + "us": 1536763736367000, + }, + "transaction": Object { + "id": "a", + }, + }, "docType": "span", "duration": 4694, "id": "b2", - "name": "GET [0:0:0:0:0:0:0:1]", "offset": 1000, + "parent": Object { + "doc": Object { + "service": Object { + "name": "opbeans-java", + }, + "timestamp": Object { + "us": 1536763736366000, + }, + "transaction": Object { + "id": "a", + "name": "APIRestController#products", + }, + }, + "docType": "transaction", + "duration": 9480, + "id": "a", + "offset": 0, + "parent": undefined, + "skew": 0, + }, "parentId": "a", - "serviceName": "opbeans-java", "skew": 0, - "span": Object { + }, + Object { + "doc": Object { + "parent": Object { + "id": "a", + }, + "service": Object { + "name": "opbeans-java", + }, + "span": Object { + "id": "b", + "name": "GET [0:0:0:0:0:0:0:1]", + }, + "timestamp": Object { + "us": 1536763736368000, + }, "transaction": Object { "id": "a", }, }, - "timestamp": 1536763736367000, - }, - Object { - "childIds": Array [ - "c", - ], "docType": "span", "duration": 4694, "id": "b", - "name": "GET [0:0:0:0:0:0:0:1]", "offset": 2000, + "parent": Object { + "doc": Object { + "service": Object { + "name": "opbeans-java", + }, + "timestamp": Object { + "us": 1536763736366000, + }, + "transaction": Object { + "id": "a", + "name": "APIRestController#products", + }, + }, + "docType": "transaction", + "duration": 9480, + "id": "a", + "offset": 0, + "parent": undefined, + "skew": 0, + }, "parentId": "a", - "serviceName": "opbeans-java", "skew": 0, - "span": Object { + }, + Object { + "doc": Object { + "parent": Object { + "id": "b", + }, + "service": Object { + "name": "opbeans-java", + }, + "timestamp": Object { + "us": 1536763736369000, + }, "transaction": Object { - "id": "a", + "id": "c", + "name": "APIRestController#productsRemote", }, }, - "timestamp": 1536763736368000, - }, - Object { - "childIds": Array [ - "d", - ], "docType": "transaction", "duration": 3581, - "errorCount": 0, "id": "c", - "name": "APIRestController#productsRemote", "offset": 3000, + "parent": Object { + "doc": Object { + "parent": Object { + "id": "a", + }, + "service": Object { + "name": "opbeans-java", + }, + "span": Object { + "id": "b", + "name": "GET [0:0:0:0:0:0:0:1]", + }, + "timestamp": Object { + "us": 1536763736368000, + }, + "transaction": Object { + "id": "a", + }, + }, + "docType": "span", + "duration": 4694, + "id": "b", + "offset": 2000, + "parent": Object { + "doc": Object { + "service": Object { + "name": "opbeans-java", + }, + "timestamp": Object { + "us": 1536763736366000, + }, + "transaction": Object { + "id": "a", + "name": "APIRestController#products", + }, + }, + "docType": "transaction", + "duration": 9480, + "id": "a", + "offset": 0, + "parent": undefined, + "skew": 0, + }, + "parentId": "a", + "skew": 0, + }, "parentId": "b", - "serviceName": "opbeans-java", "skew": 0, - "timestamp": 1536763736369000, - "transaction": Object {}, }, Object { - "childIds": Array [], + "doc": Object { + "parent": Object { + "id": "c", + }, + "service": Object { + "name": "opbeans-java", + }, + "span": Object { + "id": "d", + "name": "SELECT", + }, + "timestamp": Object { + "us": 1536763736371000, + }, + "transaction": Object { + "id": "c", + }, + }, "docType": "span", "duration": 210, "id": "d", - "name": "SELECT", "offset": 5000, - "parentId": "c", - "serviceName": "opbeans-java", - "skew": 0, - "span": Object { - "transaction": Object { - "id": "c", + "parent": Object { + "doc": Object { + "parent": Object { + "id": "b", + }, + "service": Object { + "name": "opbeans-java", + }, + "timestamp": Object { + "us": 1536763736369000, + }, + "transaction": Object { + "id": "c", + "name": "APIRestController#productsRemote", + }, + }, + "docType": "transaction", + "duration": 3581, + "id": "c", + "offset": 3000, + "parent": Object { + "doc": Object { + "parent": Object { + "id": "a", + }, + "service": Object { + "name": "opbeans-java", + }, + "span": Object { + "id": "b", + "name": "GET [0:0:0:0:0:0:0:1]", + }, + "timestamp": Object { + "us": 1536763736368000, + }, + "transaction": Object { + "id": "a", + }, + }, + "docType": "span", + "duration": 4694, + "id": "b", + "offset": 2000, + "parent": Object { + "doc": Object { + "service": Object { + "name": "opbeans-java", + }, + "timestamp": Object { + "us": 1536763736366000, + }, + "transaction": Object { + "id": "a", + "name": "APIRestController#products", + }, + }, + "docType": "transaction", + "duration": 9480, + "id": "a", + "offset": 0, + "parent": undefined, + "skew": 0, + }, + "parentId": "a", + "skew": 0, }, + "parentId": "b", + "skew": 0, }, - "timestamp": 1536763736371000, + "parentId": "c", + "skew": 0, }, ] `; diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers.test.ts b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers.test.ts index 6166515fd9d38..426842bc02f51 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers.test.ts +++ b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers.test.ts @@ -11,8 +11,10 @@ import { getClockSkew, getOrderedWaterfallItems, getWaterfall, - IWaterfallItem + IWaterfallItem, + IWaterfallTransaction } from './waterfall_helpers'; +import { APMError } from '../../../../../../../../typings/es_schemas/ui/APMError'; describe('waterfall_helpers', () => { describe('getWaterfall', () => { @@ -80,7 +82,7 @@ describe('waterfall_helpers', () => { }, timestamp: { us: 1549324795785760 } } as Span, - { + ({ parent: { id: 'mySpanIdD' }, processor: { event: 'transaction' }, trace: { id: 'myTraceId' }, @@ -88,10 +90,36 @@ describe('waterfall_helpers', () => { transaction: { duration: { us: 8634 }, name: 'Api::ProductsController#index', - id: 'myTransactionId2' + id: 'myTransactionId2', + marks: { + agent: { + domInteractive: 382, + domComplete: 383, + timeToFirstByte: 14 + } + } }, timestamp: { us: 1549324795823304 } - } as Transaction + } as unknown) as Transaction, + ({ + processor: { event: 'error' }, + parent: { id: 'myTransactionId1' }, + timestamp: { us: 1549324795810000 }, + trace: { id: 'myTraceId' }, + transaction: { id: 'myTransactionId1' }, + error: { + id: 'error1', + grouping_key: 'errorGroupingKey1', + log: { + message: 'error message' + } + }, + service: { name: 'opbeans-ruby' }, + agent: { + name: 'ruby', + version: '2' + } + } as unknown) as APMError ]; it('should return full waterfall', () => { @@ -107,8 +135,10 @@ describe('waterfall_helpers', () => { }, entryTransactionId ); - expect(waterfall.orderedItems.length).toBe(6); - expect(waterfall.orderedItems[0].id).toBe('myTransactionId1'); + + expect(waterfall.items.length).toBe(7); + expect(waterfall.items[0].id).toBe('myTransactionId1'); + expect(waterfall.errorsCount).toEqual(1); expect(waterfall).toMatchSnapshot(); }); @@ -125,26 +155,11 @@ describe('waterfall_helpers', () => { }, entryTransactionId ); - expect(waterfall.orderedItems.length).toBe(4); - expect(waterfall.orderedItems[0].id).toBe('myTransactionId2'); - expect(waterfall).toMatchSnapshot(); - }); - it('getTransactionById', () => { - const entryTransactionId = 'myTransactionId1'; - const errorsPerTransaction = { - myTransactionId1: 2, - myTransactionId2: 3 - }; - const waterfall = getWaterfall( - { - trace: { items: hits, exceedsMax: false }, - errorsPerTransaction - }, - entryTransactionId - ); - const transaction = waterfall.getTransactionById('myTransactionId2'); - expect(transaction!.transaction.id).toBe('myTransactionId2'); + expect(waterfall.items.length).toBe(4); + expect(waterfall.items[0].id).toBe('myTransactionId2'); + expect(waterfall.errorsCount).toEqual(0); + expect(waterfall).toMatchSnapshot(); }); }); @@ -152,84 +167,102 @@ describe('waterfall_helpers', () => { it('should order items correctly', () => { const items: IWaterfallItem[] = [ { + docType: 'span', + doc: { + parent: { id: 'c' }, + service: { name: 'opbeans-java' }, + transaction: { + id: 'c' + }, + timestamp: { us: 1536763736371000 }, + span: { + id: 'd', + name: 'SELECT' + } + } as Span, id: 'd', parentId: 'c', - serviceName: 'opbeans-java', - name: 'SELECT', duration: 210, - timestamp: 1536763736371000, offset: 0, - skew: 0, + skew: 0 + }, + { docType: 'span', - span: { + doc: { + parent: { id: 'a' }, + service: { name: 'opbeans-java' }, transaction: { - id: 'c' + id: 'a' + }, + timestamp: { us: 1536763736368000 }, + span: { + id: 'b', + name: 'GET [0:0:0:0:0:0:0:1]' } - } as Span - }, - { + } as Span, id: 'b', parentId: 'a', - serviceName: 'opbeans-java', - name: 'GET [0:0:0:0:0:0:0:1]', duration: 4694, - timestamp: 1536763736368000, offset: 0, - skew: 0, + skew: 0 + }, + { docType: 'span', - span: { + doc: { + parent: { id: 'a' }, + service: { name: 'opbeans-java' }, transaction: { id: 'a' + }, + timestamp: { us: 1536763736367000 }, + span: { + id: 'b2', + name: 'GET [0:0:0:0:0:0:0:1]' } - } as Span - }, - { + } as Span, id: 'b2', parentId: 'a', - serviceName: 'opbeans-java', - name: 'GET [0:0:0:0:0:0:0:1]', duration: 4694, - timestamp: 1536763736367000, offset: 0, - skew: 0, - docType: 'span', - span: { - transaction: { - id: 'a' - } - } as Span + skew: 0 }, { + docType: 'transaction', + doc: { + parent: { id: 'b' }, + service: { name: 'opbeans-java' }, + timestamp: { us: 1536763736369000 }, + transaction: { id: 'c', name: 'APIRestController#productsRemote' } + } as Transaction, id: 'c', parentId: 'b', - serviceName: 'opbeans-java', - name: 'APIRestController#productsRemote', duration: 3581, - timestamp: 1536763736369000, offset: 0, - skew: 0, - docType: 'transaction', - transaction: {} as Transaction, - errorCount: 0 + skew: 0 }, { + docType: 'transaction', + doc: { + service: { name: 'opbeans-java' }, + timestamp: { us: 1536763736366000 }, + transaction: { + id: 'a', + name: 'APIRestController#products' + } + } as Transaction, id: 'a', - serviceName: 'opbeans-java', - name: 'APIRestController#products', duration: 9480, - timestamp: 1536763736366000, offset: 0, - skew: 0, - docType: 'transaction', - transaction: {} as Transaction, - errorCount: 0 + skew: 0 } ]; const childrenByParentId = groupBy(items, hit => hit.parentId ? hit.parentId : 'root' ); - const entryTransactionItem = childrenByParentId.root[0]; + const entryTransactionItem = childrenByParentId + .root[0] as IWaterfallTransaction; + expect( getOrderedWaterfallItems(childrenByParentId, entryTransactionItem) ).toMatchSnapshot(); @@ -237,13 +270,32 @@ describe('waterfall_helpers', () => { it('should handle cyclic references', () => { const items = [ - { id: 'a', timestamp: 10 } as IWaterfallItem, - { id: 'a', parentId: 'a', timestamp: 20 } as IWaterfallItem + { + docType: 'transaction', + id: 'a', + doc: ({ + transaction: { id: 'a' }, + timestamp: { us: 10 } + } as unknown) as Transaction + } as IWaterfallItem, + { + docType: 'span', + id: 'b', + parentId: 'a', + doc: ({ + span: { + id: 'b' + }, + parent: { id: 'a' }, + timestamp: { us: 20 } + } as unknown) as Span + } as IWaterfallItem ]; const childrenByParentId = groupBy(items, hit => hit.parentId ? hit.parentId : 'root' ); - const entryTransactionItem = childrenByParentId.root[0]; + const entryTransactionItem = childrenByParentId + .root[0] as IWaterfallTransaction; expect( getOrderedWaterfallItems(childrenByParentId, entryTransactionItem) ).toMatchSnapshot(); @@ -254,12 +306,17 @@ describe('waterfall_helpers', () => { it('should adjust when child starts before parent', () => { const child = { docType: 'transaction', - timestamp: 0, + doc: { + timestamp: { us: 0 } + }, duration: 50 } as IWaterfallItem; const parent = { - timestamp: 100, + docType: 'transaction', + doc: { + timestamp: { us: 100 } + }, duration: 100, skew: 5 } as IWaterfallItem; @@ -270,12 +327,17 @@ describe('waterfall_helpers', () => { it('should not adjust when child starts after parent has ended', () => { const child = { docType: 'transaction', - timestamp: 250, + doc: { + timestamp: { us: 250 } + }, duration: 50 } as IWaterfallItem; const parent = { - timestamp: 100, + docType: 'transaction', + doc: { + timestamp: { us: 100 } + }, duration: 100, skew: 5 } as IWaterfallItem; @@ -286,12 +348,17 @@ describe('waterfall_helpers', () => { it('should not adjust when child starts within parent duration', () => { const child = { docType: 'transaction', - timestamp: 150, + doc: { + timestamp: { us: 150 } + }, duration: 50 } as IWaterfallItem; const parent = { - timestamp: 100, + docType: 'transaction', + doc: { + timestamp: { us: 100 } + }, duration: 100, skew: 5 } as IWaterfallItem; @@ -305,7 +372,27 @@ describe('waterfall_helpers', () => { } as IWaterfallItem; const parent = { - timestamp: 100, + docType: 'span', + doc: { + timestamp: { us: 100 } + }, + duration: 100, + skew: 5 + } as IWaterfallItem; + + expect(getClockSkew(child, parent)).toBe(5); + }); + + it('should return parent skew for errors', () => { + const child = { + docType: 'error' + } as IWaterfallItem; + + const parent = { + docType: 'transaction', + doc: { + timestamp: { us: 100 } + }, duration: 100, skew: 5 } as IWaterfallItem; diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers.ts b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers.ts index 2a69c5f51173d..1af6cddb3ba4a 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers.ts +++ b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers.ts @@ -6,60 +6,52 @@ import theme from '@elastic/eui/dist/eui_theme_light.json'; import { + first, flatten, groupBy, - indexBy, + isEmpty, sortBy, + sum, uniq, - zipObject, - isEmpty, - first + zipObject } from 'lodash'; import { TraceAPIResponse } from '../../../../../../../../server/lib/traces/get_trace'; +import { APMError } from '../../../../../../../../typings/es_schemas/ui/APMError'; import { Span } from '../../../../../../../../typings/es_schemas/ui/Span'; import { Transaction } from '../../../../../../../../typings/es_schemas/ui/Transaction'; -interface IWaterfallIndex { - [key: string]: IWaterfallItem | undefined; -} - interface IWaterfallGroup { [key: string]: IWaterfallItem[]; } export interface IWaterfall { entryTransaction?: Transaction; - traceRoot?: Transaction; - traceRootDuration?: number; + rootTransaction?: Transaction; /** * Duration in us */ duration: number; - services: string[]; - orderedItems: IWaterfallItem[]; - itemsById: IWaterfallIndex; - getTransactionById: (id?: IWaterfallItem['id']) => Transaction | undefined; - errorCountByTransactionId: TraceAPIResponse['errorsPerTransaction']; + items: IWaterfallItem[]; + errorsPerTransaction: TraceAPIResponse['errorsPerTransaction']; + errorsCount: number; serviceColors: IServiceColors; } -interface IWaterfallItemBase { - id: string | number; +interface IWaterfallItemBase { + docType: U; + doc: T; + + id: string; + + parent?: IWaterfallItem; parentId?: string; - serviceName: string; - name: string; /** * Duration in us */ duration: number; - /** - * start timestamp in us - */ - timestamp: number; - /** * offset from first item in us */ @@ -69,53 +61,53 @@ interface IWaterfallItemBase { * skew from timestamp in us */ skew: number; - childIds?: Array; -} - -interface IWaterfallItemTransaction extends IWaterfallItemBase { - transaction: Transaction; - docType: 'transaction'; - errorCount: number; } -interface IWaterfallItemSpan extends IWaterfallItemBase { - span: Span; - docType: 'span'; -} +export type IWaterfallTransaction = IWaterfallItemBase< + Transaction, + 'transaction' +>; +export type IWaterfallSpan = IWaterfallItemBase; +export type IWaterfallError = IWaterfallItemBase; -export type IWaterfallItem = IWaterfallItemSpan | IWaterfallItemTransaction; +export type IWaterfallItem = + | IWaterfallTransaction + | IWaterfallSpan + | IWaterfallError; -function getTransactionItem( - transaction: Transaction, - errorsPerTransaction: TraceAPIResponse['errorsPerTransaction'] -): IWaterfallItemTransaction { +function getTransactionItem(transaction: Transaction): IWaterfallTransaction { return { + docType: 'transaction', + doc: transaction, id: transaction.transaction.id, - parentId: transaction.parent && transaction.parent.id, - serviceName: transaction.service.name, - name: transaction.transaction.name, + parentId: transaction.parent?.id, duration: transaction.transaction.duration.us, - timestamp: transaction.timestamp.us, offset: 0, - skew: 0, - docType: 'transaction', - transaction, - errorCount: errorsPerTransaction[transaction.transaction.id] || 0 + skew: 0 }; } -function getSpanItem(span: Span): IWaterfallItemSpan { +function getSpanItem(span: Span): IWaterfallSpan { return { + docType: 'span', + doc: span, id: span.span.id, - parentId: span.parent && span.parent.id, - serviceName: span.service.name, - name: span.span.name, + parentId: span.parent?.id, duration: span.span.duration.us, - timestamp: span.timestamp.us, + offset: 0, + skew: 0 + }; +} + +function getErrorItem(error: APMError): IWaterfallError { + return { + docType: 'error', + doc: error, + id: error.error.id, + parentId: error.parent?.id, offset: 0, skew: 0, - docType: 'span', - span + duration: 0 }; } @@ -126,18 +118,17 @@ export function getClockSkew( if (!parentItem) { return 0; } - switch (item.docType) { - // don't calculate skew for spans. Just use parent's skew + // don't calculate skew for spans and errors. Just use parent's skew + case 'error': case 'span': return parentItem.skew; - // transaction is the inital entry in a service. Calculate skew for this, and it will be propogated to all child spans case 'transaction': { - const parentStart = parentItem.timestamp + parentItem.skew; + const parentStart = parentItem.doc.timestamp.us + parentItem.skew; // determine if child starts before the parent - const offsetStart = parentStart - item.timestamp; + const offsetStart = parentStart - item.doc.timestamp.us; if (offsetStart > 0) { const latency = Math.max(parentItem.duration - item.duration, 0) / 2; return offsetStart + latency; @@ -151,9 +142,14 @@ export function getClockSkew( export function getOrderedWaterfallItems( childrenByParentId: IWaterfallGroup, - entryTransactionItem: IWaterfallItem + entryWaterfallTransaction?: IWaterfallTransaction ) { + if (!entryWaterfallTransaction) { + return []; + } + const entryTimestamp = entryWaterfallTransaction.doc.timestamp.us; const visitedWaterfallItemSet = new Set(); + function getSortedChildren( item: IWaterfallItem, parentItem?: IWaterfallItem @@ -162,10 +158,16 @@ export function getOrderedWaterfallItems( return []; } visitedWaterfallItemSet.add(item); - const children = sortBy(childrenByParentId[item.id] || [], 'timestamp'); - item.childIds = children.map(child => child.id); - item.offset = item.timestamp - entryTransactionItem.timestamp; + const children = sortBy( + childrenByParentId[item.id] || [], + 'doc.timestamp.us' + ); + + item.parent = parentItem; + // get offset from the beginning of trace + item.offset = item.doc.timestamp.us - entryTimestamp; + // move the item to the right if it starts before its parent item.skew = getClockSkew(item, parentItem); const deepChildren = flatten( @@ -174,24 +176,21 @@ export function getOrderedWaterfallItems( return [item, ...deepChildren]; } - return getSortedChildren(entryTransactionItem); + return getSortedChildren(entryWaterfallTransaction); } -function getTraceRoot(childrenByParentId: IWaterfallGroup) { +function getRootTransaction(childrenByParentId: IWaterfallGroup) { const item = first(childrenByParentId.root); if (item && item.docType === 'transaction') { - return item.transaction; + return item.doc; } } -function getServices(items: IWaterfallItem[]) { - const serviceNames = items.map(item => item.serviceName); - return uniq(serviceNames); -} - export type IServiceColors = Record; -function getServiceColors(services: string[]) { +function getServiceColors(waterfallItems: IWaterfallItem[]) { + const services = uniq(waterfallItems.map(item => item.doc.service.name)); + const assignedColors = [ theme.euiColorVis1, theme.euiColorVis0, @@ -205,30 +204,35 @@ function getServiceColors(services: string[]) { return zipObject(services, assignedColors) as IServiceColors; } -function getDuration(items: IWaterfallItem[]) { - if (items.length === 0) { - return 0; - } - const timestampStart = items[0].timestamp; - const timestampEnd = Math.max( - ...items.map(item => item.timestamp + item.duration + item.skew) +const getWaterfallDuration = (waterfallItems: IWaterfallItem[]) => + Math.max( + ...waterfallItems.map(item => item.offset + item.skew + item.duration), + 0 ); - return timestampEnd - timestampStart; -} -function createGetTransactionById(itemsById: IWaterfallIndex) { - return (id?: IWaterfallItem['id']) => { - if (!id) { - return undefined; +const getWaterfallItems = (items: TraceAPIResponse['trace']['items']) => + items.map(item => { + const docType = item.processor.event; + switch (docType) { + case 'span': + return getSpanItem(item as Span); + case 'transaction': + return getTransactionItem(item as Transaction); + case 'error': + return getErrorItem(item as APMError); } + }); - const item = itemsById[id]; - const isTransaction = item?.docType === 'transaction'; - if (isTransaction) { - return (item as IWaterfallItemTransaction).transaction; - } - }; -} +const getChildrenGroupedByParentId = (waterfallItems: IWaterfallItem[]) => + groupBy(waterfallItems, item => (item.parentId ? item.parentId : 'root')); + +const getEntryWaterfallTransaction = ( + entryTransactionId: string, + waterfallItems: IWaterfallItem[] +): IWaterfallTransaction | undefined => + waterfallItems.find( + item => item.docType === 'transaction' && item.id === entryTransactionId + ) as IWaterfallTransaction; export function getWaterfall( { trace, errorsPerTransaction }: TraceAPIResponse, @@ -236,59 +240,41 @@ export function getWaterfall( ): IWaterfall { if (isEmpty(trace.items) || !entryTransactionId) { return { - services: [], duration: 0, - orderedItems: [], - itemsById: {}, - getTransactionById: () => undefined, - errorCountByTransactionId: errorsPerTransaction, + items: [], + errorsPerTransaction, + errorsCount: sum(Object.values(errorsPerTransaction)), serviceColors: {} }; } - const waterfallItems = trace.items.map(traceItem => { - const docType = traceItem.processor.event; - switch (docType) { - case 'span': - return getSpanItem(traceItem as Span); - case 'transaction': - return getTransactionItem( - traceItem as Transaction, - errorsPerTransaction - ); - } - }); + const waterfallItems: IWaterfallItem[] = getWaterfallItems(trace.items); + + const childrenByParentId = getChildrenGroupedByParentId(waterfallItems); - const childrenByParentId = groupBy(waterfallItems, item => - item.parentId ? item.parentId : 'root' + const entryWaterfallTransaction = getEntryWaterfallTransaction( + entryTransactionId, + waterfallItems ); - const entryTransactionItem = waterfallItems.find( - waterfallItem => - waterfallItem.docType === 'transaction' && - waterfallItem.id === entryTransactionId + + const items = getOrderedWaterfallItems( + childrenByParentId, + entryWaterfallTransaction ); - const itemsById: IWaterfallIndex = indexBy(waterfallItems, 'id'); - const orderedItems = entryTransactionItem - ? getOrderedWaterfallItems(childrenByParentId, entryTransactionItem) - : []; - const traceRoot = getTraceRoot(childrenByParentId); - const duration = getDuration(orderedItems); - const traceRootDuration = traceRoot && traceRoot.transaction.duration.us; - const services = getServices(orderedItems); - const getTransactionById = createGetTransactionById(itemsById); - const serviceColors = getServiceColors(services); - const entryTransaction = getTransactionById(entryTransactionId); + + const rootTransaction = getRootTransaction(childrenByParentId); + const duration = getWaterfallDuration(items); + const serviceColors = getServiceColors(items); + + const entryTransaction = entryWaterfallTransaction?.doc; return { entryTransaction, - traceRoot, - traceRootDuration, + rootTransaction, duration, - services, - orderedItems, - itemsById, - getTransactionById, - errorCountByTransactionId: errorsPerTransaction, + items, + errorsPerTransaction, + errorsCount: items.filter(item => item.docType === 'error').length, serviceColors }; } diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/get_agent_marks.test.ts b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/get_agent_marks.test.ts deleted file mode 100644 index 9c805bb1cf385..0000000000000 --- a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/get_agent_marks.test.ts +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { Transaction } from '../../../../../../typings/es_schemas/ui/Transaction'; -import { getAgentMarks } from './get_agent_marks'; - -describe('getAgentMarks', () => { - it('should sort the marks by time', () => { - const transaction: Transaction = { - transaction: { - marks: { - agent: { - domInteractive: 117, - timeToFirstByte: 10, - domComplete: 118 - } - } - } - } as any; - expect(getAgentMarks(transaction)).toEqual([ - { name: 'timeToFirstByte', us: 10000 }, - { name: 'domInteractive', us: 117000 }, - { name: 'domComplete', us: 118000 } - ]); - }); - - it('should return empty array if marks are missing', () => { - const transaction: Transaction = { - transaction: {} - } as any; - expect(getAgentMarks(transaction)).toEqual([]); - }); - - it('should return empty array if agent marks are missing', () => { - const transaction: Transaction = { - transaction: { marks: {} } - } as any; - expect(getAgentMarks(transaction)).toEqual([]); - }); -}); diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/get_agent_marks.ts b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/get_agent_marks.ts deleted file mode 100644 index af76451db68b7..0000000000000 --- a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/get_agent_marks.ts +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { sortBy } from 'lodash'; -import { Transaction } from '../../../../../../typings/es_schemas/ui/Transaction'; - -export interface AgentMark { - name: string; - us: number; -} - -export function getAgentMarks(transaction: Transaction): AgentMark[] { - if (!(transaction.transaction.marks && transaction.transaction.marks.agent)) { - return []; - } - - return sortBy( - Object.entries(transaction.transaction.marks.agent).map(([name, ms]) => ({ - name, - us: ms * 1000 - })), - 'us' - ); -} diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/index.tsx index 2f34cc86c5cfc..77be5c999f7c3 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/index.tsx @@ -6,16 +6,13 @@ import { Location } from 'history'; import React from 'react'; -import { Transaction } from '../../../../../../typings/es_schemas/ui/Transaction'; import { IUrlParams } from '../../../../../context/UrlParamsContext/types'; -import { getAgentMarks } from './get_agent_marks'; import { ServiceLegends } from './ServiceLegends'; import { Waterfall } from './Waterfall'; import { IWaterfall } from './Waterfall/waterfall_helpers/waterfall_helpers'; interface Props { urlParams: IUrlParams; - transaction: Transaction; location: Location; waterfall: IWaterfall; exceedsMax: boolean; @@ -24,11 +21,9 @@ interface Props { export function WaterfallContainer({ location, urlParams, - transaction, waterfall, exceedsMax }: Props) { - const agentMarks = getAgentMarks(transaction); if (!waterfall) { return null; } @@ -37,10 +32,8 @@ export function WaterfallContainer({
diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/__tests__/ErrorCount.test.tsx b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/__tests__/ErrorCount.test.tsx new file mode 100644 index 0000000000000..62b5f7834d3a9 --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/__tests__/ErrorCount.test.tsx @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { fireEvent, render } from '@testing-library/react'; +import React from 'react'; +import { expectTextsInDocument } from '../../../../../utils/testHelpers'; +import { ErrorCount } from '../ErrorCount'; + +describe('ErrorCount', () => { + it('shows singular error message', () => { + const component = render(); + expectTextsInDocument(component, ['1 Error']); + }); + it('shows plural error message', () => { + const component = render(); + expectTextsInDocument(component, ['2 Errors']); + }); + it('prevents click propagation', () => { + const mock = jest.fn(); + const { getByText } = render( + + ); + fireEvent( + getByText('1 Error'), + new MouseEvent('click', { + bubbles: true, + cancelable: true + }) + ); + expect(mock).not.toHaveBeenCalled(); + }); +}); diff --git a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/index.tsx b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/index.tsx index b56370a59c8e2..6dcab6c6b97c1 100644 --- a/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/index.tsx +++ b/x-pack/legacy/plugins/apm/public/components/app/TransactionDetails/WaterfallWithSummmary/index.tsx @@ -5,30 +5,29 @@ */ import { + EuiEmptyPrompt, EuiFlexGroup, EuiFlexItem, + EuiPagination, EuiPanel, EuiSpacer, - EuiEmptyPrompt, - EuiTitle, - EuiPagination + EuiTitle } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { Location } from 'history'; -import React, { useState, useEffect } from 'react'; -import { sum } from 'lodash'; +import React, { useEffect, useState } from 'react'; import styled from 'styled-components'; -import { IUrlParams } from '../../../../context/UrlParamsContext/types'; -import { TransactionActionMenu } from '../../../shared/TransactionActionMenu/TransactionActionMenu'; -import { TransactionTabs } from './TransactionTabs'; -import { IWaterfall } from './WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers'; -import { LoadingStatePrompt } from '../../../shared/LoadingStatePrompt'; -import { TransactionSummary } from '../../../shared/Summary/TransactionSummary'; import { IBucket } from '../../../../../server/lib/transactions/distribution/get_buckets/transform'; +import { IUrlParams } from '../../../../context/UrlParamsContext/types'; +import { px, units } from '../../../../style/variables'; import { history } from '../../../../utils/history'; import { fromQuery, toQuery } from '../../../shared/Links/url_helpers'; +import { LoadingStatePrompt } from '../../../shared/LoadingStatePrompt'; +import { TransactionSummary } from '../../../shared/Summary/TransactionSummary'; +import { TransactionActionMenu } from '../../../shared/TransactionActionMenu/TransactionActionMenu'; import { MaybeViewTraceLink } from './MaybeViewTraceLink'; -import { units, px } from '../../../../style/variables'; +import { TransactionTabs } from './TransactionTabs'; +import { IWaterfall } from './WaterfallContainer/Waterfall/waterfall_helpers/waterfall_helpers'; const PaginationContainer = styled.div` margin-left: ${px(units.quarter)}; @@ -140,8 +139,8 @@ export const WaterfallWithSummmary: React.FC = ({ diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Links/apm/APMLink.tsx b/x-pack/legacy/plugins/apm/public/components/shared/Links/apm/APMLink.tsx index 0312e94d7ee19..eba59f6e3ce44 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/Links/apm/APMLink.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/Links/apm/APMLink.tsx @@ -18,7 +18,7 @@ interface Props extends EuiLinkAnchorProps { children?: React.ReactNode; } -export type APMLinkExtendProps = Omit; +export type APMLinkExtendProps = Omit; export const PERSISTENT_APM_PARAMS = [ 'kuery', diff --git a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/SpanMetadata/__test__/SpanMetadata.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/SpanMetadata/__test__/SpanMetadata.test.tsx index 4b6355034f16a..99d8a0790a816 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/SpanMetadata/__test__/SpanMetadata.test.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/SpanMetadata/__test__/SpanMetadata.test.tsx @@ -31,11 +31,15 @@ describe('SpanMetadata', () => { name: 'opbeans-java' }, span: { - id: '7efbc7056b746fcb' + id: '7efbc7056b746fcb', + message: { + age: { ms: 1577958057123 }, + queue: { name: 'queue name' } + } } } as unknown) as Span; const output = render(, renderOptions); - expectTextsInDocument(output, ['Service', 'Agent']); + expectTextsInDocument(output, ['Service', 'Agent', 'Message']); }); }); describe('when a span is presented', () => { @@ -55,11 +59,15 @@ describe('SpanMetadata', () => { response: { status_code: 200 } }, subtype: 'http', - type: 'external' + type: 'external', + message: { + age: { ms: 1577958057123 }, + queue: { name: 'queue name' } + } } } as unknown) as Span; const output = render(, renderOptions); - expectTextsInDocument(output, ['Service', 'Agent', 'Span']); + expectTextsInDocument(output, ['Service', 'Agent', 'Span', 'Message']); }); }); describe('when there is no id inside span', () => { @@ -83,7 +91,7 @@ describe('SpanMetadata', () => { } as unknown) as Span; const output = render(, renderOptions); expectTextsInDocument(output, ['Service', 'Agent']); - expectTextsNotInDocument(output, ['Span']); + expectTextsNotInDocument(output, ['Span', 'Message']); }); }); }); diff --git a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/SpanMetadata/sections.ts b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/SpanMetadata/sections.ts index 7012bbcc8fcea..5a83a9bf4ef9e 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/SpanMetadata/sections.ts +++ b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/SpanMetadata/sections.ts @@ -11,7 +11,8 @@ import { SPAN, LABELS, TRANSACTION, - TRACE + TRACE, + MESSAGE_SPAN } from '../sections'; export const SPAN_METADATA_SECTIONS: Section[] = [ @@ -20,5 +21,6 @@ export const SPAN_METADATA_SECTIONS: Section[] = [ TRANSACTION, TRACE, SERVICE, + MESSAGE_SPAN, AGENT ]; diff --git a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/TransactionMetadata/__test__/TransactionMetadata.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/TransactionMetadata/__test__/TransactionMetadata.test.tsx index 1fcb093fa0354..93e87e884ea76 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/TransactionMetadata/__test__/TransactionMetadata.test.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/TransactionMetadata/__test__/TransactionMetadata.test.tsx @@ -35,6 +35,10 @@ function getTransaction() { notIncluded: 'transaction not included value', custom: { someKey: 'custom value' + }, + message: { + age: { ms: 1577958057123 }, + queue: { name: 'queue name' } } } } as unknown) as Transaction; @@ -59,7 +63,8 @@ describe('TransactionMetadata', () => { 'Agent', 'URL', 'User', - 'Custom' + 'Custom', + 'Message' ]); }); @@ -81,7 +86,9 @@ describe('TransactionMetadata', () => { 'agent.someKey', 'url.someKey', 'user.someKey', - 'transaction.custom.someKey' + 'transaction.custom.someKey', + 'transaction.message.age.ms', + 'transaction.message.queue.name' ]); // excluded keys @@ -109,7 +116,9 @@ describe('TransactionMetadata', () => { 'agent value', 'url value', 'user value', - 'custom value' + 'custom value', + '1577958057123', + 'queue name' ]); // excluded values @@ -138,7 +147,8 @@ describe('TransactionMetadata', () => { 'Process', 'Agent', 'URL', - 'Custom' + 'Custom', + 'Message' ]); }); }); diff --git a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/TransactionMetadata/sections.ts b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/TransactionMetadata/sections.ts index 6b30c82bc35a0..18751efc6e1c1 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/TransactionMetadata/sections.ts +++ b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/TransactionMetadata/sections.ts @@ -18,7 +18,8 @@ import { PAGE, USER, USER_AGENT, - CUSTOM_TRANSACTION + CUSTOM_TRANSACTION, + MESSAGE_TRANSACTION } from '../sections'; export const TRANSACTION_METADATA_SECTIONS: Section[] = [ @@ -29,6 +30,7 @@ export const TRANSACTION_METADATA_SECTIONS: Section[] = [ CONTAINER, SERVICE, PROCESS, + MESSAGE_TRANSACTION, AGENT, URL, { ...PAGE, key: 'transaction.page' }, diff --git a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/sections.ts b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/sections.ts index 403663ce2095a..ac8e9559357e3 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/sections.ts +++ b/x-pack/legacy/plugins/apm/public/components/shared/MetadataTable/sections.ts @@ -136,3 +136,20 @@ export const CUSTOM_TRANSACTION: Section = { key: 'transaction.custom', label: customLabel }; + +const messageLabel = i18n.translate( + 'xpack.apm.metadataTable.section.messageLabel', + { + defaultMessage: 'Message' + } +); + +export const MESSAGE_TRANSACTION: Section = { + key: 'transaction.message', + label: messageLabel +}; + +export const MESSAGE_SPAN: Section = { + key: 'span.message', + label: messageLabel +}; diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Summary/ErrorCountSummaryItem.tsx b/x-pack/legacy/plugins/apm/public/components/shared/Summary/ErrorCountSummaryItem.tsx deleted file mode 100644 index 964debbedb2e4..0000000000000 --- a/x-pack/legacy/plugins/apm/public/components/shared/Summary/ErrorCountSummaryItem.tsx +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import React from 'react'; -import { i18n } from '@kbn/i18n'; -import styled from 'styled-components'; -import { px } from '../../../../public/style/variables'; -import { ErrorCountBadge } from '../../app/TransactionDetails/WaterfallWithSummmary/ErrorCountBadge'; -import { units } from '../../../style/variables'; - -interface Props { - count: number; -} - -const Badge = styled(ErrorCountBadge)` - margin-top: ${px(units.eighth)}; -`; - -const ErrorCountSummaryItem = ({ count }: Props) => { - return ( - - {i18n.translate('xpack.apm.transactionDetails.errorCount', { - defaultMessage: - '{errorCount, number} {errorCount, plural, one {Error} other {Errors}}', - values: { errorCount: count } - })} - - ); -}; - -export { ErrorCountSummaryItem }; diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Summary/ErrorCountSummaryItemBadge.tsx b/x-pack/legacy/plugins/apm/public/components/shared/Summary/ErrorCountSummaryItemBadge.tsx new file mode 100644 index 0000000000000..7558f002c0afc --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/components/shared/Summary/ErrorCountSummaryItemBadge.tsx @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import styled from 'styled-components'; +import { EuiBadge } from '@elastic/eui'; +import euiThemeLight from '@elastic/eui/dist/eui_theme_light.json'; +import { px } from '../../../../public/style/variables'; +import { units } from '../../../style/variables'; + +interface Props { + count: number; +} + +const Badge = styled(EuiBadge)` + margin-top: ${px(units.eighth)}; +`; + +export const ErrorCountSummaryItemBadge = ({ count }: Props) => ( + + {i18n.translate('xpack.apm.transactionDetails.errorCount', { + defaultMessage: + '{errorCount, number} {errorCount, plural, one {Error} other {Errors}}', + values: { errorCount: count } + })} + +); diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Summary/TransactionSummary.tsx b/x-pack/legacy/plugins/apm/public/components/shared/Summary/TransactionSummary.tsx index 8b7380a18edc3..51da61cd7c1a6 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/Summary/TransactionSummary.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/Summary/TransactionSummary.tsx @@ -8,7 +8,7 @@ import { Transaction } from '../../../../typings/es_schemas/ui/Transaction'; import { Summary } from './'; import { TimestampTooltip } from '../TimestampTooltip'; import { DurationSummaryItem } from './DurationSummaryItem'; -import { ErrorCountSummaryItem } from './ErrorCountSummaryItem'; +import { ErrorCountSummaryItemBadge } from './ErrorCountSummaryItemBadge'; import { isRumAgentName } from '../../../../common/agent_name'; import { HttpInfoSummaryItem } from './HttpInfoSummaryItem'; import { TransactionResultSummaryItem } from './TransactionResultSummaryItem'; @@ -54,7 +54,7 @@ const TransactionSummary = ({ parentType="trace" />, getTransactionResultSummaryItem(transaction), - errorCount ? : null, + errorCount ? : null, transaction.user_agent ? ( ) : null diff --git a/x-pack/legacy/plugins/apm/public/components/shared/Summary/__test__/ErrorCountSummaryItemBadge.test.tsx b/x-pack/legacy/plugins/apm/public/components/shared/Summary/__test__/ErrorCountSummaryItemBadge.test.tsx new file mode 100644 index 0000000000000..33f5752b6389b --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/components/shared/Summary/__test__/ErrorCountSummaryItemBadge.test.tsx @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { ErrorCountSummaryItemBadge } from '../ErrorCountSummaryItemBadge'; +import { render } from '@testing-library/react'; +import { expectTextsInDocument } from '../../../../utils/testHelpers'; + +describe('ErrorCountSummaryItemBadge', () => { + it('shows singular error message', () => { + const component = render(); + expectTextsInDocument(component, ['1 Error']); + }); + it('shows plural error message', () => { + const component = render(); + expectTextsInDocument(component, ['2 Errors']); + }); +}); diff --git a/x-pack/legacy/plugins/apm/public/components/shared/charts/CustomPlot/AnnotationsPlot.tsx b/x-pack/legacy/plugins/apm/public/components/shared/charts/CustomPlot/AnnotationsPlot.tsx index fb087612f8e3d..6eff4759b2e7c 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/charts/CustomPlot/AnnotationsPlot.tsx +++ b/x-pack/legacy/plugins/apm/public/components/shared/charts/CustomPlot/AnnotationsPlot.tsx @@ -65,7 +65,7 @@ export function AnnotationsPlot(props: Props) { } > - +
))} diff --git a/x-pack/legacy/plugins/apm/public/components/shared/charts/CustomPlot/Legends.js b/x-pack/legacy/plugins/apm/public/components/shared/charts/CustomPlot/Legends.js index 848c975942ff6..c4d16c9fcf7dd 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/charts/CustomPlot/Legends.js +++ b/x-pack/legacy/plugins/apm/public/components/shared/charts/CustomPlot/Legends.js @@ -7,7 +7,7 @@ import PropTypes from 'prop-types'; import React from 'react'; import styled from 'styled-components'; -import Legend from '../Legend'; +import { Legend } from '../Legend'; import { unit, units, @@ -129,7 +129,7 @@ export default function Legends({ } indicator={() => (
- +
)} disabled={!showAnnotations} diff --git a/x-pack/legacy/plugins/apm/public/components/shared/charts/CustomPlot/test/__snapshots__/CustomPlot.test.js.snap b/x-pack/legacy/plugins/apm/public/components/shared/charts/CustomPlot/test/__snapshots__/CustomPlot.test.js.snap index c46cbbbcccc0b..557751a0f0226 100644 --- a/x-pack/legacy/plugins/apm/public/components/shared/charts/CustomPlot/test/__snapshots__/CustomPlot.test.js.snap +++ b/x-pack/legacy/plugins/apm/public/components/shared/charts/CustomPlot/test/__snapshots__/CustomPlot.test.js.snap @@ -2725,11 +2725,14 @@ Array [ @@ -2763,11 +2766,14 @@ Array [ @@ -2794,11 +2800,14 @@ Array [ @@ -5167,11 +5176,14 @@ Array [ Avg. @@ -5210,11 +5222,14 @@ Array [ 95th @@ -5253,11 +5268,14 @@ Array [ 99th @@ -5886,11 +5904,14 @@ Array [ @@ -5924,11 +5945,14 @@ Array [ @@ -5955,11 +5979,14 @@ Array [ diff --git a/x-pack/legacy/plugins/apm/public/components/shared/charts/Legend/index.js b/x-pack/legacy/plugins/apm/public/components/shared/charts/Legend/index.js deleted file mode 100644 index 601482430b00f..0000000000000 --- a/x-pack/legacy/plugins/apm/public/components/shared/charts/Legend/index.js +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React, { PureComponent } from 'react'; -import styled from 'styled-components'; -import { units, px, fontSizes } from '../../../../style/variables'; -import theme from '@elastic/eui/dist/eui_theme_light.json'; - -const Container = styled.div` - display: flex; - align-items: center; - font-size: ${props => props.fontSize}; - color: ${theme.euiColorDarkShade}; - cursor: ${props => (props.clickable ? 'pointer' : 'initial')}; - opacity: ${props => (props.disabled ? 0.4 : 1)}; - user-select: none; -`; - -export const Indicator = styled.span` - width: ${props => px(props.radius)}; - height: ${props => px(props.radius)}; - margin-right: ${props => px(props.radius / 2)}; - background: ${props => props.color}; - border-radius: 100%; -`; - -export default class Legend extends PureComponent { - render() { - const { - onClick, - text, - color = theme.euiColorVis1, - fontSize = fontSizes.small, - radius = units.minus - 1, - disabled = false, - clickable = false, - indicator, - ...rest - } = this.props; - return ( - - {indicator ? indicator() : } - {text} - - ); - } -} diff --git a/x-pack/legacy/plugins/apm/public/components/shared/charts/Legend/index.tsx b/x-pack/legacy/plugins/apm/public/components/shared/charts/Legend/index.tsx new file mode 100644 index 0000000000000..436b020bc9eba --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/components/shared/charts/Legend/index.tsx @@ -0,0 +1,94 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import theme from '@elastic/eui/dist/eui_theme_light.json'; +import React from 'react'; +import styled from 'styled-components'; +import { fontSizes, px, units } from '../../../../style/variables'; + +export enum Shape { + circle = 'circle', + square = 'square' +} + +interface ContainerProps { + onClick: (e: Event) => void; + fontSize?: string; + clickable: boolean; + disabled: boolean; +} +const Container = styled.div` + display: flex; + align-items: center; + font-size: ${props => props.fontSize}; + color: ${theme.euiColorDarkShade}; + cursor: ${props => (props.clickable ? 'pointer' : 'initial')}; + opacity: ${props => (props.disabled ? 0.4 : 1)}; + user-select: none; +`; + +interface IndicatorProps { + radius: number; + color: string; + shape: Shape; + withMargin: boolean; +} +export const Indicator = styled.span` + width: ${props => px(props.radius)}; + height: ${props => px(props.radius)}; + margin-right: ${props => (props.withMargin ? px(props.radius / 2) : 0)}; + background: ${props => props.color}; + border-radius: ${props => { + return props.shape === Shape.circle ? '100%' : '0'; + }}; +`; + +interface Props { + onClick?: any; + text?: string; + color?: string; + fontSize?: string; + radius?: number; + disabled?: boolean; + clickable?: boolean; + shape?: Shape; + indicator?: () => React.ReactNode; +} + +export const Legend: React.FC = ({ + onClick, + text, + color = theme.euiColorVis1, + fontSize = fontSizes.small, + radius = units.minus - 1, + disabled = false, + clickable = false, + shape = Shape.circle, + indicator, + ...rest +}) => { + return ( + + {indicator ? ( + indicator() + ) : ( + + )} + {text} + + ); +}; diff --git a/x-pack/legacy/plugins/apm/public/components/shared/charts/Timeline/AgentMarker.js b/x-pack/legacy/plugins/apm/public/components/shared/charts/Timeline/AgentMarker.js deleted file mode 100644 index 8ee23d61fe0eb..0000000000000 --- a/x-pack/legacy/plugins/apm/public/components/shared/charts/Timeline/AgentMarker.js +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import PropTypes from 'prop-types'; -import { EuiToolTip } from '@elastic/eui'; -import Legend from '../Legend'; -import { units, px } from '../../../../style/variables'; -import styled from 'styled-components'; -import { asDuration } from '../../../../utils/formatters'; -import theme from '@elastic/eui/dist/eui_theme_light.json'; - -const NameContainer = styled.div` - border-bottom: 1px solid ${theme.euiColorMediumShade}; - padding-bottom: ${px(units.half)}; -`; - -const TimeContainer = styled.div` - color: ${theme.euiColorMediumShade}; - padding-top: ${px(units.half)}; -`; - -export default function AgentMarker({ agentMark, x }) { - const legendWidth = 11; - return ( -
- - {agentMark.name} - {asDuration(agentMark.us)} -
- } - > - -
- - ); -} - -AgentMarker.propTypes = { - agentMark: PropTypes.object.isRequired, - x: PropTypes.number.isRequired -}; diff --git a/x-pack/legacy/plugins/apm/public/components/shared/charts/Timeline/Marker/AgentMarker.tsx b/x-pack/legacy/plugins/apm/public/components/shared/charts/Timeline/Marker/AgentMarker.tsx new file mode 100644 index 0000000000000..ffdbfe6cce7ec --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/components/shared/charts/Timeline/Marker/AgentMarker.tsx @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiToolTip } from '@elastic/eui'; +import theme from '@elastic/eui/dist/eui_theme_light.json'; +import React from 'react'; +import styled from 'styled-components'; +import { px, units } from '../../../../../style/variables'; +import { asDuration } from '../../../../../utils/formatters'; +import { Legend } from '../../Legend'; +import { AgentMark } from '../../../../app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Marks/get_agent_marks'; + +const NameContainer = styled.div` + border-bottom: 1px solid ${theme.euiColorMediumShade}; + padding-bottom: ${px(units.half)}; +`; + +const TimeContainer = styled.div` + color: ${theme.euiColorMediumShade}; + padding-top: ${px(units.half)}; +`; + +interface Props { + mark: AgentMark; +} + +export const AgentMarker: React.FC = ({ mark }) => { + return ( + <> + + {mark.id} + {asDuration(mark.offset)} + + } + > + + + + ); +}; diff --git a/x-pack/legacy/plugins/apm/public/components/shared/charts/Timeline/Marker/ErrorMarker.tsx b/x-pack/legacy/plugins/apm/public/components/shared/charts/Timeline/Marker/ErrorMarker.tsx new file mode 100644 index 0000000000000..51368a4fb946d --- /dev/null +++ b/x-pack/legacy/plugins/apm/public/components/shared/charts/Timeline/Marker/ErrorMarker.tsx @@ -0,0 +1,103 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiPopover, EuiText } from '@elastic/eui'; +import theme from '@elastic/eui/dist/eui_theme_light.json'; +import React, { useState } from 'react'; +import styled from 'styled-components'; +import { + TRACE_ID, + TRANSACTION_ID +} from '../../../../../../common/elasticsearch_fieldnames'; +import { useUrlParams } from '../../../../../hooks/useUrlParams'; +import { px, unit, units } from '../../../../../style/variables'; +import { asDuration } from '../../../../../utils/formatters'; +import { ErrorMark } from '../../../../app/TransactionDetails/WaterfallWithSummmary/WaterfallContainer/Marks/get_error_marks'; +import { ErrorDetailLink } from '../../../Links/apm/ErrorDetailLink'; +import { Legend, Shape } from '../../Legend'; + +interface Props { + mark: ErrorMark; +} + +const Popover = styled.div` + max-width: ${px(280)}; +`; + +const TimeLegend = styled(Legend)` + margin-bottom: ${px(unit)}; +`; + +const ErrorLink = styled(ErrorDetailLink)` + display: block; + margin: ${px(units.half)} 0 ${px(units.half)} 0; +`; + +const Button = styled(Legend)` + height: 20px; + display: flex; + align-items: flex-end; +`; + +export const ErrorMarker: React.FC = ({ mark }) => { + const { urlParams } = useUrlParams(); + const [isPopoverOpen, showPopover] = useState(false); + + const togglePopover = () => showPopover(!isPopoverOpen); + + const button = ( + @@ -386,4 +396,4 @@ - + diff --git a/x-pack/legacy/plugins/graph/public/components/app.tsx b/x-pack/legacy/plugins/graph/public/components/app.tsx index 5ff7fc2e5da93..957a8f66907a1 100644 --- a/x-pack/legacy/plugins/graph/public/components/app.tsx +++ b/x-pack/legacy/plugins/graph/public/components/app.tsx @@ -16,6 +16,7 @@ import { FieldManager } from './field_manager'; import { SearchBarProps, SearchBar } from './search_bar'; import { GraphStore } from '../state_management'; import { GuidancePanel } from './guidance_panel'; +import { GraphTitle } from './graph_title'; import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public'; @@ -52,6 +53,7 @@ export function GraphApp(props: GraphAppProps) { > <> + {props.isInitialized && }
diff --git a/x-pack/legacy/plugins/graph/public/components/field_manager/field_manager.test.tsx b/x-pack/legacy/plugins/graph/public/components/field_manager/field_manager.test.tsx index 2f38f68b01f16..d7cbcf59213be 100644 --- a/x-pack/legacy/plugins/graph/public/components/field_manager/field_manager.test.tsx +++ b/x-pack/legacy/plugins/graph/public/components/field_manager/field_manager.test.tsx @@ -33,6 +33,7 @@ describe('field_manager', () => { selected: true, type: 'string', hopSize: 5, + aggregatable: true, }, { name: 'field2', @@ -42,6 +43,7 @@ describe('field_manager', () => { type: 'string', hopSize: 0, lastValidHopSize: 5, + aggregatable: false, }, { name: 'field3', @@ -50,6 +52,16 @@ describe('field_manager', () => { selected: false, type: 'string', hopSize: 5, + aggregatable: true, + }, + { + name: 'field4', + color: 'orange', + icon: getSuitableIcon('field4'), + selected: false, + type: 'string', + hopSize: 5, + aggregatable: false, }, ]) ); @@ -86,6 +98,17 @@ describe('field_manager', () => { ).toEqual('field2'); }); + it('should show selected non-aggregatable fields in picker, but hide unselected ones', () => { + expect( + getInstance() + .find(FieldPicker) + .dive() + .find(EuiSelectable) + .prop('options') + .map((option: { label: string }) => option.label) + ).toEqual(['field1', 'field2', 'field3']); + }); + it('should select fields from picker', () => { expect( getInstance() @@ -130,6 +153,25 @@ describe('field_manager', () => { expect(getInstance().find(FieldEditor).length).toEqual(1); }); + it('should show remove non-aggregatable fields from picker after deselection', () => { + act(() => { + getInstance() + .find(FieldEditor) + .at(1) + .dive() + .find(EuiContextMenu) + .prop('panels')![0].items![2].onClick!({} as any); + }); + expect( + getInstance() + .find(FieldPicker) + .dive() + .find(EuiSelectable) + .prop('options') + .map((option: { label: string }) => option.label) + ).toEqual(['field1', 'field3']); + }); + it('should disable field', () => { const toggleItem = getInstance() .find(FieldEditor) diff --git a/x-pack/legacy/plugins/graph/public/components/field_manager/field_picker.tsx b/x-pack/legacy/plugins/graph/public/components/field_manager/field_picker.tsx index 6ad792defb669..b38e3f8430980 100644 --- a/x-pack/legacy/plugins/graph/public/components/field_manager/field_picker.tsx +++ b/x-pack/legacy/plugins/graph/public/components/field_manager/field_picker.tsx @@ -114,9 +114,26 @@ export function FieldPicker({ function toOptions( fields: WorkspaceField[] ): Array<{ label: string; checked?: 'on' | 'off'; prepend?: ReactNode }> { - return fields.map(field => ({ - label: field.name, - prepend: , - checked: field.selected ? 'on' : undefined, - })); + return ( + fields + // don't show non-aggregatable fields, except for the case when they are already selected. + // this is necessary to ensure backwards compatibility with existing workspaces that might + // contain non-aggregatable fields. + .filter(field => isExplorable(field) || field.selected) + .map(field => ({ + label: field.name, + prepend: , + checked: field.selected ? 'on' : undefined, + })) + ); +} + +const explorableTypes = ['string', 'number', 'date', 'ip', 'boolean']; + +function isExplorable(field: WorkspaceField) { + if (!field.aggregatable) { + return false; + } + + return explorableTypes.includes(field.type); } diff --git a/x-pack/legacy/plugins/graph/public/components/graph_title.tsx b/x-pack/legacy/plugins/graph/public/components/graph_title.tsx new file mode 100644 index 0000000000000..8151900da0c07 --- /dev/null +++ b/x-pack/legacy/plugins/graph/public/components/graph_title.tsx @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { connect } from 'react-redux'; +import { EuiScreenReaderOnly } from '@elastic/eui'; +import React from 'react'; + +import { GraphState, metaDataSelector } from '../state_management'; + +interface GraphTitleProps { + title: string; +} + +/** + * Component showing the title of the current workspace as a heading visible for screen readers + */ +export const GraphTitle = connect((state: GraphState) => ({ + title: metaDataSelector(state).title, +}))(({ title }: GraphTitleProps) => ( + +

{title}

+
+)); diff --git a/x-pack/legacy/plugins/graph/public/components/guidance_panel/_guidance_panel.scss b/x-pack/legacy/plugins/graph/public/components/guidance_panel/_guidance_panel.scss index f1c332eba1aa8..e1423b794dcd3 100644 --- a/x-pack/legacy/plugins/graph/public/components/guidance_panel/_guidance_panel.scss +++ b/x-pack/legacy/plugins/graph/public/components/guidance_panel/_guidance_panel.scss @@ -15,16 +15,10 @@ position: relative; padding-left: $euiSizeXL; margin-bottom: $euiSizeL; - - button { - // make buttons wrap lines like regular text - display: contents; - } } .gphGuidancePanel__item--disabled { color: $euiColorDarkShade; - pointer-events: none; button { color: $euiColorDarkShade !important; diff --git a/x-pack/legacy/plugins/graph/public/components/guidance_panel/guidance_panel.tsx b/x-pack/legacy/plugins/graph/public/components/guidance_panel/guidance_panel.tsx index 5fae9720db39a..f34b82d6bb1a3 100644 --- a/x-pack/legacy/plugins/graph/public/components/guidance_panel/guidance_panel.tsx +++ b/x-pack/legacy/plugins/graph/public/components/guidance_panel/guidance_panel.tsx @@ -13,6 +13,7 @@ import { EuiText, EuiLink, EuiCallOut, + EuiScreenReaderOnly, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import classNames from 'classnames'; @@ -53,6 +54,7 @@ function ListItem({ 'gphGuidancePanel__item--disabled': state === 'disabled', })} aria-disabled={state === 'disabled'} + aria-current={state === 'active' ? 'step' : undefined} > {state !== 'disabled' && ( -

+

{i18n.translate('xpack.graph.guidancePanel.title', { defaultMessage: 'Three steps to your graph', })} @@ -104,7 +106,7 @@ function GuidancePanelComponent(props: GuidancePanelProps) { -
    +
      {i18n.translate( @@ -116,7 +118,7 @@ function GuidancePanelComponent(props: GuidancePanelProps) { - + {i18n.translate('xpack.graph.guidancePanel.fieldsItem.fieldsButtonLabel', { defaultMessage: 'Add fields.', })} @@ -128,7 +130,7 @@ function GuidancePanelComponent(props: GuidancePanelProps) { defaultMessage="Enter a query in the search bar to start exploring. Don't know where to start? {topTerms}." values={{ topTerms: ( - + {i18n.translate('xpack.graph.guidancePanel.nodesItem.topTermsButtonLabel', { defaultMessage: 'Graph the top terms', })} @@ -137,7 +139,7 @@ function GuidancePanelComponent(props: GuidancePanelProps) { }} /> -
+
@@ -157,7 +159,15 @@ function GuidancePanelComponent(props: GuidancePanelProps) { title={i18n.translate('xpack.graph.noDataSourceNotificationMessageTitle', { defaultMessage: 'No data source', })} + heading="h1" > + +

+ {i18n.translate('xpack.graph.noDataSourceNotificationMessageTitle', { + defaultMessage: 'No data source', + })} +

+

+

-

+ } />
@@ -88,12 +89,12 @@ function getNoItemsMessage( +

-

+ } body={ diff --git a/x-pack/legacy/plugins/graph/public/components/search_bar.test.tsx b/x-pack/legacy/plugins/graph/public/components/search_bar.test.tsx index 9dec725a7a2a1..360561df71957 100644 --- a/x-pack/legacy/plugins/graph/public/components/search_bar.test.tsx +++ b/x-pack/legacy/plugins/graph/public/components/search_bar.test.tsx @@ -9,8 +9,7 @@ import { SearchBar, OuterSearchBarProps } from './search_bar'; import React, { ReactElement } from 'react'; import { CoreStart } from 'src/core/public'; import { act } from 'react-dom/test-utils'; -import { IndexPattern } from 'src/legacy/core_plugins/data/public'; -import { QueryStringInput } from '../../../../../../src/plugins/data/public'; +import { IndexPattern, QueryStringInput } from '../../../../../../src/plugins/data/public'; import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public'; import { I18nProvider } from '@kbn/i18n/react'; diff --git a/x-pack/legacy/plugins/graph/public/components/search_bar.tsx b/x-pack/legacy/plugins/graph/public/components/search_bar.tsx index 83686d6e8f416..c7c5830cadfe1 100644 --- a/x-pack/legacy/plugins/graph/public/components/search_bar.tsx +++ b/x-pack/legacy/plugins/graph/public/components/search_bar.tsx @@ -10,7 +10,6 @@ import React, { useState, useEffect } from 'react'; import { i18n } from '@kbn/i18n'; import { connect } from 'react-redux'; import { IndexPatternSavedObject, IndexPatternProvider } from '../types'; -import { IndexPattern } from '../../../../../../src/legacy/core_plugins/data/public'; import { openSourceModal } from '../services/source_modal'; import { GraphState, @@ -21,6 +20,7 @@ import { import { useKibana } from '../../../../../../src/plugins/kibana_react/public'; import { + IndexPattern, QueryStringInput, IDataPluginServices, Query, diff --git a/x-pack/legacy/plugins/graph/public/components/settings/settings.test.tsx b/x-pack/legacy/plugins/graph/public/components/settings/settings.test.tsx index a615901f40e25..0109e1f5a5ac7 100644 --- a/x-pack/legacy/plugins/graph/public/components/settings/settings.test.tsx +++ b/x-pack/legacy/plugins/graph/public/components/settings/settings.test.tsx @@ -112,6 +112,7 @@ describe('settings', () => { code: '1', label: 'test', }, + aggregatable: true, }, { selected: false, @@ -123,6 +124,7 @@ describe('settings', () => { code: '1', label: 'test', }, + aggregatable: true, }, ]) ); diff --git a/x-pack/legacy/plugins/graph/public/services/fetch_top_nodes.test.ts b/x-pack/legacy/plugins/graph/public/services/fetch_top_nodes.test.ts index 3bfc868fcb06e..79ff4debc7e82 100644 --- a/x-pack/legacy/plugins/graph/public/services/fetch_top_nodes.test.ts +++ b/x-pack/legacy/plugins/graph/public/services/fetch_top_nodes.test.ts @@ -13,8 +13,24 @@ describe('fetch_top_nodes', () => { it('should build terms agg', async () => { const postMock = jest.fn(() => Promise.resolve({ resp: {} })); await fetchTopNodes(postMock as any, 'test', [ - { color: '', hopSize: 5, icon, name: 'field1', selected: false, type: 'string' }, - { color: '', hopSize: 5, icon, name: 'field2', selected: false, type: 'string' }, + { + color: '', + hopSize: 5, + icon, + name: 'field1', + selected: false, + type: 'string', + aggregatable: true, + }, + { + color: '', + hopSize: 5, + icon, + name: 'field2', + selected: false, + type: 'string', + aggregatable: true, + }, ]); expect(postMock).toHaveBeenCalledWith('../api/graph/searchProxy', { body: JSON.stringify({ @@ -65,8 +81,24 @@ describe('fetch_top_nodes', () => { }) ); const result = await fetchTopNodes(postMock as any, 'test', [ - { color: 'red', hopSize: 5, icon, name: 'field1', selected: false, type: 'string' }, - { color: 'blue', hopSize: 5, icon, name: 'field2', selected: false, type: 'string' }, + { + color: 'red', + hopSize: 5, + icon, + name: 'field1', + selected: false, + type: 'string', + aggregatable: true, + }, + { + color: 'blue', + hopSize: 5, + icon, + name: 'field2', + selected: false, + type: 'string', + aggregatable: true, + }, ]); expect(result.length).toEqual(4); expect(result[0]).toEqual({ diff --git a/x-pack/legacy/plugins/graph/public/services/index_pattern_cache.ts b/x-pack/legacy/plugins/graph/public/services/index_pattern_cache.ts index e4e02f860db14..9bbda0b551193 100644 --- a/x-pack/legacy/plugins/graph/public/services/index_pattern_cache.ts +++ b/x-pack/legacy/plugins/graph/public/services/index_pattern_cache.ts @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IndexPattern } from 'src/legacy/core_plugins/data/public'; import { IndexPatternProvider } from '../types'; +import { IndexPattern } from '../../../../../../src/plugins/data/public'; export function createCachedIndexPatternProvider( indexPatternGetter: (id: string) => Promise diff --git a/x-pack/legacy/plugins/graph/public/services/persistence/deserialize.test.ts b/x-pack/legacy/plugins/graph/public/services/persistence/deserialize.test.ts index c7f8b72cc1abf..1861479f85f18 100644 --- a/x-pack/legacy/plugins/graph/public/services/persistence/deserialize.test.ts +++ b/x-pack/legacy/plugins/graph/public/services/persistence/deserialize.test.ts @@ -6,9 +6,9 @@ import { GraphWorkspaceSavedObject, Workspace } from '../../types'; import { savedWorkspaceToAppState } from './deserialize'; -import { IndexPattern } from 'src/legacy/core_plugins/data/public'; import { createWorkspace } from '../../angular/graph_client_workspace'; import { outlinkEncoders } from '../../helpers/outlink_encoders'; +import { IndexPattern } from '../../../../../../../src/plugins/data/public'; describe('deserialize', () => { let savedWorkspace: GraphWorkspaceSavedObject; @@ -119,9 +119,9 @@ describe('deserialize', () => { savedWorkspace, { getNonScriptedFields: () => [ - { name: 'field1', type: 'string' }, - { name: 'field2', type: 'string' }, - { name: 'field3', type: 'string' }, + { name: 'field1', type: 'string', aggregatable: true }, + { name: 'field2', type: 'string', aggregatable: true }, + { name: 'field3', type: 'string', aggregatable: true }, ], } as IndexPattern, workspace @@ -140,6 +140,7 @@ describe('deserialize', () => { expect(allFields).toMatchInlineSnapshot(` Array [ Object { + "aggregatable": true, "color": "black", "hopSize": undefined, "icon": undefined, @@ -149,6 +150,7 @@ describe('deserialize', () => { "type": "string", }, Object { + "aggregatable": true, "color": "black", "hopSize": undefined, "icon": undefined, @@ -158,6 +160,7 @@ describe('deserialize', () => { "type": "string", }, Object { + "aggregatable": true, "color": "#CE0060", "hopSize": 5, "icon": Object { diff --git a/x-pack/legacy/plugins/graph/public/services/persistence/deserialize.ts b/x-pack/legacy/plugins/graph/public/services/persistence/deserialize.ts index 9a13da65e976a..43425077cc174 100644 --- a/x-pack/legacy/plugins/graph/public/services/persistence/deserialize.ts +++ b/x-pack/legacy/plugins/graph/public/services/persistence/deserialize.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IndexPattern } from 'src/legacy/core_plugins/data/public/'; import { SerializedNode, UrlTemplate, @@ -25,6 +24,7 @@ import { colorChoices, iconChoicesByClass, } from '../../helpers/style_choices'; +import { IndexPattern } from '../../../../../../../src/plugins/data/public'; const defaultAdvancedSettings: AdvancedSettings = { useSignificance: true, @@ -89,6 +89,7 @@ export function mapFields(indexPattern: IndexPattern): WorkspaceField[] { color: colorChoices[index % colorChoices.length], selected: false, type: field.type, + aggregatable: Boolean(field.aggregatable), })) .sort((a, b) => { if (a.name < b.name) { diff --git a/x-pack/legacy/plugins/graph/public/services/persistence/serialize.test.ts b/x-pack/legacy/plugins/graph/public/services/persistence/serialize.test.ts index 0e0c750383a71..a3942eccfdac3 100644 --- a/x-pack/legacy/plugins/graph/public/services/persistence/serialize.test.ts +++ b/x-pack/legacy/plugins/graph/public/services/persistence/serialize.test.ts @@ -41,6 +41,7 @@ describe('serialize', () => { name: 'field1', selected: true, type: 'string', + aggregatable: true, }, { color: 'black', @@ -48,6 +49,7 @@ describe('serialize', () => { name: 'field2', selected: true, type: 'string', + aggregatable: true, }, ], selectedIndex: { diff --git a/x-pack/legacy/plugins/graph/public/state_management/datasource.sagas.ts b/x-pack/legacy/plugins/graph/public/state_management/datasource.sagas.ts index 7afea3d2a765a..34d39e71dec55 100644 --- a/x-pack/legacy/plugins/graph/public/state_management/datasource.sagas.ts +++ b/x-pack/legacy/plugins/graph/public/state_management/datasource.sagas.ts @@ -6,7 +6,6 @@ import { takeLatest, put, call, select } from 'redux-saga/effects'; import { i18n } from '@kbn/i18n'; -import { IndexPattern } from 'src/legacy/core_plugins/data/public'; import { Action } from 'typescript-fsa'; import { GraphStoreDependencies } from './store'; import { loadFields } from './fields'; @@ -18,6 +17,7 @@ import { setDatasource, requestDatasource, } from './datasource'; +import { IndexPattern } from '../../../../../../src/plugins/data/public'; /** * Saga loading field information when the datasource is switched. This will overwrite current settings diff --git a/x-pack/legacy/plugins/graph/public/state_management/datasource.test.ts b/x-pack/legacy/plugins/graph/public/state_management/datasource.test.ts index 7e51e463f9f61..041098a9aaae5 100644 --- a/x-pack/legacy/plugins/graph/public/state_management/datasource.test.ts +++ b/x-pack/legacy/plugins/graph/public/state_management/datasource.test.ts @@ -10,7 +10,7 @@ import { datasourceSelector, requestDatasource } from './datasource'; import { datasourceSaga } from './datasource.sagas'; import { fieldsSelector } from './fields'; import { updateSettings } from './advanced_settings'; -import { IndexPattern } from 'src/legacy/core_plugins/data/public'; +import { IndexPattern } from '../../../../../../src/plugins/data/public'; const waitForPromise = () => new Promise(r => setTimeout(r)); diff --git a/x-pack/legacy/plugins/graph/public/state_management/mocks.ts b/x-pack/legacy/plugins/graph/public/state_management/mocks.ts index 9672edef31d6f..5a4f0d033aa42 100644 --- a/x-pack/legacy/plugins/graph/public/state_management/mocks.ts +++ b/x-pack/legacy/plugins/graph/public/state_management/mocks.ts @@ -4,13 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IndexPattern } from 'src/legacy/core_plugins/data/public'; import { NotificationsStart, HttpStart } from 'kibana/public'; import createSagaMiddleware from 'redux-saga'; import { createStore, applyMiddleware, AnyAction } from 'redux'; import { ChromeStart } from 'kibana/public'; import { GraphStoreDependencies, createRootReducer, GraphStore, GraphState } from './store'; import { Workspace, GraphWorkspaceSavedObject, IndexPatternSavedObject } from '../types'; +import { IndexPattern } from '../../../../../../src/plugins/data/public'; jest.mock('ui/new_platform'); diff --git a/x-pack/legacy/plugins/graph/public/types/app_state.ts b/x-pack/legacy/plugins/graph/public/types/app_state.ts index 24b1876bb713f..876f2cf23b53a 100644 --- a/x-pack/legacy/plugins/graph/public/types/app_state.ts +++ b/x-pack/legacy/plugins/graph/public/types/app_state.ts @@ -5,9 +5,9 @@ */ import { SimpleSavedObject } from 'src/core/public'; -import { IndexPattern } from 'src/legacy/core_plugins/data/public'; import { FontawesomeIcon } from '../helpers/style_choices'; import { OutlinkEncoder } from '../helpers/outlink_encoders'; +import { IndexPattern } from '../../../../../../src/plugins/data/public'; export interface UrlTemplate { url: string; @@ -25,6 +25,7 @@ export interface WorkspaceField { icon: FontawesomeIcon; selected: boolean; type: string; + aggregatable: boolean; } export interface AdvancedSettings { diff --git a/x-pack/legacy/plugins/graph/public/types/persistence.ts b/x-pack/legacy/plugins/graph/public/types/persistence.ts index 7fc5e15d9ea72..adb07605b61c4 100644 --- a/x-pack/legacy/plugins/graph/public/types/persistence.ts +++ b/x-pack/legacy/plugins/graph/public/types/persistence.ts @@ -37,7 +37,7 @@ export interface SerializedUrlTemplate extends Omit { +export interface SerializedField extends Omit { iconClass: string; } diff --git a/x-pack/legacy/plugins/index_lifecycle_management/__jest__/components/edit_policy.test.js b/x-pack/legacy/plugins/index_lifecycle_management/__jest__/components/edit_policy.test.js index 9463eccb93a02..2c0ea7fe699b8 100644 --- a/x-pack/legacy/plugins/index_lifecycle_management/__jest__/components/edit_policy.test.js +++ b/x-pack/legacy/plugins/index_lifecycle_management/__jest__/components/edit_policy.test.js @@ -20,8 +20,8 @@ import sinon from 'sinon'; import { findTestSubject } from '@elastic/eui/lib/test'; import { positiveNumbersAboveZeroErrorMessage, - numberRequiredMessage, positiveNumberRequiredMessage, + numberRequiredMessage, maximumAgeRequiredMessage, maximumSizeRequiredMessage, policyNameRequiredMessage, @@ -243,17 +243,18 @@ describe('edit policy', () => { noRollover(rendered); setPolicyName(rendered, 'mypolicy'); activatePhase(rendered, 'warm'); + setPhaseAfter(rendered, 'warm', ''); save(rendered); expectedErrorMessages(rendered, [numberRequiredMessage]); }); - test('should show positive number required above zero error when trying to save warm phase with 0 for after', () => { + test('should allow 0 for phase timing', () => { const rendered = mountWithIntl(component); noRollover(rendered); setPolicyName(rendered, 'mypolicy'); activatePhase(rendered, 'warm'); setPhaseAfter(rendered, 'warm', 0); save(rendered); - expectedErrorMessages(rendered, [positiveNumbersAboveZeroErrorMessage]); + expectedErrorMessages(rendered, []); }); test('should show positive number required error when trying to save warm phase with -1 for after', () => { const rendered = mountWithIntl(component); @@ -383,14 +384,14 @@ describe('edit policy', () => { }); }); describe('cold phase', () => { - test('should show positive number required error when trying to save cold phase with 0 for after', () => { + test('should allow 0 for phase timing', () => { const rendered = mountWithIntl(component); noRollover(rendered); setPolicyName(rendered, 'mypolicy'); activatePhase(rendered, 'cold'); setPhaseAfter(rendered, 'cold', 0); save(rendered); - expectedErrorMessages(rendered, [positiveNumbersAboveZeroErrorMessage]); + expectedErrorMessages(rendered, []); }); test('should show positive number required error when trying to save cold phase with -1 for after', () => { const rendered = mountWithIntl(component); @@ -464,14 +465,14 @@ describe('edit policy', () => { }); }); describe('delete phase', () => { - test('should show positive number required error when trying to save delete phase with 0 for after', () => { + test('should allow 0 for phase timing', () => { const rendered = mountWithIntl(component); noRollover(rendered); setPolicyName(rendered, 'mypolicy'); activatePhase(rendered, 'delete'); setPhaseAfter(rendered, 'delete', 0); save(rendered); - expectedErrorMessages(rendered, [positiveNumbersAboveZeroErrorMessage]); + expectedErrorMessages(rendered, []); }); test('should show positive number required error when trying to save delete phase with -1 for after', () => { const rendered = mountWithIntl(component); diff --git a/x-pack/legacy/plugins/index_lifecycle_management/public/sections/edit_policy/components/min_age_input.js b/x-pack/legacy/plugins/index_lifecycle_management/public/sections/edit_policy/components/min_age_input.js index 0ed28bbaa905f..b4c9f4e958cd2 100644 --- a/x-pack/legacy/plugins/index_lifecycle_management/public/sections/edit_policy/components/min_age_input.js +++ b/x-pack/legacy/plugins/index_lifecycle_management/public/sections/edit_policy/components/min_age_input.js @@ -131,7 +131,7 @@ export const MinAgeInput = props => { onChange={async e => { setPhaseData(PHASE_ROLLOVER_MINIMUM_AGE, e.target.value); }} - min={1} + min={0} />
diff --git a/x-pack/legacy/plugins/index_lifecycle_management/public/store/defaults/cold_phase.js b/x-pack/legacy/plugins/index_lifecycle_management/public/store/defaults/cold_phase.js index b0af0e6547803..a8f7fd3f4bdfa 100644 --- a/x-pack/legacy/plugins/index_lifecycle_management/public/store/defaults/cold_phase.js +++ b/x-pack/legacy/plugins/index_lifecycle_management/public/store/defaults/cold_phase.js @@ -17,7 +17,7 @@ import { export const defaultColdPhase = { [PHASE_ENABLED]: false, [PHASE_ROLLOVER_ALIAS]: '', - [PHASE_ROLLOVER_MINIMUM_AGE]: '', + [PHASE_ROLLOVER_MINIMUM_AGE]: 0, [PHASE_ROLLOVER_MINIMUM_AGE_UNITS]: 'd', [PHASE_NODE_ATTRS]: '', [PHASE_REPLICA_COUNT]: '', diff --git a/x-pack/legacy/plugins/index_lifecycle_management/public/store/defaults/delete_phase.js b/x-pack/legacy/plugins/index_lifecycle_management/public/store/defaults/delete_phase.js index 5a44539ff90f8..b5296cd83fabd 100644 --- a/x-pack/legacy/plugins/index_lifecycle_management/public/store/defaults/delete_phase.js +++ b/x-pack/legacy/plugins/index_lifecycle_management/public/store/defaults/delete_phase.js @@ -15,7 +15,7 @@ export const defaultDeletePhase = { [PHASE_ENABLED]: false, [PHASE_ROLLOVER_ENABLED]: false, [PHASE_ROLLOVER_ALIAS]: '', - [PHASE_ROLLOVER_MINIMUM_AGE]: '', + [PHASE_ROLLOVER_MINIMUM_AGE]: 0, [PHASE_ROLLOVER_MINIMUM_AGE_UNITS]: 'd', }; export const defaultEmptyDeletePhase = defaultDeletePhase; diff --git a/x-pack/legacy/plugins/index_lifecycle_management/public/store/defaults/warm_phase.js b/x-pack/legacy/plugins/index_lifecycle_management/public/store/defaults/warm_phase.js index d3dc55178b253..f02ac2096675f 100644 --- a/x-pack/legacy/plugins/index_lifecycle_management/public/store/defaults/warm_phase.js +++ b/x-pack/legacy/plugins/index_lifecycle_management/public/store/defaults/warm_phase.js @@ -23,7 +23,7 @@ export const defaultWarmPhase = { [PHASE_ROLLOVER_ALIAS]: '', [PHASE_FORCE_MERGE_SEGMENTS]: '', [PHASE_FORCE_MERGE_ENABLED]: false, - [PHASE_ROLLOVER_MINIMUM_AGE]: '', + [PHASE_ROLLOVER_MINIMUM_AGE]: 0, [PHASE_ROLLOVER_MINIMUM_AGE_UNITS]: 'd', [PHASE_NODE_ATTRS]: '', [PHASE_SHRINK_ENABLED]: false, diff --git a/x-pack/legacy/plugins/index_lifecycle_management/public/store/selectors/lifecycle.js b/x-pack/legacy/plugins/index_lifecycle_management/public/store/selectors/lifecycle.js index 026845c78ee66..750a7feb19c3d 100644 --- a/x-pack/legacy/plugins/index_lifecycle_management/public/store/selectors/lifecycle.js +++ b/x-pack/legacy/plugins/index_lifecycle_management/public/store/selectors/lifecycle.js @@ -120,12 +120,6 @@ export const validatePhase = (type, phase, errors) => { phaseErrors[numberedAttribute] = [numberRequiredMessage]; } else if (phase[numberedAttribute] < 0) { phaseErrors[numberedAttribute] = [positiveNumberRequiredMessage]; - } else if ( - (numberedAttribute === PHASE_ROLLOVER_MINIMUM_AGE || - numberedAttribute === PHASE_PRIMARY_SHARD_COUNT) && - phase[numberedAttribute] < 1 - ) { - phaseErrors[numberedAttribute] = [positiveNumbersAboveZeroErrorMessage]; } } } diff --git a/x-pack/legacy/plugins/infra/common/ecs_allowed_list.ts b/x-pack/legacy/plugins/infra/common/ecs_allowed_list.ts index 1728cd1fa4b45..f1d0577b4cb19 100644 --- a/x-pack/legacy/plugins/infra/common/ecs_allowed_list.ts +++ b/x-pack/legacy/plugins/infra/common/ecs_allowed_list.ts @@ -3,7 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { first } from 'lodash'; +import { first, memoize } from 'lodash'; export const ECS_ALLOWED_LIST = [ 'host', @@ -46,8 +46,9 @@ export const DOCKER_ALLOWED_LIST = [ ]; export const AWS_S3_ALLOWED_LIST = ['aws.s3']; +export const AWS_METRICS_ALLOWED_LIST = ['aws.cloudwatch']; -export const getAllowedListForPrefix = (prefix: string) => { +export const getAllowedListForPrefix = memoize((prefix: string) => { const firstPart = first(prefix.split(/\./)); const defaultAllowedList = prefix ? [...ECS_ALLOWED_LIST, prefix] : ECS_ALLOWED_LIST; switch (firstPart) { @@ -61,7 +62,10 @@ export const getAllowedListForPrefix = (prefix: string) => { if (prefix === 'aws.s3_daily_storage') { return [...defaultAllowedList, ...AWS_S3_ALLOWED_LIST]; } + if (prefix === 'aws.metrics') { + return [...defaultAllowedList, ...AWS_METRICS_ALLOWED_LIST]; + } default: return defaultAllowedList; } -}; +}); diff --git a/x-pack/legacy/plugins/infra/common/http_api/log_analysis/results/index.ts b/x-pack/legacy/plugins/infra/common/http_api/log_analysis/results/index.ts index 1749421277719..d9ca9a96ffe51 100644 --- a/x-pack/legacy/plugins/infra/common/http_api/log_analysis/results/index.ts +++ b/x-pack/legacy/plugins/infra/common/http_api/log_analysis/results/index.ts @@ -4,4 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ +export * from './log_entry_categories'; +export * from './log_entry_category_datasets'; export * from './log_entry_rate'; diff --git a/x-pack/legacy/plugins/infra/common/http_api/log_analysis/results/log_entry_categories.ts b/x-pack/legacy/plugins/infra/common/http_api/log_analysis/results/log_entry_categories.ts new file mode 100644 index 0000000000000..66823c25237ac --- /dev/null +++ b/x-pack/legacy/plugins/infra/common/http_api/log_analysis/results/log_entry_categories.ts @@ -0,0 +1,109 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as rt from 'io-ts'; + +import { + badRequestErrorRT, + forbiddenErrorRT, + timeRangeRT, + routeTimingMetadataRT, +} from '../../shared'; + +export const LOG_ANALYSIS_GET_LOG_ENTRY_CATEGORIES_PATH = + '/api/infra/log_analysis/results/log_entry_categories'; + +/** + * request + */ + +const logEntryCategoriesHistogramParametersRT = rt.type({ + id: rt.string, + timeRange: timeRangeRT, + bucketCount: rt.number, +}); + +export type LogEntryCategoriesHistogramParameters = rt.TypeOf< + typeof logEntryCategoriesHistogramParametersRT +>; + +export const getLogEntryCategoriesRequestPayloadRT = rt.type({ + data: rt.intersection([ + rt.type({ + // the number of categories to fetch + categoryCount: rt.number, + // the id of the source configuration + sourceId: rt.string, + // the time range to fetch the categories from + timeRange: timeRangeRT, + // a list of histograms to create + histograms: rt.array(logEntryCategoriesHistogramParametersRT), + }), + rt.partial({ + // the datasets to filter for (optional, unfiltered if not present) + datasets: rt.array(rt.string), + }), + ]), +}); + +export type GetLogEntryCategoriesRequestPayload = rt.TypeOf< + typeof getLogEntryCategoriesRequestPayloadRT +>; + +/** + * response + */ + +export const logEntryCategoryHistogramBucketRT = rt.type({ + startTime: rt.number, + bucketDuration: rt.number, + logEntryCount: rt.number, +}); + +export type LogEntryCategoryHistogramBucket = rt.TypeOf; + +export const logEntryCategoryHistogramRT = rt.type({ + histogramId: rt.string, + buckets: rt.array(logEntryCategoryHistogramBucketRT), +}); + +export type LogEntryCategoryHistogram = rt.TypeOf; + +export const logEntryCategoryRT = rt.type({ + categoryId: rt.number, + datasets: rt.array(rt.string), + histograms: rt.array(logEntryCategoryHistogramRT), + logEntryCount: rt.number, + maximumAnomalyScore: rt.number, + regularExpression: rt.string, +}); + +export type LogEntryCategory = rt.TypeOf; + +export const getLogEntryCategoriesSuccessReponsePayloadRT = rt.intersection([ + rt.type({ + data: rt.type({ + categories: rt.array(logEntryCategoryRT), + }), + }), + rt.partial({ + timing: routeTimingMetadataRT, + }), +]); + +export type GetLogEntryCategoriesSuccessResponsePayload = rt.TypeOf< + typeof getLogEntryCategoriesSuccessReponsePayloadRT +>; + +export const getLogEntryCategoriesResponsePayloadRT = rt.union([ + getLogEntryCategoriesSuccessReponsePayloadRT, + badRequestErrorRT, + forbiddenErrorRT, +]); + +export type GetLogEntryCategoriesReponsePayload = rt.TypeOf< + typeof getLogEntryCategoriesResponsePayloadRT +>; diff --git a/x-pack/legacy/plugins/infra/common/http_api/log_analysis/results/log_entry_category_datasets.ts b/x-pack/legacy/plugins/infra/common/http_api/log_analysis/results/log_entry_category_datasets.ts new file mode 100644 index 0000000000000..934d1052fa29f --- /dev/null +++ b/x-pack/legacy/plugins/infra/common/http_api/log_analysis/results/log_entry_category_datasets.ts @@ -0,0 +1,63 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as rt from 'io-ts'; + +import { + badRequestErrorRT, + forbiddenErrorRT, + timeRangeRT, + routeTimingMetadataRT, +} from '../../shared'; + +export const LOG_ANALYSIS_GET_LOG_ENTRY_CATEGORY_DATASETS_PATH = + '/api/infra/log_analysis/results/log_entry_category_datasets'; + +/** + * request + */ + +export const getLogEntryCategoryDatasetsRequestPayloadRT = rt.type({ + data: rt.type({ + // the id of the source configuration + sourceId: rt.string, + // the time range to fetch the category datasets from + timeRange: timeRangeRT, + }), +}); + +export type GetLogEntryCategoryDatasetsRequestPayload = rt.TypeOf< + typeof getLogEntryCategoryDatasetsRequestPayloadRT +>; + +/** + * response + */ + +export const getLogEntryCategoryDatasetsSuccessReponsePayloadRT = rt.intersection([ + rt.type({ + data: rt.type({ + datasets: rt.array(rt.string), + }), + }), + rt.partial({ + timing: routeTimingMetadataRT, + }), +]); + +export type GetLogEntryCategoryDatasetsSuccessResponsePayload = rt.TypeOf< + typeof getLogEntryCategoryDatasetsSuccessReponsePayloadRT +>; + +export const getLogEntryCategoryDatasetsResponsePayloadRT = rt.union([ + getLogEntryCategoryDatasetsSuccessReponsePayloadRT, + badRequestErrorRT, + forbiddenErrorRT, +]); + +export type GetLogEntryCategoryDatasetsReponsePayload = rt.TypeOf< + typeof getLogEntryCategoryDatasetsResponsePayloadRT +>; diff --git a/x-pack/legacy/plugins/infra/common/http_api/log_entries/common.ts b/x-pack/legacy/plugins/infra/common/http_api/log_entries/common.ts index 3eb7e278bf99c..0b31222322007 100644 --- a/x-pack/legacy/plugins/infra/common/http_api/log_entries/common.ts +++ b/x-pack/legacy/plugins/infra/common/http_api/log_entries/common.ts @@ -10,3 +10,4 @@ export const logEntriesCursorRT = rt.type({ time: rt.number, tiebreaker: rt.number, }); +export type LogEntriesCursor = rt.TypeOf; diff --git a/x-pack/legacy/plugins/infra/common/http_api/log_entries/entries.ts b/x-pack/legacy/plugins/infra/common/http_api/log_entries/entries.ts new file mode 100644 index 0000000000000..97bdad23beb24 --- /dev/null +++ b/x-pack/legacy/plugins/infra/common/http_api/log_entries/entries.ts @@ -0,0 +1,94 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as rt from 'io-ts'; +import { logEntriesCursorRT } from './common'; + +export const LOG_ENTRIES_PATH = '/api/log_entries/entries'; + +export const logEntriesBaseRequestRT = rt.intersection([ + rt.type({ + sourceId: rt.string, + startDate: rt.number, + endDate: rt.number, + }), + rt.partial({ + query: rt.string, + size: rt.number, + }), +]); + +export const logEntriesBeforeRequestRT = rt.intersection([ + logEntriesBaseRequestRT, + rt.type({ before: rt.union([logEntriesCursorRT, rt.literal('last')]) }), +]); + +export const logEntriesAfterRequestRT = rt.intersection([ + logEntriesBaseRequestRT, + rt.type({ after: rt.union([logEntriesCursorRT, rt.literal('first')]) }), +]); + +export const logEntriesCenteredRT = rt.intersection([ + logEntriesBaseRequestRT, + rt.type({ center: logEntriesCursorRT }), +]); + +export const logEntriesRequestRT = rt.union([ + logEntriesBaseRequestRT, + logEntriesBeforeRequestRT, + logEntriesAfterRequestRT, + logEntriesCenteredRT, +]); + +export type LogEntriesRequest = rt.TypeOf; + +// JSON value +const valueRT = rt.union([rt.string, rt.number, rt.boolean, rt.object, rt.null, rt.undefined]); + +export const logMessagePartRT = rt.union([ + rt.type({ + constant: rt.string, + }), + rt.type({ + field: rt.string, + value: valueRT, + highlights: rt.array(rt.string), + }), +]); + +export const logColumnRT = rt.union([ + rt.type({ columnId: rt.string, timestamp: rt.number }), + rt.type({ + columnId: rt.string, + field: rt.string, + value: rt.union([rt.string, rt.undefined]), + highlights: rt.array(rt.string), + }), + rt.type({ + columnId: rt.string, + message: rt.array(logMessagePartRT), + }), +]); + +export const logEntryRT = rt.type({ + id: rt.string, + cursor: logEntriesCursorRT, + columns: rt.array(logColumnRT), +}); + +export type LogMessagepart = rt.TypeOf; +export type LogColumn = rt.TypeOf; +export type LogEntry = rt.TypeOf; + +export const logEntriesResponseRT = rt.type({ + data: rt.type({ + entries: rt.array(logEntryRT), + topCursor: logEntriesCursorRT, + bottomCursor: logEntriesCursorRT, + }), +}); + +export type LogEntriesResponse = rt.TypeOf; diff --git a/x-pack/legacy/plugins/infra/common/http_api/log_entries/highlights.ts b/x-pack/legacy/plugins/infra/common/http_api/log_entries/highlights.ts new file mode 100644 index 0000000000000..516cd67f2764d --- /dev/null +++ b/x-pack/legacy/plugins/infra/common/http_api/log_entries/highlights.ts @@ -0,0 +1,62 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as rt from 'io-ts'; +import { + logEntriesBaseRequestRT, + logEntriesBeforeRequestRT, + logEntriesAfterRequestRT, + logEntriesCenteredRT, + logEntryRT, +} from './entries'; +import { logEntriesCursorRT } from './common'; + +export const LOG_ENTRIES_HIGHLIGHTS_PATH = '/api/log_entries/highlights'; + +const highlightsRT = rt.type({ + highlightTerms: rt.array(rt.string), +}); + +export const logEntriesHighlightsBaseRequestRT = rt.intersection([ + logEntriesBaseRequestRT, + highlightsRT, +]); + +export const logEntriesHighlightsBeforeRequestRT = rt.intersection([ + logEntriesBeforeRequestRT, + highlightsRT, +]); + +export const logEntriesHighlightsAfterRequestRT = rt.intersection([ + logEntriesAfterRequestRT, + highlightsRT, +]); + +export const logEntriesHighlightsCenteredRequestRT = rt.intersection([ + logEntriesCenteredRT, + highlightsRT, +]); + +export const logEntriesHighlightsRequestRT = rt.union([ + logEntriesHighlightsBaseRequestRT, + logEntriesHighlightsBeforeRequestRT, + logEntriesHighlightsAfterRequestRT, + logEntriesHighlightsCenteredRequestRT, +]); + +export type LogEntriesHighlightsRequest = rt.TypeOf; + +export const logEntriesHighlightsResponseRT = rt.type({ + data: rt.array( + rt.type({ + topCursor: logEntriesCursorRT, + bottomCursor: logEntriesCursorRT, + entries: rt.array(logEntryRT), + }) + ), +}); + +export type LogEntriesHighlightsResponse = rt.TypeOf; diff --git a/x-pack/legacy/plugins/infra/common/http_api/log_entries/index.ts b/x-pack/legacy/plugins/infra/common/http_api/log_entries/index.ts index 8fed914c3dc8c..490f295cbff68 100644 --- a/x-pack/legacy/plugins/infra/common/http_api/log_entries/index.ts +++ b/x-pack/legacy/plugins/infra/common/http_api/log_entries/index.ts @@ -4,6 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ +export * from './common'; +export * from './entries'; +export * from './highlights'; export * from './item'; export * from './summary'; export * from './summary_highlights'; diff --git a/x-pack/legacy/plugins/infra/common/http_api/node_details_api.ts b/x-pack/legacy/plugins/infra/common/http_api/node_details_api.ts index 46aab881bce4c..0ef5ae82baeb9 100644 --- a/x-pack/legacy/plugins/infra/common/http_api/node_details_api.ts +++ b/x-pack/legacy/plugins/infra/common/http_api/node_details_api.ts @@ -50,6 +50,6 @@ export const NodeDetailsRequestRT = rt.intersection([ ]); // export type NodeDetailsRequest = InfraWrappableRequest; - +export type NodeDetailsMetricData = rt.TypeOf; export type NodeDetailsRequest = rt.TypeOf; export type NodeDetailsMetricDataResponse = rt.TypeOf; diff --git a/x-pack/legacy/plugins/infra/common/http_api/shared/index.ts b/x-pack/legacy/plugins/infra/common/http_api/shared/index.ts index 1047ca2f2a01a..caeb1914cb8a2 100644 --- a/x-pack/legacy/plugins/infra/common/http_api/shared/index.ts +++ b/x-pack/legacy/plugins/infra/common/http_api/shared/index.ts @@ -7,3 +7,4 @@ export * from './errors'; export * from './metric_statistics'; export * from './time_range'; +export * from './timing'; diff --git a/x-pack/legacy/plugins/infra/common/http_api/shared/timing.ts b/x-pack/legacy/plugins/infra/common/http_api/shared/timing.ts new file mode 100644 index 0000000000000..a208921c03d6f --- /dev/null +++ b/x-pack/legacy/plugins/infra/common/http_api/shared/timing.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as rt from 'io-ts'; + +import { tracingSpanRT } from '../../performance_tracing'; + +export const routeTimingMetadataRT = rt.type({ + spans: rt.array(tracingSpanRT), +}); diff --git a/x-pack/legacy/plugins/infra/common/http_api/snapshot_api.ts b/x-pack/legacy/plugins/infra/common/http_api/snapshot_api.ts index 4ee0c9e23b68f..c7c15fd8af161 100644 --- a/x-pack/legacy/plugins/infra/common/http_api/snapshot_api.ts +++ b/x-pack/legacy/plugins/infra/common/http_api/snapshot_api.ts @@ -19,7 +19,7 @@ export const SnapshotNodePathRT = rt.intersection([ const SnapshotNodeMetricOptionalRT = rt.partial({ value: rt.union([rt.number, rt.null]), - average: rt.union([rt.number, rt.null]), + avg: rt.union([rt.number, rt.null]), max: rt.union([rt.number, rt.null]), }); @@ -27,8 +27,12 @@ const SnapshotNodeMetricRequiredRT = rt.type({ name: SnapshotMetricTypeRT, }); +export const SnapshotNodeMetricRT = rt.intersection([ + SnapshotNodeMetricRequiredRT, + SnapshotNodeMetricOptionalRT, +]); export const SnapshotNodeRT = rt.type({ - metric: rt.intersection([SnapshotNodeMetricRequiredRT, SnapshotNodeMetricOptionalRT]), + metric: SnapshotNodeMetricRT, path: rt.array(SnapshotNodePathRT), }); @@ -43,18 +47,24 @@ export const InfraTimerangeInputRT = rt.type({ from: rt.number, }); +export const SnapshotGroupByRT = rt.array( + rt.partial({ + label: rt.union([rt.string, rt.null]), + field: rt.union([rt.string, rt.null]), + }) +); + +export const SnapshotMetricInputRT = rt.type({ + type: SnapshotMetricTypeRT, +}); + export const SnapshotRequestRT = rt.intersection([ rt.type({ timerange: InfraTimerangeInputRT, metric: rt.type({ type: SnapshotMetricTypeRT, }), - groupBy: rt.array( - rt.partial({ - label: rt.union([rt.string, rt.null]), - field: rt.union([rt.string, rt.null]), - }) - ), + groupBy: SnapshotGroupByRT, nodeType: ItemTypeRT, sourceId: rt.string, }), @@ -65,6 +75,11 @@ export const SnapshotRequestRT = rt.intersection([ }), ]); +export type SnapshotNodePath = rt.TypeOf; +export type SnapshotMetricInput = rt.TypeOf; +export type InfraTimerangeInput = rt.TypeOf; +export type SnapshotNodeMetric = rt.TypeOf; +export type SnapshotGroupBy = rt.TypeOf; export type SnapshotRequest = rt.TypeOf; export type SnapshotNode = rt.TypeOf; export type SnapshotNodeResponse = rt.TypeOf; diff --git a/x-pack/legacy/plugins/infra/common/inventory_models/aws_ec2/toolbar_items.tsx b/x-pack/legacy/plugins/infra/common/inventory_models/aws_ec2/toolbar_items.tsx index 490b5c552dcc3..4b16728a52bec 100644 --- a/x-pack/legacy/plugins/infra/common/inventory_models/aws_ec2/toolbar_items.tsx +++ b/x-pack/legacy/plugins/infra/common/inventory_models/aws_ec2/toolbar_items.tsx @@ -7,16 +7,16 @@ import React from 'react'; import { ToolbarProps } from '../../../public/components/inventory/toolbars/toolbar'; import { MetricsAndGroupByToolbarItems } from '../shared/compontents/metrics_and_groupby_toolbar_items'; -import { InfraSnapshotMetricType } from '../../graphql/types'; import { CloudToolbarItems } from '../shared/compontents/cloud_toolbar_items'; +import { SnapshotMetricType } from '../types'; export const AwsEC2ToolbarItems = (props: ToolbarProps) => { - const metricTypes = [ - InfraSnapshotMetricType.cpu, - InfraSnapshotMetricType.rx, - InfraSnapshotMetricType.tx, - InfraSnapshotMetricType.diskIOReadBytes, - InfraSnapshotMetricType.diskIOWriteBytes, + const metricTypes: SnapshotMetricType[] = [ + 'cpu', + 'rx', + 'tx', + 'diskIOReadBytes', + 'diskIOWriteBytes', ]; const groupByFields = [ 'cloud.availability_zone', diff --git a/x-pack/legacy/plugins/infra/common/inventory_models/aws_rds/toolbar_items.tsx b/x-pack/legacy/plugins/infra/common/inventory_models/aws_rds/toolbar_items.tsx index 86ed57e8f4c7f..e4654825a8de1 100644 --- a/x-pack/legacy/plugins/infra/common/inventory_models/aws_rds/toolbar_items.tsx +++ b/x-pack/legacy/plugins/infra/common/inventory_models/aws_rds/toolbar_items.tsx @@ -6,17 +6,18 @@ import React from 'react'; import { ToolbarProps } from '../../../public/components/inventory/toolbars/toolbar'; -import { InfraSnapshotMetricType } from '../../../public/graphql/types'; + import { MetricsAndGroupByToolbarItems } from '../shared/compontents/metrics_and_groupby_toolbar_items'; import { CloudToolbarItems } from '../shared/compontents/cloud_toolbar_items'; +import { SnapshotMetricType } from '../types'; export const AwsRDSToolbarItems = (props: ToolbarProps) => { - const metricTypes = [ - InfraSnapshotMetricType.cpu, - InfraSnapshotMetricType.rdsConnections, - InfraSnapshotMetricType.rdsQueriesExecuted, - InfraSnapshotMetricType.rdsActiveTransactions, - InfraSnapshotMetricType.rdsLatency, + const metricTypes: SnapshotMetricType[] = [ + 'cpu', + 'rdsConnections', + 'rdsQueriesExecuted', + 'rdsActiveTransactions', + 'rdsLatency', ]; const groupByFields = [ 'cloud.availability_zone', diff --git a/x-pack/legacy/plugins/infra/common/inventory_models/aws_s3/toolbar_items.tsx b/x-pack/legacy/plugins/infra/common/inventory_models/aws_s3/toolbar_items.tsx index 276b6b83eb43d..d1aa48260db47 100644 --- a/x-pack/legacy/plugins/infra/common/inventory_models/aws_s3/toolbar_items.tsx +++ b/x-pack/legacy/plugins/infra/common/inventory_models/aws_s3/toolbar_items.tsx @@ -6,17 +6,17 @@ import React from 'react'; import { ToolbarProps } from '../../../public/components/inventory/toolbars/toolbar'; -import { InfraSnapshotMetricType } from '../../../public/graphql/types'; import { MetricsAndGroupByToolbarItems } from '../shared/compontents/metrics_and_groupby_toolbar_items'; import { CloudToolbarItems } from '../shared/compontents/cloud_toolbar_items'; +import { SnapshotMetricType } from '../types'; export const AwsS3ToolbarItems = (props: ToolbarProps) => { - const metricTypes = [ - InfraSnapshotMetricType.s3BucketSize, - InfraSnapshotMetricType.s3NumberOfObjects, - InfraSnapshotMetricType.s3TotalRequests, - InfraSnapshotMetricType.s3DownloadBytes, - InfraSnapshotMetricType.s3UploadBytes, + const metricTypes: SnapshotMetricType[] = [ + 's3BucketSize', + 's3NumberOfObjects', + 's3TotalRequests', + 's3DownloadBytes', + 's3UploadBytes', ]; const groupByFields = ['cloud.region']; return ( diff --git a/x-pack/legacy/plugins/infra/common/inventory_models/aws_sqs/toolbar_items.tsx b/x-pack/legacy/plugins/infra/common/inventory_models/aws_sqs/toolbar_items.tsx index 67baa22a5e6b0..9c5ff51b2d9a2 100644 --- a/x-pack/legacy/plugins/infra/common/inventory_models/aws_sqs/toolbar_items.tsx +++ b/x-pack/legacy/plugins/infra/common/inventory_models/aws_sqs/toolbar_items.tsx @@ -7,16 +7,16 @@ import React from 'react'; import { ToolbarProps } from '../../../public/components/inventory/toolbars/toolbar'; import { MetricsAndGroupByToolbarItems } from '../shared/compontents/metrics_and_groupby_toolbar_items'; -import { InfraSnapshotMetricType } from '../../graphql/types'; import { CloudToolbarItems } from '../shared/compontents/cloud_toolbar_items'; +import { SnapshotMetricType } from '../types'; export const AwsSQSToolbarItems = (props: ToolbarProps) => { - const metricTypes = [ - InfraSnapshotMetricType.sqsMessagesVisible, - InfraSnapshotMetricType.sqsMessagesDelayed, - InfraSnapshotMetricType.sqsMessagesSent, - InfraSnapshotMetricType.sqsMessagesEmpty, - InfraSnapshotMetricType.sqsOldestMessage, + const metricTypes: SnapshotMetricType[] = [ + 'sqsMessagesVisible', + 'sqsMessagesDelayed', + 'sqsMessagesSent', + 'sqsMessagesEmpty', + 'sqsOldestMessage', ]; const groupByFields = ['cloud.region']; return ( diff --git a/x-pack/legacy/plugins/infra/common/inventory_models/container/toolbar_items.tsx b/x-pack/legacy/plugins/infra/common/inventory_models/container/toolbar_items.tsx index 9ed2cbe6dea08..f1e7ea721cabd 100644 --- a/x-pack/legacy/plugins/infra/common/inventory_models/container/toolbar_items.tsx +++ b/x-pack/legacy/plugins/infra/common/inventory_models/container/toolbar_items.tsx @@ -7,15 +7,10 @@ import React from 'react'; import { ToolbarProps } from '../../../public/components/inventory/toolbars/toolbar'; import { MetricsAndGroupByToolbarItems } from '../shared/compontents/metrics_and_groupby_toolbar_items'; -import { InfraSnapshotMetricType } from '../../graphql/types'; +import { SnapshotMetricType } from '../types'; export const ContainerToolbarItems = (props: ToolbarProps) => { - const metricTypes = [ - InfraSnapshotMetricType.cpu, - InfraSnapshotMetricType.memory, - InfraSnapshotMetricType.rx, - InfraSnapshotMetricType.tx, - ]; + const metricTypes: SnapshotMetricType[] = ['cpu', 'memory', 'rx', 'tx']; const groupByFields = [ 'host.name', 'cloud.availability_zone', diff --git a/x-pack/legacy/plugins/infra/common/inventory_models/host/toolbar_items.tsx b/x-pack/legacy/plugins/infra/common/inventory_models/host/toolbar_items.tsx index f8df81a33a8ec..6e0563f2f555b 100644 --- a/x-pack/legacy/plugins/infra/common/inventory_models/host/toolbar_items.tsx +++ b/x-pack/legacy/plugins/infra/common/inventory_models/host/toolbar_items.tsx @@ -7,17 +7,10 @@ import React from 'react'; import { ToolbarProps } from '../../../public/components/inventory/toolbars/toolbar'; import { MetricsAndGroupByToolbarItems } from '../shared/compontents/metrics_and_groupby_toolbar_items'; -import { InfraSnapshotMetricType } from '../../graphql/types'; +import { SnapshotMetricType } from '../types'; export const HostToolbarItems = (props: ToolbarProps) => { - const metricTypes = [ - InfraSnapshotMetricType.cpu, - InfraSnapshotMetricType.memory, - InfraSnapshotMetricType.load, - InfraSnapshotMetricType.rx, - InfraSnapshotMetricType.tx, - InfraSnapshotMetricType.logRate, - ]; + const metricTypes: SnapshotMetricType[] = ['cpu', 'memory', 'load', 'rx', 'tx', 'logRate']; const groupByFields = [ 'cloud.availability_zone', 'cloud.machine.type', diff --git a/x-pack/legacy/plugins/infra/common/inventory_models/pod/toolbar_items.tsx b/x-pack/legacy/plugins/infra/common/inventory_models/pod/toolbar_items.tsx index 9ef4a889dc589..b075f10a15680 100644 --- a/x-pack/legacy/plugins/infra/common/inventory_models/pod/toolbar_items.tsx +++ b/x-pack/legacy/plugins/infra/common/inventory_models/pod/toolbar_items.tsx @@ -7,15 +7,10 @@ import React from 'react'; import { ToolbarProps } from '../../../public/components/inventory/toolbars/toolbar'; import { MetricsAndGroupByToolbarItems } from '../shared/compontents/metrics_and_groupby_toolbar_items'; -import { InfraSnapshotMetricType } from '../../graphql/types'; +import { SnapshotMetricType } from '../types'; export const PodToolbarItems = (props: ToolbarProps) => { - const metricTypes = [ - InfraSnapshotMetricType.cpu, - InfraSnapshotMetricType.memory, - InfraSnapshotMetricType.rx, - InfraSnapshotMetricType.tx, - ]; + const metricTypes: SnapshotMetricType[] = ['cpu', 'memory', 'rx', 'tx']; const groupByFields = ['kubernetes.namespace', 'kubernetes.node.name', 'service.type']; return ( ; - // combines and abstracts job and datafeed status export type JobStatus = | 'unknown' diff --git a/x-pack/legacy/plugins/infra/common/log_analysis/log_analysis_results.ts b/x-pack/legacy/plugins/infra/common/log_analysis/log_analysis_results.ts new file mode 100644 index 0000000000000..1dcd4a10fc4e3 --- /dev/null +++ b/x-pack/legacy/plugins/infra/common/log_analysis/log_analysis_results.ts @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const ML_SEVERITY_SCORES = { + warning: 3, + minor: 25, + major: 50, + critical: 75, +}; + +export type MLSeverityScoreCategories = keyof typeof ML_SEVERITY_SCORES; + +export const ML_SEVERITY_COLORS = { + critical: 'rgb(228, 72, 72)', + major: 'rgb(229, 113, 0)', + minor: 'rgb(255, 221, 0)', + warning: 'rgb(125, 180, 226)', +}; + +export const getSeverityCategoryForScore = ( + score: number +): MLSeverityScoreCategories | undefined => { + if (score >= ML_SEVERITY_SCORES.critical) { + return 'critical'; + } else if (score >= ML_SEVERITY_SCORES.major) { + return 'major'; + } else if (score >= ML_SEVERITY_SCORES.minor) { + return 'minor'; + } else if (score >= ML_SEVERITY_SCORES.warning) { + return 'warning'; + } else { + // Category is too low to include + return undefined; + } +}; + +export const formatAnomalyScore = (score: number) => { + return Math.round(score); +}; + +export const getFriendlyNameForPartitionId = (partitionId: string) => { + return partitionId !== '' ? partitionId : 'unknown'; +}; diff --git a/x-pack/legacy/plugins/infra/common/log_analysis/log_entry_categories_analysis.ts b/x-pack/legacy/plugins/infra/common/log_analysis/log_entry_categories_analysis.ts new file mode 100644 index 0000000000000..0957126ee52e3 --- /dev/null +++ b/x-pack/legacy/plugins/infra/common/log_analysis/log_entry_categories_analysis.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as rt from 'io-ts'; + +export const logEntryCategoriesJobTypeRT = rt.keyof({ + 'log-entry-categories-count': null, +}); + +export type LogEntryCategoriesJobType = rt.TypeOf; + +export const logEntryCategoriesJobTypes: LogEntryCategoriesJobType[] = [ + 'log-entry-categories-count', +]; diff --git a/x-pack/legacy/plugins/infra/common/log_analysis/log_entry_rate_analysis.ts b/x-pack/legacy/plugins/infra/common/log_analysis/log_entry_rate_analysis.ts new file mode 100644 index 0000000000000..7fd668dc4ebce --- /dev/null +++ b/x-pack/legacy/plugins/infra/common/log_analysis/log_entry_rate_analysis.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as rt from 'io-ts'; + +export const logEntryRateJobTypeRT = rt.keyof({ + 'log-entry-rate': null, +}); + +export type LogEntryRateJobType = rt.TypeOf; + +export const logEntryRateJobTypes: LogEntryRateJobType[] = ['log-entry-rate']; diff --git a/x-pack/legacy/plugins/infra/common/performance_tracing.ts b/x-pack/legacy/plugins/infra/common/performance_tracing.ts new file mode 100644 index 0000000000000..3e96f3c19d06d --- /dev/null +++ b/x-pack/legacy/plugins/infra/common/performance_tracing.ts @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as rt from 'io-ts'; +import uuid from 'uuid'; + +export const tracingSpanRT = rt.type({ + duration: rt.number, + id: rt.string, + name: rt.string, + start: rt.number, +}); + +export type TracingSpan = rt.TypeOf; + +export type ActiveTrace = (endTime?: number) => TracingSpan; + +export const startTracingSpan = (name: string): ActiveTrace => { + const initialState: TracingSpan = { + duration: Number.POSITIVE_INFINITY, + id: uuid.v4(), + name, + start: Date.now(), + }; + + return (endTime: number = Date.now()) => ({ + ...initialState, + duration: endTime - initialState.start, + }); +}; diff --git a/x-pack/legacy/plugins/infra/common/runtime_types.ts b/x-pack/legacy/plugins/infra/common/runtime_types.ts index 297743f9b3456..d5b858df38def 100644 --- a/x-pack/legacy/plugins/infra/common/runtime_types.ts +++ b/x-pack/legacy/plugins/infra/common/runtime_types.ts @@ -4,11 +4,22 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Errors } from 'io-ts'; +import { fold } from 'fp-ts/lib/Either'; +import { identity } from 'fp-ts/lib/function'; +import { pipe } from 'fp-ts/lib/pipeable'; +import { Errors, Type } from 'io-ts'; import { failure } from 'io-ts/lib/PathReporter'; +type ErrorFactory = (message: string) => Error; + export const createPlainError = (message: string) => new Error(message); -export const throwErrors = (createError: (message: string) => Error) => (errors: Errors) => { +export const throwErrors = (createError: ErrorFactory) => (errors: Errors) => { throw createError(failure(errors).join('\n')); }; + +export const decodeOrThrow = ( + runtimeType: Type, + createError: ErrorFactory = createPlainError +) => (inputValue: I) => + pipe(runtimeType.decode(inputValue), fold(throwErrors(createError), identity)); diff --git a/x-pack/legacy/plugins/infra/public/apps/start_app.tsx b/x-pack/legacy/plugins/infra/public/apps/start_app.tsx index 8ccb051724ede..dbdc827478a45 100644 --- a/x-pack/legacy/plugins/infra/public/apps/start_app.tsx +++ b/x-pack/legacy/plugins/infra/public/apps/start_app.tsx @@ -27,6 +27,7 @@ import { KibanaContextProvider, } from '../../../../../../src/plugins/kibana_react/public'; import { ROOT_ELEMENT_ID } from '../app'; + // NP_TODO: Type plugins export async function startApp(libs: InfraFrontendLibs, core: CoreStart, plugins: any) { const history = createHashHistory(); diff --git a/x-pack/legacy/plugins/infra/public/components/inventory/layout.tsx b/x-pack/legacy/plugins/infra/public/components/inventory/layout.tsx index 2d4205c4a3642..4dd9803c7bfce 100644 --- a/x-pack/legacy/plugins/infra/public/components/inventory/layout.tsx +++ b/x-pack/legacy/plugins/infra/public/components/inventory/layout.tsx @@ -6,11 +6,6 @@ import React from 'react'; import { InfraWaffleMapOptions, InfraWaffleMapBounds } from '../../lib/lib'; -import { - InfraNodeType, - InfraSnapshotMetricInput, - InfraSnapshotGroupbyInput, -} from '../../graphql/types'; import { KueryFilterQuery } from '../../store/local/waffle_filter'; import { NodesOverview } from '../nodes_overview'; @@ -18,10 +13,12 @@ import { Toolbar } from './toolbars/toolbar'; import { PageContent } from '../page'; import { useSnapshot } from '../../containers/waffle/use_snaphot'; import { useInventoryMeta } from '../../containers/inventory_metadata/use_inventory_meta'; +import { SnapshotMetricInput, SnapshotGroupBy } from '../../../common/http_api/snapshot_api'; +import { InventoryItemType } from '../../../common/inventory_models/types'; export interface LayoutProps { options: InfraWaffleMapOptions; - nodeType: InfraNodeType; + nodeType: InventoryItemType; onDrilldown: (filter: KueryFilterQuery) => void; currentTime: number; onViewChange: (view: string) => void; @@ -30,8 +27,8 @@ export interface LayoutProps { autoBounds: boolean; filterQuery: string | null | undefined; - metric: InfraSnapshotMetricInput; - groupBy: InfraSnapshotGroupbyInput[]; + metric: SnapshotMetricInput; + groupBy: SnapshotGroupBy; sourceId: string; accountId: string; region: string; diff --git a/x-pack/legacy/plugins/infra/public/components/inventory/toolbars/toolbar.tsx b/x-pack/legacy/plugins/infra/public/components/inventory/toolbars/toolbar.tsx index 1188e486385ea..ee0eceee6d157 100644 --- a/x-pack/legacy/plugins/infra/public/components/inventory/toolbars/toolbar.tsx +++ b/x-pack/legacy/plugins/infra/public/components/inventory/toolbars/toolbar.tsx @@ -7,13 +7,9 @@ import React, { FunctionComponent } from 'react'; import { Action } from 'typescript-fsa'; import { EuiFlexItem } from '@elastic/eui'; +import { SnapshotMetricInput, SnapshotGroupBy } from '../../../../common/http_api/snapshot_api'; import { InventoryCloudAccount } from '../../../../common/http_api/inventory_meta_api'; import { findToolbar } from '../../../../common/inventory_models/toolbars'; -import { - InfraNodeType, - InfraSnapshotMetricInput, - InfraSnapshotGroupbyInput, -} from '../../../graphql/types'; import { ToolbarWrapper } from './toolbar_wrapper'; import { waffleOptionsSelectors } from '../../../store'; @@ -22,11 +18,12 @@ import { WithWaffleViewState } from '../../../containers/waffle/with_waffle_view import { SavedViewsToolbarControls } from '../../saved_views/toolbar_control'; import { inventoryViewSavedObjectType } from '../../../../common/saved_objects/inventory_view'; import { IIndexPattern } from '../../../../../../../../src/plugins/data/public'; +import { InventoryItemType } from '../../../../common/inventory_models/types'; export interface ToolbarProps { createDerivedIndexPattern: (type: 'logs' | 'metrics' | 'both') => IIndexPattern; - changeMetric: (payload: InfraSnapshotMetricInput) => Action; - changeGroupBy: (payload: InfraSnapshotGroupbyInput[]) => Action; + changeMetric: (payload: SnapshotMetricInput) => Action; + changeGroupBy: (payload: SnapshotGroupBy) => Action; changeCustomOptions: (payload: InfraGroupByOptions[]) => Action; changeAccount: (id: string) => Action; changeRegion: (name: string) => Action; @@ -70,7 +67,7 @@ const wrapToolbarItems = ( }; interface Props { - nodeType: InfraNodeType; + nodeType: InventoryItemType; regions: string[]; accounts: InventoryCloudAccount[]; } diff --git a/x-pack/legacy/plugins/infra/public/components/inventory/toolbars/toolbar_wrapper.tsx b/x-pack/legacy/plugins/infra/public/components/inventory/toolbars/toolbar_wrapper.tsx index 9a31544ce4cce..231030362438f 100644 --- a/x-pack/legacy/plugins/infra/public/components/inventory/toolbars/toolbar_wrapper.tsx +++ b/x-pack/legacy/plugins/infra/public/components/inventory/toolbars/toolbar_wrapper.tsx @@ -7,12 +7,12 @@ import React from 'react'; import { EuiFlexGroup } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; +import { SnapshotMetricType } from '../../../../common/inventory_models/types'; import { WithSource } from '../../../containers/with_source'; import { WithWaffleOptions } from '../../../containers/waffle/with_waffle_options'; import { Toolbar } from '../../eui/toolbar'; import { ToolbarProps } from './toolbar'; import { fieldToName } from '../../waffle/lib/field_to_display_name'; -import { InfraSnapshotMetricType } from '../../../graphql/types'; interface Props { children: (props: Omit) => React.ReactElement; @@ -144,123 +144,125 @@ export const toGroupByOpt = (field: string) => ({ field, }); -export const toMetricOpt = (metric: InfraSnapshotMetricType) => { +export const toMetricOpt = ( + metric: SnapshotMetricType +): { text: string; value: SnapshotMetricType } => { switch (metric) { - case InfraSnapshotMetricType.cpu: + case 'cpu': return { text: ToolbarTranslations.CPUUsage, - value: InfraSnapshotMetricType.cpu, + value: 'cpu', }; - case InfraSnapshotMetricType.memory: + case 'memory': return { text: ToolbarTranslations.MemoryUsage, - value: InfraSnapshotMetricType.memory, + value: 'memory', }; - case InfraSnapshotMetricType.rx: + case 'rx': return { text: ToolbarTranslations.InboundTraffic, - value: InfraSnapshotMetricType.rx, + value: 'rx', }; - case InfraSnapshotMetricType.tx: + case 'tx': return { text: ToolbarTranslations.OutboundTraffic, - value: InfraSnapshotMetricType.tx, + value: 'tx', }; - case InfraSnapshotMetricType.logRate: + case 'logRate': return { text: ToolbarTranslations.LogRate, - value: InfraSnapshotMetricType.logRate, + value: 'logRate', }; - case InfraSnapshotMetricType.load: + case 'load': return { text: ToolbarTranslations.Load, - value: InfraSnapshotMetricType.load, + value: 'load', }; - case InfraSnapshotMetricType.count: + case 'count': return { text: ToolbarTranslations.Count, - value: InfraSnapshotMetricType.count, + value: 'count', }; - case InfraSnapshotMetricType.diskIOReadBytes: + case 'diskIOReadBytes': return { text: ToolbarTranslations.DiskIOReadBytes, - value: InfraSnapshotMetricType.diskIOReadBytes, + value: 'diskIOReadBytes', }; - case InfraSnapshotMetricType.diskIOWriteBytes: + case 'diskIOWriteBytes': return { text: ToolbarTranslations.DiskIOWriteBytes, - value: InfraSnapshotMetricType.diskIOWriteBytes, + value: 'diskIOWriteBytes', }; - case InfraSnapshotMetricType.s3BucketSize: + case 's3BucketSize': return { text: ToolbarTranslations.s3BucketSize, - value: InfraSnapshotMetricType.s3BucketSize, + value: 's3BucketSize', }; - case InfraSnapshotMetricType.s3TotalRequests: + case 's3TotalRequests': return { text: ToolbarTranslations.s3TotalRequests, - value: InfraSnapshotMetricType.s3TotalRequests, + value: 's3TotalRequests', }; - case InfraSnapshotMetricType.s3NumberOfObjects: + case 's3NumberOfObjects': return { text: ToolbarTranslations.s3NumberOfObjects, - value: InfraSnapshotMetricType.s3NumberOfObjects, + value: 's3NumberOfObjects', }; - case InfraSnapshotMetricType.s3DownloadBytes: + case 's3DownloadBytes': return { text: ToolbarTranslations.s3DownloadBytes, - value: InfraSnapshotMetricType.s3DownloadBytes, + value: 's3DownloadBytes', }; - case InfraSnapshotMetricType.s3UploadBytes: + case 's3UploadBytes': return { text: ToolbarTranslations.s3UploadBytes, - value: InfraSnapshotMetricType.s3UploadBytes, + value: 's3UploadBytes', }; - case InfraSnapshotMetricType.rdsConnections: + case 'rdsConnections': return { text: ToolbarTranslations.rdsConnections, - value: InfraSnapshotMetricType.rdsConnections, + value: 'rdsConnections', }; - case InfraSnapshotMetricType.rdsQueriesExecuted: + case 'rdsQueriesExecuted': return { text: ToolbarTranslations.rdsQueriesExecuted, - value: InfraSnapshotMetricType.rdsQueriesExecuted, + value: 'rdsQueriesExecuted', }; - case InfraSnapshotMetricType.rdsActiveTransactions: + case 'rdsActiveTransactions': return { text: ToolbarTranslations.rdsActiveTransactions, - value: InfraSnapshotMetricType.rdsActiveTransactions, + value: 'rdsActiveTransactions', }; - case InfraSnapshotMetricType.rdsLatency: + case 'rdsLatency': return { text: ToolbarTranslations.rdsLatency, - value: InfraSnapshotMetricType.rdsLatency, + value: 'rdsLatency', }; - case InfraSnapshotMetricType.sqsMessagesVisible: + case 'sqsMessagesVisible': return { text: ToolbarTranslations.sqsMessagesVisible, - value: InfraSnapshotMetricType.sqsMessagesVisible, + value: 'sqsMessagesVisible', }; - case InfraSnapshotMetricType.sqsMessagesDelayed: + case 'sqsMessagesDelayed': return { text: ToolbarTranslations.sqsMessagesDelayed, - value: InfraSnapshotMetricType.sqsMessagesDelayed, + value: 'sqsMessagesDelayed', }; - case InfraSnapshotMetricType.sqsMessagesSent: + case 'sqsMessagesSent': return { text: ToolbarTranslations.sqsMessagesSent, - value: InfraSnapshotMetricType.sqsMessagesSent, + value: 'sqsMessagesSent', }; - case InfraSnapshotMetricType.sqsMessagesEmpty: + case 'sqsMessagesEmpty': return { text: ToolbarTranslations.sqsMessagesEmpty, - value: InfraSnapshotMetricType.sqsMessagesEmpty, + value: 'sqsMessagesEmpty', }; - case InfraSnapshotMetricType.sqsOldestMessage: + case 'sqsOldestMessage': return { text: ToolbarTranslations.sqsOldestMessage, - value: InfraSnapshotMetricType.sqsOldestMessage, + value: 'sqsOldestMessage', }; } }; diff --git a/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_job_status/index.ts b/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_job_status/index.ts index 06229a26afd19..e954cf21229ee 100644 --- a/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_job_status/index.ts +++ b/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_job_status/index.ts @@ -5,3 +5,4 @@ */ export * from './log_analysis_job_problem_indicator'; +export * from './recreate_job_button'; diff --git a/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_job_status/log_analysis_job_problem_indicator.tsx b/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_job_status/log_analysis_job_problem_indicator.tsx index 018c5f5e0570d..8a16d819e12c2 100644 --- a/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_job_status/log_analysis_job_problem_indicator.tsx +++ b/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_job_status/log_analysis_job_problem_indicator.tsx @@ -17,13 +17,22 @@ export const LogAnalysisJobProblemIndicator: React.FC<{ onRecreateMlJobForReconfiguration: () => void; onRecreateMlJobForUpdate: () => void; }> = ({ jobStatus, setupStatus, onRecreateMlJobForReconfiguration, onRecreateMlJobForUpdate }) => { - if (jobStatus === 'stopped') { + if (isStopped(jobStatus)) { return ; - } else if (setupStatus === 'skippedButUpdatable') { + } else if (isUpdatable(setupStatus)) { return ; - } else if (setupStatus === 'skippedButReconfigurable') { + } else if (isReconfigurable(setupStatus)) { return ; } return null; // no problem to indicate }; + +const isStopped = (jobStatus: JobStatus) => jobStatus === 'stopped'; + +const isUpdatable = (setupStatus: SetupStatus) => setupStatus === 'skippedButUpdatable'; + +const isReconfigurable = (setupStatus: SetupStatus) => setupStatus === 'skippedButReconfigurable'; + +export const jobHasProblem = (jobStatus: JobStatus, setupStatus: SetupStatus) => + isStopped(jobStatus) || isUpdatable(setupStatus) || isReconfigurable(setupStatus); diff --git a/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_job_status/recreate_job_button.tsx b/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_job_status/recreate_job_button.tsx new file mode 100644 index 0000000000000..74e8d197ef455 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_job_status/recreate_job_button.tsx @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiButton, PropsOf } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import React from 'react'; + +export const RecreateJobButton: React.FunctionComponent> = props => ( + + + +); diff --git a/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_job_status/recreate_job_callout.tsx b/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_job_status/recreate_job_callout.tsx index b95054bbd6a9b..5b872d4ee5147 100644 --- a/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_job_status/recreate_job_callout.tsx +++ b/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_job_status/recreate_job_callout.tsx @@ -5,8 +5,9 @@ */ import React from 'react'; -import { EuiCallOut, EuiButton } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiCallOut } from '@elastic/eui'; + +import { RecreateJobButton } from './recreate_job_button'; export const RecreateJobCallout: React.FC<{ onRecreateMlJob: () => void; @@ -14,11 +15,6 @@ export const RecreateJobCallout: React.FC<{ }> = ({ children, onRecreateMlJob, title }) => (

{children}

- - - +
); diff --git a/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_results/first_use_callout.tsx b/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_results/first_use_callout.tsx new file mode 100644 index 0000000000000..7fcdcc89a633a --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_results/first_use_callout.tsx @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiCallOut } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; + +export const FirstUseCallout = () => { + return ( + +

+ {i18n.translate('xpack.infra.logs.analysis.onboardingSuccessContent', { + defaultMessage: + 'Please allow a few minutes for our machine learning robots to begin collecting data.', + })} +

+
+ ); +}; diff --git a/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_results/index.ts b/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_results/index.ts index 8a4ceb70252a3..a3139124e6c9f 100644 --- a/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_results/index.ts +++ b/x-pack/legacy/plugins/infra/public/components/logging/log_analysis_results/index.ts @@ -5,3 +5,4 @@ */ export * from './analyze_in_ml_button'; +export * from './first_use_callout'; diff --git a/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/column_headers.tsx b/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/column_headers.tsx index bf4a09769d254..6516993397e02 100644 --- a/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/column_headers.tsx +++ b/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/column_headers.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { useContext } from 'react'; import { transparentize } from 'polished'; import euiStyled from '../../../../../../common/eui_styled_components'; @@ -21,28 +21,26 @@ import { LogEntryColumnWidths, } from './log_entry_column'; import { ASSUMED_SCROLLBAR_WIDTH } from './vertical_scroll_panel'; -import { WithLogPosition } from '../../../containers/logs/with_log_position'; +import { LogPositionState } from '../../../containers/logs/log_position'; import { localizedDate } from '../../../utils/formatters/datetime'; export const LogColumnHeaders: React.FunctionComponent<{ columnConfigurations: LogColumnConfiguration[]; columnWidths: LogEntryColumnWidths; }> = ({ columnConfigurations, columnWidths }) => { + const { firstVisiblePosition } = useContext(LogPositionState.Context); return ( {columnConfigurations.map(columnConfiguration => { if (isTimestampLogColumnConfiguration(columnConfiguration)) { return ( - - {({ firstVisiblePosition }) => ( - - {firstVisiblePosition ? localizedDate(firstVisiblePosition.time) : 'Timestamp'} - - )} - + + {firstVisiblePosition ? localizedDate(firstVisiblePosition.time) : 'Timestamp'} + ); } else if (isMessageLogColumnConfiguration(columnConfiguration)) { return ( diff --git a/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/scrollable_log_text_stream_view.tsx b/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/scrollable_log_text_stream_view.tsx index a5b85788fdea9..84bcfa911125a 100644 --- a/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/scrollable_log_text_stream_view.tsx +++ b/x-pack/legacy/plugins/infra/public/components/logging/log_text_stream/scrollable_log_text_stream_view.tsx @@ -55,17 +55,13 @@ interface ScrollableLogTextStreamViewProps { setFlyoutVisibility: (visible: boolean) => void; highlightedItem: string | null; currentHighlightKey: UniqueTimeKey | null; - scrollLock: { - enable: () => void; - disable: () => void; - isEnabled: boolean; - }; } interface ScrollableLogTextStreamViewState { target: TimeKey | null; targetId: string | null; items: StreamItem[]; + isScrollLocked: boolean; } export class ScrollableLogTextStreamView extends React.PureComponent< @@ -81,8 +77,7 @@ export class ScrollableLogTextStreamView extends React.PureComponent< // Prevent new entries from being appended and moving the stream forward when // the user has scrolled up during live streaming - const nextItems = - hasItems && nextProps.scrollLock.isEnabled ? prevState.items : nextProps.items; + const nextItems = hasItems && prevState.isScrollLocked ? prevState.items : nextProps.items; if (nextProps.isStreaming && hasItems) { return { @@ -121,6 +116,7 @@ export class ScrollableLogTextStreamView extends React.PureComponent< target: null, targetId: null, items: props.items, + isScrollLocked: false, }; } @@ -137,9 +133,8 @@ export class ScrollableLogTextStreamView extends React.PureComponent< lastLoadedTime, scale, wrap, - scrollLock, } = this.props; - const { targetId, items } = this.state; + const { targetId, items, isScrollLocked } = this.state; const hasItems = items.length > 0; return ( @@ -187,7 +182,7 @@ export class ScrollableLogTextStreamView extends React.PureComponent< target={targetId} hideScrollbar={true} data-test-subj={'logStream'} - isLocked={scrollLock.isEnabled} + isLocked={isScrollLocked} entriesCount={items.length} > {registerChild => ( @@ -248,7 +243,7 @@ export class ScrollableLogTextStreamView extends React.PureComponent< lastStreamingUpdate={isStreaming ? lastLoadedTime : null} onLoadMore={this.handleLoadNewerItems} /> - {scrollLock.isEnabled && ( + {isScrollLocked && ( { if (fromScroll && this.props.isStreaming) { - this.props.scrollLock[pagesBelow === 0 ? 'disable' : 'enable'](); + this.setState({ + isScrollLocked: pagesBelow !== 0, + }); } this.props.reportVisibleInterval({ endKey: parseStreamItemId(bottomChild), @@ -322,11 +319,11 @@ export class ScrollableLogTextStreamView extends React.PureComponent< ); private handleJumpToTail = () => { - const { items, scrollLock } = this.props; - scrollLock.disable(); + const { items } = this.props; const lastItemTarget = getStreamItemId(items[items.length - 1]); this.setState({ targetId: lastItemTarget, + isScrollLocked: false, }); }; } diff --git a/x-pack/legacy/plugins/infra/public/components/metrics_explorer/chart.tsx b/x-pack/legacy/plugins/infra/public/components/metrics_explorer/chart.tsx index a700a455cd2bb..9491fe3024f94 100644 --- a/x-pack/legacy/plugins/infra/public/components/metrics_explorer/chart.tsx +++ b/x-pack/legacy/plugins/infra/public/components/metrics_explorer/chart.tsx @@ -7,15 +7,7 @@ import React, { useCallback, useMemo } from 'react'; import { EuiTitle, EuiToolTip, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { - Axis, - Chart, - getAxisId, - niceTimeFormatter, - Position, - Settings, - TooltipValue, -} from '@elastic/charts'; +import { Axis, Chart, niceTimeFormatter, Position, Settings, TooltipValue } from '@elastic/charts'; import { first, last } from 'lodash'; import moment from 'moment'; import { MetricsExplorerSeries } from '../../../server/routes/metrics_explorer/types'; @@ -139,13 +131,13 @@ export const MetricsExplorerChart = ({ /> ))} { const toDateStrig = '2019-01-01T12:00:00Z'; const to = DateMath.parse(toDateStrig, { roundUp: true })!; const from = DateMath.parse(fromDateStrig)!; - const link = createNodeDetailLink( - InfraNodeType.host, - 'example-01', - fromDateStrig, - toDateStrig - ); + const link = createNodeDetailLink('host', 'example-01', fromDateStrig, toDateStrig); expect(link).toBe( `#/link-to/host-detail/example-01?to=${to.valueOf()}&from=${from.valueOf()}` ); diff --git a/x-pack/legacy/plugins/infra/public/components/metrics_explorer/chart_context_menu.tsx b/x-pack/legacy/plugins/infra/public/components/metrics_explorer/chart_context_menu.tsx index 597386fc24ee8..298f7dd8f8d17 100644 --- a/x-pack/legacy/plugins/infra/public/components/metrics_explorer/chart_context_menu.tsx +++ b/x-pack/legacy/plugins/infra/public/components/metrics_explorer/chart_context_menu.tsx @@ -21,9 +21,9 @@ import { MetricsExplorerChartOptions, } from '../../containers/metrics_explorer/use_metrics_explorer_options'; import { createTSVBLink } from './helpers/create_tsvb_link'; -import { InfraNodeType } from '../../graphql/types'; import { getNodeDetailUrl } from '../../pages/link_to/redirect_to_node_detail'; import { SourceConfiguration } from '../../utils/source_configuration'; +import { InventoryItemType } from '../../../common/inventory_models/types'; interface Props { options: MetricsExplorerOptions; @@ -35,15 +35,18 @@ interface Props { chartOptions: MetricsExplorerChartOptions; } -const fieldToNodeType = (source: SourceConfiguration, field: string): InfraNodeType | undefined => { +const fieldToNodeType = ( + source: SourceConfiguration, + field: string +): InventoryItemType | undefined => { if (source.fields.host === field) { - return InfraNodeType.host; + return 'host'; } if (source.fields.pod === field) { - return InfraNodeType.pod; + return 'pod'; } if (source.fields.container === field) { - return InfraNodeType.container; + return 'container'; } }; @@ -54,7 +57,7 @@ const dateMathExpressionToEpoch = (dateMathExpression: string, roundUp = false): }; export const createNodeDetailLink = ( - nodeType: InfraNodeType, + nodeType: InventoryItemType, nodeId: string, from: string, to: string diff --git a/x-pack/legacy/plugins/infra/public/components/metrics_explorer/series_chart.tsx b/x-pack/legacy/plugins/infra/public/components/metrics_explorer/series_chart.tsx index 8c3c80e0a3852..f365faa134d93 100644 --- a/x-pack/legacy/plugins/infra/public/components/metrics_explorer/series_chart.tsx +++ b/x-pack/legacy/plugins/infra/public/components/metrics_explorer/series_chart.tsx @@ -7,9 +7,6 @@ import React from 'react'; import { ScaleType, - getSpecId, - DataSeriesColorsValues, - CustomSeriesColorsMap, AreaSeries, BarSeries, RecursivePartial, @@ -45,13 +42,6 @@ export const MetricsExplorerAreaChart = ({ metric, id, series, type, stack }: Pr colorTransformer(MetricsExplorerColor.color0); const yAccessor = `metric_${id}`; - const specId = getSpecId(yAccessor); - const colors: DataSeriesColorsValues = { - colorValues: [], - specId, - }; - const customColors: CustomSeriesColorsMap = new Map(); - customColors.set(colors, color); const chartId = `series-${series.id}-${yAccessor}`; const seriesAreaStyle: RecursivePartial = { @@ -66,8 +56,8 @@ export const MetricsExplorerAreaChart = ({ metric, id, series, type, stack }: Pr }; return ( ); }; @@ -87,13 +77,6 @@ export const MetricsExplorerBarChart = ({ metric, id, series, stack }: Props) => colorTransformer(MetricsExplorerColor.color0); const yAccessor = `metric_${id}`; - const specId = getSpecId(yAccessor); - const colors: DataSeriesColorsValues = { - colorValues: [], - specId, - }; - const customColors: CustomSeriesColorsMap = new Map(); - customColors.set(colors, color); const chartId = `series-${series.id}-${yAccessor}`; const seriesBarStyle: RecursivePartial = { @@ -108,8 +91,8 @@ export const MetricsExplorerBarChart = ({ metric, id, series, stack }: Props) => }; return ( data={series.rows} stackAccessors={stack ? ['timestamp'] : void 0} barSeriesStyle={seriesBarStyle} - customSeriesColors={customColors} + customSeriesColors={[color]} /> ); }; diff --git a/x-pack/legacy/plugins/infra/public/components/nodes_overview/index.tsx b/x-pack/legacy/plugins/infra/public/components/nodes_overview/index.tsx index 21db0c73f612a..8e8015ce6a82e 100644 --- a/x-pack/legacy/plugins/infra/public/components/nodes_overview/index.tsx +++ b/x-pack/legacy/plugins/infra/public/components/nodes_overview/index.tsx @@ -11,7 +11,6 @@ import { get, max, min } from 'lodash'; import React from 'react'; import euiStyled from '../../../../../common/eui_styled_components'; -import { InfraSnapshotMetricType, InfraSnapshotNode, InfraNodeType } from '../../graphql/types'; import { InfraFormatterType, InfraWaffleMapBounds, InfraWaffleMapOptions } from '../../lib/lib'; import { KueryFilterQuery } from '../../store/local/waffle_filter'; import { createFormatter } from '../../utils/formatters'; @@ -22,10 +21,11 @@ import { ViewSwitcher } from '../waffle/view_switcher'; import { TableView } from './table'; import { SnapshotNode } from '../../../common/http_api/snapshot_api'; import { convertIntervalToString } from '../../utils/convert_interval_to_string'; +import { InventoryItemType } from '../../../common/inventory_models/types'; interface Props { options: InfraWaffleMapOptions; - nodeType: InfraNodeType; + nodeType: InventoryItemType; nodes: SnapshotNode[]; loading: boolean; reload: () => void; @@ -49,56 +49,56 @@ interface MetricFormatters { } const METRIC_FORMATTERS: MetricFormatters = { - [InfraSnapshotMetricType.count]: { formatter: InfraFormatterType.number, template: '{{value}}' }, - [InfraSnapshotMetricType.cpu]: { + ['count']: { formatter: InfraFormatterType.number, template: '{{value}}' }, + ['cpu']: { formatter: InfraFormatterType.percent, template: '{{value}}', }, - [InfraSnapshotMetricType.memory]: { + ['memory']: { formatter: InfraFormatterType.percent, template: '{{value}}', }, - [InfraSnapshotMetricType.rx]: { formatter: InfraFormatterType.bits, template: '{{value}}/s' }, - [InfraSnapshotMetricType.tx]: { formatter: InfraFormatterType.bits, template: '{{value}}/s' }, - [InfraSnapshotMetricType.logRate]: { + ['rx']: { formatter: InfraFormatterType.bits, template: '{{value}}/s' }, + ['tx']: { formatter: InfraFormatterType.bits, template: '{{value}}/s' }, + ['logRate']: { formatter: InfraFormatterType.abbreviatedNumber, template: '{{value}}/s', }, - [InfraSnapshotMetricType.diskIOReadBytes]: { + ['diskIOReadBytes']: { formatter: InfraFormatterType.bytes, template: '{{value}}/s', }, - [InfraSnapshotMetricType.diskIOWriteBytes]: { + ['diskIOWriteBytes']: { formatter: InfraFormatterType.bytes, template: '{{value}}/s', }, - [InfraSnapshotMetricType.s3BucketSize]: { + ['s3BucketSize']: { formatter: InfraFormatterType.bytes, template: '{{value}}', }, - [InfraSnapshotMetricType.s3TotalRequests]: { + ['s3TotalRequests']: { formatter: InfraFormatterType.abbreviatedNumber, template: '{{value}}', }, - [InfraSnapshotMetricType.s3NumberOfObjects]: { + ['s3NumberOfObjects']: { formatter: InfraFormatterType.abbreviatedNumber, template: '{{value}}', }, - [InfraSnapshotMetricType.s3UploadBytes]: { + ['s3UploadBytes']: { formatter: InfraFormatterType.bytes, template: '{{value}}', }, - [InfraSnapshotMetricType.s3DownloadBytes]: { + ['s3DownloadBytes']: { formatter: InfraFormatterType.bytes, template: '{{value}}', }, - [InfraSnapshotMetricType.sqsOldestMessage]: { + ['sqsOldestMessage']: { formatter: InfraFormatterType.number, template: '{{value}} seconds', }, }; -const calculateBoundsFromNodes = (nodes: InfraSnapshotNode[]): InfraWaffleMapBounds => { +const calculateBoundsFromNodes = (nodes: SnapshotNode[]): InfraWaffleMapBounds => { const maxValues = nodes.map(node => node.metric.max); const minValues = nodes.map(node => node.metric.value); // if there is only one value then we need to set the bottom range to zero for min @@ -211,11 +211,7 @@ export const NodesOverview = class extends React.Component { // TODO: Change this to a real implimentation using the tickFormatter from the prototype as an example. private formatter = (val: string | number) => { const { metric } = this.props.options; - const metricFormatter = get( - METRIC_FORMATTERS, - metric.type, - METRIC_FORMATTERS[InfraSnapshotMetricType.count] - ); + const metricFormatter = get(METRIC_FORMATTERS, metric.type, METRIC_FORMATTERS.count); if (val == null) { return ''; } diff --git a/x-pack/legacy/plugins/infra/public/components/nodes_overview/table.tsx b/x-pack/legacy/plugins/infra/public/components/nodes_overview/table.tsx index 8996b170f01d2..dc0de6f6e9c69 100644 --- a/x-pack/legacy/plugins/infra/public/components/nodes_overview/table.tsx +++ b/x-pack/legacy/plugins/infra/public/components/nodes_overview/table.tsx @@ -10,14 +10,15 @@ import { i18n } from '@kbn/i18n'; import { last } from 'lodash'; import React from 'react'; import { createWaffleMapNode } from '../../containers/waffle/nodes_to_wafflemap'; -import { InfraSnapshotNode, InfraSnapshotNodePath, InfraNodeType } from '../../graphql/types'; import { InfraWaffleMapNode, InfraWaffleMapOptions } from '../../lib/lib'; import { fieldToName } from '../waffle/lib/field_to_display_name'; import { NodeContextMenu } from '../waffle/node_context_menu'; +import { InventoryItemType } from '../../../common/inventory_models/types'; +import { SnapshotNode, SnapshotNodePath } from '../../../common/http_api/snapshot_api'; interface Props { - nodes: InfraSnapshotNode[]; - nodeType: InfraNodeType; + nodes: SnapshotNode[]; + nodeType: InventoryItemType; options: InfraWaffleMapOptions; formatter: (subject: string | number) => string; currentTime: number; @@ -30,7 +31,7 @@ const initialState = { type State = Readonly; -const getGroupPaths = (path: InfraSnapshotNodePath[]) => { +const getGroupPaths = (path: SnapshotNodePath[]) => { switch (path.length) { case 3: return path.slice(0, 2); diff --git a/x-pack/legacy/plugins/infra/public/components/source_configuration/fields_configuration_panel.tsx b/x-pack/legacy/plugins/infra/public/components/source_configuration/fields_configuration_panel.tsx index 5f3d1a63e72eb..e80b1489728cc 100644 --- a/x-pack/legacy/plugins/infra/public/components/source_configuration/fields_configuration_panel.tsx +++ b/x-pack/legacy/plugins/infra/public/components/source_configuration/fields_configuration_panel.tsx @@ -12,7 +12,10 @@ import { EuiFormRow, EuiSpacer, EuiTitle, + EuiCallOut, + EuiLink, } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import React from 'react'; @@ -26,6 +29,7 @@ interface FieldsConfigurationPanelProps { podFieldProps: InputFieldProps; tiebreakerFieldProps: InputFieldProps; timestampFieldProps: InputFieldProps; + displaySettings: 'metrics' | 'logs'; } export const FieldsConfigurationPanel = ({ @@ -36,251 +40,303 @@ export const FieldsConfigurationPanel = ({ podFieldProps, tiebreakerFieldProps, timestampFieldProps, -}: FieldsConfigurationPanelProps) => ( - - -

- -

-
- - + displaySettings, +}: FieldsConfigurationPanelProps) => { + const isHostValueDefault = hostFieldProps.value === 'host.name'; + const isContainerValueDefault = containerFieldProps.value === 'container.id'; + const isPodValueDefault = podFieldProps.value === 'kubernetes.pod.uid'; + const isTimestampValueDefault = timestampFieldProps.value === '@timestamp'; + const isTiebreakerValueDefault = tiebreakerFieldProps.value === '_doc'; + return ( + + +

-

- } - description={ - - } - > - @timestamp, - }} - /> - } - isInvalid={timestampFieldProps.isInvalid} - label={ - - } - > - - -
- - - - } - description={ - - } - > - _doc, - }} - /> - } - isInvalid={tiebreakerFieldProps.isInvalid} - label={ - - } - > - - - - - - - } - description={ - - } - > - container.id, - }} - /> - } - isInvalid={containerFieldProps.isInvalid} - label={ - - } + + + + - - - - - - - } - description={ - - } - > - host.name, + documentationLink: ( + + + + ), + ecsLink: ( + + ECS + + ), }} /> +

+ + + + + } - isInvalid={hostFieldProps.isInvalid} - label={ + description={ } > - -
-
- - - - } - description={ - - } - > - kubernetes.pod.uid, - }} + helpText={ + @timestamp, + }} + /> + } + isInvalid={timestampFieldProps.isInvalid} + label={ + + } + > + - } - isInvalid={podFieldProps.isInvalid} - label={ - - } - > - - - -
-); + + + {displaySettings === 'logs' && ( + <> + + + + } + description={ + + } + > + _doc, + }} + /> + } + isInvalid={tiebreakerFieldProps.isInvalid} + label={ + + } + > + + + + + )} + {displaySettings === 'metrics' && ( + <> + + + + } + description={ + + } + > + container.id, + }} + /> + } + isInvalid={containerFieldProps.isInvalid} + label={ + + } + > + + + + + + + } + description={ + + } + > + host.name, + }} + /> + } + isInvalid={hostFieldProps.isInvalid} + label={ + + } + > + + + + + + + } + description={ + + } + > + kubernetes.pod.uid, + }} + /> + } + isInvalid={podFieldProps.isInvalid} + label={ + + } + > + + + + + )} + + ); +}; diff --git a/x-pack/legacy/plugins/infra/public/components/source_configuration/indices_configuration_panel.tsx b/x-pack/legacy/plugins/infra/public/components/source_configuration/indices_configuration_panel.tsx index e779b35975ec3..eed6768c8846c 100644 --- a/x-pack/legacy/plugins/infra/public/components/source_configuration/indices_configuration_panel.tsx +++ b/x-pack/legacy/plugins/infra/public/components/source_configuration/indices_configuration_panel.tsx @@ -23,6 +23,7 @@ interface IndicesConfigurationPanelProps { readOnly: boolean; logAliasFieldProps: InputFieldProps; metricAliasFieldProps: InputFieldProps; + displaySettings: 'metrics' | 'logs'; } export const IndicesConfigurationPanel = ({ @@ -30,6 +31,7 @@ export const IndicesConfigurationPanel = ({ readOnly, logAliasFieldProps, metricAliasFieldProps, + displaySettings, }: IndicesConfigurationPanelProps) => ( @@ -41,101 +43,105 @@ export const IndicesConfigurationPanel = ({ - - - - } - description={ - - } - > - metricbeat-*, - }} - /> + {displaySettings === 'metrics' && ( + + + } - isInvalid={metricAliasFieldProps.isInvalid} - label={ + description={ } > - - - - - - - } - description={ - - } - > - filebeat-*, - }} + helpText={ + metricbeat-*, + }} + /> + } + isInvalid={metricAliasFieldProps.isInvalid} + label={ + + } + > + + + + )} + {displaySettings === 'logs' && ( + + + } - isInvalid={logAliasFieldProps.isInvalid} - label={ + description={ } > - - - + helpText={ + filebeat-*, + }} + /> + } + isInvalid={logAliasFieldProps.isInvalid} + label={ + + } + > + + + + )} ); diff --git a/x-pack/legacy/plugins/infra/public/components/source_configuration/source_configuration_settings.tsx b/x-pack/legacy/plugins/infra/public/components/source_configuration/source_configuration_settings.tsx index 31afedc8f31ee..68dbdf38e6af6 100644 --- a/x-pack/legacy/plugins/infra/public/components/source_configuration/source_configuration_settings.tsx +++ b/x-pack/legacy/plugins/infra/public/components/source_configuration/source_configuration_settings.tsx @@ -25,13 +25,16 @@ import { IndicesConfigurationPanel } from './indices_configuration_panel'; import { NameConfigurationPanel } from './name_configuration_panel'; import { LogColumnsConfigurationPanel } from './log_columns_configuration_panel'; import { useSourceConfigurationFormState } from './source_configuration_form_state'; +import { SourceLoadingPage } from '../source_loading_page'; interface SourceConfigurationSettingsProps { shouldAllowEdit: boolean; + displaySettings: 'metrics' | 'logs'; } export const SourceConfigurationSettings = ({ shouldAllowEdit, + displaySettings, }: SourceConfigurationSettingsProps) => { const { createSourceConfiguration, @@ -80,7 +83,10 @@ export const SourceConfigurationSettings = ({ source, ]); - if (!source || !source.configuration) { + if (!source) { + return ; + } + if (!source.configuration) { return null; } @@ -112,6 +118,7 @@ export const SourceConfigurationSettings = ({ logAliasFieldProps={indicesConfigurationProps.logAlias} metricAliasFieldProps={indicesConfigurationProps.metricAlias} readOnly={!isWriteable} + displaySettings={displaySettings} /> @@ -124,18 +131,21 @@ export const SourceConfigurationSettings = ({ readOnly={!isWriteable} tiebreakerFieldProps={indicesConfigurationProps.tiebreakerField} timestampFieldProps={indicesConfigurationProps.timestampField} + displaySettings={displaySettings} /> - - - + {displaySettings === 'logs' && ( + + + + )} {errors.length > 0 ? ( <> diff --git a/x-pack/legacy/plugins/infra/public/components/waffle/group_of_groups.tsx b/x-pack/legacy/plugins/infra/public/components/waffle/group_of_groups.tsx index 7a229fbbe02ec..60a117e3ed617 100644 --- a/x-pack/legacy/plugins/infra/public/components/waffle/group_of_groups.tsx +++ b/x-pack/legacy/plugins/infra/public/components/waffle/group_of_groups.tsx @@ -7,7 +7,6 @@ import React from 'react'; import euiStyled from '../../../../../common/eui_styled_components'; -import { InfraNodeType } from '../../graphql/types'; import { InfraWaffleMapBounds, InfraWaffleMapGroupOfGroups, @@ -15,6 +14,7 @@ import { } from '../../lib/lib'; import { GroupName } from './group_name'; import { GroupOfNodes } from './group_of_nodes'; +import { InventoryItemType } from '../../../common/inventory_models/types'; interface Props { onDrilldown: (filter: string) => void; @@ -22,7 +22,7 @@ interface Props { group: InfraWaffleMapGroupOfGroups; formatter: (val: number) => string; bounds: InfraWaffleMapBounds; - nodeType: InfraNodeType; + nodeType: InventoryItemType; currentTime: number; } diff --git a/x-pack/legacy/plugins/infra/public/components/waffle/group_of_nodes.tsx b/x-pack/legacy/plugins/infra/public/components/waffle/group_of_nodes.tsx index c40c68cbdbf28..b47b8f6a1bf39 100644 --- a/x-pack/legacy/plugins/infra/public/components/waffle/group_of_nodes.tsx +++ b/x-pack/legacy/plugins/infra/public/components/waffle/group_of_nodes.tsx @@ -7,7 +7,6 @@ import React from 'react'; import euiStyled from '../../../../../common/eui_styled_components'; -import { InfraNodeType } from '../../graphql/types'; import { InfraWaffleMapBounds, InfraWaffleMapGroupOfNodes, @@ -15,6 +14,7 @@ import { } from '../../lib/lib'; import { GroupName } from './group_name'; import { Node } from './node'; +import { InventoryItemType } from '../../../common/inventory_models/types'; interface Props { onDrilldown: (filter: string) => void; @@ -23,7 +23,7 @@ interface Props { formatter: (val: number) => string; isChild: boolean; bounds: InfraWaffleMapBounds; - nodeType: InfraNodeType; + nodeType: InventoryItemType; currentTime: number; } diff --git a/x-pack/legacy/plugins/infra/public/components/waffle/lib/create_uptime_link.test.ts b/x-pack/legacy/plugins/infra/public/components/waffle/lib/create_uptime_link.test.ts index 6fdf8be15bc9e..1a285ceefec0e 100644 --- a/x-pack/legacy/plugins/infra/public/components/waffle/lib/create_uptime_link.test.ts +++ b/x-pack/legacy/plugins/infra/public/components/waffle/lib/create_uptime_link.test.ts @@ -10,8 +10,7 @@ import { InfraWaffleMapLegendMode, InfraFormatterType, } from '../../../lib/lib'; -import { InfraNodeType } from '../../../../common/graphql/types'; -import { InfraSnapshotMetricType } from '../../../graphql/types'; +import { SnapshotMetricType } from '../../../../common/inventory_models/types'; const options: InfraWaffleMapOptions = { fields: { @@ -24,7 +23,7 @@ const options: InfraWaffleMapOptions = { }, formatter: InfraFormatterType.percent, formatTemplate: '{{value}}', - metric: { type: InfraSnapshotMetricType.cpu }, + metric: { type: 'cpu' }, groupBy: [], legend: { type: InfraWaffleMapLegendMode.gradient, @@ -41,13 +40,13 @@ describe('createUptimeLink()', () => { ip: '10.0.1.2', path: [], metric: { - name: InfraSnapshotMetricType.cpu, + name: 'cpu' as SnapshotMetricType, value: 0.5, max: 0.8, avg: 0.6, }, }; - expect(createUptimeLink(options, InfraNodeType.host, node)).toBe( + expect(createUptimeLink(options, 'host', node)).toBe( '../app/uptime#/?search=host.ip:"10.0.1.2"' ); }); @@ -59,13 +58,13 @@ describe('createUptimeLink()', () => { name: 'host-01', path: [], metric: { - name: InfraSnapshotMetricType.cpu, + name: 'cpu' as SnapshotMetricType, value: 0.5, max: 0.8, avg: 0.6, }, }; - expect(createUptimeLink(options, InfraNodeType.host, node)).toBe( + expect(createUptimeLink(options, 'host', node)).toBe( '../app/uptime#/?search=host.name:"host-01"' ); }); @@ -77,13 +76,13 @@ describe('createUptimeLink()', () => { name: 'pod-01', path: [], metric: { - name: InfraSnapshotMetricType.cpu, + name: 'cpu' as SnapshotMetricType, value: 0.5, max: 0.8, avg: 0.6, }, }; - expect(createUptimeLink(options, InfraNodeType.pod, node)).toBe( + expect(createUptimeLink(options, 'pod', node)).toBe( '../app/uptime#/?search=kubernetes.pod.uid:"29193-pod-02939"' ); }); @@ -95,13 +94,13 @@ describe('createUptimeLink()', () => { name: 'docker-01', path: [], metric: { - name: InfraSnapshotMetricType.cpu, + name: 'cpu' as SnapshotMetricType, value: 0.5, max: 0.8, avg: 0.6, }, }; - expect(createUptimeLink(options, InfraNodeType.container, node)).toBe( + expect(createUptimeLink(options, 'container', node)).toBe( '../app/uptime#/?search=container.id:"docker-1234"' ); }); diff --git a/x-pack/legacy/plugins/infra/public/components/waffle/lib/create_uptime_link.ts b/x-pack/legacy/plugins/infra/public/components/waffle/lib/create_uptime_link.ts index 7fc0d3cfeee41..05b4b6d514cd0 100644 --- a/x-pack/legacy/plugins/infra/public/components/waffle/lib/create_uptime_link.ts +++ b/x-pack/legacy/plugins/infra/public/components/waffle/lib/create_uptime_link.ts @@ -5,17 +5,17 @@ */ import { get } from 'lodash'; -import { InfraNodeType } from '../../../graphql/types'; import { InfraWaffleMapNode, InfraWaffleMapOptions } from '../../../lib/lib'; +import { InventoryItemType } from '../../../../common/inventory_models/types'; const BASE_URL = '../app/uptime#/?search='; export const createUptimeLink = ( options: InfraWaffleMapOptions, - nodeType: InfraNodeType, + nodeType: InventoryItemType, node: InfraWaffleMapNode ) => { - if (nodeType === InfraNodeType.host && node.ip) { + if (nodeType === 'host' && node.ip) { return `${BASE_URL}host.ip:"${node.ip}"`; } const field = get(options, ['fields', nodeType], ''); diff --git a/x-pack/legacy/plugins/infra/public/components/waffle/map.tsx b/x-pack/legacy/plugins/infra/public/components/waffle/map.tsx index 6c0209a60f1cd..91fb84840f37f 100644 --- a/x-pack/legacy/plugins/infra/public/components/waffle/map.tsx +++ b/x-pack/legacy/plugins/infra/public/components/waffle/map.tsx @@ -11,17 +11,18 @@ import { isWaffleMapGroupWithGroups, isWaffleMapGroupWithNodes, } from '../../containers/waffle/type_guards'; -import { InfraSnapshotNode, InfraNodeType } from '../../graphql/types'; import { InfraWaffleMapBounds, InfraWaffleMapOptions } from '../../lib/lib'; import { AutoSizer } from '../auto_sizer'; import { GroupOfGroups } from './group_of_groups'; import { GroupOfNodes } from './group_of_nodes'; import { Legend } from './legend'; import { applyWaffleMapLayout } from './lib/apply_wafflemap_layout'; +import { SnapshotNode } from '../../../common/http_api/snapshot_api'; +import { InventoryItemType } from '../../../common/inventory_models/types'; interface Props { - nodes: InfraSnapshotNode[]; - nodeType: InfraNodeType; + nodes: SnapshotNode[]; + nodeType: InventoryItemType; options: InfraWaffleMapOptions; formatter: (subject: string | number) => string; currentTime: number; diff --git a/x-pack/legacy/plugins/infra/public/components/waffle/node.tsx b/x-pack/legacy/plugins/infra/public/components/waffle/node.tsx index f0770064c3cf9..72e2a4c8f35d7 100644 --- a/x-pack/legacy/plugins/infra/public/components/waffle/node.tsx +++ b/x-pack/legacy/plugins/infra/public/components/waffle/node.tsx @@ -11,10 +11,10 @@ import { i18n } from '@kbn/i18n'; import { ConditionalToolTip } from './conditional_tooltip'; import euiStyled from '../../../../../common/eui_styled_components'; -import { InfraNodeType } from '../../graphql/types'; import { InfraWaffleMapBounds, InfraWaffleMapNode, InfraWaffleMapOptions } from '../../lib/lib'; import { colorFromValue } from './lib/color_from_value'; import { NodeContextMenu } from './node_context_menu'; +import { InventoryItemType } from '../../../common/inventory_models/types'; const initialState = { isPopoverOpen: false, @@ -28,7 +28,7 @@ interface Props { node: InfraWaffleMapNode; formatter: (val: number) => string; bounds: InfraWaffleMapBounds; - nodeType: InfraNodeType; + nodeType: InventoryItemType; currentTime: number; } diff --git a/x-pack/legacy/plugins/infra/public/components/waffle/node_context_menu.tsx b/x-pack/legacy/plugins/infra/public/components/waffle/node_context_menu.tsx index cb894f37c1fce..5a90efcc51a57 100644 --- a/x-pack/legacy/plugins/infra/public/components/waffle/node_context_menu.tsx +++ b/x-pack/legacy/plugins/infra/public/components/waffle/node_context_menu.tsx @@ -13,19 +13,19 @@ import { import { i18n } from '@kbn/i18n'; import React from 'react'; -import { InfraNodeType } from '../../graphql/types'; import { InfraWaffleMapNode, InfraWaffleMapOptions } from '../../lib/lib'; import { getNodeDetailUrl, getNodeLogsUrl } from '../../pages/link_to'; import { createUptimeLink } from './lib/create_uptime_link'; import { findInventoryModel } from '../../../common/inventory_models'; import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; +import { InventoryItemType } from '../../../common/inventory_models/types'; interface Props { options: InfraWaffleMapOptions; currentTime: number; children: any; node: InfraWaffleMapNode; - nodeType: InfraNodeType; + nodeType: InventoryItemType; isPopoverOpen: boolean; closePopover: () => void; popoverPosition: EuiPopoverProps['anchorPosition']; @@ -47,7 +47,7 @@ export const NodeContextMenu = ({ // We need to have some exceptions until 7.0 & ECS is finalized. Reference // #26620 for the details for these fields. // TODO: This is tech debt, remove it after 7.0 & ECS migration. - const apmField = nodeType === InfraNodeType.host ? 'host.hostname' : inventoryModel.fields.id; + const apmField = nodeType === 'host' ? 'host.hostname' : inventoryModel.fields.id; const nodeLogsMenuItem = { name: i18n.translate('xpack.infra.nodeContextMenu.viewLogsName', { @@ -95,8 +95,7 @@ export const NodeContextMenu = ({ const showAPMTraceLink = inventoryModel.crosslinkSupport.apm && uiCapabilities?.apm && uiCapabilities?.apm.show; const showUptimeLink = - inventoryModel.crosslinkSupport.uptime && - ([InfraNodeType.pod, InfraNodeType.container].includes(nodeType) || node.ip); + inventoryModel.crosslinkSupport.uptime && (['pod', 'container'].includes(nodeType) || node.ip); const items = [ ...(showLogsLink ? [nodeLogsMenuItem] : []), diff --git a/x-pack/legacy/plugins/infra/public/components/waffle/waffle_group_by_controls.tsx b/x-pack/legacy/plugins/infra/public/components/waffle/waffle_group_by_controls.tsx index 0a9df1f666f3d..003eeb96cc41c 100644 --- a/x-pack/legacy/plugins/infra/public/components/waffle/waffle_group_by_controls.tsx +++ b/x-pack/legacy/plugins/infra/public/components/waffle/waffle_group_by_controls.tsx @@ -17,16 +17,17 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import React from 'react'; import { IFieldType } from 'src/plugins/data/public'; -import { InfraNodeType, InfraSnapshotGroupbyInput } from '../../graphql/types'; import { InfraGroupByOptions } from '../../lib/lib'; import { CustomFieldPanel } from './custom_field_panel'; import euiStyled from '../../../../../common/eui_styled_components'; +import { InventoryItemType } from '../../../common/inventory_models/types'; +import { SnapshotGroupBy } from '../../../common/http_api/snapshot_api'; interface Props { options: Array<{ text: string; field: string; toolTipContent?: string }>; - nodeType: InfraNodeType; - groupBy: InfraSnapshotGroupbyInput[]; - onChange: (groupBy: InfraSnapshotGroupbyInput[]) => void; + nodeType: InventoryItemType; + groupBy: SnapshotGroupBy; + onChange: (groupBy: SnapshotGroupBy) => void; onChangeCustomOptions: (options: InfraGroupByOptions[]) => void; fields: IFieldType[]; customOptions: InfraGroupByOptions[]; diff --git a/x-pack/legacy/plugins/infra/public/components/waffle/waffle_inventory_switcher.tsx b/x-pack/legacy/plugins/infra/public/components/waffle/waffle_inventory_switcher.tsx index bdd08ab6b366f..dec988593b0ce 100644 --- a/x-pack/legacy/plugins/infra/public/components/waffle/waffle_inventory_switcher.tsx +++ b/x-pack/legacy/plugins/infra/public/components/waffle/waffle_inventory_switcher.tsx @@ -14,20 +14,17 @@ import { import React, { useCallback, useState, useMemo } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; -import { - InfraSnapshotMetricInput, - InfraSnapshotMetricType, - InfraNodeType, - InfraSnapshotGroupbyInput, -} from '../../graphql/types'; import { findInventoryModel } from '../../../common/inventory_models'; import { InventoryItemType } from '../../../common/inventory_models/types'; +import { SnapshotMetricInput, SnapshotGroupBy } from '../../../common/http_api/snapshot_api'; interface WaffleInventorySwitcherProps { - nodeType: InfraNodeType; - changeNodeType: (nodeType: InfraNodeType) => void; - changeGroupBy: (groupBy: InfraSnapshotGroupbyInput[]) => void; - changeMetric: (metric: InfraSnapshotMetricInput) => void; + nodeType: InventoryItemType; + changeNodeType: (nodeType: InventoryItemType) => void; + changeGroupBy: (groupBy: SnapshotGroupBy) => void; + changeMetric: (metric: SnapshotMetricInput) => void; + changeAccount: (id: string) => void; + changeRegion: (name: string) => void; } const getDisplayNameForType = (type: InventoryItemType) => { @@ -39,30 +36,34 @@ export const WaffleInventorySwitcher: React.FC = ( changeNodeType, changeGroupBy, changeMetric, + changeAccount, + changeRegion, nodeType, }) => { const [isOpen, setIsOpen] = useState(false); const closePopover = useCallback(() => setIsOpen(false), []); const openPopover = useCallback(() => setIsOpen(true), []); const goToNodeType = useCallback( - (targetNodeType: InfraNodeType) => { + (targetNodeType: InventoryItemType) => { closePopover(); changeNodeType(targetNodeType); changeGroupBy([]); + changeAccount(''); + changeRegion(''); const inventoryModel = findInventoryModel(targetNodeType); changeMetric({ - type: inventoryModel.metrics.defaultSnapshot as InfraSnapshotMetricType, + type: inventoryModel.metrics.defaultSnapshot, }); }, - [closePopover, changeNodeType, changeGroupBy, changeMetric] + [closePopover, changeNodeType, changeGroupBy, changeMetric, changeAccount, changeRegion] ); - const goToHost = useCallback(() => goToNodeType('host' as InfraNodeType), [goToNodeType]); - const goToK8 = useCallback(() => goToNodeType('pod' as InfraNodeType), [goToNodeType]); - const goToDocker = useCallback(() => goToNodeType('container' as InfraNodeType), [goToNodeType]); - const goToAwsEC2 = useCallback(() => goToNodeType('awsEC2' as InfraNodeType), [goToNodeType]); - const goToAwsS3 = useCallback(() => goToNodeType('awsS3' as InfraNodeType), [goToNodeType]); - const goToAwsRDS = useCallback(() => goToNodeType('awsRDS' as InfraNodeType), [goToNodeType]); - const goToAwsSQS = useCallback(() => goToNodeType('awsSQS' as InfraNodeType), [goToNodeType]); + const goToHost = useCallback(() => goToNodeType('host'), [goToNodeType]); + const goToK8 = useCallback(() => goToNodeType('pod'), [goToNodeType]); + const goToDocker = useCallback(() => goToNodeType('container'), [goToNodeType]); + const goToAwsEC2 = useCallback(() => goToNodeType('awsEC2'), [goToNodeType]); + const goToAwsS3 = useCallback(() => goToNodeType('awsS3'), [goToNodeType]); + const goToAwsRDS = useCallback(() => goToNodeType('awsRDS'), [goToNodeType]); + const goToAwsSQS = useCallback(() => goToNodeType('awsSQS'), [goToNodeType]); const panels = useMemo( () => [ diff --git a/x-pack/legacy/plugins/infra/public/components/waffle/waffle_metric_controls.tsx b/x-pack/legacy/plugins/infra/public/components/waffle/waffle_metric_controls.tsx index d5ae6fcf7f7a2..f9e48730eaaf2 100644 --- a/x-pack/legacy/plugins/infra/public/components/waffle/waffle_metric_controls.tsx +++ b/x-pack/legacy/plugins/infra/public/components/waffle/waffle_metric_controls.tsx @@ -14,13 +14,13 @@ import { import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import React from 'react'; - -import { InfraSnapshotMetricInput, InfraSnapshotMetricType } from '../../graphql/types'; +import { SnapshotMetricInput } from '../../../common/http_api/snapshot_api'; +import { SnapshotMetricType } from '../../../common/inventory_models/types'; interface Props { - options: Array<{ text: string; value: InfraSnapshotMetricType }>; - metric: InfraSnapshotMetricInput; - onChange: (metric: InfraSnapshotMetricInput) => void; + options: Array<{ text: string; value: SnapshotMetricType }>; + metric: SnapshotMetricInput; + onChange: (metric: SnapshotMetricInput) => void; } const initialState = { @@ -90,7 +90,7 @@ export const WaffleMetricControls = class extends React.PureComponent ({ isPopoverOpen: !state.isPopoverOpen })); }; - private handleClick = (value: InfraSnapshotMetricType) => () => { + private handleClick = (value: SnapshotMetricType) => () => { this.props.onChange({ type: value }); this.handleClose(); }; diff --git a/x-pack/legacy/plugins/infra/public/containers/inventory_metadata/use_inventory_meta.ts b/x-pack/legacy/plugins/infra/public/containers/inventory_metadata/use_inventory_meta.ts index c7a7b4d61bd12..0ed1f3e35449b 100644 --- a/x-pack/legacy/plugins/infra/public/containers/inventory_metadata/use_inventory_meta.ts +++ b/x-pack/legacy/plugins/infra/public/containers/inventory_metadata/use_inventory_meta.ts @@ -13,9 +13,9 @@ import { InventoryMetaResponseRT, InventoryMetaResponse, } from '../../../common/http_api/inventory_meta_api'; -import { InfraNodeType } from '../../graphql/types'; +import { InventoryItemType } from '../../../common/inventory_models/types'; -export function useInventoryMeta(sourceId: string, nodeType: InfraNodeType) { +export function useInventoryMeta(sourceId: string, nodeType: InventoryItemType) { const decodeResponse = (response: any) => { return pipe( InventoryMetaResponseRT.decode(response), diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/ml_get_jobs_summary_api.ts b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/ml_get_jobs_summary_api.ts index 41c155e185c3a..a067285026e33 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/ml_get_jobs_summary_api.ts +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_analysis/api/ml_get_jobs_summary_api.ts @@ -41,6 +41,7 @@ export type FetchJobStatusRequestPayload = rt.TypeOf ( jobSummaries .filter(jobSummary => jobSummary.id === jobId) .every( - jobSummary => - jobSummary.fullJob && - jobSummary.fullJob.custom_settings && - jobSummary.fullJob.custom_settings.job_revision && - jobSummary.fullJob.custom_settings.job_revision >= currentRevision + jobSummary => (jobSummary?.fullJob?.custom_settings?.job_revision ?? 0) >= currentRevision ); const isJobConfigurationConsistent = ( diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_entries/index.ts b/x-pack/legacy/plugins/infra/public/containers/logs/log_entries/index.ts index 3020ad7eb5f84..ed82c0854cdea 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_entries/index.ts +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_entries/index.ts @@ -5,7 +5,7 @@ */ import { useEffect, useState, useReducer, useCallback } from 'react'; import createContainer from 'constate'; -import { pick, throttle } from 'lodash'; +import { pick, throttle, omit } from 'lodash'; import { useGraphQLQueries } from './gql_queries'; import { TimeKey, timeKeyIsBetween } from '../../../../common/time'; import { InfraLogEntry } from './types'; @@ -45,6 +45,7 @@ interface LogEntriesProps { pagesAfterEnd: number | null; sourceId: string; isAutoReloading: boolean; + jumpToTargetPosition: (position: TimeKey) => void; } type FetchEntriesParams = Omit; @@ -65,7 +66,7 @@ export type LogEntriesStateParams = { } & LogEntriesResponse; export interface LogEntriesCallbacks { - fetchNewerEntries: () => Promise; + fetchNewerEntries: () => Promise; } export const logEntriesInitialCallbacks = { fetchNewerEntries: async () => {}, @@ -127,10 +128,13 @@ const useFetchEntriesEffect = ( const [prevParams, cachePrevParams] = useState(props); const [startedStreaming, setStartedStreaming] = useState(false); - const runFetchNewEntriesRequest = async () => { + const runFetchNewEntriesRequest = async (override = {}) => { dispatch({ type: Action.FetchingNewEntries }); try { - const payload = await getLogEntriesAround(props); + const payload = await getLogEntriesAround({ + ...omit(props, 'jumpToTargetPosition'), + ...override, + }); dispatch({ type: Action.ReceiveNewEntries, payload }); } catch (e) { dispatch({ type: Action.ErrorOnNewEntries }); @@ -150,6 +154,7 @@ const useFetchEntriesEffect = ( type: getEntriesBefore ? Action.ReceiveEntriesBefore : Action.ReceiveEntriesAfter, payload, }); + return payload.entriesEnd; } catch (e) { dispatch({ type: Action.ErrorOnMoreEntries }); } @@ -185,19 +190,37 @@ const useFetchEntriesEffect = ( const fetchNewerEntries = useCallback( throttle(() => runFetchMoreEntriesRequest(ShouldFetchMoreEntries.After), 500), - [props] + [props, state.entriesEnd] ); - const streamEntriesEffectDependencies = [props.isAutoReloading, state.isLoadingMore]; + const streamEntriesEffectDependencies = [ + props.isAutoReloading, + state.isLoadingMore, + state.isReloading, + ]; const streamEntriesEffect = () => { (async () => { - if (props.isAutoReloading && !state.isLoadingMore) { + if (props.isAutoReloading && !state.isLoadingMore && !state.isReloading) { if (startedStreaming) { await new Promise(res => setTimeout(res, 5000)); } else { + const nowKey = { + tiebreaker: 0, + time: Date.now(), + }; + props.jumpToTargetPosition(nowKey); setStartedStreaming(true); + if (state.hasMoreAfterEnd) { + runFetchNewEntriesRequest({ + timeKey: nowKey, + }); + return; + } + } + const newEntriesEnd = await runFetchMoreEntriesRequest(ShouldFetchMoreEntries.After); + if (newEntriesEnd) { + props.jumpToTargetPosition(newEntriesEnd); } - fetchNewerEntries(); } else if (!props.isAutoReloading) { setStartedStreaming(false); } diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_filter/index.ts b/x-pack/legacy/plugins/infra/public/containers/logs/log_filter/index.ts index a737d19a5923d..4c4c317759bb3 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_filter/index.ts +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_filter/index.ts @@ -4,23 +4,5 @@ * you may not use this file except in compliance with the Elastic License. */ -import { useContext } from 'react'; -import createContainer from 'constate'; -import { ReduxStateContext } from '../../../utils/redux_context'; -import { logFilterSelectors as logFilterReduxSelectors } from '../../../store/local/selectors'; - -export const useLogFilterState = () => { - const { local: state } = useContext(ReduxStateContext); - const filterQuery = logFilterReduxSelectors.selectLogFilterQueryAsJson(state); - return { filterQuery }; -}; - -export interface LogFilterStateParams { - filterQuery: string | null; -} - -export const logFilterInitialState = { - filterQuery: null, -}; - -export const LogFilterState = createContainer(useLogFilterState); +export * from './log_filter_state'; +export * from './with_log_filter_url_state'; diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_filter/log_filter_state.ts b/x-pack/legacy/plugins/infra/public/containers/logs/log_filter/log_filter_state.ts new file mode 100644 index 0000000000000..2911ee729638a --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_filter/log_filter_state.ts @@ -0,0 +1,101 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { useState, useMemo } from 'react'; +import createContainer from 'constate'; +import { IIndexPattern } from 'src/plugins/data/public'; +import { esKuery } from '../../../../../../../../src/plugins/data/public'; +import { convertKueryToElasticSearchQuery } from '../../../utils/kuery'; + +export interface KueryFilterQuery { + kind: 'kuery'; + expression: string; +} + +export interface SerializedFilterQuery { + query: KueryFilterQuery; + serializedQuery: string; +} + +interface LogFilterInternalStateParams { + filterQuery: SerializedFilterQuery | null; + filterQueryDraft: KueryFilterQuery | null; +} + +export const logFilterInitialState: LogFilterInternalStateParams = { + filterQuery: null, + filterQueryDraft: null, +}; + +export type LogFilterStateParams = Omit & { + filterQuery: SerializedFilterQuery['serializedQuery'] | null; + filterQueryAsKuery: SerializedFilterQuery['query'] | null; + isFilterQueryDraftValid: boolean; +}; +export interface LogFilterCallbacks { + setLogFilterQueryDraft: (expression: string) => void; + applyLogFilterQuery: (expression: string) => void; +} + +export const useLogFilterState: (props: { + indexPattern: IIndexPattern; +}) => LogFilterStateParams & LogFilterCallbacks = ({ indexPattern }) => { + const [state, setState] = useState(logFilterInitialState); + const { filterQuery, filterQueryDraft } = state; + + const setLogFilterQueryDraft = useMemo(() => { + const setDraft = (payload: KueryFilterQuery) => + setState(prevState => ({ ...prevState, filterQueryDraft: payload })); + return (expression: string) => + setDraft({ + kind: 'kuery', + expression, + }); + }, []); + const applyLogFilterQuery = useMemo(() => { + const applyQuery = (payload: SerializedFilterQuery) => + setState(prevState => ({ + ...prevState, + filterQueryDraft: payload.query, + filterQuery: payload, + })); + return (expression: string) => + applyQuery({ + query: { + kind: 'kuery', + expression, + }, + serializedQuery: convertKueryToElasticSearchQuery(expression, indexPattern), + }); + }, [indexPattern]); + + const isFilterQueryDraftValid = useMemo(() => { + if (filterQueryDraft?.kind === 'kuery') { + try { + esKuery.fromKueryExpression(filterQueryDraft.expression); + } catch (err) { + return false; + } + } + + return true; + }, [filterQueryDraft]); + + const serializedFilterQuery = useMemo(() => (filterQuery ? filterQuery.serializedQuery : null), [ + filterQuery, + ]); + + return { + ...state, + filterQueryAsKuery: state.filterQuery ? state.filterQuery.query : null, + filterQuery: serializedFilterQuery, + isFilterQueryDraftValid, + setLogFilterQueryDraft, + applyLogFilterQuery, + }; +}; + +export const LogFilterState = createContainer(useLogFilterState); diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_filter/with_log_filter_url_state.tsx b/x-pack/legacy/plugins/infra/public/containers/logs/log_filter/with_log_filter_url_state.tsx new file mode 100644 index 0000000000000..d1da6c715cfc5 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_filter/with_log_filter_url_state.tsx @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useContext } from 'react'; +import { LogFilterState, LogFilterStateParams } from './log_filter_state'; +import { replaceStateKeyInQueryString, UrlStateContainer } from '../../../utils/url_state'; + +type LogFilterUrlState = LogFilterStateParams['filterQueryAsKuery']; + +export const WithLogFilterUrlState: React.FC = () => { + const { filterQueryAsKuery, applyLogFilterQuery } = useContext(LogFilterState.Context); + return ( + { + if (urlState) { + applyLogFilterQuery(urlState.expression); + } + }} + onInitialize={urlState => { + if (urlState) { + applyLogFilterQuery(urlState.expression); + } + }} + /> + ); +}; + +const mapToFilterQuery = (value: any): LogFilterUrlState | undefined => + value?.kind === 'kuery' && typeof value.expression === 'string' + ? { + kind: value.kind, + expression: value.expression, + } + : undefined; + +export const replaceLogFilterInQueryString = (expression: string) => + replaceStateKeyInQueryString('logFilter', { + kind: 'kuery', + expression, + }); diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/index.ts b/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/index.ts index 605e90914b174..0d548a6a78b1c 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/index.ts +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/index.ts @@ -5,4 +5,3 @@ */ export * from './log_highlights'; -export * from './redux_bridges'; diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/log_highlights.tsx b/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/log_highlights.tsx index fa1ccb4efa4bb..a4a94851ad383 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/log_highlights.tsx +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/log_highlights.tsx @@ -9,9 +9,9 @@ import { useState, useContext } from 'react'; import { useLogEntryHighlights } from './log_entry_highlights'; import { useLogSummaryHighlights } from './log_summary_highlights'; import { useNextAndPrevious } from './next_and_previous'; -import { useReduxBridgeSetters } from './redux_bridge_setters'; import { useLogSummaryBufferInterval } from '../log_summary'; import { LogViewConfiguration } from '../log_view_configuration'; +import { LogPositionState } from '../log_position'; import { TimeKey } from '../../../../common/time'; export const useLogHighlightsState = ({ @@ -28,14 +28,7 @@ export const useLogHighlightsState = ({ filterQuery: string | null; }) => { const [highlightTerms, setHighlightTerms] = useState([]); - const { - visibleMidpoint, - setFilterQuery, - setVisibleMidpoint, - jumpToTarget, - setJumpToTarget, - } = useReduxBridgeSetters(); - + const { visibleMidpoint, jumpToTargetPosition } = useContext(LogPositionState.Context); const { intervalSize: summaryIntervalSize } = useContext(LogViewConfiguration.Context); const { start: summaryStart, @@ -79,25 +72,22 @@ export const useLogHighlightsState = ({ visibleMidpoint, logEntryHighlights, highlightTerms, - jumpToTarget, + jumpToTargetPosition, }); return { highlightTerms, setHighlightTerms, - setFilterQuery, logEntryHighlights, logEntryHighlightsById, logSummaryHighlights, loadLogEntryHighlightsRequest, loadLogSummaryHighlightsRequest, - setVisibleMidpoint, currentHighlightKey, hasPreviousHighlight, hasNextHighlight, goToPreviousHighlight, goToNextHighlight, - setJumpToTarget, }; }; diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/next_and_previous.tsx b/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/next_and_previous.tsx index 62a43a5412825..7557550883f11 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/next_and_previous.tsx +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/next_and_previous.tsx @@ -17,12 +17,12 @@ import { LogEntryHighlights } from './log_entry_highlights'; export const useNextAndPrevious = ({ highlightTerms, - jumpToTarget, + jumpToTargetPosition, logEntryHighlights, visibleMidpoint, }: { highlightTerms: string[]; - jumpToTarget: (target: TimeKey) => void; + jumpToTargetPosition: (target: TimeKey) => void; logEntryHighlights: LogEntryHighlights | undefined; visibleMidpoint: TimeKey | null; }) => { @@ -41,9 +41,9 @@ export const useNextAndPrevious = ({ useEffect(() => { if (currentTimeKey) { - jumpToTarget(currentTimeKey); + jumpToTargetPosition(currentTimeKey); } - }, [currentTimeKey, jumpToTarget]); + }, [currentTimeKey, jumpToTargetPosition]); useEffect(() => { if (currentTimeKey === null && entries.length > 0) { diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/redux_bridge_setters.tsx b/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/redux_bridge_setters.tsx deleted file mode 100644 index 0e778f35188f0..0000000000000 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/redux_bridge_setters.tsx +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { useState } from 'react'; -import { TimeKey } from '../../../../common/time'; - -export const useReduxBridgeSetters = () => { - const [filterQuery, setFilterQuery] = useState(null); - const [visibleMidpoint, setVisibleMidpoint] = useState(null); - const [jumpToTarget, setJumpToTarget] = useState<(target: TimeKey) => void>(() => undefined); - - return { - filterQuery, - visibleMidpoint, - setFilterQuery, - setVisibleMidpoint, - jumpToTarget, - setJumpToTarget, - }; -}; diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/redux_bridges.tsx b/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/redux_bridges.tsx deleted file mode 100644 index 9ea8987d4f326..0000000000000 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_highlights/redux_bridges.tsx +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React, { useEffect, useContext } from 'react'; - -import { TimeKey } from '../../../../common/time'; -import { withLogFilter } from '../with_log_filter'; -import { withLogPosition } from '../with_log_position'; -import { LogHighlightsState } from './log_highlights'; - -// Bridges Redux container state with Hooks state. Once state is moved fully from -// Redux to Hooks this can be removed. - -export const LogHighlightsPositionBridge = withLogPosition( - ({ - visibleMidpoint, - jumpToTargetPosition, - }: { - visibleMidpoint: TimeKey | null; - jumpToTargetPosition: (target: TimeKey) => void; - }) => { - const { setJumpToTarget, setVisibleMidpoint } = useContext(LogHighlightsState.Context); - useEffect(() => { - setVisibleMidpoint(visibleMidpoint); - }, [setVisibleMidpoint, visibleMidpoint]); - - useEffect(() => { - setJumpToTarget(() => jumpToTargetPosition); - }, [jumpToTargetPosition, setJumpToTarget]); - - return null; - } -); - -export const LogHighlightsFilterQueryBridge = withLogFilter( - ({ serializedFilterQuery }: { serializedFilterQuery: string | null }) => { - const { setFilterQuery } = useContext(LogHighlightsState.Context); - - useEffect(() => { - setFilterQuery(serializedFilterQuery); - }, [serializedFilterQuery, setFilterQuery]); - - return null; - } -); - -export const LogHighlightsBridge = ({ indexPattern }: { indexPattern: any }) => ( - <> - - - -); diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_position/index.ts b/x-pack/legacy/plugins/infra/public/containers/logs/log_position/index.ts index 7cc8050aafd14..54a2bc88ad3d0 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_position/index.ts +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_position/index.ts @@ -4,32 +4,5 @@ * you may not use this file except in compliance with the Elastic License. */ -import { useContext } from 'react'; -import createContainer from 'constate'; -import { ReduxStateContext } from '../../../utils/redux_context'; -import { logPositionSelectors as logPositionReduxSelectors } from '../../../store/local/selectors'; -import { TimeKey } from '../../../../common/time'; - -export const useLogPositionState = () => { - const { local: state } = useContext(ReduxStateContext); - const timeKey = logPositionReduxSelectors.selectVisibleMidpointOrTarget(state); - const pages = logPositionReduxSelectors.selectPagesBeforeAndAfter(state); - const isAutoReloading = logPositionReduxSelectors.selectIsAutoReloading(state); - return { timeKey, isAutoReloading, ...pages }; -}; - -export interface LogPositionStateParams { - timeKey: TimeKey | null; - pagesAfterEnd: number | null; - pagesBeforeStart: number | null; - isAutoReloading: boolean; -} - -export const logPositionInitialState = { - timeKey: null, - pagesAfterEnd: null, - pagesBeforeStart: null, - isAutoReloading: false, -}; - -export const LogPositionState = createContainer(useLogPositionState); +export * from './log_position_state'; +export * from './with_log_position_url_state'; diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_position/log_position_state.ts b/x-pack/legacy/plugins/infra/public/containers/logs/log_position/log_position_state.ts new file mode 100644 index 0000000000000..1a8274024bd26 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_position/log_position_state.ts @@ -0,0 +1,107 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { useState, useMemo, useEffect, useCallback } from 'react'; +import createContainer from 'constate'; +import { TimeKey } from '../../../../common/time'; + +type TimeKeyOrNull = TimeKey | null; + +interface VisiblePositions { + startKey: TimeKeyOrNull; + middleKey: TimeKeyOrNull; + endKey: TimeKeyOrNull; + pagesAfterEnd: number; + pagesBeforeStart: number; +} + +export interface LogPositionStateParams { + targetPosition: TimeKeyOrNull; + isAutoReloading: boolean; + firstVisiblePosition: TimeKeyOrNull; + pagesBeforeStart: number; + pagesAfterEnd: number; + visibleMidpoint: TimeKeyOrNull; + visibleMidpointTime: number | null; + visibleTimeInterval: { start: number; end: number } | null; +} + +export interface LogPositionCallbacks { + jumpToTargetPosition: (pos: TimeKeyOrNull) => void; + jumpToTargetPositionTime: (time: number) => void; + reportVisiblePositions: (visPos: VisiblePositions) => void; + startLiveStreaming: () => void; + stopLiveStreaming: () => void; +} + +const useVisibleMidpoint = (middleKey: TimeKeyOrNull, targetPosition: TimeKeyOrNull) => { + // Of the two dependencies `middleKey` and `targetPosition`, return + // whichever one was the most recently updated. This allows the UI controls + // to display a newly-selected `targetPosition` before loading new data; + // otherwise the previous `middleKey` would linger in the UI for the entirety + // of the loading operation, which the user could perceive as unresponsiveness + const [store, update] = useState({ + middleKey, + targetPosition, + currentValue: middleKey || targetPosition, + }); + useEffect(() => { + if (middleKey !== store.middleKey) { + update({ targetPosition, middleKey, currentValue: middleKey }); + } else if (targetPosition !== store.targetPosition) { + update({ targetPosition, middleKey, currentValue: targetPosition }); + } + }, [middleKey, targetPosition]); // eslint-disable-line react-hooks/exhaustive-deps + + return store.currentValue; +}; + +export const useLogPositionState: () => LogPositionStateParams & LogPositionCallbacks = () => { + const [targetPosition, jumpToTargetPosition] = useState(null); + const [isAutoReloading, setIsAutoReloading] = useState(false); + const [visiblePositions, reportVisiblePositions] = useState({ + endKey: null, + middleKey: null, + startKey: null, + pagesBeforeStart: Infinity, + pagesAfterEnd: Infinity, + }); + + const { startKey, middleKey, endKey, pagesBeforeStart, pagesAfterEnd } = visiblePositions; + + const visibleMidpoint = useVisibleMidpoint(middleKey, targetPosition); + + const visibleTimeInterval = useMemo( + () => (startKey && endKey ? { start: startKey.time, end: endKey.time } : null), + [startKey, endKey] + ); + + const state = { + targetPosition, + isAutoReloading, + firstVisiblePosition: startKey, + pagesBeforeStart, + pagesAfterEnd, + visibleMidpoint, + visibleMidpointTime: visibleMidpoint ? visibleMidpoint.time : null, + visibleTimeInterval, + }; + + const callbacks = { + jumpToTargetPosition, + jumpToTargetPositionTime: useCallback( + (time: number) => jumpToTargetPosition({ tiebreaker: 0, time }), + [jumpToTargetPosition] + ), + reportVisiblePositions, + startLiveStreaming: useCallback(() => setIsAutoReloading(true), [setIsAutoReloading]), + stopLiveStreaming: useCallback(() => setIsAutoReloading(false), [setIsAutoReloading]), + }; + + return { ...state, ...callbacks }; +}; + +export const LogPositionState = createContainer(useLogPositionState); diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_position/with_log_position_url_state.tsx b/x-pack/legacy/plugins/infra/public/containers/logs/log_position/with_log_position_url_state.tsx new file mode 100644 index 0000000000000..a877750587be4 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_position/with_log_position_url_state.tsx @@ -0,0 +1,94 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useContext, useMemo } from 'react'; + +import { pickTimeKey } from '../../../../common/time'; +import { replaceStateKeyInQueryString, UrlStateContainer } from '../../../utils/url_state'; +import { LogPositionState, LogPositionStateParams } from './log_position_state'; + +/** + * Url State + */ + +interface LogPositionUrlState { + position: LogPositionStateParams['visibleMidpoint'] | undefined; + streamLive?: boolean | undefined; +} + +export const WithLogPositionUrlState = () => { + const { + visibleMidpoint, + isAutoReloading, + jumpToTargetPosition, + jumpToTargetPositionTime, + startLiveStreaming, + stopLiveStreaming, + } = useContext(LogPositionState.Context); + const urlState = useMemo( + () => ({ + position: visibleMidpoint ? pickTimeKey(visibleMidpoint) : null, + streamLive: isAutoReloading, + }), + [visibleMidpoint, isAutoReloading] + ); + return ( + { + if (newUrlState && newUrlState.position) { + jumpToTargetPosition(newUrlState.position); + } + if (newUrlState && newUrlState.streamLive) { + startLiveStreaming(); + } else if ( + newUrlState && + typeof newUrlState.streamLive !== 'undefined' && + !newUrlState.streamLive + ) { + stopLiveStreaming(); + } + }} + onInitialize={(initialUrlState: LogPositionUrlState | undefined) => { + if (initialUrlState && initialUrlState.position) { + jumpToTargetPosition(initialUrlState.position); + } else { + jumpToTargetPositionTime(Date.now()); + } + if (initialUrlState && initialUrlState.streamLive) { + startLiveStreaming(); + } + }} + /> + ); +}; + +const mapToUrlState = (value: any): LogPositionUrlState | undefined => + value + ? { + position: mapToPositionUrlState(value.position), + streamLive: mapToStreamLiveUrlState(value.streamLive), + } + : undefined; + +const mapToPositionUrlState = (value: any) => + value && typeof value.time === 'number' && typeof value.tiebreaker === 'number' + ? pickTimeKey(value) + : undefined; + +const mapToStreamLiveUrlState = (value: any) => (typeof value === 'boolean' ? value : undefined); + +export const replaceLogPositionInQueryString = (time: number) => + Number.isNaN(time) + ? (value: string) => value + : replaceStateKeyInQueryString('logPosition', { + position: { + time, + tiebreaker: 0, + }, + }); diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/log_summary/with_summary.ts b/x-pack/legacy/plugins/infra/public/containers/logs/log_summary/with_summary.ts index 61c603130df52..4db0d2e645448 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/log_summary/with_summary.ts +++ b/x-pack/legacy/plugins/infra/public/containers/logs/log_summary/with_summary.ts @@ -5,41 +5,34 @@ */ import { useContext } from 'react'; -import { connect } from 'react-redux'; -import { logFilterSelectors, logPositionSelectors, State } from '../../../store'; import { RendererFunction } from '../../../utils/typed_react'; import { Source } from '../../source'; import { LogViewConfiguration } from '../log_view_configuration'; import { LogSummaryBuckets, useLogSummary } from './log_summary'; +import { LogFilterState } from '../log_filter'; +import { LogPositionState } from '../log_position'; -export const WithSummary = connect((state: State) => ({ - visibleMidpointTime: logPositionSelectors.selectVisibleMidpointOrTargetTime(state), - filterQuery: logFilterSelectors.selectLogFilterQueryAsJson(state), -}))( - ({ - children, - filterQuery, - visibleMidpointTime, - }: { - children: RendererFunction<{ - buckets: LogSummaryBuckets; - start: number | null; - end: number | null; - }>; - filterQuery: string | null; - visibleMidpointTime: number | null; - }) => { - const { intervalSize } = useContext(LogViewConfiguration.Context); - const { sourceId } = useContext(Source.Context); +export const WithSummary = ({ + children, +}: { + children: RendererFunction<{ + buckets: LogSummaryBuckets; + start: number | null; + end: number | null; + }>; +}) => { + const { intervalSize } = useContext(LogViewConfiguration.Context); + const { sourceId } = useContext(Source.Context); + const { filterQuery } = useContext(LogFilterState.Context); + const { visibleMidpointTime } = useContext(LogPositionState.Context); - const { buckets, start, end } = useLogSummary( - sourceId, - visibleMidpointTime, - intervalSize, - filterQuery - ); + const { buckets, start, end } = useLogSummary( + sourceId, + visibleMidpointTime, + intervalSize, + filterQuery + ); - return children({ buckets, start, end }); - } -); + return children({ buckets, start, end }); +}; diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/with_log_filter.tsx b/x-pack/legacy/plugins/infra/public/containers/logs/with_log_filter.tsx deleted file mode 100644 index 60261fc728ebb..0000000000000 --- a/x-pack/legacy/plugins/infra/public/containers/logs/with_log_filter.tsx +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import { connect } from 'react-redux'; - -import { IIndexPattern } from 'src/plugins/data/public'; -import { logFilterActions, logFilterSelectors, State } from '../../store'; -import { FilterQuery } from '../../store/local/log_filter'; -import { convertKueryToElasticSearchQuery } from '../../utils/kuery'; -import { asChildFunctionRenderer } from '../../utils/typed_react'; -import { bindPlainActionCreators } from '../../utils/typed_redux'; -import { replaceStateKeyInQueryString, UrlStateContainer } from '../../utils/url_state'; - -interface WithLogFilterProps { - indexPattern: IIndexPattern; -} - -export const withLogFilter = connect( - (state: State) => ({ - filterQuery: logFilterSelectors.selectLogFilterQuery(state), - serializedFilterQuery: logFilterSelectors.selectLogFilterQueryAsJson(state), - filterQueryDraft: logFilterSelectors.selectLogFilterQueryDraft(state), - isFilterQueryDraftValid: logFilterSelectors.selectIsLogFilterQueryDraftValid(state), - }), - (dispatch, ownProps: WithLogFilterProps) => - bindPlainActionCreators({ - applyFilterQuery: (query: FilterQuery) => - logFilterActions.applyLogFilterQuery({ - query, - serializedQuery: convertKueryToElasticSearchQuery( - query.expression, - ownProps.indexPattern - ), - }), - applyFilterQueryFromKueryExpression: (expression: string) => - logFilterActions.applyLogFilterQuery({ - query: { - kind: 'kuery', - expression, - }, - serializedQuery: convertKueryToElasticSearchQuery(expression, ownProps.indexPattern), - }), - setFilterQueryDraft: logFilterActions.setLogFilterQueryDraft, - setFilterQueryDraftFromKueryExpression: (expression: string) => - logFilterActions.setLogFilterQueryDraft({ - kind: 'kuery', - expression, - }), - })(dispatch) -); - -export const WithLogFilter = asChildFunctionRenderer(withLogFilter); - -/** - * Url State - */ - -type LogFilterUrlState = ReturnType; - -type WithLogFilterUrlStateProps = WithLogFilterProps; - -export const WithLogFilterUrlState: React.FC = ({ indexPattern }) => ( - - {({ applyFilterQuery, filterQuery }) => ( - { - if (urlState) { - applyFilterQuery(urlState); - } - }} - onInitialize={urlState => { - if (urlState) { - applyFilterQuery(urlState); - } - }} - /> - )} - -); - -const mapToFilterQuery = (value: any): LogFilterUrlState | undefined => - value && value.kind === 'kuery' && typeof value.expression === 'string' - ? { - kind: value.kind, - expression: value.expression, - } - : undefined; - -export const replaceLogFilterInQueryString = (expression: string) => - replaceStateKeyInQueryString('logFilter', { - kind: 'kuery', - expression, - }); diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/with_log_position.tsx b/x-pack/legacy/plugins/infra/public/containers/logs/with_log_position.tsx deleted file mode 100644 index 075f3904d994c..0000000000000 --- a/x-pack/legacy/plugins/infra/public/containers/logs/with_log_position.tsx +++ /dev/null @@ -1,127 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import { connect } from 'react-redux'; -import { createSelector } from 'reselect'; - -import { pickTimeKey } from '../../../common/time'; -import { logPositionActions, logPositionSelectors, State } from '../../store'; -import { asChildFunctionRenderer } from '../../utils/typed_react'; -import { bindPlainActionCreators } from '../../utils/typed_redux'; -import { replaceStateKeyInQueryString, UrlStateContainer } from '../../utils/url_state'; - -export const withLogPosition = connect( - (state: State) => ({ - firstVisiblePosition: logPositionSelectors.selectFirstVisiblePosition(state), - isAutoReloading: logPositionSelectors.selectIsAutoReloading(state), - isScrollLocked: logPositionSelectors.selectAutoReloadScrollLock(state), - lastVisiblePosition: logPositionSelectors.selectFirstVisiblePosition(state), - targetPosition: logPositionSelectors.selectTargetPosition(state), - urlState: selectPositionUrlState(state), - visibleTimeInterval: logPositionSelectors.selectVisibleTimeInterval(state), - visibleMidpoint: logPositionSelectors.selectVisibleMidpointOrTarget(state), - visibleMidpointTime: logPositionSelectors.selectVisibleMidpointOrTargetTime(state), - }), - bindPlainActionCreators({ - jumpToTargetPosition: logPositionActions.jumpToTargetPosition, - jumpToTargetPositionTime: logPositionActions.jumpToTargetPositionTime, - reportVisiblePositions: logPositionActions.reportVisiblePositions, - startLiveStreaming: logPositionActions.startAutoReload, - stopLiveStreaming: logPositionActions.stopAutoReload, - scrollLockLiveStreaming: logPositionActions.lockAutoReloadScroll, - scrollUnlockLiveStreaming: logPositionActions.unlockAutoReloadScroll, - }) -); - -export const WithLogPosition = asChildFunctionRenderer(withLogPosition, { - onCleanup: ({ stopLiveStreaming }) => stopLiveStreaming(), -}); - -/** - * Url State - */ - -interface LogPositionUrlState { - position?: ReturnType; - streamLive?: ReturnType; -} - -export const WithLogPositionUrlState = () => ( - - {({ - jumpToTargetPosition, - jumpToTargetPositionTime, - startLiveStreaming, - stopLiveStreaming, - urlState, - }) => ( - { - if (newUrlState && newUrlState.position) { - jumpToTargetPosition(newUrlState.position); - } - if (newUrlState && newUrlState.streamLive) { - startLiveStreaming(); - } else if ( - newUrlState && - typeof newUrlState.streamLive !== 'undefined' && - !newUrlState.streamLive - ) { - stopLiveStreaming(); - } - }} - onInitialize={initialUrlState => { - if (initialUrlState && initialUrlState.position) { - jumpToTargetPosition(initialUrlState.position); - } else { - jumpToTargetPositionTime(Date.now()); - } - if (initialUrlState && initialUrlState.streamLive) { - startLiveStreaming(); - } - }} - /> - )} - -); - -const selectPositionUrlState = createSelector( - logPositionSelectors.selectVisibleMidpointOrTarget, - logPositionSelectors.selectIsAutoReloading, - (position, streamLive) => ({ - position: position ? pickTimeKey(position) : null, - streamLive, - }) -); - -const mapToUrlState = (value: any): LogPositionUrlState | undefined => - value - ? { - position: mapToPositionUrlState(value.position), - streamLive: mapToStreamLiveUrlState(value.streamLive), - } - : undefined; - -const mapToPositionUrlState = (value: any) => - value && typeof value.time === 'number' && typeof value.tiebreaker === 'number' - ? pickTimeKey(value) - : undefined; - -const mapToStreamLiveUrlState = (value: any) => (typeof value === 'boolean' ? value : undefined); - -export const replaceLogPositionInQueryString = (time: number) => - Number.isNaN(time) - ? (value: string) => value - : replaceStateKeyInQueryString('logPosition', { - position: { - time, - tiebreaker: 0, - }, - }); diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/with_log_search_controls_props.ts b/x-pack/legacy/plugins/infra/public/containers/logs/with_log_search_controls_props.ts deleted file mode 100644 index e387da9575426..0000000000000 --- a/x-pack/legacy/plugins/infra/public/containers/logs/with_log_search_controls_props.ts +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -/** - * Temporary Workaround - * This is not a well-designed container. It only exists to enable quick - * migration of the redux-based logging ui into the infra-ui codebase. It will - * be removed during the refactoring to graphql/apollo. - */ -import { connect } from 'react-redux'; -import { bindPlainActionCreators } from '../../utils/typed_redux'; - -import { - // searchActions, - // searchResultsSelectors, - // sharedSelectors, - logPositionActions, - State, -} from '../../store'; - -export const withLogSearchControlsProps = connect( - (state: State) => ({ - // isLoadingSearchResults: searchResultsSelectors.selectIsLoadingSearchResults(state), - // nextSearchResult: sharedSelectors.selectNextSearchResultKey(state), - // previousSearchResult: sharedSelectors.selectPreviousSearchResultKey(state), - }), - bindPlainActionCreators({ - // clearSearch: searchActions.clearSearch, - jumpToTarget: logPositionActions.jumpToTargetPosition, - // search: searchActions.search, - }) -); diff --git a/x-pack/legacy/plugins/infra/public/containers/logs/with_stream_items.ts b/x-pack/legacy/plugins/infra/public/containers/logs/with_stream_items.ts index 9b20676486af2..6da9cd7513cba 100644 --- a/x-pack/legacy/plugins/infra/public/containers/logs/with_stream_items.ts +++ b/x-pack/legacy/plugins/infra/public/containers/logs/with_stream_items.ts @@ -10,7 +10,6 @@ import { LogEntry, LogEntryHighlight } from '../../utils/log_entry'; import { RendererFunction } from '../../utils/typed_react'; // deep inporting to avoid a circular import problem import { LogHighlightsState } from './log_highlights/log_highlights'; -import { LogPositionState } from './log_position'; import { LogEntriesState, LogEntriesStateParams, LogEntriesCallbacks } from './log_entries'; import { UniqueTimeKey } from '../../../common/time'; @@ -24,18 +23,17 @@ export const WithStreamItems: React.FunctionComponent<{ >; }> = ({ children }) => { const [logEntries, logEntriesCallbacks] = useContext(LogEntriesState.Context); - const { isAutoReloading } = useContext(LogPositionState.Context); const { currentHighlightKey, logEntryHighlightsById } = useContext(LogHighlightsState.Context); const items = useMemo( () => - logEntries.isReloading && !isAutoReloading + logEntries.isReloading ? [] : logEntries.entries.map(logEntry => createLogEntryStreamItem(logEntry, logEntryHighlightsById[logEntry.gid] || []) ), - [isAutoReloading, logEntries.entries, logEntries.isReloading, logEntryHighlightsById] + [logEntries.entries, logEntries.isReloading, logEntryHighlightsById] ); return children({ diff --git a/x-pack/legacy/plugins/infra/public/containers/metadata/use_metadata.ts b/x-pack/legacy/plugins/infra/public/containers/metadata/use_metadata.ts index 83e176aea1039..52c522ce8efd4 100644 --- a/x-pack/legacy/plugins/infra/public/containers/metadata/use_metadata.ts +++ b/x-pack/legacy/plugins/infra/public/containers/metadata/use_metadata.ts @@ -8,16 +8,15 @@ import { useEffect } from 'react'; import { fold } from 'fp-ts/lib/Either'; import { identity } from 'fp-ts/lib/function'; import { pipe } from 'fp-ts/lib/pipeable'; -import { InfraNodeType } from '../../graphql/types'; import { InfraMetadata, InfraMetadataRT } from '../../../common/http_api/metadata_api'; import { useHTTPRequest } from '../../hooks/use_http_request'; import { throwErrors, createPlainError } from '../../../common/runtime_types'; -import { InventoryMetric } from '../../../common/inventory_models/types'; +import { InventoryMetric, InventoryItemType } from '../../../common/inventory_models/types'; import { getFilteredMetrics } from './lib/get_filtered_metrics'; export function useMetadata( nodeId: string, - nodeType: InfraNodeType, + nodeType: InventoryItemType, requiredMetrics: InventoryMetric[], sourceId: string ) { diff --git a/x-pack/legacy/plugins/infra/public/containers/node_details/use_node_details.ts b/x-pack/legacy/plugins/infra/public/containers/node_details/use_node_details.ts index e86e56a591199..189c48ba1a70c 100644 --- a/x-pack/legacy/plugins/infra/public/containers/node_details/use_node_details.ts +++ b/x-pack/legacy/plugins/infra/public/containers/node_details/use_node_details.ts @@ -6,19 +6,19 @@ import { fold } from 'fp-ts/lib/Either'; import { identity } from 'fp-ts/lib/function'; import { pipe } from 'fp-ts/lib/pipeable'; -import { InfraNodeType, InfraTimerangeInput } from '../../graphql/types'; import { throwErrors, createPlainError } from '../../../common/runtime_types'; import { useHTTPRequest } from '../../hooks/use_http_request'; import { NodeDetailsMetricDataResponseRT, NodeDetailsMetricDataResponse, } from '../../../common/http_api/node_details_api'; -import { InventoryMetric } from '../../../common/inventory_models/types'; +import { InventoryMetric, InventoryItemType } from '../../../common/inventory_models/types'; +import { InfraTimerangeInput } from '../../../common/http_api/snapshot_api'; export function useNodeDetails( metrics: InventoryMetric[], nodeId: string, - nodeType: InfraNodeType, + nodeType: InventoryItemType, sourceId: string, timerange: InfraTimerangeInput, cloudId: string diff --git a/x-pack/legacy/plugins/infra/public/containers/waffle/nodes_to_wafflemap.ts b/x-pack/legacy/plugins/infra/public/containers/waffle/nodes_to_wafflemap.ts index 18595d4788630..d2511ba7e669e 100644 --- a/x-pack/legacy/plugins/infra/public/containers/waffle/nodes_to_wafflemap.ts +++ b/x-pack/legacy/plugins/infra/public/containers/waffle/nodes_to_wafflemap.ts @@ -6,8 +6,6 @@ import { i18n } from '@kbn/i18n'; import { first, last } from 'lodash'; - -import { InfraSnapshotNode, InfraSnapshotNodePath } from '../../graphql/types'; import { InfraWaffleMapGroup, InfraWaffleMapGroupOfGroups, @@ -15,14 +13,15 @@ import { InfraWaffleMapNode, } from '../../lib/lib'; import { isWaffleMapGroupWithGroups, isWaffleMapGroupWithNodes } from './type_guards'; +import { SnapshotNodePath, SnapshotNode } from '../../../common/http_api/snapshot_api'; -export function createId(path: InfraSnapshotNodePath[]) { +export function createId(path: SnapshotNodePath[]) { return path.map(p => p.value).join('/'); } function findOrCreateGroupWithNodes( groups: InfraWaffleMapGroup[], - path: InfraSnapshotNodePath[] + path: SnapshotNodePath[] ): InfraWaffleMapGroupOfNodes { const id = path.length === 0 ? '__all__' : createId(path); /** @@ -62,7 +61,7 @@ function findOrCreateGroupWithNodes( function findOrCreateGroupWithGroups( groups: InfraWaffleMapGroup[], - path: InfraSnapshotNodePath[] + path: SnapshotNodePath[] ): InfraWaffleMapGroupOfGroups { const id = path.length === 0 ? '__all__' : createId(path); const lastPath = last(path); @@ -85,7 +84,7 @@ function findOrCreateGroupWithGroups( }; } -export function createWaffleMapNode(node: InfraSnapshotNode): InfraWaffleMapNode { +export function createWaffleMapNode(node: SnapshotNode): InfraWaffleMapNode { const nodePathItem = last(node.path); if (!nodePathItem) { throw new Error('There must be at least one node path item'); @@ -106,8 +105,8 @@ function withoutGroup(group: InfraWaffleMapGroup) { }; } -export function nodesToWaffleMap(nodes: InfraSnapshotNode[]): InfraWaffleMapGroup[] { - return nodes.reduce((groups: InfraWaffleMapGroup[], node: InfraSnapshotNode) => { +export function nodesToWaffleMap(nodes: SnapshotNode[]): InfraWaffleMapGroup[] { + return nodes.reduce((groups: InfraWaffleMapGroup[], node: SnapshotNode) => { const waffleNode = createWaffleMapNode(node); if (node.path.length === 2) { const parentGroup = findOrCreateGroupWithNodes( diff --git a/x-pack/legacy/plugins/infra/public/containers/waffle/use_snaphot.ts b/x-pack/legacy/plugins/infra/public/containers/waffle/use_snaphot.ts index d318d81235309..31f02f46caeda 100644 --- a/x-pack/legacy/plugins/infra/public/containers/waffle/use_snaphot.ts +++ b/x-pack/legacy/plugins/infra/public/containers/waffle/use_snaphot.ts @@ -8,23 +8,20 @@ import { useEffect } from 'react'; import { fold } from 'fp-ts/lib/Either'; import { identity } from 'fp-ts/lib/function'; import { pipe } from 'fp-ts/lib/pipeable'; -import { - InfraNodeType, - InfraSnapshotMetricInput, - InfraSnapshotGroupbyInput, -} from '../../graphql/types'; import { throwErrors, createPlainError } from '../../../common/runtime_types'; import { useHTTPRequest } from '../../hooks/use_http_request'; import { SnapshotNodeResponseRT, SnapshotNodeResponse, + SnapshotGroupBy, } from '../../../common/http_api/snapshot_api'; +import { InventoryItemType, SnapshotMetricType } from '../../../common/inventory_models/types'; export function useSnapshot( filterQuery: string | null | undefined, - metric: InfraSnapshotMetricInput, - groupBy: InfraSnapshotGroupbyInput[], - nodeType: InfraNodeType, + metric: { type: SnapshotMetricType }, + groupBy: SnapshotGroupBy, + nodeType: InventoryItemType, sourceId: string, currentTime: number, accountId: string, diff --git a/x-pack/legacy/plugins/infra/public/containers/waffle/with_waffle_options.tsx b/x-pack/legacy/plugins/infra/public/containers/waffle/with_waffle_options.tsx index 6d10a753f9ed0..45222b32aa51f 100644 --- a/x-pack/legacy/plugins/infra/public/containers/waffle/with_waffle_options.tsx +++ b/x-pack/legacy/plugins/infra/public/containers/waffle/with_waffle_options.tsx @@ -9,17 +9,17 @@ import { connect } from 'react-redux'; import { createSelector } from 'reselect'; import { isBoolean, isNumber } from 'lodash'; -import { - InfraSnapshotMetricInput, - InfraSnapshotMetricType, - InfraNodeType, - InfraSnapshotGroupbyInput, -} from '../../graphql/types'; import { InfraGroupByOptions } from '../../lib/lib'; import { State, waffleOptionsActions, waffleOptionsSelectors } from '../../store'; import { asChildFunctionRenderer } from '../../utils/typed_react'; import { bindPlainActionCreators } from '../../utils/typed_redux'; import { UrlStateContainer } from '../../utils/url_state'; +import { SnapshotMetricInput, SnapshotGroupBy } from '../../../common/http_api/snapshot_api'; +import { + SnapshotMetricTypeRT, + InventoryItemType, + ItemTypeRT, +} from '../../../common/inventory_models/types'; const selectOptionsUrlState = createSelector( waffleOptionsSelectors.selectMetric, @@ -194,13 +194,13 @@ const mapToUrlState = (value: any): WaffleOptionsUrlState | undefined => } : undefined; -const isInfraNodeType = (value: any): value is InfraNodeType => value in InfraNodeType; +const isInfraNodeType = (value: any): value is InventoryItemType => value in ItemTypeRT; -const isInfraSnapshotMetricInput = (subject: any): subject is InfraSnapshotMetricInput => { - return subject != null && subject.type in InfraSnapshotMetricType; +const isInfraSnapshotMetricInput = (subject: any): subject is SnapshotMetricInput => { + return subject != null && subject.type in SnapshotMetricTypeRT; }; -const isInfraSnapshotGroupbyInput = (subject: any): subject is InfraSnapshotGroupbyInput => { +const isInfraSnapshotGroupbyInput = (subject: any): subject is SnapshotGroupBy => { return subject != null && subject.type != null; }; diff --git a/x-pack/legacy/plugins/infra/public/containers/with_options.tsx b/x-pack/legacy/plugins/infra/public/containers/with_options.tsx index 349d7fda74f1f..4294697fd4103 100644 --- a/x-pack/legacy/plugins/infra/public/containers/with_options.tsx +++ b/x-pack/legacy/plugins/infra/public/containers/with_options.tsx @@ -7,7 +7,6 @@ import moment from 'moment'; import React from 'react'; -import { InfraSnapshotMetricType } from '../graphql/types'; import { InfraFormatterType, InfraOptions, InfraWaffleMapLegendMode } from '../lib/lib'; import { RendererFunction } from '../utils/typed_react'; @@ -24,7 +23,7 @@ const initialState = { wafflemap: { formatter: InfraFormatterType.percent, formatTemplate: '{{value}}', - metric: { type: InfraSnapshotMetricType.cpu }, + metric: { type: 'cpu' }, groupBy: [], legend: { type: InfraWaffleMapLegendMode.gradient, diff --git a/x-pack/legacy/plugins/infra/public/lib/lib.ts b/x-pack/legacy/plugins/infra/public/lib/lib.ts index 4b402ce6beafe..9f851e185018b 100644 --- a/x-pack/legacy/plugins/infra/public/lib/lib.ts +++ b/x-pack/legacy/plugins/infra/public/lib/lib.ts @@ -10,14 +10,14 @@ import ApolloClient from 'apollo-client'; import { AxiosRequestConfig } from 'axios'; import React from 'react'; import { Observable } from 'rxjs'; +import { SourceQuery } from '../graphql/types'; import { - InfraSnapshotMetricInput, - InfraSnapshotNodeMetric, - InfraSnapshotNodePath, - InfraSnapshotGroupbyInput, + SnapshotMetricInput, + SnapshotGroupBy, InfraTimerangeInput, - SourceQuery, -} from '../graphql/types'; + SnapshotNodeMetric, + SnapshotNodePath, +} from '../../common/http_api/snapshot_api'; export interface InfraFrontendLibs { apolloClient: InfraApolloClient; @@ -102,8 +102,8 @@ export interface InfraWaffleMapNode { id: string; name: string; ip?: string | null; - path: InfraSnapshotNodePath[]; - metric: InfraSnapshotNodeMetric; + path: SnapshotNodePath[]; + metric: SnapshotNodeMetric; } export type InfraWaffleMapGroup = InfraWaffleMapGroupOfNodes | InfraWaffleMapGroupOfGroups; @@ -165,8 +165,8 @@ export interface InfraWaffleMapOptions { fields?: SourceQuery.Query['source']['configuration']['fields'] | null; formatter: InfraFormatterType; formatTemplate: string; - metric: InfraSnapshotMetricInput; - groupBy: InfraSnapshotGroupbyInput[]; + metric: SnapshotMetricInput; + groupBy: SnapshotGroupBy; legend: InfraWaffleMapLegend; } diff --git a/x-pack/legacy/plugins/infra/public/pages/infrastructure/index.tsx b/x-pack/legacy/plugins/infra/public/pages/infrastructure/index.tsx index dfe4fb05d669a..5eaa2850aebdb 100644 --- a/x-pack/legacy/plugins/infra/public/pages/infrastructure/index.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/infrastructure/index.tsx @@ -20,7 +20,7 @@ import { WithSource } from '../../containers/with_source'; import { Source } from '../../containers/source'; import { MetricsExplorerPage } from './metrics_explorer'; import { SnapshotPage } from './snapshot'; -import { SettingsPage } from '../shared/settings'; +import { MetricsSettingsPage } from './settings'; import { AppNavigation } from '../../components/navigation/app_navigation'; import { SourceLoadingPage } from '../../components/source_loading_page'; import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; @@ -106,7 +106,7 @@ export const InfrastructurePage = ({ match }: RouteComponentProps) => { )} /> - + diff --git a/x-pack/legacy/plugins/infra/public/pages/infrastructure/settings.tsx b/x-pack/legacy/plugins/infra/public/pages/infrastructure/settings.tsx new file mode 100644 index 0000000000000..d75af7879d17a --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/pages/infrastructure/settings.tsx @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { SourceConfigurationSettings } from '../../components/source_configuration/source_configuration_settings'; +import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; + +export const MetricsSettingsPage = () => { + const uiCapabilities = useKibana().services.application?.capabilities; + return ( + + ); +}; diff --git a/x-pack/legacy/plugins/infra/public/pages/infrastructure/snapshot/toolbar.tsx b/x-pack/legacy/plugins/infra/public/pages/infrastructure/snapshot/toolbar.tsx index a828cd207aa5b..a5780f44050e1 100644 --- a/x-pack/legacy/plugins/infra/public/pages/infrastructure/snapshot/toolbar.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/infrastructure/snapshot/toolbar.tsx @@ -23,12 +23,21 @@ export const SnapshotToolbar = () => ( - {({ changeMetric, changeNodeType, changeGroupBy, nodeType }) => ( + {({ + changeMetric, + changeNodeType, + changeGroupBy, + changeAccount, + changeRegion, + nodeType, + }) => ( )} diff --git a/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_logs.tsx b/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_logs.tsx index c409470eb24c7..b3fcaff75aafd 100644 --- a/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_logs.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_logs.tsx @@ -8,8 +8,8 @@ import compose from 'lodash/fp/compose'; import React from 'react'; import { match as RouteMatch, Redirect, RouteComponentProps } from 'react-router-dom'; -import { replaceLogFilterInQueryString } from '../../containers/logs/with_log_filter'; -import { replaceLogPositionInQueryString } from '../../containers/logs/with_log_position'; +import { replaceLogFilterInQueryString } from '../../containers/logs/log_filter'; +import { replaceLogPositionInQueryString } from '../../containers/logs/log_position'; import { replaceSourceIdInQueryString } from '../../containers/source_id'; import { getFilterFromLocation, getTimeFromLocation } from './query_params'; diff --git a/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_node_detail.tsx b/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_node_detail.tsx index a2cebbb96a4f0..ec1cc8ba45332 100644 --- a/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_node_detail.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_node_detail.tsx @@ -8,12 +8,12 @@ import React from 'react'; import { Redirect, RouteComponentProps } from 'react-router-dom'; import { replaceMetricTimeInQueryString } from '../metrics/containers/with_metrics_time'; -import { InfraNodeType } from '../../graphql/types'; import { getFromFromLocation, getToFromLocation } from './query_params'; +import { InventoryItemType } from '../../../common/inventory_models/types'; type RedirectToNodeDetailProps = RouteComponentProps<{ nodeId: string; - nodeType: InfraNodeType; + nodeType: InventoryItemType; }>; export const RedirectToNodeDetail = ({ @@ -36,7 +36,7 @@ export const getNodeDetailUrl = ({ to, from, }: { - nodeType: InfraNodeType; + nodeType: InventoryItemType; nodeId: string; to?: number; from?: number; diff --git a/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_node_logs.tsx b/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_node_logs.tsx index 4af50df343859..6f1fdd606dd73 100644 --- a/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_node_logs.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/link_to/redirect_to_node_logs.tsx @@ -11,21 +11,25 @@ import React from 'react'; import { Redirect, RouteComponentProps } from 'react-router-dom'; import { LoadingPage } from '../../components/loading_page'; -import { replaceLogFilterInQueryString } from '../../containers/logs/with_log_filter'; -import { replaceLogPositionInQueryString } from '../../containers/logs/with_log_position'; +import { replaceLogFilterInQueryString } from '../../containers/logs/log_filter'; +import { replaceLogPositionInQueryString } from '../../containers/logs/log_position'; import { replaceSourceIdInQueryString } from '../../containers/source_id'; -import { InfraNodeType, SourceConfigurationFields } from '../../graphql/types'; +import { SourceConfigurationFields } from '../../graphql/types'; import { getFilterFromLocation, getTimeFromLocation } from './query_params'; import { useSource } from '../../containers/source/source'; import { findInventoryFields } from '../../../common/inventory_models'; +import { InventoryItemType } from '../../../common/inventory_models/types'; type RedirectToNodeLogsType = RouteComponentProps<{ nodeId: string; - nodeType: InfraNodeType; + nodeType: InventoryItemType; sourceId?: string; }>; -const getFieldByNodeType = (nodeType: InfraNodeType, fields: SourceConfigurationFields.Fields) => { +const getFieldByNodeType = ( + nodeType: InventoryItemType, + fields: SourceConfigurationFields.Fields +) => { const inventoryFields = findInventoryFields(nodeType, fields); return inventoryFields.id; }; @@ -75,6 +79,6 @@ export const getNodeLogsUrl = ({ time, }: { nodeId: string; - nodeType: InfraNodeType; + nodeType: InventoryItemType; time?: number; }) => [`#/link-to/${nodeType}-logs/`, nodeId, ...(time ? [`?time=${time}`] : [])].join(''); diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/index.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/index.tsx index a8a75f99253c2..f38f066b5323f 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/index.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/index.tsx @@ -17,7 +17,7 @@ import { SourceLoadingPage } from '../../components/source_loading_page'; import { SourceErrorPage } from '../../components/source_error_page'; import { Source, useSource } from '../../containers/source'; import { StreamPage } from './stream'; -import { SettingsPage } from '../shared/settings'; +import { LogsSettingsPage } from './settings'; import { AppNavigation } from '../../components/navigation/app_navigation'; import { useLogAnalysisCapabilities, @@ -107,7 +107,7 @@ export const LogsPage = ({ match }: RouteComponentProps) => { - + - jobTypes.reduce( + logEntryCategoriesJobTypes.reduce( (accumulatedJobIds, jobType) => ({ ...accumulatedJobIds, [jobType]: getJobId(spaceId, sourceId, jobType), }), - {} as Record + {} as Record ); const getJobSummary = async (spaceId: string, sourceId: string) => { - const response = await callJobsSummaryAPI(spaceId, sourceId, jobTypes); + const response = await callJobsSummaryAPI(spaceId, sourceId, logEntryCategoriesJobTypes); const jobIds = Object.values(getJobIds(spaceId, sourceId)); return response.filter(jobSummary => jobIds.includes(jobSummary.id)); @@ -83,7 +82,7 @@ const setUpModule = async ( }; const cleanUpModule = async (spaceId: string, sourceId: string) => { - return await cleanUpJobsAndDatafeeds(spaceId, sourceId, jobTypes); + return await cleanUpJobsAndDatafeeds(spaceId, sourceId, logEntryCategoriesJobTypes); }; const validateSetupIndices = async ({ indices, timestampField }: ModuleSourceConfiguration) => { @@ -103,9 +102,9 @@ const validateSetupIndices = async ({ indices, timestampField }: ModuleSourceCon ]); }; -export const logEntryCategoriesModule: ModuleDescriptor = { +export const logEntryCategoriesModule: ModuleDescriptor = { moduleId, - jobTypes, + jobTypes: logEntryCategoriesJobTypes, bucketSpan, getJobIds, getJobSummary, diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_categories/page_content.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_categories/page_content.tsx index 9a50acf622ee1..cc59d73055796 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_categories/page_content.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_categories/page_content.tsx @@ -14,6 +14,7 @@ import { MlUnavailablePrompt, } from '../../../components/logging/log_analysis_setup'; import { LogAnalysisCapabilities } from '../../../containers/logs/log_analysis'; +import { LogEntryCategoriesResultsContent } from './page_results_content'; import { LogEntryCategoriesSetupContent } from './page_setup_content'; import { useLogEntryCategoriesModuleContext } from './use_log_entry_categories_module'; @@ -44,8 +45,7 @@ export const LogEntryCategoriesPageContent = () => { } else if (setupStatus === 'unknown') { return ; } else if (isSetupStatusWithResults(setupStatus)) { - return null; - // return ; + return ; } else { return ; } diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_categories/page_results_content.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_categories/page_results_content.tsx new file mode 100644 index 0000000000000..ffffba0691749 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_categories/page_results_content.tsx @@ -0,0 +1,240 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import datemath from '@elastic/datemath'; +import { EuiFlexGroup, EuiFlexItem, EuiPage, EuiPanel, EuiSuperDatePicker } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import moment from 'moment'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; + +import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public'; +import euiStyled from '../../../../../../common/eui_styled_components'; +import { TimeRange } from '../../../../common/http_api/shared/time_range'; +import { + LogAnalysisJobProblemIndicator, + jobHasProblem, +} from '../../../components/logging/log_analysis_job_status'; +import { FirstUseCallout } from '../../../components/logging/log_analysis_results'; +import { useInterval } from '../../../hooks/use_interval'; +import { useTrackPageview } from '../../../hooks/use_track_metric'; +import { TopCategoriesSection } from './sections/top_categories'; +import { useLogEntryCategoriesModuleContext } from './use_log_entry_categories_module'; +import { useLogEntryCategoriesResults } from './use_log_entry_categories_results'; +import { + StringTimeRange, + useLogEntryCategoriesResultsUrlState, +} from './use_log_entry_categories_results_url_state'; + +const JOB_STATUS_POLLING_INTERVAL = 30000; + +export const LogEntryCategoriesResultsContent: React.FunctionComponent = () => { + useTrackPageview({ app: 'infra_logs', path: 'log_entry_categories_results' }); + useTrackPageview({ app: 'infra_logs', path: 'log_entry_categories_results', delay: 15000 }); + + const { + fetchJobStatus, + jobStatus, + setupStatus, + viewSetupForReconfiguration, + viewSetupForUpdate, + jobIds, + sourceConfiguration: { sourceId }, + } = useLogEntryCategoriesModuleContext(); + + const { + timeRange: selectedTimeRange, + setTimeRange: setSelectedTimeRange, + autoRefresh, + setAutoRefresh, + } = useLogEntryCategoriesResultsUrlState(); + + const [categoryQueryTimeRange, setCategoryQueryTimeRange] = useState<{ + lastChangedTime: number; + timeRange: TimeRange; + }>(() => ({ + lastChangedTime: Date.now(), + timeRange: stringToNumericTimeRange(selectedTimeRange), + })); + + const [categoryQueryDatasets, setCategoryQueryDatasets] = useState([]); + + const { services } = useKibana<{}>(); + + const showLoadDataErrorNotification = useCallback( + (error: Error) => { + // eslint-disable-next-line no-unused-expressions + services.notifications?.toasts.addError(error, { + title: loadDataErrorTitle, + }); + }, + [services.notifications] + ); + + const { + getLogEntryCategoryDatasets, + getTopLogEntryCategories, + isLoadingLogEntryCategoryDatasets, + isLoadingTopLogEntryCategories, + logEntryCategoryDatasets, + topLogEntryCategories, + } = useLogEntryCategoriesResults({ + categoriesCount: 25, + endTime: categoryQueryTimeRange.timeRange.endTime, + filteredDatasets: categoryQueryDatasets, + onGetTopLogEntryCategoriesError: showLoadDataErrorNotification, + sourceId, + startTime: categoryQueryTimeRange.timeRange.startTime, + }); + + const handleQueryTimeRangeChange = useCallback( + ({ start: startTime, end: endTime }: { start: string; end: string }) => { + setCategoryQueryTimeRange(previousQueryParameters => ({ + ...previousQueryParameters, + timeRange: stringToNumericTimeRange({ startTime, endTime }), + lastChangedTime: Date.now(), + })); + }, + [setCategoryQueryTimeRange] + ); + + const handleSelectedTimeRangeChange = useCallback( + (selectedTime: { start: string; end: string; isInvalid: boolean }) => { + if (selectedTime.isInvalid) { + return; + } + setSelectedTimeRange({ + startTime: selectedTime.start, + endTime: selectedTime.end, + }); + handleQueryTimeRangeChange(selectedTime); + }, + [setSelectedTimeRange, handleQueryTimeRangeChange] + ); + + const handleAutoRefreshChange = useCallback( + ({ isPaused, refreshInterval: interval }: { isPaused: boolean; refreshInterval: number }) => { + setAutoRefresh({ + isPaused, + interval, + }); + }, + [setAutoRefresh] + ); + + const isFirstUse = useMemo(() => setupStatus === 'hiddenAfterSuccess', [setupStatus]); + + const hasResults = useMemo(() => topLogEntryCategories.length > 0, [ + topLogEntryCategories.length, + ]); + + useEffect(() => { + getTopLogEntryCategories(); + }, [getTopLogEntryCategories, categoryQueryDatasets, categoryQueryTimeRange.lastChangedTime]); + + useEffect(() => { + getLogEntryCategoryDatasets(); + }, [getLogEntryCategoryDatasets, categoryQueryTimeRange.lastChangedTime]); + + useInterval(() => { + fetchJobStatus(); + }, JOB_STATUS_POLLING_INTERVAL); + + useInterval( + () => { + handleQueryTimeRangeChange({ + start: selectedTimeRange.startTime, + end: selectedTimeRange.endTime, + }); + }, + autoRefresh.isPaused ? null : autoRefresh.interval + ); + + return ( + + + + + + + + + + + + + {jobHasProblem(jobStatus['log-entry-categories-count'], setupStatus) ? ( + + + + ) : null} + {isFirstUse && !hasResults ? ( + + + + ) : null} + + + + + + + + ); +}; + +const stringToNumericTimeRange = (timeRange: StringTimeRange): TimeRange => ({ + startTime: moment( + datemath.parse(timeRange.startTime, { + momentInstance: moment, + }) + ).valueOf(), + endTime: moment( + datemath.parse(timeRange.endTime, { + momentInstance: moment, + roundUp: true, + }) + ).valueOf(), +}); + +// This is needed due to the flex-basis: 100% !important; rule that +// kicks in on small screens via media queries breaking when using direction="column" +export const ResultsContentPage = euiStyled(EuiPage)` + flex: 1 0 0%; + flex-direction: column; + + .euiFlexGroup--responsive > .euiFlexItem { + flex-basis: auto !important; + } +`; + +const loadDataErrorTitle = i18n.translate( + 'xpack.infra.logs.logEntryCategories.loadDataErrorTitle', + { + defaultMessage: 'Failed to load category data', + } +); diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/anomaly_severity_indicator.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/anomaly_severity_indicator.tsx new file mode 100644 index 0000000000000..e50231316fb5a --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/anomaly_severity_indicator.tsx @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiHealth } from '@elastic/eui'; +import React, { useMemo } from 'react'; +import { + formatAnomalyScore, + getSeverityCategoryForScore, + ML_SEVERITY_COLORS, +} from '../../../../../../common/log_analysis'; + +export const AnomalySeverityIndicator: React.FunctionComponent<{ + anomalyScore: number; +}> = ({ anomalyScore }) => { + const severityColor = useMemo(() => getColorForAnomalyScore(anomalyScore), [anomalyScore]); + + return {formatAnomalyScore(anomalyScore)}; +}; + +const getColorForAnomalyScore = (anomalyScore: number) => { + const severityCategory = getSeverityCategoryForScore(anomalyScore); + + if (severityCategory != null && severityCategory in ML_SEVERITY_COLORS) { + return ML_SEVERITY_COLORS[severityCategory]; + } else { + return 'subdued'; + } +}; diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/category_expression.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/category_expression.tsx new file mode 100644 index 0000000000000..5c8b18528cae6 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/category_expression.tsx @@ -0,0 +1,65 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import React, { memo } from 'react'; + +import euiStyled from '../../../../../../../../common/eui_styled_components'; + +export const RegularExpressionRepresentation: React.FunctionComponent<{ + maximumSegmentCount?: number; + regularExpression: string; +}> = memo(({ maximumSegmentCount = 30, regularExpression }) => { + const segments = regularExpression.split(collapsedRegularExpressionCharacters); + + return ( + + {segments + .slice(0, maximumSegmentCount) + .map((segment, segmentIndex) => [ + segmentIndex > 0 ? ( + + ) : null, + + {segment.replace(escapedRegularExpressionCharacters, '$1')} + , + ])} + {segments.length > maximumSegmentCount ? ( + + … + + ) : null} + + ); +}); + +const CategoryPattern = euiStyled.span` + font-family: ${props => props.theme.eui.euiCodeFontFamily}; + word-break: break-all; +`; + +const CategoryPatternWildcard = euiStyled.span` + color: ${props => props.theme.eui.euiColorMediumShade}; +`; + +const CategoryPatternSegment = euiStyled.span` + font-weight: bold; +`; + +const collapsedRegularExpressionCharacters = /\.[+*]\??/g; + +const escapedRegularExpressionCharacters = /\\([\\^$*+?.()\[\]])/g; diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/datasets_list.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/datasets_list.tsx new file mode 100644 index 0000000000000..c30612f54be00 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/datasets_list.tsx @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; + +import { getFriendlyNameForPartitionId } from '../../../../../../common/log_analysis'; + +export const DatasetsList: React.FunctionComponent<{ + datasets: string[]; +}> = ({ datasets }) => ( +
    + {datasets.sort().map(dataset => { + const datasetLabel = getFriendlyNameForPartitionId(dataset); + return
  • {datasetLabel}
  • ; + })} +
+); diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/datasets_selector.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/datasets_selector.tsx new file mode 100644 index 0000000000000..9c22caa4b3465 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/datasets_selector.tsx @@ -0,0 +1,60 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiComboBox, EuiComboBoxOptionProps } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React, { useCallback, useMemo } from 'react'; + +import { getFriendlyNameForPartitionId } from '../../../../../../common/log_analysis'; + +type DatasetOptionProps = EuiComboBoxOptionProps; + +export const DatasetsSelector: React.FunctionComponent<{ + availableDatasets: string[]; + isLoading?: boolean; + onChangeDatasetSelection: (datasets: string[]) => void; + selectedDatasets: string[]; +}> = ({ availableDatasets, isLoading = false, onChangeDatasetSelection, selectedDatasets }) => { + const options = useMemo( + () => + availableDatasets.map(dataset => ({ + value: dataset, + label: getFriendlyNameForPartitionId(dataset), + })), + [availableDatasets] + ); + + const selectedOptions = useMemo( + () => options.filter(({ value }) => value != null && selectedDatasets.includes(value)), + [options, selectedDatasets] + ); + + const handleChange = useCallback( + (newSelectedOptions: DatasetOptionProps[]) => + onChangeDatasetSelection(newSelectedOptions.map(({ value }) => value).filter(isDefined)), + [onChangeDatasetSelection] + ); + + return ( + + ); +}; + +const datasetFilterPlaceholder = i18n.translate( + 'xpack.infra.logs.logEntryCategories.datasetFilterPlaceholder', + { + defaultMessage: 'Filter by datasets', + } +); + +const isDefined = (value: Value): value is NonNullable => value != null; diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/index.ts b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/index.ts new file mode 100644 index 0000000000000..e699bbf956f94 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export * from './top_categories_section'; diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/log_entry_count_sparkline.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/log_entry_count_sparkline.tsx new file mode 100644 index 0000000000000..7a29ea9aa0ebc --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/log_entry_count_sparkline.tsx @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useMemo } from 'react'; + +import { LogEntryCategoryHistogram } from '../../../../../../common/http_api/log_analysis'; +import { TimeRange } from '../../../../../../common/http_api/shared'; +import { SingleMetricComparison } from './single_metric_comparison'; +import { SingleMetricSparkline } from './single_metric_sparkline'; + +export const LogEntryCountSparkline: React.FunctionComponent<{ + currentCount: number; + histograms: LogEntryCategoryHistogram[]; + timeRange: TimeRange; +}> = ({ currentCount, histograms, timeRange }) => { + const metric = useMemo( + () => + histograms + .find(histogram => histogram.histogramId === 'history') + ?.buckets?.map(({ startTime: timestamp, logEntryCount: value }) => ({ + timestamp, + value, + })) ?? [], + [histograms] + ); + const referenceCount = useMemo( + () => + histograms.find(histogram => histogram.histogramId === 'reference')?.buckets?.[0] + ?.logEntryCount ?? 0, + [histograms] + ); + + const overallTimeRange = useMemo( + () => ({ + endTime: timeRange.endTime, + startTime: timeRange.startTime - (timeRange.endTime - timeRange.startTime), + }), + [timeRange.endTime, timeRange.startTime] + ); + + return ( + <> + + + + ); +}; diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/single_metric_comparison.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/single_metric_comparison.tsx new file mode 100644 index 0000000000000..1352afb60a505 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/single_metric_comparison.tsx @@ -0,0 +1,57 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiIcon, EuiTextColor } from '@elastic/eui'; +import numeral from '@elastic/numeral'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; + +import euiStyled from '../../../../../../../../common/eui_styled_components'; + +export const SingleMetricComparison: React.FunctionComponent<{ + currentValue: number; + previousValue: number; +}> = ({ currentValue, previousValue }) => { + const changeFactor = currentValue / previousValue - 1; + + if (changeFactor < 0) { + return ( + + + {formatPercentage(changeFactor)} + + ); + } else if (changeFactor > 0 && Number.isFinite(changeFactor)) { + return ( + + + {formatPercentage(changeFactor)} + + ); + } else if (changeFactor > 0 && !Number.isFinite(changeFactor)) { + return ( + + + {newCategoryTrendLabel} + + ); + } + + return null; +}; + +const formatPercentage = (value: number) => numeral(value).format('+0,0 %'); + +const newCategoryTrendLabel = i18n.translate( + 'xpack.infra.logs.logEntryCategories.newCategoryTrendLabel', + { + defaultMessage: 'new', + } +); + +const NoWrapSpan = euiStyled.span` + white-space: nowrap; +`; diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/single_metric_sparkline.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/single_metric_sparkline.tsx new file mode 100644 index 0000000000000..5fb8e3380f23f --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/single_metric_sparkline.tsx @@ -0,0 +1,65 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useMemo } from 'react'; +import { Chart, Settings, AreaSeries } from '@elastic/charts'; +import { + EUI_CHARTS_THEME_LIGHT, + EUI_SPARKLINE_THEME_PARTIAL, + EUI_CHARTS_THEME_DARK, +} from '@elastic/eui/dist/eui_charts_theme'; + +import { useKibanaUiSetting } from '../../../../../utils/use_kibana_ui_setting'; +import { TimeRange } from '../../../../../../common/http_api/shared'; + +interface TimeSeriesPoint { + timestamp: number; + value: number; +} + +const timestampAccessor = 'timestamp'; +const valueAccessor = ['value']; +const sparklineSize = { + height: 20, + width: 100, +}; + +export const SingleMetricSparkline: React.FunctionComponent<{ + metric: TimeSeriesPoint[]; + timeRange: TimeRange; +}> = ({ metric, timeRange }) => { + const [isDarkMode] = useKibanaUiSetting('theme:darkMode'); + + const theme = useMemo( + () => [ + // localThemeOverride, + EUI_SPARKLINE_THEME_PARTIAL, + isDarkMode ? EUI_CHARTS_THEME_DARK.theme : EUI_CHARTS_THEME_LIGHT.theme, + ], + [isDarkMode] + ); + + const xDomain = useMemo( + () => ({ + max: timeRange.endTime, + min: timeRange.startTime, + }), + [timeRange] + ); + + return ( + + + + + ); +}; diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/top_categories_section.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/top_categories_section.tsx new file mode 100644 index 0000000000000..0281615a59c78 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/top_categories_section.tsx @@ -0,0 +1,82 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner, EuiSpacer, EuiTitle } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; + +import { LogEntryCategory } from '../../../../../../common/http_api/log_analysis'; +import { TimeRange } from '../../../../../../common/http_api/shared'; +import { LoadingOverlayWrapper } from '../../../../../components/loading_overlay_wrapper'; +import { RecreateJobButton } from '../../../../../components/logging/log_analysis_job_status'; +import { AnalyzeInMlButton } from '../../../../../components/logging/log_analysis_results'; +import { DatasetsSelector } from './datasets_selector'; +import { TopCategoriesTable } from './top_categories_table'; + +export const TopCategoriesSection: React.FunctionComponent<{ + availableDatasets: string[]; + isLoadingDatasets?: boolean; + isLoadingTopCategories?: boolean; + jobId: string; + onChangeDatasetSelection: (datasets: string[]) => void; + onRequestRecreateMlJob: () => void; + selectedDatasets: string[]; + timeRange: TimeRange; + topCategories: LogEntryCategory[]; +}> = ({ + availableDatasets, + isLoadingDatasets = false, + isLoadingTopCategories = false, + jobId, + onChangeDatasetSelection, + onRequestRecreateMlJob, + selectedDatasets, + timeRange, + topCategories, +}) => { + return ( + <> + + + +

{title}

+
+
+ + + + + + +
+ + + + } + > + + + + ); +}; + +const title = i18n.translate('xpack.infra.logs.logEntryCategories.topCategoriesSectionTitle', { + defaultMessage: 'Log message categories', +}); + +const loadingAriaLabel = i18n.translate( + 'xpack.infra.logs.logEntryCategories.topCategoriesSectionLoadingAriaLabel', + { defaultMessage: 'Loading message categories' } +); + +const LoadingOverlayContent = () => ; diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/top_categories_table.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/top_categories_table.tsx new file mode 100644 index 0000000000000..3d20aef03ff15 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_categories/sections/top_categories/top_categories_table.tsx @@ -0,0 +1,106 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiBasicTable, EuiBasicTableColumn } from '@elastic/eui'; +import numeral from '@elastic/numeral'; +import { i18n } from '@kbn/i18n'; +import React, { useMemo } from 'react'; + +import euiStyled from '../../../../../../../../common/eui_styled_components'; +import { + LogEntryCategory, + LogEntryCategoryHistogram, +} from '../../../../../../common/http_api/log_analysis'; +import { TimeRange } from '../../../../../../common/http_api/shared'; +import { AnomalySeverityIndicator } from './anomaly_severity_indicator'; +import { RegularExpressionRepresentation } from './category_expression'; +import { DatasetsList } from './datasets_list'; +import { LogEntryCountSparkline } from './log_entry_count_sparkline'; + +export const TopCategoriesTable = euiStyled( + ({ + className, + timeRange, + topCategories, + }: { + className?: string; + timeRange: TimeRange; + topCategories: LogEntryCategory[]; + }) => { + const columns = useMemo(() => createColumns(timeRange), [timeRange]); + + return ( + + ); + } +)` + &.euiTableRow--topAligned .euiTableRowCell { + vertical-align: top; + } +`; + +const createColumns = (timeRange: TimeRange): Array> => [ + { + align: 'right', + field: 'logEntryCount', + name: i18n.translate('xpack.infra.logs.logEntryCategories.countColumnTitle', { + defaultMessage: 'Message count', + }), + render: (logEntryCount: number) => { + return numeral(logEntryCount).format('0,0'); + }, + width: '120px', + }, + { + field: 'histograms', + name: i18n.translate('xpack.infra.logs.logEntryCategories.trendColumnTitle', { + defaultMessage: 'Trend', + }), + render: (histograms: LogEntryCategoryHistogram[], item) => { + return ( + + ); + }, + width: '220px', + }, + { + field: 'regularExpression', + name: i18n.translate('xpack.infra.logs.logEntryCategories.categoryColumnTitle', { + defaultMessage: 'Category', + }), + truncateText: true, + render: (regularExpression: string) => ( + + ), + }, + { + field: 'datasets', + name: i18n.translate('xpack.infra.logs.logEntryCategories.datasetColumnTitle', { + defaultMessage: 'Datasets', + }), + render: (datasets: string[]) => , + width: '200px', + }, + { + align: 'right', + field: 'maximumAnomalyScore', + name: i18n.translate('xpack.infra.logs.logEntryCategories.maximumAnomalyScoreColumnTitle', { + defaultMessage: 'Maximum anomaly score', + }), + render: (maximumAnomalyScore: number) => ( + + ), + width: '160px', + }, +]; diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_categories/service_calls/get_log_entry_category_datasets.ts b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_categories/service_calls/get_log_entry_category_datasets.ts new file mode 100644 index 0000000000000..942ded4230e97 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_categories/service_calls/get_log_entry_category_datasets.ts @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { fold } from 'fp-ts/lib/Either'; +import { pipe } from 'fp-ts/lib/pipeable'; +import { identity } from 'fp-ts/lib/function'; +import { npStart } from 'ui/new_platform'; + +import { + getLogEntryCategoryDatasetsRequestPayloadRT, + getLogEntryCategoryDatasetsSuccessReponsePayloadRT, + LOG_ANALYSIS_GET_LOG_ENTRY_CATEGORY_DATASETS_PATH, +} from '../../../../../common/http_api/log_analysis'; +import { createPlainError, throwErrors } from '../../../../../common/runtime_types'; + +export const callGetLogEntryCategoryDatasetsAPI = async ( + sourceId: string, + startTime: number, + endTime: number +) => { + const response = await npStart.core.http.fetch( + LOG_ANALYSIS_GET_LOG_ENTRY_CATEGORY_DATASETS_PATH, + { + method: 'POST', + body: JSON.stringify( + getLogEntryCategoryDatasetsRequestPayloadRT.encode({ + data: { + sourceId, + timeRange: { + startTime, + endTime, + }, + }, + }) + ), + } + ); + + return pipe( + getLogEntryCategoryDatasetsSuccessReponsePayloadRT.decode(response), + fold(throwErrors(createPlainError), identity) + ); +}; diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_categories/service_calls/get_top_log_entry_categories.ts b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_categories/service_calls/get_top_log_entry_categories.ts new file mode 100644 index 0000000000000..35d6f1ec4f893 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_categories/service_calls/get_top_log_entry_categories.ts @@ -0,0 +1,67 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { fold } from 'fp-ts/lib/Either'; +import { pipe } from 'fp-ts/lib/pipeable'; +import { identity } from 'fp-ts/lib/function'; +import { npStart } from 'ui/new_platform'; + +import { + getLogEntryCategoriesRequestPayloadRT, + getLogEntryCategoriesSuccessReponsePayloadRT, + LOG_ANALYSIS_GET_LOG_ENTRY_CATEGORIES_PATH, +} from '../../../../../common/http_api/log_analysis'; +import { createPlainError, throwErrors } from '../../../../../common/runtime_types'; + +export const callGetTopLogEntryCategoriesAPI = async ( + sourceId: string, + startTime: number, + endTime: number, + categoryCount: number, + datasets?: string[] +) => { + const intervalDuration = endTime - startTime; + + const response = await npStart.core.http.fetch(LOG_ANALYSIS_GET_LOG_ENTRY_CATEGORIES_PATH, { + method: 'POST', + body: JSON.stringify( + getLogEntryCategoriesRequestPayloadRT.encode({ + data: { + sourceId, + timeRange: { + startTime, + endTime, + }, + categoryCount, + datasets, + histograms: [ + { + id: 'history', + timeRange: { + startTime: startTime - intervalDuration, + endTime, + }, + bucketCount: 10, + }, + { + id: 'reference', + timeRange: { + startTime: startTime - intervalDuration, + endTime: startTime, + }, + bucketCount: 1, + }, + ], + }, + }) + ), + }); + + return pipe( + getLogEntryCategoriesSuccessReponsePayloadRT.decode(response), + fold(throwErrors(createPlainError), identity) + ); +}; diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_categories/use_log_entry_categories_results.ts b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_categories/use_log_entry_categories_results.ts new file mode 100644 index 0000000000000..2282582dc2bd6 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_categories/use_log_entry_categories_results.ts @@ -0,0 +1,116 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { useMemo, useState } from 'react'; + +import { + GetLogEntryCategoriesSuccessResponsePayload, + GetLogEntryCategoryDatasetsSuccessResponsePayload, +} from '../../../../common/http_api/log_analysis'; +import { useTrackedPromise, CanceledPromiseError } from '../../../utils/use_tracked_promise'; +import { callGetTopLogEntryCategoriesAPI } from './service_calls/get_top_log_entry_categories'; +import { callGetLogEntryCategoryDatasetsAPI } from './service_calls/get_log_entry_category_datasets'; + +type TopLogEntryCategories = GetLogEntryCategoriesSuccessResponsePayload['data']['categories']; +type LogEntryCategoryDatasets = GetLogEntryCategoryDatasetsSuccessResponsePayload['data']['datasets']; + +export const useLogEntryCategoriesResults = ({ + categoriesCount, + filteredDatasets: filteredDatasets, + endTime, + onGetLogEntryCategoryDatasetsError, + onGetTopLogEntryCategoriesError, + sourceId, + startTime, +}: { + categoriesCount: number; + filteredDatasets: string[]; + endTime: number; + onGetLogEntryCategoryDatasetsError?: (error: Error) => void; + onGetTopLogEntryCategoriesError?: (error: Error) => void; + sourceId: string; + startTime: number; +}) => { + const [topLogEntryCategories, setTopLogEntryCategories] = useState([]); + const [logEntryCategoryDatasets, setLogEntryCategoryDatasets] = useState< + LogEntryCategoryDatasets + >([]); + + const [getTopLogEntryCategoriesRequest, getTopLogEntryCategories] = useTrackedPromise( + { + cancelPreviousOn: 'creation', + createPromise: async () => { + return await callGetTopLogEntryCategoriesAPI( + sourceId, + startTime, + endTime, + categoriesCount, + filteredDatasets + ); + }, + onResolve: ({ data: { categories } }) => { + setTopLogEntryCategories(categories); + }, + onReject: error => { + if ( + error instanceof Error && + !(error instanceof CanceledPromiseError) && + onGetTopLogEntryCategoriesError + ) { + onGetTopLogEntryCategoriesError(error); + } + }, + }, + [categoriesCount, endTime, filteredDatasets, sourceId, startTime] + ); + + const [getLogEntryCategoryDatasetsRequest, getLogEntryCategoryDatasets] = useTrackedPromise( + { + cancelPreviousOn: 'creation', + createPromise: async () => { + return await callGetLogEntryCategoryDatasetsAPI(sourceId, startTime, endTime); + }, + onResolve: ({ data: { datasets } }) => { + setLogEntryCategoryDatasets(datasets); + }, + onReject: error => { + if ( + error instanceof Error && + !(error instanceof CanceledPromiseError) && + onGetLogEntryCategoryDatasetsError + ) { + onGetLogEntryCategoryDatasetsError(error); + } + }, + }, + [categoriesCount, endTime, sourceId, startTime] + ); + + const isLoadingTopLogEntryCategories = useMemo( + () => getTopLogEntryCategoriesRequest.state === 'pending', + [getTopLogEntryCategoriesRequest.state] + ); + + const isLoadingLogEntryCategoryDatasets = useMemo( + () => getLogEntryCategoryDatasetsRequest.state === 'pending', + [getLogEntryCategoryDatasetsRequest.state] + ); + + const isLoading = useMemo( + () => isLoadingTopLogEntryCategories || isLoadingLogEntryCategoryDatasets, + [isLoadingLogEntryCategoryDatasets, isLoadingTopLogEntryCategories] + ); + + return { + getLogEntryCategoryDatasets, + getTopLogEntryCategories, + isLoading, + isLoadingLogEntryCategoryDatasets, + isLoadingTopLogEntryCategories, + logEntryCategoryDatasets, + topLogEntryCategories, + }; +}; diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_categories/use_log_entry_categories_results_url_state.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_categories/use_log_entry_categories_results_url_state.tsx new file mode 100644 index 0000000000000..bf30f96e4b741 --- /dev/null +++ b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_categories/use_log_entry_categories_results_url_state.tsx @@ -0,0 +1,64 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { fold } from 'fp-ts/lib/Either'; +import { constant, identity } from 'fp-ts/lib/function'; +import { pipe } from 'fp-ts/lib/pipeable'; +import * as rt from 'io-ts'; + +import { useUrlState } from '../../../utils/use_url_state'; + +const autoRefreshRT = rt.union([ + rt.type({ + interval: rt.number, + isPaused: rt.boolean, + }), + rt.undefined, +]); + +export const stringTimeRangeRT = rt.type({ + startTime: rt.string, + endTime: rt.string, +}); +export type StringTimeRange = rt.TypeOf; + +const urlTimeRangeRT = rt.union([stringTimeRangeRT, rt.undefined]); + +const TIME_RANGE_URL_STATE_KEY = 'timeRange'; +const AUTOREFRESH_URL_STATE_KEY = 'autoRefresh'; + +export const useLogEntryCategoriesResultsUrlState = () => { + const [timeRange, setTimeRange] = useUrlState({ + defaultState: { + startTime: 'now-2w', + endTime: 'now', + }, + decodeUrlState: (value: unknown) => + pipe(urlTimeRangeRT.decode(value), fold(constant(undefined), identity)), + encodeUrlState: urlTimeRangeRT.encode, + urlStateKey: TIME_RANGE_URL_STATE_KEY, + writeDefaultState: true, + }); + + const [autoRefresh, setAutoRefresh] = useUrlState({ + defaultState: { + isPaused: false, + interval: 60000, + }, + decodeUrlState: (value: unknown) => + pipe(autoRefreshRT.decode(value), fold(constant(undefined), identity)), + encodeUrlState: autoRefreshRT.encode, + urlStateKey: AUTOREFRESH_URL_STATE_KEY, + writeDefaultState: true, + }); + + return { + timeRange, + setTimeRange, + autoRefresh, + setAutoRefresh, + }; +}; diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/first_use.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/first_use.tsx deleted file mode 100644 index 1ab9356a69e2a..0000000000000 --- a/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/first_use.tsx +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import { i18n } from '@kbn/i18n'; -import { EuiCallOut, EuiSpacer } from '@elastic/eui'; - -export const FirstUseCallout = () => { - return ( - <> - -

- {i18n.translate('xpack.infra.logs.logsAnalysisResults.onboardingSuccessContent', { - defaultMessage: - 'Please allow a few minutes for our machine learning robots to begin collecting data.', - })} -

-
- - - ); -}; diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/module_descriptor.ts b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/module_descriptor.ts index 52be313264335..52ba3101dbc38 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/module_descriptor.ts +++ b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/module_descriptor.ts @@ -4,7 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import { bucketSpan, getJobId, partitionField } from '../../../../common/log_analysis'; +import { + bucketSpan, + getJobId, + LogEntryRateJobType, + logEntryRateJobTypes, + partitionField, +} from '../../../../common/log_analysis'; import { ModuleDescriptor, @@ -16,22 +22,19 @@ import { callGetMlModuleAPI } from '../../../containers/logs/log_analysis/api/ml import { callSetupMlModuleAPI } from '../../../containers/logs/log_analysis/api/ml_setup_module_api'; import { callValidateIndicesAPI } from '../../../containers/logs/log_analysis/api/validate_indices'; -const jobTypes = ['log-entry-rate']; const moduleId = 'logs_ui_analysis'; -type JobType = typeof jobTypes[0]; - const getJobIds = (spaceId: string, sourceId: string) => - jobTypes.reduce( + logEntryRateJobTypes.reduce( (accumulatedJobIds, jobType) => ({ ...accumulatedJobIds, [jobType]: getJobId(spaceId, sourceId, jobType), }), - {} as Record + {} as Record ); const getJobSummary = async (spaceId: string, sourceId: string) => { - const response = await callJobsSummaryAPI(spaceId, sourceId, jobTypes); + const response = await callJobsSummaryAPI(spaceId, sourceId, logEntryRateJobTypes); const jobIds = Object.values(getJobIds(spaceId, sourceId)); return response.filter(jobSummary => jobIds.includes(jobSummary.id)); @@ -78,7 +81,7 @@ const setUpModule = async ( }; const cleanUpModule = async (spaceId: string, sourceId: string) => { - return await cleanUpJobsAndDatafeeds(spaceId, sourceId, jobTypes); + return await cleanUpJobsAndDatafeeds(spaceId, sourceId, logEntryRateJobTypes); }; const validateSetupIndices = async ({ indices, timestampField }: ModuleSourceConfiguration) => { @@ -94,9 +97,9 @@ const validateSetupIndices = async ({ indices, timestampField }: ModuleSourceCon ]); }; -export const logEntryRateModule: ModuleDescriptor = { +export const logEntryRateModule: ModuleDescriptor = { moduleId, - jobTypes, + jobTypes: logEntryRateJobTypes, bucketSpan, getJobIds, getJobSummary, diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/page_results_content.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/page_results_content.tsx index b6ab8acdea5b2..693444c02ce5f 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/page_results_content.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/page_results_content.tsx @@ -11,6 +11,7 @@ import { EuiFlexItem, EuiPage, EuiPanel, + EuiSpacer, EuiSuperDatePicker, EuiText, } from '@elastic/eui'; @@ -26,7 +27,6 @@ import { LoadingOverlayWrapper } from '../../../components/loading_overlay_wrapp import { useInterval } from '../../../hooks/use_interval'; import { useTrackPageview } from '../../../hooks/use_track_metric'; import { useKibanaUiSetting } from '../../../utils/use_kibana_ui_setting'; -import { FirstUseCallout } from './first_use'; import { AnomaliesResults } from './sections/anomalies'; import { LogRateResults } from './sections/log_rate'; import { useLogEntryRateModuleContext } from './use_log_entry_rate_module'; @@ -35,6 +35,7 @@ import { StringTimeRange, useLogAnalysisResultsUrlState, } from './use_log_entry_rate_results_url_state'; +import { FirstUseCallout } from '../../../components/logging/log_analysis_results'; const JOB_STATUS_POLLING_INTERVAL = 30000; @@ -196,7 +197,12 @@ export const LogEntryRateResultsContent: React.FunctionComponent = () => {
- {isFirstUse && !hasResults ? : null} + {isFirstUse && !hasResults ? ( + <> + + + + ) : null} { // This is needed due to the flex-basis: 100% !important; rule that // kicks in on small screens via media queries breaking when using direction="column" export const ResultsContentPage = euiStyled(EuiPage)` + flex: 1 0 0%; + .euiFlexGroup--responsive > .euiFlexItem { flex-basis: auto !important; } diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/chart.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/chart.tsx index e4100c6d774b1..1a3a7d9e2b572 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/chart.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/chart.tsx @@ -9,14 +9,11 @@ import { Axis, BarSeries, Chart, - getAxisId, - getSpecId, niceTimeFormatter, Settings, TooltipValue, LIGHT_THEME, DARK_THEME, - getAnnotationId, RectAnnotation, } from '@elastic/charts'; import numeral from '@elastic/numeral'; @@ -25,8 +22,11 @@ import moment from 'moment'; import React, { useCallback, useMemo } from 'react'; import { TimeRange } from '../../../../../../common/http_api/shared/time_range'; +import { + MLSeverityScoreCategories, + ML_SEVERITY_COLORS, +} from '../../../../../../common/log_analysis'; import { useKibanaUiSetting } from '../../../../../utils/use_kibana_ui_setting'; -import { MLSeverityScoreCategories } from '../helpers/data_formatters'; export const AnomaliesChart: React.FunctionComponent<{ chartId: string; @@ -44,7 +44,7 @@ export const AnomaliesChart: React.FunctionComponent<{ [timeRange] ); - const logEntryRateSpecId = getSpecId('averageValues'); + const logEntryRateSpecId = 'averageValues'; const tooltipProps = useMemo( () => ({ @@ -67,13 +67,13 @@ export const AnomaliesChart: React.FunctionComponent<{
numeral(value.toPrecision(3)).format('0[.][00]a')} // https://github.com/adamwdraper/Numeral-js/issues/194 /> @@ -102,7 +102,7 @@ export const AnomaliesChart: React.FunctionComponent<{ }; interface SeverityConfig { - annotationId: AnnotationId; + id: AnnotationId; style: { fill: string; opacity: number; @@ -111,20 +111,20 @@ interface SeverityConfig { const severityConfigs: Record = { warning: { - annotationId: getAnnotationId(`anomalies-warning`), - style: { fill: 'rgb(125, 180, 226)', opacity: 0.7 }, + id: `anomalies-warning`, + style: { fill: ML_SEVERITY_COLORS.warning, opacity: 0.7 }, }, minor: { - annotationId: getAnnotationId(`anomalies-minor`), - style: { fill: 'rgb(255, 221, 0)', opacity: 0.7 }, + id: `anomalies-minor`, + style: { fill: ML_SEVERITY_COLORS.minor, opacity: 0.7 }, }, major: { - annotationId: getAnnotationId(`anomalies-major`), - style: { fill: 'rgb(229, 113, 0)', opacity: 0.7 }, + id: `anomalies-major`, + style: { fill: ML_SEVERITY_COLORS.major, opacity: 0.7 }, }, critical: { - annotationId: getAnnotationId(`anomalies-critical`), - style: { fill: 'rgb(228, 72, 72)', opacity: 0.7 }, + id: `anomalies-critical`, + style: { fill: ML_SEVERITY_COLORS.critical, opacity: 0.7 }, }, }; @@ -138,7 +138,7 @@ const renderAnnotations = ( diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/index.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/index.tsx index e5e719c2d69f6..4aff907cfad66 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/index.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/index.tsx @@ -12,7 +12,6 @@ import { EuiStat, EuiTitle, EuiLoadingSpinner, - EuiButton, } from '@elastic/eui'; import numeral from '@elastic/numeral'; import { i18n } from '@kbn/i18n'; @@ -21,16 +20,18 @@ import React, { useMemo } from 'react'; import euiStyled from '../../../../../../../../common/eui_styled_components'; import { LogEntryRateResults } from '../../use_log_entry_rate_results'; import { TimeRange } from '../../../../../../common/http_api/shared/time_range'; -import { JobStatus, SetupStatus } from '../../../../../../common/log_analysis'; +import { formatAnomalyScore, JobStatus, SetupStatus } from '../../../../../../common/log_analysis'; import { - formatAnomalyScore, getAnnotationsForAll, getLogEntryRateCombinedSeries, getTopAnomalyScoreAcrossAllPartitions, } from '../helpers/data_formatters'; import { AnomaliesChart } from './chart'; import { AnomaliesTable } from './table'; -import { LogAnalysisJobProblemIndicator } from '../../../../../components/logging/log_analysis_job_status'; +import { + LogAnalysisJobProblemIndicator, + RecreateJobButton, +} from '../../../../../components/logging/log_analysis_job_status'; import { AnalyzeInMlButton } from '../../../../../components/logging/log_analysis_results'; import { LoadingOverlayWrapper } from '../../../../../components/loading_overlay_wrapper'; @@ -99,9 +100,7 @@ export const AnomaliesResults: React.FunctionComponent<{ - - Recreate jobs - + diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/table.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/table.tsx index 45893315c7361..3e86b45fadfdd 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/table.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/sections/anomalies/table.tsx @@ -4,15 +4,19 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useMemo, useState, useCallback } from 'react'; import { EuiBasicTable, EuiButtonIcon } from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; import { RIGHT_ALIGNMENT } from '@elastic/eui/lib/services'; +import { i18n } from '@kbn/i18n'; +import React, { useCallback, useMemo, useState } from 'react'; + +import euiStyled from '../../../../../../../../common/eui_styled_components'; import { TimeRange } from '../../../../../../common/http_api/shared/time_range'; +import { + formatAnomalyScore, + getFriendlyNameForPartitionId, +} from '../../../../../../common/log_analysis'; import { LogEntryRateResults } from '../../use_log_entry_rate_results'; import { AnomaliesTableExpandedRow } from './expanded_row'; -import { formatAnomalyScore, getFriendlyNameForPartitionId } from '../helpers/data_formatters'; -import euiStyled from '../../../../../../../../common/eui_styled_components'; interface TableItem { id: string; diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/sections/helpers/data_formatters.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/sections/helpers/data_formatters.tsx index f9b85fc4e20c2..e8e4c18e7420c 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/sections/helpers/data_formatters.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/sections/helpers/data_formatters.tsx @@ -7,17 +7,14 @@ import { RectAnnotationDatum } from '@elastic/charts'; import { i18n } from '@kbn/i18n'; +import { + formatAnomalyScore, + getFriendlyNameForPartitionId, + getSeverityCategoryForScore, + MLSeverityScoreCategories, +} from '../../../../../../common/log_analysis'; import { LogEntryRateResults } from '../../use_log_entry_rate_results'; -const ML_SEVERITY_SCORES = { - warning: 3, - minor: 25, - major: 50, - critical: 75, -}; - -export type MLSeverityScoreCategories = keyof typeof ML_SEVERITY_SCORES; - export const getLogEntryRatePartitionedSeries = (results: LogEntryRateResults) => { return results.histogramBuckets.reduce>( (buckets, bucket) => { @@ -182,26 +179,3 @@ export const getTopAnomalyScoreAcrossAllPartitions = (results: LogEntryRateResul ); return Math.max(...allTopScores); }; - -const getSeverityCategoryForScore = (score: number): MLSeverityScoreCategories | undefined => { - if (score >= ML_SEVERITY_SCORES.critical) { - return 'critical'; - } else if (score >= ML_SEVERITY_SCORES.major) { - return 'major'; - } else if (score >= ML_SEVERITY_SCORES.minor) { - return 'minor'; - } else if (score >= ML_SEVERITY_SCORES.warning) { - return 'warning'; - } else { - // Category is too low to include - return undefined; - } -}; - -export const formatAnomalyScore = (score: number) => { - return Math.round(score); -}; - -export const getFriendlyNameForPartitionId = (partitionId: string) => { - return partitionId !== '' ? partitionId : 'unknown'; -}; diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/sections/log_rate/bar_chart.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/sections/log_rate/bar_chart.tsx index 5055e3fc08239..7969c61bc9f34 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/sections/log_rate/bar_chart.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/log_entry_rate/sections/log_rate/bar_chart.tsx @@ -8,8 +8,6 @@ import { Axis, BarSeries, Chart, - getAxisId, - getSpecId, niceTimeFormatter, Settings, TooltipValue, @@ -37,8 +35,6 @@ export const LogEntryRateBarChart: React.FunctionComponent<{ [timeRange] ); - const logEntryRateSpecId = getSpecId('averageValues'); - const tooltipProps = useMemo( () => ({ headerFormatter: (tooltipData: TooltipValue) => @@ -61,18 +57,18 @@ export const LogEntryRateBarChart: React.FunctionComponent<{
numeral(value.toPrecision(3)).format('0[.][00]a')} // https://github.com/adamwdraper/Numeral-js/issues/194 /> { + const uiCapabilities = useKibana().services.application?.capabilities; + return ( + + ); +}; diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/stream/page_logs_content.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/stream/page_logs_content.tsx index 88212849d4594..f789c16b67d9c 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/stream/page_logs_content.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/stream/page_logs_content.tsx @@ -15,23 +15,22 @@ import { PageContent } from '../../../components/page'; import { WithSummary } from '../../../containers/logs/log_summary'; import { LogViewConfiguration } from '../../../containers/logs/log_view_configuration'; -import { WithLogFilter, WithLogFilterUrlState } from '../../../containers/logs/with_log_filter'; +import { LogFilterState } from '../../../containers/logs/log_filter'; import { LogFlyout as LogFlyoutState, WithFlyoutOptionsUrlState, } from '../../../containers/logs/log_flyout'; import { WithLogMinimapUrlState } from '../../../containers/logs/with_log_minimap'; -import { WithLogPositionUrlState } from '../../../containers/logs/with_log_position'; -import { WithLogPosition } from '../../../containers/logs/with_log_position'; +import { LogPositionState } from '../../../containers/logs/log_position'; import { WithLogTextviewUrlState } from '../../../containers/logs/with_log_textview'; import { WithStreamItems } from '../../../containers/logs/with_stream_items'; import { Source } from '../../../containers/source'; import { LogsToolbar } from './page_toolbar'; -import { LogHighlightsBridge, LogHighlightsState } from '../../../containers/logs/log_highlights'; +import { LogHighlightsState } from '../../../containers/logs/log_highlights'; export const LogsPageLogsContent: React.FunctionComponent = () => { - const { createDerivedIndexPattern, source, sourceId, version } = useContext(Source.Context); + const { source, sourceId, version } = useContext(Source.Context); const { intervalSize, textScale, textWrap } = useContext(LogViewConfiguration.Context); const { setFlyoutVisibility, @@ -43,121 +42,92 @@ export const LogsPageLogsContent: React.FunctionComponent = () => { isLoading, } = useContext(LogFlyoutState.Context); const { logSummaryHighlights } = useContext(LogHighlightsState.Context); - const derivedIndexPattern = createDerivedIndexPattern('logs'); + const { applyLogFilterQuery } = useContext(LogFilterState.Context); + const { + isAutoReloading, + targetPosition, + visibleMidpointTime, + visibleTimeInterval, + reportVisiblePositions, + jumpToTargetPosition, + stopLiveStreaming, + } = useContext(LogPositionState.Context); return ( <> - - - - - {({ applyFilterQueryFromKueryExpression }) => ( - - {({ jumpToTargetPosition, stopLiveStreaming }) => - flyoutVisible ? ( - { - jumpToTargetPosition(timeKey); - setSurroundingLogsId(flyoutItemId); - stopLiveStreaming(); - }} - setFlyoutVisibility={setFlyoutVisibility} - flyoutItem={flyoutItem} - loading={isLoading} - /> - ) : null - } - - )} - + {flyoutVisible ? ( + { + jumpToTargetPosition(timeKey); + setSurroundingLogsId(flyoutItemId); + stopLiveStreaming(); + }} + setFlyoutVisibility={setFlyoutVisibility} + flyoutItem={flyoutItem} + loading={isLoading} + /> + ) : null} - + {({ - isAutoReloading, - jumpToTargetPosition, - reportVisiblePositions, - targetPosition, - scrollLockLiveStreaming, - scrollUnlockLiveStreaming, - isScrollLocked, + currentHighlightKey, + hasMoreAfterEnd, + hasMoreBeforeStart, + isLoadingMore, + isReloading, + items, + lastLoadedTime, + fetchNewerEntries, }) => ( - - {({ - currentHighlightKey, - hasMoreAfterEnd, - hasMoreBeforeStart, - isLoadingMore, - isReloading, - items, - lastLoadedTime, - fetchNewerEntries, - }) => ( - - )} - + )} - + + {({ measureRef, bounds: { height = 0 }, content: { width = 0 } }) => { return ( {({ buckets }) => ( - - {({ - isAutoReloading, - jumpToTargetPosition, - visibleMidpointTime, - visibleTimeInterval, - }) => ( - - {({ isReloading }) => ( - 0 - ? logSummaryHighlights[0].buckets - : [] - } - target={visibleMidpointTime} - /> - )} - + + {({ isReloading }) => ( + 0 ? logSummaryHighlights[0].buckets : [] + } + target={visibleMidpointTime} + /> )} - + )} diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/stream/page_providers.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/stream/page_providers.tsx index 3613c000c9d23..d19f27bf1286e 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/stream/page_providers.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/stream/page_providers.tsx @@ -9,25 +9,42 @@ import React, { useContext } from 'react'; import { LogFlyout } from '../../../containers/logs/log_flyout'; import { LogViewConfiguration } from '../../../containers/logs/log_view_configuration'; import { LogHighlightsState } from '../../../containers/logs/log_highlights/log_highlights'; -import { LogPositionState } from '../../../containers/logs/log_position'; -import { LogFilterState } from '../../../containers/logs/log_filter'; +import { LogPositionState, WithLogPositionUrlState } from '../../../containers/logs/log_position'; +import { LogFilterState, WithLogFilterUrlState } from '../../../containers/logs/log_filter'; import { LogEntriesState } from '../../../containers/logs/log_entries'; import { Source } from '../../../containers/source'; +const LogFilterStateProvider: React.FC = ({ children }) => { + const { createDerivedIndexPattern } = useContext(Source.Context); + const derivedIndexPattern = createDerivedIndexPattern('logs'); + return ( + + + {children} + + ); +}; + const LogEntriesStateProvider: React.FC = ({ children }) => { const { sourceId } = useContext(Source.Context); - const { timeKey, pagesBeforeStart, pagesAfterEnd, isAutoReloading } = useContext( - LogPositionState.Context - ); + const { + targetPosition, + pagesBeforeStart, + pagesAfterEnd, + isAutoReloading, + jumpToTargetPosition, + } = useContext(LogPositionState.Context); const { filterQuery } = useContext(LogFilterState.Context); + const entriesProps = { - timeKey, + timeKey: targetPosition, pagesBeforeStart, pagesAfterEnd, filterQuery, sourceId, isAutoReloading, + jumpToTargetPosition, }; return {children}; }; @@ -51,11 +68,12 @@ export const LogsPageProviders: React.FunctionComponent = ({ children }) => { - + + {children} - + diff --git a/x-pack/legacy/plugins/infra/public/pages/logs/stream/page_toolbar.tsx b/x-pack/legacy/plugins/infra/public/pages/logs/stream/page_toolbar.tsx index 60c0ebdfc349a..000dfd1065f12 100644 --- a/x-pack/legacy/plugins/infra/public/pages/logs/stream/page_toolbar.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/logs/stream/page_toolbar.tsx @@ -19,8 +19,8 @@ import { LogTextWrapControls } from '../../../components/logging/log_text_wrap_c import { LogTimeControls } from '../../../components/logging/log_time_controls'; import { LogFlyout } from '../../../containers/logs/log_flyout'; import { LogViewConfiguration } from '../../../containers/logs/log_view_configuration'; -import { WithLogFilter } from '../../../containers/logs/with_log_filter'; -import { WithLogPosition } from '../../../containers/logs/with_log_position'; +import { LogFilterState } from '../../../containers/logs/log_filter'; +import { LogPositionState } from '../../../containers/logs/log_position'; import { Source } from '../../../containers/source'; import { WithKueryAutocompletion } from '../../../containers/with_kuery_autocompletion'; @@ -37,7 +37,12 @@ export const LogsToolbar = () => { textScale, textWrap, } = useContext(LogViewConfiguration.Context); - + const { + filterQueryDraft, + isFilterQueryDraftValid, + applyLogFilterQuery, + setLogFilterQueryDraft, + } = useContext(LogFilterState.Context); const { setSurroundingLogsId } = useContext(LogFlyout.Context); const { @@ -49,44 +54,41 @@ export const LogsToolbar = () => { goToPreviousHighlight, goToNextHighlight, } = useContext(LogHighlightsState.Context); + const { + visibleMidpointTime, + isAutoReloading, + jumpToTargetPositionTime, + startLiveStreaming, + stopLiveStreaming, + } = useContext(LogPositionState.Context); return ( {({ isLoadingSuggestions, loadSuggestions, suggestions }) => ( - - {({ - applyFilterQueryFromKueryExpression, - filterQueryDraft, - isFilterQueryDraftValid, - setFilterQueryDraftFromKueryExpression, - }) => ( - { - setSurroundingLogsId(null); - setFilterQueryDraftFromKueryExpression(expression); - }} - onSubmit={(expression: string) => { - setSurroundingLogsId(null); - applyFilterQueryFromKueryExpression(expression); - }} - placeholder={i18n.translate( - 'xpack.infra.logsPage.toolbar.kqlSearchFieldPlaceholder', - { defaultMessage: 'Search for log entries… (e.g. host.name:host-1)' } - )} - suggestions={suggestions} - value={filterQueryDraft ? filterQueryDraft.expression : ''} - aria-label={i18n.translate( - 'xpack.infra.logsPage.toolbar.kqlSearchFieldAriaLabel', - { defaultMessage: 'Search for log entries' } - )} - /> + { + setSurroundingLogsId(null); + setLogFilterQueryDraft(expression); + }} + onSubmit={(expression: string) => { + setSurroundingLogsId(null); + applyLogFilterQuery(expression); + }} + placeholder={i18n.translate( + 'xpack.infra.logsPage.toolbar.kqlSearchFieldPlaceholder', + { defaultMessage: 'Search for log entries… (e.g. host.name:host-1)' } )} - + suggestions={suggestions} + value={filterQueryDraft ? filterQueryDraft.expression : ''} + aria-label={i18n.translate('xpack.infra.logsPage.toolbar.kqlSearchFieldAriaLabel', { + defaultMessage: 'Search for log entries', + })} + /> )} @@ -119,26 +121,16 @@ export const LogsToolbar = () => { /> - - {({ - visibleMidpointTime, - isAutoReloading, - jumpToTargetPositionTime, - startLiveStreaming, - stopLiveStreaming, - }) => ( - { - startLiveStreaming(); - setSurroundingLogsId(null); - }} - stopLiveStreaming={stopLiveStreaming} - /> - )} - + { + startLiveStreaming(); + setSurroundingLogsId(null); + }} + stopLiveStreaming={stopLiveStreaming} + /> diff --git a/x-pack/legacy/plugins/infra/public/pages/metrics/components/chart_section_vis.tsx b/x-pack/legacy/plugins/infra/public/pages/metrics/components/chart_section_vis.tsx index 3d4d656823332..1731e9aa62cda 100644 --- a/x-pack/legacy/plugins/infra/public/pages/metrics/components/chart_section_vis.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/metrics/components/chart_section_vis.tsx @@ -6,15 +6,7 @@ import React, { useCallback, useMemo } from 'react'; import moment from 'moment'; import { i18n } from '@kbn/i18n'; -import { - Axis, - Chart, - getAxisId, - niceTimeFormatter, - Position, - Settings, - TooltipValue, -} from '@elastic/charts'; +import { Axis, Chart, niceTimeFormatter, Position, Settings, TooltipValue } from '@elastic/charts'; import { EuiPageContentBody } from '@elastic/eui'; import { getChartTheme } from '../../../components/metrics_explorer/helpers/get_chart_theme'; import { SeriesChart } from './series_chart'; @@ -106,12 +98,12 @@ export const ChartSectionVis = ({
- + {metric && metric.series.map(series => ( { +export const getMaxMinTimestamp = (metric: NodeDetailsMetricData): [number, number] => { if (metric.series.some(seriesHasLessThen2DataPoints)) { return [0, 0]; } diff --git a/x-pack/legacy/plugins/infra/public/pages/metrics/components/node_details_page.tsx b/x-pack/legacy/plugins/infra/public/pages/metrics/components/node_details_page.tsx index 2f4eb57cd5161..cc662f8fd5b57 100644 --- a/x-pack/legacy/plugins/infra/public/pages/metrics/components/node_details_page.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/metrics/components/node_details_page.tsx @@ -13,9 +13,9 @@ import { EuiHideFor, EuiTitle, } from '@elastic/eui'; -import { InventoryMetric } from '../../../../common/inventory_models/types'; +import { InfraTimerangeInput } from '../../../../common/http_api/snapshot_api'; +import { InventoryMetric, InventoryItemType } from '../../../../common/inventory_models/types'; import { useNodeDetails } from '../../../containers/node_details/use_node_details'; -import { InfraNodeType, InfraTimerangeInput } from '../../../graphql/types'; import { MetricsSideNav } from './side_nav'; import { AutoSizer } from '../../../components/auto_sizer'; import { MetricsTimeControls } from './time_controls'; @@ -32,7 +32,7 @@ interface Props { requiredMetrics: InventoryMetric[]; nodeId: string; cloudId: string; - nodeType: InfraNodeType; + nodeType: InventoryItemType; sourceId: string; timeRange: MetricsTimeInput; parsedTimeRange: InfraTimerangeInput; diff --git a/x-pack/legacy/plugins/infra/public/pages/metrics/components/page_body.tsx b/x-pack/legacy/plugins/infra/public/pages/metrics/components/page_body.tsx index 81c96c0ffe68d..414b9c60adee3 100644 --- a/x-pack/legacy/plugins/infra/public/pages/metrics/components/page_body.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/metrics/components/page_body.tsx @@ -8,16 +8,17 @@ import React from 'react'; import { i18n } from '@kbn/i18n'; import { findLayout } from '../../../../common/inventory_models/layouts'; import { InventoryItemType } from '../../../../common/inventory_models/types'; -import { InfraMetricData } from '../../../graphql/types'; + import { MetricsTimeInput } from '../containers/with_metrics_time'; import { InfraLoadingPanel } from '../../../components/loading'; import { NoData } from '../../../components/empty_states'; +import { NodeDetailsMetricData } from '../../../../common/http_api/node_details_api'; interface Props { loading: boolean; refetch: () => void; type: InventoryItemType; - metrics: InfraMetricData[]; + metrics: NodeDetailsMetricData[]; onChangeRangeTime?: (time: MetricsTimeInput) => void; isLiveStreaming?: boolean; stopLiveStreaming?: () => void; diff --git a/x-pack/legacy/plugins/infra/public/pages/metrics/components/series_chart.tsx b/x-pack/legacy/plugins/infra/public/pages/metrics/components/series_chart.tsx index b75f669a6d1ec..4ab45d5f46233 100644 --- a/x-pack/legacy/plugins/infra/public/pages/metrics/components/series_chart.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/metrics/components/series_chart.tsx @@ -9,9 +9,6 @@ import { AreaSeries, BarSeries, ScaleType, - getSpecId, - DataSeriesColorsValues, - CustomSeriesColorsMap, RecursivePartial, BarSeriesStyle, AreaSeriesStyle, @@ -46,15 +43,9 @@ export const AreaChart = ({ id, color, series, name, type, stack }: Props) => { visible: true, }, }; - const colors: DataSeriesColorsValues = { - colorValues: [], - specId: getSpecId(id), - }; - const customColors: CustomSeriesColorsMap = new Map(); - customColors.set(colors, color || '#999'); return ( { yAccessors={['value']} data={series.data} areaSeriesStyle={style} - customSeriesColors={color ? customColors : void 0} + customSeriesColors={color ? [color] : void 0} stackAccessors={stack ? ['timestamp'] : void 0} /> ); @@ -79,15 +70,9 @@ export const BarChart = ({ id, color, series, name, type, stack }: Props) => { opacity: 1, }, }; - const colors: DataSeriesColorsValues = { - colorValues: [], - specId: getSpecId(id), - }; - const customColors: CustomSeriesColorsMap = new Map(); - customColors.set(colors, color || '#999'); return ( { yAccessors={['value']} data={series.data} barSeriesStyle={style} - customSeriesColors={customColors} + customSeriesColors={color ? [color] : void 0} stackAccessors={stack ? ['timestamp'] : void 0} /> ); diff --git a/x-pack/legacy/plugins/infra/public/pages/metrics/containers/with_metrics.tsx b/x-pack/legacy/plugins/infra/public/pages/metrics/containers/with_metrics.tsx deleted file mode 100644 index 6f7e411628d27..0000000000000 --- a/x-pack/legacy/plugins/infra/public/pages/metrics/containers/with_metrics.tsx +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import { ApolloError } from 'apollo-client'; -import React from 'react'; -import { Query } from 'react-apollo'; -import { i18n } from '@kbn/i18n'; -import { - InfraMetric, - InfraMetricData, - InfraNodeType, - MetricsQuery, - InfraTimerangeInput, -} from '../../../graphql/types'; -import { metricsQuery } from './metrics.gql_query'; -import { InventoryMetric, InventoryMetricRT } from '../../../../common/inventory_models/types'; - -interface WithMetricsArgs { - metrics: InfraMetricData[]; - error?: ApolloError | undefined; - loading: boolean; - refetch: () => void; -} - -interface WithMetricsProps { - children: (args: WithMetricsArgs) => React.ReactNode; - requiredMetrics: InventoryMetric[]; - nodeType: InfraNodeType; - nodeId: string; - cloudId: string; - sourceId: string; - timerange: InfraTimerangeInput; -} - -const isInfraMetrics = (subject: any[]): subject is InfraMetric[] => { - return subject.every(s => InventoryMetricRT.is(s)); -}; - -export const WithMetrics = ({ - children, - requiredMetrics, - sourceId, - timerange, - nodeType, - nodeId, - cloudId, -}: WithMetricsProps) => { - if (!isInfraMetrics(requiredMetrics)) { - throw new Error( - i18n.translate('xpack.infra.invalidInventoryMetricsError', { - defaultMessage: 'One of the InfraMetric is invalid', - }) - ); - } - - return ( - - query={metricsQuery} - fetchPolicy="no-cache" - notifyOnNetworkStatusChange - variables={{ - sourceId, - metrics: requiredMetrics, - nodeType, - nodeId, - cloudId, - timerange, - }} - > - {({ data, error, loading, refetch }) => { - return children({ - metrics: filterOnlyInfraMetricData(data && data.source && data.source.metrics), - error, - loading, - refetch, - }); - }} - - ); -}; - -const filterOnlyInfraMetricData = ( - metrics: Array | undefined -): InfraMetricData[] => { - if (!metrics) { - return []; - } - return metrics.filter(m => m !== null).map(m => m as InfraMetricData); -}; diff --git a/x-pack/legacy/plugins/infra/public/pages/metrics/index.tsx b/x-pack/legacy/plugins/infra/public/pages/metrics/index.tsx index b7e8274802555..4c0d2d7c93d7a 100644 --- a/x-pack/legacy/plugins/infra/public/pages/metrics/index.tsx +++ b/x-pack/legacy/plugins/infra/public/pages/metrics/index.tsx @@ -10,7 +10,6 @@ import { DocumentTitle } from '../../components/document_title'; import { Header } from '../../components/header'; import { ColumnarPage, PageContent } from '../../components/page'; import { WithMetricsTime, WithMetricsTimeUrlState } from './containers/with_metrics_time'; -import { InfraNodeType } from '../../graphql/types'; import { withMetricPageProviders } from './page_providers'; import { useMetadata } from '../../containers/metadata/use_metadata'; import { Source } from '../../containers/source'; @@ -19,6 +18,7 @@ import { findInventoryModel } from '../../../common/inventory_models'; import { NavItem } from './lib/side_nav_context'; import { NodeDetailsPage } from './components/node_details_page'; import { useKibana } from '../../../../../../../src/plugins/kibana_react/public'; +import { InventoryItemType } from '../../../common/inventory_models/types'; const DetailPageContent = euiStyled(PageContent)` overflow: auto; @@ -39,7 +39,7 @@ export const MetricDetail = withMetricPageProviders( withTheme(({ match, theme }: Props) => { const uiCapabilities = useKibana().services.application?.capabilities; const nodeId = match.params.node; - const nodeType = match.params.type as InfraNodeType; + const nodeType = match.params.type as InventoryItemType; const inventoryModel = findInventoryModel(nodeType); const { sourceId } = useContext(Source.Context); const { diff --git a/x-pack/legacy/plugins/infra/public/pages/metrics/types.ts b/x-pack/legacy/plugins/infra/public/pages/metrics/types.ts index e752164796150..4b9e32fddcc76 100644 --- a/x-pack/legacy/plugins/infra/public/pages/metrics/types.ts +++ b/x-pack/legacy/plugins/infra/public/pages/metrics/types.ts @@ -6,12 +6,12 @@ import rt from 'io-ts'; import { EuiTheme } from '../../../../../common/eui_styled_components'; -import { InfraMetricData } from '../../graphql/types'; import { InventoryFormatterTypeRT } from '../../../common/inventory_models/types'; import { MetricsTimeInput } from './containers/with_metrics_time'; +import { NodeDetailsMetricData } from '../../../common/http_api/node_details_api'; export interface LayoutProps { - metrics?: InfraMetricData[]; + metrics?: NodeDetailsMetricData[]; onChangeRangeTime?: (time: MetricsTimeInput) => void; isLiveStreaming?: boolean; stopLiveStreaming?: () => void; @@ -55,7 +55,7 @@ export const VisSectionPropsRT = rt.partial({ export type VisSectionProps = rt.TypeOf & { id?: string; - metric?: InfraMetricData; + metric?: NodeDetailsMetricData; onChangeRangeTime?: (time: MetricsTimeInput) => void; isLiveStreaming?: boolean; stopLiveStreaming?: () => void; diff --git a/x-pack/legacy/plugins/infra/public/pages/shared/settings/index.tsx b/x-pack/legacy/plugins/infra/public/pages/shared/settings/index.tsx deleted file mode 100644 index 23ed1b273aa0e..0000000000000 --- a/x-pack/legacy/plugins/infra/public/pages/shared/settings/index.tsx +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import { SourceConfigurationSettings } from '../../../components/source_configuration/source_configuration_settings'; -import { useKibana } from '../../../../../../../../src/plugins/kibana_react/public'; - -export const SettingsPage = () => { - const uiCapabilities = useKibana().services.application?.capabilities; - return ( - - ); -}; diff --git a/x-pack/legacy/plugins/infra/public/store/actions.ts b/x-pack/legacy/plugins/infra/public/store/actions.ts index e2be0d64b8f1e..8a5d1f6c668d0 100644 --- a/x-pack/legacy/plugins/infra/public/store/actions.ts +++ b/x-pack/legacy/plugins/infra/public/store/actions.ts @@ -4,10 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -export { - logFilterActions, - logPositionActions, - waffleFilterActions, - waffleTimeActions, - waffleOptionsActions, -} from './local'; +export { waffleFilterActions, waffleTimeActions, waffleOptionsActions } from './local'; diff --git a/x-pack/legacy/plugins/infra/public/store/local/actions.ts b/x-pack/legacy/plugins/infra/public/store/local/actions.ts index 1c6918ea4dc12..1c79d5a515cd4 100644 --- a/x-pack/legacy/plugins/infra/public/store/local/actions.ts +++ b/x-pack/legacy/plugins/infra/public/store/local/actions.ts @@ -4,8 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -export { logFilterActions } from './log_filter'; -export { logPositionActions } from './log_position'; export { waffleFilterActions } from './waffle_filter'; export { waffleTimeActions } from './waffle_time'; export { waffleOptionsActions } from './waffle_options'; diff --git a/x-pack/legacy/plugins/infra/public/store/local/log_filter/actions.ts b/x-pack/legacy/plugins/infra/public/store/local/log_filter/actions.ts deleted file mode 100644 index 32da478c61969..0000000000000 --- a/x-pack/legacy/plugins/infra/public/store/local/log_filter/actions.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import actionCreatorFactory from 'typescript-fsa'; - -import { FilterQuery, SerializedFilterQuery } from './reducer'; - -const actionCreator = actionCreatorFactory('x-pack/infra/local/log_filter'); - -export const setLogFilterQueryDraft = actionCreator('SET_LOG_FILTER_QUERY_DRAFT'); - -export const applyLogFilterQuery = actionCreator('APPLY_LOG_FILTER_QUERY'); diff --git a/x-pack/legacy/plugins/infra/public/store/local/log_filter/index.ts b/x-pack/legacy/plugins/infra/public/store/local/log_filter/index.ts deleted file mode 100644 index 369f5f013d669..0000000000000 --- a/x-pack/legacy/plugins/infra/public/store/local/log_filter/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import * as logFilterActions from './actions'; -import * as logFilterSelectors from './selectors'; - -export { logFilterActions, logFilterSelectors }; -export * from './reducer'; diff --git a/x-pack/legacy/plugins/infra/public/store/local/log_filter/reducer.ts b/x-pack/legacy/plugins/infra/public/store/local/log_filter/reducer.ts deleted file mode 100644 index afb77dd9ddc6a..0000000000000 --- a/x-pack/legacy/plugins/infra/public/store/local/log_filter/reducer.ts +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { reducerWithInitialState } from 'typescript-fsa-reducers/dist'; - -import { applyLogFilterQuery, setLogFilterQueryDraft } from './actions'; - -export interface KueryFilterQuery { - kind: 'kuery'; - expression: string; -} - -export type FilterQuery = KueryFilterQuery; - -export interface SerializedFilterQuery { - query: FilterQuery; - serializedQuery: string; -} - -export interface LogFilterState { - filterQuery: SerializedFilterQuery | null; - filterQueryDraft: KueryFilterQuery | null; -} - -export const initialLogFilterState: LogFilterState = { - filterQuery: null, - filterQueryDraft: null, -}; - -export const logFilterReducer = reducerWithInitialState(initialLogFilterState) - .case(setLogFilterQueryDraft, (state, filterQueryDraft) => ({ - ...state, - filterQueryDraft, - })) - .case(applyLogFilterQuery, (state, filterQuery) => ({ - ...state, - filterQuery, - filterQueryDraft: filterQuery.query, - })) - .build(); diff --git a/x-pack/legacy/plugins/infra/public/store/local/log_filter/selectors.ts b/x-pack/legacy/plugins/infra/public/store/local/log_filter/selectors.ts deleted file mode 100644 index f17f7be4defe9..0000000000000 --- a/x-pack/legacy/plugins/infra/public/store/local/log_filter/selectors.ts +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { createSelector } from 'reselect'; -import { LogFilterState } from './reducer'; -import { esKuery } from '../../../../../../../../src/plugins/data/public'; - -export const selectLogFilterQuery = (state: LogFilterState) => - state.filterQuery ? state.filterQuery.query : null; - -export const selectLogFilterQueryAsJson = (state: LogFilterState) => - state.filterQuery ? state.filterQuery.serializedQuery : null; - -export const selectLogFilterQueryDraft = (state: LogFilterState) => state.filterQueryDraft; - -export const selectIsLogFilterQueryDraftValid = createSelector( - selectLogFilterQueryDraft, - filterQueryDraft => { - if (filterQueryDraft && filterQueryDraft.kind === 'kuery') { - try { - esKuery.fromKueryExpression(filterQueryDraft.expression); - } catch (err) { - return false; - } - } - - return true; - } -); diff --git a/x-pack/legacy/plugins/infra/public/store/local/log_position/actions.ts b/x-pack/legacy/plugins/infra/public/store/local/log_position/actions.ts deleted file mode 100644 index ad83b6fda1b04..0000000000000 --- a/x-pack/legacy/plugins/infra/public/store/local/log_position/actions.ts +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import actionCreatorFactory from 'typescript-fsa'; - -import { TimeKey } from '../../../../common/time'; - -const actionCreator = actionCreatorFactory('x-pack/infra/local/log_position'); - -export const jumpToTargetPosition = actionCreator('JUMP_TO_TARGET_POSITION'); - -export const jumpToTargetPositionTime = (time: number, fromAutoReload: boolean = false) => - jumpToTargetPosition({ - tiebreaker: 0, - time, - fromAutoReload, - }); - -export interface ReportVisiblePositionsPayload { - pagesAfterEnd: number; - pagesBeforeStart: number; - endKey: TimeKey | null; - middleKey: TimeKey | null; - startKey: TimeKey | null; - fromScroll: boolean; -} - -export const reportVisiblePositions = actionCreator( - 'REPORT_VISIBLE_POSITIONS' -); - -export const startAutoReload = actionCreator('START_AUTO_RELOAD'); -export const stopAutoReload = actionCreator('STOP_AUTO_RELOAD'); - -export const lockAutoReloadScroll = actionCreator('LOCK_AUTO_RELOAD_SCROLL'); -export const unlockAutoReloadScroll = actionCreator('UNLOCK_AUTO_RELOAD_SCROLL'); diff --git a/x-pack/legacy/plugins/infra/public/store/local/log_position/epic.ts b/x-pack/legacy/plugins/infra/public/store/local/log_position/epic.ts deleted file mode 100644 index de9e806469b06..0000000000000 --- a/x-pack/legacy/plugins/infra/public/store/local/log_position/epic.ts +++ /dev/null @@ -1,53 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { Action } from 'redux'; -import { Epic, combineEpics } from 'redux-observable'; -import { timer } from 'rxjs'; -import { exhaustMap, filter, map, takeUntil, mapTo, withLatestFrom } from 'rxjs/operators'; - -import { - jumpToTargetPosition, - jumpToTargetPositionTime, - startAutoReload, - stopAutoReload, -} from './actions'; - -const LIVE_STREAM_INTERVAL = 5000; - -const createLiveStreamEpic = (): Epic< - Action, - Action, - State, - { selectIsAutoReloadingScrollLocked: (state: State) => boolean } -> => (action$, state$, { selectIsAutoReloadingScrollLocked }) => - action$.pipe( - filter(startAutoReload.match), - exhaustMap(() => - timer(0, LIVE_STREAM_INTERVAL).pipe( - withLatestFrom(state$), - filter(([, state]) => selectIsAutoReloadingScrollLocked(state) === false), - map(() => jumpToTargetPositionTime(Date.now(), true)), - takeUntil(action$.pipe(filter(stopAutoReload.match))) - ) - ) - ); - -const createLiveStreamScrollCancelEpic = (): Epic< - Action, - Action, - State, - { selectIsAutoReloadingLogEntries: (state: State) => boolean } -> => (action$, state$, { selectIsAutoReloadingLogEntries }) => - action$.pipe( - filter(action => jumpToTargetPosition.match(action) && !action.payload.fromAutoReload), - withLatestFrom(state$), - filter(([, state]) => selectIsAutoReloadingLogEntries(state)), - mapTo(stopAutoReload()) - ); - -export const createLogPositionEpic = () => - combineEpics(createLiveStreamEpic(), createLiveStreamScrollCancelEpic()); diff --git a/x-pack/legacy/plugins/infra/public/store/local/log_position/index.ts b/x-pack/legacy/plugins/infra/public/store/local/log_position/index.ts deleted file mode 100644 index 3edb289985d55..0000000000000 --- a/x-pack/legacy/plugins/infra/public/store/local/log_position/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import * as logPositionActions from './actions'; -import * as logPositionSelectors from './selectors'; - -export { logPositionActions, logPositionSelectors }; -export * from './reducer'; diff --git a/x-pack/legacy/plugins/infra/public/store/local/log_position/reducer.ts b/x-pack/legacy/plugins/infra/public/store/local/log_position/reducer.ts deleted file mode 100644 index 2ca8be8e40d86..0000000000000 --- a/x-pack/legacy/plugins/infra/public/store/local/log_position/reducer.ts +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { combineReducers } from 'redux'; -import { reducerWithInitialState } from 'typescript-fsa-reducers/dist'; - -import { TimeKey } from '../../../../common/time'; -import { - jumpToTargetPosition, - reportVisiblePositions, - startAutoReload, - stopAutoReload, - lockAutoReloadScroll, - unlockAutoReloadScroll, -} from './actions'; - -interface ManualTargetPositionUpdatePolicy { - policy: 'manual'; -} - -interface IntervalTargetPositionUpdatePolicy { - policy: 'interval'; -} - -type TargetPositionUpdatePolicy = - | ManualTargetPositionUpdatePolicy - | IntervalTargetPositionUpdatePolicy; - -export interface LogPositionState { - targetPosition: TimeKey | null; - updatePolicy: TargetPositionUpdatePolicy; - visiblePositions: { - startKey: TimeKey | null; - middleKey: TimeKey | null; - endKey: TimeKey | null; - pagesAfterEnd: number; - pagesBeforeStart: number; - }; - controlsShouldDisplayTargetPosition: boolean; - autoReloadScrollLock: boolean; -} - -export const initialLogPositionState: LogPositionState = { - targetPosition: null, - updatePolicy: { - policy: 'manual', - }, - visiblePositions: { - endKey: null, - middleKey: null, - startKey: null, - pagesBeforeStart: Infinity, - pagesAfterEnd: Infinity, - }, - controlsShouldDisplayTargetPosition: false, - autoReloadScrollLock: false, -}; - -const targetPositionReducer = reducerWithInitialState(initialLogPositionState.targetPosition).case( - jumpToTargetPosition, - (state, target) => target -); - -const targetPositionUpdatePolicyReducer = reducerWithInitialState( - initialLogPositionState.updatePolicy -) - .case(startAutoReload, () => ({ - policy: 'interval', - })) - .case(stopAutoReload, () => ({ - policy: 'manual', - })); - -const visiblePositionReducer = reducerWithInitialState( - initialLogPositionState.visiblePositions -).case( - reportVisiblePositions, - (state, { startKey, middleKey, endKey, pagesBeforeStart, pagesAfterEnd }) => ({ - endKey, - middleKey, - startKey, - pagesBeforeStart, - pagesAfterEnd, - }) -); - -// Determines whether to use the target position or the visible midpoint when -// displaying a timestamp or time range in the toolbar and log minimap. When the -// user jumps to a new target, the final visible midpoint is indeterminate until -// all the new data has finished loading, so using this flag reduces the perception -// that the UI is jumping around inaccurately -const controlsShouldDisplayTargetPositionReducer = reducerWithInitialState( - initialLogPositionState.controlsShouldDisplayTargetPosition -) - .case(jumpToTargetPosition, () => true) - .case(stopAutoReload, () => false) - .case(startAutoReload, () => true) - .case(reportVisiblePositions, (state, { fromScroll }) => { - if (fromScroll) return false; - return state; - }); - -const autoReloadScrollLockReducer = reducerWithInitialState( - initialLogPositionState.autoReloadScrollLock -) - .case(startAutoReload, () => false) - .case(stopAutoReload, () => false) - .case(lockAutoReloadScroll, () => true) - .case(unlockAutoReloadScroll, () => false); - -export const logPositionReducer = combineReducers({ - targetPosition: targetPositionReducer, - updatePolicy: targetPositionUpdatePolicyReducer, - visiblePositions: visiblePositionReducer, - controlsShouldDisplayTargetPosition: controlsShouldDisplayTargetPositionReducer, - autoReloadScrollLock: autoReloadScrollLockReducer, -}); diff --git a/x-pack/legacy/plugins/infra/public/store/local/log_position/selectors.ts b/x-pack/legacy/plugins/infra/public/store/local/log_position/selectors.ts deleted file mode 100644 index 30fd4d3f77b5c..0000000000000 --- a/x-pack/legacy/plugins/infra/public/store/local/log_position/selectors.ts +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { createSelector } from 'reselect'; - -import { LogPositionState } from './reducer'; - -export const selectTargetPosition = (state: LogPositionState) => state.targetPosition; - -export const selectIsAutoReloading = (state: LogPositionState) => - state.updatePolicy.policy === 'interval'; - -export const selectAutoReloadScrollLock = (state: LogPositionState) => state.autoReloadScrollLock; - -export const selectFirstVisiblePosition = (state: LogPositionState) => - state.visiblePositions.startKey ? state.visiblePositions.startKey : null; - -export const selectMiddleVisiblePosition = (state: LogPositionState) => - state.visiblePositions.middleKey ? state.visiblePositions.middleKey : null; - -export const selectLastVisiblePosition = (state: LogPositionState) => - state.visiblePositions.endKey ? state.visiblePositions.endKey : null; - -export const selectPagesBeforeAndAfter = (state: LogPositionState) => - state.visiblePositions - ? { - pagesBeforeStart: state.visiblePositions.pagesBeforeStart, - pagesAfterEnd: state.visiblePositions.pagesAfterEnd, - } - : { pagesBeforeStart: null, pagesAfterEnd: null }; -export const selectControlsShouldDisplayTargetPosition = (state: LogPositionState) => - state.controlsShouldDisplayTargetPosition; - -export const selectVisibleMidpointOrTarget = createSelector( - selectMiddleVisiblePosition, - selectTargetPosition, - selectControlsShouldDisplayTargetPosition, - (middleVisiblePosition, targetPosition, displayTargetPosition) => { - if (displayTargetPosition) { - return targetPosition; - } else if (middleVisiblePosition) { - return middleVisiblePosition; - } else if (targetPosition) { - return targetPosition; - } else { - return null; - } - } -); - -export const selectVisibleMidpointOrTargetTime = createSelector( - selectVisibleMidpointOrTarget, - visibleMidpointOrTarget => (visibleMidpointOrTarget ? visibleMidpointOrTarget.time : null) -); - -export const selectVisibleTimeInterval = createSelector( - selectFirstVisiblePosition, - selectLastVisiblePosition, - (firstVisiblePosition, lastVisiblePosition) => - firstVisiblePosition && lastVisiblePosition - ? { - start: firstVisiblePosition.time, - end: lastVisiblePosition.time, - } - : null -); diff --git a/x-pack/legacy/plugins/infra/public/store/local/reducer.ts b/x-pack/legacy/plugins/infra/public/store/local/reducer.ts index 6308f1bc75427..9e194a5d37f49 100644 --- a/x-pack/legacy/plugins/infra/public/store/local/reducer.ts +++ b/x-pack/legacy/plugins/infra/public/store/local/reducer.ts @@ -6,8 +6,6 @@ import { combineReducers } from 'redux'; -import { initialLogFilterState, logFilterReducer, LogFilterState } from './log_filter'; -import { initialLogPositionState, logPositionReducer, LogPositionState } from './log_position'; import { initialWaffleFilterState, waffleFilterReducer, WaffleFilterState } from './waffle_filter'; import { initialWaffleOptionsState, @@ -17,24 +15,18 @@ import { import { initialWaffleTimeState, waffleTimeReducer, WaffleTimeState } from './waffle_time'; export interface LocalState { - logFilter: LogFilterState; - logPosition: LogPositionState; waffleFilter: WaffleFilterState; waffleTime: WaffleTimeState; waffleMetrics: WaffleOptionsState; } export const initialLocalState: LocalState = { - logFilter: initialLogFilterState, - logPosition: initialLogPositionState, waffleFilter: initialWaffleFilterState, waffleTime: initialWaffleTimeState, waffleMetrics: initialWaffleOptionsState, }; export const localReducer = combineReducers({ - logFilter: logFilterReducer, - logPosition: logPositionReducer, waffleFilter: waffleFilterReducer, waffleTime: waffleTimeReducer, waffleMetrics: waffleOptionsReducer, diff --git a/x-pack/legacy/plugins/infra/public/store/local/selectors.ts b/x-pack/legacy/plugins/infra/public/store/local/selectors.ts index ef57835496f60..56ffc53c2bc72 100644 --- a/x-pack/legacy/plugins/infra/public/store/local/selectors.ts +++ b/x-pack/legacy/plugins/infra/public/store/local/selectors.ts @@ -5,23 +5,11 @@ */ import { globalizeSelectors } from '../../utils/typed_redux'; -import { logFilterSelectors as innerLogFilterSelectors } from './log_filter'; -import { logPositionSelectors as innerLogPositionSelectors } from './log_position'; import { LocalState } from './reducer'; import { waffleFilterSelectors as innerWaffleFilterSelectors } from './waffle_filter'; import { waffleOptionsSelectors as innerWaffleOptionsSelectors } from './waffle_options'; import { waffleTimeSelectors as innerWaffleTimeSelectors } from './waffle_time'; -export const logFilterSelectors = globalizeSelectors( - (state: LocalState) => state.logFilter, - innerLogFilterSelectors -); - -export const logPositionSelectors = globalizeSelectors( - (state: LocalState) => state.logPosition, - innerLogPositionSelectors -); - export const waffleFilterSelectors = globalizeSelectors( (state: LocalState) => state.waffleFilter, innerWaffleFilterSelectors diff --git a/x-pack/legacy/plugins/infra/public/store/local/waffle_options/actions.ts b/x-pack/legacy/plugins/infra/public/store/local/waffle_options/actions.ts index d72e9e05f2aea..4a1b45084b08a 100644 --- a/x-pack/legacy/plugins/infra/public/store/local/waffle_options/actions.ts +++ b/x-pack/legacy/plugins/infra/public/store/local/waffle_options/actions.ts @@ -5,19 +5,16 @@ */ import actionCreatorFactory from 'typescript-fsa'; -import { - InfraSnapshotMetricInput, - InfraNodeType, - InfraSnapshotGroupbyInput, -} from '../../../graphql/types'; +import { SnapshotGroupBy, SnapshotMetricInput } from '../../../../common/http_api/snapshot_api'; +import { InventoryItemType } from '../../../../common/inventory_models/types'; import { InfraGroupByOptions, InfraWaffleMapBounds } from '../../../lib/lib'; const actionCreator = actionCreatorFactory('x-pack/infra/local/waffle_options'); -export const changeMetric = actionCreator('CHANGE_METRIC'); -export const changeGroupBy = actionCreator('CHANGE_GROUP_BY'); +export const changeMetric = actionCreator('CHANGE_METRIC'); +export const changeGroupBy = actionCreator('CHANGE_GROUP_BY'); export const changeCustomOptions = actionCreator('CHANGE_CUSTOM_OPTIONS'); -export const changeNodeType = actionCreator('CHANGE_NODE_TYPE'); +export const changeNodeType = actionCreator('CHANGE_NODE_TYPE'); export const changeView = actionCreator('CHANGE_VIEW'); export const changeBoundsOverride = actionCreator('CHANGE_BOUNDS_OVERRIDE'); export const changeAutoBounds = actionCreator('CHANGE_AUTO_BOUNDS'); diff --git a/x-pack/legacy/plugins/infra/public/store/local/waffle_options/reducer.ts b/x-pack/legacy/plugins/infra/public/store/local/waffle_options/reducer.ts index a8aa91695b3a5..9d86ffe612a28 100644 --- a/x-pack/legacy/plugins/infra/public/store/local/waffle_options/reducer.ts +++ b/x-pack/legacy/plugins/infra/public/store/local/waffle_options/reducer.ts @@ -6,13 +6,7 @@ import { combineReducers } from 'redux'; import { reducerWithInitialState } from 'typescript-fsa-reducers'; - -import { - InfraSnapshotMetricInput, - InfraSnapshotMetricType, - InfraNodeType, - InfraSnapshotGroupbyInput, -} from '../../../graphql/types'; +import { SnapshotMetricInput, SnapshotGroupBy } from '../../../../common/http_api/snapshot_api'; import { InfraGroupByOptions, InfraWaffleMapBounds } from '../../../lib/lib'; import { changeAutoBounds, @@ -25,11 +19,12 @@ import { changeAccount, changeRegion, } from './actions'; +import { InventoryItemType } from '../../../../common/inventory_models/types'; export interface WaffleOptionsState { - metric: InfraSnapshotMetricInput; - groupBy: InfraSnapshotGroupbyInput[]; - nodeType: InfraNodeType; + metric: SnapshotMetricInput; + groupBy: SnapshotGroupBy; + nodeType: InventoryItemType; view: string; customOptions: InfraGroupByOptions[]; boundsOverride: InfraWaffleMapBounds; @@ -39,9 +34,9 @@ export interface WaffleOptionsState { } export const initialWaffleOptionsState: WaffleOptionsState = { - metric: { type: InfraSnapshotMetricType.cpu }, + metric: { type: 'cpu' }, groupBy: [], - nodeType: InfraNodeType.host, + nodeType: 'host', view: 'map', customOptions: [], boundsOverride: { max: 1, min: 0 }, diff --git a/x-pack/legacy/plugins/infra/public/store/selectors.ts b/x-pack/legacy/plugins/infra/public/store/selectors.ts index aecba1779d036..f4011c232cba4 100644 --- a/x-pack/legacy/plugins/infra/public/store/selectors.ts +++ b/x-pack/legacy/plugins/infra/public/store/selectors.ts @@ -6,8 +6,6 @@ import { globalizeSelectors } from '../utils/typed_redux'; import { - logFilterSelectors as localLogFilterSelectors, - logPositionSelectors as localLogPositionSelectors, waffleFilterSelectors as localWaffleFilterSelectors, waffleOptionsSelectors as localWaffleOptionsSelectors, waffleTimeSelectors as localWaffleTimeSelectors, @@ -19,8 +17,6 @@ import { State } from './reducer'; const selectLocal = (state: State) => state.local; -export const logFilterSelectors = globalizeSelectors(selectLocal, localLogFilterSelectors); -export const logPositionSelectors = globalizeSelectors(selectLocal, localLogPositionSelectors); export const waffleFilterSelectors = globalizeSelectors(selectLocal, localWaffleFilterSelectors); export const waffleTimeSelectors = globalizeSelectors(selectLocal, localWaffleTimeSelectors); export const waffleOptionsSelectors = globalizeSelectors(selectLocal, localWaffleOptionsSelectors); diff --git a/x-pack/legacy/plugins/infra/public/store/store.ts b/x-pack/legacy/plugins/infra/public/store/store.ts index 601db0f56a693..cae0622c5e4a1 100644 --- a/x-pack/legacy/plugins/infra/public/store/store.ts +++ b/x-pack/legacy/plugins/infra/public/store/store.ts @@ -9,15 +9,7 @@ import { createEpicMiddleware } from 'redux-observable'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; -import { - createRootEpic, - initialState, - logFilterSelectors, - logPositionSelectors, - reducer, - State, - waffleTimeSelectors, -} from '.'; +import { createRootEpic, initialState, reducer, State, waffleTimeSelectors } from '.'; import { InfraApolloClient, InfraObservableApi } from '../lib/lib'; declare global { @@ -37,11 +29,6 @@ export function createStore({ apolloClient, observableApi }: StoreDependencies) const middlewareDependencies = { postToApi$: observableApi.pipe(map(({ post }) => post)), apolloClient$: apolloClient, - selectIsAutoReloadingLogEntries: logPositionSelectors.selectIsAutoReloading, - selectIsAutoReloadingScrollLocked: logPositionSelectors.selectAutoReloadScrollLock, - selectLogFilterQueryAsJson: logFilterSelectors.selectLogFilterQueryAsJson, - selectLogTargetPosition: logPositionSelectors.selectTargetPosition, - selectVisibleLogMidpointOrTarget: logPositionSelectors.selectVisibleMidpointOrTarget, selectWaffleTimeUpdatePolicyInterval: waffleTimeSelectors.selectTimeUpdatePolicyInterval, }; diff --git a/x-pack/legacy/plugins/infra/public/utils/use_tracked_promise.ts b/x-pack/legacy/plugins/infra/public/utils/use_tracked_promise.ts index c23bab7026aaa..e9a966b97e4dd 100644 --- a/x-pack/legacy/plugins/infra/public/utils/use_tracked_promise.ts +++ b/x-pack/legacy/plugins/infra/public/utils/use_tracked_promise.ts @@ -248,7 +248,7 @@ interface CancelablePromise { promise: Promise; } -class CanceledPromiseError extends Error { +export class CanceledPromiseError extends Error { public isCanceled = true; constructor(message?: string) { @@ -257,6 +257,6 @@ class CanceledPromiseError extends Error { } } -class SilentCanceledPromiseError extends CanceledPromiseError {} +export class SilentCanceledPromiseError extends CanceledPromiseError {} const noOp = () => undefined; diff --git a/x-pack/legacy/plugins/infra/server/infra_server.ts b/x-pack/legacy/plugins/infra/server/infra_server.ts index 108e1b1e3f392..4f290cb05f056 100644 --- a/x-pack/legacy/plugins/infra/server/infra_server.ts +++ b/x-pack/legacy/plugins/infra/server/infra_server.ts @@ -12,6 +12,8 @@ import { createSourceStatusResolvers } from './graphql/source_status'; import { createSourcesResolvers } from './graphql/sources'; import { InfraBackendLibs } from './lib/infra_types'; import { + initGetLogEntryCategoriesRoute, + initGetLogEntryCategoryDatasetsRoute, initGetLogEntryRateRoute, initValidateLogAnalysisIndicesRoute, } from './routes/log_analysis'; @@ -20,6 +22,8 @@ import { initMetadataRoute } from './routes/metadata'; import { initSnapshotRoute } from './routes/snapshot'; import { initNodeDetailsRoute } from './routes/node_details'; import { + initLogEntriesRoute, + initLogEntriesHighlightsRoute, initLogEntriesSummaryRoute, initLogEntriesSummaryHighlightsRoute, initLogEntriesItemRoute, @@ -39,10 +43,14 @@ export const initInfraServer = (libs: InfraBackendLibs) => { libs.framework.registerGraphQLEndpoint('/graphql', schema); initIpToHostName(libs); + initGetLogEntryCategoriesRoute(libs); + initGetLogEntryCategoryDatasetsRoute(libs); initGetLogEntryRateRoute(libs); initSnapshotRoute(libs); initNodeDetailsRoute(libs); initValidateLogAnalysisIndicesRoute(libs); + initLogEntriesRoute(libs); + initLogEntriesHighlightsRoute(libs); initLogEntriesSummaryRoute(libs); initLogEntriesSummaryHighlightsRoute(libs); initLogEntriesItemRoute(libs); diff --git a/x-pack/legacy/plugins/infra/server/lib/adapters/log_entries/kibana_log_entries_adapter.ts b/x-pack/legacy/plugins/infra/server/lib/adapters/log_entries/kibana_log_entries_adapter.ts index ec45171baa7b0..b936d79a8edcd 100644 --- a/x-pack/legacy/plugins/infra/server/lib/adapters/log_entries/kibana_log_entries_adapter.ts +++ b/x-pack/legacy/plugins/infra/server/lib/adapters/log_entries/kibana_log_entries_adapter.ts @@ -8,6 +8,7 @@ import { timeMilliseconds } from 'd3-time'; import * as runtimeTypes from 'io-ts'; +import { compact } from 'lodash'; import first from 'lodash/fp/first'; import get from 'lodash/fp/get'; import has from 'lodash/fp/has'; @@ -17,12 +18,14 @@ import { map, fold } from 'fp-ts/lib/Either'; import { identity, constant } from 'fp-ts/lib/function'; import { RequestHandlerContext } from 'src/core/server'; import { compareTimeKeys, isTimeKey, TimeKey } from '../../../../common/time'; -import { JsonObject } from '../../../../common/typed_json'; +import { JsonObject, JsonValue } from '../../../../common/typed_json'; import { LogEntriesAdapter, + LogEntriesParams, LogEntryDocument, LogEntryQuery, LogSummaryBucket, + LOG_ENTRIES_PAGE_SIZE, } from '../../domains/log_entries_domain'; import { InfraSourceConfiguration } from '../../sources'; import { SortedSearchHit } from '../framework'; @@ -82,6 +85,84 @@ export class InfraKibanaLogEntriesAdapter implements LogEntriesAdapter { return direction === 'asc' ? documents : documents.reverse(); } + public async getLogEntries( + requestContext: RequestHandlerContext, + sourceConfiguration: InfraSourceConfiguration, + fields: string[], + params: LogEntriesParams + ): Promise { + const { startDate, endDate, query, cursor, size, highlightTerm } = params; + + const { sortDirection, searchAfterClause } = processCursor(cursor); + + const highlightQuery = createHighlightQuery(highlightTerm, fields); + + const highlightClause = highlightQuery + ? { + highlight: { + boundary_scanner: 'word', + fields: fields.reduce( + (highlightFieldConfigs, fieldName) => ({ + ...highlightFieldConfigs, + [fieldName]: {}, + }), + {} + ), + fragment_size: 1, + number_of_fragments: 100, + post_tags: [''], + pre_tags: [''], + highlight_query: highlightQuery, + }, + } + : {}; + + const sort = { + [sourceConfiguration.fields.timestamp]: sortDirection, + [sourceConfiguration.fields.tiebreaker]: sortDirection, + }; + + const esQuery = { + allowNoIndices: true, + index: sourceConfiguration.logAlias, + ignoreUnavailable: true, + body: { + size: typeof size !== 'undefined' ? size : LOG_ENTRIES_PAGE_SIZE, + track_total_hits: false, + _source: fields, + query: { + bool: { + filter: [ + ...createFilterClauses(query, highlightQuery), + { + range: { + [sourceConfiguration.fields.timestamp]: { + gte: startDate, + lte: endDate, + format: TIMESTAMP_FORMAT, + }, + }, + }, + ], + }, + }, + sort, + ...highlightClause, + ...searchAfterClause, + }, + }; + + const esResult = await this.framework.callWithRequest( + requestContext, + 'search', + esQuery + ); + + const hits = sortDirection === 'asc' ? esResult.hits.hits : esResult.hits.hits.reverse(); + return mapHitsToLogEntryDocuments(hits, sourceConfiguration.fields.timestamp, fields); + } + + /** @deprecated */ public async getContainedLogEntryDocuments( requestContext: RequestHandlerContext, sourceConfiguration: InfraSourceConfiguration, @@ -319,6 +400,34 @@ function getLookupIntervals(start: number, direction: 'asc' | 'desc'): Array<[nu return intervals; } +function mapHitsToLogEntryDocuments( + hits: SortedSearchHit[], + timestampField: string, + fields: string[] +): LogEntryDocument[] { + return hits.map(hit => { + const logFields = fields.reduce<{ [fieldName: string]: JsonValue }>( + (flattenedFields, field) => { + if (has(field, hit._source)) { + flattenedFields[field] = get(field, hit._source); + } + return flattenedFields; + }, + {} + ); + + return { + gid: hit._id, + // timestamp: hit._source[timestampField], + // FIXME s/key/cursor/g + key: { time: hit.sort[0], tiebreaker: hit.sort[1] }, + fields: logFields, + highlights: hit.highlight || {}, + }; + }); +} + +/** @deprecated */ const convertHitToLogEntryDocument = (fields: string[]) => ( hit: SortedSearchHit ): LogEntryDocument => ({ @@ -352,9 +461,62 @@ const convertDateRangeBucketToSummaryBucket = ( })), }); +const createHighlightQuery = ( + highlightTerm: string | undefined, + fields: string[] +): LogEntryQuery | undefined => { + if (highlightTerm) { + return { + multi_match: { + fields, + lenient: true, + query: highlightTerm, + type: 'phrase', + }, + }; + } +}; + +const createFilterClauses = ( + filterQuery?: LogEntryQuery, + highlightQuery?: LogEntryQuery +): LogEntryQuery[] => { + if (filterQuery && highlightQuery) { + return [{ bool: { filter: [filterQuery, highlightQuery] } }]; + } + + return compact([filterQuery, highlightQuery]) as LogEntryQuery[]; +}; + const createQueryFilterClauses = (filterQuery: LogEntryQuery | undefined) => filterQuery ? [filterQuery] : []; +function processCursor( + cursor: LogEntriesParams['cursor'] +): { + sortDirection: 'asc' | 'desc'; + searchAfterClause: { search_after?: readonly [number, number] }; +} { + if (cursor) { + if ('before' in cursor) { + return { + sortDirection: 'desc', + searchAfterClause: + cursor.before !== 'last' + ? { search_after: [cursor.before.time, cursor.before.tiebreaker] as const } + : {}, + }; + } else if (cursor.after !== 'first') { + return { + sortDirection: 'asc', + searchAfterClause: { search_after: [cursor.after.time, cursor.after.tiebreaker] as const }, + }; + } + } + + return { sortDirection: 'asc', searchAfterClause: {} }; +} + const LogSummaryDateRangeBucketRuntimeType = runtimeTypes.intersection([ runtimeTypes.type({ doc_count: runtimeTypes.number, diff --git a/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/adapter_types.ts b/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/adapter_types.ts index 844eaf7604927..6659cb060b1a8 100644 --- a/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/adapter_types.ts +++ b/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/adapter_types.ts @@ -6,22 +6,19 @@ import { RequestHandlerContext, KibanaRequest } from 'src/core/server'; import { - InfraMetric, - InfraMetricData, - InfraNodeType, - InfraTimerangeInput, -} from '../../../graphql/types'; + NodeDetailsRequest, + NodeDetailsMetricData, +} from '../../../../common/http_api/node_details_api'; +import { InventoryMetric } from '../../../../common/inventory_models/types'; import { InfraSourceConfiguration } from '../../sources'; -export interface InfraMetricsRequestOptions { +export interface InfraMetricsRequestOptions + extends Omit { nodeIds: { nodeId: string; cloudId?: string | null; }; - nodeType: InfraNodeType; sourceConfiguration: InfraSourceConfiguration; - timerange: InfraTimerangeInput; - metrics: InfraMetric[]; } export interface InfraMetricsAdapter { @@ -29,7 +26,7 @@ export interface InfraMetricsAdapter { requestContext: RequestHandlerContext, options: InfraMetricsRequestOptions, request: KibanaRequest - ): Promise; + ): Promise; } export enum InfraMetricModelQueryType { @@ -52,7 +49,7 @@ export enum InfraMetricModelMetricType { } export interface InfraMetricModel { - id: InfraMetric; + id: InventoryMetric; requires: string[]; index_pattern: string | string[]; interval: string; diff --git a/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/kibana_metrics_adapter.ts b/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/kibana_metrics_adapter.ts index 6acb8afbfb249..eb5ac05644a22 100644 --- a/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/kibana_metrics_adapter.ts +++ b/x-pack/legacy/plugins/infra/server/lib/adapters/metrics/kibana_metrics_adapter.ts @@ -7,12 +7,16 @@ import { i18n } from '@kbn/i18n'; import { flatten, get } from 'lodash'; import { KibanaRequest, RequestHandlerContext } from 'src/core/server'; -import { InfraMetric, InfraMetricData } from '../../../graphql/types'; +import { NodeDetailsMetricData } from '../../../../common/http_api/node_details_api'; import { KibanaFramework } from '../framework/kibana_framework_adapter'; import { InfraMetricsAdapter, InfraMetricsRequestOptions } from './adapter_types'; import { checkValidNode } from './lib/check_valid_node'; import { metrics, findInventoryFields } from '../../../../common/inventory_models'; -import { TSVBMetricModelCreator } from '../../../../common/inventory_models/types'; +import { + TSVBMetricModelCreator, + InventoryMetric, + InventoryMetricRT, +} from '../../../../common/inventory_models/types'; import { calculateMetricInterval } from '../../../utils/calculate_metric_interval'; export class KibanaMetricsAdapter implements InfraMetricsAdapter { @@ -26,7 +30,7 @@ export class KibanaMetricsAdapter implements InfraMetricsAdapter { requestContext: RequestHandlerContext, options: InfraMetricsRequestOptions, rawRequest: KibanaRequest - ): Promise { + ): Promise { const indexPattern = `${options.sourceConfiguration.metricAlias},${options.sourceConfiguration.logAlias}`; const fields = findInventoryFields(options.nodeType, options.sourceConfiguration.fields); const nodeField = fields.id; @@ -58,8 +62,7 @@ export class KibanaMetricsAdapter implements InfraMetricsAdapter { ); return metricIds.map((id: string) => { - const infraMetricId: InfraMetric = (InfraMetric as any)[id]; - if (!infraMetricId) { + if (!InventoryMetricRT.is(id)) { throw new Error( i18n.translate('xpack.infra.kibanaMetrics.invalidInfraMetricErrorMessage', { defaultMessage: '{id} is not a valid InfraMetric', @@ -69,9 +72,9 @@ export class KibanaMetricsAdapter implements InfraMetricsAdapter { }) ); } - const panel = result[infraMetricId]; + const panel = result[id]; return { - id: infraMetricId, + id, series: panel.series.map(series => { return { id: series.id, @@ -87,7 +90,7 @@ export class KibanaMetricsAdapter implements InfraMetricsAdapter { } async makeTSVBRequest( - metricId: InfraMetric, + metricId: InventoryMetric, options: InfraMetricsRequestOptions, nodeField: string, requestContext: RequestHandlerContext diff --git a/x-pack/legacy/plugins/infra/server/lib/compose/kibana.ts b/x-pack/legacy/plugins/infra/server/lib/compose/kibana.ts index 305841aa52d36..d8a39a6b9c16f 100644 --- a/x-pack/legacy/plugins/infra/server/lib/compose/kibana.ts +++ b/x-pack/legacy/plugins/infra/server/lib/compose/kibana.ts @@ -12,7 +12,7 @@ import { InfraFieldsDomain } from '../domains/fields_domain'; import { InfraLogEntriesDomain } from '../domains/log_entries_domain'; import { InfraMetricsDomain } from '../domains/metrics_domain'; import { InfraBackendLibs, InfraDomainLibs } from '../infra_types'; -import { InfraLogAnalysis } from '../log_analysis'; +import { LogEntryCategoriesAnalysis, LogEntryRateAnalysis } from '../log_analysis'; import { InfraSnapshot } from '../snapshot'; import { InfraSourceStatus } from '../source_status'; import { InfraSources } from '../sources'; @@ -29,7 +29,8 @@ export function compose(core: CoreSetup, config: InfraConfig, plugins: InfraServ sources, }); const snapshot = new InfraSnapshot({ sources, framework }); - const logAnalysis = new InfraLogAnalysis({ framework }); + const logEntryCategoriesAnalysis = new LogEntryCategoriesAnalysis({ framework }); + const logEntryRateAnalysis = new LogEntryRateAnalysis({ framework }); // TODO: separate these out individually and do away with "domains" as a temporary group const domainLibs: InfraDomainLibs = { @@ -45,7 +46,8 @@ export function compose(core: CoreSetup, config: InfraConfig, plugins: InfraServ const libs: InfraBackendLibs = { configuration: config, // NP_TODO: Do we ever use this anywhere? framework, - logAnalysis, + logEntryCategoriesAnalysis, + logEntryRateAnalysis, snapshot, sources, sourceStatus, diff --git a/x-pack/legacy/plugins/infra/server/lib/domains/log_entries_domain/log_entries_domain.ts b/x-pack/legacy/plugins/infra/server/lib/domains/log_entries_domain/log_entries_domain.ts index 347f0dcf795bc..2f71d56e1e0e3 100644 --- a/x-pack/legacy/plugins/infra/server/lib/domains/log_entries_domain/log_entries_domain.ts +++ b/x-pack/legacy/plugins/infra/server/lib/domains/log_entries_domain/log_entries_domain.ts @@ -13,7 +13,10 @@ import { JsonObject } from '../../../../common/typed_json'; import { LogEntriesSummaryBucket, LogEntriesSummaryHighlightsBucket, + LogEntry, LogEntriesItem, + LogEntriesCursor, + LogColumn, } from '../../../../common/http_api'; import { InfraLogEntry, InfraLogMessageSegment } from '../../../graphql/types'; import { @@ -32,12 +35,84 @@ import { compileFormattingRules, } from './message'; +export interface LogEntriesParams { + startDate: number; + endDate: number; + size?: number; + query?: JsonObject; + cursor?: { before: LogEntriesCursor | 'last' } | { after: LogEntriesCursor | 'first' }; + highlightTerm?: string; +} +export interface LogEntriesAroundParams { + startDate: number; + endDate: number; + size?: number; + center: LogEntriesCursor; + query?: JsonObject; + highlightTerm?: string; +} + +export const LOG_ENTRIES_PAGE_SIZE = 200; + export class InfraLogEntriesDomain { constructor( private readonly adapter: LogEntriesAdapter, private readonly libs: { sources: InfraSources } ) {} + /* Name is temporary until we can clean up the GraphQL implementation */ + /* eslint-disable-next-line @typescript-eslint/camelcase */ + public async getLogEntriesAround__new( + requestContext: RequestHandlerContext, + sourceId: string, + params: LogEntriesAroundParams + ) { + const { startDate, endDate, center, query, size, highlightTerm } = params; + + /* + * For odd sizes we will round this value down for the first half, and up + * for the second. This keeps the center cursor right in the center. + * + * For even sizes the half before is one entry bigger than the half after. + * [1, 2, 3, 4, 5, *6*, 7, 8, 9, 10] + * | 5 entries | |4 entries| + */ + const halfSize = (size || LOG_ENTRIES_PAGE_SIZE) / 2; + + const entriesBefore = await this.getLogEntries(requestContext, sourceId, { + startDate, + endDate, + query, + cursor: { before: center }, + size: Math.floor(halfSize), + highlightTerm, + }); + + /* + * Elasticsearch's `search_after` returns documents after the specified cursor. + * - If we have documents before the center, we search after the last of + * those. The first document of the new group is the center. + * - If there were no documents, we search one milisecond before the + * center. It then becomes the first document. + */ + const cursorAfter = + entriesBefore.length > 0 + ? entriesBefore[entriesBefore.length - 1].cursor + : { time: center.time - 1, tiebreaker: 0 }; + + const entriesAfter = await this.getLogEntries(requestContext, sourceId, { + startDate, + endDate, + query, + cursor: { after: cursorAfter }, + size: Math.ceil(halfSize), + highlightTerm, + }); + + return [...entriesBefore, ...entriesAfter]; + } + + /** @deprecated */ public async getLogEntriesAround( requestContext: RequestHandlerContext, sourceId: string, @@ -102,6 +177,62 @@ export class InfraLogEntriesDomain { }; } + public async getLogEntries( + requestContext: RequestHandlerContext, + sourceId: string, + params: LogEntriesParams + ): Promise { + const { configuration } = await this.libs.sources.getSourceConfiguration( + requestContext, + sourceId + ); + + const messageFormattingRules = compileFormattingRules( + getBuiltinRules(configuration.fields.message) + ); + + const requiredFields = getRequiredFields(configuration, messageFormattingRules); + + const documents = await this.adapter.getLogEntries( + requestContext, + configuration, + requiredFields, + params + ); + + const entries = documents.map(doc => { + return { + id: doc.gid, + cursor: doc.key, + columns: configuration.logColumns.map( + (column): LogColumn => { + if ('timestampColumn' in column) { + return { + columnId: column.timestampColumn.id, + timestamp: doc.key.time, + }; + } else if ('messageColumn' in column) { + return { + columnId: column.messageColumn.id, + message: messageFormattingRules.format(doc.fields, doc.highlights), + }; + } else { + return { + columnId: column.fieldColumn.id, + field: column.fieldColumn.field, + value: stringify(doc.fields[column.fieldColumn.field]), + highlights: doc.highlights[column.fieldColumn.field] || [], + }; + } + } + ), + }; + }); + + return entries; + } + + /** @deprecated */ public async getLogEntriesBetween( requestContext: RequestHandlerContext, sourceId: string, @@ -133,6 +264,7 @@ export class InfraLogEntriesDomain { return entries; } + /** @deprecated */ public async getLogEntryHighlights( requestContext: RequestHandlerContext, sourceId: string, @@ -324,6 +456,13 @@ export interface LogEntriesAdapter { highlightQuery?: LogEntryQuery ): Promise; + getLogEntries( + requestContext: RequestHandlerContext, + sourceConfiguration: InfraSourceConfiguration, + fields: string[], + params: LogEntriesParams + ): Promise; + getContainedLogEntryDocuments( requestContext: RequestHandlerContext, sourceConfiguration: InfraSourceConfiguration, @@ -366,6 +505,7 @@ export interface LogSummaryBucket { topEntryKeys: TimeKey[]; } +/** @deprecated */ const convertLogDocumentToEntry = ( sourceId: string, logColumns: InfraSourceConfiguration['logColumns'], diff --git a/x-pack/legacy/plugins/infra/server/lib/domains/metrics_domain.ts b/x-pack/legacy/plugins/infra/server/lib/domains/metrics_domain.ts index e53e45afae5c4..ac76e264ff0ed 100644 --- a/x-pack/legacy/plugins/infra/server/lib/domains/metrics_domain.ts +++ b/x-pack/legacy/plugins/infra/server/lib/domains/metrics_domain.ts @@ -5,8 +5,8 @@ */ import { KibanaRequest, RequestHandlerContext } from 'src/core/server'; -import { InfraMetricData } from '../../graphql/types'; import { InfraMetricsAdapter, InfraMetricsRequestOptions } from '../adapters/metrics/adapter_types'; +import { NodeDetailsMetricData } from '../../../common/http_api/node_details_api'; export class InfraMetricsDomain { private adapter: InfraMetricsAdapter; @@ -19,7 +19,7 @@ export class InfraMetricsDomain { requestContext: RequestHandlerContext, options: InfraMetricsRequestOptions, rawRequest: KibanaRequest - ): Promise { + ): Promise { return await this.adapter.getMetrics(requestContext, options, rawRequest); } } diff --git a/x-pack/legacy/plugins/infra/server/lib/infra_types.ts b/x-pack/legacy/plugins/infra/server/lib/infra_types.ts index 46d32885600df..d52416b39596b 100644 --- a/x-pack/legacy/plugins/infra/server/lib/infra_types.ts +++ b/x-pack/legacy/plugins/infra/server/lib/infra_types.ts @@ -8,7 +8,7 @@ import { InfraSourceConfiguration } from '../../public/graphql/types'; import { InfraFieldsDomain } from './domains/fields_domain'; import { InfraLogEntriesDomain } from './domains/log_entries_domain'; import { InfraMetricsDomain } from './domains/metrics_domain'; -import { InfraLogAnalysis } from './log_analysis/log_analysis'; +import { LogEntryCategoriesAnalysis, LogEntryRateAnalysis } from './log_analysis'; import { InfraSnapshot } from './snapshot'; import { InfraSources } from './sources'; import { InfraSourceStatus } from './source_status'; @@ -31,7 +31,8 @@ export interface InfraDomainLibs { export interface InfraBackendLibs extends InfraDomainLibs { configuration: InfraConfig; framework: KibanaFramework; - logAnalysis: InfraLogAnalysis; + logEntryCategoriesAnalysis: LogEntryCategoriesAnalysis; + logEntryRateAnalysis: LogEntryRateAnalysis; snapshot: InfraSnapshot; sources: InfraSources; sourceStatus: InfraSourceStatus; diff --git a/x-pack/legacy/plugins/infra/server/lib/log_analysis/errors.ts b/x-pack/legacy/plugins/infra/server/lib/log_analysis/errors.ts index dc5c87c61fdce..d1c8316ad061b 100644 --- a/x-pack/legacy/plugins/infra/server/lib/log_analysis/errors.ts +++ b/x-pack/legacy/plugins/infra/server/lib/log_analysis/errors.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -export class NoLogRateResultsIndexError extends Error { +export class NoLogAnalysisResultsIndexError extends Error { constructor(message?: string) { super(message); Object.setPrototypeOf(this, new.target.prototype); diff --git a/x-pack/legacy/plugins/infra/server/lib/log_analysis/index.ts b/x-pack/legacy/plugins/infra/server/lib/log_analysis/index.ts index 0b58c71c1db7b..44c2bafce4194 100644 --- a/x-pack/legacy/plugins/infra/server/lib/log_analysis/index.ts +++ b/x-pack/legacy/plugins/infra/server/lib/log_analysis/index.ts @@ -5,4 +5,5 @@ */ export * from './errors'; -export * from './log_analysis'; +export * from './log_entry_categories_analysis'; +export * from './log_entry_rate_analysis'; diff --git a/x-pack/legacy/plugins/infra/server/lib/log_analysis/log_analysis.ts b/x-pack/legacy/plugins/infra/server/lib/log_analysis/log_analysis.ts deleted file mode 100644 index fac49a7980f26..0000000000000 --- a/x-pack/legacy/plugins/infra/server/lib/log_analysis/log_analysis.ts +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { pipe } from 'fp-ts/lib/pipeable'; -import { map, fold } from 'fp-ts/lib/Either'; -import { identity } from 'fp-ts/lib/function'; -import { getJobId } from '../../../common/log_analysis'; -import { throwErrors, createPlainError } from '../../../common/runtime_types'; -import { KibanaFramework } from '../adapters/framework/kibana_framework_adapter'; -import { NoLogRateResultsIndexError } from './errors'; -import { - logRateModelPlotResponseRT, - createLogEntryRateQuery, - LogRateModelPlotBucket, - CompositeTimestampPartitionKey, -} from './queries'; -import { RequestHandlerContext, KibanaRequest } from '../../../../../../../src/core/server'; - -const COMPOSITE_AGGREGATION_BATCH_SIZE = 1000; - -export class InfraLogAnalysis { - constructor( - private readonly libs: { - framework: KibanaFramework; - } - ) {} - - public getJobIds(request: KibanaRequest, sourceId: string) { - return { - logEntryRate: getJobId(this.libs.framework.getSpaceId(request), sourceId, 'log-entry-rate'), - }; - } - - public async getLogEntryRateBuckets( - requestContext: RequestHandlerContext, - sourceId: string, - startTime: number, - endTime: number, - bucketDuration: number, - request: KibanaRequest - ) { - const logRateJobId = this.getJobIds(request, sourceId).logEntryRate; - let mlModelPlotBuckets: LogRateModelPlotBucket[] = []; - let afterLatestBatchKey: CompositeTimestampPartitionKey | undefined; - - while (true) { - const mlModelPlotResponse = await this.libs.framework.callWithRequest( - requestContext, - 'search', - createLogEntryRateQuery( - logRateJobId, - startTime, - endTime, - bucketDuration, - COMPOSITE_AGGREGATION_BATCH_SIZE, - afterLatestBatchKey - ) - ); - - if (mlModelPlotResponse._shards.total === 0) { - throw new NoLogRateResultsIndexError( - `Failed to find ml result index for job ${logRateJobId}.` - ); - } - - const { after_key: afterKey, buckets: latestBatchBuckets } = pipe( - logRateModelPlotResponseRT.decode(mlModelPlotResponse), - map(response => response.aggregations.timestamp_partition_buckets), - fold(throwErrors(createPlainError), identity) - ); - - mlModelPlotBuckets = [...mlModelPlotBuckets, ...latestBatchBuckets]; - afterLatestBatchKey = afterKey; - - if (latestBatchBuckets.length < COMPOSITE_AGGREGATION_BATCH_SIZE) { - break; - } - } - - return mlModelPlotBuckets.reduce< - Array<{ - partitions: Array<{ - analysisBucketCount: number; - anomalies: Array<{ - actualLogEntryRate: number; - anomalyScore: number; - duration: number; - startTime: number; - typicalLogEntryRate: number; - }>; - averageActualLogEntryRate: number; - maximumAnomalyScore: number; - numberOfLogEntries: number; - partitionId: string; - }>; - startTime: number; - }> - >((histogramBuckets, timestampPartitionBucket) => { - const previousHistogramBucket = histogramBuckets[histogramBuckets.length - 1]; - const partition = { - analysisBucketCount: timestampPartitionBucket.filter_model_plot.doc_count, - anomalies: timestampPartitionBucket.filter_records.top_hits_record.hits.hits.map( - ({ _source: record }) => ({ - actualLogEntryRate: record.actual[0], - anomalyScore: record.record_score, - duration: record.bucket_span * 1000, - startTime: record.timestamp, - typicalLogEntryRate: record.typical[0], - }) - ), - averageActualLogEntryRate: - timestampPartitionBucket.filter_model_plot.average_actual.value || 0, - maximumAnomalyScore: - timestampPartitionBucket.filter_records.maximum_record_score.value || 0, - numberOfLogEntries: timestampPartitionBucket.filter_model_plot.sum_actual.value || 0, - partitionId: timestampPartitionBucket.key.partition, - }; - if ( - previousHistogramBucket && - previousHistogramBucket.startTime === timestampPartitionBucket.key.timestamp - ) { - return [ - ...histogramBuckets.slice(0, -1), - { - ...previousHistogramBucket, - partitions: [...previousHistogramBucket.partitions, partition], - }, - ]; - } else { - return [ - ...histogramBuckets, - { - partitions: [partition], - startTime: timestampPartitionBucket.key.timestamp, - }, - ]; - } - }, []); - } -} diff --git a/x-pack/legacy/plugins/infra/server/lib/log_analysis/log_entry_categories_analysis.ts b/x-pack/legacy/plugins/infra/server/lib/log_analysis/log_entry_categories_analysis.ts new file mode 100644 index 0000000000000..f2b6c468df69f --- /dev/null +++ b/x-pack/legacy/plugins/infra/server/lib/log_analysis/log_entry_categories_analysis.ts @@ -0,0 +1,363 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { KibanaRequest, RequestHandlerContext } from '../../../../../../../src/core/server'; +import { getJobId, logEntryCategoriesJobTypes } from '../../../common/log_analysis'; +import { startTracingSpan, TracingSpan } from '../../../common/performance_tracing'; +import { decodeOrThrow } from '../../../common/runtime_types'; +import { KibanaFramework } from '../adapters/framework/kibana_framework_adapter'; +import { NoLogAnalysisResultsIndexError } from './errors'; +import { + createLogEntryCategoriesQuery, + logEntryCategoriesResponseRT, + LogEntryCategoryHit, +} from './queries/log_entry_categories'; +import { + createLogEntryCategoryHistogramsQuery, + logEntryCategoryHistogramsResponseRT, +} from './queries/log_entry_category_histograms'; +import { + CompositeDatasetKey, + createLogEntryDatasetsQuery, + LogEntryDatasetBucket, + logEntryDatasetsResponseRT, +} from './queries/log_entry_data_sets'; +import { + createTopLogEntryCategoriesQuery, + topLogEntryCategoriesResponseRT, +} from './queries/top_log_entry_categories'; + +const COMPOSITE_AGGREGATION_BATCH_SIZE = 1000; + +export class LogEntryCategoriesAnalysis { + constructor( + private readonly libs: { + framework: KibanaFramework; + } + ) {} + + public async getTopLogEntryCategories( + requestContext: RequestHandlerContext, + request: KibanaRequest, + sourceId: string, + startTime: number, + endTime: number, + categoryCount: number, + datasets: string[], + histograms: HistogramParameters[] + ) { + const finalizeTopLogEntryCategoriesSpan = startTracingSpan('get top categories'); + + const logEntryCategoriesCountJobId = getJobId( + this.libs.framework.getSpaceId(request), + sourceId, + logEntryCategoriesJobTypes[0] + ); + + const { + topLogEntryCategories, + timing: { spans: fetchTopLogEntryCategoriesAggSpans }, + } = await this.fetchTopLogEntryCategories( + requestContext, + logEntryCategoriesCountJobId, + startTime, + endTime, + categoryCount, + datasets + ); + + const categoryIds = topLogEntryCategories.map(({ categoryId }) => categoryId); + + const { + logEntryCategoriesById, + timing: { spans: fetchTopLogEntryCategoryPatternsSpans }, + } = await this.fetchLogEntryCategories( + requestContext, + logEntryCategoriesCountJobId, + categoryIds + ); + + const { + categoryHistogramsById, + timing: { spans: fetchTopLogEntryCategoryHistogramsSpans }, + } = await this.fetchTopLogEntryCategoryHistograms( + requestContext, + logEntryCategoriesCountJobId, + categoryIds, + histograms + ); + + const topLogEntryCategoriesSpan = finalizeTopLogEntryCategoriesSpan(); + + return { + data: topLogEntryCategories.map(topCategory => ({ + ...topCategory, + regularExpression: logEntryCategoriesById[topCategory.categoryId]?._source.regex ?? '', + histograms: categoryHistogramsById[topCategory.categoryId] ?? [], + })), + timing: { + spans: [ + topLogEntryCategoriesSpan, + ...fetchTopLogEntryCategoriesAggSpans, + ...fetchTopLogEntryCategoryPatternsSpans, + ...fetchTopLogEntryCategoryHistogramsSpans, + ], + }, + }; + } + + public async getLogEntryCategoryDatasets( + requestContext: RequestHandlerContext, + request: KibanaRequest, + sourceId: string, + startTime: number, + endTime: number + ) { + const finalizeLogEntryDatasetsSpan = startTracingSpan('get data sets'); + + const logEntryCategoriesCountJobId = getJobId( + this.libs.framework.getSpaceId(request), + sourceId, + logEntryCategoriesJobTypes[0] + ); + + let logEntryDatasetBuckets: LogEntryDatasetBucket[] = []; + let afterLatestBatchKey: CompositeDatasetKey | undefined; + let esSearchSpans: TracingSpan[] = []; + + while (true) { + const finalizeEsSearchSpan = startTracingSpan('fetch category dataset batch from ES'); + + const logEntryDatasetsResponse = decodeOrThrow(logEntryDatasetsResponseRT)( + await this.libs.framework.callWithRequest( + requestContext, + 'search', + createLogEntryDatasetsQuery( + logEntryCategoriesCountJobId, + startTime, + endTime, + COMPOSITE_AGGREGATION_BATCH_SIZE, + afterLatestBatchKey + ) + ) + ); + + if (logEntryDatasetsResponse._shards.total === 0) { + throw new NoLogAnalysisResultsIndexError( + `Failed to find ml result index for job ${logEntryCategoriesCountJobId}.` + ); + } + + const { + after_key: afterKey, + buckets: latestBatchBuckets, + } = logEntryDatasetsResponse.aggregations.dataset_buckets; + + logEntryDatasetBuckets = [...logEntryDatasetBuckets, ...latestBatchBuckets]; + afterLatestBatchKey = afterKey; + esSearchSpans = [...esSearchSpans, finalizeEsSearchSpan()]; + + if (latestBatchBuckets.length < COMPOSITE_AGGREGATION_BATCH_SIZE) { + break; + } + } + + const logEntryDatasetsSpan = finalizeLogEntryDatasetsSpan(); + + return { + data: logEntryDatasetBuckets.map(logEntryDatasetBucket => logEntryDatasetBucket.key.dataset), + timing: { + spans: [logEntryDatasetsSpan, ...esSearchSpans], + }, + }; + } + + private async fetchTopLogEntryCategories( + requestContext: RequestHandlerContext, + logEntryCategoriesCountJobId: string, + startTime: number, + endTime: number, + categoryCount: number, + datasets: string[] + ) { + const finalizeEsSearchSpan = startTracingSpan('Fetch top categories from ES'); + + const topLogEntryCategoriesResponse = decodeOrThrow(topLogEntryCategoriesResponseRT)( + await this.libs.framework.callWithRequest( + requestContext, + 'search', + createTopLogEntryCategoriesQuery( + logEntryCategoriesCountJobId, + startTime, + endTime, + categoryCount, + datasets + ) + ) + ); + + const esSearchSpan = finalizeEsSearchSpan(); + + if (topLogEntryCategoriesResponse._shards.total === 0) { + throw new NoLogAnalysisResultsIndexError( + `Failed to find ml result index for job ${logEntryCategoriesCountJobId}.` + ); + } + + const topLogEntryCategories = topLogEntryCategoriesResponse.aggregations.terms_category_id.buckets.map( + topCategoryBucket => ({ + categoryId: parseCategoryId(topCategoryBucket.key), + logEntryCount: topCategoryBucket.filter_model_plot.sum_actual.value ?? 0, + datasets: topCategoryBucket.filter_model_plot.terms_dataset.buckets.map( + datasetBucket => datasetBucket.key + ), + maximumAnomalyScore: topCategoryBucket.filter_record.maximum_record_score.value ?? 0, + }) + ); + + return { + topLogEntryCategories, + timing: { + spans: [esSearchSpan], + }, + }; + } + + private async fetchLogEntryCategories( + requestContext: RequestHandlerContext, + logEntryCategoriesCountJobId: string, + categoryIds: number[] + ) { + if (categoryIds.length === 0) { + return { + logEntryCategoriesById: {}, + timing: { spans: [] }, + }; + } + + const finalizeEsSearchSpan = startTracingSpan('Fetch category patterns from ES'); + + const logEntryCategoriesResponse = decodeOrThrow(logEntryCategoriesResponseRT)( + await this.libs.framework.callWithRequest( + requestContext, + 'search', + createLogEntryCategoriesQuery(logEntryCategoriesCountJobId, categoryIds) + ) + ); + + const esSearchSpan = finalizeEsSearchSpan(); + + const logEntryCategoriesById = logEntryCategoriesResponse.hits.hits.reduce< + Record + >( + (accumulatedCategoriesById, categoryHit) => ({ + ...accumulatedCategoriesById, + [categoryHit._source.category_id]: categoryHit, + }), + {} + ); + + return { + logEntryCategoriesById, + timing: { + spans: [esSearchSpan], + }, + }; + } + + private async fetchTopLogEntryCategoryHistograms( + requestContext: RequestHandlerContext, + logEntryCategoriesCountJobId: string, + categoryIds: number[], + histograms: HistogramParameters[] + ) { + if (categoryIds.length === 0 || histograms.length === 0) { + return { + categoryHistogramsById: {}, + timing: { spans: [] }, + }; + } + + const finalizeEsSearchSpan = startTracingSpan('Fetch category histograms from ES'); + + const categoryHistogramsReponses = await Promise.all( + histograms.map(({ bucketCount, endTime, id: histogramId, startTime }) => + this.libs.framework + .callWithRequest( + requestContext, + 'search', + createLogEntryCategoryHistogramsQuery( + logEntryCategoriesCountJobId, + categoryIds, + startTime, + endTime, + bucketCount + ) + ) + .then(decodeOrThrow(logEntryCategoryHistogramsResponseRT)) + .then(response => ({ + histogramId, + histogramBuckets: response.aggregations.filters_categories.buckets, + })) + ) + ); + + const esSearchSpan = finalizeEsSearchSpan(); + + const categoryHistogramsById = Object.values(categoryHistogramsReponses).reduce< + Record< + number, + Array<{ + histogramId: string; + buckets: Array<{ + bucketDuration: number; + logEntryCount: number; + startTime: number; + }>; + }> + > + >( + (outerAccumulatedHistograms, { histogramId, histogramBuckets }) => + Object.entries(histogramBuckets).reduce( + (innerAccumulatedHistograms, [categoryBucketKey, categoryBucket]) => { + const categoryId = parseCategoryId(categoryBucketKey); + return { + ...innerAccumulatedHistograms, + [categoryId]: [ + ...(innerAccumulatedHistograms[categoryId] ?? []), + { + histogramId, + buckets: categoryBucket.histogram_timestamp.buckets.map(bucket => ({ + bucketDuration: categoryBucket.histogram_timestamp.meta.bucketDuration, + logEntryCount: bucket.sum_actual.value, + startTime: bucket.key, + })), + }, + ], + }; + }, + outerAccumulatedHistograms + ), + {} + ); + + return { + categoryHistogramsById, + timing: { + spans: [esSearchSpan], + }, + }; + } +} + +const parseCategoryId = (rawCategoryId: string) => parseInt(rawCategoryId, 10); + +interface HistogramParameters { + id: string; + startTime: number; + endTime: number; + bucketCount: number; +} diff --git a/x-pack/legacy/plugins/infra/server/lib/log_analysis/log_entry_rate_analysis.ts b/x-pack/legacy/plugins/infra/server/lib/log_analysis/log_entry_rate_analysis.ts new file mode 100644 index 0000000000000..515856fa6be8a --- /dev/null +++ b/x-pack/legacy/plugins/infra/server/lib/log_analysis/log_entry_rate_analysis.ts @@ -0,0 +1,143 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { pipe } from 'fp-ts/lib/pipeable'; +import { map, fold } from 'fp-ts/lib/Either'; +import { identity } from 'fp-ts/lib/function'; +import { getJobId } from '../../../common/log_analysis'; +import { throwErrors, createPlainError } from '../../../common/runtime_types'; +import { KibanaFramework } from '../adapters/framework/kibana_framework_adapter'; +import { NoLogAnalysisResultsIndexError } from './errors'; +import { + logRateModelPlotResponseRT, + createLogEntryRateQuery, + LogRateModelPlotBucket, + CompositeTimestampPartitionKey, +} from './queries'; +import { RequestHandlerContext, KibanaRequest } from '../../../../../../../src/core/server'; + +const COMPOSITE_AGGREGATION_BATCH_SIZE = 1000; + +export class LogEntryRateAnalysis { + constructor( + private readonly libs: { + framework: KibanaFramework; + } + ) {} + + public getJobIds(request: KibanaRequest, sourceId: string) { + return { + logEntryRate: getJobId(this.libs.framework.getSpaceId(request), sourceId, 'log-entry-rate'), + }; + } + + public async getLogEntryRateBuckets( + requestContext: RequestHandlerContext, + request: KibanaRequest, + sourceId: string, + startTime: number, + endTime: number, + bucketDuration: number + ) { + const logRateJobId = this.getJobIds(request, sourceId).logEntryRate; + let mlModelPlotBuckets: LogRateModelPlotBucket[] = []; + let afterLatestBatchKey: CompositeTimestampPartitionKey | undefined; + + while (true) { + const mlModelPlotResponse = await this.libs.framework.callWithRequest( + requestContext, + 'search', + createLogEntryRateQuery( + logRateJobId, + startTime, + endTime, + bucketDuration, + COMPOSITE_AGGREGATION_BATCH_SIZE, + afterLatestBatchKey + ) + ); + + if (mlModelPlotResponse._shards.total === 0) { + throw new NoLogAnalysisResultsIndexError( + `Failed to find ml result index for job ${logRateJobId}.` + ); + } + + const { after_key: afterKey, buckets: latestBatchBuckets } = pipe( + logRateModelPlotResponseRT.decode(mlModelPlotResponse), + map(response => response.aggregations.timestamp_partition_buckets), + fold(throwErrors(createPlainError), identity) + ); + + mlModelPlotBuckets = [...mlModelPlotBuckets, ...latestBatchBuckets]; + afterLatestBatchKey = afterKey; + + if (latestBatchBuckets.length < COMPOSITE_AGGREGATION_BATCH_SIZE) { + break; + } + } + + return mlModelPlotBuckets.reduce< + Array<{ + partitions: Array<{ + analysisBucketCount: number; + anomalies: Array<{ + actualLogEntryRate: number; + anomalyScore: number; + duration: number; + startTime: number; + typicalLogEntryRate: number; + }>; + averageActualLogEntryRate: number; + maximumAnomalyScore: number; + numberOfLogEntries: number; + partitionId: string; + }>; + startTime: number; + }> + >((histogramBuckets, timestampPartitionBucket) => { + const previousHistogramBucket = histogramBuckets[histogramBuckets.length - 1]; + const partition = { + analysisBucketCount: timestampPartitionBucket.filter_model_plot.doc_count, + anomalies: timestampPartitionBucket.filter_records.top_hits_record.hits.hits.map( + ({ _source: record }) => ({ + actualLogEntryRate: record.actual[0], + anomalyScore: record.record_score, + duration: record.bucket_span * 1000, + startTime: record.timestamp, + typicalLogEntryRate: record.typical[0], + }) + ), + averageActualLogEntryRate: + timestampPartitionBucket.filter_model_plot.average_actual.value || 0, + maximumAnomalyScore: + timestampPartitionBucket.filter_records.maximum_record_score.value || 0, + numberOfLogEntries: timestampPartitionBucket.filter_model_plot.sum_actual.value || 0, + partitionId: timestampPartitionBucket.key.partition, + }; + if ( + previousHistogramBucket && + previousHistogramBucket.startTime === timestampPartitionBucket.key.timestamp + ) { + return [ + ...histogramBuckets.slice(0, -1), + { + ...previousHistogramBucket, + partitions: [...previousHistogramBucket.partitions, partition], + }, + ]; + } else { + return [ + ...histogramBuckets, + { + partitions: [partition], + startTime: timestampPartitionBucket.key.timestamp, + }, + ]; + } + }, []); + } +} diff --git a/x-pack/legacy/plugins/infra/server/lib/log_analysis/queries/common.ts b/x-pack/legacy/plugins/infra/server/lib/log_analysis/queries/common.ts new file mode 100644 index 0000000000000..92ef4fb4e35c9 --- /dev/null +++ b/x-pack/legacy/plugins/infra/server/lib/log_analysis/queries/common.ts @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +const ML_ANOMALY_INDEX_PREFIX = '.ml-anomalies-'; + +export const getMlResultIndex = (jobId: string) => `${ML_ANOMALY_INDEX_PREFIX}${jobId}`; + +export const defaultRequestParameters = { + allowNoIndices: true, + ignoreUnavailable: true, + trackScores: false, + trackTotalHits: false, +}; + +export const createTimeRangeFilters = (startTime: number, endTime: number) => [ + { + range: { + timestamp: { + gte: startTime, + lte: endTime, + }, + }, + }, +]; + +export const createResultTypeFilters = (resultType: 'model_plot' | 'record') => [ + { + term: { + result_type: { + value: resultType, + }, + }, + }, +]; diff --git a/x-pack/legacy/plugins/infra/server/lib/log_analysis/queries/index.ts b/x-pack/legacy/plugins/infra/server/lib/log_analysis/queries/index.ts index 1749421277719..8c470acbf02fb 100644 --- a/x-pack/legacy/plugins/infra/server/lib/log_analysis/queries/index.ts +++ b/x-pack/legacy/plugins/infra/server/lib/log_analysis/queries/index.ts @@ -5,3 +5,4 @@ */ export * from './log_entry_rate'; +export * from './top_log_entry_categories'; diff --git a/x-pack/legacy/plugins/infra/server/lib/log_analysis/queries/log_entry_categories.ts b/x-pack/legacy/plugins/infra/server/lib/log_analysis/queries/log_entry_categories.ts new file mode 100644 index 0000000000000..63b3632f03784 --- /dev/null +++ b/x-pack/legacy/plugins/infra/server/lib/log_analysis/queries/log_entry_categories.ts @@ -0,0 +1,53 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as rt from 'io-ts'; + +import { commonSearchSuccessResponseFieldsRT } from '../../../utils/elasticsearch_runtime_types'; +import { defaultRequestParameters, getMlResultIndex } from './common'; + +export const createLogEntryCategoriesQuery = ( + logEntryCategoriesJobId: string, + categoryIds: number[] +) => ({ + ...defaultRequestParameters, + body: { + query: { + bool: { + filter: [ + { + terms: { + category_id: categoryIds, + }, + }, + ], + }, + }, + _source: ['category_id', 'regex'], + }, + index: getMlResultIndex(logEntryCategoriesJobId), + size: categoryIds.length, +}); + +export const logEntryCategoryHitRT = rt.type({ + _source: rt.type({ + category_id: rt.number, + regex: rt.string, + }), +}); + +export type LogEntryCategoryHit = rt.TypeOf; + +export const logEntryCategoriesResponseRT = rt.intersection([ + commonSearchSuccessResponseFieldsRT, + rt.type({ + hits: rt.type({ + hits: rt.array(logEntryCategoryHitRT), + }), + }), +]); + +export type logEntryCategoriesResponse = rt.TypeOf; diff --git a/x-pack/legacy/plugins/infra/server/lib/log_analysis/queries/log_entry_category_histograms.ts b/x-pack/legacy/plugins/infra/server/lib/log_analysis/queries/log_entry_category_histograms.ts new file mode 100644 index 0000000000000..67087f3b4775b --- /dev/null +++ b/x-pack/legacy/plugins/infra/server/lib/log_analysis/queries/log_entry_category_histograms.ts @@ -0,0 +1,125 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as rt from 'io-ts'; + +import { commonSearchSuccessResponseFieldsRT } from '../../../utils/elasticsearch_runtime_types'; +import { + createResultTypeFilters, + createTimeRangeFilters, + defaultRequestParameters, + getMlResultIndex, +} from './common'; + +export const createLogEntryCategoryHistogramsQuery = ( + logEntryCategoriesJobId: string, + categoryIds: number[], + startTime: number, + endTime: number, + bucketCount: number +) => ({ + ...defaultRequestParameters, + body: { + query: { + bool: { + filter: [ + ...createTimeRangeFilters(startTime, endTime), + ...createResultTypeFilters('model_plot'), + ...createCategoryFilters(categoryIds), + ], + }, + }, + aggs: { + filters_categories: { + filters: createCategoryFiltersAggregation(categoryIds), + aggs: { + histogram_timestamp: createHistogramAggregation(startTime, endTime, bucketCount), + }, + }, + }, + }, + index: getMlResultIndex(logEntryCategoriesJobId), + size: 0, +}); + +const createCategoryFilters = (categoryIds: number[]) => [ + { + terms: { + by_field_value: categoryIds, + }, + }, +]; + +const createCategoryFiltersAggregation = (categoryIds: number[]) => ({ + filters: categoryIds.reduce>( + (categoryFilters, categoryId) => ({ + ...categoryFilters, + [`${categoryId}`]: { + term: { + by_field_value: categoryId, + }, + }, + }), + {} + ), +}); + +const createHistogramAggregation = (startTime: number, endTime: number, bucketCount: number) => { + const bucketDuration = Math.round((endTime - startTime) / bucketCount); + + return { + histogram: { + field: 'timestamp', + interval: bucketDuration, + offset: startTime, + }, + meta: { + bucketDuration, + }, + aggs: { + sum_actual: { + sum: { + field: 'actual', + }, + }, + }, + }; +}; + +export const logEntryCategoryFilterBucketRT = rt.type({ + doc_count: rt.number, + histogram_timestamp: rt.type({ + meta: rt.type({ + bucketDuration: rt.number, + }), + buckets: rt.array( + rt.type({ + key: rt.number, + doc_count: rt.number, + sum_actual: rt.type({ + value: rt.number, + }), + }) + ), + }), +}); + +export type LogEntryCategoryFilterBucket = rt.TypeOf; + +export const logEntryCategoryHistogramsResponseRT = rt.intersection([ + commonSearchSuccessResponseFieldsRT, + rt.type({ + aggregations: rt.type({ + filters_categories: rt.type({ + buckets: rt.record(rt.string, logEntryCategoryFilterBucketRT), + }), + }), + }), +]); + +export type LogEntryCategorHistogramsResponse = rt.TypeOf< + typeof logEntryCategoryHistogramsResponseRT +>; diff --git a/x-pack/legacy/plugins/infra/server/lib/log_analysis/queries/log_entry_data_sets.ts b/x-pack/legacy/plugins/infra/server/lib/log_analysis/queries/log_entry_data_sets.ts new file mode 100644 index 0000000000000..b41a21a21b6a6 --- /dev/null +++ b/x-pack/legacy/plugins/infra/server/lib/log_analysis/queries/log_entry_data_sets.ts @@ -0,0 +1,93 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as rt from 'io-ts'; + +import { commonSearchSuccessResponseFieldsRT } from '../../../utils/elasticsearch_runtime_types'; +import { defaultRequestParameters, getMlResultIndex } from './common'; + +export const createLogEntryDatasetsQuery = ( + logEntryAnalysisJobId: string, + startTime: number, + endTime: number, + size: number, + afterKey?: CompositeDatasetKey +) => ({ + ...defaultRequestParameters, + body: { + query: { + bool: { + filter: [ + { + range: { + timestamp: { + gte: startTime, + lt: endTime, + }, + }, + }, + { + term: { + result_type: { + value: 'model_plot', + }, + }, + }, + ], + }, + }, + aggs: { + dataset_buckets: { + composite: { + after: afterKey, + size, + sources: [ + { + dataset: { + terms: { + field: 'partition_field_value', + order: 'asc', + }, + }, + }, + ], + }, + }, + }, + }, + index: getMlResultIndex(logEntryAnalysisJobId), + size: 0, +}); + +const compositeDatasetKeyRT = rt.type({ + dataset: rt.string, +}); + +export type CompositeDatasetKey = rt.TypeOf; + +const logEntryDatasetBucketRT = rt.type({ + key: compositeDatasetKeyRT, +}); + +export type LogEntryDatasetBucket = rt.TypeOf; + +export const logEntryDatasetsResponseRT = rt.intersection([ + commonSearchSuccessResponseFieldsRT, + rt.type({ + aggregations: rt.type({ + dataset_buckets: rt.intersection([ + rt.type({ + buckets: rt.array(logEntryDatasetBucketRT), + }), + rt.partial({ + after_key: compositeDatasetKeyRT, + }), + ]), + }), + }), +]); + +export type LogEntryDatasetsResponse = rt.TypeOf; diff --git a/x-pack/legacy/plugins/infra/server/lib/log_analysis/queries/log_entry_rate.ts b/x-pack/legacy/plugins/infra/server/lib/log_analysis/queries/log_entry_rate.ts index 2dd0880cbf8cb..def7caf578b94 100644 --- a/x-pack/legacy/plugins/infra/server/lib/log_analysis/queries/log_entry_rate.ts +++ b/x-pack/legacy/plugins/infra/server/lib/log_analysis/queries/log_entry_rate.ts @@ -6,7 +6,7 @@ import * as rt from 'io-ts'; -const ML_ANOMALY_INDEX_PREFIX = '.ml-anomalies-'; +import { defaultRequestParameters, getMlResultIndex } from './common'; export const createLogEntryRateQuery = ( logRateJobId: string, @@ -16,7 +16,7 @@ export const createLogEntryRateQuery = ( size: number, afterKey?: CompositeTimestampPartitionKey ) => ({ - allowNoIndices: true, + ...defaultRequestParameters, body: { query: { bool: { @@ -118,11 +118,8 @@ export const createLogEntryRateQuery = ( }, }, }, - ignoreUnavailable: true, - index: `${ML_ANOMALY_INDEX_PREFIX}${logRateJobId}`, + index: getMlResultIndex(logRateJobId), size: 0, - trackScores: false, - trackTotalHits: false, }); const logRateMlRecordRT = rt.type({ diff --git a/x-pack/legacy/plugins/infra/server/lib/log_analysis/queries/top_log_entry_categories.ts b/x-pack/legacy/plugins/infra/server/lib/log_analysis/queries/top_log_entry_categories.ts new file mode 100644 index 0000000000000..22b0ef748f5f8 --- /dev/null +++ b/x-pack/legacy/plugins/infra/server/lib/log_analysis/queries/top_log_entry_categories.ts @@ -0,0 +1,160 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as rt from 'io-ts'; + +import { commonSearchSuccessResponseFieldsRT } from '../../../utils/elasticsearch_runtime_types'; +import { + createResultTypeFilters, + createTimeRangeFilters, + defaultRequestParameters, + getMlResultIndex, +} from './common'; + +export const createTopLogEntryCategoriesQuery = ( + logEntryCategoriesJobId: string, + startTime: number, + endTime: number, + size: number, + datasets: string[], + sortDirection: 'asc' | 'desc' = 'desc' +) => ({ + ...defaultRequestParameters, + body: { + query: { + bool: { + filter: [ + ...createTimeRangeFilters(startTime, endTime), + ...createDatasetsFilters(datasets), + { + bool: { + should: [ + { + bool: { + filter: [ + ...createResultTypeFilters('model_plot'), + { + range: { + actual: { + gt: 0, + }, + }, + }, + ], + }, + }, + { + bool: { + filter: createResultTypeFilters('record'), + }, + }, + ], + minimum_should_match: 1, + }, + }, + ], + }, + }, + aggs: { + terms_category_id: { + terms: { + field: 'by_field_value', + size, + order: { + 'filter_model_plot>sum_actual': sortDirection, + }, + }, + aggs: { + filter_model_plot: { + filter: { + term: { + result_type: 'model_plot', + }, + }, + aggs: { + sum_actual: { + sum: { + field: 'actual', + }, + }, + terms_dataset: { + terms: { + field: 'partition_field_value', + size: 1000, + }, + }, + }, + }, + filter_record: { + filter: { + term: { + result_type: 'record', + }, + }, + aggs: { + maximum_record_score: { + max: { + field: 'record_score', + }, + }, + }, + }, + }, + }, + }, + }, + index: getMlResultIndex(logEntryCategoriesJobId), + size: 0, +}); + +const createDatasetsFilters = (datasets: string[]) => + datasets.length > 0 + ? [ + { + terms: { + partition_field_value: datasets, + }, + }, + ] + : []; + +const metricAggregationRT = rt.type({ + value: rt.union([rt.number, rt.null]), +}); + +export const logEntryCategoryBucketRT = rt.type({ + key: rt.string, + doc_count: rt.number, + filter_record: rt.type({ + maximum_record_score: metricAggregationRT, + }), + filter_model_plot: rt.type({ + sum_actual: metricAggregationRT, + terms_dataset: rt.type({ + buckets: rt.array( + rt.type({ + key: rt.string, + doc_count: rt.number, + }) + ), + }), + }), +}); + +export type LogEntryCategoryBucket = rt.TypeOf; + +export const topLogEntryCategoriesResponseRT = rt.intersection([ + commonSearchSuccessResponseFieldsRT, + rt.type({ + aggregations: rt.type({ + terms_category_id: rt.type({ + buckets: rt.array(logEntryCategoryBucketRT), + }), + }), + }), +]); + +export type TopLogEntryCategoriesResponse = rt.TypeOf; diff --git a/x-pack/legacy/plugins/infra/server/lib/snapshot/create_timerange_with_interval.ts b/x-pack/legacy/plugins/infra/server/lib/snapshot/create_timerange_with_interval.ts index 6c27e54a78bee..6f036475a1e1c 100644 --- a/x-pack/legacy/plugins/infra/server/lib/snapshot/create_timerange_with_interval.ts +++ b/x-pack/legacy/plugins/infra/server/lib/snapshot/create_timerange_with_interval.ts @@ -7,11 +7,12 @@ import { uniq } from 'lodash'; import { RequestHandlerContext } from 'kibana/server'; import { InfraSnapshotRequestOptions } from './types'; -import { InfraTimerangeInput } from '../../../public/graphql/types'; import { getMetricsAggregations } from './query_helpers'; import { calculateMetricInterval } from '../../utils/calculate_metric_interval'; import { SnapshotModel, SnapshotModelMetricAggRT } from '../../../common/inventory_models/types'; import { KibanaFramework } from '../adapters/framework/kibana_framework_adapter'; +import { getDatasetForField } from '../../routes/metrics_explorer/lib/get_dataset_for_field'; +import { InfraTimerangeInput } from '../../../common/http_api/snapshot_api'; export const createTimeRangeWithInterval = async ( framework: KibanaFramework, @@ -19,7 +20,7 @@ export const createTimeRangeWithInterval = async ( options: InfraSnapshotRequestOptions ): Promise => { const aggregations = getMetricsAggregations(options); - const modules = aggregationsToModules(aggregations); + const modules = await aggregationsToModules(framework, requestContext, aggregations, options); const interval = (await calculateMetricInterval( framework, @@ -39,21 +40,30 @@ export const createTimeRangeWithInterval = async ( }; }; -const aggregationsToModules = (aggregations: SnapshotModel): string[] => { - return uniq( - Object.values(aggregations) - .reduce((modules, agg) => { - if (SnapshotModelMetricAggRT.is(agg)) { - return modules.concat(Object.values(agg).map(a => a?.field)); - } - return modules; - }, [] as Array) - .filter(v => v) - .map(field => - field! - .split(/\./) - .slice(0, 2) - .join('.') - ) - ) as string[]; +const aggregationsToModules = async ( + framework: KibanaFramework, + requestContext: RequestHandlerContext, + aggregations: SnapshotModel, + options: InfraSnapshotRequestOptions +): Promise => { + const uniqueFields = Object.values(aggregations) + .reduce>((fields, agg) => { + if (SnapshotModelMetricAggRT.is(agg)) { + return uniq(fields.concat(Object.values(agg).map(a => a?.field))); + } + return fields; + }, []) + .filter(v => v) as string[]; + const fields = await Promise.all( + uniqueFields.map( + async field => + await getDatasetForField( + framework, + requestContext, + field as string, + options.sourceConfiguration.metricAlias + ) + ) + ); + return fields.filter(f => f) as string[]; }; diff --git a/x-pack/legacy/plugins/infra/server/lib/snapshot/response_helpers.test.ts b/x-pack/legacy/plugins/infra/server/lib/snapshot/response_helpers.test.ts index 28146624a8a89..20220aef1133d 100644 --- a/x-pack/legacy/plugins/infra/server/lib/snapshot/response_helpers.test.ts +++ b/x-pack/legacy/plugins/infra/server/lib/snapshot/response_helpers.test.ts @@ -5,7 +5,6 @@ */ import { isIPv4, getIPFromBucket, InfraSnapshotNodeGroupByBucket } from './response_helpers'; -import { InfraNodeType } from '../../graphql/types'; describe('InfraOps ResponseHelpers', () => { describe('isIPv4', () => { @@ -44,7 +43,7 @@ describe('InfraOps ResponseHelpers', () => { }, }, }; - expect(getIPFromBucket(InfraNodeType.host, bucket)).toBe('192.168.1.4'); + expect(getIPFromBucket('host', bucket)).toBe('192.168.1.4'); }); it('should NOT return ipv6 address', () => { const bucket: InfraSnapshotNodeGroupByBucket = { @@ -72,7 +71,7 @@ describe('InfraOps ResponseHelpers', () => { }, }, }; - expect(getIPFromBucket(InfraNodeType.host, bucket)).toBe(null); + expect(getIPFromBucket('host', bucket)).toBe(null); }); }); }); diff --git a/x-pack/legacy/plugins/infra/server/lib/snapshot/response_helpers.ts b/x-pack/legacy/plugins/infra/server/lib/snapshot/response_helpers.ts index d22f41ff152f7..12f284c363bd5 100644 --- a/x-pack/legacy/plugins/infra/server/lib/snapshot/response_helpers.ts +++ b/x-pack/legacy/plugins/infra/server/lib/snapshot/response_helpers.ts @@ -7,15 +7,11 @@ import { isNumber, last, max, sum, get } from 'lodash'; import moment from 'moment'; -import { - InfraSnapshotMetricType, - InfraSnapshotNodePath, - InfraSnapshotNodeMetric, - InfraNodeType, -} from '../../graphql/types'; import { getIntervalInSeconds } from '../../utils/get_interval_in_seconds'; import { InfraSnapshotRequestOptions } from './types'; import { findInventoryModel } from '../../../common/inventory_models'; +import { InventoryItemType, SnapshotMetricType } from '../../../common/inventory_models/types'; +import { SnapshotNodeMetric, SnapshotNodePath } from '../../../common/http_api/snapshot_api'; export interface InfraSnapshotNodeMetricsBucket { key: { id: string }; @@ -70,7 +66,7 @@ export interface InfraSnapshotNodeGroupByBucket { export const isIPv4 = (subject: string) => /^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$/.test(subject); export const getIPFromBucket = ( - nodeType: InfraNodeType, + nodeType: InventoryItemType, bucket: InfraSnapshotNodeGroupByBucket ): string | null => { const inventoryModel = findInventoryModel(nodeType); @@ -92,10 +88,10 @@ export const getIPFromBucket = ( export const getNodePath = ( groupBucket: InfraSnapshotNodeGroupByBucket, options: InfraSnapshotRequestOptions -): InfraSnapshotNodePath[] => { +): SnapshotNodePath[] => { const node = groupBucket.key; const path = options.groupBy.map(gb => { - return { value: node[`${gb.field}`], label: node[`${gb.field}`] } as InfraSnapshotNodePath; + return { value: node[`${gb.field}`], label: node[`${gb.field}`] } as SnapshotNodePath; }); const ip = getIPFromBucket(options.nodeType, groupBucket); path.push({ value: node.id, label: node.name || node.id, ip }); @@ -121,7 +117,7 @@ export const getNodeMetricsForLookup = ( export const getNodeMetrics = ( nodeBuckets: InfraSnapshotMetricsBucket[], options: InfraSnapshotRequestOptions -): InfraSnapshotNodeMetric => { +): SnapshotNodeMetric => { if (!nodeBuckets) { return { name: options.metric.type, @@ -156,18 +152,15 @@ const findLastFullBucket = ( }, last(buckets)); }; -const getMetricValueFromBucket = ( - type: InfraSnapshotMetricType, - bucket: InfraSnapshotMetricsBucket -) => { +const getMetricValueFromBucket = (type: SnapshotMetricType, bucket: InfraSnapshotMetricsBucket) => { const metric = bucket[type]; return (metric && (metric.normalized_value || metric.value)) || 0; }; -function calculateMax(buckets: InfraSnapshotMetricsBucket[], type: InfraSnapshotMetricType) { +function calculateMax(buckets: InfraSnapshotMetricsBucket[], type: SnapshotMetricType) { return max(buckets.map(bucket => getMetricValueFromBucket(type, bucket))) || 0; } -function calculateAvg(buckets: InfraSnapshotMetricsBucket[], type: InfraSnapshotMetricType) { +function calculateAvg(buckets: InfraSnapshotMetricsBucket[], type: SnapshotMetricType) { return sum(buckets.map(bucket => getMetricValueFromBucket(type, bucket))) / buckets.length || 0; } diff --git a/x-pack/legacy/plugins/infra/server/lib/snapshot/snapshot.ts b/x-pack/legacy/plugins/infra/server/lib/snapshot/snapshot.ts index 1a724673608a2..ef453be76d8d7 100644 --- a/x-pack/legacy/plugins/infra/server/lib/snapshot/snapshot.ts +++ b/x-pack/legacy/plugins/infra/server/lib/snapshot/snapshot.ts @@ -5,7 +5,6 @@ */ import { RequestHandlerContext } from 'src/core/server'; -import { InfraSnapshotNode } from '../../graphql/types'; import { InfraDatabaseSearchResponse } from '../adapters/framework'; import { KibanaFramework } from '../adapters/framework/kibana_framework_adapter'; import { InfraSources } from '../sources'; @@ -30,6 +29,7 @@ import { createAfterKeyHandler } from '../../utils/create_afterkey_handler'; import { findInventoryModel } from '../../../common/inventory_models'; import { InfraSnapshotRequestOptions } from './types'; import { createTimeRangeWithInterval } from './create_timerange_with_interval'; +import { SnapshotNode } from '../../../common/http_api/snapshot_api'; export class InfraSnapshot { constructor(private readonly libs: { sources: InfraSources; framework: KibanaFramework }) {} @@ -37,7 +37,7 @@ export class InfraSnapshot { public async getNodes( requestContext: RequestHandlerContext, options: InfraSnapshotRequestOptions - ): Promise<{ nodes: InfraSnapshotNode[]; interval: string }> { + ): Promise<{ nodes: SnapshotNode[]; interval: string }> { // Both requestGroupedNodes and requestNodeMetrics may send several requests to elasticsearch // in order to page through the results of their respective composite aggregations. // Both chains of requests are supposed to run in parallel, and their results be merged @@ -186,7 +186,7 @@ const mergeNodeBuckets = ( nodeGroupByBuckets: InfraSnapshotNodeGroupByBucket[], nodeMetricsBuckets: InfraSnapshotNodeMetricsBucket[], options: InfraSnapshotRequestOptions -): InfraSnapshotNode[] => { +): SnapshotNode[] => { const nodeMetricsForLookup = getNodeMetricsForLookup(nodeMetricsBuckets); return nodeGroupByBuckets.map(node => { diff --git a/x-pack/legacy/plugins/infra/server/lib/snapshot/types.ts b/x-pack/legacy/plugins/infra/server/lib/snapshot/types.ts index 31823b2811121..3627747e327e0 100644 --- a/x-pack/legacy/plugins/infra/server/lib/snapshot/types.ts +++ b/x-pack/legacy/plugins/infra/server/lib/snapshot/types.ts @@ -5,21 +5,11 @@ */ import { JsonObject } from '../../../common/typed_json'; -import { - InfraNodeType, - InfraSourceConfiguration, - InfraTimerangeInput, - InfraSnapshotGroupbyInput, - InfraSnapshotMetricInput, -} from '../../../public/graphql/types'; +import { InfraSourceConfiguration } from '../../../public/graphql/types'; +import { SnapshotRequest } from '../../../common/http_api/snapshot_api'; -export interface InfraSnapshotRequestOptions { - nodeType: InfraNodeType; +export interface InfraSnapshotRequestOptions + extends Omit { sourceConfiguration: InfraSourceConfiguration; - timerange: InfraTimerangeInput; - groupBy: InfraSnapshotGroupbyInput[]; - metric: InfraSnapshotMetricInput; filterQuery: JsonObject | undefined; - accountId?: string; - region?: string; } diff --git a/x-pack/legacy/plugins/infra/server/new_platform_plugin.ts b/x-pack/legacy/plugins/infra/server/new_platform_plugin.ts index 147729a1d0b3e..d3c6f7a5f70a1 100644 --- a/x-pack/legacy/plugins/infra/server/new_platform_plugin.ts +++ b/x-pack/legacy/plugins/infra/server/new_platform_plugin.ts @@ -17,7 +17,7 @@ import { InfraElasticsearchSourceStatusAdapter } from './lib/adapters/source_sta import { InfraFieldsDomain } from './lib/domains/fields_domain'; import { InfraLogEntriesDomain } from './lib/domains/log_entries_domain'; import { InfraMetricsDomain } from './lib/domains/metrics_domain'; -import { InfraLogAnalysis } from './lib/log_analysis'; +import { LogEntryCategoriesAnalysis, LogEntryRateAnalysis } from './lib/log_analysis'; import { InfraSnapshot } from './lib/snapshot'; import { InfraSourceStatus } from './lib/source_status'; import { InfraSources } from './lib/sources'; @@ -87,7 +87,8 @@ export class InfraServerPlugin { } ); const snapshot = new InfraSnapshot({ sources, framework }); - const logAnalysis = new InfraLogAnalysis({ framework }); + const logEntryCategoriesAnalysis = new LogEntryCategoriesAnalysis({ framework }); + const logEntryRateAnalysis = new LogEntryRateAnalysis({ framework }); // TODO: separate these out individually and do away with "domains" as a temporary group const domainLibs: InfraDomainLibs = { @@ -103,7 +104,8 @@ export class InfraServerPlugin { this.libs = { configuration: this.config, framework, - logAnalysis, + logEntryCategoriesAnalysis, + logEntryRateAnalysis, snapshot, sources, sourceStatus, diff --git a/x-pack/legacy/plugins/infra/server/routes/log_analysis/results/index.ts b/x-pack/legacy/plugins/infra/server/routes/log_analysis/results/index.ts index 1749421277719..d9ca9a96ffe51 100644 --- a/x-pack/legacy/plugins/infra/server/routes/log_analysis/results/index.ts +++ b/x-pack/legacy/plugins/infra/server/routes/log_analysis/results/index.ts @@ -4,4 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ +export * from './log_entry_categories'; +export * from './log_entry_category_datasets'; export * from './log_entry_rate'; diff --git a/x-pack/legacy/plugins/infra/server/routes/log_analysis/results/log_entry_categories.ts b/x-pack/legacy/plugins/infra/server/routes/log_analysis/results/log_entry_categories.ts new file mode 100644 index 0000000000000..7eb7de57b2f92 --- /dev/null +++ b/x-pack/legacy/plugins/infra/server/routes/log_analysis/results/log_entry_categories.ts @@ -0,0 +1,93 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import Boom from 'boom'; + +import { pipe } from 'fp-ts/lib/pipeable'; +import { fold } from 'fp-ts/lib/Either'; +import { identity } from 'fp-ts/lib/function'; +import { schema } from '@kbn/config-schema'; +import { InfraBackendLibs } from '../../../lib/infra_types'; +import { + LOG_ANALYSIS_GET_LOG_ENTRY_CATEGORIES_PATH, + getLogEntryCategoriesRequestPayloadRT, + getLogEntryCategoriesSuccessReponsePayloadRT, +} from '../../../../common/http_api/log_analysis'; +import { throwErrors } from '../../../../common/runtime_types'; +import { NoLogAnalysisResultsIndexError } from '../../../lib/log_analysis'; + +const anyObject = schema.object({}, { allowUnknowns: true }); + +export const initGetLogEntryCategoriesRoute = ({ + framework, + logEntryCategoriesAnalysis, +}: InfraBackendLibs) => { + framework.registerRoute( + { + method: 'post', + path: LOG_ANALYSIS_GET_LOG_ENTRY_CATEGORIES_PATH, + validate: { + // short-circuit forced @kbn/config-schema validation so we can do io-ts validation + body: anyObject, + }, + }, + async (requestContext, request, response) => { + const { + data: { + categoryCount, + histograms, + sourceId, + timeRange: { startTime, endTime }, + datasets, + }, + } = pipe( + getLogEntryCategoriesRequestPayloadRT.decode(request.body), + fold(throwErrors(Boom.badRequest), identity) + ); + + try { + const { + data: topLogEntryCategories, + timing, + } = await logEntryCategoriesAnalysis.getTopLogEntryCategories( + requestContext, + request, + sourceId, + startTime, + endTime, + categoryCount, + datasets ?? [], + histograms.map(histogram => ({ + bucketCount: histogram.bucketCount, + endTime: histogram.timeRange.endTime, + id: histogram.id, + startTime: histogram.timeRange.startTime, + })) + ); + + return response.ok({ + body: getLogEntryCategoriesSuccessReponsePayloadRT.encode({ + data: { + categories: topLogEntryCategories, + }, + timing, + }), + }); + } catch (e) { + const { statusCode = 500, message = 'Unknown error occurred' } = e; + + if (e instanceof NoLogAnalysisResultsIndexError) { + return response.notFound({ body: { message } }); + } + + return response.customError({ + statusCode, + body: { message }, + }); + } + } + ); +}; diff --git a/x-pack/legacy/plugins/infra/server/routes/log_analysis/results/log_entry_category_datasets.ts b/x-pack/legacy/plugins/infra/server/routes/log_analysis/results/log_entry_category_datasets.ts new file mode 100644 index 0000000000000..8132633028277 --- /dev/null +++ b/x-pack/legacy/plugins/infra/server/routes/log_analysis/results/log_entry_category_datasets.ts @@ -0,0 +1,82 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { schema } from '@kbn/config-schema'; +import Boom from 'boom'; +import { fold } from 'fp-ts/lib/Either'; +import { identity } from 'fp-ts/lib/function'; +import { pipe } from 'fp-ts/lib/pipeable'; + +import { + getLogEntryCategoryDatasetsRequestPayloadRT, + getLogEntryCategoryDatasetsSuccessReponsePayloadRT, + LOG_ANALYSIS_GET_LOG_ENTRY_CATEGORY_DATASETS_PATH, +} from '../../../../common/http_api/log_analysis'; +import { throwErrors } from '../../../../common/runtime_types'; +import { InfraBackendLibs } from '../../../lib/infra_types'; +import { NoLogAnalysisResultsIndexError } from '../../../lib/log_analysis'; + +const anyObject = schema.object({}, { allowUnknowns: true }); + +export const initGetLogEntryCategoryDatasetsRoute = ({ + framework, + logEntryCategoriesAnalysis, +}: InfraBackendLibs) => { + framework.registerRoute( + { + method: 'post', + path: LOG_ANALYSIS_GET_LOG_ENTRY_CATEGORY_DATASETS_PATH, + validate: { + // short-circuit forced @kbn/config-schema validation so we can do io-ts validation + body: anyObject, + }, + }, + async (requestContext, request, response) => { + const { + data: { + sourceId, + timeRange: { startTime, endTime }, + }, + } = pipe( + getLogEntryCategoryDatasetsRequestPayloadRT.decode(request.body), + fold(throwErrors(Boom.badRequest), identity) + ); + + try { + const { + data: logEntryCategoryDatasets, + timing, + } = await logEntryCategoriesAnalysis.getLogEntryCategoryDatasets( + requestContext, + request, + sourceId, + startTime, + endTime + ); + + return response.ok({ + body: getLogEntryCategoryDatasetsSuccessReponsePayloadRT.encode({ + data: { + datasets: logEntryCategoryDatasets, + }, + timing, + }), + }); + } catch (e) { + const { statusCode = 500, message = 'Unknown error occurred' } = e; + + if (e instanceof NoLogAnalysisResultsIndexError) { + return response.notFound({ body: { message } }); + } + + return response.customError({ + statusCode, + body: { message }, + }); + } + } + ); +}; diff --git a/x-pack/legacy/plugins/infra/server/routes/log_analysis/results/log_entry_rate.ts b/x-pack/legacy/plugins/infra/server/routes/log_analysis/results/log_entry_rate.ts index 9778311bd8e58..6551316fd0c64 100644 --- a/x-pack/legacy/plugins/infra/server/routes/log_analysis/results/log_entry_rate.ts +++ b/x-pack/legacy/plugins/infra/server/routes/log_analysis/results/log_entry_rate.ts @@ -18,11 +18,11 @@ import { GetLogEntryRateSuccessResponsePayload, } from '../../../../common/http_api/log_analysis'; import { throwErrors } from '../../../../common/runtime_types'; -import { NoLogRateResultsIndexError } from '../../../lib/log_analysis'; +import { NoLogAnalysisResultsIndexError } from '../../../lib/log_analysis'; const anyObject = schema.object({}, { allowUnknowns: true }); -export const initGetLogEntryRateRoute = ({ framework, logAnalysis }: InfraBackendLibs) => { +export const initGetLogEntryRateRoute = ({ framework, logEntryRateAnalysis }: InfraBackendLibs) => { framework.registerRoute( { method: 'post', @@ -39,13 +39,13 @@ export const initGetLogEntryRateRoute = ({ framework, logAnalysis }: InfraBacken fold(throwErrors(Boom.badRequest), identity) ); - const logEntryRateBuckets = await logAnalysis.getLogEntryRateBuckets( + const logEntryRateBuckets = await logEntryRateAnalysis.getLogEntryRateBuckets( requestContext, + request, payload.data.sourceId, payload.data.timeRange.startTime, payload.data.timeRange.endTime, - payload.data.bucketDuration, - request + payload.data.bucketDuration ); return response.ok({ @@ -59,7 +59,7 @@ export const initGetLogEntryRateRoute = ({ framework, logAnalysis }: InfraBacken }); } catch (e) { const { statusCode = 500, message = 'Unknown error occurred' } = e; - if (e instanceof NoLogRateResultsIndexError) { + if (e instanceof NoLogAnalysisResultsIndexError) { return response.notFound({ body: { message } }); } return response.customError({ diff --git a/x-pack/legacy/plugins/infra/server/routes/log_entries/entries.ts b/x-pack/legacy/plugins/infra/server/routes/log_entries/entries.ts new file mode 100644 index 0000000000000..361535886ab22 --- /dev/null +++ b/x-pack/legacy/plugins/infra/server/routes/log_entries/entries.ts @@ -0,0 +1,85 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import Boom from 'boom'; + +import { pipe } from 'fp-ts/lib/pipeable'; +import { fold } from 'fp-ts/lib/Either'; +import { identity } from 'fp-ts/lib/function'; +import { schema } from '@kbn/config-schema'; + +import { throwErrors } from '../../../common/runtime_types'; + +import { InfraBackendLibs } from '../../lib/infra_types'; +import { + LOG_ENTRIES_PATH, + logEntriesRequestRT, + logEntriesResponseRT, +} from '../../../common/http_api/log_entries'; +import { parseFilterQuery } from '../../utils/serialized_query'; +import { LogEntriesParams } from '../../lib/domains/log_entries_domain'; + +const escapeHatch = schema.object({}, { allowUnknowns: true }); + +export const initLogEntriesRoute = ({ framework, logEntries }: InfraBackendLibs) => { + framework.registerRoute( + { + method: 'post', + path: LOG_ENTRIES_PATH, + validate: { body: escapeHatch }, + }, + async (requestContext, request, response) => { + try { + const payload = pipe( + logEntriesRequestRT.decode(request.body), + fold(throwErrors(Boom.badRequest), identity) + ); + + const { startDate, endDate, sourceId, query, size } = payload; + + let entries; + if ('center' in payload) { + entries = await logEntries.getLogEntriesAround__new(requestContext, sourceId, { + startDate, + endDate, + query: parseFilterQuery(query), + center: payload.center, + size, + }); + } else { + let cursor: LogEntriesParams['cursor']; + if ('before' in payload) { + cursor = { before: payload.before }; + } else if ('after' in payload) { + cursor = { after: payload.after }; + } + + entries = await logEntries.getLogEntries(requestContext, sourceId, { + startDate, + endDate, + query: parseFilterQuery(query), + cursor, + size, + }); + } + + return response.ok({ + body: logEntriesResponseRT.encode({ + data: { + entries, + topCursor: entries[0].cursor, + bottomCursor: entries[entries.length - 1].cursor, + }, + }), + }); + } catch (error) { + return response.internalError({ + body: error.message, + }); + } + } + ); +}; diff --git a/x-pack/legacy/plugins/infra/server/routes/log_entries/highlights.ts b/x-pack/legacy/plugins/infra/server/routes/log_entries/highlights.ts new file mode 100644 index 0000000000000..8af81a6ee313d --- /dev/null +++ b/x-pack/legacy/plugins/infra/server/routes/log_entries/highlights.ts @@ -0,0 +1,96 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import Boom from 'boom'; + +import { pipe } from 'fp-ts/lib/pipeable'; +import { fold } from 'fp-ts/lib/Either'; +import { identity } from 'fp-ts/lib/function'; +import { schema } from '@kbn/config-schema'; + +import { throwErrors } from '../../../common/runtime_types'; + +import { InfraBackendLibs } from '../../lib/infra_types'; +import { + LOG_ENTRIES_HIGHLIGHTS_PATH, + logEntriesHighlightsRequestRT, + logEntriesHighlightsResponseRT, +} from '../../../common/http_api/log_entries'; +import { parseFilterQuery } from '../../utils/serialized_query'; +import { LogEntriesParams } from '../../lib/domains/log_entries_domain'; + +const escapeHatch = schema.object({}, { allowUnknowns: true }); + +export const initLogEntriesHighlightsRoute = ({ framework, logEntries }: InfraBackendLibs) => { + framework.registerRoute( + { + method: 'post', + path: LOG_ENTRIES_HIGHLIGHTS_PATH, + validate: { body: escapeHatch }, + }, + async (requestContext, request, response) => { + try { + const payload = pipe( + logEntriesHighlightsRequestRT.decode(request.body), + fold(throwErrors(Boom.badRequest), identity) + ); + + const { startDate, endDate, sourceId, query, size, highlightTerms } = payload; + + let entriesPerHighlightTerm; + + if ('center' in payload) { + entriesPerHighlightTerm = await Promise.all( + highlightTerms.map(highlightTerm => + logEntries.getLogEntriesAround__new(requestContext, sourceId, { + startDate, + endDate, + query: parseFilterQuery(query), + center: payload.center, + size, + highlightTerm, + }) + ) + ); + } else { + let cursor: LogEntriesParams['cursor']; + if ('before' in payload) { + cursor = { before: payload.before }; + } else if ('after' in payload) { + cursor = { after: payload.after }; + } + + entriesPerHighlightTerm = await Promise.all( + highlightTerms.map(highlightTerm => + logEntries.getLogEntries(requestContext, sourceId, { + startDate, + endDate, + query: parseFilterQuery(query), + cursor, + size, + highlightTerm, + }) + ) + ); + } + + return response.ok({ + body: logEntriesHighlightsResponseRT.encode({ + data: entriesPerHighlightTerm.map(entries => ({ + entries, + topCursor: entries[0].cursor, + bottomCursor: entries[entries.length - 1].cursor, + })), + }), + }); + } catch (error) { + return response.internalError({ + body: error.message, + }); + } + } + ); +}; diff --git a/x-pack/legacy/plugins/infra/server/routes/log_entries/index.ts b/x-pack/legacy/plugins/infra/server/routes/log_entries/index.ts index 8fed914c3dc8c..1090d35d89b85 100644 --- a/x-pack/legacy/plugins/infra/server/routes/log_entries/index.ts +++ b/x-pack/legacy/plugins/infra/server/routes/log_entries/index.ts @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +export * from './entries'; +export * from './highlights'; export * from './item'; export * from './summary'; export * from './summary_highlights'; diff --git a/x-pack/legacy/plugins/infra/server/routes/metadata/lib/get_node_info.ts b/x-pack/legacy/plugins/infra/server/routes/metadata/lib/get_node_info.ts index 4ff0df30abedd..751e494164756 100644 --- a/x-pack/legacy/plugins/infra/server/routes/metadata/lib/get_node_info.ts +++ b/x-pack/legacy/plugins/infra/server/routes/metadata/lib/get_node_info.ts @@ -8,7 +8,6 @@ import { first, set, startsWith } from 'lodash'; import { RequestHandlerContext } from 'src/core/server'; import { KibanaFramework } from '../../../lib/adapters/framework/kibana_framework_adapter'; import { InfraSourceConfiguration } from '../../../lib/sources'; -import { InfraNodeType } from '../../../graphql/types'; import { InfraMetadataInfo } from '../../../../common/http_api/metadata_api'; import { getPodNodeName } from './get_pod_node_name'; import { CLOUD_METRICS_MODULES } from '../../../lib/constants'; @@ -27,7 +26,7 @@ export const getNodeInfo = async ( // can report pod details and we can't rely on the host/cloud information associated // with the kubernetes.pod.uid. We need to first lookup the `kubernetes.node.name` // then use that to lookup the host's node information. - if (nodeType === InfraNodeType.pod) { + if (nodeType === 'pod') { const kubernetesNodeName = await getPodNodeName( framework, requestContext, @@ -41,7 +40,7 @@ export const getNodeInfo = async ( requestContext, sourceConfiguration, kubernetesNodeName, - InfraNodeType.host + 'host' ); } return {}; diff --git a/x-pack/legacy/plugins/infra/server/routes/metrics_explorer/lib/create_metrics_model.ts b/x-pack/legacy/plugins/infra/server/routes/metrics_explorer/lib/create_metrics_model.ts index 9e5fe16d482b2..7e47f50c76088 100644 --- a/x-pack/legacy/plugins/infra/server/routes/metrics_explorer/lib/create_metrics_model.ts +++ b/x-pack/legacy/plugins/infra/server/routes/metrics_explorer/lib/create_metrics_model.ts @@ -6,11 +6,10 @@ import { InfraMetricModelMetricType } from '../../../lib/adapters/metrics'; import { MetricsExplorerRequestBody } from '../types'; -import { InfraMetric } from '../../../graphql/types'; import { TSVBMetricModel } from '../../../../common/inventory_models/types'; export const createMetricModel = (options: MetricsExplorerRequestBody): TSVBMetricModel => { return { - id: InfraMetric.custom, + id: 'custom', requires: [], index_pattern: options.indexPattern, interval: options.timerange.interval, diff --git a/x-pack/legacy/plugins/infra/server/routes/metrics_explorer/lib/get_dataset_for_field.ts b/x-pack/legacy/plugins/infra/server/routes/metrics_explorer/lib/get_dataset_for_field.ts new file mode 100644 index 0000000000000..66f0ca8fc706a --- /dev/null +++ b/x-pack/legacy/plugins/infra/server/routes/metrics_explorer/lib/get_dataset_for_field.ts @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { RequestHandlerContext } from 'kibana/server'; +import { KibanaFramework } from '../../../lib/adapters/framework/kibana_framework_adapter'; + +interface EventDatasetHit { + _source: { + event?: { + dataset?: string; + }; + }; +} + +export const getDatasetForField = async ( + framework: KibanaFramework, + requestContext: RequestHandlerContext, + field: string, + indexPattern: string +) => { + const params = { + allowNoIndices: true, + ignoreUnavailable: true, + terminateAfter: 1, + index: indexPattern, + body: { + query: { exists: { field } }, + size: 1, + _source: ['event.dataset'], + }, + }; + + const response = await framework.callWithRequest( + requestContext, + 'search', + params + ); + if (response.hits.total.value === 0) { + return null; + } + + return response.hits.hits?.[0]._source.event?.dataset; +}; diff --git a/x-pack/legacy/plugins/infra/server/routes/metrics_explorer/lib/populate_series_with_tsvb_data.ts b/x-pack/legacy/plugins/infra/server/routes/metrics_explorer/lib/populate_series_with_tsvb_data.ts index 17fc46b41278a..8ab3fdccbe72b 100644 --- a/x-pack/legacy/plugins/infra/server/routes/metrics_explorer/lib/populate_series_with_tsvb_data.ts +++ b/x-pack/legacy/plugins/infra/server/routes/metrics_explorer/lib/populate_series_with_tsvb_data.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { union } from 'lodash'; +import { union, uniq } from 'lodash'; import { KibanaRequest, RequestHandlerContext } from 'src/core/server'; import { KibanaFramework } from '../../../lib/adapters/framework/kibana_framework_adapter'; import { @@ -16,6 +16,7 @@ import { import { createMetricModel } from './create_metrics_model'; import { JsonObject } from '../../../../common/typed_json'; import { calculateMetricInterval } from '../../../utils/calculate_metric_interval'; +import { getDatasetForField } from './get_dataset_for_field'; export const populateSeriesWithTSVBData = ( request: KibanaRequest, @@ -53,6 +54,12 @@ export const populateSeriesWithTSVBData = ( // Create the TSVB model based on the request options const model = createMetricModel(options); + const modules = await Promise.all( + uniq(options.metrics.filter(m => m.field)).map( + async m => + await getDatasetForField(framework, requestContext, m.field as string, options.indexPattern) + ) + ); const calculatedInterval = await calculateMetricInterval( framework, requestContext, @@ -61,14 +68,7 @@ export const populateSeriesWithTSVBData = ( timestampField: options.timerange.field, timerange: options.timerange, }, - options.metrics - .filter(metric => metric.field) - .map(metric => { - return metric - .field!.split(/\./) - .slice(0, 2) - .join('.'); - }) + modules.filter(m => m) as string[] ); if (calculatedInterval) { diff --git a/x-pack/legacy/plugins/infra/server/routes/node_details/index.ts b/x-pack/legacy/plugins/infra/server/routes/node_details/index.ts index a9419cd27e684..4a09615f0a17c 100644 --- a/x-pack/legacy/plugins/infra/server/routes/node_details/index.ts +++ b/x-pack/legacy/plugins/infra/server/routes/node_details/index.ts @@ -11,7 +11,7 @@ import { identity } from 'fp-ts/lib/function'; import { InfraBackendLibs } from '../../lib/infra_types'; import { UsageCollector } from '../../usage/usage_collector'; import { InfraMetricsRequestOptions } from '../../lib/adapters/metrics'; -import { InfraNodeType, InfraMetric } from '../../graphql/types'; + import { NodeDetailsRequestRT, NodeDetailsMetricDataResponseRT, @@ -46,9 +46,9 @@ export const initNodeDetailsRoute = (libs: InfraBackendLibs) => { nodeId, cloudId, }, - nodeType: nodeType as InfraNodeType, + nodeType, sourceConfiguration: source.configuration, - metrics: metrics as InfraMetric[], + metrics, timerange, }; return response.ok({ diff --git a/x-pack/legacy/plugins/infra/server/routes/snapshot/index.ts b/x-pack/legacy/plugins/infra/server/routes/snapshot/index.ts index ba7f52e9ec1e7..5f28e41d80c25 100644 --- a/x-pack/legacy/plugins/infra/server/routes/snapshot/index.ts +++ b/x-pack/legacy/plugins/infra/server/routes/snapshot/index.ts @@ -11,10 +11,8 @@ import { identity } from 'fp-ts/lib/function'; import { InfraBackendLibs } from '../../lib/infra_types'; import { UsageCollector } from '../../usage/usage_collector'; import { parseFilterQuery } from '../../utils/serialized_query'; -import { InfraNodeType, InfraSnapshotMetricInput } from '../../../public/graphql/types'; import { SnapshotRequestRT, SnapshotNodeResponseRT } from '../../../common/http_api/snapshot_api'; import { throwErrors } from '../../../common/runtime_types'; -import { InfraSnapshotRequestOptions } from '../../lib/snapshot/types'; const escapeHatch = schema.object({}, { allowUnknowns: true }); @@ -46,16 +44,14 @@ export const initSnapshotRoute = (libs: InfraBackendLibs) => { ); const source = await libs.sources.getSourceConfiguration(requestContext, sourceId); UsageCollector.countNode(nodeType); - const options: InfraSnapshotRequestOptions = { + const options = { filterQuery: parseFilterQuery(filterQuery), accountId, region, - // TODO: Use common infra metric and replace graphql type - nodeType: nodeType as InfraNodeType, + nodeType, groupBy, sourceConfiguration: source.configuration, - // TODO: Use common infra metric and replace graphql type - metric: metric as InfraSnapshotMetricInput, + metric, timerange, }; const nodesWithInterval = await libs.snapshot.getNodes(requestContext, options); diff --git a/x-pack/legacy/plugins/infra/server/usage/usage_collector.ts b/x-pack/legacy/plugins/infra/server/usage/usage_collector.ts index 60b9372b135df..26578bfd2b794 100644 --- a/x-pack/legacy/plugins/infra/server/usage/usage_collector.ts +++ b/x-pack/legacy/plugins/infra/server/usage/usage_collector.ts @@ -5,7 +5,6 @@ */ import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; -import { InfraNodeType } from '../graphql/types'; import { InventoryItemType } from '../../common/inventory_models/types'; const KIBANA_REPORTING_TYPE = 'infraops'; @@ -38,10 +37,10 @@ export class UsageCollector { this.maybeInitializeBucket(bucket); switch (nodeType) { - case InfraNodeType.pod: + case 'pod': this.counters[bucket].infraopsKubernetes += 1; break; - case InfraNodeType.container: + case 'container': this.counters[bucket].infraopsDocker += 1; break; default: diff --git a/x-pack/legacy/plugins/infra/server/utils/calculate_metric_interval.ts b/x-pack/legacy/plugins/infra/server/utils/calculate_metric_interval.ts index 586193a3c242d..7cbbdc0f2145b 100644 --- a/x-pack/legacy/plugins/infra/server/utils/calculate_metric_interval.ts +++ b/x-pack/legacy/plugins/infra/server/utils/calculate_metric_interval.ts @@ -5,9 +5,9 @@ */ import { RequestHandlerContext } from 'src/core/server'; -import { InfraNodeType } from '../graphql/types'; import { findInventoryModel } from '../../common/inventory_models'; import { KibanaFramework } from '../lib/adapters/framework/kibana_framework_adapter'; +import { InventoryItemType } from '../../common/inventory_models/types'; interface Options { indexPattern: string; @@ -27,7 +27,7 @@ export const calculateMetricInterval = async ( requestContext: RequestHandlerContext, options: Options, modules?: string[], - nodeType?: InfraNodeType // TODO: check that this type still makes sense + nodeType?: InventoryItemType // TODO: check that this type still makes sense ) => { let from = options.timerange.from; if (nodeType) { diff --git a/x-pack/legacy/plugins/infra/server/utils/elasticsearch_runtime_types.ts b/x-pack/legacy/plugins/infra/server/utils/elasticsearch_runtime_types.ts new file mode 100644 index 0000000000000..a48c65d648b25 --- /dev/null +++ b/x-pack/legacy/plugins/infra/server/utils/elasticsearch_runtime_types.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as rt from 'io-ts'; + +export const commonSearchSuccessResponseFieldsRT = rt.type({ + _shards: rt.type({ + total: rt.number, + successful: rt.number, + skipped: rt.number, + failed: rt.number, + }), + timed_out: rt.boolean, + took: rt.number, +}); diff --git a/x-pack/legacy/plugins/infra/types/eui.d.ts b/x-pack/legacy/plugins/infra/types/eui.d.ts index afcb445a66adb..e73a73076923d 100644 --- a/x-pack/legacy/plugins/infra/types/eui.d.ts +++ b/x-pack/legacy/plugins/infra/types/eui.d.ts @@ -34,7 +34,7 @@ declare module '@elastic/eui' { items: Array<{ id: string; name: string; - onClick: () => void; + onClick?: () => void; }>; }>; mobileTitle?: React.ReactNode; diff --git a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/field.js b/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/field.js index 82e8c602a7a3e..3e5c92dfc007f 100644 --- a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/field.js +++ b/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/field.js @@ -6,7 +6,7 @@ import React from 'react'; import { flatten } from 'lodash'; import { escapeKuery } from './escape_kuery'; -import { sortPrefixFirst } from 'ui/utils/sort_prefix_first'; +import { sortPrefixFirst } from './sort_prefix_first'; import { isFilterable } from '../../../../../../src/plugins/data/public'; import { FormattedMessage } from '@kbn/i18n/react'; diff --git a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/sort_prefix_first.test.ts b/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/sort_prefix_first.test.ts new file mode 100644 index 0000000000000..7db05a60377da --- /dev/null +++ b/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/sort_prefix_first.test.ts @@ -0,0 +1,79 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { sortPrefixFirst } from './sort_prefix_first'; + +describe('sortPrefixFirst', () => { + test('should return the original unmodified array if no prefix is provided', () => { + const array = ['foo', 'bar', 'baz']; + const result = sortPrefixFirst(array); + + expect(result).toBe(array); + expect(result).toEqual(['foo', 'bar', 'baz']); + }); + + test('should sort items that match the prefix first without modifying the original array', () => { + const array = ['foo', 'bar', 'baz']; + const result = sortPrefixFirst(array, 'b'); + + expect(result).not.toBe(array); + expect(result).toEqual(['bar', 'baz', 'foo']); + expect(array).toEqual(['foo', 'bar', 'baz']); + }); + + test('should not modify the order of the array other than matching prefix without modifying the original array', () => { + const array = ['foo', 'bar', 'baz', 'qux', 'quux']; + const result = sortPrefixFirst(array, 'b'); + + expect(result).not.toBe(array); + expect(result).toEqual(['bar', 'baz', 'foo', 'qux', 'quux']); + expect(array).toEqual(['foo', 'bar', 'baz', 'qux', 'quux']); + }); + + test('should sort objects by property if provided', () => { + const array = [ + { name: 'foo' }, + { name: 'bar' }, + { name: 'baz' }, + { name: 'qux' }, + { name: 'quux' }, + ]; + const result = sortPrefixFirst(array, 'b', 'name'); + + expect(result).not.toBe(array); + expect(result).toEqual([ + { name: 'bar' }, + { name: 'baz' }, + { name: 'foo' }, + { name: 'qux' }, + { name: 'quux' }, + ]); + expect(array).toEqual([ + { name: 'foo' }, + { name: 'bar' }, + { name: 'baz' }, + { name: 'qux' }, + { name: 'quux' }, + ]); + }); + + test('should handle numbers', () => { + const array = [1, 50, 5]; + const result = sortPrefixFirst(array, 5); + + expect(result).not.toBe(array); + expect(result).toEqual([50, 5, 1]); + }); + + test('should handle mixed case', () => { + const array = ['Date Histogram', 'Histogram']; + const prefix = 'histo'; + const result = sortPrefixFirst(array, prefix); + + expect(result).not.toBe(array); + expect(result).toEqual(['Histogram', 'Date Histogram']); + }); +}); diff --git a/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/sort_prefix_first.ts b/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/sort_prefix_first.ts new file mode 100644 index 0000000000000..123e440b75231 --- /dev/null +++ b/x-pack/legacy/plugins/kuery_autocomplete/public/autocomplete_providers/sort_prefix_first.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { partition } from 'lodash'; + +export function sortPrefixFirst(array: any[], prefix?: string | number, property?: string): any[] { + if (!prefix) { + return array; + } + const lowerCasePrefix = ('' + prefix).toLowerCase(); + + const partitions = partition(array, entry => { + const value = ('' + (property ? entry[property] : entry)).toLowerCase(); + return value.startsWith(lowerCasePrefix); + }); + return [...partitions[0], ...partitions[1]]; +} diff --git a/x-pack/legacy/plugins/lens/index.ts b/x-pack/legacy/plugins/lens/index.ts index c4a684381b17c..a4eb24d4a4de4 100644 --- a/x-pack/legacy/plugins/lens/index.ts +++ b/x-pack/legacy/plugins/lens/index.ts @@ -11,6 +11,7 @@ import KbnServer, { Server } from 'src/legacy/server/kbn_server'; import mappings from './mappings.json'; import { PLUGIN_ID, getEditPath, NOT_INTERNATIONALIZED_PRODUCT_NAME } from './common'; import { lensServerPlugin } from './server'; +import { getTaskManagerSetup, getTaskManagerStart } from '../task_manager/server'; export const lens: LegacyPluginInitializer = kibana => { return new kibana.Plugin({ @@ -64,6 +65,12 @@ export const lens: LegacyPluginInitializer = kibana => { savedObjects: server.savedObjects, config: server.config(), server, + taskManager: getTaskManagerSetup(server)!, + }); + + plugin.start(kbnServer.newPlatform.start.core, { + server, + taskManager: getTaskManagerStart(server)!, }); server.events.on('stop', () => { diff --git a/x-pack/legacy/plugins/lens/public/app_plugin/app.test.tsx b/x-pack/legacy/plugins/lens/public/app_plugin/app.test.tsx index 1cdae05833b98..794128832461b 100644 --- a/x-pack/legacy/plugins/lens/public/app_plugin/app.test.tsx +++ b/x-pack/legacy/plugins/lens/public/app_plugin/app.test.tsx @@ -80,6 +80,7 @@ describe('Lens App', () => { docId?: string; docStorage: SavedObjectStore; redirectTo: (id?: string) => void; + addToDashboardMode?: boolean; }> { return ({ editorFrame: createMockFrame(), @@ -126,6 +127,7 @@ describe('Lens App', () => { docId?: string; docStorage: SavedObjectStore; redirectTo: (id?: string) => void; + addToDashboardMode?: boolean; }>; } @@ -306,6 +308,7 @@ describe('Lens App', () => { docId?: string; docStorage: SavedObjectStore; redirectTo: (id?: string) => void; + addToDashboardMode?: boolean; }>; beforeEach(() => { @@ -344,14 +347,19 @@ describe('Lens App', () => { async function save({ initialDocId, + addToDashboardMode, ...saveProps }: SaveProps & { initialDocId?: string; + addToDashboardMode?: boolean; }) { const args = { ...defaultArgs, docId: initialDocId, }; + if (addToDashboardMode) { + args.addToDashboardMode = addToDashboardMode; + } args.editorFrame = frame; (args.docStorage.load as jest.Mock).mockResolvedValue({ id: '1234', @@ -543,6 +551,23 @@ describe('Lens App', () => { expect(getButton(instance).disableButton).toEqual(false); }); + + it('saves new doc and redirects to dashboard', async () => { + const { args } = await save({ + initialDocId: undefined, + addToDashboardMode: true, + newCopyOnSave: false, + newTitle: 'hello there', + }); + + expect(args.docStorage.save).toHaveBeenCalledWith({ + expression: 'kibana 3', + id: undefined, + title: 'hello there', + }); + + expect(args.redirectTo).toHaveBeenCalledWith('aaa'); + }); }); }); diff --git a/x-pack/legacy/plugins/lens/public/app_plugin/app.tsx b/x-pack/legacy/plugins/lens/public/app_plugin/app.tsx index 443bcf99a4f09..f33cd41f46a11 100644 --- a/x-pack/legacy/plugins/lens/public/app_plugin/app.tsx +++ b/x-pack/legacy/plugins/lens/public/app_plugin/app.tsx @@ -13,6 +13,7 @@ import { SavedObjectSaveModal } from 'ui/saved_objects/components/saved_object_s import { AppMountContext, NotificationsStart } from 'src/core/public'; import { IStorageWrapper } from 'src/plugins/kibana_utils/public'; import { npStart } from 'ui/new_platform'; +import { FormattedMessage } from '@kbn/i18n/react'; import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public'; import { Document, SavedObjectStore } from '../persistence'; import { EditorFrameInstance } from '../types'; @@ -50,6 +51,7 @@ export function App({ docId, docStorage, redirectTo, + addToDashboardMode, }: { editorFrame: EditorFrameInstance; data: DataPublicPluginStart; @@ -58,6 +60,7 @@ export function App({ docId?: string; docStorage: SavedObjectStore; redirectTo: (id?: string) => void; + addToDashboardMode?: boolean; }) { const language = storage.get('kibana.userQueryLanguage') || core.uiSettings.get('search:queryLanguage'); @@ -152,6 +155,7 @@ export function App({ const isSaveable = lastKnownDoc && + lastKnownDoc.expression && lastKnownDoc.expression.length > 0 && core.application.capabilities.visualize.save; @@ -165,6 +169,13 @@ export function App({ const { TopNavMenu } = npStart.plugins.navigation.ui; + const confirmButton = addToDashboardMode ? ( + + ) : null; + return ( { + .catch(e => { + // eslint-disable-next-line no-console + console.dir(e); trackUiEvent('save_failed'); core.notifications.toasts.addDanger( i18n.translate('xpack.lens.app.docSavingError', { @@ -336,10 +348,11 @@ export function App({ }} onClose={() => setState(s => ({ ...s, isSaveModalVisible: false }))} title={lastKnownDoc.title || ''} - showCopyOnSave={true} + showCopyOnSave={!addToDashboardMode} objectType={i18n.translate('xpack.lens.app.saveModalType', { defaultMessage: 'Lens visualization', })} + confirmButtonLabel={confirmButton} /> )} diff --git a/x-pack/legacy/plugins/lens/public/app_plugin/plugin.tsx b/x-pack/legacy/plugins/lens/public/app_plugin/plugin.tsx index b1eac8e287bd8..7465de2dba7f1 100644 --- a/x-pack/legacy/plugins/lens/public/app_plugin/plugin.tsx +++ b/x-pack/legacy/plugins/lens/public/app_plugin/plugin.tsx @@ -14,11 +14,13 @@ import 'uiExports/visResponseHandlers'; import 'uiExports/savedObjectTypes'; import React from 'react'; -import { I18nProvider, FormattedMessage } from '@kbn/i18n/react'; -import { HashRouter, Switch, Route, RouteComponentProps } from 'react-router-dom'; +import { FormattedMessage, I18nProvider } from '@kbn/i18n/react'; +import { HashRouter, Route, RouteComponentProps, Switch } from 'react-router-dom'; import { render, unmountComponentAtNode } from 'react-dom'; import { CoreSetup, CoreStart, SavedObjectsClientContract } from 'src/core/public'; import { DataPublicPluginStart } from 'src/plugins/data/public'; +import rison, { RisonObject, RisonValue } from 'rison-node'; +import { isObject } from 'lodash'; import { DataStart } from '../../../../../../src/legacy/core_plugins/data/public'; import { Storage } from '../../../../../../src/plugins/kibana_utils/public'; import { editorFrameSetup, editorFrameStart, editorFrameStop } from '../editor_frame_plugin'; @@ -41,6 +43,11 @@ import { import { NOT_INTERNATIONALIZED_PRODUCT_NAME } from '../../common'; import { KibanaLegacySetup } from '../../../../../../src/plugins/kibana_legacy/public'; import { EditorFrameStart } from '../types'; +import { + addEmbeddableToDashboardUrl, + getUrlVars, + getLensUrlFromDashboardAbsoluteUrl, +} from '../../../../../../src/legacy/core_plugins/kibana/public/dashboard/np_ready/url_helper'; export interface LensPluginSetupDependencies { kibana_legacy: KibanaLegacySetup; @@ -51,6 +58,9 @@ export interface LensPluginStartDependencies { dataShim: DataStart; } +export const isRisonObject = (value: RisonValue): value is RisonObject => { + return isObject(value); +}; export class AppPlugin { private startDependencies: { data: DataPublicPluginStart; @@ -84,7 +94,6 @@ export class AppPlugin { } const { data, savedObjectsClient, editorFrame } = this.startDependencies; addHelpMenuToAppChrome(context.core.chrome); - const instance = editorFrame.createInstance({}); setReportManager( @@ -93,9 +102,60 @@ export class AppPlugin { http: core.http, }) ); + const updateUrlTime = (urlVars: Record): void => { + const decoded: RisonObject = rison.decode(urlVars._g) as RisonObject; + if (!decoded) { + return; + } + // @ts-ignore + decoded.time = data.query.timefilter.timefilter.getTime(); + urlVars._g = rison.encode((decoded as unknown) as RisonObject); + }; + const redirectTo = ( + routeProps: RouteComponentProps<{ id?: string }>, + addToDashboardMode: boolean, + id?: string + ) => { + if (!id) { + routeProps.history.push('/lens'); + } else if (!addToDashboardMode) { + routeProps.history.push(`/lens/edit/${id}`); + } else if (addToDashboardMode && id) { + routeProps.history.push(`/lens/edit/${id}`); + const url = context.core.chrome.navLinks.get('kibana:dashboard'); + if (!url) { + throw new Error('Cannot get last dashboard url'); + } + const lastDashboardAbsoluteUrl = url.url; + const basePath = context.core.http.basePath.get(); + const lensUrl = getLensUrlFromDashboardAbsoluteUrl( + lastDashboardAbsoluteUrl, + basePath, + id + ); + if (!lastDashboardAbsoluteUrl || !lensUrl) { + throw new Error('Cannot get last dashboard url'); + } + window.history.pushState({}, '', lensUrl); + const urlVars = getUrlVars(lastDashboardAbsoluteUrl); + updateUrlTime(urlVars); // we need to pass in timerange in query params directly + const dashboardParsedUrl = addEmbeddableToDashboardUrl( + lastDashboardAbsoluteUrl, + basePath, + id, + urlVars + ); + if (!dashboardParsedUrl) { + throw new Error('Problem parsing dashboard url'); + } + window.history.pushState({}, '', dashboardParsedUrl); + } + }; const renderEditor = (routeProps: RouteComponentProps<{ id?: string }>) => { trackUiEvent('loaded'); + const addToDashboardMode = + !!routeProps.location.search && routeProps.location.search.includes('addToDashboard'); return ( { - if (!id) { - routeProps.history.push('/lens'); - } else { - routeProps.history.push(`/lens/edit/${id}`); - } - }} + redirectTo={id => redirectTo(routeProps, addToDashboardMode, id)} + addToDashboardMode={addToDashboardMode} /> ); }; @@ -119,6 +174,7 @@ export class AppPlugin { trackUiEvent('loaded_404'); return ; } + render( diff --git a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/embeddable/expression_wrapper.tsx b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/embeddable/expression_wrapper.tsx index 3dd4373347129..9b85e89cd80eb 100644 --- a/x-pack/legacy/plugins/lens/public/editor_frame_plugin/embeddable/expression_wrapper.tsx +++ b/x-pack/legacy/plugins/lens/public/editor_frame_plugin/embeddable/expression_wrapper.tsx @@ -13,7 +13,7 @@ import { ExpressionRenderer } from 'src/plugins/expressions/public'; export interface ExpressionWrapperProps { ExpressionRenderer: ExpressionRenderer; - expression: string; + expression: string | null; context: { timeRange?: TimeRange; query?: Query; @@ -29,7 +29,7 @@ export function ExpressionWrapper({ }: ExpressionWrapperProps) { return ( - {expression === '' ? ( + {expression === null || expression === '' ? ( diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/dimension_panel.test.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/dimension_panel.test.tsx index 82e172b6bd7e2..626ef99ac13aa 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/dimension_panel.test.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/dimension_panel.test.tsx @@ -610,7 +610,7 @@ describe('IndexPatternDimensionPanel', () => { }); }); - it('should indicate document compatibility with selected field operation', () => { + it('should select the Records field when count is selected', () => { const initialState: IndexPatternPrivateState = { ...state, layers: { @@ -639,12 +639,9 @@ describe('IndexPatternDimensionPanel', () => { .find('button[data-test-subj="lns-indexPatternDimensionIncompatible-count"]') .simulate('click'); - const options = wrapper.find(EuiComboBox).prop('options'); - - expect(options![0]['data-test-subj']).not.toContain('Incompatible'); - options![1].options!.map(option => - expect(option['data-test-subj']).toContain('Incompatible') - ); + const newColumnState = setState.mock.calls[0][0].layers.first.columns.col2; + expect(newColumnState.operationType).toEqual('count'); + expect(newColumnState.sourceField).toEqual('Records'); }); it('should indicate document and field compatibility with selected document operation', () => { diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/popover_editor.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/popover_editor.tsx index c44d63b01c1b3..98773c04db4a6 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/popover_editor.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/dimension_panel/popover_editor.tsx @@ -127,7 +127,7 @@ export function PopoverEditor(props: PopoverEditorProps) { compatibleWithCurrentField ? '' : 'Incompatible' }-${operationType}`, onClick() { - if (!selectedColumn) { + if (!selectedColumn || !compatibleWithCurrentField) { const possibleFields = fieldByOperation[operationType] || []; if (possibleFields.length === 1) { @@ -152,11 +152,6 @@ export function PopoverEditor(props: PopoverEditorProps) { trackUiEvent(`indexpattern_dimension_operation_${operationType}`); return; } - if (!compatibleWithCurrentField) { - setInvalidOperationType(operationType); - trackUiEvent(`indexpattern_dimension_operation_${operationType}`); - return; - } if (incompatibleSelectedOperationType) { setInvalidOperationType(null); } diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/field_item.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/field_item.tsx index 20505107be122..4a58f0354882a 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/field_item.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/field_item.tsx @@ -26,9 +26,6 @@ import { Axis, BarSeries, Chart, - DataSeriesColorsValues, - getAxisId, - getSpecId, niceTimeFormatter, Position, ScaleType, @@ -391,18 +388,11 @@ function FieldItemPopoverContents(props: State & FieldItemProps) { } if (histogram && histogram.buckets.length) { - const specId = getSpecId( - i18n.translate('xpack.lens.indexPattern.fieldStatsCountLabel', { - defaultMessage: 'Count', - }) - ); - const colors: DataSeriesColorsValues = { - colorValues: [], - specId, - }; + const specId = i18n.translate('xpack.lens.indexPattern.fieldStatsCountLabel', { + defaultMessage: 'Count', + }); const expectedColor = getColorForDataType(field.type); - - const seriesColors = new Map([[colors, expectedColor]]); + const seriesColors = expectedColor ? [expectedColor] : undefined; if (field.type === 'date') { return wrapInPopover( @@ -422,7 +412,7 @@ function FieldItemPopoverContents(props: State & FieldItemProps) { /> formatter.convert(d)} diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.tsx b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.tsx index 15f19bb9d97e6..b58a2d8ca52c7 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.tsx +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/indexpattern.tsx @@ -115,8 +115,12 @@ export function getIndexPatternDatasource({ const indexPatternDatasource: Datasource = { id: 'indexpattern', - initialize(state?: IndexPatternPersistedState) { - return loadInitialState({ state, savedObjectsClient }); + async initialize(state?: IndexPatternPersistedState) { + return loadInitialState({ + state, + savedObjectsClient, + defaultIndexPatternId: core.uiSettings.get('defaultIndex'), + }); }, getPersistableState({ currentIndexPatternId, layers }: IndexPatternPrivateState) { diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/loader.test.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/loader.test.ts index 2fb678aed5a54..e180ab690d418 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/loader.test.ts +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/loader.test.ts @@ -114,8 +114,9 @@ const sampleIndexPatterns = { { name: 'source', type: 'string', - aggregatable: true, - searchable: true, + aggregatable: false, + searchable: false, + scripted: true, aggregationRestrictions: { terms: { agg: 'terms', @@ -196,7 +197,7 @@ describe('loader', () => { expect(cache).toMatchObject(sampleIndexPatterns); }); - it('should not allow full text fields', async () => { + it('should allow scripted, but not full text fields', async () => { const cache = await loadIndexPatterns({ cache: {}, patterns: ['a', 'b'], @@ -286,6 +287,26 @@ describe('loader', () => { }); }); + it('should use the default index pattern id, if provided', async () => { + const state = await loadInitialState({ + defaultIndexPatternId: 'b', + savedObjectsClient: mockClient(), + }); + + expect(state).toMatchObject({ + currentIndexPatternId: 'b', + indexPatternRefs: [ + { id: 'a', title: sampleIndexPatterns.a.title }, + { id: 'b', title: sampleIndexPatterns.b.title }, + ], + indexPatterns: { + b: sampleIndexPatterns.b, + }, + layers: {}, + showEmptyFields: false, + }); + }); + it('should initialize from saved state', async () => { const savedState: IndexPatternPersistedState = { currentIndexPatternId: 'b', diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/loader.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/loader.ts index 661c627f3454f..7f46f50786cf4 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/loader.ts +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/loader.ts @@ -84,9 +84,11 @@ export async function loadIndexPatterns({ export async function loadInitialState({ state, savedObjectsClient, + defaultIndexPatternId, }: { state?: IndexPatternPersistedState; savedObjectsClient: SavedObjectsClient; + defaultIndexPatternId?: string; }): Promise { const indexPatternRefs = await loadIndexPatternRefs(savedObjectsClient); const requiredPatterns = _.unique( @@ -94,7 +96,7 @@ export async function loadInitialState({ ? Object.values(state.layers) .map(l => l.indexPatternId) .concat(state.currentIndexPatternId) - : [indexPatternRefs[0].id] + : [defaultIndexPatternId || indexPatternRefs[0].id] ); const currentIndexPatternId = requiredPatterns[0]; @@ -280,7 +282,7 @@ function fromSavedObject( type, title: attributes.title, fields: (JSON.parse(attributes.fields) as IndexPatternField[]) - .filter(({ aggregatable }) => !!aggregatable) + .filter(({ aggregatable, scripted }) => !!aggregatable || !!scripted) .concat(documentField), typeMeta: attributes.typeMeta ? (JSON.parse(attributes.typeMeta) as SavedRestrictionsInfo) diff --git a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/types.ts b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/types.ts index 9ed5083633314..50478515d19ce 100644 --- a/x-pack/legacy/plugins/lens/public/indexpattern_plugin/types.ts +++ b/x-pack/legacy/plugins/lens/public/indexpattern_plugin/types.ts @@ -39,6 +39,7 @@ export interface IndexPatternField { type: string; esTypes?: string[]; aggregatable: boolean; + scripted?: boolean; searchable: boolean; aggregationRestrictions?: AggregationRestrictions; } diff --git a/x-pack/legacy/plugins/lens/public/persistence/saved_object_store.ts b/x-pack/legacy/plugins/lens/public/persistence/saved_object_store.ts index 4337482663a9c..1d36e18c726ec 100644 --- a/x-pack/legacy/plugins/lens/public/persistence/saved_object_store.ts +++ b/x-pack/legacy/plugins/lens/public/persistence/saved_object_store.ts @@ -13,7 +13,7 @@ export interface Document { type?: string; visualizationType: string | null; title: string; - expression: string; + expression: string | null; state: { datasourceMetaData: { filterableIndexPatterns: Array<{ id: string; title: string }>; diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/__snapshots__/xy_expression.test.tsx.snap b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/__snapshots__/xy_expression.test.tsx.snap index 5089a3b8a3a22..8df2d764c0208 100644 --- a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/__snapshots__/xy_expression.test.tsx.snap +++ b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/__snapshots__/xy_expression.test.tsx.snap @@ -4,7 +4,7 @@ exports[`xy_expression XYChart component it renders area 1`] = ` - - - - - - - - - - - - - - - - - - - - - - - - - - - - { .find(Axis) .first() .prop('tickFormat'); + + if (!tickFormatter) { + throw new Error('tickFormatter prop not found'); + } + tickFormatter('I'); expect(convertSpy).toHaveBeenCalledWith('I'); diff --git a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.tsx b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.tsx index 3c113eaca7ed1..32c1ace5b1770 100644 --- a/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.tsx +++ b/x-pack/legacy/plugins/lens/public/xy_visualization_plugin/xy_expression.tsx @@ -12,8 +12,6 @@ import { Settings, Axis, LineSeries, - getAxisId, - getSpecId, AreaSeries, BarSeries, Position, @@ -205,7 +203,7 @@ export function XYChart({ data, args, formatFactory, timeZone }: XYChartRenderPr /> (); + export class LensServer implements Plugin<{}, {}, {}, {}> { setup(core: CoreSetup, plugins: PluginSetupContract) { setupRoutes(core, plugins); - registerLensUsageCollector(plugins.usageCollection, plugins.server); - initializeLensTelemetry(core, plugins.server); - + registerLensUsageCollector( + plugins.usageCollection, + taskManagerStartContract$.pipe(first()).toPromise() + ); + initializeLensTelemetry(plugins.server, plugins.taskManager); return {}; } - start() { + start(core: CoreStart, plugins: PluginStartContract) { + scheduleLensTelemetry(plugins.server, plugins.taskManager); + taskManagerStartContract$.next(plugins.taskManager); + taskManagerStartContract$.complete(); return {}; } diff --git a/x-pack/legacy/plugins/lens/server/usage/collectors.ts b/x-pack/legacy/plugins/lens/server/usage/collectors.ts index 274b72c33e59a..666b3718d5125 100644 --- a/x-pack/legacy/plugins/lens/server/usage/collectors.ts +++ b/x-pack/legacy/plugins/lens/server/usage/collectors.ts @@ -6,32 +6,25 @@ import moment from 'moment'; import { get } from 'lodash'; -import { Server } from 'src/legacy/server/kbn_server'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; +import { TaskManagerStartContract } from '../../../../../plugins/task_manager/server'; import { LensUsage, LensTelemetryState } from './types'; -export function registerLensUsageCollector(usageCollection: UsageCollectionSetup, server: Server) { +export function registerLensUsageCollector( + usageCollection: UsageCollectionSetup, + taskManager: Promise +) { let isCollectorReady = false; - async function determineIfTaskManagerIsReady() { - let isReady = false; - try { - isReady = await isTaskManagerReady(server); - } catch (err) {} // eslint-disable-line - - if (isReady) { - isCollectorReady = true; - } else { - setTimeout(determineIfTaskManagerIsReady, 500); - } - } - determineIfTaskManagerIsReady(); - + taskManager.then(() => { + // mark lensUsageCollector as ready to collect when the TaskManager is ready + isCollectorReady = true; + }); const lensUsageCollector = usageCollection.makeUsageCollector({ type: 'lens', fetch: async (): Promise => { try { - const docs = await getLatestTaskState(server); + const docs = await getLatestTaskState(await taskManager); // get the accumulated state from the recurring task const state: LensTelemetryState = get(docs, '[0].state'); @@ -73,17 +66,7 @@ function addEvents(prevEvents: Record, newEvents: Record Promise; -export function initializeLensTelemetry(core: CoreSetup, server: Server) { - registerLensTelemetryTask(core, server); - scheduleTasks(server); -} - -function registerLensTelemetryTask(core: CoreSetup, server: Server) { - const taskManager = server.plugins.task_manager; - +export function initializeLensTelemetry(server: Server, taskManager?: TaskManagerSetupContract) { if (!taskManager) { server.log(['debug', 'telemetry'], `Task manager is not available`); - return; + } else { + registerLensTelemetryTask(server, taskManager); } +} +export function scheduleLensTelemetry(server: Server, taskManager?: TaskManagerStartContract) { + if (taskManager) { + scheduleTasks(server, taskManager); + } +} + +function registerLensTelemetryTask(server: Server, taskManager: TaskManagerSetupContract) { taskManager.registerTaskDefinitions({ [TELEMETRY_TASK_TYPE]: { title: 'Lens telemetry fetch task', @@ -62,17 +68,11 @@ function registerLensTelemetryTask(core: CoreSetup, server: Server) { }); } -function scheduleTasks(server: Server) { - const taskManager = server.plugins.task_manager; +function scheduleTasks(server: Server, taskManager: TaskManagerStartContract) { const { kbnServer } = (server.plugins.xpack_main as XPackMainPlugin & { status: { plugin: { kbnServer: KbnServer } }; }).status.plugin; - if (!taskManager) { - server.log(['debug', 'telemetry'], `Task manager is not available`); - return; - } - kbnServer.afterPluginsInit(() => { // The code block below can't await directly within "afterPluginsInit" // callback due to circular dependency The server isn't "ready" until diff --git a/x-pack/legacy/plugins/license_management/public/register_route.ts b/x-pack/legacy/plugins/license_management/public/register_route.ts index 994a888ac020a..fc1678a866ad3 100644 --- a/x-pack/legacy/plugins/license_management/public/register_route.ts +++ b/x-pack/legacy/plugins/license_management/public/register_route.ts @@ -52,7 +52,7 @@ if (licenseManagementUiEnabled) { }; const initializeTelemetry = ($injector: any) => { - const telemetryEnabled = $injector.get('telemetryEnabled'); + const telemetryEnabled = npStart.core.injectedMetadata.getInjectedVar('telemetryEnabled'); const Private = $injector.get('Private'); const telemetryOptInProvider = Private(TelemetryOptInProvider); setTelemetryOptInService(telemetryOptInProvider); @@ -87,6 +87,7 @@ if (licenseManagementUiEnabled) { const unmountApp = await app.mount({ ...npStart } as any, { element, appBasePath: '', + onAppLeave: () => undefined, }); manageAngularLifecycle($scope, $route, unmountApp as any); }, diff --git a/x-pack/legacy/plugins/logstash/public/sections/pipeline_edit/components/pipeline_edit/pipeline_edit.js b/x-pack/legacy/plugins/logstash/public/sections/pipeline_edit/components/pipeline_edit/pipeline_edit.js index aa7f88a62397c..83446278fdeca 100755 --- a/x-pack/legacy/plugins/logstash/public/sections/pipeline_edit/components/pipeline_edit/pipeline_edit.js +++ b/x-pack/legacy/plugins/logstash/public/sections/pipeline_edit/components/pipeline_edit/pipeline_edit.js @@ -8,6 +8,7 @@ import React from 'react'; import { render } from 'react-dom'; import { isEmpty } from 'lodash'; import { uiModules } from 'ui/modules'; +import { npSetup } from 'ui/new_platform'; import { toastNotifications } from 'ui/notify'; import { I18nContext } from 'ui/i18n'; import { PipelineEditor } from '../../../../components/pipeline_editor'; @@ -21,7 +22,6 @@ app.directive('pipelineEdit', function($injector) { const pipelineService = $injector.get('pipelineService'); const licenseService = $injector.get('logstashLicenseService'); const kbnUrl = $injector.get('kbnUrl'); - const shieldUser = $injector.get('ShieldUser'); const $route = $injector.get('$route'); return { @@ -32,7 +32,7 @@ app.directive('pipelineEdit', function($injector) { scope.$evalAsync(kbnUrl.change(`/management/logstash/pipelines/${id}/edit`)); const userResource = logstashSecurity.isSecurityEnabled() - ? await shieldUser.getCurrent().$promise + ? await npSetup.plugins.security.authc.getCurrentUser() : null; render( diff --git a/x-pack/legacy/plugins/maps/common/constants.js b/x-pack/legacy/plugins/maps/common/constants.js index b97845a458d51..6e7776d43f4d4 100644 --- a/x-pack/legacy/plugins/maps/common/constants.js +++ b/x-pack/legacy/plugins/maps/common/constants.js @@ -23,6 +23,7 @@ export const EMS_TILES_VECTOR_TILE_PATH = 'ems/tiles/vector/tile'; export const MAP_SAVED_OBJECT_TYPE = 'map'; export const APP_ID = 'maps'; export const APP_ICON = 'gisApp'; +export const TELEMETRY_TYPE = 'maps-telemetry'; export const MAP_APP_PATH = `app/${APP_ID}`; export const GIS_API_PATH = `api/${APP_ID}`; diff --git a/x-pack/legacy/plugins/maps/public/actions/map_actions.js b/x-pack/legacy/plugins/maps/public/actions/map_actions.js index 053b389a59011..f05b7eba9e7e0 100644 --- a/x-pack/legacy/plugins/maps/public/actions/map_actions.js +++ b/x-pack/legacy/plugins/maps/public/actions/map_actions.js @@ -39,7 +39,7 @@ export const SET_LAYER_ERROR_STATUS = 'SET_LAYER_ERROR_STATUS'; export const ADD_WAITING_FOR_MAP_READY_LAYER = 'ADD_WAITING_FOR_MAP_READY_LAYER'; export const CLEAR_WAITING_FOR_MAP_READY_LAYER_LIST = 'CLEAR_WAITING_FOR_MAP_READY_LAYER_LIST'; export const REMOVE_LAYER = 'REMOVE_LAYER'; -export const TOGGLE_LAYER_VISIBLE = 'TOGGLE_LAYER_VISIBLE'; +export const SET_LAYER_VISIBILITY = 'SET_LAYER_VISIBILITY'; export const MAP_EXTENT_CHANGED = 'MAP_EXTENT_CHANGED'; export const MAP_READY = 'MAP_READY'; export const MAP_DESTROYED = 'MAP_DESTROYED'; @@ -72,6 +72,7 @@ export const DISABLE_TOOLTIP_CONTROL = 'DISABLE_TOOLTIP_CONTROL'; export const HIDE_TOOLBAR_OVERLAY = 'HIDE_TOOLBAR_OVERLAY'; export const HIDE_LAYER_CONTROL = 'HIDE_LAYER_CONTROL'; export const HIDE_VIEW_CONTROL = 'HIDE_VIEW_CONTROL'; +export const SET_WAITING_FOR_READY_HIDDEN_LAYERS = 'SET_WAITING_FOR_READY_HIDDEN_LAYERS'; function getLayerLoadingCallbacks(dispatch, layerId) { return { @@ -252,23 +253,25 @@ export function cleanTooltipStateForLayer(layerId, layerFeatures = []) { }; } -export function toggleLayerVisible(layerId) { +export function setLayerVisibility(layerId, makeVisible) { return async (dispatch, getState) => { //if the current-state is invisible, we also want to sync data //e.g. if a layer was invisible at start-up, it won't have any data loaded const layer = getLayerById(layerId, getState()); - if (!layer) { + + // If the layer visibility is already what we want it to be, do nothing + if (!layer || layer.isVisible() === makeVisible) { return; } - const makeVisible = !layer.isVisible(); if (!makeVisible) { dispatch(cleanTooltipStateForLayer(layerId)); } await dispatch({ - type: TOGGLE_LAYER_VISIBLE, + type: SET_LAYER_VISIBILITY, layerId, + visibility: makeVisible, }); if (makeVisible) { dispatch(syncDataForLayer(layerId)); @@ -276,6 +279,18 @@ export function toggleLayerVisible(layerId) { }; } +export function toggleLayerVisible(layerId) { + return async (dispatch, getState) => { + const layer = getLayerById(layerId, getState()); + if (!layer) { + return; + } + const makeVisible = !layer.isVisible(); + + dispatch(setLayerVisibility(layerId, makeVisible)); + }; +} + export function setSelectedLayer(layerId) { return async (dispatch, getState) => { const oldSelectedLayer = getSelectedLayerId(getState()); @@ -840,3 +855,17 @@ export function hideLayerControl() { export function hideViewControl() { return { type: HIDE_VIEW_CONTROL, hideViewControl: true }; } + +export function setHiddenLayers(hiddenLayerIds) { + return (dispatch, getState) => { + const isMapReady = getMapReady(getState()); + + if (!isMapReady) { + dispatch({ type: SET_WAITING_FOR_READY_HIDDEN_LAYERS, hiddenLayerIds }); + } else { + getLayerListRaw(getState()).forEach(layer => + dispatch(setLayerVisibility(layer.id, !hiddenLayerIds.includes(layer.id))) + ); + } + }; +} diff --git a/x-pack/legacy/plugins/maps/public/elasticsearch_geo_utils.test.js b/x-pack/legacy/plugins/maps/public/elasticsearch_geo_utils.test.js index 170ede9f2bb26..fb4b0a6e29e6c 100644 --- a/x-pack/legacy/plugins/maps/public/elasticsearch_geo_utils.test.js +++ b/x-pack/legacy/plugins/maps/public/elasticsearch_geo_utils.test.js @@ -5,7 +5,7 @@ */ jest.mock('ui/new_platform'); -jest.mock('ui/index_patterns'); + jest.mock('./kibana_services', () => { return { SPATIAL_FILTER_TYPE: 'spatial_filter', @@ -20,7 +20,7 @@ import { convertMapExtentToPolygon, roundCoordinates, } from './elasticsearch_geo_utils'; -import { flattenHitWrapper } from 'ui/index_patterns'; +import { indexPatterns } from '../../../../../src/plugins/data/public'; const geoFieldName = 'location'; const mapExtent = { @@ -172,7 +172,7 @@ describe('hitsToGeoJson', () => { }, }, }; - const indexPatternFlattenHit = flattenHitWrapper(indexPatternMock); + const indexPatternFlattenHit = indexPatterns.flattenHitWrapper(indexPatternMock); it('Should handle geoField being an object', () => { const hits = [ diff --git a/x-pack/legacy/plugins/maps/public/embeddable/README.md b/x-pack/legacy/plugins/maps/public/embeddable/README.md index eb6571a96016c..1de327702fb87 100644 --- a/x-pack/legacy/plugins/maps/public/embeddable/README.md +++ b/x-pack/legacy/plugins/maps/public/embeddable/README.md @@ -9,6 +9,7 @@ - **hideToolbarOverlay:** (Boolean) Will disable toolbar, which can be used to navigate to coordinate by entering lat/long and zoom values. - **hideLayerControl:** (Boolean) Will hide useful layer control, which can be used to hide/show a layer to get a refined view of the map. - **hideViewControl:** (Boolean) Will hide view control at bottom right of the map, which shows lat/lon values based on mouse hover in the map, this is useful to get coordinate value from a particular point in map. +- **hiddenLayers:** (Array of Strings) Array of layer ids that should be hidden. Any other layers will be set to visible regardless of their value in the layerList used to initialize the embeddable ### Creating a Map embeddable from saved object ``` diff --git a/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable.js b/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable.js index 2ee766f91fbca..c723e996ee679 100644 --- a/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable.js +++ b/x-pack/legacy/plugins/maps/public/embeddable/map_embeddable.js @@ -32,11 +32,12 @@ import { hideToolbarOverlay, hideLayerControl, hideViewControl, + setHiddenLayers, } from '../actions/map_actions'; import { setReadOnly, setIsLayerTOCOpen, setOpenTOCDetails } from '../actions/ui_actions'; import { getIsLayerTOCOpen, getOpenTOCDetails } from '../selectors/ui_selectors'; import { getInspectorAdapters, setEventHandlers } from '../reducers/non_serializable_instances'; -import { getMapCenter, getMapZoom } from '../selectors/map_selectors'; +import { getMapCenter, getMapZoom, getHiddenLayerIds } from '../selectors/map_selectors'; import { MAP_SAVED_OBJECT_TYPE } from '../../common/constants'; export class MapEmbeddable extends Embeddable { @@ -153,6 +154,9 @@ export class MapEmbeddable extends Embeddable { } this._store.dispatch(replaceLayerList(this._layerList)); + if (this.input.hiddenLayers) { + this._store.dispatch(setHiddenLayers(this.input.hiddenLayers)); + } this._dispatchSetQuery(this.input); this._dispatchSetRefreshConfig(this.input); @@ -244,5 +248,13 @@ export class MapEmbeddable extends Embeddable { openTOCDetails, }); } + + const hiddenLayerIds = getHiddenLayerIds(this._store.getState()); + + if (!_.isEqual(this.input.hiddenLayers, hiddenLayerIds)) { + this.updateInput({ + hiddenLayers: hiddenLayerIds, + }); + } } } diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/ems_tms_source/create_source_editor.js b/x-pack/legacy/plugins/maps/public/layers/sources/ems_tms_source/create_source_editor.js deleted file mode 100644 index 65986d5bc93df..0000000000000 --- a/x-pack/legacy/plugins/maps/public/layers/sources/ems_tms_source/create_source_editor.js +++ /dev/null @@ -1,88 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import { EuiSelect, EuiFormRow } from '@elastic/eui'; - -import { getEMSClient } from '../../../meta'; -import { getEmsUnavailableMessage } from '../ems_unavailable_message'; -import { i18n } from '@kbn/i18n'; - -export const AUTO_SELECT = 'auto_select'; - -export class EMSTMSCreateSourceEditor extends React.Component { - state = { - emsTmsOptionsRaw: null, - }; - - _loadTmsOptions = async () => { - const emsClient = getEMSClient(); - const emsTMSServices = await emsClient.getTMSServices(); - const options = emsTMSServices.map(tmsService => { - return { - id: tmsService.getId(), - name: tmsService.getDisplayName(), - }; - }); - options.unshift({ - id: AUTO_SELECT, - name: i18n.translate('xpack.maps.source.emsTile.autoLabel', { - defaultMessage: 'Autoselect based on Kibana theme', - }), - }); - if (this._isMounted) { - this.setState({ - emsTmsOptionsRaw: options, - }); - } - }; - - _onEmsTileServiceChange = e => { - const value = e.target.value; - const isAutoSelect = value === AUTO_SELECT; - this.props.onSourceConfigChange({ - id: isAutoSelect ? null : value, - isAutoSelect, - }); - }; - - componentWillUnmount() { - this._isMounted = false; - } - - componentDidMount() { - this._isMounted = true; - this._loadTmsOptions(); - } - - render() { - if (!this.state.emsTmsOptionsRaw) { - // TODO display loading message - return null; - } - - const emsTileOptions = this.state.emsTmsOptionsRaw.map(service => ({ - value: service.id, - text: service.name || service.id, - })); - - return ( - - - - ); - } -} diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/ems_tms_source/ems_tms_source.js b/x-pack/legacy/plugins/maps/public/layers/sources/ems_tms_source/ems_tms_source.js index 8850c4c07ab73..76ecc18f2f7d7 100644 --- a/x-pack/legacy/plugins/maps/public/layers/sources/ems_tms_source/ems_tms_source.js +++ b/x-pack/legacy/plugins/maps/public/layers/sources/ems_tms_source/ems_tms_source.js @@ -11,7 +11,8 @@ import { AbstractTMSSource } from '../tms_source'; import { VectorTileLayer } from '../../vector_tile_layer'; import { getEMSClient } from '../../../meta'; -import { EMSTMSCreateSourceEditor } from './create_source_editor'; +import { TileServiceSelect } from './tile_service_select'; +import { UpdateSourceEditor } from './update_source_editor'; import { i18n } from '@kbn/i18n'; import { getDataSourceLabel } from '../../../../common/i18n_getters'; import { EMS_TMS } from '../../../../common/constants'; @@ -41,7 +42,7 @@ export class EMSTMSSource extends AbstractTMSSource { onPreviewSource(source); }; - return ; + return ; } constructor(descriptor, inspectorAdapters) { @@ -55,6 +56,10 @@ export class EMSTMSSource extends AbstractTMSSource { ); } + renderSourceSettingsEditor({ onChange }) { + return ; + } + async getImmutableProperties() { const displayName = await this.getDisplayName(); const autoSelectMsg = i18n.translate('xpack.maps.source.emsTile.isAutoSelectLabel', { @@ -78,7 +83,7 @@ export class EMSTMSSource extends AbstractTMSSource { async _getEMSTMSService() { const emsClient = getEMSClient(); const emsTMSServices = await emsClient.getTMSServices(); - const emsTileLayerId = this._getEmsTileLayerId(); + const emsTileLayerId = this.getTileLayerId(); const tmsService = emsTMSServices.find(tmsService => tmsService.getId() === emsTileLayerId); if (!tmsService) { throw new Error( @@ -110,7 +115,7 @@ export class EMSTMSSource extends AbstractTMSSource { const emsTMSService = await this._getEMSTMSService(); return emsTMSService.getDisplayName(); } catch (error) { - return this._getEmsTileLayerId(); + return this.getTileLayerId(); } } @@ -129,7 +134,7 @@ export class EMSTMSSource extends AbstractTMSSource { } getSpriteNamespacePrefix() { - return 'ems/' + this._getEmsTileLayerId(); + return 'ems/' + this.getTileLayerId(); } async getVectorStyleSheetAndSpriteMeta(isRetina) { @@ -142,7 +147,7 @@ export class EMSTMSSource extends AbstractTMSSource { }; } - _getEmsTileLayerId() { + getTileLayerId() { if (!this._descriptor.isAutoSelect) { return this._descriptor.id; } diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/ems_tms_source/tile_service_select.js b/x-pack/legacy/plugins/maps/public/layers/sources/ems_tms_source/tile_service_select.js new file mode 100644 index 0000000000000..337fc7aa46693 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/sources/ems_tms_source/tile_service_select.js @@ -0,0 +1,94 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { EuiSelect, EuiFormRow } from '@elastic/eui'; + +import { getEMSClient } from '../../../meta'; +import { getEmsUnavailableMessage } from '../ems_unavailable_message'; +import { i18n } from '@kbn/i18n'; + +export const AUTO_SELECT = 'auto_select'; + +export class TileServiceSelect extends React.Component { + state = { + emsTmsOptions: [], + hasLoaded: false, + }; + + componentWillUnmount() { + this._isMounted = false; + } + + componentDidMount() { + this._isMounted = true; + this._loadTmsOptions(); + } + + _loadTmsOptions = async () => { + const emsClient = getEMSClient(); + const emsTMSServices = await emsClient.getTMSServices(); + + if (!this._isMounted) { + return; + } + + const emsTmsOptions = emsTMSServices.map(tmsService => { + return { + value: tmsService.getId(), + text: tmsService.getDisplayName() ? tmsService.getDisplayName() : tmsService.getId(), + }; + }); + emsTmsOptions.unshift({ + value: AUTO_SELECT, + text: i18n.translate('xpack.maps.source.emsTile.autoLabel', { + defaultMessage: 'Autoselect based on Kibana theme', + }), + }); + this.setState({ emsTmsOptions, hasLoaded: true }); + }; + + _onChange = e => { + const value = e.target.value; + const isAutoSelect = value === AUTO_SELECT; + this.props.onTileSelect({ + id: isAutoSelect ? null : value, + isAutoSelect, + }); + }; + + render() { + const helpText = + this.state.hasLoaded && this.state.emsTmsOptions.length === 0 + ? getEmsUnavailableMessage() + : null; + + let selectedId; + if (this.props.config) { + selectedId = this.props.config.isAutoSelect ? AUTO_SELECT : this.props.config.id; + } + + return ( + + + + ); + } +} diff --git a/x-pack/legacy/plugins/maps/public/layers/sources/ems_tms_source/update_source_editor.js b/x-pack/legacy/plugins/maps/public/layers/sources/ems_tms_source/update_source_editor.js new file mode 100644 index 0000000000000..4d567b8dbb32a --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/sources/ems_tms_source/update_source_editor.js @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { Fragment } from 'react'; +import { EuiTitle, EuiPanel, EuiSpacer } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { TileServiceSelect } from './tile_service_select'; + +export function UpdateSourceEditor({ onChange, config }) { + const _onTileSelect = ({ id, isAutoSelect }) => { + onChange({ propName: 'id', value: id }); + onChange({ propName: 'isAutoSelect', value: isAutoSelect }); + }; + + return ( + + + +
+ +
+
+ + + + +
+ + +
+ ); +} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_stops.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_stops.js index df8464d141b5a..d523cf5870912 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_stops.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/color_stops.js @@ -113,7 +113,7 @@ export const ColorStops = ({ colorStops = [{ stop: 0, color: DEFAULT_COLOR }], o display="rowCompressed" >
- + {stopInput} {colorInput} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/dynamic_color_form.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/dynamic_color_form.js new file mode 100644 index 0000000000000..5e0f7434b04d0 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/dynamic_color_form.js @@ -0,0 +1,62 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import _ from 'lodash'; +import React, { Fragment } from 'react'; +import { FieldSelect } from '../field_select'; +import { ColorRampSelect } from './color_ramp_select'; +import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; + +export function DynamicColorForm({ + fields, + onDynamicStyleChange, + staticDynamicSelect, + styleProperty, +}) { + const styleOptions = styleProperty.getOptions(); + + const onFieldChange = ({ field }) => { + onDynamicStyleChange(styleProperty.getStyleName(), { ...styleOptions, field }); + }; + + const onColorChange = colorOptions => { + onDynamicStyleChange(styleProperty.getStyleName(), { + ...styleOptions, + ...colorOptions, + }); + }; + + let colorRampSelect; + if (styleOptions.field && styleOptions.field.name) { + colorRampSelect = ( + + ); + } + + return ( + + + {staticDynamicSelect} + + + + + + {colorRampSelect} + + ); +} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/dynamic_color_selection.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/dynamic_color_selection.js deleted file mode 100644 index 84327635f2b65..0000000000000 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/dynamic_color_selection.js +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import _ from 'lodash'; -import React, { Fragment } from 'react'; -import PropTypes from 'prop-types'; -import { dynamicColorShape } from '../style_option_shapes'; -import { FieldSelect, fieldShape } from '../field_select'; -import { ColorRampSelect } from './color_ramp_select'; -import { EuiSpacer } from '@elastic/eui'; - -export function DynamicColorSelection({ fields, onChange, styleOptions }) { - const onFieldChange = ({ field }) => { - onChange({ ...styleOptions, field }); - }; - - const onColorChange = colorOptions => { - onChange({ ...styleOptions, ...colorOptions }); - }; - - return ( - - - - - - ); -} - -DynamicColorSelection.propTypes = { - fields: PropTypes.arrayOf(fieldShape).isRequired, - styleOptions: dynamicColorShape.isRequired, - onChange: PropTypes.func.isRequired, -}; diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/static_color_form.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/static_color_form.js new file mode 100644 index 0000000000000..48befa1ca74c0 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/static_color_form.js @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { EuiColorPicker, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; + +export function StaticColorForm({ + onStaticStyleChange, + staticDynamicSelect, + styleProperty, + swatches, +}) { + const onColorChange = color => { + onStaticStyleChange(styleProperty.getStyleName(), { color }); + }; + + return ( + + {staticDynamicSelect} + + + + + ); +} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/static_color_selection.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/static_color_selection.js deleted file mode 100644 index e42b582dc3929..0000000000000 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/static_color_selection.js +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import PropTypes from 'prop-types'; -import { EuiColorPicker } from '@elastic/eui'; -import { staticColorShape } from '../style_option_shapes'; - -export function StaticColorSelection({ onChange, styleOptions, swatches }) { - const onColorChange = color => { - onChange({ color }); - }; - - return ( - - ); -} - -StaticColorSelection.propTypes = { - styleOptions: staticColorShape.isRequired, - onChange: PropTypes.func.isRequired, -}; diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/vector_style_color_editor.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/vector_style_color_editor.js index c7745fa69a82f..43e7050b3d1d2 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/vector_style_color_editor.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/color/vector_style_color_editor.js @@ -6,21 +6,29 @@ import React from 'react'; -import { StaticDynamicStyleRow } from '../static_dynamic_style_row'; -import { DynamicColorSelection } from './dynamic_color_selection'; -import { StaticColorSelection } from './static_color_selection'; +import { StylePropEditor } from '../style_prop_editor'; +import { DynamicColorForm } from './dynamic_color_form'; +import { StaticColorForm } from './static_color_form'; +import { i18n } from '@kbn/i18n'; export function VectorStyleColorEditor(props) { + const colorForm = props.styleProperty.isDynamic() ? ( + + ) : ( + + ); + return ( - + + {colorForm} + ); } diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/get_vector_style_label.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/get_vector_style_label.js index 325fc28f92051..16cfd34c95ab3 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/get_vector_style_label.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/get_vector_style_label.js @@ -42,6 +42,14 @@ export function getVectorStyleLabel(styleName) { return i18n.translate('xpack.maps.styles.vector.labelSizeLabel', { defaultMessage: 'Label size', }); + case VECTOR_STYLES.LABEL_BORDER_COLOR: + return i18n.translate('xpack.maps.styles.vector.labelBorderColorLabel', { + defaultMessage: 'Label border color', + }); + case VECTOR_STYLES.LABEL_BORDER_SIZE: + return i18n.translate('xpack.maps.styles.vector.labelBorderWidthLabel', { + defaultMessage: 'Label border width', + }); default: return styleName; } diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/dynamic_label_form.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/dynamic_label_form.js new file mode 100644 index 0000000000000..bad13b487cc29 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/dynamic_label_form.js @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import _ from 'lodash'; +import React from 'react'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { FieldSelect } from '../field_select'; + +export function DynamicLabelForm({ + fields, + onDynamicStyleChange, + staticDynamicSelect, + styleProperty, +}) { + const styleOptions = styleProperty.getOptions(); + + const onFieldChange = ({ field }) => { + onDynamicStyleChange(styleProperty.getStyleName(), { ...styleOptions, field }); + }; + + return ( + + {staticDynamicSelect} + + + + + ); +} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/dynamic_label_selector.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/dynamic_label_selector.js deleted file mode 100644 index e393341b90696..0000000000000 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/dynamic_label_selector.js +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import _ from 'lodash'; -import React from 'react'; -import { FieldSelect } from '../field_select'; - -export function DynamicLabelSelector({ fields, styleOptions, onChange }) { - const onFieldChange = ({ field }) => { - onChange({ ...styleOptions, field }); - }; - - return ( - - ); -} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/static_label_form.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/static_label_form.js new file mode 100644 index 0000000000000..721487b5d8ff0 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/static_label_form.js @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiFieldText, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; + +export function StaticLabelForm({ onStaticStyleChange, staticDynamicSelect, styleProperty }) { + const onValueChange = event => { + onStaticStyleChange(styleProperty.getStyleName(), { value: event.target.value }); + }; + + return ( + + {staticDynamicSelect} + + + + + ); +} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/static_label_selector.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/static_label_selector.js deleted file mode 100644 index ea296a3312799..0000000000000 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/static_label_selector.js +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import { i18n } from '@kbn/i18n'; -import { EuiFieldText } from '@elastic/eui'; - -export function StaticLabelSelector({ onChange, styleOptions }) { - const onValueChange = event => { - onChange({ value: event.target.value }); - }; - - return ( - - ); -} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/vector_style_label_border_size_editor.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/vector_style_label_border_size_editor.js new file mode 100644 index 0000000000000..7d06e8b530011 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/vector_style_label_border_size_editor.js @@ -0,0 +1,65 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; + +import { EuiFormRow, EuiSelect } from '@elastic/eui'; +import { LABEL_BORDER_SIZES, VECTOR_STYLES } from '../../vector_style_defaults'; +import { getVectorStyleLabel } from '../get_vector_style_label'; +import { i18n } from '@kbn/i18n'; + +const options = [ + { + value: LABEL_BORDER_SIZES.NONE, + text: i18n.translate('xpack.maps.styles.labelBorderSize.noneLabel', { + defaultMessage: 'None', + }), + }, + { + value: LABEL_BORDER_SIZES.SMALL, + text: i18n.translate('xpack.maps.styles.labelBorderSize.smallLabel', { + defaultMessage: 'Small', + }), + }, + { + value: LABEL_BORDER_SIZES.MEDIUM, + text: i18n.translate('xpack.maps.styles.labelBorderSize.mediumLabel', { + defaultMessage: 'Medium', + }), + }, + { + value: LABEL_BORDER_SIZES.LARGE, + text: i18n.translate('xpack.maps.styles.labelBorderSize.largeLabel', { + defaultMessage: 'Large', + }), + }, +]; + +export function VectorStyleLabelBorderSizeEditor({ handlePropertyChange, styleProperty }) { + function onChange(e) { + const styleDescriptor = { + options: { size: e.target.value }, + }; + handlePropertyChange(styleProperty.getStyleName(), styleDescriptor); + } + + return ( + + + + ); +} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/vector_style_label_editor.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/vector_style_label_editor.js index 6bca56425d38d..aaa21ea315f36 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/vector_style_label_editor.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/label/vector_style_label_editor.js @@ -6,16 +6,16 @@ import React from 'react'; -import { StaticDynamicStyleRow } from '../static_dynamic_style_row'; -import { DynamicLabelSelector } from './dynamic_label_selector'; -import { StaticLabelSelector } from './static_label_selector'; +import { StylePropEditor } from '../style_prop_editor'; +import { DynamicLabelForm } from './dynamic_label_form'; +import { StaticLabelForm } from './static_label_form'; export function VectorStyleLabelEditor(props) { - return ( - + const labelForm = props.styleProperty.isDynamic() ? ( + + ) : ( + ); + + return {labelForm}; } diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/__snapshots__/vector_icon.test.js.snap b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/__snapshots__/vector_icon.test.js.snap index 57368b52a2bce..5837a80ec3083 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/__snapshots__/vector_icon.test.js.snap +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/__snapshots__/vector_icon.test.js.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`Renders CircleIcon with correct styles when isPointOnly 1`] = ` +exports[`Renders CircleIcon 1`] = ` `; -exports[`Renders LineIcon with correct styles when isLineOnly 1`] = ` +exports[`Renders LineIcon 1`] = ` `; -exports[`Renders PolygonIcon with correct styles when not line only or not point only 1`] = ` +exports[`Renders PolygonIcon 1`] = ` `; -exports[`Renders SymbolIcon with correct styles when isPointOnly and symbolId provided 1`] = ` +exports[`Renders SymbolIcon 1`] = ` ; - } +export function VectorIcon({ fillColor, isPointsOnly, isLinesOnly, strokeColor, symbolId }) { + if (isLinesOnly) { const style = { - stroke: extractColorFromStyleProperty(this.props.lineColor, 'none'), - strokeWidth: '1px', - fill: extractColorFromStyleProperty(this.props.fillColor, 'grey'), + stroke: strokeColor, + strokeWidth: '4px', }; - - if (!this.state.isPointsOnly) { - return ; - } - - if (!this.props.symbolId) { - return ; - } - - return ( - - ); - } -} - -function extractColorFromStyleProperty(colorStyleProperty, defaultColor) { - if (!colorStyleProperty) { - return defaultColor; + return ; } - if (colorStyleProperty.type === VectorStyle.STYLE_TYPE.STATIC) { - return colorStyleProperty.options.color; - } + const style = { + stroke: strokeColor, + strokeWidth: '1px', + fill: fillColor, + }; - // Do not use dynamic color unless configuration is complete - if (!colorStyleProperty.options.field || !colorStyleProperty.options.field.name) { - return defaultColor; + if (!isPointsOnly) { + return ; } - // return middle of gradient for dynamic style property - - if (colorStyleProperty.options.useCustomColorRamp) { - if ( - !colorStyleProperty.options.customColorRamp || - !colorStyleProperty.options.customColorRamp.length - ) { - return defaultColor; - } - // favor the lowest color in even arrays - const middleIndex = Math.floor((colorStyleProperty.options.customColorRamp.length - 1) / 2); - return colorStyleProperty.options.customColorRamp[middleIndex].color; + if (!symbolId) { + return ; } - return getColorRampCenterColor(colorStyleProperty.options.color); + return ( + + ); } -const colorStylePropertyShape = PropTypes.shape({ - type: PropTypes.string.isRequired, - options: PropTypes.oneOfType([dynamicColorShape, staticColorShape]).isRequired, -}); - VectorIcon.propTypes = { - fillColor: colorStylePropertyShape, - lineColor: colorStylePropertyShape, + fillColor: PropTypes.string, + isPointsOnly: PropTypes.bool.isRequired, + isLinesOnly: PropTypes.bool.isRequired, + strokeColor: PropTypes.string.isRequired, symbolId: PropTypes.string, - loadIsPointsOnly: PropTypes.func.isRequired, - loadIsLinesOnly: PropTypes.func.isRequired, }; diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/vector_icon.test.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/vector_icon.test.js index 0341f1f22ca0a..9d1a4d75beba2 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/vector_icon.test.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/vector_icon.test.js @@ -8,93 +8,51 @@ import React from 'react'; import { shallow } from 'enzyme'; import { VectorIcon } from './vector_icon'; -import { VectorStyle } from '../../vector_style'; -let isPointsOnly = false; -let isLinesOnly = false; -const defaultProps = { - loadIsPointsOnly: () => { - return isPointsOnly; - }, - loadIsLinesOnly: () => { - return isLinesOnly; - }, - fillColor: { - type: VectorStyle.STYLE_TYPE.STATIC, - options: { - color: '#ff0000', - }, - }, - lineColor: { - type: VectorStyle.STYLE_TYPE.DYNAMIC, - options: { - color: 'Blues', - field: { - name: 'prop1', - }, - }, - }, -}; - -function configureIsLinesOnly() { - isLinesOnly = true; - isPointsOnly = false; -} - -function configureIsPointsOnly() { - isLinesOnly = false; - isPointsOnly = true; -} - -function configureNotLineOrPointOnly() { - isLinesOnly = false; - isPointsOnly = false; -} - -test('Renders PolygonIcon with correct styles when not line only or not point only', async () => { - configureNotLineOrPointOnly(); - const component = shallow(); - - // Ensure all promises resolve - await new Promise(resolve => process.nextTick(resolve)); - // Ensure the state changes are reflected - component.update(); +test('Renders PolygonIcon', () => { + const component = shallow( + + ); expect(component).toMatchSnapshot(); }); -test('Renders LineIcon with correct styles when isLineOnly', async () => { - configureIsLinesOnly(); - const component = shallow(); - - // Ensure all promises resolve - await new Promise(resolve => process.nextTick(resolve)); - // Ensure the state changes are reflected - component.update(); +test('Renders LineIcon', () => { + const component = shallow( + + ); expect(component).toMatchSnapshot(); }); -test('Renders CircleIcon with correct styles when isPointOnly', async () => { - configureIsPointsOnly(); - const component = shallow(); - - // Ensure all promises resolve - await new Promise(resolve => process.nextTick(resolve)); - // Ensure the state changes are reflected - component.update(); +test('Renders CircleIcon', () => { + const component = shallow( + + ); expect(component).toMatchSnapshot(); }); -test('Renders SymbolIcon with correct styles when isPointOnly and symbolId provided', async () => { - configureIsPointsOnly(); - const component = shallow(); - - // Ensure all promises resolve - await new Promise(resolve => process.nextTick(resolve)); - // Ensure the state changes are reflected - component.update(); +test('Renders SymbolIcon', () => { + const component = shallow( + + ); expect(component).toMatchSnapshot(); }); diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/vector_style_legend.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/vector_style_legend.js index 9e7afb0aa8873..a7e98c83468ae 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/vector_style_legend.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/legend/vector_style_legend.js @@ -4,49 +4,18 @@ * you may not use this file except in compliance with the Elastic License. */ -import _ from 'lodash'; -import React, { Component, Fragment } from 'react'; - -export class VectorStyleLegend extends Component { - state = { - styles: [], - }; - - componentDidMount() { - this._isMounted = true; - this._prevStyleDescriptors = undefined; - this._loadRows(); - } - - componentDidUpdate() { - this._loadRows(); - } - - componentWillUnmount() { - this._isMounted = false; - } - - _loadRows = _.debounce(async () => { - const styles = await this.props.getLegendDetailStyleProperties(); - const styleDescriptorPromises = styles.map(async style => { - return { - type: style.getStyleName(), - options: style.getOptions(), - fieldMeta: style.getFieldMeta(), - label: await style.getField().getLabel(), - }; - }); - - const styleDescriptors = await Promise.all(styleDescriptorPromises); - if (this._isMounted && !_.isEqual(styleDescriptors, this._prevStyleDescriptors)) { - this._prevStyleDescriptors = styleDescriptors; - this.setState({ styles: styles }); - } - }, 100); - - render() { - return this.state.styles.map(style => { - return {style.renderLegendDetailRow()}; - }); - } +import React, { Fragment } from 'react'; + +export function VectorStyleLegend({ isLinesOnly, isPointsOnly, styles, symbolId }) { + return styles.map(style => { + return ( + + {style.renderLegendDetailRow({ + isLinesOnly, + isPointsOnly, + symbolId, + })} + + ); + }); } diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/dynamic_orientation_form.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/dynamic_orientation_form.js new file mode 100644 index 0000000000000..e0b7e7b2865a2 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/dynamic_orientation_form.js @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import _ from 'lodash'; +import React from 'react'; +import { FieldSelect } from '../field_select'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; + +export function DynamicOrientationForm({ + fields, + onDynamicStyleChange, + staticDynamicSelect, + styleProperty, +}) { + const styleOptions = styleProperty.getOptions(); + + const onFieldChange = ({ field }) => { + onDynamicStyleChange(styleProperty.getStyleName(), { + ...styleOptions, + field, + }); + }; + + return ( + + {staticDynamicSelect} + + + + + ); +} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/dynamic_orientation_selection.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/dynamic_orientation_selection.js deleted file mode 100644 index 8ad3916ac6509..0000000000000 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/dynamic_orientation_selection.js +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import _ from 'lodash'; -import React from 'react'; -import PropTypes from 'prop-types'; -import { dynamicOrientationShape } from '../style_option_shapes'; -import { FieldSelect, fieldShape } from '../field_select'; - -export function DynamicOrientationSelection({ fields, styleOptions, onChange }) { - const onFieldChange = ({ field }) => { - onChange({ ...styleOptions, field }); - }; - - return ( - - ); -} - -DynamicOrientationSelection.propTypes = { - fields: PropTypes.arrayOf(fieldShape).isRequired, - styleOptions: dynamicOrientationShape.isRequired, - onChange: PropTypes.func.isRequired, -}; diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/orientation_editor.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/orientation_editor.js index e97252a5e79da..915fc92c9fb38 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/orientation_editor.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/orientation_editor.js @@ -6,20 +6,16 @@ import React from 'react'; -import { StaticDynamicStyleRow } from '../static_dynamic_style_row'; -import { DynamicOrientationSelection } from './dynamic_orientation_selection'; -import { StaticOrientationSelection } from './static_orientation_selection'; +import { StylePropEditor } from '../style_prop_editor'; +import { DynamicOrientationForm } from './dynamic_orientation_form'; +import { StaticOrientationForm } from './static_orientation_form'; export function OrientationEditor(props) { - return ( - + const orientationForm = props.styleProperty.isDynamic() ? ( + + ) : ( + ); + + return {orientationForm}; } diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/static_orientation_form.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/static_orientation_form.js new file mode 100644 index 0000000000000..8c4418f95e1d2 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/static_orientation_form.js @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { ValidatedRange } from '../../../../../components/validated_range'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; + +export function StaticOrientationForm({ onStaticStyleChange, staticDynamicSelect, styleProperty }) { + const onOrientationChange = orientation => { + onStaticStyleChange(styleProperty.getStyleName(), { orientation }); + }; + + return ( + + {staticDynamicSelect} + + + + + ); +} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/static_orientation_selection.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/static_orientation_selection.js deleted file mode 100644 index b5529c6987459..0000000000000 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/orientation/static_orientation_selection.js +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import PropTypes from 'prop-types'; -import { staticOrientationShape } from '../style_option_shapes'; -import { ValidatedRange } from '../../../../../components/validated_range'; - -export function StaticOrientationSelection({ onChange, styleOptions }) { - const onOrientationChange = orientation => { - onChange({ orientation }); - }; - - return ( - - ); -} - -StaticOrientationSelection.propTypes = { - styleOptions: staticOrientationShape.isRequired, - onChange: PropTypes.func.isRequired, -}; diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/dynamic_size_form.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/dynamic_size_form.js new file mode 100644 index 0000000000000..8b069cd53b731 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/dynamic_size_form.js @@ -0,0 +1,63 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import _ from 'lodash'; +import React, { Fragment } from 'react'; +import { FieldSelect } from '../field_select'; +import { SizeRangeSelector } from './size_range_selector'; +import { EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; + +export function DynamicSizeForm({ + fields, + onDynamicStyleChange, + staticDynamicSelect, + styleProperty, +}) { + const styleOptions = styleProperty.getOptions(); + + const onFieldChange = ({ field }) => { + onDynamicStyleChange(styleProperty.getStyleName(), { ...styleOptions, field }); + }; + + const onSizeRangeChange = ({ minSize, maxSize }) => { + onDynamicStyleChange(styleProperty.getStyleName(), { + ...styleOptions, + minSize, + maxSize, + }); + }; + + let sizeRange; + if (styleOptions.field && styleOptions.field.name) { + sizeRange = ( + + ); + } + + return ( + + + {staticDynamicSelect} + + + + + + {sizeRange} + + ); +} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/dynamic_size_selection.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/dynamic_size_selection.js deleted file mode 100644 index 76c5b97976bbc..0000000000000 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/dynamic_size_selection.js +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import _ from 'lodash'; -import React, { Fragment } from 'react'; -import PropTypes from 'prop-types'; -import { dynamicSizeShape } from '../style_option_shapes'; -import { FieldSelect, fieldShape } from '../field_select'; -import { SizeRangeSelector } from './size_range_selector'; -import { EuiSpacer } from '@elastic/eui'; - -export function DynamicSizeSelection({ fields, styleOptions, onChange }) { - const onFieldChange = ({ field }) => { - onChange({ ...styleOptions, field }); - }; - - const onSizeRangeChange = ({ minSize, maxSize }) => { - onChange({ ...styleOptions, minSize, maxSize }); - }; - - return ( - - - - - - ); -} - -DynamicSizeSelection.propTypes = { - fields: PropTypes.arrayOf(fieldShape).isRequired, - styleOptions: dynamicSizeShape.isRequired, - onChange: PropTypes.func.isRequired, -}; diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/static_size_form.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/static_size_form.js new file mode 100644 index 0000000000000..d8fe1322db3e3 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/static_size_form.js @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { ValidatedRange } from '../../../../../components/validated_range'; +import { i18n } from '@kbn/i18n'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; + +export function StaticSizeForm({ onStaticStyleChange, staticDynamicSelect, styleProperty }) { + const onSizeChange = size => { + onStaticStyleChange(styleProperty.getStyleName(), { size }); + }; + + return ( + + {staticDynamicSelect} + + + + + ); +} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/static_size_selection.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/static_size_selection.js deleted file mode 100644 index 38f8fe53d1748..0000000000000 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/static_size_selection.js +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import PropTypes from 'prop-types'; -import { staticSizeShape } from '../style_option_shapes'; -import { ValidatedRange } from '../../../../../components/validated_range'; -import { i18n } from '@kbn/i18n'; - -export function StaticSizeSelection({ onChange, styleOptions }) { - const onSizeChange = size => { - onChange({ size }); - }; - - return ( - - ); -} - -StaticSizeSelection.propTypes = { - styleOptions: staticSizeShape.isRequired, - onChange: PropTypes.func.isRequired, -}; diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/vector_style_size_editor.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/vector_style_size_editor.js index 6580bfc00e0ad..e344f72bd429a 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/vector_style_size_editor.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/size/vector_style_size_editor.js @@ -6,20 +6,16 @@ import React from 'react'; -import { StaticDynamicStyleRow } from '../static_dynamic_style_row'; -import { DynamicSizeSelection } from './dynamic_size_selection'; -import { StaticSizeSelection } from './static_size_selection'; +import { StylePropEditor } from '../style_prop_editor'; +import { DynamicSizeForm } from './dynamic_size_form'; +import { StaticSizeForm } from './static_size_form'; export function VectorStyleSizeEditor(props) { - return ( - + const sizeForm = props.styleProperty.isDynamic() ? ( + + ) : ( + ); + + return {sizeForm}; } diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/static_dynamic_style_row.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/static_dynamic_style_row.js deleted file mode 100644 index 625f63a77374d..0000000000000 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/static_dynamic_style_row.js +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React, { Component, Fragment } from 'react'; -import { VectorStyle } from '../vector_style'; -import { i18n } from '@kbn/i18n'; -import { FieldMetaOptionsPopover } from './field_meta_options_popover'; -import { getVectorStyleLabel } from './get_vector_style_label'; - -import { EuiFlexGroup, EuiFlexItem, EuiToolTip, EuiFormRow, EuiButtonToggle } from '@elastic/eui'; - -export class StaticDynamicStyleRow extends Component { - // Store previous options locally so when type is toggled, - // previous style options can be used. - prevStaticStyleOptions = this.props.defaultStaticStyleOptions; - prevDynamicStyleOptions = this.props.defaultDynamicStyleOptions; - - _canBeDynamic() { - return this.props.fields.length > 0; - } - - _isDynamic() { - return this.props.styleProperty.isDynamic(); - } - - _getStyleOptions() { - return this.props.styleProperty.getOptions(); - } - - _onFieldMetaOptionsChange = fieldMetaOptions => { - const styleDescriptor = { - type: VectorStyle.STYLE_TYPE.DYNAMIC, - options: { - ...this._getStyleOptions(), - fieldMetaOptions, - }, - }; - this.props.handlePropertyChange(this.props.styleProperty.getStyleName(), styleDescriptor); - }; - - _onStaticStyleChange = options => { - const styleDescriptor = { - type: VectorStyle.STYLE_TYPE.STATIC, - options, - }; - this.props.handlePropertyChange(this.props.styleProperty.getStyleName(), styleDescriptor); - }; - - _onDynamicStyleChange = options => { - const styleDescriptor = { - type: VectorStyle.STYLE_TYPE.DYNAMIC, - options, - }; - this.props.handlePropertyChange(this.props.styleProperty.getStyleName(), styleDescriptor); - }; - - _onTypeToggle = () => { - if (this._isDynamic()) { - // preserve current dynmaic style - this.prevDynamicStyleOptions = this._getStyleOptions(); - // toggle to static style - this._onStaticStyleChange(this.prevStaticStyleOptions); - return; - } - - // preserve current static style - this.prevStaticStyleOptions = this._getStyleOptions(); - // toggle to dynamic style - this._onDynamicStyleChange(this.prevDynamicStyleOptions); - }; - - _renderStyleSelector() { - if (this._isDynamic()) { - const DynamicSelector = this.props.DynamicSelector; - return ( - - - - - ); - } - - const StaticSelector = this.props.StaticSelector; - return ( - - ); - } - - render() { - const isDynamic = this._isDynamic(); - const dynamicTooltipContent = isDynamic - ? i18n.translate('xpack.maps.styles.staticDynamic.staticDescription', { - defaultMessage: 'Use static styling properties to symbolize features.', - }) - : i18n.translate('xpack.maps.styles.staticDynamic.dynamicDescription', { - defaultMessage: 'Use property values to symbolize features.', - }); - - return ( - - - - {this._renderStyleSelector()} - - - {this._canBeDynamic() && ( - - - - - - - - )} - - ); - } -} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/style_prop_editor.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/style_prop_editor.js new file mode 100644 index 0000000000000..1ac8edfb2cc69 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/style_prop_editor.js @@ -0,0 +1,104 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { Component, Fragment } from 'react'; +import { FieldMetaOptionsPopover } from './field_meta_options_popover'; +import { getVectorStyleLabel } from './get_vector_style_label'; +import { EuiFormRow, EuiSelect } from '@elastic/eui'; +import { VectorStyle } from '../vector_style'; +import { i18n } from '@kbn/i18n'; + +export class StylePropEditor extends Component { + _prevStaticStyleOptions = this.props.defaultStaticStyleOptions; + _prevDynamicStyleOptions = this.props.defaultDynamicStyleOptions; + + _onTypeToggle = () => { + if (this.props.styleProperty.isDynamic()) { + // preserve current dynmaic style + this._prevDynamicStyleOptions = this.props.styleProperty.getOptions(); + // toggle to static style + this.props.onStaticStyleChange( + this.props.styleProperty.getStyleName(), + this._prevStaticStyleOptions + ); + } else { + // preserve current static style + this._prevStaticStyleOptions = this.props.styleProperty.getOptions(); + // toggle to dynamic style + this.props.onDynamicStyleChange( + this.props.styleProperty.getStyleName(), + this._prevDynamicStyleOptions + ); + } + }; + + _onFieldMetaOptionsChange = fieldMetaOptions => { + const options = { + ...this.props.styleProperty.getOptions(), + fieldMetaOptions, + }; + this.props.onDynamicStyleChange(this.props.styleProperty.getStyleName(), options); + }; + + renderStaticDynamicSelect() { + const options = [ + { + value: VectorStyle.STYLE_TYPE.STATIC, + text: this.props.customStaticOptionLabel + ? this.props.customStaticOptionLabel + : i18n.translate('xpack.maps.styles.staticDynamicSelect.staticLabel', { + defaultMessage: 'Fixed', + }), + }, + { + value: VectorStyle.STYLE_TYPE.DYNAMIC, + text: i18n.translate('xpack.maps.styles.staticDynamicSelect.dynamicLabel', { + defaultMessage: 'By value', + }), + }, + ]; + + return ( + + ); + } + + render() { + const fieldMetaOptionsPopover = this.props.styleProperty.isDynamic() ? ( + + ) : null; + + return ( + + + {React.cloneElement(this.props.children, { + staticDynamicSelect: this.renderStaticDynamicSelect(), + })} + {fieldMetaOptionsPopover} + + + ); + } +} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/vector_style_editor.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/vector_style_editor.js index 44f630db9d890..bd22b4b9cc5ce 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/vector_style_editor.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/components/vector_style_editor.js @@ -12,8 +12,14 @@ import { VectorStyleColorEditor } from './color/vector_style_color_editor'; import { VectorStyleSizeEditor } from './size/vector_style_size_editor'; import { VectorStyleSymbolEditor } from './vector_style_symbol_editor'; import { VectorStyleLabelEditor } from './label/vector_style_label_editor'; +import { VectorStyleLabelBorderSizeEditor } from './label/vector_style_label_border_size_editor'; +import { VectorStyle } from '../vector_style'; import { OrientationEditor } from './orientation/orientation_editor'; -import { getDefaultDynamicProperties, getDefaultStaticProperties } from '../vector_style_defaults'; +import { + getDefaultDynamicProperties, + getDefaultStaticProperties, + VECTOR_STYLES, +} from '../vector_style_defaults'; import { DEFAULT_FILL_COLORS, DEFAULT_LINE_COLORS } from '../../color_utils'; import { VECTOR_SHAPE_TYPES } from '../../../sources/vector_feature_types'; import { SYMBOLIZE_AS_ICON } from '../vector_constants'; @@ -81,25 +87,14 @@ export class VectorStyleEditor extends Component { async _loadSupportedFeatures() { const supportedFeatures = await this.props.layer.getSource().getSupportedShapeTypes(); - const isPointsOnly = await this.props.loadIsPointsOnly(); - const isLinesOnly = await this.props.loadIsLinesOnly(); - if (!this._isMounted) { return; } - if ( - _.isEqual(supportedFeatures, this.state.supportedFeatures) && - isPointsOnly === this.state.isPointsOnly && - isLinesOnly === this.state.isLinesOnly - ) { - return; - } - let selectedFeature = VECTOR_SHAPE_TYPES.POLYGON; - if (isPointsOnly) { + if (this.props.isPointsOnly) { selectedFeature = VECTOR_SHAPE_TYPES.POINT; - } else if (isLinesOnly) { + } else if (this.props.isLinesOnly) { selectedFeature = VECTOR_SHAPE_TYPES.LINE; } @@ -107,12 +102,7 @@ export class VectorStyleEditor extends Component { !_.isEqual(supportedFeatures, this.state.supportedFeatures) || selectedFeature !== this.state.selectedFeature ) { - this.setState({ - supportedFeatures, - selectedFeature, - isPointsOnly, - isLinesOnly, - }); + this.setState({ supportedFeatures, selectedFeature }); } } @@ -128,15 +118,36 @@ export class VectorStyleEditor extends Component { this.props.onIsTimeAwareChange(event.target.checked); }; + _onStaticStyleChange = (propertyName, options) => { + const styleDescriptor = { + type: VectorStyle.STYLE_TYPE.STATIC, + options, + }; + this.props.handlePropertyChange(propertyName, styleDescriptor); + }; + + _onDynamicStyleChange = (propertyName, options) => { + const styleDescriptor = { + type: VectorStyle.STYLE_TYPE.DYNAMIC, + options, + }; + this.props.handlePropertyChange(propertyName, styleDescriptor); + }; + _renderFillColor() { return ( ); } @@ -145,11 +156,16 @@ export class VectorStyleEditor extends Component { return ( ); } @@ -157,11 +173,16 @@ export class VectorStyleEditor extends Component { _renderLineWidth() { return ( ); } @@ -169,11 +190,16 @@ export class VectorStyleEditor extends Component { _renderSymbolSize() { return ( ); } @@ -182,30 +208,66 @@ export class VectorStyleEditor extends Component { return ( + + + + + + @@ -217,11 +279,16 @@ export class VectorStyleEditor extends Component { if (this.props.symbolDescriptor.options.symbolizeAs === SYMBOLIZE_AS_ICON) { iconOrientation = ( ); } diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/__snapshots__/dynamic_color_property.test.js.snap b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/__snapshots__/dynamic_color_property.test.js.snap new file mode 100644 index 0000000000000..8da8cfaa71e2c --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/__snapshots__/dynamic_color_property.test.js.snap @@ -0,0 +1,17 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Should render categorical legend 1`] = `""`; + +exports[`Should render ranged legend 1`] = ` + + } + maxLabel="100_format" + minLabel="0_format" + propertyLabel="Border color" +/> +`; diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/components/categorical_legend.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/components/categorical_legend.js new file mode 100644 index 0000000000000..a46492b6034a7 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/components/categorical_legend.js @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import _ from 'lodash'; +const EMPTY_VALUE = ''; + +export class CategoricalLegend extends React.Component { + state = { + label: EMPTY_VALUE, + }; + + componentDidMount() { + this._isMounted = true; + this._loadParams(); + } + + componentDidUpdate() { + this._loadParams(); + } + + componentWillUnmount() { + this._isMounted = false; + } + + async _loadParams() { + const label = await this.props.style.getField().getLabel(); + const newState = { label }; + if (this._isMounted && !_.isEqual(this.state, newState)) { + this.setState(newState); + } + } + + render() { + if (this.state.label === EMPTY_VALUE) { + return null; + } + return this.props.style.renderBreakedLegend({ + fieldLabel: this.state.label, + isLinesOnly: this.props.isLinesOnly, + isPointsOnly: this.props.isPointsOnly, + symbolId: this.props.symbolId, + }); + } +} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/components/dynamic_legend_row.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/components/dynamic_legend_row.js deleted file mode 100644 index 5933bd1575b2a..0000000000000 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/components/dynamic_legend_row.js +++ /dev/null @@ -1,81 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import _ from 'lodash'; -import { RangedStyleLegendRow } from '../../../components/ranged_style_legend_row'; -import { getVectorStyleLabel } from '../../components/get_vector_style_label'; - -const EMPTY_VALUE = ''; - -export class DynamicLegendRow extends React.Component { - constructor() { - super(); - this._isMounted = false; - this.state = { - label: EMPTY_VALUE, - }; - } - - async _loadParams() { - const label = await this.props.style.getField().getLabel(); - const newState = { label }; - if (this._isMounted && !_.isEqual(this.state, newState)) { - this.setState(newState); - } - } - - componentDidUpdate() { - this._loadParams(); - } - - componentWillUnmount() { - this._isMounted = false; - } - - componentDidMount() { - this._isMounted = true; - this._loadParams(); - } - - _formatValue(value) { - if (value === EMPTY_VALUE) { - return value; - } - return this.props.style.formatField(value); - } - - render() { - const fieldMeta = this.props.style.getFieldMeta(); - - let minLabel = EMPTY_VALUE; - let maxLabel = EMPTY_VALUE; - if (fieldMeta) { - const range = { min: fieldMeta.min, max: fieldMeta.max }; - const min = this._formatValue(_.get(range, 'min', EMPTY_VALUE)); - minLabel = - this.props.style.isFieldMetaEnabled() && range && range.isMinOutsideStdRange - ? `< ${min}` - : min; - - const max = this._formatValue(_.get(range, 'max', EMPTY_VALUE)); - maxLabel = - this.props.style.isFieldMetaEnabled() && range && range.isMaxOutsideStdRange - ? `> ${max}` - : max; - } - - return ( - - ); - } -} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/components/ordinal_legend.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/components/ordinal_legend.js new file mode 100644 index 0000000000000..564bae3ef3f72 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/components/ordinal_legend.js @@ -0,0 +1,78 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import _ from 'lodash'; +import { RangedStyleLegendRow } from '../../../components/ranged_style_legend_row'; +const EMPTY_VALUE = ''; + +export class OrdinalLegend extends React.Component { + constructor() { + super(); + this._isMounted = false; + this.state = { + label: EMPTY_VALUE, + }; + } + + async _loadParams() { + const label = await this.props.style.getField().getLabel(); + const newState = { label }; + if (this._isMounted && !_.isEqual(this.state, newState)) { + this.setState(newState); + } + } + + _formatValue(value) { + if (value === EMPTY_VALUE) { + return value; + } + return this.props.style.formatField(value); + } + + componentDidUpdate() { + this._loadParams(); + } + + componentWillUnmount() { + this._isMounted = false; + } + + componentDidMount() { + this._isMounted = true; + this._loadParams(); + } + render() { + const fieldMeta = this.props.style.getFieldMeta(); + + let minLabel = EMPTY_VALUE; + let maxLabel = EMPTY_VALUE; + if (fieldMeta) { + const range = { min: fieldMeta.min, max: fieldMeta.max }; + const min = this._formatValue(_.get(range, 'min', EMPTY_VALUE)); + minLabel = + this.props.style.isFieldMetaEnabled() && range && range.isMinOutsideStdRange + ? `< ${min}` + : min; + + const max = this._formatValue(_.get(range, 'max', EMPTY_VALUE)); + maxLabel = + this.props.style.isFieldMetaEnabled() && range && range.isMaxOutsideStdRange + ? `> ${max}` + : max; + } + + return ( + + ); + } +} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_color_property.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_color_property.js index 3961325c3bd59..804a0f8975d3e 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_color_property.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_color_property.js @@ -10,6 +10,9 @@ import { getComputedFieldName } from '../style_util'; import { getColorRampStops } from '../../color_utils'; import { ColorGradient } from '../../components/color_gradient'; import React from 'react'; +import { EuiFlexGroup, EuiFlexItem, EuiSpacer, EuiText, EuiToolTip } from '@elastic/eui'; +import { VectorIcon } from '../components/legend/vector_icon'; +import { VECTOR_STYLES } from '../vector_style_defaults'; export class DynamicColorProperty extends DynamicStyleProperty { syncCircleColorWithMb(mbLayerId, mbMap, alpha) { @@ -52,6 +55,11 @@ export class DynamicColorProperty extends DynamicStyleProperty { mbMap.setPaintProperty(mbLayerId, 'text-opacity', alpha); } + syncLabelBorderColorWithMb(mbLayerId, mbMap) { + const color = this._getMbColor(); + mbMap.setPaintProperty(mbLayerId, 'text-halo-color', color); + } + isCustomColorRamp() { return this._options.useCustomColorRamp; } @@ -64,6 +72,14 @@ export class DynamicColorProperty extends DynamicStyleProperty { return !this.isCustomColorRamp(); } + isRanged() { + return !this.isCustomColorRamp(); + } + + hasBreaks() { + return this.isCustomColorRamp(); + } + _getMbColor() { const isDynamicConfigComplete = _.has(this._options, 'field.name') && _.has(this._options, 'color'); @@ -117,11 +133,81 @@ export class DynamicColorProperty extends DynamicStyleProperty { return getColorRampStops(this._options.color); } - renderLegendHeader() { + renderRangeLegendHeader() { if (this._options.color) { return ; } else { return null; } } + + _renderStopIcon(color, isLinesOnly, isPointsOnly, symbolId) { + if (this.getStyleName() === VECTOR_STYLES.LABEL_COLOR) { + const style = { color: color }; + return ( + + Tx + + ); + } + + const fillColor = this.getStyleName() === VECTOR_STYLES.FILL_COLOR ? color : 'none'; + return ( + + ); + } + + _renderColorbreaks({ isLinesOnly, isPointsOnly, symbolId }) { + if (!this._options.customColorRamp) { + return null; + } + + return this._options.customColorRamp.map((config, index) => { + const value = this.formatField(config.stop); + return ( + + + + {value} + + + {this._renderStopIcon(config.color, isLinesOnly, isPointsOnly, symbolId)} + + + + ); + }); + } + + renderBreakedLegend({ fieldLabel, isPointsOnly, isLinesOnly, symbolId }) { + return ( +
+ + + {this._renderColorbreaks({ + isPointsOnly, + isLinesOnly, + symbolId, + })} + + + + + + + {fieldLabel} + + + + + +
+ ); + } } diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_color_property.test.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_color_property.test.js new file mode 100644 index 0000000000000..21c24e837b412 --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_color_property.test.js @@ -0,0 +1,88 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +jest.mock('../components/vector_style_editor', () => ({ + VectorStyleEditor: () => { + return
mockVectorStyleEditor
; + }, +})); + +import React from 'react'; +import { shallow } from 'enzyme'; + +import { VECTOR_STYLES } from '../vector_style_defaults'; +import { DynamicColorProperty } from './dynamic_color_property'; + +const mockField = { + async getLabel() { + return 'foobar_label'; + }, + + getName() { + return 'foobar'; + }, + supportsFieldMeta() { + return true; + }, +}; + +test('Should render ranged legend', () => { + const colorStyle = new DynamicColorProperty( + { + color: 'Blues', + }, + VECTOR_STYLES.LINE_COLOR, + mockField, + () => { + return { min: 0, max: 100 }; + }, + () => { + return x => x + '_format'; + } + ); + + const legendRow = colorStyle.renderLegendDetailRow({ + isPointsOnly: true, + isLinesOnly: false, + }); + const component = shallow(legendRow); + + expect(component).toMatchSnapshot(); +}); + +test('Should render categorical legend', () => { + const colorStyle = new DynamicColorProperty( + { + useCustomColorRamp: true, + customColorRamp: [ + { + stop: 0, + color: '#FF0000', + }, + { + stop: 10, + color: '#00FF00', + }, + ], + }, + VECTOR_STYLES.LINE_COLOR, + mockField, + () => { + return { min: 0, max: 100 }; + }, + () => { + return x => x + '_format'; + } + ); + + const legendRow = colorStyle.renderLegendDetailRow({ + isPointsOnly: true, + isLinesOnly: false, + }); + const component = shallow(legendRow); + + expect(component).toMatchSnapshot(); +}); diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_size_property.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_size_property.js index 05e235f6926a6..5a4da1a80c918 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_size_property.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_size_property.js @@ -5,7 +5,7 @@ */ import { DynamicStyleProperty } from './dynamic_style_property'; -import { getComputedFieldName } from '../style_util'; + import { HALF_LARGE_MAKI_ICON_SIZE, LARGE_MAKI_ICON_SIZE, @@ -63,7 +63,7 @@ export class DynamicSizeProperty extends DynamicStyleProperty { } syncHaloWidthWithMb(mbLayerId, mbMap) { - const haloWidth = this._getMbSize(); + const haloWidth = this.getMbSizeExpression(); mbMap.setPaintProperty(mbLayerId, 'icon-halo-width', haloWidth); } @@ -76,7 +76,7 @@ export class DynamicSizeProperty extends DynamicStyleProperty { mbMap.setLayoutProperty(symbolLayerId, 'icon-image', `${symbolId}-${iconPixels}`); const halfIconPixels = iconPixels / 2; - const targetName = getComputedFieldName(VECTOR_STYLES.ICON_SIZE, this._options.field.name); + const targetName = this.getComputedFieldName(); // Using property state instead of feature-state because layout properties do not support feature-state mbMap.setLayoutProperty(symbolLayerId, 'icon-size', [ 'interpolate', @@ -94,29 +94,29 @@ export class DynamicSizeProperty extends DynamicStyleProperty { } syncCircleStrokeWidthWithMb(mbLayerId, mbMap) { - const lineWidth = this._getMbSize(); + const lineWidth = this.getMbSizeExpression(); mbMap.setPaintProperty(mbLayerId, 'circle-stroke-width', lineWidth); } syncCircleRadiusWithMb(mbLayerId, mbMap) { - const circleRadius = this._getMbSize(); + const circleRadius = this.getMbSizeExpression(); mbMap.setPaintProperty(mbLayerId, 'circle-radius', circleRadius); } syncLineWidthWithMb(mbLayerId, mbMap) { - const lineWidth = this._getMbSize(); + const lineWidth = this.getMbSizeExpression(); mbMap.setPaintProperty(mbLayerId, 'line-width', lineWidth); } syncLabelSizeWithMb(mbLayerId, mbMap) { - const lineWidth = this._getMbSize(); + const lineWidth = this.getMbSizeExpression(); mbMap.setLayoutProperty(mbLayerId, 'text-size', lineWidth); } - _getMbSize() { + getMbSizeExpression() { if (this._isSizeDynamicConfigComplete(this._options)) { return this._getMbDataDrivenSize({ - targetName: getComputedFieldName(this._styleName, this._options.field.name), + targetName: this.getComputedFieldName(), minSize: this._options.minSize, maxSize: this._options.maxSize, }); @@ -146,7 +146,7 @@ export class DynamicSizeProperty extends DynamicStyleProperty { ); } - renderLegendHeader() { + renderRangeLegendHeader() { let icons; if (this.getStyleName() === VECTOR_STYLES.LINE_WIDTH) { icons = getLineWidthIcons(); @@ -157,7 +157,7 @@ export class DynamicSizeProperty extends DynamicStyleProperty { } return ( - + {icons.map((icon, index) => { const isLast = index === icons.length - 1; let spacer; diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_style_property.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_style_property.js index e6448f365b44a..97ab7cb78015b 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_style_property.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_style_property.js @@ -8,9 +8,10 @@ import _ from 'lodash'; import { AbstractStyleProperty } from './style_property'; import { DEFAULT_SIGMA } from '../vector_style_defaults'; import { STYLE_TYPE } from '../../../../../common/constants'; -import { DynamicLegendRow } from './components/dynamic_legend_row'; -import { scaleValue } from '../style_util'; +import { scaleValue, getComputedFieldName } from '../style_util'; import React from 'react'; +import { OrdinalLegend } from './components/ordinal_legend'; +import { CategoricalLegend } from './components/categorical_legend'; export class DynamicStyleProperty extends AbstractStyleProperty { static type = STYLE_TYPE.DYNAMIC; @@ -30,6 +31,13 @@ export class DynamicStyleProperty extends AbstractStyleProperty { return this._field; } + getComputedFieldName() { + if (!this.isComplete()) { + return null; + } + return getComputedFieldName(this._styleName, this.getField().getName()); + } + isDynamic() { return true; } @@ -38,6 +46,14 @@ export class DynamicStyleProperty extends AbstractStyleProperty { return true; } + hasBreaks() { + return false; + } + + isRanged() { + return true; + } + isComplete() { return !!this._field; } @@ -75,6 +91,10 @@ export class DynamicStyleProperty extends AbstractStyleProperty { } pluckStyleMetaFromFeatures(features) { + if (!this.isOrdinal()) { + return null; + } + const name = this.getField().getName(); let min = Infinity; let max = -Infinity; @@ -97,6 +117,10 @@ export class DynamicStyleProperty extends AbstractStyleProperty { } pluckStyleMetaFromFieldMetaData(fieldMetaData) { + if (!this.isOrdinal()) { + return null; + } + const realFieldName = this._field.getESDocFieldName ? this._field.getESDocFieldName() : this._field.getName(); @@ -120,13 +144,13 @@ export class DynamicStyleProperty extends AbstractStyleProperty { } formatField(value) { - if (!this.getField()) { + if (this.getField()) { + const fieldName = this.getField().getName(); + const fieldFormatter = this._getFieldFormatter(fieldName); + return fieldFormatter ? fieldFormatter(value) : value; + } else { return value; } - - const fieldName = this.getField().getName(); - const fieldFormatter = this._getFieldFormatter(fieldName); - return fieldFormatter ? fieldFormatter(value) : value; } getMbValue(value) { @@ -144,7 +168,32 @@ export class DynamicStyleProperty extends AbstractStyleProperty { return valueAsFloat; } - renderLegendDetailRow() { - return ; + renderBreakedLegend() { + return null; + } + + _renderCategoricalLegend({ isPointsOnly, isLinesOnly, symbolId }) { + return ( + + ); + } + + _renderRangeLegend() { + return ; + } + + renderLegendDetailRow({ isPointsOnly, isLinesOnly, symbolId }) { + if (this.isRanged()) { + return this._renderRangeLegend(); + } else if (this.hasBreaks()) { + return this._renderCategoricalLegend({ isPointsOnly, isLinesOnly, symbolId }); + } else { + return null; + } } } diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_text_property.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_text_property.js index b716030d2f263..fbc4c3af78f98 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_text_property.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/dynamic_text_property.js @@ -32,8 +32,4 @@ export class DynamicTextProperty extends DynamicStyleProperty { isScaled() { return false; } - - renderHeader() { - return null; - } } diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/label_border_size_property.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/label_border_size_property.js new file mode 100644 index 0000000000000..e08c2875c310e --- /dev/null +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/label_border_size_property.js @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import _ from 'lodash'; +import { AbstractStyleProperty } from './style_property'; +import { DEFAULT_LABEL_SIZE, LABEL_BORDER_SIZES } from '../vector_style_defaults'; + +const SMALL_SIZE = 1 / 16; +const MEDIUM_SIZE = 1 / 8; +const LARGE_SIZE = 1 / 5; // halo of 1/4 is just a square. Use smaller ratio to preserve contour on letters + +function getWidthRatio(size) { + switch (size) { + case LABEL_BORDER_SIZES.LARGE: + return LARGE_SIZE; + case LABEL_BORDER_SIZES.MEDIUM: + return MEDIUM_SIZE; + default: + return SMALL_SIZE; + } +} + +export class LabelBorderSizeProperty extends AbstractStyleProperty { + constructor(options, styleName, labelSizeProperty) { + super(options, styleName); + this._labelSizeProperty = labelSizeProperty; + } + + syncLabelBorderSizeWithMb(mbLayerId, mbMap) { + const widthRatio = getWidthRatio(this.getOptions().size); + + if (this.getOptions().size === LABEL_BORDER_SIZES.NONE) { + mbMap.setPaintProperty(mbLayerId, 'text-halo-width', 0); + } else if (this._labelSizeProperty.isDynamic() && this._labelSizeProperty.isComplete()) { + const labelSizeExpression = this._labelSizeProperty.getMbSizeExpression(); + mbMap.setPaintProperty(mbLayerId, 'text-halo-width', [ + 'max', + ['*', labelSizeExpression, widthRatio], + 1, + ]); + } else { + const labelSize = _.get(this._labelSizeProperty.getOptions(), 'size', DEFAULT_LABEL_SIZE); + const labelBorderSize = Math.max(labelSize * widthRatio, 1); + mbMap.setPaintProperty(mbLayerId, 'text-halo-width', labelBorderSize); + } + } +} diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/static_color_property.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/static_color_property.js index 658eb6a164556..ebe2a322711fc 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/static_color_property.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/static_color_property.js @@ -39,4 +39,8 @@ export class StaticColorProperty extends StaticStyleProperty { mbMap.setPaintProperty(mbLayerId, 'text-color', this._options.color); mbMap.setPaintProperty(mbLayerId, 'text-opacity', alpha); } + + syncLabelBorderColorWithMb(mbLayerId, mbMap) { + mbMap.setPaintProperty(mbLayerId, 'text-halo-color', this._options.color); + } } diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/style_property.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/style_property.js index 8da76a775229e..52e1a46a18e94 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/style_property.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/properties/style_property.js @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { getVectorStyleLabel } from '../components/get_vector_style_label'; export class AbstractStyleProperty { constructor(options, styleName) { this._options = options; @@ -36,11 +37,15 @@ export class AbstractStyleProperty { return this._options || {}; } - renderLegendHeader() { + renderRangeLegendHeader() { return null; } renderLegendDetailRow() { return null; } + + getDisplayStyleName() { + return getVectorStyleLabel(this.getStyleName()); + } } diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/style_util.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/style_util.js index b8fc428a62a52..7bd60ea6502bc 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/style_util.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/style_util.js @@ -17,10 +17,6 @@ export function isOnlySingleFeatureType(featureType, supportedFeatures, hasFeatu return supportedFeatures[0] === featureType; } - if (!hasFeatureType) { - return false; - } - const featureTypes = Object.keys(hasFeatureType); return featureTypes.reduce((isOnlyTargetFeatureType, featureTypeKey) => { const hasFeature = hasFeatureType[featureTypeKey]; diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.js index dc688d1291518..30d1c5726ba48 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.js @@ -38,6 +38,8 @@ import { StaticOrientationProperty } from './properties/static_orientation_prope import { DynamicOrientationProperty } from './properties/dynamic_orientation_property'; import { StaticTextProperty } from './properties/static_text_property'; import { DynamicTextProperty } from './properties/dynamic_text_property'; +import { LabelBorderSizeProperty } from './properties/label_border_size_property'; +import { extractColorFromStyleProperty } from './components/legend/extract_color_from_style_property'; const POINTS = [GEO_JSON_TYPE.POINT, GEO_JSON_TYPE.MULTI_POINT]; const LINES = [GEO_JSON_TYPE.LINE_STRING, GEO_JSON_TYPE.MULTI_LINE_STRING]; @@ -99,6 +101,15 @@ export class VectorStyle extends AbstractStyle { this._descriptor.properties[VECTOR_STYLES.LABEL_COLOR], VECTOR_STYLES.LABEL_COLOR ); + this._labelBorderColorStyleProperty = this._makeColorProperty( + this._descriptor.properties[VECTOR_STYLES.LABEL_BORDER_COLOR], + VECTOR_STYLES.LABEL_BORDER_COLOR + ); + this._labelBorderSizeStyleProperty = new LabelBorderSizeProperty( + this._descriptor.properties[VECTOR_STYLES.LABEL_BORDER_SIZE].options, + VECTOR_STYLES.LABEL_BORDER_SIZE, + this._labelSizeStyleProperty + ); } _getAllStyleProperties() { @@ -111,6 +122,8 @@ export class VectorStyle extends AbstractStyle { this._labelStyleProperty, this._labelSizeStyleProperty, this._labelColorStyleProperty, + this._labelBorderColorStyleProperty, + this._labelBorderSizeStyleProperty, ]; } @@ -142,8 +155,8 @@ export class VectorStyle extends AbstractStyle { styleProperties={styleProperties} symbolDescriptor={this._descriptor.properties[VECTOR_STYLES.SYMBOL]} layer={layer} - loadIsPointsOnly={this._getIsPointsOnly} - loadIsLinesOnly={this._getIsLinesOnly} + isPointsOnly={this._getIsPointsOnly()} + isLinesOnly={this._getIsLinesOnly()} onIsTimeAwareChange={onIsTimeAwareChange} isTimeAware={this.isTimeAware()} showIsTimeAware={propertiesWithFieldMeta.length > 0} @@ -217,43 +230,57 @@ export class VectorStyle extends AbstractStyle { async pluckStyleMetaFromSourceDataRequest(sourceDataRequest) { const features = _.get(sourceDataRequest.getData(), 'features', []); - if (features.length === 0) { - return {}; - } - - const dynamicProperties = this.getDynamicPropertiesArray(); const supportedFeatures = await this._source.getSupportedShapeTypes(); - const isSingleFeatureType = supportedFeatures.length === 1; - if (dynamicProperties.length === 0 && isSingleFeatureType) { - // no meta data to pull from source data request. - return {}; - } - - let hasPoints = false; - let hasLines = false; - let hasPolygons = false; - for (let i = 0; i < features.length; i++) { - const feature = features[i]; - if (!hasPoints && POINTS.includes(feature.geometry.type)) { - hasPoints = true; - } - if (!hasLines && LINES.includes(feature.geometry.type)) { - hasLines = true; - } - if (!hasPolygons && POLYGONS.includes(feature.geometry.type)) { - hasPolygons = true; + const hasFeatureType = { + [VECTOR_SHAPE_TYPES.POINT]: false, + [VECTOR_SHAPE_TYPES.LINE]: false, + [VECTOR_SHAPE_TYPES.POLYGON]: false, + }; + if (supportedFeatures.length > 1) { + for (let i = 0; i < features.length; i++) { + const feature = features[i]; + if (!hasFeatureType[VECTOR_SHAPE_TYPES.POINT] && POINTS.includes(feature.geometry.type)) { + hasFeatureType[VECTOR_SHAPE_TYPES.POINT] = true; + } + if (!hasFeatureType[VECTOR_SHAPE_TYPES.LINE] && LINES.includes(feature.geometry.type)) { + hasFeatureType[VECTOR_SHAPE_TYPES.LINE] = true; + } + if ( + !hasFeatureType[VECTOR_SHAPE_TYPES.POLYGON] && + POLYGONS.includes(feature.geometry.type) + ) { + hasFeatureType[VECTOR_SHAPE_TYPES.POLYGON] = true; + } } } const featuresMeta = { - hasFeatureType: { - [VECTOR_SHAPE_TYPES.POINT]: hasPoints, - [VECTOR_SHAPE_TYPES.LINE]: hasLines, - [VECTOR_SHAPE_TYPES.POLYGON]: hasPolygons, + geometryTypes: { + isPointsOnly: isOnlySingleFeatureType( + VECTOR_SHAPE_TYPES.POINT, + supportedFeatures, + hasFeatureType + ), + isLinesOnly: isOnlySingleFeatureType( + VECTOR_SHAPE_TYPES.LINE, + supportedFeatures, + hasFeatureType + ), + isPolygonsOnly: isOnlySingleFeatureType( + VECTOR_SHAPE_TYPES.POLYGON, + supportedFeatures, + hasFeatureType + ), }, }; + const dynamicProperties = this.getDynamicPropertiesArray(); + if (dynamicProperties.length === 0 || features.length === 0) { + // no additional meta data to pull from source data request. + return featuresMeta; + } + dynamicProperties.forEach(dynamicProperty => { const styleMeta = dynamicProperty.pluckStyleMetaFromFeatures(features); if (styleMeta) { @@ -290,24 +317,16 @@ export class VectorStyle extends AbstractStyle { ); } - _isOnlySingleFeatureType = async featureType => { - return isOnlySingleFeatureType( - featureType, - await this._source.getSupportedShapeTypes(), - this._getStyleMeta().hasFeatureType - ); - }; - - _getIsPointsOnly = async () => { - return this._isOnlySingleFeatureType(VECTOR_SHAPE_TYPES.POINT); + _getIsPointsOnly = () => { + return _.get(this._getStyleMeta(), 'geometryTypes.isPointsOnly', false); }; - _getIsLinesOnly = async () => { - return this._isOnlySingleFeatureType(VECTOR_SHAPE_TYPES.LINE); + _getIsLinesOnly = () => { + return _.get(this._getStyleMeta(), 'geometryTypes.isLinesOnly', false); }; - _getIsPolygonsOnly = async () => { - return this._isOnlySingleFeatureType(VECTOR_SHAPE_TYPES.POLYGON); + _getIsPolygonsOnly = () => { + return _.get(this._getStyleMeta(), 'geometryTypes.isPolygonsOnly', false); }; _getDynamicPropertyByFieldName(fieldName) { @@ -386,37 +405,50 @@ export class VectorStyle extends AbstractStyle { return _.get(this._descriptor, '__styleMeta', {}); }; - getIcon = () => { - const styles = this.getRawProperties(); - const symbolId = this.arePointsSymbolizedAsCircles() + _getSymbolId() { + return this.arePointsSymbolizedAsCircles() ? undefined : this._descriptor.properties.symbol.options.symbolId; + } + + getIcon = () => { + const isLinesOnly = this._getIsLinesOnly(); + const strokeColor = isLinesOnly + ? extractColorFromStyleProperty(this._descriptor.properties[VECTOR_STYLES.LINE_COLOR], 'grey') + : extractColorFromStyleProperty( + this._descriptor.properties[VECTOR_STYLES.LINE_COLOR], + 'none' + ); + const fillColor = isLinesOnly + ? null + : extractColorFromStyleProperty( + this._descriptor.properties[VECTOR_STYLES.FILL_COLOR], + 'grey' + ); + return ( ); }; - _getLegendDetailStyleProperties = async () => { - const isLinesOnly = await this._getIsLinesOnly(); - const isPolygonsOnly = await this._getIsPolygonsOnly(); - + _getLegendDetailStyleProperties = () => { return this.getDynamicPropertiesArray().filter(styleProperty => { const styleName = styleProperty.getStyleName(); if ([VECTOR_STYLES.ICON_ORIENTATION, VECTOR_STYLES.LABEL_TEXT].includes(styleName)) { return false; } - if (isLinesOnly) { + if (this._getIsLinesOnly()) { return LINE_STYLES.includes(styleName); } - if (isPolygonsOnly) { + if (this._getIsPolygonsOnly()) { return POLYGON_STYLES.includes(styleName); } @@ -425,13 +457,17 @@ export class VectorStyle extends AbstractStyle { }; async hasLegendDetails() { - const styles = await this._getLegendDetailStyleProperties(); - return styles.length > 0; + return this._getLegendDetailStyleProperties().length > 0; } renderLegendDetails() { return ( - + ); } @@ -513,6 +549,8 @@ export class VectorStyle extends AbstractStyle { this._labelStyleProperty.syncTextFieldWithMb(textLayerId, mbMap); this._labelColorStyleProperty.syncLabelColorWithMb(textLayerId, mbMap, alpha); this._labelSizeStyleProperty.syncLabelSizeWithMb(textLayerId, mbMap); + this._labelBorderSizeStyleProperty.syncLabelBorderSizeWithMb(textLayerId, mbMap); + this._labelBorderColorStyleProperty.syncLabelBorderColorWithMb(textLayerId, mbMap); } setMBSymbolPropertiesForPoints({ mbMap, symbolLayerId, alpha }) { @@ -613,7 +651,13 @@ export class VectorStyle extends AbstractStyle { return new StaticTextProperty(descriptor.options, VECTOR_STYLES.LABEL_TEXT); } else if (descriptor.type === DynamicStyleProperty.type) { const field = this._makeField(descriptor.options.field); - return new DynamicTextProperty(descriptor.options, VECTOR_STYLES.LABEL_TEXT, field); + return new DynamicTextProperty( + descriptor.options, + VECTOR_STYLES.LABEL_TEXT, + field, + this._getFieldMeta, + this._getFieldFormatter + ); } else { throw new Error(`${descriptor} not implemented`); } diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.test.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.test.js index aa0badd5583d5..c250d83720580 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.test.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style.test.js @@ -102,6 +102,17 @@ describe('getDescriptorWithMissingStylePropsRemoved', () => { }, type: 'STATIC', }, + labelBorderColor: { + options: { + color: '#FFFFFF', + }, + type: 'STATIC', + }, + labelBorderSize: { + options: { + size: 'SMALL', + }, + }, labelColor: { options: { color: '#000000', @@ -159,11 +170,9 @@ describe('pluckStyleMetaFromSourceDataRequest', () => { const vectorStyle = new VectorStyle({}, new MockSource()); const featuresMeta = await vectorStyle.pluckStyleMetaFromSourceDataRequest(sourceDataRequest); - expect(featuresMeta.hasFeatureType).toEqual({ - LINE: false, - POINT: true, - POLYGON: false, - }); + expect(featuresMeta.geometryTypes.isPointsOnly).toBe(true); + expect(featuresMeta.geometryTypes.isLinesOnly).toBe(false); + expect(featuresMeta.geometryTypes.isPolygonsOnly).toBe(false); }); it('Should identify when feature collection only contains lines', async () => { @@ -189,11 +198,9 @@ describe('pluckStyleMetaFromSourceDataRequest', () => { const vectorStyle = new VectorStyle({}, new MockSource()); const featuresMeta = await vectorStyle.pluckStyleMetaFromSourceDataRequest(sourceDataRequest); - expect(featuresMeta.hasFeatureType).toEqual({ - LINE: true, - POINT: false, - POLYGON: false, - }); + expect(featuresMeta.geometryTypes.isPointsOnly).toBe(false); + expect(featuresMeta.geometryTypes.isLinesOnly).toBe(true); + expect(featuresMeta.geometryTypes.isPolygonsOnly).toBe(false); }); }); @@ -241,11 +248,9 @@ describe('pluckStyleMetaFromSourceDataRequest', () => { ); const featuresMeta = await vectorStyle.pluckStyleMetaFromSourceDataRequest(sourceDataRequest); - expect(featuresMeta.hasFeatureType).toEqual({ - LINE: false, - POINT: true, - POLYGON: false, - }); + expect(featuresMeta.geometryTypes.isPointsOnly).toBe(true); + expect(featuresMeta.geometryTypes.isLinesOnly).toBe(false); + expect(featuresMeta.geometryTypes.isPolygonsOnly).toBe(false); }); it('Should extract scaled field range', async () => { @@ -275,88 +280,3 @@ describe('pluckStyleMetaFromSourceDataRequest', () => { }); }); }); - -describe('checkIfOnlyFeatureType', () => { - describe('source supports single feature type', () => { - it('isPointsOnly should be true when source feature type only supports points', async () => { - const vectorStyle = new VectorStyle( - {}, - new MockSource({ - supportedShapeTypes: [VECTOR_SHAPE_TYPES.POINT], - }) - ); - const isPointsOnly = await vectorStyle._getIsPointsOnly(); - expect(isPointsOnly).toBe(true); - }); - - it('isLineOnly should be false when source feature type only supports points', async () => { - const vectorStyle = new VectorStyle( - {}, - new MockSource({ - supportedShapeTypes: [VECTOR_SHAPE_TYPES.POINT], - }) - ); - const isLineOnly = await vectorStyle._getIsLinesOnly(); - expect(isLineOnly).toBe(false); - }); - }); - - describe('source supports multiple feature types', () => { - it('isPointsOnly should be true when data contains just points', async () => { - const vectorStyle = new VectorStyle( - { - __styleMeta: { - hasFeatureType: { - POINT: true, - LINE: false, - POLYGON: false, - }, - }, - }, - new MockSource({ - supportedShapeTypes: Object.values(VECTOR_SHAPE_TYPES), - }) - ); - const isPointsOnly = await vectorStyle._getIsPointsOnly(); - expect(isPointsOnly).toBe(true); - }); - - it('isPointsOnly should be false when data contains just lines', async () => { - const vectorStyle = new VectorStyle( - { - __styleMeta: { - hasFeatureType: { - POINT: false, - LINE: true, - POLYGON: false, - }, - }, - }, - new MockSource({ - supportedShapeTypes: Object.values(VECTOR_SHAPE_TYPES), - }) - ); - const isPointsOnly = await vectorStyle._getIsPointsOnly(); - expect(isPointsOnly).toBe(false); - }); - - it('isPointsOnly should be false when data contains points, lines, and polygons', async () => { - const vectorStyle = new VectorStyle( - { - __styleMeta: { - hasFeatureType: { - POINT: true, - LINE: true, - POLYGON: true, - }, - }, - }, - new MockSource({ - supportedShapeTypes: Object.values(VECTOR_SHAPE_TYPES), - }) - ); - const isPointsOnly = await vectorStyle._getIsPointsOnly(); - expect(isPointsOnly).toBe(false); - }); - }); -}); diff --git a/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style_defaults.js b/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style_defaults.js index 4bae90c3165f2..3631613e7907c 100644 --- a/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style_defaults.js +++ b/x-pack/legacy/plugins/maps/public/layers/styles/vector/vector_style_defaults.js @@ -16,6 +16,14 @@ export const MAX_SIZE = 64; export const DEFAULT_MIN_SIZE = 4; export const DEFAULT_MAX_SIZE = 32; export const DEFAULT_SIGMA = 3; +export const DEFAULT_LABEL_SIZE = 14; + +export const LABEL_BORDER_SIZES = { + NONE: 'NONE', + SMALL: 'SMALL', + MEDIUM: 'MEDIUM', + LARGE: 'LARGE', +}; export const VECTOR_STYLES = { SYMBOL: 'symbol', @@ -27,6 +35,8 @@ export const VECTOR_STYLES = { LABEL_TEXT: 'labelText', LABEL_COLOR: 'labelColor', LABEL_SIZE: 'labelSize', + LABEL_BORDER_COLOR: 'labelBorderColor', + LABEL_BORDER_SIZE: 'labelBorderSize', }; export const LINE_STYLES = [VECTOR_STYLES.LINE_COLOR, VECTOR_STYLES.LINE_WIDTH]; @@ -45,6 +55,11 @@ export function getDefaultProperties(mapColors = []) { symbolId: DEFAULT_ICON, }, }, + [VECTOR_STYLES.LABEL_BORDER_SIZE]: { + options: { + size: LABEL_BORDER_SIZES.SMALL, + }, + }, }; } @@ -103,7 +118,13 @@ export function getDefaultStaticProperties(mapColors = []) { [VECTOR_STYLES.LABEL_SIZE]: { type: VectorStyle.STYLE_TYPE.STATIC, options: { - size: 14, + size: DEFAULT_LABEL_SIZE, + }, + }, + [VECTOR_STYLES.LABEL_BORDER_COLOR]: { + type: VectorStyle.STYLE_TYPE.STATIC, + options: { + color: isDarkMode ? '#000000' : '#FFFFFF', }, }, }; @@ -158,7 +179,7 @@ export function getDefaultDynamicProperties() { }, }, [VECTOR_STYLES.ICON_ORIENTATION]: { - type: VectorStyle.STYLE_TYPE.STATIC, + type: VectorStyle.STYLE_TYPE.DYNAMIC, options: { field: undefined, fieldMetaOptions: { @@ -168,13 +189,13 @@ export function getDefaultDynamicProperties() { }, }, [VECTOR_STYLES.LABEL_TEXT]: { - type: VectorStyle.STYLE_TYPE.STATIC, + type: VectorStyle.STYLE_TYPE.DYNAMIC, options: { field: undefined, }, }, [VECTOR_STYLES.LABEL_COLOR]: { - type: VectorStyle.STYLE_TYPE.STATIC, + type: VectorStyle.STYLE_TYPE.DYNAMIC, options: { color: COLOR_GRADIENTS[0].value, field: undefined, @@ -185,7 +206,7 @@ export function getDefaultDynamicProperties() { }, }, [VECTOR_STYLES.LABEL_SIZE]: { - type: VectorStyle.STYLE_TYPE.STATIC, + type: VectorStyle.STYLE_TYPE.DYNAMIC, options: { minSize: DEFAULT_MIN_SIZE, maxSize: DEFAULT_MAX_SIZE, @@ -196,5 +217,16 @@ export function getDefaultDynamicProperties() { }, }, }, + [VECTOR_STYLES.LABEL_BORDER_COLOR]: { + type: VectorStyle.STYLE_TYPE.DYNAMIC, + options: { + color: COLOR_GRADIENTS[0].value, + field: undefined, + fieldMetaOptions: { + isEnabled: true, + sigma: DEFAULT_SIGMA, + }, + }, + }, }; } diff --git a/x-pack/legacy/plugins/maps/public/layers/util/can_skip_fetch.js b/x-pack/legacy/plugins/maps/public/layers/util/can_skip_fetch.js index d9182be56a75f..7abfee1b184f0 100644 --- a/x-pack/legacy/plugins/maps/public/layers/util/can_skip_fetch.js +++ b/x-pack/legacy/plugins/maps/public/layers/util/can_skip_fetch.js @@ -168,5 +168,5 @@ export function canSkipFormattersUpdate({ prevDataRequest, nextMeta }) { return false; } - return !_.isEqual(prevMeta.fieldNames, nextMeta.fieldNames); + return _.isEqual(prevMeta.fieldNames, nextMeta.fieldNames); } diff --git a/x-pack/legacy/plugins/maps/public/layers/vector_tile_layer.js b/x-pack/legacy/plugins/maps/public/layers/vector_tile_layer.js index c1b590f56ae52..b09ccdc3af8ba 100644 --- a/x-pack/legacy/plugins/maps/public/layers/vector_tile_layer.js +++ b/x-pack/legacy/plugins/maps/public/layers/vector_tile_layer.js @@ -31,32 +31,58 @@ export class VectorTileLayer extends TileLayer { return tileLayerDescriptor; } + _canSkipSync({ prevDataRequest, nextMeta }) { + if (!prevDataRequest) { + return false; + } + const prevMeta = prevDataRequest.getMeta(); + if (!prevMeta) { + return false; + } + + return prevMeta.tileLayerId === nextMeta.tileLayerId; + } + async syncData({ startLoading, stopLoading, onLoadError, dataFilters }) { if (!this.isVisible() || !this.showAtZoomLevel(dataFilters.zoom)) { return; } - const sourceDataRequest = this.getSourceDataRequest(); - if (sourceDataRequest) { - //data is immmutable + + const nextMeta = { tileLayerId: this._source.getTileLayerId() }; + const canSkipSync = this._canSkipSync({ + prevDataRequest: this.getSourceDataRequest(), + nextMeta, + }); + if (canSkipSync) { return; } + const requestToken = Symbol(`layer-source-refresh:${this.getId()} - source`); - startLoading(SOURCE_DATA_ID_ORIGIN, requestToken, dataFilters); try { + startLoading(SOURCE_DATA_ID_ORIGIN, requestToken, dataFilters); const styleAndSprites = await this._source.getVectorStyleSheetAndSpriteMeta(isRetina()); const spriteSheetImageData = await loadSpriteSheetImageData(styleAndSprites.spriteMeta.png); const data = { ...styleAndSprites, spriteSheetImageData, }; - stopLoading(SOURCE_DATA_ID_ORIGIN, requestToken, data, {}); + stopLoading(SOURCE_DATA_ID_ORIGIN, requestToken, data, nextMeta); } catch (error) { onLoadError(SOURCE_DATA_ID_ORIGIN, requestToken, error.message); } } _generateMbId(name) { - return this.getId() + '_' + name; + return `${this.getId()}_${name}`; + } + + _generateMbSourceIdPrefix() { + const DELIMITTER = '___'; + return `${this.getId()}${DELIMITTER}${this._source.getTileLayerId()}${DELIMITTER}`; + } + + _generateMbSourceId(name) { + return `${this._generateMbSourceIdPrefix()}${name}`; } _getVectorStyle() { @@ -103,19 +129,15 @@ export class VectorTileLayer extends TileLayer { return []; } const sourceIds = Object.keys(vectorStyle.sources); - return sourceIds.map(sourceId => this._generateMbId(sourceId)); + return sourceIds.map(sourceId => this._generateMbSourceId(sourceId)); } ownsMbLayerId(mbLayerId) { - //todo optimize: do not create temp array - const mbLayerIds = this.getMbLayerIds(); - return mbLayerIds.indexOf(mbLayerId) >= 0; + return mbLayerId.startsWith(this.getId()); } ownsMbSourceId(mbSourceId) { - //todo optimize: do not create temp array - const mbSourceIds = this.getMbSourceIds(); - return mbSourceIds.indexOf(mbSourceId) >= 0; + return mbSourceId.startsWith(this.getId()); } _makeNamespacedImageId(imageId) { @@ -123,19 +145,43 @@ export class VectorTileLayer extends TileLayer { return prefix + imageId; } + _requiresPrevSourceCleanup(mbMap) { + const sourceIdPrefix = this._generateMbSourceIdPrefix(); + const mbStyle = mbMap.getStyle(); + return Object.keys(mbStyle.sources).some(mbSourceId => { + const doesMbSourceBelongToLayer = this.ownsMbSourceId(mbSourceId); + const doesMbSourceBelongToSource = mbSourceId.startsWith(sourceIdPrefix); + return doesMbSourceBelongToLayer && !doesMbSourceBelongToSource; + }); + } + syncLayerWithMB(mbMap) { const vectorStyle = this._getVectorStyle(); if (!vectorStyle) { return; } + if (this._requiresPrevSourceCleanup(mbMap)) { + const mbStyle = mbMap.getStyle(); + mbStyle.layers.forEach(mbLayer => { + if (this.ownsMbLayerId(mbLayer.id)) { + mbMap.removeLayer(mbLayer.id); + } + }); + Object.keys(mbStyle.sources).some(mbSourceId => { + if (this.ownsMbSourceId(mbSourceId)) { + mbMap.removeSource(mbSourceId); + } + }); + } + let initialBootstrapCompleted = false; const sourceIds = Object.keys(vectorStyle.sources); sourceIds.forEach(sourceId => { if (initialBootstrapCompleted) { return; } - const mbSourceId = this._generateMbId(sourceId); + const mbSourceId = this._generateMbSourceId(sourceId); const mbSource = mbMap.getSource(mbSourceId); if (mbSource) { //if a single source is present, the layer already has bootstrapped with the mbMap @@ -174,7 +220,7 @@ export class VectorTileLayer extends TileLayer { } const newLayerObject = { ...layer, - source: this._generateMbId(layer.source), + source: this._generateMbSourceId(layer.source), id: mbLayerId, }; diff --git a/x-pack/legacy/plugins/maps/public/reducers/map.js b/x-pack/legacy/plugins/maps/public/reducers/map.js index 7dd60f013cefd..ac409c685c71a 100644 --- a/x-pack/legacy/plugins/maps/public/reducers/map.js +++ b/x-pack/legacy/plugins/maps/public/reducers/map.js @@ -16,7 +16,7 @@ import { ADD_WAITING_FOR_MAP_READY_LAYER, CLEAR_WAITING_FOR_MAP_READY_LAYER_LIST, REMOVE_LAYER, - TOGGLE_LAYER_VISIBLE, + SET_LAYER_VISIBILITY, MAP_EXTENT_CHANGED, MAP_READY, MAP_DESTROYED, @@ -46,6 +46,7 @@ import { HIDE_TOOLBAR_OVERLAY, HIDE_LAYER_CONTROL, HIDE_VIEW_CONTROL, + SET_WAITING_FOR_READY_HIDDEN_LAYERS, } from '../actions/map_actions'; import { copyPersistentState, TRACKED_LAYER_DESCRIPTOR } from './util'; @@ -307,8 +308,8 @@ export function map(state = INITIAL_STATE, action) { ...state, waitingForMapReadyLayerList: [], }; - case TOGGLE_LAYER_VISIBLE: - return updateLayerInList(state, action.layerId, 'visible'); + case SET_LAYER_VISIBILITY: + return updateLayerInList(state, action.layerId, 'visible', action.visibility); case UPDATE_LAYER_STYLE: const styleLayerId = action.layerId; return updateLayerInList(state, styleLayerId, 'style', { ...action.style }); @@ -376,6 +377,14 @@ export function map(state = INITIAL_STATE, action) { hideViewControl: action.hideViewControl, }, }; + case SET_WAITING_FOR_READY_HIDDEN_LAYERS: + return { + ...state, + waitingForMapReadyLayerList: state.waitingForMapReadyLayerList.map(layer => ({ + ...layer, + visible: !action.hiddenLayerIds.includes(layer.id), + })), + }; default: return state; } diff --git a/x-pack/legacy/plugins/maps/public/selectors/map_selectors.js b/x-pack/legacy/plugins/maps/public/selectors/map_selectors.js index 3d8e6f97ef077..4b3d1355e4264 100644 --- a/x-pack/legacy/plugins/maps/public/selectors/map_selectors.js +++ b/x-pack/legacy/plugins/maps/public/selectors/map_selectors.js @@ -150,6 +150,10 @@ export const getLayerList = createSelector( } ); +export const getHiddenLayerIds = createSelector(getLayerListRaw, layers => + layers.filter(layer => !layer.visible).map(layer => layer.id) +); + export const getSelectedLayer = createSelector( getSelectedLayerId, getLayerList, diff --git a/x-pack/legacy/plugins/maps/server/maps_telemetry/maps_telemetry.js b/x-pack/legacy/plugins/maps/server/maps_telemetry/maps_telemetry.js index 6d078ae35ef85..848c964f4b6d4 100644 --- a/x-pack/legacy/plugins/maps/server/maps_telemetry/maps_telemetry.js +++ b/x-pack/legacy/plugins/maps/server/maps_telemetry/maps_telemetry.js @@ -5,10 +5,16 @@ */ import _ from 'lodash'; -import { EMS_FILE, ES_GEO_FIELD_TYPE, MAP_SAVED_OBJECT_TYPE } from '../../common/constants'; +import { + EMS_FILE, + ES_GEO_FIELD_TYPE, + MAP_SAVED_OBJECT_TYPE, + TELEMETRY_TYPE, +} from '../../common/constants'; -function getSavedObjectsClient(server, callCluster) { +function getSavedObjectsClient(server) { const { SavedObjectsClient, getSavedObjectsRepository } = server.savedObjects; + const callCluster = server.plugins.elasticsearch.getCluster('admin').callWithInternalUser; const internalRepository = getSavedObjectsRepository(callCluster); return new SavedObjectsClient(internalRepository); } @@ -79,7 +85,7 @@ export function buildMapsTelemetry({ mapSavedObjects, indexPatternSavedObjects, // Total count of maps mapsTotalCount: mapsCount, // Time of capture - timeCaptured: new Date(), + timeCaptured: new Date().toISOString(), attributesPerMap: { // Count of data sources per map dataSourcesCount: { @@ -115,16 +121,16 @@ async function getIndexPatternSavedObjects(savedObjectsClient) { return _.get(indexPatternSavedObjects, 'saved_objects', []); } -export async function getMapsTelemetry(server, callCluster) { - const savedObjectsClient = getSavedObjectsClient(server, callCluster); +export async function getMapsTelemetry(server) { + const savedObjectsClient = getSavedObjectsClient(server); const mapSavedObjects = await getMapSavedObjects(savedObjectsClient); const indexPatternSavedObjects = await getIndexPatternSavedObjects(savedObjectsClient); const settings = { showMapVisualizationTypes: server.config().get('xpack.maps.showMapVisualizationTypes'), }; const mapsTelemetry = buildMapsTelemetry({ mapSavedObjects, indexPatternSavedObjects, settings }); - return await savedObjectsClient.create('maps-telemetry', mapsTelemetry, { - id: 'maps-telemetry', + return await savedObjectsClient.create(TELEMETRY_TYPE, mapsTelemetry, { + id: TELEMETRY_TYPE, overwrite: true, }); } diff --git a/x-pack/legacy/plugins/maps/server/maps_telemetry/maps_usage_collector.js b/x-pack/legacy/plugins/maps/server/maps_telemetry/maps_usage_collector.js index d1011736e77f8..9c575e66f7556 100644 --- a/x-pack/legacy/plugins/maps/server/maps_telemetry/maps_usage_collector.js +++ b/x-pack/legacy/plugins/maps/server/maps_telemetry/maps_usage_collector.js @@ -4,85 +4,19 @@ * you may not use this file except in compliance with the Elastic License. */ -import _ from 'lodash'; -import { TASK_ID, scheduleTask, registerMapsTelemetryTask } from './telemetry_task'; +import { getMapsTelemetry } from './maps_telemetry'; +import { TELEMETRY_TYPE } from '../../common/constants'; export function initTelemetryCollection(usageCollection, server) { - registerMapsTelemetryTask(server); - scheduleTask(server); - registerMapsUsageCollector(usageCollection, server); -} - -async function isTaskManagerReady(server) { - const result = await fetch(server); - return result !== null; -} - -async function fetch(server) { - let docs; - const taskManager = server.plugins.task_manager; - - if (!taskManager) { - return null; - } - - try { - ({ docs } = await taskManager.fetch({ - query: { - bool: { - filter: { - term: { - _id: `task:${TASK_ID}`, - }, - }, - }, - }, - })); - } catch (err) { - const errMessage = err && err.message ? err.message : err.toString(); - /* - * The usage service WILL to try to fetch from this collector before the task manager has been initialized, because the task manager - * has to wait for all plugins to initialize first. - * It's fine to ignore it as next time around it will be initialized (or it will throw a different type of error) - */ - if (errMessage.indexOf('NotInitialized') >= 0) { - return null; - } else { - throw err; - } + if (!usageCollection) { + return; } - return docs; -} - -export function buildCollectorObj(server) { - let isCollectorReady = false; - async function determineIfTaskManagerIsReady() { - let isReady = false; - try { - isReady = await isTaskManagerReady(server); - } catch (err) {} // eslint-disable-line - - if (isReady) { - isCollectorReady = true; - } else { - setTimeout(determineIfTaskManagerIsReady, 500); - } - } - determineIfTaskManagerIsReady(); - - return { - type: 'maps', - isReady: () => isCollectorReady, - fetch: async () => { - const docs = await fetch(server); - return _.get(docs, '[0].state.stats'); - }, - }; -} + const mapsUsageCollector = usageCollection.makeUsageCollector({ + type: TELEMETRY_TYPE, + isReady: () => true, + fetch: async () => await getMapsTelemetry(server), + }); -export function registerMapsUsageCollector(usageCollection, server) { - const collectorObj = buildCollectorObj(server); - const mapsUsageCollector = usageCollection.makeUsageCollector(collectorObj); usageCollection.registerCollector(mapsUsageCollector); } diff --git a/x-pack/legacy/plugins/maps/server/maps_telemetry/maps_usage_collector.test.js b/x-pack/legacy/plugins/maps/server/maps_telemetry/maps_usage_collector.test.js index 727d60b5088aa..c5a3fca89b560 100644 --- a/x-pack/legacy/plugins/maps/server/maps_telemetry/maps_usage_collector.test.js +++ b/x-pack/legacy/plugins/maps/server/maps_telemetry/maps_usage_collector.test.js @@ -4,60 +4,32 @@ * you may not use this file except in compliance with the Elastic License. */ -import sinon from 'sinon'; -import { getMockCallWithInternal, getMockKbnServer, getMockTaskFetch } from '../test_utils'; -import { buildCollectorObj } from './maps_usage_collector'; +import { initTelemetryCollection } from './maps_usage_collector'; describe('buildCollectorObj#fetch', () => { - let mockKbnServer; + let makeUsageCollectorStub; + let registerStub; + let usageCollection; beforeEach(() => { - mockKbnServer = getMockKbnServer(); + makeUsageCollectorStub = jest.fn(); + registerStub = jest.fn(); + usageCollection = { + makeUsageCollector: makeUsageCollectorStub, + registerCollector: registerStub, + }; }); - test('can return empty stats', async () => { - const { type, fetch } = buildCollectorObj(mockKbnServer); - expect(type).toBe('maps'); - const fetchResult = await fetch(); - expect(fetchResult).toEqual({}); - }); - - test('provides known stats', async () => { - const mockTaskFetch = getMockTaskFetch([ - { - state: { - runs: 2, - stats: { wombat_sightings: { total: 712, max: 84, min: 7, avg: 63 } }, - }, - }, - ]); - mockKbnServer = getMockKbnServer(getMockCallWithInternal(), mockTaskFetch); - - const { type, fetch } = buildCollectorObj(mockKbnServer); - expect(type).toBe('maps'); - const fetchResult = await fetch(); - expect(fetchResult).toEqual({ wombat_sightings: { total: 712, max: 84, min: 7, avg: 63 } }); - }); - - describe('Error handling', () => { - test('Silently handles Task Manager NotInitialized', async () => { - const mockTaskFetch = sinon.stub(); - mockTaskFetch.rejects( - new Error('NotInitialized taskManager is still waiting for plugins to load') - ); - mockKbnServer = getMockKbnServer(getMockCallWithInternal(), mockTaskFetch); - - const { fetch } = buildCollectorObj(mockKbnServer); - await expect(fetch()).resolves.toBe(undefined); - }); - // In real life, the CollectorSet calls fetch and handles errors - test('defers the errors', async () => { - const mockTaskFetch = sinon.stub(); - mockTaskFetch.rejects(new Error('Sad violin')); - mockKbnServer = getMockKbnServer(getMockCallWithInternal(), mockTaskFetch); + test('makes and registers maps usage collector', async () => { + const serverPlaceholder = {}; + initTelemetryCollection(usageCollection, serverPlaceholder); - const { fetch } = buildCollectorObj(mockKbnServer); - await expect(fetch()).rejects.toMatchObject(new Error('Sad violin')); + expect(registerStub).toHaveBeenCalledTimes(1); + expect(makeUsageCollectorStub).toHaveBeenCalledTimes(1); + expect(makeUsageCollectorStub).toHaveBeenCalledWith({ + type: expect.any(String), + isReady: expect.any(Function), + fetch: expect.any(Function), }); }); }); diff --git a/x-pack/legacy/plugins/maps/server/maps_telemetry/telemetry_task.js b/x-pack/legacy/plugins/maps/server/maps_telemetry/telemetry_task.js deleted file mode 100644 index db5df358abc39..0000000000000 --- a/x-pack/legacy/plugins/maps/server/maps_telemetry/telemetry_task.js +++ /dev/null @@ -1,125 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { getMapsTelemetry } from './maps_telemetry'; - -const TELEMETRY_TASK_TYPE = 'maps_telemetry'; - -export const TASK_ID = `Maps-${TELEMETRY_TASK_TYPE}`; - -export function scheduleTask(server) { - const taskManager = server.plugins.task_manager; - - if (!taskManager) { - server.log(['debug', 'telemetry'], `Task manager is not available`); - return; - } - - const { kbnServer } = server.plugins.xpack_main.status.plugin; - - kbnServer.afterPluginsInit(() => { - // The code block below can't await directly within "afterPluginsInit" - // callback due to circular dependency. The server isn't "ready" until - // this code block finishes. Migrations wait for server to be ready before - // executing. Saved objects repository waits for migrations to finish before - // finishing the request. To avoid this, we'll await within a separate - // function block. - (async () => { - try { - await taskManager.ensureScheduled({ - id: TASK_ID, - taskType: TELEMETRY_TASK_TYPE, - state: { stats: {}, runs: 0 }, - }); - } catch (e) { - server.log(['warning', 'maps'], `Error scheduling telemetry task, received ${e.message}`); - } - })(); - }); -} - -export function registerMapsTelemetryTask(server) { - const taskManager = server.plugins.task_manager; - - if (!taskManager) { - server.log(['debug', 'telemetry'], `Task manager is not available`); - return; - } - - taskManager.registerTaskDefinitions({ - [TELEMETRY_TASK_TYPE]: { - title: 'Maps telemetry fetch task', - type: TELEMETRY_TASK_TYPE, - timeout: '1m', - createTaskRunner: telemetryTaskRunner(server), - }, - }); -} - -export function telemetryTaskRunner(server) { - return ({ taskInstance }) => { - const { state } = taskInstance; - const prevState = state; - - const callCluster = server.plugins.elasticsearch.getCluster('admin').callWithInternalUser; - - let mapsTelemetryTask; - - return { - async run({ taskCanceled = false } = {}) { - try { - mapsTelemetryTask = makeCancelable(getMapsTelemetry(server, callCluster), taskCanceled); - } catch (err) { - server.log(['warning'], `Error loading maps telemetry: ${err}`); - } finally { - return mapsTelemetryTask.promise - .then((mapsTelemetry = {}) => { - return { - state: { - runs: state.runs || 0 + 1, - stats: mapsTelemetry.attributes || prevState.stats || {}, - }, - runAt: getNextMidnight(), - }; - }) - .catch(errMsg => - server.log(['warning'], `Error executing maps telemetry task: ${errMsg}`) - ); - } - }, - async cancel() { - if (mapsTelemetryTask) { - mapsTelemetryTask.cancel(); - } else { - server.log(['warning'], `Can not cancel "mapsTelemetryTask", it has not been defined`); - } - }, - }; - }; -} - -function makeCancelable(promise, isCanceled) { - const logMsg = 'Maps telemetry task has been cancelled'; - const wrappedPromise = new Promise((resolve, reject) => { - promise - .then(val => (isCanceled ? reject(logMsg) : resolve(val))) - .catch(err => (isCanceled ? reject(logMsg) : reject(err.message))); - }); - - return { - promise: wrappedPromise, - cancel() { - isCanceled = true; - }, - }; -} - -function getNextMidnight() { - const nextMidnight = new Date(); - nextMidnight.setHours(0, 0, 0, 0); - nextMidnight.setDate(nextMidnight.getDate() + 1); - return nextMidnight; -} diff --git a/x-pack/legacy/plugins/maps/server/maps_telemetry/telemetry_task.test.js b/x-pack/legacy/plugins/maps/server/maps_telemetry/telemetry_task.test.js deleted file mode 100644 index ad23ed1634204..0000000000000 --- a/x-pack/legacy/plugins/maps/server/maps_telemetry/telemetry_task.test.js +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { getMockKbnServer, getMockTaskInstance } from '../test_utils'; -import { telemetryTaskRunner } from './telemetry_task'; -import * as mapsTelemetry from './maps_telemetry'; -jest.mock('./maps_telemetry'); - -const expectedAttributes = { - expect: 'values', - toBe: 'populated', -}; - -const generateTelemetry = ({ includeAttributes = true } = {}) => { - mapsTelemetry.getMapsTelemetry = async () => ({ // eslint-disable-line - attributes: includeAttributes ? expectedAttributes : {}, - }); -}; - -describe('telemetryTaskRunner', () => { - let mockTaskInstance; - let mockKbnServer; - let taskRunner; - - beforeEach(() => { - mockTaskInstance = getMockTaskInstance(); - mockKbnServer = getMockKbnServer(); - taskRunner = telemetryTaskRunner(mockKbnServer)({ taskInstance: mockTaskInstance }); - }); - - test('returns empty stats as default', async () => { - generateTelemetry({ includeAttributes: false }); - - const runResult = await taskRunner.run(); - - expect(runResult).toMatchObject({ - state: { - runs: 1, - stats: {}, - }, - }); - }); - - // Return stats when run normally - test('returns stats normally', async () => { - generateTelemetry(); - - const runResult = await taskRunner.run(); - - expect(runResult).toMatchObject({ - state: { - runs: 1, - stats: expectedAttributes, - }, - }); - }); - - test('cancels when cancel flag set to "true", returns undefined', async () => { - generateTelemetry(); - - const runResult = await taskRunner.run({ taskCanceled: true }); - - expect(runResult).toBe(undefined); - }); -}); diff --git a/x-pack/legacy/plugins/maps/server/test_utils/index.js b/x-pack/legacy/plugins/maps/server/test_utils/index.js index 944d65a21aae2..f208917e20924 100644 --- a/x-pack/legacy/plugins/maps/server/test_utils/index.js +++ b/x-pack/legacy/plugins/maps/server/test_utils/index.js @@ -25,24 +25,3 @@ export const getMockCallWithInternal = (hits = defaultMockSavedObjects) => { export const getMockTaskFetch = (docs = defaultMockTaskDocs) => { return () => Promise.resolve({ docs }); }; - -export const getMockKbnServer = ( - mockCallWithInternal = getMockCallWithInternal(), - mockTaskFetch = getMockTaskFetch() -) => ({ - plugins: { - elasticsearch: { - getCluster: () => ({ - callWithInternalUser: mockCallWithInternal, - }), - }, - xpack_main: {}, - task_manager: { - registerTaskDefinitions: () => undefined, - schedule: () => Promise.resolve(), - fetch: mockTaskFetch, - }, - }, - config: () => ({ get: () => '' }), - log: () => undefined, -}); diff --git a/x-pack/legacy/plugins/ml/common/constants/new_job.ts b/x-pack/legacy/plugins/ml/common/constants/new_job.ts index 004824bef1c9d..ccd108cd2698f 100644 --- a/x-pack/legacy/plugins/ml/common/constants/new_job.ts +++ b/x-pack/legacy/plugins/ml/common/constants/new_job.ts @@ -9,16 +9,24 @@ export enum JOB_TYPE { MULTI_METRIC = 'multi_metric', POPULATION = 'population', ADVANCED = 'advanced', + CATEGORIZATION = 'categorization', } export enum CREATED_BY_LABEL { SINGLE_METRIC = 'single-metric-wizard', MULTI_METRIC = 'multi-metric-wizard', POPULATION = 'population-wizard', + CATEGORIZATION = 'categorization-wizard', } export const DEFAULT_MODEL_MEMORY_LIMIT = '10MB'; export const DEFAULT_BUCKET_SPAN = '15m'; +export const DEFAULT_RARE_BUCKET_SPAN = '1h'; export const DEFAULT_QUERY_DELAY = '60s'; export const SHARED_RESULTS_INDEX_NAME = 'shared'; + +export const NUMBER_OF_CATEGORY_EXAMPLES = 5; +export const CATEGORY_EXAMPLES_MULTIPLIER = 20; +export const CATEGORY_EXAMPLES_WARNING_LIMIT = 0.75; +export const CATEGORY_EXAMPLES_ERROR_LIMIT = 0.2; diff --git a/x-pack/legacy/plugins/ml/common/types/categories.ts b/x-pack/legacy/plugins/ml/common/types/categories.ts new file mode 100644 index 0000000000000..6ccd13ed9a39e --- /dev/null +++ b/x-pack/legacy/plugins/ml/common/types/categories.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export type CategoryId = number; + +export interface Category { + job_id: string; + category_id: CategoryId; + terms: string; + regex: string; + max_matching_length: number; + examples: string[]; + grok_pattern: string; +} + +export interface Token { + token: string; + start_offset: number; + end_offset: number; + type: string; + position: number; +} diff --git a/x-pack/legacy/plugins/ml/common/types/jobs.ts b/x-pack/legacy/plugins/ml/common/types/jobs.ts index 07c2be3e7f0b4..47f34f6568eed 100644 --- a/x-pack/legacy/plugins/ml/common/types/jobs.ts +++ b/x-pack/legacy/plugins/ml/common/types/jobs.ts @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +import { Moment } from 'moment'; + // TS TODO: This is not yet a fully fledged representation of the job data structure, // but it fulfills some basic TypeScript related needs. export interface MlJob { @@ -63,6 +65,20 @@ export interface MlSummaryJob { export type MlSummaryJobs = MlSummaryJob[]; +export interface MlJobWithTimeRange extends MlJob { + groups: string[]; + timeRange: { + from: number; + to: number; + fromPx: number; + toPx: number; + fromMoment: Moment; + toMoment: Moment; + widthPx: number; + label: string; + }; +} + export function isMlJob(arg: any): arg is MlJob { return typeof arg.job_id === 'string'; } diff --git a/x-pack/legacy/plugins/ml/common/util/job_utils.js b/x-pack/legacy/plugins/ml/common/util/job_utils.js index 757dfbd7a9a77..8982cebed522e 100644 --- a/x-pack/legacy/plugins/ml/common/util/job_utils.js +++ b/x-pack/legacy/plugins/ml/common/util/job_utils.js @@ -521,7 +521,7 @@ export function validateModelMemoryLimitUnits(modelMemoryLimit) { let valid = true; if (modelMemoryLimit !== undefined) { - const mml = modelMemoryLimit.toUpperCase(); + const mml = String(modelMemoryLimit).toUpperCase(); const mmlSplit = mml.match(/\d+(\w+)$/); const unit = mmlSplit && mmlSplit.length === 2 ? mmlSplit[1] : null; diff --git a/x-pack/legacy/plugins/ml/public/application/components/annotations/annotation_flyout/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/ml/public/application/components/annotations/annotation_flyout/__snapshots__/index.test.tsx.snap index 29831190824ad..dba73c246c3d0 100644 --- a/x-pack/legacy/plugins/ml/public/application/components/annotations/annotation_flyout/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/ml/public/application/components/annotations/annotation_flyout/__snapshots__/index.test.tsx.snap @@ -1,109 +1,3 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`AnnotationFlyout Initialization. 1`] = ` - -`; +exports[`AnnotationFlyout Initialization. 1`] = `""`; diff --git a/x-pack/legacy/plugins/ml/public/application/components/annotations/annotation_flyout/index.test.tsx b/x-pack/legacy/plugins/ml/public/application/components/annotations/annotation_flyout/index.test.tsx index 7fa47f3518b81..d71a23f478282 100644 --- a/x-pack/legacy/plugins/ml/public/application/components/annotations/annotation_flyout/index.test.tsx +++ b/x-pack/legacy/plugins/ml/public/application/components/annotations/annotation_flyout/index.test.tsx @@ -4,10 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { injectObservablesAsProps } from '../../../util/observable_utils'; +import useObservable from 'react-use/lib/useObservable'; + import mockAnnotations from '../annotations_table/__mocks__/mock_annotations.json'; -import React, { ComponentType } from 'react'; +import React from 'react'; import { mountWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers'; import { Annotation } from '../../../../../common/types/annotations'; @@ -25,11 +26,14 @@ describe('AnnotationFlyout', () => { const annotation = mockAnnotations[1] as Annotation; annotation$.next(annotation); - // injectObservablesAsProps wraps the observable in a new component - const ObservableComponent = injectObservablesAsProps( - { annotation: annotation$ }, - (AnnotationFlyout as any) as ComponentType - ); + // useObservable wraps the observable in a new component + const ObservableComponent = (props: any) => { + const annotationProp = useObservable(annotation$); + if (annotationProp === undefined) { + return null; + } + return ; + }; const wrapper = mountWithIntl(); const updateBtn = wrapper.find('EuiButton').first(); @@ -40,11 +44,14 @@ describe('AnnotationFlyout', () => { const annotation = mockAnnotations[2] as Annotation; annotation$.next(annotation); - // injectObservablesAsProps wraps the observable in a new component - const ObservableComponent = injectObservablesAsProps( - { annotation: annotation$ }, - (AnnotationFlyout as any) as ComponentType - ); + // useObservable wraps the observable in a new component + const ObservableComponent = (props: any) => { + const annotationProp = useObservable(annotation$); + if (annotationProp === undefined) { + return null; + } + return ; + }; const wrapper = mountWithIntl(); const updateBtn = wrapper.find('EuiButton').first(); diff --git a/x-pack/legacy/plugins/ml/public/application/components/annotations/annotation_flyout/index.tsx b/x-pack/legacy/plugins/ml/public/application/components/annotations/annotation_flyout/index.tsx index 84c16360795ea..6668518822710 100644 --- a/x-pack/legacy/plugins/ml/public/application/components/annotations/annotation_flyout/index.tsx +++ b/x-pack/legacy/plugins/ml/public/application/components/annotations/annotation_flyout/index.tsx @@ -4,7 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { Component, ComponentType, Fragment, ReactNode } from 'react'; +import React, { Component, Fragment, FC, ReactNode } from 'react'; +import useObservable from 'react-use/lib/useObservable'; import * as Rx from 'rxjs'; import { @@ -23,16 +24,16 @@ import { } from '@elastic/eui'; import { CommonProps } from '@elastic/eui'; -import { FormattedMessage, injectI18n } from '@kbn/i18n/react'; -import { InjectedIntlProps } from 'react-intl'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; + import { toastNotifications } from 'ui/notify'; import { ANNOTATION_MAX_LENGTH_CHARS } from '../../../../../common/constants/annotations'; import { annotation$, - annotationsRefresh$, + annotationsRefreshed, AnnotationState, } from '../../../services/annotations_service'; -import { injectObservablesAsProps } from '../../../util/observable_utils'; import { AnnotationDescriptionList } from '../annotation_description_list'; import { DeleteAnnotationModal } from '../delete_annotation_modal'; @@ -46,7 +47,7 @@ interface State { isDeleteModalVisible: boolean; } -class AnnotationFlyoutIntl extends Component { +class AnnotationFlyoutIntl extends Component { public state: State = { isDeleteModalVisible: false, }; @@ -73,7 +74,7 @@ class AnnotationFlyoutIntl extends Component { - const { annotation, intl } = this.props; + const { annotation } = this.props; if (annotation === null) { return; @@ -82,31 +83,30 @@ class AnnotationFlyoutIntl extends Component { @@ -116,7 +116,7 @@ class AnnotationFlyoutIntl extends Component { // Validates the entered text, returning an array of error messages // for display in the form. An empty array is returned if the text is valid. - const { annotation, intl } = this.props; + const { annotation } = this.props; const errors: string[] = []; if (annotation === null) { return errors; @@ -124,8 +124,7 @@ class AnnotationFlyoutIntl extends Component ANNOTATION_MAX_LENGTH_CHARS) { const charsOver = textLength - ANNOTATION_MAX_LENGTH_CHARS; errors.push( - intl.formatMessage( - { - id: 'xpack.ml.timeSeriesExplorer.annotationFlyout.maxLengthError', - defaultMessage: - '{charsOver, number} {charsOver, plural, one {character} other {characters}} above maximum length of {maxChars}', - }, - { + i18n.translate('xpack.ml.timeSeriesExplorer.annotationFlyout.maxLengthError', { + defaultMessage: + '{charsOver, number} {charsOver, plural, one {character} other {characters}} above maximum length of {maxChars}', + values: { maxChars: ANNOTATION_MAX_LENGTH_CHARS, charsOver, - } - ) + }, + }) ); } @@ -153,7 +149,7 @@ class AnnotationFlyoutIntl extends Component { - const { annotation, intl } = this.props; + const { annotation } = this.props; if (annotation === null) { return; @@ -164,27 +160,25 @@ class AnnotationFlyoutIntl extends Component { - annotationsRefresh$.next(true); + annotationsRefreshed(); if (typeof annotation._id === 'undefined') { toastNotifications.addSuccess( - intl.formatMessage( + i18n.translate( + 'xpack.ml.timeSeriesExplorer.timeSeriesChart.addedAnnotationNotificationMessage', { - id: - 'xpack.ml.timeSeriesExplorer.timeSeriesChart.addedAnnotationNotificationMessage', defaultMessage: 'Added an annotation for job with ID {jobId}.', - }, - { jobId: annotation.job_id } + values: { jobId: annotation.job_id }, + } ) ); } else { toastNotifications.addSuccess( - intl.formatMessage( + i18n.translate( + 'xpack.ml.timeSeriesExplorer.timeSeriesChart.updatedAnnotationNotificationMessage', { - id: - 'xpack.ml.timeSeriesExplorer.timeSeriesChart.updatedAnnotationNotificationMessage', defaultMessage: 'Updated annotation for job with ID {jobId}.', - }, - { jobId: annotation.job_id } + values: { jobId: annotation.job_id }, + } ) ); } @@ -192,26 +186,24 @@ class AnnotationFlyoutIntl extends Component { if (typeof annotation._id === 'undefined') { toastNotifications.addDanger( - intl.formatMessage( + i18n.translate( + 'xpack.ml.timeSeriesExplorer.timeSeriesChart.errorWithCreatingAnnotationNotificationErrorMessage', { - id: - 'xpack.ml.timeSeriesExplorer.timeSeriesChart.errorWithCreatingAnnotationNotificationErrorMessage', defaultMessage: 'An error occurred creating the annotation for job with ID {jobId}: {error}', - }, - { jobId: annotation.job_id, error: JSON.stringify(resp) } + values: { jobId: annotation.job_id, error: JSON.stringify(resp) }, + } ) ); } else { toastNotifications.addDanger( - intl.formatMessage( + i18n.translate( + 'xpack.ml.timeSeriesExplorer.timeSeriesChart.errorWithUpdatingAnnotationNotificationErrorMessage', { - id: - 'xpack.ml.timeSeriesExplorer.timeSeriesChart.errorWithUpdatingAnnotationNotificationErrorMessage', defaultMessage: 'An error occurred updating the annotation for job with ID {jobId}: {error}', - }, - { jobId: annotation.job_id, error: JSON.stringify(resp) } + values: { jobId: annotation.job_id, error: JSON.stringify(resp) }, + } ) ); } @@ -219,7 +211,7 @@ class AnnotationFlyoutIntl extends Component ANNOTATION_MAX_LENGTH_CHARS * lengthRatioToShowWarning ) { - helpText = intl.formatMessage( + helpText = i18n.translate( + 'xpack.ml.timeSeriesExplorer.annotationFlyout.approachingMaxLengthWarning', { - id: 'xpack.ml.timeSeriesExplorer.annotationFlyout.approachingMaxLengthWarning', defaultMessage: '{charsRemaining, number} {charsRemaining, plural, one {character} other {characters}} remaining', - }, - { charsRemaining: ANNOTATION_MAX_LENGTH_CHARS - annotation.annotation.length } + values: { charsRemaining: ANNOTATION_MAX_LENGTH_CHARS - annotation.annotation.length }, + } ); } @@ -344,7 +336,12 @@ class AnnotationFlyoutIntl extends Component = props => { + const annotationProp = useObservable(annotation$); + + if (annotationProp === undefined) { + return null; + } + + return ; +}; diff --git a/x-pack/legacy/plugins/ml/public/application/components/annotations/annotations_table/annotations_table.js b/x-pack/legacy/plugins/ml/public/application/components/annotations/annotations_table/annotations_table.js index f270d14b53e56..6c4e8925f369f 100644 --- a/x-pack/legacy/plugins/ml/public/application/components/annotations/annotations_table/annotations_table.js +++ b/x-pack/legacy/plugins/ml/public/application/components/annotations/annotations_table/annotations_table.js @@ -42,7 +42,11 @@ import { isTimeSeriesViewJob, } from '../../../../../common/util/job_utils'; -import { annotation$, annotationsRefresh$ } from '../../../services/annotations_service'; +import { + annotation$, + annotationsRefresh$, + annotationsRefreshed, +} from '../../../services/annotations_service'; import { FormattedMessage, injectI18n } from '@kbn/i18n/react'; @@ -136,7 +140,7 @@ const AnnotationsTable = injectI18n( this.annotationsRefreshSubscription = annotationsRefresh$.subscribe(() => this.getAnnotations() ); - annotationsRefresh$.next(true); + annotationsRefreshed(); } } @@ -150,7 +154,7 @@ const AnnotationsTable = injectI18n( this.state.isLoading === false && this.state.jobId !== this.props.jobs[0].job_id ) { - annotationsRefresh$.next(true); + annotationsRefreshed(); this.previousJobId = this.props.jobs[0].job_id; } } diff --git a/x-pack/legacy/plugins/ml/public/application/components/anomalies_table/anomalies_table.js b/x-pack/legacy/plugins/ml/public/application/components/anomalies_table/anomalies_table.js index d1beb360793f2..bc3ce88921110 100644 --- a/x-pack/legacy/plugins/ml/public/application/components/anomalies_table/anomalies_table.js +++ b/x-pack/legacy/plugins/ml/public/application/components/anomalies_table/anomalies_table.js @@ -146,7 +146,7 @@ class AnomaliesTable extends Component { }; render() { - const { timefilter, tableData, filter, influencerFilter } = this.props; + const { bounds, tableData, filter, influencerFilter } = this.props; if ( tableData === undefined || @@ -175,7 +175,7 @@ class AnomaliesTable extends Component { tableData.examplesByJobId, this.isShowingAggregatedData(), tableData.interval, - timefilter, + bounds, tableData.showViewSeriesLink, this.state.showRuleEditorFlyout, this.state.itemIdToExpandedRowMap, @@ -224,7 +224,7 @@ class AnomaliesTable extends Component { } } AnomaliesTable.propTypes = { - timefilter: PropTypes.object.isRequired, + bounds: PropTypes.object.isRequired, tableData: PropTypes.object, filter: PropTypes.func, influencerFilter: PropTypes.func, diff --git a/x-pack/legacy/plugins/ml/public/application/components/anomalies_table/anomalies_table_columns.js b/x-pack/legacy/plugins/ml/public/application/components/anomalies_table/anomalies_table_columns.js index 75941edddeb56..36faac45164f4 100644 --- a/x-pack/legacy/plugins/ml/public/application/components/anomalies_table/anomalies_table_columns.js +++ b/x-pack/legacy/plugins/ml/public/application/components/anomalies_table/anomalies_table_columns.js @@ -55,7 +55,7 @@ export function getColumns( examplesByJobId, isAggregatedData, interval, - timefilter, + bounds, showViewSeriesLink, showRuleEditorFlyout, itemIdToExpandedRowMap, @@ -262,10 +262,10 @@ export function getColumns( return ( ); diff --git a/x-pack/legacy/plugins/ml/public/application/components/anomalies_table/links_menu.js b/x-pack/legacy/plugins/ml/public/application/components/anomalies_table/links_menu.js index b4821ddb564c9..8cbee27bdd9a8 100644 --- a/x-pack/legacy/plugins/ml/public/application/components/anomalies_table/links_menu.js +++ b/x-pack/legacy/plugins/ml/public/application/components/anomalies_table/links_menu.js @@ -37,10 +37,10 @@ export const LinksMenu = injectI18n( class LinksMenu extends Component { static propTypes = { anomaly: PropTypes.object.isRequired, + bounds: PropTypes.object.isRequired, showViewSeriesLink: PropTypes.bool, isAggregatedData: PropTypes.bool, interval: PropTypes.string, - timefilter: PropTypes.object.isRequired, showRuleEditorFlyout: PropTypes.func, }; @@ -146,7 +146,7 @@ export const LinksMenu = injectI18n( viewSeries = () => { const record = this.props.anomaly.source; - const bounds = this.props.timefilter.getActiveBounds(); + const bounds = this.props.bounds; const from = bounds.min.toISOString(); // e.g. 2016-02-08T16:00:00.000Z const to = bounds.max.toISOString(); diff --git a/x-pack/legacy/plugins/ml/public/application/components/chart_tooltip/chart_tooltip.tsx b/x-pack/legacy/plugins/ml/public/application/components/chart_tooltip/chart_tooltip.tsx index aa28831e8d807..a28dc41fa1790 100644 --- a/x-pack/legacy/plugins/ml/public/application/components/chart_tooltip/chart_tooltip.tsx +++ b/x-pack/legacy/plugins/ml/public/application/components/chart_tooltip/chart_tooltip.tsx @@ -37,8 +37,7 @@ function useRefWithCallback() { if (left + tooltipWidth > contentWidth) { // the tooltip is hanging off the side of the page, // so move it to the other side of the target - const markerWidthAdjustment = 25; - left = left - (tooltipWidth + offset.x + markerWidthAdjustment); + left = left - (tooltipWidth + offset.x); } const top = targetPosition.top + offset.y - parentBounding.top; diff --git a/x-pack/legacy/plugins/ml/public/application/components/controls/checkbox_showcharts/checkbox_showcharts.js b/x-pack/legacy/plugins/ml/public/application/components/controls/checkbox_showcharts/checkbox_showcharts.js deleted file mode 100644 index 89a5fafc491b5..0000000000000 --- a/x-pack/legacy/plugins/ml/public/application/components/controls/checkbox_showcharts/checkbox_showcharts.js +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -/* - * React component for a checkbox element to toggle charts display. - */ -import React, { Component } from 'react'; -import { BehaviorSubject } from 'rxjs'; - -import { EuiCheckbox } from '@elastic/eui'; - -import makeId from '@elastic/eui/lib/components/form/form_row/make_id'; -import { FormattedMessage } from '@kbn/i18n/react'; - -import { injectObservablesAsProps } from '../../../util/observable_utils'; - -export const showCharts$ = new BehaviorSubject(true); - -class CheckboxShowChartsUnwrapped extends Component { - onChange = e => { - const showCharts = e.target.checked; - showCharts$.next(showCharts); - }; - - render() { - return ( - - } - checked={this.props.showCharts} - onChange={this.onChange} - /> - ); - } -} - -const CheckboxShowCharts = injectObservablesAsProps( - { - showCharts: showCharts$, - }, - CheckboxShowChartsUnwrapped -); - -export { CheckboxShowCharts }; diff --git a/x-pack/legacy/plugins/ml/public/application/components/controls/checkbox_showcharts/checkbox_showcharts.tsx b/x-pack/legacy/plugins/ml/public/application/components/controls/checkbox_showcharts/checkbox_showcharts.tsx new file mode 100644 index 0000000000000..70538d4dc3a91 --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/application/components/controls/checkbox_showcharts/checkbox_showcharts.tsx @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/* + * React component for a checkbox element to toggle charts display. + */ +import React, { FC } from 'react'; + +import { EuiCheckbox } from '@elastic/eui'; +// @ts-ignore +import makeId from '@elastic/eui/lib/components/form/form_row/make_id'; + +import { FormattedMessage } from '@kbn/i18n/react'; + +import { useUrlState } from '../../../util/url_state'; + +const SHOW_CHARTS_DEFAULT = true; +const SHOW_CHARTS_APP_STATE_NAME = 'mlShowCharts'; + +export const useShowCharts = () => { + const [appState, setAppState] = useUrlState('_a'); + + return [ + appState?.mlShowCharts !== undefined ? appState?.mlShowCharts : SHOW_CHARTS_DEFAULT, + (d: boolean) => setAppState(SHOW_CHARTS_APP_STATE_NAME, d), + ]; +}; + +export const CheckboxShowCharts: FC = () => { + const [showCharts, setShowCarts] = useShowCharts(); + + const onChange = (e: React.ChangeEvent) => { + setShowCarts(e.target.checked); + }; + + return ( + + } + checked={showCharts} + onChange={onChange} + /> + ); +}; diff --git a/x-pack/legacy/plugins/ml/public/application/components/controls/checkbox_showcharts/index.d.ts b/x-pack/legacy/plugins/ml/public/application/components/controls/checkbox_showcharts/index.d.ts deleted file mode 100644 index 4d6952d3b3fc3..0000000000000 --- a/x-pack/legacy/plugins/ml/public/application/components/controls/checkbox_showcharts/index.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { BehaviorSubject } from 'rxjs'; - -export const showCharts$: BehaviorSubject; diff --git a/x-pack/legacy/plugins/ml/public/application/components/controls/checkbox_showcharts/index.js b/x-pack/legacy/plugins/ml/public/application/components/controls/checkbox_showcharts/index.js deleted file mode 100644 index b7957b807591c..0000000000000 --- a/x-pack/legacy/plugins/ml/public/application/components/controls/checkbox_showcharts/index.js +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export { CheckboxShowCharts, showCharts$ } from './checkbox_showcharts'; diff --git a/x-pack/legacy/plugins/ml/public/application/components/controls/checkbox_showcharts/index.ts b/x-pack/legacy/plugins/ml/public/application/components/controls/checkbox_showcharts/index.ts new file mode 100644 index 0000000000000..d868b9570f337 --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/application/components/controls/checkbox_showcharts/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { useShowCharts, CheckboxShowCharts } from './checkbox_showcharts'; diff --git a/x-pack/legacy/plugins/ml/public/application/components/controls/index.js b/x-pack/legacy/plugins/ml/public/application/components/controls/index.js deleted file mode 100644 index 26cb89d672632..0000000000000 --- a/x-pack/legacy/plugins/ml/public/application/components/controls/index.js +++ /dev/null @@ -1,9 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export { CheckboxShowCharts, showCharts$ } from './checkbox_showcharts'; -export { interval$, SelectInterval } from './select_interval'; -export { SelectSeverity, severity$, SEVERITY_OPTIONS } from './select_severity'; diff --git a/x-pack/legacy/plugins/ml/public/application/components/controls/index.ts b/x-pack/legacy/plugins/ml/public/application/components/controls/index.ts new file mode 100644 index 0000000000000..f3e1ef8358867 --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/application/components/controls/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { CheckboxShowCharts } from './checkbox_showcharts'; +export { SelectInterval } from './select_interval'; +export { SelectSeverity, SEVERITY_OPTIONS } from './select_severity'; diff --git a/x-pack/legacy/plugins/ml/public/application/components/controls/select_interval/index.d.ts b/x-pack/legacy/plugins/ml/public/application/components/controls/select_interval/index.d.ts deleted file mode 100644 index 4a8273972389a..0000000000000 --- a/x-pack/legacy/plugins/ml/public/application/components/controls/select_interval/index.d.ts +++ /dev/null @@ -1,12 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { BehaviorSubject } from 'rxjs'; - -export const interval$: BehaviorSubject<{ - value: string; - text: string; -}>; diff --git a/x-pack/legacy/plugins/ml/public/application/components/controls/select_interval/index.js b/x-pack/legacy/plugins/ml/public/application/components/controls/select_interval/index.js deleted file mode 100644 index aec48f4c626ca..0000000000000 --- a/x-pack/legacy/plugins/ml/public/application/components/controls/select_interval/index.js +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export { interval$, SelectInterval } from './select_interval'; diff --git a/x-pack/legacy/plugins/ml/public/application/components/controls/select_interval/index.ts b/x-pack/legacy/plugins/ml/public/application/components/controls/select_interval/index.ts new file mode 100644 index 0000000000000..32a0b53077818 --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/application/components/controls/select_interval/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { useTableInterval, SelectInterval } from './select_interval'; diff --git a/x-pack/legacy/plugins/ml/public/application/components/controls/select_interval/select_interval.js b/x-pack/legacy/plugins/ml/public/application/components/controls/select_interval/select_interval.js deleted file mode 100644 index fce538c0c8c7e..0000000000000 --- a/x-pack/legacy/plugins/ml/public/application/components/controls/select_interval/select_interval.js +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -/* - * React component for rendering a select element with various aggregation interval levels. - */ - -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import { BehaviorSubject } from 'rxjs'; - -import { EuiSelect } from '@elastic/eui'; - -import { i18n } from '@kbn/i18n'; - -import { injectObservablesAsProps } from '../../../util/observable_utils'; - -const OPTIONS = [ - { - value: 'auto', - text: i18n.translate('xpack.ml.controls.selectInterval.autoLabel', { defaultMessage: 'Auto' }), - }, - { - value: 'hour', - text: i18n.translate('xpack.ml.controls.selectInterval.hourLabel', { - defaultMessage: '1 hour', - }), - }, - { - value: 'day', - text: i18n.translate('xpack.ml.controls.selectInterval.dayLabel', { defaultMessage: '1 day' }), - }, - { - value: 'second', - text: i18n.translate('xpack.ml.controls.selectInterval.showAllLabel', { - defaultMessage: 'Show all', - }), - }, -]; - -function optionValueToInterval(value) { - // Builds the corresponding interval object with the required display and val properties - // from the specified value. - const option = OPTIONS.find(opt => opt.value === value); - - // Default to auto if supplied value doesn't map to one of the options. - let interval = OPTIONS[0]; - if (option !== undefined) { - interval = { display: option.text, val: option.value }; - } - - return interval; -} - -export const interval$ = new BehaviorSubject(optionValueToInterval(OPTIONS[0].value)); - -class SelectIntervalUnwrapped extends Component { - static propTypes = { - interval: PropTypes.object.isRequired, - }; - - onChange = e => { - const interval = optionValueToInterval(e.target.value); - interval$.next(interval); - }; - - render() { - return ( - - ); - } -} - -const SelectInterval = injectObservablesAsProps({ interval: interval$ }, SelectIntervalUnwrapped); - -export { SelectInterval }; diff --git a/x-pack/legacy/plugins/ml/public/application/components/controls/select_interval/select_interval.test.js b/x-pack/legacy/plugins/ml/public/application/components/controls/select_interval/select_interval.test.js deleted file mode 100644 index c99d25a68f722..0000000000000 --- a/x-pack/legacy/plugins/ml/public/application/components/controls/select_interval/select_interval.test.js +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import { shallowWithIntl } from 'test_utils/enzyme_helpers'; -import { SelectInterval } from './select_interval'; - -describe('SelectInterval', () => { - test('creates correct initial selected value', () => { - const wrapper = shallowWithIntl(); - const defaultSelectedValue = wrapper.props().interval.val; - - expect(defaultSelectedValue).toBe('auto'); - }); - - test('currently selected value is updated correctly on click', () => { - const wrapper = shallowWithIntl(); - const select = wrapper.first().shallow(); - - const defaultSelectedValue = wrapper.props().interval.val; - expect(defaultSelectedValue).toBe('auto'); - - select.simulate('change', { target: { value: 'day' } }); - const updatedSelectedValue = wrapper.props().interval.val; - expect(updatedSelectedValue).toBe('day'); - }); -}); diff --git a/x-pack/legacy/plugins/ml/public/application/components/controls/select_interval/select_interval.test.tsx b/x-pack/legacy/plugins/ml/public/application/components/controls/select_interval/select_interval.test.tsx new file mode 100644 index 0000000000000..e1861b887b2a9 --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/application/components/controls/select_interval/select_interval.test.tsx @@ -0,0 +1,55 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { act } from 'react-dom/test-utils'; +import { MemoryRouter } from 'react-router-dom'; +import { mount } from 'enzyme'; + +import { EuiSelect } from '@elastic/eui'; + +import { SelectInterval } from './select_interval'; + +describe('SelectInterval', () => { + test('creates correct initial selected value', () => { + const wrapper = mount( + + + + ); + const select = wrapper.find(EuiSelect); + + const defaultSelectedValue = select.props().value; + expect(defaultSelectedValue).toBe('auto'); + }); + + test('currently selected value is updated correctly on click', done => { + const wrapper = mount( + + + + ); + const select = wrapper.find(EuiSelect).first(); + const defaultSelectedValue = select.props().value; + expect(defaultSelectedValue).toBe('auto'); + + const onChange = select.props().onChange; + + act(() => { + if (onChange !== undefined) { + onChange({ target: { value: 'day' } } as React.ChangeEvent); + } + }); + + setImmediate(() => { + wrapper.update(); + const updatedSelect = wrapper.find(EuiSelect).first(); + const updatedSelectedValue = updatedSelect.props().value; + expect(updatedSelectedValue).toBe('day'); + done(); + }); + }); +}); diff --git a/x-pack/legacy/plugins/ml/public/application/components/controls/select_interval/select_interval.tsx b/x-pack/legacy/plugins/ml/public/application/components/controls/select_interval/select_interval.tsx new file mode 100644 index 0000000000000..cea3ef2a497b0 --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/application/components/controls/select_interval/select_interval.tsx @@ -0,0 +1,88 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/* + * React component for rendering a select element with various aggregation interval levels. + */ + +import React, { FC } from 'react'; + +import { EuiSelect } from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +import { useUrlState } from '../../../util/url_state'; + +interface TableInterval { + display: string; + val: string; +} + +const OPTIONS = [ + { + value: 'auto', + text: i18n.translate('xpack.ml.controls.selectInterval.autoLabel', { defaultMessage: 'Auto' }), + }, + { + value: 'hour', + text: i18n.translate('xpack.ml.controls.selectInterval.hourLabel', { + defaultMessage: '1 hour', + }), + }, + { + value: 'day', + text: i18n.translate('xpack.ml.controls.selectInterval.dayLabel', { defaultMessage: '1 day' }), + }, + { + value: 'second', + text: i18n.translate('xpack.ml.controls.selectInterval.showAllLabel', { + defaultMessage: 'Show all', + }), + }, +]; + +function optionValueToInterval(value: string) { + // Builds the corresponding interval object with the required display and val properties + // from the specified value. + const option = OPTIONS.find(opt => opt.value === value); + + // Default to auto if supplied value doesn't map to one of the options. + let interval: TableInterval = { display: OPTIONS[0].text, val: OPTIONS[0].value }; + if (option !== undefined) { + interval = { display: option.text, val: option.value }; + } + + return interval; +} + +const TABLE_INTERVAL_DEFAULT = optionValueToInterval('auto'); +const TABLE_INTERVAL_APP_STATE_NAME = 'mlSelectInterval'; + +export const useTableInterval = () => { + const [appState, setAppState] = useUrlState('_a'); + + return [ + (appState && appState[TABLE_INTERVAL_APP_STATE_NAME]) || TABLE_INTERVAL_DEFAULT, + (d: TableInterval) => setAppState(TABLE_INTERVAL_APP_STATE_NAME, d), + ]; +}; + +export const SelectInterval: FC = () => { + const [interval, setInterval] = useTableInterval(); + + const onChange = (e: React.ChangeEvent) => { + setInterval(optionValueToInterval(e.target.value)); + }; + + return ( + + ); +}; diff --git a/x-pack/legacy/plugins/ml/public/application/components/controls/select_severity/index.d.ts b/x-pack/legacy/plugins/ml/public/application/components/controls/select_severity/index.d.ts deleted file mode 100644 index 006d23da56f82..0000000000000 --- a/x-pack/legacy/plugins/ml/public/application/components/controls/select_severity/index.d.ts +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { BehaviorSubject } from 'rxjs'; - -export const severity$: BehaviorSubject<{ - val: number; - display: string; - color: string; -}>; diff --git a/x-pack/legacy/plugins/ml/public/application/components/controls/select_severity/index.js b/x-pack/legacy/plugins/ml/public/application/components/controls/select_severity/index.js deleted file mode 100644 index f26c16c6ff77d..0000000000000 --- a/x-pack/legacy/plugins/ml/public/application/components/controls/select_severity/index.js +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export { SelectSeverity, severity$, SEVERITY_OPTIONS } from './select_severity'; diff --git a/x-pack/legacy/plugins/ml/public/application/components/controls/select_severity/index.ts b/x-pack/legacy/plugins/ml/public/application/components/controls/select_severity/index.ts new file mode 100644 index 0000000000000..1f524dc1c2ffd --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/application/components/controls/select_severity/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { useTableSeverity, SelectSeverity, SEVERITY_OPTIONS } from './select_severity'; diff --git a/x-pack/legacy/plugins/ml/public/application/components/controls/select_severity/select_severity.js b/x-pack/legacy/plugins/ml/public/application/components/controls/select_severity/select_severity.js deleted file mode 100644 index 53d65d6622b94..0000000000000 --- a/x-pack/legacy/plugins/ml/public/application/components/controls/select_severity/select_severity.js +++ /dev/null @@ -1,139 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -/* - * React component for rendering a select element with threshold levels. - */ -import PropTypes from 'prop-types'; -import React, { Component, Fragment } from 'react'; -import { i18n } from '@kbn/i18n'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { BehaviorSubject } from 'rxjs'; - -import { EuiHealth, EuiSpacer, EuiSuperSelect, EuiText } from '@elastic/eui'; - -import { getSeverityColor } from '../../../../../common/util/anomaly_utils'; -import { injectObservablesAsProps } from '../../../util/observable_utils'; - -const warningLabel = i18n.translate('xpack.ml.controls.selectSeverity.warningLabel', { - defaultMessage: 'warning', -}); -const minorLabel = i18n.translate('xpack.ml.controls.selectSeverity.minorLabel', { - defaultMessage: 'minor', -}); -const majorLabel = i18n.translate('xpack.ml.controls.selectSeverity.majorLabel', { - defaultMessage: 'major', -}); -const criticalLabel = i18n.translate('xpack.ml.controls.selectSeverity.criticalLabel', { - defaultMessage: 'critical', -}); - -const optionsMap = { - [warningLabel]: 0, - [minorLabel]: 25, - [majorLabel]: 50, - [criticalLabel]: 75, -}; - -export const SEVERITY_OPTIONS = [ - { - val: 0, - display: warningLabel, - color: getSeverityColor(0), - }, - { - val: 25, - display: minorLabel, - color: getSeverityColor(25), - }, - { - val: 50, - display: majorLabel, - color: getSeverityColor(50), - }, - { - val: 75, - display: criticalLabel, - color: getSeverityColor(75), - }, -]; - -function optionValueToThreshold(value) { - // Get corresponding threshold object with required display and val properties from the specified value. - let threshold = SEVERITY_OPTIONS.find(opt => opt.val === value); - - // Default to warning if supplied value doesn't map to one of the options. - if (threshold === undefined) { - threshold = SEVERITY_OPTIONS[0]; - } - - return threshold; -} - -export const severity$ = new BehaviorSubject(SEVERITY_OPTIONS[0]); - -class SelectSeverityUnwrapped extends Component { - onChange = valueDisplay => { - const threshold = optionValueToThreshold(optionsMap[valueDisplay]); - severity$.next(threshold); - }; - - getOptions = () => - SEVERITY_OPTIONS.map(({ color, display, val }) => ({ - value: display, - inputDisplay: ( - - - {display} - - - ), - dropdownDisplay: ( - - - {display} - - - -

- -

-
-
- ), - })); - - render() { - const { severity } = this.props; - const options = this.getOptions(); - - return ( - - ); - } -} - -SelectSeverityUnwrapped.propTypes = { - classNames: PropTypes.string, -}; - -SelectSeverityUnwrapped.defaultProps = { - classNames: '', -}; - -const SelectSeverity = injectObservablesAsProps({ severity: severity$ }, SelectSeverityUnwrapped); - -export { SelectSeverity }; diff --git a/x-pack/legacy/plugins/ml/public/application/components/controls/select_severity/select_severity.test.js b/x-pack/legacy/plugins/ml/public/application/components/controls/select_severity/select_severity.test.js deleted file mode 100644 index ec2fe7d1cdeac..0000000000000 --- a/x-pack/legacy/plugins/ml/public/application/components/controls/select_severity/select_severity.test.js +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import { shallowWithIntl } from 'test_utils/enzyme_helpers'; -import { SelectSeverity } from './select_severity'; - -describe('SelectSeverity', () => { - test('creates correct severity options and initial selected value', () => { - const wrapper = shallowWithIntl(); - const select = wrapper.first().shallow(); - - const options = select.instance().getOptions(); - const defaultSelectedValue = wrapper.props().severity.display; - - expect(defaultSelectedValue).toBe('warning'); - expect(options.length).toEqual(4); - - // excpect options Array to equal Array containing Object that contains the property - expect(options).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - value: 'warning', - }), - ]) - ); - - expect(options).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - value: 'minor', - }), - ]) - ); - - expect(options).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - value: 'major', - }), - ]) - ); - - expect(options).toEqual( - expect.arrayContaining([ - expect.objectContaining({ - value: 'critical', - }), - ]) - ); - }); - - test('state for currently selected value is updated correctly on click', () => { - const wrapper = shallowWithIntl(); - const select = wrapper.first().shallow(); - - const defaultSelectedValue = wrapper.props().severity.display; - expect(defaultSelectedValue).toBe('warning'); - - select.simulate('change', 'critical'); - const updatedSelectedValue = wrapper.props().severity.display; - expect(updatedSelectedValue).toBe('critical'); - }); -}); diff --git a/x-pack/legacy/plugins/ml/public/application/components/controls/select_severity/select_severity.test.tsx b/x-pack/legacy/plugins/ml/public/application/components/controls/select_severity/select_severity.test.tsx new file mode 100644 index 0000000000000..e30c48c10a194 --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/application/components/controls/select_severity/select_severity.test.tsx @@ -0,0 +1,92 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { act } from 'react-dom/test-utils'; +import { MemoryRouter } from 'react-router-dom'; +import { mount } from 'enzyme'; + +import { EuiSuperSelect } from '@elastic/eui'; + +import { SelectSeverity } from './select_severity'; + +describe('SelectSeverity', () => { + test('creates correct severity options and initial selected value', () => { + const wrapper = mount( + + + + ); + const select = wrapper.find(EuiSuperSelect); + + const options = select.props().options; + const defaultSelectedValue = select.props().valueOfSelected; + + expect(defaultSelectedValue).toBe('warning'); + expect(options.length).toEqual(4); + + // excpect options Array to equal Array containing Object that contains the property + expect(options).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + value: 'warning', + }), + ]) + ); + + expect(options).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + value: 'minor', + }), + ]) + ); + + expect(options).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + value: 'major', + }), + ]) + ); + + expect(options).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + value: 'critical', + }), + ]) + ); + }); + + test('state for currently selected value is updated correctly on click', done => { + const wrapper = mount( + + + + ); + + const select = wrapper.find(EuiSuperSelect).first(); + const defaultSelectedValue = select.props().valueOfSelected; + expect(defaultSelectedValue).toBe('warning'); + + const onChange = select.props().onChange; + + act(() => { + if (onChange !== undefined) { + onChange('critical'); + } + }); + + setImmediate(() => { + wrapper.update(); + const updatedSelect = wrapper.find(EuiSuperSelect).first(); + const updatedSelectedValue = updatedSelect.props().valueOfSelected; + expect(updatedSelectedValue).toBe('critical'); + done(); + }); + }); +}); diff --git a/x-pack/legacy/plugins/ml/public/application/components/controls/select_severity/select_severity.tsx b/x-pack/legacy/plugins/ml/public/application/components/controls/select_severity/select_severity.tsx new file mode 100644 index 0000000000000..a03594a5f213e --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/application/components/controls/select_severity/select_severity.tsx @@ -0,0 +1,141 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/* + * React component for rendering a select element with threshold levels. + */ +import React, { Fragment, FC } from 'react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; + +import { EuiHealth, EuiSpacer, EuiSuperSelect, EuiText } from '@elastic/eui'; + +import { getSeverityColor } from '../../../../../common/util/anomaly_utils'; +import { useUrlState } from '../../../util/url_state'; + +const warningLabel = i18n.translate('xpack.ml.controls.selectSeverity.warningLabel', { + defaultMessage: 'warning', +}); +const minorLabel = i18n.translate('xpack.ml.controls.selectSeverity.minorLabel', { + defaultMessage: 'minor', +}); +const majorLabel = i18n.translate('xpack.ml.controls.selectSeverity.majorLabel', { + defaultMessage: 'major', +}); +const criticalLabel = i18n.translate('xpack.ml.controls.selectSeverity.criticalLabel', { + defaultMessage: 'critical', +}); + +const optionsMap = { + [warningLabel]: 0, + [minorLabel]: 25, + [majorLabel]: 50, + [criticalLabel]: 75, +}; + +interface TableSeverity { + val: number; + display: string; + color: string; +} + +export const SEVERITY_OPTIONS: TableSeverity[] = [ + { + val: 0, + display: warningLabel, + color: getSeverityColor(0), + }, + { + val: 25, + display: minorLabel, + color: getSeverityColor(25), + }, + { + val: 50, + display: majorLabel, + color: getSeverityColor(50), + }, + { + val: 75, + display: criticalLabel, + color: getSeverityColor(75), + }, +]; + +function optionValueToThreshold(value: number) { + // Get corresponding threshold object with required display and val properties from the specified value. + let threshold = SEVERITY_OPTIONS.find(opt => opt.val === value); + + // Default to warning if supplied value doesn't map to one of the options. + if (threshold === undefined) { + threshold = SEVERITY_OPTIONS[0]; + } + + return threshold; +} + +const TABLE_SEVERITY_DEFAULT = SEVERITY_OPTIONS[0]; +const TABLE_SEVERITY_APP_STATE_NAME = 'mlSelectSeverity'; + +export const useTableSeverity = () => { + const [appState, setAppState] = useUrlState('_a'); + + return [ + (appState && appState[TABLE_SEVERITY_APP_STATE_NAME]) || TABLE_SEVERITY_DEFAULT, + (d: TableSeverity) => setAppState(TABLE_SEVERITY_APP_STATE_NAME, d), + ]; +}; + +const getSeverityOptions = () => + SEVERITY_OPTIONS.map(({ color, display, val }) => ({ + value: display, + inputDisplay: ( + + + {display} + + + ), + dropdownDisplay: ( + + + {display} + + + +

+ +

+
+
+ ), + })); + +interface Props { + classNames?: string; +} + +export const SelectSeverity: FC = ({ classNames } = { classNames: '' }) => { + const [severity, setSeverity] = useTableSeverity(); + + const onChange = (valueDisplay: string) => { + setSeverity(optionValueToThreshold(optionsMap[valueDisplay])); + }; + + return ( + + ); +}; diff --git a/x-pack/legacy/plugins/ml/public/application/components/data_recognizer/data_recognizer.d.ts b/x-pack/legacy/plugins/ml/public/application/components/data_recognizer/data_recognizer.d.ts index 2a15c597fed81..4258b3761f72c 100644 --- a/x-pack/legacy/plugins/ml/public/application/components/data_recognizer/data_recognizer.d.ts +++ b/x-pack/legacy/plugins/ml/public/application/components/data_recognizer/data_recognizer.d.ts @@ -5,9 +5,8 @@ */ import { FC } from 'react'; - -import { IndexPattern } from 'ui/index_patterns'; import { SavedSearchSavedObject } from '../../../../common/types/kibana'; +import { IndexPattern } from '../../../../../../../../src/plugins/data/public'; declare const DataRecognizer: FC<{ indexPattern: IndexPattern; diff --git a/x-pack/legacy/plugins/ml/public/application/components/full_time_range_selector/__snapshots__/full_time_range_selector.test.tsx.snap b/x-pack/legacy/plugins/ml/public/application/components/full_time_range_selector/__snapshots__/full_time_range_selector.test.tsx.snap index 0e14f0b402fb2..d633b755ec6ba 100644 --- a/x-pack/legacy/plugins/ml/public/application/components/full_time_range_selector/__snapshots__/full_time_range_selector.test.tsx.snap +++ b/x-pack/legacy/plugins/ml/public/application/components/full_time_range_selector/__snapshots__/full_time_range_selector.test.tsx.snap @@ -3,7 +3,6 @@ exports[`FullTimeRangeSelector renders the selector 1`] = ` diff --git a/x-pack/legacy/plugins/ml/public/application/components/full_time_range_selector/full_time_range_selector.test.tsx b/x-pack/legacy/plugins/ml/public/application/components/full_time_range_selector/full_time_range_selector.test.tsx index 38e4a3fc24278..a4e2ebe6784d8 100644 --- a/x-pack/legacy/plugins/ml/public/application/components/full_time_range_selector/full_time_range_selector.test.tsx +++ b/x-pack/legacy/plugins/ml/public/application/components/full_time_range_selector/full_time_range_selector.test.tsx @@ -6,9 +6,9 @@ import React from 'react'; import { shallowWithIntl } from 'test_utils/enzyme_helpers'; -import { IndexPattern } from 'ui/index_patterns'; import { FullTimeRangeSelector } from './index'; import { Query } from 'src/plugins/data/public'; +import { IndexPattern } from '../../../../../../../../src/plugins/data/public'; // Create a mock for the setFullTimeRange function in the service. // The mock is hoisted to the top, so need to prefix the mock function diff --git a/x-pack/legacy/plugins/ml/public/application/components/full_time_range_selector/full_time_range_selector.tsx b/x-pack/legacy/plugins/ml/public/application/components/full_time_range_selector/full_time_range_selector.tsx index f637ffb9beea0..4460ced7079c3 100644 --- a/x-pack/legacy/plugins/ml/public/application/components/full_time_range_selector/full_time_range_selector.tsx +++ b/x-pack/legacy/plugins/ml/public/application/components/full_time_range_selector/full_time_range_selector.tsx @@ -8,9 +8,9 @@ import React, { FC } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { Query } from 'src/plugins/data/public'; -import { IndexPattern } from 'ui/index_patterns'; import { EuiButton } from '@elastic/eui'; import { setFullTimeRange } from './full_time_range_selector_service'; +import { IndexPattern } from '../../../../../../../../src/plugins/data/public'; interface Props { indexPattern: IndexPattern; @@ -31,7 +31,6 @@ export const FullTimeRangeSelector: FC = ({ indexPattern, query, disabled } return ( setRange(indexPattern, query)} data-test-subj="mlButtonUseFullData" diff --git a/x-pack/legacy/plugins/ml/public/application/components/full_time_range_selector/full_time_range_selector_service.ts b/x-pack/legacy/plugins/ml/public/application/components/full_time_range_selector/full_time_range_selector_service.ts index bd31ccf7eca3d..e69aaf2ede037 100644 --- a/x-pack/legacy/plugins/ml/public/application/components/full_time_range_selector/full_time_range_selector_service.ts +++ b/x-pack/legacy/plugins/ml/public/application/components/full_time_range_selector/full_time_range_selector_service.ts @@ -7,12 +7,12 @@ import moment from 'moment'; import { i18n } from '@kbn/i18n'; -import { IndexPattern } from 'ui/index_patterns'; import { toastNotifications } from 'ui/notify'; import { timefilter } from 'ui/timefilter'; import { Query } from 'src/plugins/data/public'; import dateMath from '@elastic/datemath'; import { ml, GetTimeFieldRangeResponse } from '../../services/ml_api_service'; +import { IndexPattern } from '../../../../../../../../src/plugins/data/public'; export interface TimeRange { from: number; diff --git a/x-pack/legacy/plugins/ml/public/application/components/job_selector/index.js b/x-pack/legacy/plugins/ml/public/application/components/job_selector/index.ts similarity index 100% rename from x-pack/legacy/plugins/ml/public/application/components/job_selector/index.js rename to x-pack/legacy/plugins/ml/public/application/components/job_selector/index.ts diff --git a/x-pack/legacy/plugins/ml/public/application/components/job_selector/job_select_service_utils.d.ts b/x-pack/legacy/plugins/ml/public/application/components/job_selector/job_select_service_utils.d.ts deleted file mode 100644 index fe5966524c7e5..0000000000000 --- a/x-pack/legacy/plugins/ml/public/application/components/job_selector/job_select_service_utils.d.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { BehaviorSubject } from 'rxjs'; - -import { State } from 'ui/state_management/state'; - -export declare type JobSelectService$ = BehaviorSubject<{ - selection: string[]; - groups: string[]; - resetSelection: boolean; -}>; - -declare interface JobSelectService { - jobSelectService$: JobSelectService$; - unsubscribeFromGlobalState(): void; -} - -export const jobSelectServiceFactory: (globalState: State) => JobSelectService; diff --git a/x-pack/legacy/plugins/ml/public/application/components/job_selector/job_select_service_utils.js b/x-pack/legacy/plugins/ml/public/application/components/job_selector/job_select_service_utils.js deleted file mode 100644 index 7f5c146568648..0000000000000 --- a/x-pack/legacy/plugins/ml/public/application/components/job_selector/job_select_service_utils.js +++ /dev/null @@ -1,261 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { difference, isEqual } from 'lodash'; -import { BehaviorSubject } from 'rxjs'; -import { toastNotifications } from 'ui/notify'; -import { i18n } from '@kbn/i18n'; -import moment from 'moment'; -import d3 from 'd3'; - -import { mlJobService } from '../../services/job_service'; - -function warnAboutInvalidJobIds(invalidIds) { - if (invalidIds.length > 0) { - toastNotifications.addWarning( - i18n.translate('xpack.ml.jobSelect.requestedJobsDoesNotExistWarningMessage', { - defaultMessage: `Requested -{invalidIdsLength, plural, one {job {invalidIds} does not exist} other {jobs {invalidIds} do not exist}}`, - values: { - invalidIdsLength: invalidIds.length, - invalidIds, - }, - }) - ); - } -} - -// check that the ids read from the url exist by comparing them to the -// jobs loaded via mlJobsService. -function getInvalidJobIds(ids) { - return ids.filter(id => { - const jobExists = mlJobService.jobs.some(job => job.job_id === id); - return jobExists === false && id !== '*'; - }); -} - -export const jobSelectServiceFactory = globalState => { - const { jobIds, selectedGroups } = getSelectedJobIds(globalState); - const jobSelectService$ = new BehaviorSubject({ - selection: jobIds, - groups: selectedGroups, - resetSelection: false, - }); - - // Subscribe to changes to globalState and trigger - // a jobSelectService update if the job selection changed. - const listener = () => { - const { jobIds: newJobIds, selectedGroups: newSelectedGroups } = getSelectedJobIds(globalState); - const oldSelectedJobIds = jobSelectService$.getValue().selection; - - if (newJobIds && !isEqual(oldSelectedJobIds, newJobIds)) { - jobSelectService$.next({ selection: newJobIds, groups: newSelectedGroups }); - } - }; - - globalState.on('save_with_changes', listener); - - const unsubscribeFromGlobalState = () => { - globalState.off('save_with_changes', listener); - }; - - return { jobSelectService$, unsubscribeFromGlobalState }; -}; - -function loadJobIdsFromGlobalState(globalState) { - // jobIds, groups - // fetch to get the latest state - globalState.fetch(); - - const jobIds = []; - let groups = []; - - if (globalState.ml && globalState.ml.jobIds) { - let tempJobIds = []; - groups = globalState.ml.groups || []; - - if (typeof globalState.ml.jobIds === 'string') { - tempJobIds.push(globalState.ml.jobIds); - } else { - tempJobIds = globalState.ml.jobIds; - } - tempJobIds = tempJobIds.map(id => String(id)); - - const invalidIds = getInvalidJobIds(tempJobIds); - warnAboutInvalidJobIds(invalidIds); - - let validIds = difference(tempJobIds, invalidIds); - // if there are no valid ids, warn and then select the first job - if (validIds.length === 0) { - toastNotifications.addWarning( - i18n.translate('xpack.ml.jobSelect.noJobsSelectedWarningMessage', { - defaultMessage: 'No jobs selected, auto selecting first job', - }) - ); - - if (mlJobService.jobs.length) { - validIds = [mlJobService.jobs[0].job_id]; - } - } - jobIds.push(...validIds); - } else { - // no jobs selected, use the first in the list - if (mlJobService.jobs.length) { - jobIds.push(mlJobService.jobs[0].job_id); - } - } - return { jobIds, selectedGroups: groups }; -} - -// TODO: -// Merge `setGlobalStateSkipRefresh()` and `setGlobalState()` into -// a single function similar to how we do `appStateHandler()`. -// When changing jobs in job selector it would trigger multiple events -// which in return would be consumed by Single Metric Viewer and could cause -// race conditions when updating the whole page. Because we don't control -// the internals of the involved timefilter event triggering, we use -// a global `skipRefresh` to control when Single Metric Viewer should -// skip updates triggered by timefilter. -export function setGlobalStateSkipRefresh(globalState, skipRefresh) { - globalState.fetch(); - if (globalState.ml === undefined) { - globalState.ml = {}; - } - globalState.ml.skipRefresh = skipRefresh; - globalState.save(); -} - -export function setGlobalState(globalState, { selectedIds, selectedGroups, skipRefresh }) { - globalState.fetch(); - if (globalState.ml === undefined) { - globalState.ml = {}; - } - globalState.ml.jobIds = selectedIds; - globalState.ml.groups = selectedGroups || []; - globalState.ml.skipRefresh = !!skipRefresh; - globalState.save(); -} - -// called externally to retrieve the selected jobs ids -export function getSelectedJobIds(globalState) { - return loadJobIdsFromGlobalState(globalState); -} - -export function getGroupsFromJobs(jobs) { - const groups = {}; - const groupsMap = {}; - - jobs.forEach(job => { - // Organize job by group - if (job.groups !== undefined) { - job.groups.forEach(g => { - if (groups[g] === undefined) { - groups[g] = { - id: g, - jobIds: [job.job_id], - timeRange: { - to: job.timeRange.to, - toMoment: null, - from: job.timeRange.from, - fromMoment: null, - fromPx: job.timeRange.fromPx, - toPx: job.timeRange.toPx, - widthPx: null, - }, - }; - - groupsMap[g] = [job.job_id]; - } else { - groups[g].jobIds.push(job.job_id); - groupsMap[g].push(job.job_id); - // keep track of earliest 'from' / latest 'to' for group range - if (groups[g].timeRange.to === null || job.timeRange.to > groups[g].timeRange.to) { - groups[g].timeRange.to = job.timeRange.to; - groups[g].timeRange.toMoment = job.timeRange.toMoment; - } - if (groups[g].timeRange.from === null || job.timeRange.from < groups[g].timeRange.from) { - groups[g].timeRange.from = job.timeRange.from; - groups[g].timeRange.fromMoment = job.timeRange.fromMoment; - } - if (groups[g].timeRange.toPx === null || job.timeRange.toPx > groups[g].timeRange.toPx) { - groups[g].timeRange.toPx = job.timeRange.toPx; - } - if ( - groups[g].timeRange.fromPx === null || - job.timeRange.fromPx < groups[g].timeRange.fromPx - ) { - groups[g].timeRange.fromPx = job.timeRange.fromPx; - } - } - }); - } - }); - - Object.keys(groups).forEach(groupId => { - const group = groups[groupId]; - group.timeRange.widthPx = group.timeRange.toPx - group.timeRange.fromPx; - group.timeRange.toMoment = moment(group.timeRange.to); - group.timeRange.fromMoment = moment(group.timeRange.from); - // create label - const fromString = group.timeRange.fromMoment.format('MMM Do YYYY, HH:mm'); - const toString = group.timeRange.toMoment.format('MMM Do YYYY, HH:mm'); - group.timeRange.label = i18n.translate('xpack.ml.jobSelectList.groupTimeRangeLabel', { - defaultMessage: '{fromString} to {toString}', - values: { - fromString, - toString, - }, - }); - }); - - return { groups: Object.keys(groups).map(g => groups[g]), groupsMap }; -} - -export function normalizeTimes(jobs, dateFormatTz, ganttBarWidth) { - const jobsWithTimeRange = jobs.filter(job => { - return job.timeRange.to !== undefined && job.timeRange.from !== undefined; - }); - - const min = Math.min(...jobsWithTimeRange.map(job => +job.timeRange.from)); - const max = Math.max(...jobsWithTimeRange.map(job => +job.timeRange.to)); - const ganttScale = d3.scale - .linear() - .domain([min, max]) - .range([1, ganttBarWidth]); - - jobs.forEach(job => { - if (job.timeRange.to !== undefined && job.timeRange.from !== undefined) { - job.timeRange.fromPx = ganttScale(job.timeRange.from); - job.timeRange.toPx = ganttScale(job.timeRange.to); - job.timeRange.widthPx = job.timeRange.toPx - job.timeRange.fromPx; - // Ensure at least 1 px in width so it's always visible - if (job.timeRange.widthPx < 1) { - job.timeRange.widthPx = 1; - } - - job.timeRange.toMoment = moment(job.timeRange.to).tz(dateFormatTz); - job.timeRange.fromMoment = moment(job.timeRange.from).tz(dateFormatTz); - - const fromString = job.timeRange.fromMoment.format('MMM Do YYYY, HH:mm'); - const toString = job.timeRange.toMoment.format('MMM Do YYYY, HH:mm'); - job.timeRange.label = i18n.translate('xpack.ml.jobSelector.jobTimeRangeLabel', { - defaultMessage: '{fromString} to {toString}', - values: { - fromString, - toString, - }, - }); - } else { - job.timeRange.widthPx = 0; - job.timeRange.fromPx = 0; - job.timeRange.toPx = 0; - job.timeRange.label = i18n.translate('xpack.ml.jobSelector.noResultsForJobLabel', { - defaultMessage: 'No results', - }); - } - }); - return jobs; -} diff --git a/x-pack/legacy/plugins/ml/public/application/components/job_selector/job_select_service_utils.ts b/x-pack/legacy/plugins/ml/public/application/components/job_selector/job_select_service_utils.ts new file mode 100644 index 0000000000000..1484f0a391b67 --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/application/components/job_selector/job_select_service_utils.ts @@ -0,0 +1,156 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import moment from 'moment'; +import d3 from 'd3'; + +import { Dictionary } from '../../../../common/types/common'; +import { MlJobWithTimeRange } from '../../../../common/types/jobs'; + +export function getGroupsFromJobs(jobs: MlJobWithTimeRange[]) { + const groups: Dictionary = {}; + const groupsMap: Dictionary = {}; + + jobs.forEach(job => { + // Organize job by group + if (job.groups !== undefined) { + job.groups.forEach(g => { + if (groups[g] === undefined) { + groups[g] = { + id: g, + jobIds: [job.job_id], + timeRange: { + to: job.timeRange.to, + toMoment: null, + from: job.timeRange.from, + fromMoment: null, + fromPx: job.timeRange.fromPx, + toPx: job.timeRange.toPx, + widthPx: null, + }, + }; + + groupsMap[g] = [job.job_id]; + } else { + groups[g].jobIds.push(job.job_id); + groupsMap[g].push(job.job_id); + // keep track of earliest 'from' / latest 'to' for group range + if (groups[g].timeRange.to === null || job.timeRange.to > groups[g].timeRange.to) { + groups[g].timeRange.to = job.timeRange.to; + groups[g].timeRange.toMoment = job.timeRange.toMoment; + } + if (groups[g].timeRange.from === null || job.timeRange.from < groups[g].timeRange.from) { + groups[g].timeRange.from = job.timeRange.from; + groups[g].timeRange.fromMoment = job.timeRange.fromMoment; + } + if (groups[g].timeRange.toPx === null || job.timeRange.toPx > groups[g].timeRange.toPx) { + groups[g].timeRange.toPx = job.timeRange.toPx; + } + if ( + groups[g].timeRange.fromPx === null || + job.timeRange.fromPx < groups[g].timeRange.fromPx + ) { + groups[g].timeRange.fromPx = job.timeRange.fromPx; + } + } + }); + } + }); + + Object.keys(groups).forEach(groupId => { + const group = groups[groupId]; + group.timeRange.widthPx = group.timeRange.toPx - group.timeRange.fromPx; + group.timeRange.toMoment = moment(group.timeRange.to); + group.timeRange.fromMoment = moment(group.timeRange.from); + // create label + const fromString = group.timeRange.fromMoment.format('MMM Do YYYY, HH:mm'); + const toString = group.timeRange.toMoment.format('MMM Do YYYY, HH:mm'); + group.timeRange.label = i18n.translate('xpack.ml.jobSelectList.groupTimeRangeLabel', { + defaultMessage: '{fromString} to {toString}', + values: { + fromString, + toString, + }, + }); + }); + + return { groups: Object.keys(groups).map(g => groups[g]), groupsMap }; +} + +export function getTimeRangeFromSelection(jobs: MlJobWithTimeRange[], selection: string[]) { + if (jobs.length > 0) { + const times: number[] = []; + jobs.forEach(job => { + if (selection.includes(job.job_id)) { + if (job.timeRange.from !== undefined) { + times.push(job.timeRange.from); + } + if (job.timeRange.to !== undefined) { + times.push(job.timeRange.to); + } + } + }); + if (times.length) { + const extent = d3.extent(times); + const selectedTime = { + from: moment(extent[0]).toISOString(), + to: moment(extent[1]).toISOString(), + }; + return selectedTime; + } + } +} + +export function normalizeTimes( + jobs: MlJobWithTimeRange[], + dateFormatTz: string, + ganttBarWidth: number +) { + const jobsWithTimeRange = jobs.filter(job => { + return job.timeRange.to !== undefined && job.timeRange.from !== undefined; + }); + + const min = Math.min(...jobsWithTimeRange.map(job => +job.timeRange.from)); + const max = Math.max(...jobsWithTimeRange.map(job => +job.timeRange.to)); + const ganttScale = d3.scale + .linear() + .domain([min, max]) + .range([1, ganttBarWidth]); + + jobs.forEach(job => { + if (job.timeRange.to !== undefined && job.timeRange.from !== undefined) { + job.timeRange.fromPx = ganttScale(job.timeRange.from); + job.timeRange.toPx = ganttScale(job.timeRange.to); + job.timeRange.widthPx = job.timeRange.toPx - job.timeRange.fromPx; + // Ensure at least 1 px in width so it's always visible + if (job.timeRange.widthPx < 1) { + job.timeRange.widthPx = 1; + } + + job.timeRange.toMoment = moment(job.timeRange.to).tz(dateFormatTz); + job.timeRange.fromMoment = moment(job.timeRange.from).tz(dateFormatTz); + + const fromString = job.timeRange.fromMoment.format('MMM Do YYYY, HH:mm'); + const toString = job.timeRange.toMoment.format('MMM Do YYYY, HH:mm'); + job.timeRange.label = i18n.translate('xpack.ml.jobSelector.jobTimeRangeLabel', { + defaultMessage: '{fromString} to {toString}', + values: { + fromString, + toString, + }, + }); + } else { + job.timeRange.widthPx = 0; + job.timeRange.fromPx = 0; + job.timeRange.toPx = 0; + job.timeRange.label = i18n.translate('xpack.ml.jobSelector.noResultsForJobLabel', { + defaultMessage: 'No results', + }); + } + }); + return jobs; +} diff --git a/x-pack/legacy/plugins/ml/public/application/components/job_selector/job_selector.js b/x-pack/legacy/plugins/ml/public/application/components/job_selector/job_selector.js deleted file mode 100644 index b86118c451bb7..0000000000000 --- a/x-pack/legacy/plugins/ml/public/application/components/job_selector/job_selector.js +++ /dev/null @@ -1,431 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { isEqual } from 'lodash'; -import React, { useState, useEffect, useRef, useCallback } from 'react'; -import { PropTypes } from 'prop-types'; -import moment from 'moment'; - -import { ml } from '../../services/ml_api_service'; -import { JobSelectorTable } from './job_selector_table'; -import { IdBadges } from './id_badges'; -import { NewSelectionIdBadges } from './new_selection_id_badges'; -import { timefilter } from 'ui/timefilter'; -import { - getGroupsFromJobs, - normalizeTimes, - setGlobalState, - setGlobalStateSkipRefresh, -} from './job_select_service_utils'; -import { toastNotifications } from 'ui/notify'; -import { - EuiButton, - EuiButtonEmpty, - EuiFlexItem, - EuiFlexGroup, - EuiFlyout, - EuiFlyoutBody, - EuiFlyoutFooter, - EuiFlyoutHeader, - EuiSwitch, - EuiTitle, -} from '@elastic/eui'; -import { i18n } from '@kbn/i18n'; - -function mergeSelection(jobIds, groupObjs, singleSelection) { - if (singleSelection) { - return jobIds; - } - - const selectedIds = []; - const alreadySelected = []; - - groupObjs.forEach(group => { - selectedIds.push(group.groupId); - alreadySelected.push(...group.jobIds); - }); - - jobIds.forEach(jobId => { - // Add jobId if not already included in group selection - if (alreadySelected.includes(jobId) === false) { - selectedIds.push(jobId); - } - }); - - return selectedIds; -} - -function getInitialGroupsMap(selectedGroups) { - const map = {}; - - if (selectedGroups.length) { - selectedGroups.forEach(group => { - map[group.groupId] = group.jobIds; - }); - } - - return map; -} - -const BADGE_LIMIT = 10; -const DEFAULT_GANTT_BAR_WIDTH = 299; // pixels - -export function JobSelector({ - dateFormatTz, - globalState, - jobSelectService$, - selectedJobIds, - selectedGroups, - singleSelection, - timeseriesOnly, -}) { - const [jobs, setJobs] = useState([]); - const [groups, setGroups] = useState([]); - const [maps, setMaps] = useState({ groupsMap: getInitialGroupsMap(selectedGroups), jobsMap: {} }); - const [selectedIds, setSelectedIds] = useState( - mergeSelection(selectedJobIds, selectedGroups, singleSelection) - ); - const [newSelection, setNewSelection] = useState( - mergeSelection(selectedJobIds, selectedGroups, singleSelection) - ); - const [showAllBadges, setShowAllBadges] = useState(false); - const [showAllBarBadges, setShowAllBarBadges] = useState(false); - const [applyTimeRange, setApplyTimeRange] = useState(true); - const [isFlyoutVisible, setIsFlyoutVisible] = useState(false); - const [ganttBarWidth, setGanttBarWidth] = useState(DEFAULT_GANTT_BAR_WIDTH); - const flyoutEl = useRef(null); - - useEffect(() => { - // listen for update from Single Metric Viewer - const subscription = jobSelectService$.subscribe(({ selection, resetSelection }) => { - if (resetSelection === true) { - setSelectedIds(selection); - } - }); - - return function cleanup() { - subscription.unsubscribe(); - }; - }, []); // eslint-disable-line - - // Ensure current selected ids always show up in flyout - useEffect(() => { - setNewSelection(selectedIds); - }, [isFlyoutVisible]); // eslint-disable-line - - // Wrap handleResize in useCallback as it is a dependency for useEffect on line 131 below. - // Not wrapping it would cause this dependency to change on every render - const handleResize = useCallback(() => { - if (jobs.length > 0 && flyoutEl && flyoutEl.current && flyoutEl.current.flyout) { - // get all cols in flyout table - const tableHeaderCols = flyoutEl.current.flyout.querySelectorAll('table thead th'); - // get the width of the last col - const derivedWidth = tableHeaderCols[tableHeaderCols.length - 1].offsetWidth - 16; - const normalizedJobs = normalizeTimes(jobs, dateFormatTz, derivedWidth); - setJobs(normalizedJobs); - const { groups: updatedGroups } = getGroupsFromJobs(normalizedJobs); - setGroups(updatedGroups); - setGanttBarWidth(derivedWidth); - } - }, [dateFormatTz, jobs]); - - useEffect(() => { - // Ensure ganttBar width gets calculated on resize - window.addEventListener('resize', handleResize); - - return () => { - window.removeEventListener('resize', handleResize); - }; - }, [handleResize]); - - useEffect(() => { - handleResize(); - }, [handleResize, jobs]); - - // On opening and closing the flyout, optionally update a global `skipRefresh` flag. - // This allows us to circumvent race conditions which could happen by triggering both - // timefilter and job selector related events in Single Metric Viewer. - function closeFlyout(setSkipRefresh = true) { - setIsFlyoutVisible(false); - if (setSkipRefresh) { - setGlobalStateSkipRefresh(globalState, false); - } - } - - function showFlyout(setSkipRefresh = true) { - setIsFlyoutVisible(true); - if (setSkipRefresh) { - setGlobalStateSkipRefresh(globalState, true); - } - } - - function handleJobSelectionClick() { - showFlyout(); - - ml.jobs - .jobsWithTimerange(dateFormatTz) - .then(resp => { - const normalizedJobs = normalizeTimes(resp.jobs, dateFormatTz, DEFAULT_GANTT_BAR_WIDTH); - const { groups: groupsWithTimerange, groupsMap } = getGroupsFromJobs(normalizedJobs); - setJobs(normalizedJobs); - setGroups(groupsWithTimerange); - setMaps({ groupsMap, jobsMap: resp.jobsMap }); - }) - .catch(err => { - console.log('Error fetching jobs', err); - toastNotifications.addDanger({ - title: i18n.translate('xpack.ml.jobSelector.jobFetchErrorMessage', { - defaultMessage: 'An error occurred fetching jobs. Refresh and try again.', - }), - }); - }); - } - - function handleNewSelection({ selectionFromTable }) { - setNewSelection(selectionFromTable); - } - - function applySelection() { - // allNewSelection will be a list of all job ids (including those from groups) selected from the table - const allNewSelection = []; - const groupSelection = []; - - newSelection.forEach(id => { - if (maps.groupsMap[id] !== undefined) { - // Push all jobs from selected groups into the newSelection list - allNewSelection.push(...maps.groupsMap[id]); - // if it's a group - push group obj to set in global state - groupSelection.push({ groupId: id, jobIds: maps.groupsMap[id] }); - } else { - allNewSelection.push(id); - } - }); - // create a Set to remove duplicate values - const allNewSelectionUnique = Array.from(new Set(allNewSelection)); - - const isPrevousSelection = isEqual( - { selectedJobIds, selectedGroups }, - { selectedJobIds: allNewSelectionUnique, selectedGroups: groupSelection } - ); - - setSelectedIds(newSelection); - setNewSelection([]); - - // If the job selection is unchanged, then we close the modal and - // disable skipping the timefilter listener flag in globalState. - // If the job selection changed, this will not - // update skipRefresh yet to avoid firing multiple events via - // applyTimeRangeFromSelection() and setGlobalState(). - closeFlyout(isPrevousSelection); - - // If the job selection changed, then when - // calling `applyTimeRangeFromSelection()` here - // Single Metric Viewer will skip an update - // triggered by timefilter to avoid a race - // condition caused by the job update listener - // that's also going to be triggered. - applyTimeRangeFromSelection(allNewSelectionUnique); - - // Set `skipRefresh` again to `false` here so after - // both the time range and jobs have been updated - // Single Metric Viewer should again update itself. - setGlobalState(globalState, { - selectedIds: allNewSelectionUnique, - selectedGroups: groupSelection, - skipRefresh: false, - }); - } - - function applyTimeRangeFromSelection(selection) { - if (applyTimeRange && jobs.length > 0) { - const times = []; - jobs.forEach(job => { - if (selection.includes(job.job_id)) { - if (job.timeRange.from !== undefined) { - times.push(job.timeRange.from); - } - if (job.timeRange.to !== undefined) { - times.push(job.timeRange.to); - } - } - }); - if (times.length) { - const min = Math.min(...times); - const max = Math.max(...times); - timefilter.setTime({ - from: moment(min).toISOString(), - to: moment(max).toISOString(), - }); - } - } - } - - function toggleTimerangeSwitch() { - setApplyTimeRange(!applyTimeRange); - } - - function removeId(id) { - setNewSelection(newSelection.filter(item => item !== id)); - } - - function clearSelection() { - setNewSelection([]); - } - - function renderJobSelectionBar() { - return ( - - - - setShowAllBarBadges(!showAllBarBadges)} - selectedIds={selectedIds} - showAllBarBadges={showAllBarBadges} - /> - - - - - {i18n.translate('xpack.ml.jobSelector.jobSelectionButton', { - defaultMessage: 'Edit job selection', - })} - - - - ); - } - - function renderFlyout() { - if (isFlyoutVisible) { - return ( - - - -

- {i18n.translate('xpack.ml.jobSelector.flyoutTitle', { - defaultMessage: 'Job selection', - })} -

-
-
- - - - - setShowAllBadges(!showAllBadges)} - showAllBadges={showAllBadges} - /> - - - - - - {!singleSelection && newSelection.length > 0 && ( - - {i18n.translate('xpack.ml.jobSelector.clearAllFlyoutButton', { - defaultMessage: 'Clear all', - })} - - )} - - - - - - - - - - - - - - {i18n.translate('xpack.ml.jobSelector.applyFlyoutButton', { - defaultMessage: 'Apply', - })} - - - - - {i18n.translate('xpack.ml.jobSelector.closeFlyoutButton', { - defaultMessage: 'Close', - })} - - - - -
- ); - } - } - - return ( -
- {selectedIds.length > 0 && renderJobSelectionBar()} - {renderFlyout()} -
- ); -} - -JobSelector.propTypes = { - globalState: PropTypes.object, - jobSelectService$: PropTypes.object, - selectedJobIds: PropTypes.array, - singleSelection: PropTypes.bool, - timeseriesOnly: PropTypes.bool, -}; diff --git a/x-pack/legacy/plugins/ml/public/application/components/job_selector/job_selector.tsx b/x-pack/legacy/plugins/ml/public/application/components/job_selector/job_selector.tsx new file mode 100644 index 0000000000000..f1d9dcb0ec795 --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/application/components/job_selector/job_selector.tsx @@ -0,0 +1,393 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useState, useEffect, useRef, useCallback } from 'react'; +import PropTypes from 'prop-types'; + +import { + EuiButton, + EuiButtonEmpty, + EuiFlexItem, + EuiFlexGroup, + EuiFlyout, + EuiFlyoutBody, + EuiFlyoutFooter, + EuiFlyoutHeader, + EuiSwitch, + EuiTitle, +} from '@elastic/eui'; + +import { i18n } from '@kbn/i18n'; + +import { toastNotifications } from 'ui/notify'; + +import { Dictionary } from '../../../../common/types/common'; +import { MlJobWithTimeRange } from '../../../../common/types/jobs'; +import { ml } from '../../services/ml_api_service'; +import { useUrlState } from '../../util/url_state'; +// @ts-ignore +import { JobSelectorTable } from './job_selector_table'; +// @ts-ignore +import { IdBadges } from './id_badges'; +// @ts-ignore +import { NewSelectionIdBadges } from './new_selection_id_badges'; +import { + getGroupsFromJobs, + getTimeRangeFromSelection, + normalizeTimes, +} from './job_select_service_utils'; + +interface GroupObj { + groupId: string; + jobIds: string[]; +} +function mergeSelection( + jobIds: string[], + groupObjs: GroupObj[], + singleSelection: boolean +): string[] { + if (singleSelection) { + return jobIds; + } + + const selectedIds: string[] = []; + const alreadySelected: string[] = []; + + groupObjs.forEach(group => { + selectedIds.push(group.groupId); + alreadySelected.push(...group.jobIds); + }); + + jobIds.forEach(jobId => { + // Add jobId if not already included in group selection + if (alreadySelected.includes(jobId) === false) { + selectedIds.push(jobId); + } + }); + + return selectedIds; +} + +type GroupsMap = Dictionary; +function getInitialGroupsMap(selectedGroups: GroupObj[]): GroupsMap { + const map: GroupsMap = {}; + + if (selectedGroups.length) { + selectedGroups.forEach(group => { + map[group.groupId] = group.jobIds; + }); + } + + return map; +} + +const BADGE_LIMIT = 10; +const DEFAULT_GANTT_BAR_WIDTH = 299; // pixels + +interface JobSelectorProps { + dateFormatTz: string; + singleSelection: boolean; + timeseriesOnly: boolean; +} + +export function JobSelector({ dateFormatTz, singleSelection, timeseriesOnly }: JobSelectorProps) { + const [globalState, setGlobalState] = useUrlState('_g'); + + const selectedJobIds = globalState?.ml?.jobIds ?? []; + const selectedGroups = globalState?.ml?.groups ?? []; + + const [jobs, setJobs] = useState([]); + const [groups, setGroups] = useState([]); + const [maps, setMaps] = useState({ groupsMap: getInitialGroupsMap(selectedGroups), jobsMap: {} }); + const [selectedIds, setSelectedIds] = useState( + mergeSelection(selectedJobIds, selectedGroups, singleSelection) + ); + const [newSelection, setNewSelection] = useState( + mergeSelection(selectedJobIds, selectedGroups, singleSelection) + ); + const [showAllBadges, setShowAllBadges] = useState(false); + const [showAllBarBadges, setShowAllBarBadges] = useState(false); + const [applyTimeRange, setApplyTimeRange] = useState(true); + const [isFlyoutVisible, setIsFlyoutVisible] = useState(false); + const [ganttBarWidth, setGanttBarWidth] = useState(DEFAULT_GANTT_BAR_WIDTH); + const flyoutEl = useRef<{ flyout: HTMLElement }>(null); + + // Ensure JobSelectionBar gets updated when selection via globalState changes. + useEffect(() => { + setSelectedIds(mergeSelection(selectedJobIds, selectedGroups, singleSelection)); + }, [JSON.stringify([selectedJobIds, selectedGroups])]); + + // Ensure current selected ids always show up in flyout + useEffect(() => { + setNewSelection(selectedIds); + }, [isFlyoutVisible]); // eslint-disable-line + + // Wrap handleResize in useCallback as it is a dependency for useEffect on line 131 below. + // Not wrapping it would cause this dependency to change on every render + const handleResize = useCallback(() => { + if (jobs.length > 0 && flyoutEl && flyoutEl.current && flyoutEl.current.flyout) { + // get all cols in flyout table + const tableHeaderCols: NodeListOf = flyoutEl.current.flyout.querySelectorAll( + 'table thead th' + ); + // get the width of the last col + const derivedWidth = tableHeaderCols[tableHeaderCols.length - 1].offsetWidth - 16; + const normalizedJobs = normalizeTimes(jobs, dateFormatTz, derivedWidth); + setJobs(normalizedJobs); + const { groups: updatedGroups } = getGroupsFromJobs(normalizedJobs); + setGroups(updatedGroups); + setGanttBarWidth(derivedWidth); + } + }, [dateFormatTz, jobs]); + + useEffect(() => { + // Ensure ganttBar width gets calculated on resize + window.addEventListener('resize', handleResize); + + return () => { + window.removeEventListener('resize', handleResize); + }; + }, [handleResize]); + + useEffect(() => { + handleResize(); + }, [handleResize, jobs]); + + function closeFlyout() { + setIsFlyoutVisible(false); + } + + function showFlyout() { + setIsFlyoutVisible(true); + } + + function handleJobSelectionClick() { + showFlyout(); + + ml.jobs + .jobsWithTimerange(dateFormatTz) + .then(resp => { + const normalizedJobs = normalizeTimes(resp.jobs, dateFormatTz, DEFAULT_GANTT_BAR_WIDTH); + const { groups: groupsWithTimerange, groupsMap } = getGroupsFromJobs(normalizedJobs); + setJobs(normalizedJobs); + setGroups(groupsWithTimerange); + setMaps({ groupsMap, jobsMap: resp.jobsMap }); + }) + .catch((err: any) => { + console.error('Error fetching jobs with time range', err); // eslint-disable-line + toastNotifications.addDanger({ + title: i18n.translate('xpack.ml.jobSelector.jobFetchErrorMessage', { + defaultMessage: 'An error occurred fetching jobs. Refresh and try again.', + }), + }); + }); + } + + function handleNewSelection({ selectionFromTable }: { selectionFromTable: any }) { + setNewSelection(selectionFromTable); + } + + function applySelection() { + // allNewSelection will be a list of all job ids (including those from groups) selected from the table + const allNewSelection: string[] = []; + const groupSelection: Array<{ groupId: string; jobIds: string[] }> = []; + + newSelection.forEach(id => { + if (maps.groupsMap[id] !== undefined) { + // Push all jobs from selected groups into the newSelection list + allNewSelection.push(...maps.groupsMap[id]); + // if it's a group - push group obj to set in global state + groupSelection.push({ groupId: id, jobIds: maps.groupsMap[id] }); + } else { + allNewSelection.push(id); + } + }); + // create a Set to remove duplicate values + const allNewSelectionUnique = Array.from(new Set(allNewSelection)); + + setSelectedIds(newSelection); + setNewSelection([]); + + closeFlyout(); + + const time = applyTimeRange + ? getTimeRangeFromSelection(jobs, allNewSelectionUnique) + : undefined; + + setGlobalState({ + ml: { + jobIds: allNewSelectionUnique, + groups: groupSelection, + }, + ...(time !== undefined ? { time } : {}), + }); + } + + function toggleTimerangeSwitch() { + setApplyTimeRange(!applyTimeRange); + } + + function removeId(id: string) { + setNewSelection(newSelection.filter(item => item !== id)); + } + + function clearSelection() { + setNewSelection([]); + } + + function renderJobSelectionBar() { + return ( + + + + setShowAllBarBadges(!showAllBarBadges)} + selectedIds={selectedIds} + showAllBarBadges={showAllBarBadges} + /> + + + + + {i18n.translate('xpack.ml.jobSelector.jobSelectionButton', { + defaultMessage: 'Edit job selection', + })} + + + + ); + } + + function renderFlyout() { + if (isFlyoutVisible) { + return ( + + + +

+ {i18n.translate('xpack.ml.jobSelector.flyoutTitle', { + defaultMessage: 'Job selection', + })} +

+
+
+ + + + + setShowAllBadges(!showAllBadges)} + showAllBadges={showAllBadges} + /> + + + + + + {!singleSelection && newSelection.length > 0 && ( + + {i18n.translate('xpack.ml.jobSelector.clearAllFlyoutButton', { + defaultMessage: 'Clear all', + })} + + )} + + + + + + + + + + + + + + {i18n.translate('xpack.ml.jobSelector.applyFlyoutButton', { + defaultMessage: 'Apply', + })} + + + + + {i18n.translate('xpack.ml.jobSelector.closeFlyoutButton', { + defaultMessage: 'Close', + })} + + + + +
+ ); + } + } + + return ( +
+ {selectedIds.length > 0 && renderJobSelectionBar()} + {renderFlyout()} +
+ ); +} + +JobSelector.propTypes = { + selectedJobIds: PropTypes.array, + singleSelection: PropTypes.bool, + timeseriesOnly: PropTypes.bool, +}; diff --git a/x-pack/legacy/plugins/ml/public/application/components/job_selector/use_job_selection.ts b/x-pack/legacy/plugins/ml/public/application/components/job_selector/use_job_selection.ts new file mode 100644 index 0000000000000..563156ea98055 --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/application/components/job_selector/use_job_selection.ts @@ -0,0 +1,88 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { difference } from 'lodash'; +import { useEffect } from 'react'; + +import { toastNotifications } from 'ui/notify'; +import { i18n } from '@kbn/i18n'; + +import { MlJobWithTimeRange } from '../../../../common/types/jobs'; + +import { useUrlState } from '../../util/url_state'; + +import { getTimeRangeFromSelection } from './job_select_service_utils'; + +// check that the ids read from the url exist by comparing them to the +// jobs loaded via mlJobsService. +function getInvalidJobIds(jobs: MlJobWithTimeRange[], ids: string[]) { + return ids.filter(id => { + const jobExists = jobs.some(job => job.job_id === id); + return jobExists === false && id !== '*'; + }); +} + +function warnAboutInvalidJobIds(invalidIds: string[]) { + if (invalidIds.length > 0) { + toastNotifications.addWarning( + i18n.translate('xpack.ml.jobSelect.requestedJobsDoesNotExistWarningMessage', { + defaultMessage: `Requested +{invalidIdsLength, plural, one {job {invalidIds} does not exist} other {jobs {invalidIds} do not exist}}`, + values: { + invalidIdsLength: invalidIds.length, + invalidIds: invalidIds.join(), + }, + }) + ); + } +} + +export interface JobSelection { + jobIds: string[]; + selectedGroups: string[]; +} + +export const useJobSelection = (jobs: MlJobWithTimeRange[], dateFormatTz: string) => { + const [globalState, setGlobalState] = useUrlState('_g'); + + const jobSelection: JobSelection = { jobIds: [], selectedGroups: [] }; + + const ids = globalState?.ml?.jobIds || []; + const tmpIds = (typeof ids === 'string' ? [ids] : ids).map((id: string) => String(id)); + const invalidIds = getInvalidJobIds(jobs, tmpIds); + const validIds = difference(tmpIds, invalidIds); + validIds.sort(); + + jobSelection.jobIds = validIds; + jobSelection.selectedGroups = globalState?.ml?.groups ?? []; + + useEffect(() => { + warnAboutInvalidJobIds(invalidIds); + }, [invalidIds]); + + useEffect(() => { + // if there are no valid ids, warn and then select the first job + if (validIds.length === 0 && jobs.length > 0) { + toastNotifications.addWarning( + i18n.translate('xpack.ml.jobSelect.noJobsSelectedWarningMessage', { + defaultMessage: 'No jobs selected, auto selecting first job', + }) + ); + + const mlGlobalState = globalState?.ml || {}; + mlGlobalState.jobIds = [jobs[0].job_id]; + + const time = getTimeRangeFromSelection(jobs, mlGlobalState.jobIds); + + setGlobalState({ + ...{ ml: mlGlobalState }, + ...(time !== undefined ? { time } : {}), + }); + } + }, [jobs, validIds]); + + return jobSelection; +}; diff --git a/x-pack/legacy/plugins/ml/public/application/components/navigation_menu/tabs.tsx b/x-pack/legacy/plugins/ml/public/application/components/navigation_menu/tabs.tsx index 20fa2cca41231..ac83d598f2382 100644 --- a/x-pack/legacy/plugins/ml/public/application/components/navigation_menu/tabs.tsx +++ b/x-pack/legacy/plugins/ml/public/application/components/navigation_menu/tabs.tsx @@ -5,8 +5,14 @@ */ import React, { FC, useState } from 'react'; +import { encode } from 'rison-node'; + import { EuiTabs, EuiTab, EuiLink } from '@elastic/eui'; + import { i18n } from '@kbn/i18n'; + +import { useUrlState } from '../../util/url_state'; + import { Tab } from './main_tabs'; import { TabId } from './navigation_menu'; @@ -67,6 +73,7 @@ enum TAB_TEST_SUBJECT { type TAB_TEST_SUBJECTS = keyof typeof TAB_TEST_SUBJECT; export const Tabs: FC = ({ tabId, mainTabId, disableLinks }) => { + const [globalState] = useUrlState('_g'); const [selectedTabId, setSelectedTabId] = useState(tabId); function onSelectedTabChanged(id: string) { setSelectedTabId(id); @@ -78,12 +85,16 @@ export const Tabs: FC = ({ tabId, mainTabId, disableLinks }) => { {tabs.map((tab: Tab) => { const id = tab.id; + // globalState (e.g. selected jobs and time range) should be retained when changing pages. + // appState will not be considered. + const fullGlobalStateString = globalState !== undefined ? `?_g=${encode(globalState)}` : ''; + return ( diff --git a/x-pack/legacy/plugins/ml/public/application/components/navigation_menu/top_nav/top_nav.tsx b/x-pack/legacy/plugins/ml/public/application/components/navigation_menu/top_nav/top_nav.tsx index 523970dfe12f8..ca6146f3e23b5 100644 --- a/x-pack/legacy/plugins/ml/public/application/components/navigation_menu/top_nav/top_nav.tsx +++ b/x-pack/legacy/plugins/ml/public/application/components/navigation_menu/top_nav/top_nav.tsx @@ -6,11 +6,14 @@ import React, { FC, Fragment, useState, useEffect } from 'react'; import { Subscription } from 'rxjs'; -import { EuiSuperDatePicker } from '@elastic/eui'; +import { EuiSuperDatePicker, OnRefreshProps } from '@elastic/eui'; import { TimeHistory } from 'ui/timefilter'; import { TimeRange } from 'src/plugins/data/public'; -import { mlTimefilterRefresh$ } from '../../../services/timefilter_refresh_service'; +import { + mlTimefilterRefresh$, + mlTimefilterTimeChange$, +} from '../../../services/timefilter_refresh_service'; import { useUiContext } from '../../../contexts/ui/use_ui_context'; interface Duration { @@ -29,6 +32,10 @@ function getRecentlyUsedRangesFactory(timeHistory: TimeHistory) { }; } +function updateLastRefresh(timeRange: OnRefreshProps) { + mlTimefilterRefresh$.next({ lastRefresh: Date.now(), timeRange }); +} + export const TopNav: FC = () => { const { chrome, timefilter, timeHistory } = useUiContext(); const getRecentlyUsedRanges = getRecentlyUsedRangesFactory(timeHistory); @@ -74,6 +81,7 @@ export const TopNav: FC = () => { timefilter.setTime(newTime); setTime(newTime); setRecentlyUsedRanges(getRecentlyUsedRanges()); + mlTimefilterTimeChange$.next({ lastRefresh: Date.now(), timeRange: { start, end } }); } function updateInterval({ @@ -104,7 +112,7 @@ export const TopNav: FC = () => { isAutoRefreshOnly={!isTimeRangeSelectorEnabled} refreshInterval={refreshInterval.value} onTimeChange={updateFilter} - onRefresh={() => mlTimefilterRefresh$.next()} + onRefresh={updateLastRefresh} onRefreshChange={updateInterval} recentlyUsedRanges={recentlyUsedRanges} dateFormat={dateFormat} diff --git a/x-pack/legacy/plugins/ml/public/application/components/rule_editor/__snapshots__/rule_editor_flyout.test.js.snap b/x-pack/legacy/plugins/ml/public/application/components/rule_editor/__snapshots__/rule_editor_flyout.test.js.snap index 4486899efb001..3b14025e9cb7b 100644 --- a/x-pack/legacy/plugins/ml/public/application/components/rule_editor/__snapshots__/rule_editor_flyout.test.js.snap +++ b/x-pack/legacy/plugins/ml/public/application/components/rule_editor/__snapshots__/rule_editor_flyout.test.js.snap @@ -101,7 +101,7 @@ exports[`RuleEditorFlyout renders the flyout after adding a condition to a rule values={ Object { "learnMoreLink": @@ -150,7 +150,7 @@ exports[`ValidateJob renders the button and modal with a success message 1`] = ` values={ Object { "mlJobTipsLink": ({ ui: { iconType: defaultIconType, - isLoading: false, + isLoading: true, isModalVisible: false, }, data: { @@ -150,6 +153,14 @@ Callout.propTypes = { }), }; +const LoadingSpinner = () => ( + + + + + +); + const Modal = ({ close, title, children }) => ( @@ -249,10 +260,11 @@ export class ValidateJob extends Component { const isDisabled = this.props.isDisabled !== true ? false : true; const embedded = this.props.embedded === true; const idFilterList = this.props.idFilterList || []; + const isLoading = this.state.ui.isLoading; return ( - {embedded === false && ( + {embedded === false ? (
} > - + {isLoading ? ( + + ) : ( + + )} )}
- )} - {embedded === true && ( - + ) : ( + + {isLoading ? ( + + ) : ( + + )} + )}
); diff --git a/x-pack/legacy/plugins/ml/public/application/contexts/kibana/__mocks__/index_pattern.ts b/x-pack/legacy/plugins/ml/public/application/contexts/kibana/__mocks__/index_pattern.ts index a9f30a904d54b..6a85c18de6d31 100644 --- a/x-pack/legacy/plugins/ml/public/application/contexts/kibana/__mocks__/index_pattern.ts +++ b/x-pack/legacy/plugins/ml/public/application/contexts/kibana/__mocks__/index_pattern.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IndexPattern } from 'ui/index_patterns'; +import { IndexPattern } from '../../../../../../../../../src/plugins/data/public'; export const indexPatternMock = ({ id: 'the-index-pattern-id', diff --git a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/common/analytics.ts b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/common/analytics.ts index cadc1f01c6dda..ce832513c4adc 100644 --- a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/common/analytics.ts +++ b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/common/analytics.ts @@ -58,6 +58,23 @@ export enum INDEX_STATUS { ERROR, } +export interface FieldSelectionItem { + name: string; + mappings_types: string[]; + is_included: boolean; + is_required: boolean; + feature_type?: string; + reason?: string; +} + +export interface DfAnalyticsExplainResponse { + field_selection: FieldSelectionItem[]; + memory_estimation: { + expected_memory_without_disk: string; + expected_memory_with_disk: string; + }; +} + export interface Eval { meanSquaredError: number | string; rSquared: number | string; @@ -357,6 +374,7 @@ interface LoadEvalDataConfig { searchQuery?: ResultsSearchQuery; ignoreDefaultQuery?: boolean; jobType: ANALYSIS_CONFIG_TYPE; + requiresKeyword?: boolean; } export const loadEvalData = async ({ @@ -368,6 +386,7 @@ export const loadEvalData = async ({ searchQuery, ignoreDefaultQuery, jobType, + requiresKeyword, }: LoadEvalDataConfig) => { const results: LoadEvaluateResult = { success: false, eval: null, error: null }; const defaultPredictionField = `${dependentVariable}_prediction`; @@ -375,7 +394,7 @@ export const loadEvalData = async ({ predictionFieldName ? predictionFieldName : defaultPredictionField }`; - if (jobType === ANALYSIS_CONFIG_TYPE.CLASSIFICATION) { + if (jobType === ANALYSIS_CONFIG_TYPE.CLASSIFICATION && requiresKeyword === true) { predictedField = `${predictedField}.keyword`; } diff --git a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_exploration/evaluate_panel.tsx b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_exploration/evaluate_panel.tsx index ddf52943c2feb..68ed2c08d0df1 100644 --- a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_exploration/evaluate_panel.tsx +++ b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_exploration/components/classification_exploration/evaluate_panel.tsx @@ -8,6 +8,7 @@ import React, { FC, useState, useEffect, Fragment } from 'react'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { + EuiButtonEmpty, EuiDataGrid, EuiFlexGroup, EuiFlexItem, @@ -18,6 +19,7 @@ import { EuiText, EuiTitle, } from '@elastic/eui'; +import { metadata } from 'ui/metadata'; import { ErrorCallout } from '../error_callout'; import { getDependentVar, @@ -35,8 +37,13 @@ import { ResultsSearchQuery, ANALYSIS_CONFIG_TYPE, } from '../../../../common/analytics'; +import { IIndexPattern } from '../../../../../../../../../../../src/plugins/data/common/index_patterns'; +import { ES_FIELD_TYPES } from '../../../../../../../../../../../src/plugins/data/public'; import { LoadingPanel } from '../loading_panel'; import { getColumnData } from './column_data'; +import { useKibanaContext } from '../../../../../contexts/kibana'; +import { newJobCapsService } from '../../../../../services/new_job_capabilities_service'; +import { getIndexPatternIdFromName } from '../../../../../util/index_utils'; const defaultPanelWidth = 500; @@ -55,17 +62,19 @@ export const EvaluatePanel: FC = ({ jobConfig, jobStatus, searchQuery }) const [docsCount, setDocsCount] = useState(null); const [error, setError] = useState(null); const [panelWidth, setPanelWidth] = useState(defaultPanelWidth); - // Column visibility const [visibleColumns, setVisibleColumns] = useState(() => columns.map(({ id }: { id: string }) => id) ); + const kibanaContext = useKibanaContext(); const index = jobConfig.dest.index; + const sourceIndex = jobConfig.source.index[0]; const dependentVariable = getDependentVar(jobConfig.analysis); const predictionFieldName = getPredictionFieldName(jobConfig.analysis); // default is 'ml' const resultsField = jobConfig.dest.results_field; + let requiresKeyword = false; const loadData = async ({ isTrainingClause, @@ -76,6 +85,31 @@ export const EvaluatePanel: FC = ({ jobConfig, jobStatus, searchQuery }) }) => { setIsLoading(true); + try { + const indexPatternId = getIndexPatternIdFromName(sourceIndex) || sourceIndex; + const indexPattern: IIndexPattern = await kibanaContext.indexPatterns.get(indexPatternId); + + if (indexPattern !== undefined) { + await newJobCapsService.initializeFromIndexPattern(indexPattern, false, false); + // If dependent_variable is of type keyword and text .keyword suffix is required for evaluate endpoint + const { fields } = newJobCapsService; + const depVarFieldType = fields.find(field => field.name === dependentVariable)?.type; + + // If it's a keyword type - check if it has a corresponding text type + if (depVarFieldType !== undefined && depVarFieldType === ES_FIELD_TYPES.KEYWORD) { + const field = newJobCapsService.getFieldById(dependentVariable.replace(/\.keyword$/, '')); + requiresKeyword = field !== null && field.type === ES_FIELD_TYPES.TEXT; + } else if (depVarFieldType !== undefined && depVarFieldType === ES_FIELD_TYPES.TEXT) { + // If text, check if has corresponding keyword type + const field = newJobCapsService.getFieldById(`${dependentVariable}.keyword`); + requiresKeyword = field !== null && field.type === ES_FIELD_TYPES.KEYWORD; + } + } + } catch (e) { + // Additional error handling due to missing field type is handled by loadEvalData + console.error('Unable to load new field types', error); // eslint-disable-line no-console + } + const evalData = await loadEvalData({ isTraining: false, index, @@ -85,6 +119,7 @@ export const EvaluatePanel: FC = ({ jobConfig, jobStatus, searchQuery }) searchQuery, ignoreDefaultQuery, jobType: ANALYSIS_CONFIG_TYPE.CLASSIFICATION, + requiresKeyword, }); const docsCountResp = await loadDocsCount({ @@ -210,7 +245,7 @@ export const EvaluatePanel: FC = ({ jobConfig, jobStatus, searchQuery }) - + @@ -227,6 +262,25 @@ export const EvaluatePanel: FC = ({ jobConfig, jobStatus, searchQuery }) {getTaskStateBadge(jobStatus)} + + + + + + {i18n.translate( + 'xpack.ml.dataframe.analytics.classificationExploration.classificationDocsLink', + { + defaultMessage: 'Classification evaluation docs ', + } + )} + + {error !== null && ( @@ -294,28 +348,18 @@ export const EvaluatePanel: FC = ({ jobConfig, jobStatus, searchQuery }) - - - - - - - - - - - - - + + + - + = ({ jobConfig, jobStatus, searchQuery }) return ( - + @@ -238,6 +247,25 @@ export const EvaluatePanel: FC = ({ jobConfig, jobStatus, searchQuery }) {getTaskStateBadge(jobStatus)} + + + + + + {i18n.translate( + 'xpack.ml.dataframe.analytics.classificationExploration.regressionDocsLink', + { + defaultMessage: 'Regression evaluation docs ', + } + )} + + diff --git a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_button/create_analytics_button.test.tsx b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_button/create_analytics_button.test.tsx index 15f30b6cca6c4..abb35e50ec2a2 100644 --- a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_button/create_analytics_button.test.tsx +++ b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_button/create_analytics_button.test.tsx @@ -15,11 +15,6 @@ import { kibanaContextValueMock } from '../../../../../contexts/kibana/__mocks__ import { useCreateAnalyticsForm } from '../../hooks/use_create_analytics_form'; -jest.mock('ui/index_patterns', () => ({ - validateIndexPattern: () => true, - INDEX_PATTERN_ILLEGAL_CHARACTERS: [], -})); - const getMountedHook = () => mountHook( () => useCreateAnalyticsForm(), diff --git a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_flyout/create_analytics_flyout.test.tsx b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_flyout/create_analytics_flyout.test.tsx index 880a1354e7a64..d5d509826667c 100644 --- a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_flyout/create_analytics_flyout.test.tsx +++ b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_flyout/create_analytics_flyout.test.tsx @@ -15,11 +15,6 @@ import { kibanaContextValueMock } from '../../../../../contexts/kibana/__mocks__ import { useCreateAnalyticsForm } from '../../hooks/use_create_analytics_form'; -jest.mock('ui/index_patterns', () => ({ - validateIndexPattern: () => true, - INDEX_PATTERN_ILLEGAL_CHARACTERS: [], -})); - const getMountedHook = () => mountHook( () => useCreateAnalyticsForm(), diff --git a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/create_analytics_form.test.tsx b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/create_analytics_form.test.tsx index 49381e3b1c031..d01bae9616708 100644 --- a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/create_analytics_form.test.tsx +++ b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/create_analytics_form.test.tsx @@ -15,11 +15,6 @@ import { kibanaContextValueMock } from '../../../../../contexts/kibana/__mocks__ import { useCreateAnalyticsForm } from '../../hooks/use_create_analytics_form'; -jest.mock('ui/index_patterns', () => ({ - validateIndexPattern: () => true, - INDEX_PATTERN_ILLEGAL_CHARACTERS: [], -})); - const getMountedHook = () => mountHook( () => useCreateAnalyticsForm(), @@ -45,7 +40,7 @@ describe('Data Frame Analytics: ', () => { ); const euiFormRows = wrapper.find('EuiFormRow'); - expect(euiFormRows.length).toBe(8); + expect(euiFormRows.length).toBe(9); const row1 = euiFormRows.at(0); expect(row1.find('label').text()).toBe('Job type'); diff --git a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/create_analytics_form.tsx b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/create_analytics_form.tsx index e2106e56e0346..e68523733254e 100644 --- a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/create_analytics_form.tsx +++ b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/create_analytics_form.tsx @@ -8,6 +8,7 @@ import React, { Fragment, FC, useEffect } from 'react'; import { EuiComboBox, + EuiComboBoxOptionProps, EuiForm, EuiFieldText, EuiFormRow, @@ -21,10 +22,8 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { metadata } from 'ui/metadata'; -import { IndexPattern, INDEX_PATTERN_ILLEGAL_CHARACTERS } from 'ui/index_patterns'; import { ml } from '../../../../../services/ml_api_service'; -import { Field, EVENT_RATE_FIELD_ID } from '../../../../../../../common/types/fields'; - +import { Field } from '../../../../../../../common/types/fields'; import { newJobCapsService } from '../../../../../services/new_job_capabilities_service'; import { useKibanaContext } from '../../../../../contexts/kibana'; import { CreateAnalyticsFormProps } from '../../hooks/use_create_analytics_form'; @@ -38,32 +37,16 @@ import { Messages } from './messages'; import { JobType } from './job_type'; import { JobDescriptionInput } from './job_description'; import { mmlUnitInvalidErrorMessage } from '../../hooks/use_create_analytics_form/reducer'; - -// based on code used by `ui/index_patterns` internally -// remove the space character from the list of illegal characters -INDEX_PATTERN_ILLEGAL_CHARACTERS.pop(); -const characterList = INDEX_PATTERN_ILLEGAL_CHARACTERS.join(', '); - -const NUMERICAL_FIELD_TYPES = new Set([ - 'long', - 'integer', - 'short', - 'byte', - 'double', - 'float', - 'half_float', - 'scaled_float', -]); - -const SUPPORTED_CLASSIFICATION_FIELD_TYPES = new Set(['boolean', 'text', 'keyword', 'ip']); - -// List of system fields we want to ignore for the numeric field check. -const OMIT_FIELDS: string[] = ['_source', '_type', '_index', '_id', '_version', '_score']; +import { + IndexPattern, + indexPatterns, +} from '../../../../../../../../../../../src/plugins/data/public'; +import { DfAnalyticsExplainResponse, FieldSelectionItem } from '../../../../common/analytics'; +import { shouldAddAsDepVarOption, OMIT_FIELDS } from './form_options_validation'; export const CreateAnalyticsForm: FC = ({ actions, state }) => { const { setFormState } = actions; const kibanaContext = useKibanaContext(); - const { form, indexPatternsMap, isAdvancedEditorEnabled, isJobCreated, requestMessages } = state; const { @@ -77,15 +60,22 @@ export const CreateAnalyticsForm: FC = ({ actions, sta destinationIndexNameExists, destinationIndexNameValid, destinationIndexPatternTitleExists, + excludes, + excludesOptions, + fieldOptionsFetchFail, jobId, jobIdEmpty, jobIdExists, jobIdValid, jobIdInvalidMaxLength, jobType, - loadingDepFieldOptions, + loadingDepVarOptions, + loadingFieldOptions, + maxDistinctValuesError, modelMemoryLimit, modelMemoryLimitUnitValid, + previousJobType, + previousSourceIndex, sourceIndex, sourceIndexNameEmpty, sourceIndexNameValid, @@ -93,6 +83,10 @@ export const CreateAnalyticsForm: FC = ({ actions, sta sourceIndexFieldsCheckFailed, trainingPercent, } = form; + const characterList = indexPatterns.ILLEGAL_CHARACTERS_VISIBLE.join(', '); + + const isJobTypeWithDepVar = + jobType === JOB_TYPES.REGRESSION || jobType === JOB_TYPES.CLASSIFICATION; // Find out if index pattern contain numeric fields. Provides a hint in the form // that an analytics jobs is not able to identify outliers if there are no numeric fields present. @@ -116,42 +110,99 @@ export const CreateAnalyticsForm: FC = ({ actions, sta } }; - // Regression supports numeric fields. Classification supports numeric, boolean, text, keyword and ip. - const shouldAddFieldOption = (field: Field) => { - if (field.id === EVENT_RATE_FIELD_ID) return false; + const onCreateOption = (searchValue: string, flattenedOptions: EuiComboBoxOptionProps[]) => { + const normalizedSearchValue = searchValue.trim().toLowerCase(); - const isNumerical = NUMERICAL_FIELD_TYPES.has(field.type); - const isSupportedByClassification = - isNumerical || SUPPORTED_CLASSIFICATION_FIELD_TYPES.has(field.type); + if (!normalizedSearchValue) { + return; + } + + const newOption = { + label: searchValue, + }; - if (jobType === JOB_TYPES.REGRESSION) return isNumerical; - if (jobType === JOB_TYPES.CLASSIFICATION) return isNumerical || isSupportedByClassification; + // Create the option if it doesn't exist. + if ( + !flattenedOptions.some( + (option: EuiComboBoxOptionProps) => + option.label.trim().toLowerCase() === normalizedSearchValue + ) + ) { + excludesOptions.push(newOption); + setFormState({ excludes: [...excludes, newOption.label] }); + } }; - const debouncedMmlEstimateLoad = debounce(async () => { + const debouncedGetExplainData = debounce(async () => { + // Reset if sourceIndex or jobType changes (jobType requires dependent_variable to be set - + // which won't be the case if switching from outlier detection) + if (previousSourceIndex !== sourceIndex || previousJobType !== jobType) { + setFormState({ + loadingFieldOptions: true, + }); + } + try { const jobConfig = getJobConfigFromFormState(form); delete jobConfig.dest; delete jobConfig.model_memory_limit; - const resp = await ml.dataFrameAnalytics.estimateDataFrameAnalyticsMemoryUsage(jobConfig); - setFormState({ - modelMemoryLimit: resp.memory_estimation?.expected_memory_without_disk, - }); + delete jobConfig.analyzed_fields; + const resp: DfAnalyticsExplainResponse = await ml.dataFrameAnalytics.explainDataFrameAnalytics( + jobConfig + ); + + // If sourceIndex has changed load analysis field options again + if (previousSourceIndex !== sourceIndex || previousJobType !== jobType) { + const analyzedFieldsOptions: EuiComboBoxOptionProps[] = []; + + if (resp.field_selection) { + resp.field_selection.forEach((selectedField: FieldSelectionItem) => { + if (selectedField.is_included === true && selectedField.name !== dependentVariable) { + analyzedFieldsOptions.push({ label: selectedField.name }); + } + }); + } + + setFormState({ + modelMemoryLimit: resp.memory_estimation?.expected_memory_without_disk, + excludesOptions: analyzedFieldsOptions, + loadingFieldOptions: false, + fieldOptionsFetchFail: false, + maxDistinctValuesError: undefined, + }); + } else { + setFormState({ + modelMemoryLimit: resp.memory_estimation?.expected_memory_without_disk, + }); + } } catch (e) { + let errorMessage; + if ( + jobType === JOB_TYPES.CLASSIFICATION && + e.message !== undefined && + e.message.includes('status_exception') && + e.message.includes('must have at most') + ) { + errorMessage = e.message; + } setFormState({ + fieldOptionsFetchFail: true, + maxDistinctValuesError: errorMessage, + loadingFieldOptions: false, modelMemoryLimit: jobType !== undefined ? DEFAULT_MODEL_MEMORY_LIMIT[jobType] : DEFAULT_MODEL_MEMORY_LIMIT.outlier_detection, }); } - }, 500); + }, 400); - const loadDependentFieldOptions = async () => { + const loadDepVarOptions = async () => { setFormState({ - loadingDepFieldOptions: true, + loadingDepVarOptions: true, + // clear when the source index changes dependentVariable: '', - // Reset outlier detection sourceIndex checks to default values if we've switched to regression + maxDistinctValuesError: undefined, sourceIndexFieldsCheckFailed: false, sourceIndexContainsNumericalFields: true, }); @@ -164,22 +215,23 @@ export const CreateAnalyticsForm: FC = ({ actions, sta await newJobCapsService.initializeFromIndexPattern(indexPattern); // Get fields and filter for supported types for job type const { fields } = newJobCapsService; - const options: Array<{ label: string }> = []; + + const depVarOptions: EuiComboBoxOptionProps[] = []; fields.forEach((field: Field) => { - if (shouldAddFieldOption(field)) { - options.push({ label: field.id }); + if (shouldAddAsDepVarOption(field, jobType)) { + depVarOptions.push({ label: field.id }); } }); setFormState({ - dependentVariableOptions: options, - loadingDepFieldOptions: false, + dependentVariableOptions: depVarOptions, + loadingDepVarOptions: false, dependentVariableFetchFail: false, }); } } catch (e) { - setFormState({ loadingDepFieldOptions: false, dependentVariableFetchFail: true }); + setFormState({ loadingDepVarOptions: false, dependentVariableFetchFail: true }); } }; @@ -211,13 +263,21 @@ export const CreateAnalyticsForm: FC = ({ actions, sta return errors; }; + const onSourceIndexChange = (selectedOptions: EuiComboBoxOptionProps[]) => { + setFormState({ + excludes: [], + excludesOptions: [], + previousSourceIndex: sourceIndex, + sourceIndex: selectedOptions[0].label || '', + }); + }; + useEffect(() => { - if ( - (jobType === JOB_TYPES.REGRESSION || jobType === JOB_TYPES.CLASSIFICATION) && - sourceIndexNameEmpty === false - ) { - loadDependentFieldOptions(); - } else if (jobType === JOB_TYPES.OUTLIER_DETECTION && sourceIndexNameEmpty === false) { + if (isJobTypeWithDepVar && sourceIndexNameEmpty === false) { + loadDepVarOptions(); + } + + if (jobType === JOB_TYPES.OUTLIER_DETECTION && sourceIndexNameEmpty === false) { validateSourceIndexFields(); } }, [sourceIndex, jobType, sourceIndexNameEmpty]); @@ -225,21 +285,18 @@ export const CreateAnalyticsForm: FC = ({ actions, sta useEffect(() => { const hasBasicRequiredFields = jobType !== undefined && sourceIndex !== '' && sourceIndexNameValid === true; - const jobTypesWithDepVar = - jobType === JOB_TYPES.REGRESSION || jobType === JOB_TYPES.CLASSIFICATION; const hasRequiredAnalysisFields = - (jobTypesWithDepVar && dependentVariable !== '' && trainingPercent !== undefined) || - jobType === JOB_TYPES.OUTLIER_DETECTION; + (isJobTypeWithDepVar && dependentVariable !== '') || jobType === JOB_TYPES.OUTLIER_DETECTION; if (hasBasicRequiredFields && hasRequiredAnalysisFields) { - debouncedMmlEstimateLoad(); + debouncedGetExplainData(); } return () => { - debouncedMmlEstimateLoad.cancel(); + debouncedGetExplainData.cancel(); }; - }, [jobType, sourceIndex, dependentVariable, trainingPercent]); + }, [jobType, sourceIndex, sourceIndexNameEmpty, dependentVariable, trainingPercent]); return ( @@ -356,9 +413,7 @@ export const CreateAnalyticsForm: FC = ({ actions, sta selectedOptions={ indexPatternsMap[sourceIndex] !== undefined ? [{ label: sourceIndex }] : [] } - onChange={selectedOptions => - setFormState({ sourceIndex: selectedOptions[0].label || '' }) - } + onChange={onSourceIndexChange} isClearable={false} data-test-subj="mlAnalyticsCreateJobFlyoutSourceIndexSelect" /> @@ -432,6 +487,27 @@ export const CreateAnalyticsForm: FC = ({ actions, sta {(jobType === JOB_TYPES.REGRESSION || jobType === JOB_TYPES.CLASSIFICATION) && ( + + {i18n.translate( + 'xpack.ml.dataframe.analytics.create.dependentVariableMaxDistictValuesError', + { + defaultMessage: 'Invalid. {message}', + values: { message: maxDistinctValuesError }, + } + )} + , + ] + : []), + ]} + > + + = ({ actions, sta } ) } - error={ - dependentVariableFetchFail === true && [ - - {i18n.translate( - 'xpack.ml.dataframe.analytics.create.dependentVariableOptionsFetchError', - { - defaultMessage: - 'There was a problem fetching fields. Please refresh the page and try again.', - } - )} - , - ] - } + isInvalid={maxDistinctValuesError !== undefined} + error={[ + ...(dependentVariableFetchFail === true + ? [ + + {i18n.translate( + 'xpack.ml.dataframe.analytics.create.dependentVariableOptionsFetchError', + { + defaultMessage: + 'There was a problem fetching fields. Please refresh the page and try again.', + } + )} + , + ] + : []), + ]} > = ({ actions, sta } )} isDisabled={isJobCreated} - isLoading={loadingDepFieldOptions} + isLoading={loadingDepVarOptions} singleSelection={true} options={dependentVariableOptions} selectedOptions={dependentVariable ? [{ label: dependentVariable }] : []} onChange={selectedOptions => - setFormState({ dependentVariable: selectedOptions[0].label || '' }) + setFormState({ + dependentVariable: selectedOptions[0].label || '', + }) } isClearable={false} isInvalid={dependentVariable === ''} @@ -510,6 +591,49 @@ export const CreateAnalyticsForm: FC = ({ actions, sta )} + + ({ + label: field, + }))} + onCreateOption={onCreateOption} + onChange={selectedOptions => + setFormState({ excludes: selectedOptions.map(option => option.label) }) + } + isClearable={true} + data-test-subj="mlAnalyticsCreateJobFlyoutExcludesSelect" + /> + { + if (field.id === EVENT_RATE_FIELD_ID) return false; + + const isBasicNumerical = BASIC_NUMERICAL_TYPES.has(field.type); + + const isSupportedByClassification = + isBasicNumerical || CATEGORICAL_TYPES.has(field.type) || field.type === ES_FIELD_TYPES.BOOLEAN; + + if (jobType === JOB_TYPES.REGRESSION) { + return isBasicNumerical || EXTENDED_NUMERICAL_TYPES.has(field.type); + } + if (jobType === JOB_TYPES.CLASSIFICATION) return isSupportedByClassification; +}; diff --git a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/job_type.tsx b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/job_type.tsx index 5d8a74bd10ab6..988daac528fd7 100644 --- a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/job_type.tsx +++ b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/components/create_analytics_form/job_type.tsx @@ -64,7 +64,11 @@ export const JobType: FC = ({ type, setFormState }) => { hasNoInitialSelection={true} onChange={e => { const value = e.target.value as AnalyticsJobType; - setFormState({ jobType: value }); + setFormState({ + previousJobType: type, + jobType: value, + excludes: [], + }); }} data-test-subj="mlAnalyticsCreateJobFlyoutJobTypeSelect" /> diff --git a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.test.ts b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.test.ts index 754f7a1136a97..7ea2f74908e0e 100644 --- a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.test.ts +++ b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.test.ts @@ -12,10 +12,6 @@ import { ACTION } from './actions'; import { reducer, validateAdvancedEditor } from './reducer'; import { getInitialState, JOB_TYPES } from './state'; -jest.mock('ui/index_patterns', () => ({ - validateIndexPattern: () => true, -})); - type SourceIndex = DataFrameAnalyticsConfig['source']['index']; const getMockState = ({ @@ -142,5 +138,11 @@ describe('useCreateAnalyticsForm', () => { validateAdvancedEditor(getMockState({ index: 'the-source-index', modelMemoryLimit: '' })) .isValid ).toBe(false); + // can still run validation check on model_memory_limit if number type + expect( + // @ts-ignore number is not assignable to type string - mml gets converted to string prior to creation + validateAdvancedEditor(getMockState({ index: 'the-source-index', modelMemoryLimit: 100 })) + .isValid + ).toBe(false); }); }); diff --git a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts index 06c8a6c6a8846..f35fa6aa2f451 100644 --- a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts +++ b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/reducer.ts @@ -5,9 +5,6 @@ */ import { i18n } from '@kbn/i18n'; - -import { validateIndexPattern } from 'ui/index_patterns'; - import { isValidIndexName } from '../../../../../../../common/util/es_utils'; import { Action, ACTION } from './actions'; @@ -26,6 +23,7 @@ import { isRegressionAnalysis, isClassificationAnalysis, } from '../../../../common/analytics'; +import { indexPatterns } from '../../../../../../../../../../../src/plugins/data/public'; const mmlAllowedUnitsStr = `${ALLOWED_DATA_UNITS.slice(0, ALLOWED_DATA_UNITS.length - 1).join( ', ' @@ -56,7 +54,15 @@ const getSourceIndexString = (state: State) => { }; export const validateAdvancedEditor = (state: State): State => { - const { jobIdEmpty, jobIdValid, jobIdExists, jobType, createIndexPattern } = state.form; + const { + jobIdEmpty, + jobIdValid, + jobIdExists, + jobType, + createIndexPattern, + excludes, + maxDistinctValuesError, + } = state.form; const { jobConfig } = state; state.advancedEditorMessages = []; @@ -66,9 +72,9 @@ export const validateAdvancedEditor = (state: State): State => { // general check against Kibana index pattern names, but since this is about the advanced editor // with support for arrays in the job config, we also need to check that each individual name // doesn't include a comma if index names are supplied as an array. - // `validateIndexPattern()` returns a map of messages, we're only interested here if it's valid or not. + // `indexPatterns.validate()` returns a map of messages, we're only interested here if it's valid or not. // If there are no messages, it means the index pattern is valid. - let sourceIndexNameValid = Object.keys(validateIndexPattern(sourceIndexName)).length === 0; + let sourceIndexNameValid = Object.keys(indexPatterns.validate(sourceIndexName)).length === 0; const sourceIndex = jobConfig?.source?.index; if (sourceIndexNameValid) { if (typeof sourceIndex === 'string') { @@ -92,6 +98,7 @@ export const validateAdvancedEditor = (state: State): State => { } let dependentVariableEmpty = false; + let excludesValid = true; if ( jobConfig.analysis === undefined && @@ -106,6 +113,20 @@ export const validateAdvancedEditor = (state: State): State => { ) { const dependentVariableName = getDependentVar(jobConfig.analysis) || ''; dependentVariableEmpty = dependentVariableName === ''; + + if (!dependentVariableEmpty && excludes.includes(dependentVariableName)) { + excludesValid = false; + + state.advancedEditorMessages.push({ + error: i18n.translate( + 'xpack.ml.dataframe.analytics.create.advancedEditorMessage.excludesInvalid', + { + defaultMessage: 'The dependent variable cannot be excluded.', + } + ), + message: '', + }); + } } if (sourceIndexNameEmpty) { @@ -184,6 +205,8 @@ export const validateAdvancedEditor = (state: State): State => { } state.isValid = + maxDistinctValuesError === undefined && + excludesValid && state.form.modelMemoryLimitUnitValid && !jobIdEmpty && jobIdValid && @@ -212,6 +235,7 @@ const validateForm = (state: State): State => { destinationIndexPatternTitleExists, createIndexPattern, dependentVariable, + maxDistinctValuesError, modelMemoryLimit, } = state.form; @@ -227,6 +251,7 @@ const validateForm = (state: State): State => { } state.isValid = + maxDistinctValuesError === undefined && !jobTypeEmpty && state.form.modelMemoryLimitUnitValid && !jobIdEmpty && @@ -293,7 +318,7 @@ export function reducer(state: State, action: Action): State { if (action.payload.sourceIndex !== undefined) { newFormState.sourceIndexNameEmpty = newFormState.sourceIndex === ''; - const validationMessages = validateIndexPattern(newFormState.sourceIndex); + const validationMessages = indexPatterns.validate(newFormState.sourceIndex); newFormState.sourceIndexNameValid = Object.keys(validationMessages).length === 0; } diff --git a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts index d29291f8795af..282f9ff45d0ee 100644 --- a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts +++ b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/state.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { EuiComboBoxOptionProps } from '@elastic/eui'; import { DeepPartial } from '../../../../../../../common/types/common'; import { checkPermission } from '../../../../../privilege/check_privilege'; import { mlNodesAvailable } from '../../../../../ml_nodes_check/check_ml_nodes'; @@ -45,22 +46,29 @@ export interface State { createIndexPattern: boolean; dependentVariable: DependentVariable; dependentVariableFetchFail: boolean; - dependentVariableOptions: Array<{ label: DependentVariable }> | []; + dependentVariableOptions: EuiComboBoxOptionProps[] | []; description: string; destinationIndex: EsIndexName; destinationIndexNameExists: boolean; destinationIndexNameEmpty: boolean; destinationIndexNameValid: boolean; destinationIndexPatternTitleExists: boolean; + excludes: string[]; + excludesOptions: EuiComboBoxOptionProps[]; + fieldOptionsFetchFail: boolean; jobId: DataFrameAnalyticsId; jobIdExists: boolean; jobIdEmpty: boolean; jobIdInvalidMaxLength: boolean; jobIdValid: boolean; jobType: AnalyticsJobType; - loadingDepFieldOptions: boolean; + loadingDepVarOptions: boolean; + loadingFieldOptions: boolean; + maxDistinctValuesError: string | undefined; modelMemoryLimit: string | undefined; modelMemoryLimitUnitValid: boolean; + previousJobType: null | AnalyticsJobType; + previousSourceIndex: EsIndexName | undefined; sourceIndex: EsIndexName; sourceIndexNameEmpty: boolean; sourceIndexNameValid: boolean; @@ -96,15 +104,22 @@ export const getInitialState = (): State => ({ destinationIndexNameEmpty: true, destinationIndexNameValid: false, destinationIndexPatternTitleExists: false, + excludes: [], + fieldOptionsFetchFail: false, + excludesOptions: [], jobId: '', jobIdExists: false, jobIdEmpty: true, jobIdInvalidMaxLength: false, jobIdValid: false, jobType: undefined, - loadingDepFieldOptions: false, + loadingDepVarOptions: false, + loadingFieldOptions: false, + maxDistinctValuesError: undefined, modelMemoryLimit: undefined, modelMemoryLimitUnitValid: true, + previousJobType: null, + previousSourceIndex: undefined, sourceIndex: '', sourceIndexNameEmpty: true, sourceIndexNameValid: false, @@ -146,7 +161,7 @@ export const getJobConfigFromFormState = ( index: formState.destinationIndex, }, analyzed_fields: { - excludes: [], + excludes: formState.excludes, }, analysis: { outlier_detection: {}, diff --git a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.test.tsx b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.test.tsx index 6854b439d7382..3298a7d00253f 100644 --- a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.test.tsx +++ b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/hooks/use_create_analytics_form/use_create_analytics_form.test.tsx @@ -12,10 +12,6 @@ import { kibanaContextValueMock } from '../../../../../contexts/kibana/__mocks__ import { getErrorMessage, useCreateAnalyticsForm } from './use_create_analytics_form'; -jest.mock('ui/index_patterns', () => ({ - validateIndexPattern: () => true, -})); - const getMountedHook = () => mountHook( () => useCreateAnalyticsForm(), diff --git a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/services/analytics_service/get_analytics.test.ts b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/services/analytics_service/get_analytics.test.ts index 33a073d7a686e..1aaddda358082 100644 --- a/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/services/analytics_service/get_analytics.test.ts +++ b/x-pack/legacy/plugins/ml/public/application/data_frame_analytics/pages/analytics_management/services/analytics_service/get_analytics.test.ts @@ -8,10 +8,6 @@ import { GetDataFrameAnalyticsStatsResponseOk } from '../../../../../services/ml import { getAnalyticsJobsStats } from './get_analytics'; import { DATA_FRAME_TASK_STATE } from '../../components/analytics_list/common'; -jest.mock('ui/index_patterns', () => ({ - validateIndexPattern: () => true, -})); - describe('get_analytics', () => { test('should get analytics jobs stats', () => { // arrange diff --git a/x-pack/legacy/plugins/ml/public/application/datavisualizer/index_based/components/actions_panel/actions_panel.tsx b/x-pack/legacy/plugins/ml/public/application/datavisualizer/index_based/components/actions_panel/actions_panel.tsx index 1b2f1553585cd..f291118140b9a 100644 --- a/x-pack/legacy/plugins/ml/public/application/datavisualizer/index_based/components/actions_panel/actions_panel.tsx +++ b/x-pack/legacy/plugins/ml/public/application/datavisualizer/index_based/components/actions_panel/actions_panel.tsx @@ -8,11 +8,9 @@ import React, { FC, useState } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; - -import { IndexPattern } from 'ui/index_patterns'; - import { EuiPanel, EuiSpacer, EuiText, EuiTitle, EuiFlexGroup } from '@elastic/eui'; +import { IndexPattern } from '../../../../../../../../../../src/plugins/data/public'; import { CreateJobLinkCard } from '../../../../components/create_job_link_card'; import { DataRecognizer } from '../../../../components/data_recognizer'; diff --git a/x-pack/legacy/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/document_count_chart/document_count_chart.tsx b/x-pack/legacy/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/document_count_chart/document_count_chart.tsx index dfe9eb3fe17b8..9da1235a6becd 100644 --- a/x-pack/legacy/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/document_count_chart/document_count_chart.tsx +++ b/x-pack/legacy/plugins/ml/public/application/datavisualizer/index_based/components/field_data_card/document_count_chart/document_count_chart.tsx @@ -12,8 +12,6 @@ import { Axis, BarSeries, Chart, - getAxisId, - getSpecId, niceTimeFormatter, Position, ScaleType, @@ -76,14 +74,14 @@ export const DocumentCountChart: FC = ({ }} /> - + = ({ width, height, chartData, f tooltip={{ headerFormatter }} /> kibanaFieldFormat(d, fieldFormat)} /> - d.toFixed(3)} - hide={true} - /> + d.toFixed(3)} hide={true} /> { // Obtain the list of non metric field types which appear in the index pattern. let indexedFieldTypes: ML_JOB_FIELD_TYPES[] = []; - const indexPatternFields: FieldType[] = currentIndexPattern.fields; + const indexPatternFields: IFieldType[] = currentIndexPattern.fields; indexPatternFields.forEach(field => { if (field.scripted !== true) { const dataVisualizerType: ML_JOB_FIELD_TYPES | undefined = kbnTypeToMLJobType(field); diff --git a/x-pack/legacy/plugins/ml/public/application/explorer/__tests__/explorer_directive.js b/x-pack/legacy/plugins/ml/public/application/explorer/__tests__/explorer_directive.js deleted file mode 100644 index 4626ee48b53f7..0000000000000 --- a/x-pack/legacy/plugins/ml/public/application/explorer/__tests__/explorer_directive.js +++ /dev/null @@ -1,64 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import ngMock from 'ng_mock'; -import sinon from 'sinon'; -import expect from '@kbn/expect'; - -import { - uiChromeMock, - uiTimefilterMock, - uiTimeHistoryMock, -} from '../../contexts/ui/__mocks__/mocks_mocha'; -import * as useUiContextModule from '../../contexts/ui/use_ui_context'; -import * as UiTimefilterModule from 'ui/timefilter'; - -describe('ML - Anomaly Explorer Directive', () => { - let $scope; - let $compile; - let $element; - let stubContext; - let stubTimefilterFetch; - - beforeEach(ngMock.module('kibana')); - beforeEach(() => { - ngMock.inject(function($injector) { - stubContext = sinon.stub(useUiContextModule, 'useUiContext').callsFake(function fakeFn() { - return { - chrome: uiChromeMock, - timefilter: uiTimefilterMock, - timeHistory: uiTimeHistoryMock, - }; - }); - stubTimefilterFetch = sinon - .stub(UiTimefilterModule.timefilter, 'getFetch$') - .callsFake(uiTimefilterMock.getFetch$); - - $compile = $injector.get('$compile'); - const $rootScope = $injector.get('$rootScope'); - $scope = $rootScope.$new(); - }); - }); - - afterEach(() => { - stubContext.restore(); - stubTimefilterFetch.restore(); - $scope.$destroy(); - }); - - it('Initialize Anomaly Explorer Directive', done => { - ngMock.inject(function() { - expect(() => { - $element = $compile('')($scope); - }).to.not.throwError(); - - // directive has scope: false - const scope = $element.isolateScope(); - expect(scope).to.eql(undefined); - done(); - }); - }); -}); diff --git a/x-pack/legacy/plugins/ml/public/application/explorer/actions/index.ts b/x-pack/legacy/plugins/ml/public/application/explorer/actions/index.ts index 1528a7ce7eee1..a16081892cdc8 100644 --- a/x-pack/legacy/plugins/ml/public/application/explorer/actions/index.ts +++ b/x-pack/legacy/plugins/ml/public/application/explorer/actions/index.ts @@ -5,4 +5,4 @@ */ export { jobSelectionActionCreator } from './job_selection'; -export { loadExplorerData } from './load_explorer_data'; +export { useExplorerData } from './load_explorer_data'; diff --git a/x-pack/legacy/plugins/ml/public/application/explorer/actions/job_selection.ts b/x-pack/legacy/plugins/ml/public/application/explorer/actions/job_selection.ts index 76d66bfbbf12b..994d67bfdb02c 100644 --- a/x-pack/legacy/plugins/ml/public/application/explorer/actions/job_selection.ts +++ b/x-pack/legacy/plugins/ml/public/application/explorer/actions/job_selection.ts @@ -10,13 +10,10 @@ import { map } from 'rxjs/operators'; import { mlFieldFormatService } from '../../services/field_format_service'; import { mlJobService } from '../../services/job_service'; -import { createJobs, RestoredAppState } from '../explorer_utils'; +import { EXPLORER_ACTION } from '../explorer_constants'; +import { createJobs } from '../explorer_utils'; -export function jobSelectionActionCreator( - actionName: string, - selectedJobIds: string[], - { filterData, selectedCells, viewBySwimlaneFieldName }: RestoredAppState -) { +export function jobSelectionActionCreator(selectedJobIds: string[]) { return from(mlFieldFormatService.populateFormats(selectedJobIds)).pipe( map(resp => { if (resp.err) { @@ -32,13 +29,10 @@ export function jobSelectionActionCreator( const selectedJobs = jobs.filter(job => job.selected); return { - type: actionName, + type: EXPLORER_ACTION.JOB_SELECTION_CHANGE, payload: { loading: false, - selectedCells, selectedJobs, - viewBySwimlaneFieldName, - filterData, }, }; }) diff --git a/x-pack/legacy/plugins/ml/public/application/explorer/actions/load_explorer_data.ts b/x-pack/legacy/plugins/ml/public/application/explorer/actions/load_explorer_data.ts index 6d4edd909fa8f..ed73405134224 100644 --- a/x-pack/legacy/plugins/ml/public/application/explorer/actions/load_explorer_data.ts +++ b/x-pack/legacy/plugins/ml/public/application/explorer/actions/load_explorer_data.ts @@ -6,11 +6,12 @@ import memoizeOne from 'memoize-one'; import { isEqual } from 'lodash'; +import useObservable from 'react-use/lib/useObservable'; -import { forkJoin, of } from 'rxjs'; -import { mergeMap, tap } from 'rxjs/operators'; +import { forkJoin, of, Observable, Subject } from 'rxjs'; +import { mergeMap, switchMap, tap } from 'rxjs/operators'; -import { explorerChartsContainerServiceFactory } from '../explorer_charts/explorer_charts_container_service'; +import { anomalyDataChange } from '../explorer_charts/explorer_charts_container_service'; import { VIEW_BY_JOB_LABEL } from '../explorer_constants'; import { explorerService } from '../explorer_dashboard_service'; import { @@ -25,35 +26,82 @@ import { loadTopInfluencers, loadViewBySwimlane, loadViewByTopFieldValuesForSelectedTime, + AppStateSelectedCells, + ExplorerJob, + TimeRangeBounds, } from '../explorer_utils'; import { ExplorerState } from '../reducers'; +// Memoize the data fetching methods. +// wrapWithLastRefreshArg() wraps any given function and preprends a `lastRefresh` argument +// which will be considered by memoizeOne. This way we can add the `lastRefresh` argument as a +// caching parameter without having to change all the original functions which shouldn't care +// about this parameter. The generic type T retains and returns the type information of +// the original function. const memoizeIsEqual = (newArgs: any[], lastArgs: any[]) => isEqual(newArgs, lastArgs); +const wrapWithLastRefreshArg = any>(func: T) => { + return function(lastRefresh: number, ...args: Parameters): ReturnType { + return func.apply(null, args); + }; +}; +const memoize = any>(func: T) => { + return memoizeOne(wrapWithLastRefreshArg(func), memoizeIsEqual); +}; -// Memoize the data fetching methods -// TODO: We need to track an attribute that allows refetching when the date picker -// triggers a refresh, otherwise we'll get back the stale data. Note this was also -// an issue with the previous version and the custom caching done within the component. -const memoizedLoadAnnotationsTableData = memoizeOne(loadAnnotationsTableData, memoizeIsEqual); -const memoizedLoadDataForCharts = memoizeOne(loadDataForCharts, memoizeIsEqual); -const memoizedLoadFilteredTopInfluencers = memoizeOne(loadFilteredTopInfluencers, memoizeIsEqual); -const memoizedLoadOverallData = memoizeOne(loadOverallData, memoizeIsEqual); -const memoizedLoadTopInfluencers = memoizeOne(loadTopInfluencers, memoizeIsEqual); -const memoizedLoadViewBySwimlane = memoizeOne(loadViewBySwimlane, memoizeIsEqual); -const memoizedLoadAnomaliesTableData = memoizeOne(loadAnomaliesTableData, memoizeIsEqual); +const memoizedAnomalyDataChange = memoize(anomalyDataChange); +const memoizedLoadAnnotationsTableData = memoize( + loadAnnotationsTableData +); +const memoizedLoadDataForCharts = memoize(loadDataForCharts); +const memoizedLoadFilteredTopInfluencers = memoize( + loadFilteredTopInfluencers +); +const memoizedLoadOverallData = memoize(loadOverallData); +const memoizedLoadTopInfluencers = memoize(loadTopInfluencers); +const memoizedLoadViewBySwimlane = memoize(loadViewBySwimlane); +const memoizedLoadAnomaliesTableData = memoize(loadAnomaliesTableData); const dateFormatTz = getDateFormatTz(); +export interface LoadExplorerDataConfig { + bounds: TimeRangeBounds; + influencersFilterQuery: any; + lastRefresh: number; + noInfluencersConfigured: boolean; + selectedCells: AppStateSelectedCells | undefined; + selectedJobs: ExplorerJob[]; + swimlaneBucketInterval: any; + swimlaneLimit: number; + tableInterval: string; + tableSeverity: number; + viewBySwimlaneFieldName: string; +} + +export const isLoadExplorerDataConfig = (arg: any): arg is LoadExplorerDataConfig => { + return ( + arg !== undefined && + arg.bounds !== undefined && + arg.selectedJobs !== undefined && + arg.selectedJobs !== null && + arg.viewBySwimlaneFieldName !== undefined + ); +}; + /** * Fetches the data necessary for the Anomaly Explorer using observables. * - * @param state ExplorerState + * @param config LoadExplorerDataConfig * * @return Partial */ -export function loadExplorerData(state: ExplorerState) { +function loadExplorerData(config: LoadExplorerDataConfig): Observable> { + if (!isLoadExplorerDataConfig(config)) { + return of({}); + } + const { bounds, + lastRefresh, influencersFilterQuery, noInfluencersConfigured, selectedCells, @@ -63,19 +111,12 @@ export function loadExplorerData(state: ExplorerState) { tableInterval, tableSeverity, viewBySwimlaneFieldName, - } = state; - - if (selectedJobs === null || bounds === undefined || viewBySwimlaneFieldName === undefined) { - return of({}); - } - - // TODO This factory should be refactored so we can load the charts using memoization. - const updateCharts = explorerChartsContainerServiceFactory(explorerService.setCharts); + } = config; const selectionInfluencers = getSelectionInfluencers(selectedCells, viewBySwimlaneFieldName); const jobIds = - selectedCells !== null && selectedCells.viewByFieldName === VIEW_BY_JOB_LABEL + selectedCells !== undefined && selectedCells.viewByFieldName === VIEW_BY_JOB_LABEL ? selectedCells.lanes : selectedJobs.map(d => d.id); @@ -89,12 +130,14 @@ export function loadExplorerData(state: ExplorerState) { // annotationsData, anomalyChartRecords, influencers, overallState, tableData, topFieldValues return forkJoin({ annotationsData: memoizedLoadAnnotationsTableData( + lastRefresh, selectedCells, selectedJobs, swimlaneBucketInterval.asSeconds(), bounds ), anomalyChartRecords: memoizedLoadDataForCharts( + lastRefresh, jobIds, timerange.earliestMs, timerange.latestMs, @@ -105,6 +148,7 @@ export function loadExplorerData(state: ExplorerState) { influencers: selectionInfluencers.length === 0 ? memoizedLoadTopInfluencers( + lastRefresh, jobIds, timerange.earliestMs, timerange.latestMs, @@ -113,8 +157,14 @@ export function loadExplorerData(state: ExplorerState) { influencersFilterQuery ) : Promise.resolve({}), - overallState: memoizedLoadOverallData(selectedJobs, swimlaneBucketInterval, bounds), + overallState: memoizedLoadOverallData( + lastRefresh, + selectedJobs, + swimlaneBucketInterval, + bounds + ), tableData: memoizedLoadAnomaliesTableData( + lastRefresh, selectedCells, selectedJobs, dateFormatTz, @@ -126,7 +176,7 @@ export function loadExplorerData(state: ExplorerState) { influencersFilterQuery ), topFieldValues: - selectedCells !== null && selectedCells.showTopFieldValues === true + selectedCells !== undefined && selectedCells.showTopFieldValues === true ? loadViewByTopFieldValuesForSelectedTime( timerange.earliestMs, timerange.latestMs, @@ -143,10 +193,22 @@ export function loadExplorerData(state: ExplorerState) { tap(explorerService.setViewBySwimlaneLoading), // Trigger a side-effect to update the charts. tap(({ anomalyChartRecords }) => { - if (selectedCells !== null && Array.isArray(anomalyChartRecords)) { - updateCharts(anomalyChartRecords, timerange.earliestMs, timerange.latestMs); + if (selectedCells !== undefined && Array.isArray(anomalyChartRecords)) { + memoizedAnomalyDataChange( + lastRefresh, + anomalyChartRecords, + timerange.earliestMs, + timerange.latestMs, + tableSeverity + ); } else { - updateCharts([], timerange.earliestMs, timerange.latestMs); + memoizedAnomalyDataChange( + lastRefresh, + [], + timerange.earliestMs, + timerange.latestMs, + tableSeverity + ); } }), // Load view-by swimlane data and filtered top influencers. @@ -161,6 +223,7 @@ export function loadExplorerData(state: ExplorerState) { anomalyChartRecords !== undefined && anomalyChartRecords.length > 0 ? memoizedLoadFilteredTopInfluencers( + lastRefresh, jobIds, timerange.earliestMs, timerange.latestMs, @@ -171,6 +234,7 @@ export function loadExplorerData(state: ExplorerState) { ) : Promise.resolve(influencers), viewBySwimlaneState: memoizedLoadViewBySwimlane( + lastRefresh, topFieldValues, { earliest: overallState.overallSwimlaneData.earliest, @@ -183,7 +247,10 @@ export function loadExplorerData(state: ExplorerState) { noInfluencersConfigured ), }), - ({ annotationsData, overallState, tableData }, { influencers, viewBySwimlaneState }) => { + ( + { annotationsData, overallState, tableData }, + { influencers, viewBySwimlaneState } + ): Partial => { return { annotationsData, influencers, @@ -195,3 +262,13 @@ export function loadExplorerData(state: ExplorerState) { ) ); } + +const loadExplorerData$ = new Subject(); +const explorerData$ = loadExplorerData$.pipe( + switchMap((config: LoadExplorerDataConfig) => loadExplorerData(config)) +); + +export const useExplorerData = (): [Partial | undefined, (d: any) => void] => { + const explorerData = useObservable(explorerData$); + return [explorerData, c => loadExplorerData$.next(c)]; +}; diff --git a/x-pack/legacy/plugins/ml/public/application/explorer/explorer.d.ts b/x-pack/legacy/plugins/ml/public/application/explorer/explorer.d.ts index de58b9228c076..b8df021990f58 100644 --- a/x-pack/legacy/plugins/ml/public/application/explorer/explorer.d.ts +++ b/x-pack/legacy/plugins/ml/public/application/explorer/explorer.d.ts @@ -6,13 +6,17 @@ import { FC } from 'react'; -import { State } from 'ui/state_management/state'; +import { UrlState } from '../util/url_state'; -import { JobSelectService$ } from '../components/job_selector/job_select_service_utils'; +import { JobSelection } from '../components/job_selector/use_job_selection'; + +import { ExplorerState } from '../explorer/reducers'; +import { AppStateSelectedCells } from '../explorer/explorer_utils'; declare interface ExplorerProps { - globalState: State; - jobSelectService$: JobSelectService$; + explorerState: ExplorerState; + showCharts: boolean; + setSelectedCells: (swimlaneSelectedCells: AppStateSelectedCells) => void; } export const Explorer: FC; diff --git a/x-pack/legacy/plugins/ml/public/application/explorer/explorer.js b/x-pack/legacy/plugins/ml/public/application/explorer/explorer.js index bcac1b6405ff8..7907131996578 100644 --- a/x-pack/legacy/plugins/ml/public/application/explorer/explorer.js +++ b/x-pack/legacy/plugins/ml/public/application/explorer/explorer.js @@ -10,9 +10,10 @@ import PropTypes from 'prop-types'; import React, { createRef } from 'react'; -import { FormattedMessage, injectI18n } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; import DragSelect from 'dragselect/dist/ds.min.js'; -import { merge, Subject } from 'rxjs'; +import { Subject } from 'rxjs'; import { map, takeUntil } from 'rxjs/operators'; import { @@ -24,7 +25,6 @@ import { EuiSpacer, } from '@elastic/eui'; -import { annotationsRefresh$ } from '../services/annotations_service'; import { AnnotationFlyout } from '../components/annotations/annotation_flyout'; import { AnnotationsTable } from '../components/annotations/annotations_table'; import { @@ -36,7 +36,6 @@ import { ChartTooltip } from '../components/chart_tooltip'; import { ExplorerSwimlane } from './explorer_swimlane'; import { KqlFilterBar } from '../components/kql_filter_bar'; import { TimeBuckets } from '../util/time_buckets'; -import { getSelectedJobIds } from '../components/job_selector/job_select_service_utils'; import { InfluencersList } from '../components/influencers_list'; import { ALLOW_CELL_RANGE_SELECTION, @@ -45,12 +44,11 @@ import { } from './explorer_dashboard_service'; import { LoadingIndicator } from '../components/loading_indicator/loading_indicator'; import { NavigationMenu } from '../components/navigation_menu'; -import { CheckboxShowCharts, showCharts$ } from '../components/controls/checkbox_showcharts'; +import { CheckboxShowCharts } from '../components/controls/checkbox_showcharts'; import { JobSelector } from '../components/job_selector'; -import { SelectInterval, interval$ } from '../components/controls/select_interval/select_interval'; +import { SelectInterval } from '../components/controls/select_interval/select_interval'; import { SelectLimit, limit$ } from './select_limit/select_limit'; -import { SelectSeverity, severity$ } from '../components/controls/select_severity/select_severity'; -import { injectObservablesAsProps } from '../util/observable_utils'; +import { SelectSeverity } from '../components/controls/select_severity/select_severity'; import { getKqlQueryValues, removeFilterFromQueryString, @@ -58,9 +56,8 @@ import { escapeParens, escapeDoubleQuotes, } from '../components/kql_filter_bar/utils'; -import { mlJobService } from '../services/job_service'; -import { getDateFormatTz, restoreAppState } from './explorer_utils'; +import { getDateFormatTz } from './explorer_utils'; import { getSwimlaneContainerWidth } from './legacy_utils'; import { @@ -80,8 +77,6 @@ import { ResizeChecker } from '../../../../../../../src/plugins/kibana_utils/pub import { timefilter } from 'ui/timefilter'; import { toastNotifications } from 'ui/notify'; -import { mlTimefilterRefresh$ } from '../services/timefilter_refresh_service'; - function mapSwimlaneOptionsToEuiOptions(options) { return options.map(option => ({ value: option, @@ -97,571 +92,484 @@ const ExplorerPage = ({ children, jobSelectorProps, resizeRef }) => (
); -export const Explorer = injectI18n( - injectObservablesAsProps( - { - annotationsRefresh: annotationsRefresh$, - explorerState: explorerService.state$, - showCharts: showCharts$, - }, - class Explorer extends React.Component { - static propTypes = { - annotationsRefresh: PropTypes.bool, - explorerState: PropTypes.object.isRequired, - explorer: PropTypes.object, - globalState: PropTypes.object.isRequired, - jobSelectService$: PropTypes.object.isRequired, - showCharts: PropTypes.bool.isRequired, - }; - - _unsubscribeAll = new Subject(); - // make sure dragSelect is only available if the mouse pointer is actually over a swimlane - disableDragSelectOnMouseLeave = true; - - dragSelect = new DragSelect({ - selectables: document.getElementsByClassName('sl-cell'), - callback(elements) { - if (elements.length > 1 && !ALLOW_CELL_RANGE_SELECTION) { - elements = [elements[0]]; - } - - if (elements.length > 0) { - dragSelect$.next({ - action: DRAG_SELECT_ACTION.NEW_SELECTION, - elements, - }); - } - - this.disableDragSelectOnMouseLeave = true; - }, - onDragStart() { - if (ALLOW_CELL_RANGE_SELECTION) { - dragSelect$.next({ - action: DRAG_SELECT_ACTION.DRAG_START, - }); - this.disableDragSelectOnMouseLeave = false; - } - }, - onElementSelect() { - if (ALLOW_CELL_RANGE_SELECTION) { - dragSelect$.next({ - action: DRAG_SELECT_ACTION.ELEMENT_SELECT, - }); - } - }, - }); - - // Listens to render updates of the swimlanes to update dragSelect - swimlaneRenderDoneListener = () => { - this.dragSelect.clearSelection(); - this.dragSelect.setSelectables(document.getElementsByClassName('sl-cell')); - }; - - resizeRef = createRef(); - resizeChecker = undefined; - resizeHandler = () => { - explorerService.setSwimlaneContainerWidth(getSwimlaneContainerWidth()); - }; - - componentDidMount() { - timefilter.enableTimeRangeSelector(); - timefilter.enableAutoRefreshSelector(); - - explorerService.setBounds(timefilter.getActiveBounds()); - - // Refresh all the data when the time range is altered. - merge(mlTimefilterRefresh$, timefilter.getFetch$()) - .pipe(takeUntil(this._unsubscribeAll)) - .subscribe(() => { - explorerService.setBounds(timefilter.getActiveBounds()); - }); - - limit$ - .pipe( - takeUntil(this._unsubscribeAll), - map(d => d.val) - ) - .subscribe(explorerService.setSwimlaneLimit); - - interval$ - .pipe( - takeUntil(this._unsubscribeAll), - map(d => ({ tableInterval: d.val })) - ) - .subscribe(explorerService.setState); - - severity$ - .pipe( - takeUntil(this._unsubscribeAll), - map(d => ({ tableSeverity: d.val })) - ) - .subscribe(explorerService.setState); - - // Required to redraw the time series chart when the container is resized. - this.resizeChecker = new ResizeChecker(this.resizeRef.current); - this.resizeChecker.on('resize', this.resizeHandler); - - // restore state stored in URL via AppState and subscribe to - // job updates via job selector. - if (mlJobService.jobs.length > 0) { - let initialized = false; - - this.props.jobSelectService$ - .pipe(takeUntil(this._unsubscribeAll)) - .subscribe(({ selection }) => { - if (selection !== undefined) { - if (!initialized) { - explorerService.initialize( - selection, - restoreAppState(this.props.explorerState.appState) - ); - initialized = true; - } else { - explorerService.updateJobSelection( - selection, - restoreAppState(this.props.explorerState.appState) - ); - } - } - }); - } else { - explorerService.clearJobs(); - } +export class Explorer extends React.Component { + static propTypes = { + explorerState: PropTypes.object.isRequired, + setSelectedCells: PropTypes.func.isRequired, + showCharts: PropTypes.bool.isRequired, + }; + + _unsubscribeAll = new Subject(); + // make sure dragSelect is only available if the mouse pointer is actually over a swimlane + disableDragSelectOnMouseLeave = true; + + dragSelect = new DragSelect({ + selectables: document.getElementsByClassName('sl-cell'), + callback(elements) { + if (elements.length > 1 && !ALLOW_CELL_RANGE_SELECTION) { + elements = [elements[0]]; } - componentWillUnmount() { - this._unsubscribeAll.next(); - this._unsubscribeAll.complete(); - this.resizeChecker.destroy(); + if (elements.length > 0) { + dragSelect$.next({ + action: DRAG_SELECT_ACTION.NEW_SELECTION, + elements, + }); } - resetCache() { - this.anomaliesTablePreviousArgs = null; + this.disableDragSelectOnMouseLeave = true; + }, + onDragStart() { + if (ALLOW_CELL_RANGE_SELECTION) { + dragSelect$.next({ + action: DRAG_SELECT_ACTION.DRAG_START, + }); + this.disableDragSelectOnMouseLeave = false; } - - componentDidUpdate() { - // TODO migrate annotations update - if (this.props.annotationsRefresh === true) { - annotationsRefresh$.next(false); - } + }, + onElementSelect() { + if (ALLOW_CELL_RANGE_SELECTION) { + dragSelect$.next({ + action: DRAG_SELECT_ACTION.ELEMENT_SELECT, + }); } - - viewByChangeHandler = e => explorerService.setViewBySwimlaneFieldName(e.target.value); - - isSwimlaneSelectActive = false; - onSwimlaneEnterHandler = () => this.setSwimlaneSelectActive(true); - onSwimlaneLeaveHandler = () => this.setSwimlaneSelectActive(false); - setSwimlaneSelectActive = active => { - if (this.isSwimlaneSelectActive && !active && this.disableDragSelectOnMouseLeave) { - this.dragSelect.stop(); - this.isSwimlaneSelectActive = active; - return; - } - if (!this.isSwimlaneSelectActive && active) { - this.dragSelect.start(); - this.dragSelect.clearSelection(); - this.dragSelect.setSelectables(document.getElementsByClassName('sl-cell')); - this.isSwimlaneSelectActive = active; - } - }; - - // Listener for click events in the swimlane to load corresponding anomaly data. - swimlaneCellClick = selectedCells => { - // If selectedCells is an empty object we clear any existing selection, - // otherwise we save the new selection in AppState and update the Explorer. - if (Object.keys(selectedCells).length === 0) { - explorerService.clearSelection(); - } else { - explorerService.setSelectedCells(selectedCells); - } - }; - // Escape regular parens from fieldName as that portion of the query is not wrapped in double quotes - // and will cause a syntax error when called with getKqlQueryValues - applyFilter = (fieldName, fieldValue, action) => { - const { filterActive, indexPattern, queryString } = this.props.explorerState; - - let newQueryString = ''; - const operator = 'and '; - const sanitizedFieldName = escapeParens(fieldName); - const sanitizedFieldValue = escapeDoubleQuotes(fieldValue); - - if (action === FILTER_ACTION.ADD) { - // Don't re-add if already exists in the query - const queryPattern = getQueryPattern(fieldName, fieldValue); - if (queryString.match(queryPattern) !== null) { - return; - } - newQueryString = `${ - queryString ? `${queryString} ${operator}` : '' - }${sanitizedFieldName}:"${sanitizedFieldValue}"`; - } else if (action === FILTER_ACTION.REMOVE) { - if (filterActive === false) { - return; - } else { - newQueryString = removeFilterFromQueryString( - queryString, - sanitizedFieldName, - sanitizedFieldValue - ); - } - } - - try { - const queryValues = getKqlQueryValues(`${newQueryString}`, indexPattern); - this.applyInfluencersFilterQuery(queryValues); - } catch (e) { - console.log('Invalid kuery syntax', e); // eslint-disable-line no-console - - toastNotifications.addDanger( - this.props.intl.formatMessage({ - id: 'xpack.ml.explorer.invalidKuerySyntaxErrorMessageFromTable', - defaultMessage: - 'Invalid syntax in query bar. The input must be valid Kibana Query Language (KQL)', - }) - ); - } - }; - - applyInfluencersFilterQuery = payload => { - const { filterQuery: influencersFilterQuery } = payload; - - if ( - influencersFilterQuery.match_all && - Object.keys(influencersFilterQuery.match_all).length === 0 - ) { - explorerService.clearInfluencerFilterSettings(); - } else { - explorerService.setInfluencerFilterSettings(payload); - } - }; - - render() { - const { globalState, intl, jobSelectService$, showCharts } = this.props; - - const { - annotationsData, - anomalyChartRecords, - chartsData, - filterActive, - filterPlaceHolder, - indexPattern, - influencers, - loading, - maskAll, - noInfluencersConfigured, - overallSwimlaneData, + }, + }); + + // Listens to render updates of the swimlanes to update dragSelect + swimlaneRenderDoneListener = () => { + this.dragSelect.clearSelection(); + this.dragSelect.setSelectables(document.getElementsByClassName('sl-cell')); + }; + + resizeRef = createRef(); + resizeChecker = undefined; + resizeHandler = () => { + explorerService.setSwimlaneContainerWidth(getSwimlaneContainerWidth()); + }; + + componentDidMount() { + limit$ + .pipe( + takeUntil(this._unsubscribeAll), + map(d => d.val) + ) + .subscribe(explorerService.setSwimlaneLimit); + + // Required to redraw the time series chart when the container is resized. + this.resizeChecker = new ResizeChecker(this.resizeRef.current); + this.resizeChecker.on('resize', this.resizeHandler); + } + + componentWillUnmount() { + this._unsubscribeAll.next(); + this._unsubscribeAll.complete(); + this.resizeChecker.destroy(); + } + + resetCache() { + this.anomaliesTablePreviousArgs = null; + } + + viewByChangeHandler = e => explorerService.setViewBySwimlaneFieldName(e.target.value); + + isSwimlaneSelectActive = false; + onSwimlaneEnterHandler = () => this.setSwimlaneSelectActive(true); + onSwimlaneLeaveHandler = () => this.setSwimlaneSelectActive(false); + setSwimlaneSelectActive = active => { + if (this.isSwimlaneSelectActive && !active && this.disableDragSelectOnMouseLeave) { + this.dragSelect.stop(); + this.isSwimlaneSelectActive = active; + return; + } + if (!this.isSwimlaneSelectActive && active) { + this.dragSelect.start(); + this.dragSelect.clearSelection(); + this.dragSelect.setSelectables(document.getElementsByClassName('sl-cell')); + this.isSwimlaneSelectActive = active; + } + }; + + // Listener for click events in the swimlane to load corresponding anomaly data. + swimlaneCellClick = selectedCells => { + // If selectedCells is an empty object we clear any existing selection, + // otherwise we save the new selection in AppState and update the Explorer. + if (Object.keys(selectedCells).length === 0) { + this.props.setSelectedCells(); + } else { + this.props.setSelectedCells(selectedCells); + } + }; + // Escape regular parens from fieldName as that portion of the query is not wrapped in double quotes + // and will cause a syntax error when called with getKqlQueryValues + applyFilter = (fieldName, fieldValue, action) => { + const { filterActive, indexPattern, queryString } = this.props.explorerState; + + let newQueryString = ''; + const operator = 'and '; + const sanitizedFieldName = escapeParens(fieldName); + const sanitizedFieldValue = escapeDoubleQuotes(fieldValue); + + if (action === FILTER_ACTION.ADD) { + // Don't re-add if already exists in the query + const queryPattern = getQueryPattern(fieldName, fieldValue); + if (queryString.match(queryPattern) !== null) { + return; + } + newQueryString = `${ + queryString ? `${queryString} ${operator}` : '' + }${sanitizedFieldName}:"${sanitizedFieldValue}"`; + } else if (action === FILTER_ACTION.REMOVE) { + if (filterActive === false) { + return; + } else { + newQueryString = removeFilterFromQueryString( queryString, - selectedCells, - selectedJobs, - swimlaneContainerWidth, - tableData, - tableQueryString, - viewByLoadedForTimeFormatted, - viewBySwimlaneData, - viewBySwimlaneDataLoading, - viewBySwimlaneFieldName, - viewBySwimlaneOptions, - } = this.props.explorerState; - - const { jobIds: selectedJobIds, selectedGroups } = getSelectedJobIds(globalState); - const jobSelectorProps = { - dateFormatTz: getDateFormatTz(), - globalState, - jobSelectService$, - selectedJobIds, - selectedGroups, - }; - - const noJobsFound = selectedJobs === null || selectedJobs.length === 0; - const hasResults = overallSwimlaneData.points && overallSwimlaneData.points.length > 0; - - if (loading === true) { - return ( - - - - ); - } - - if (noJobsFound) { - return ( - - - - ); - } - - if (noJobsFound && hasResults === false) { - return ( - - - - ); - } - - const mainColumnWidthClassName = - noInfluencersConfigured === true ? 'col-xs-12' : 'col-xs-10'; - const mainColumnClasses = `column ${mainColumnWidthClassName}`; - - const showOverallSwimlane = - overallSwimlaneData !== null && - overallSwimlaneData.laneLabels && - overallSwimlaneData.laneLabels.length > 0; - const showViewBySwimlane = - viewBySwimlaneData !== null && - viewBySwimlaneData.laneLabels && - viewBySwimlaneData.laneLabels.length > 0; - - return ( - -
- {/* Make sure ChartTooltip is inside this plain wrapping div so positioning can be infered correctly. */} - - - {noInfluencersConfigured === false && influencers !== undefined && ( -
- -
- )} - - {noInfluencersConfigured && ( -
-
- )} - - {noInfluencersConfigured === false && ( -
- - - - -
- )} + sanitizedFieldName, + sanitizedFieldValue + ); + } + } -
- - - + try { + const queryValues = getKqlQueryValues(`${newQueryString}`, indexPattern); + this.applyInfluencersFilterQuery(queryValues); + } catch (e) { + console.log('Invalid kuery syntax', e); // eslint-disable-line no-console + + toastNotifications.addDanger( + i18n.translate('xpack.ml.explorer.invalidKuerySyntaxErrorMessageFromTable', { + defaultMessage: + 'Invalid syntax in query bar. The input must be valid Kibana Query Language (KQL)', + }) + ); + } + }; + + applyInfluencersFilterQuery = payload => { + const { filterQuery: influencersFilterQuery } = payload; + + if ( + influencersFilterQuery.match_all && + Object.keys(influencersFilterQuery.match_all).length === 0 + ) { + explorerService.clearInfluencerFilterSettings(); + } else { + explorerService.setInfluencerFilterSettings(payload); + } + }; + + render() { + const { showCharts } = this.props; + + const { + annotationsData, + chartsData, + filterActive, + filterPlaceHolder, + indexPattern, + influencers, + loading, + maskAll, + noInfluencersConfigured, + overallSwimlaneData, + queryString, + selectedCells, + selectedJobs, + severity, + swimlaneContainerWidth, + tableData, + tableQueryString, + viewByLoadedForTimeFormatted, + viewBySwimlaneData, + viewBySwimlaneDataLoading, + viewBySwimlaneFieldName, + viewBySwimlaneOptions, + } = this.props.explorerState; + + const jobSelectorProps = { + dateFormatTz: getDateFormatTz(), + }; + + const noJobsFound = selectedJobs === null || selectedJobs.length === 0; + const hasResults = overallSwimlaneData.points && overallSwimlaneData.points.length > 0; + + if (loading === true) { + return ( + + + + ); + } -
- {showOverallSwimlane && ( - - )} -
+ if (noJobsFound) { + return ( + + + + ); + } - {viewBySwimlaneOptions.length > 0 && ( - <> - - - - - - - - - - - - - -
- {viewByLoadedForTimeFormatted && ( - - )} - {viewByLoadedForTimeFormatted === undefined && ( - - )} - {filterActive === true && - viewBySwimlaneFieldName === VIEW_BY_JOB_LABEL && ( - - )} -
-
-
-
- - {showViewBySwimlane && ( - <> - -
- -
- - )} - - {viewBySwimlaneDataLoading && } - - {!showViewBySwimlane && - !viewBySwimlaneDataLoading && - viewBySwimlaneFieldName !== null && ( - - )} - - )} + if (noJobsFound && hasResults === false) { + return ( + + + + ); + } - {annotationsData.length > 0 && ( - <> - - - - - - - - )} + const mainColumnWidthClassName = noInfluencersConfigured === true ? 'col-xs-12' : 'col-xs-10'; + const mainColumnClasses = `column ${mainColumnWidthClassName}`; + + const showOverallSwimlane = + overallSwimlaneData !== null && + overallSwimlaneData.laneLabels && + overallSwimlaneData.laneLabels.length > 0; + const showViewBySwimlane = + viewBySwimlaneData !== null && + viewBySwimlaneData.laneLabels && + viewBySwimlaneData.laneLabels.length > 0; + + const bounds = timefilter.getActiveBounds(); + + return ( + +
+ {/* Make sure ChartTooltip is inside this plain wrapping div so positioning can be infered correctly. */} + + + {noInfluencersConfigured === false && influencers !== undefined && ( +
+ +
+ )} + + {noInfluencersConfigured && ( +
+
+ )} + + {noInfluencersConfigured === false && ( +
+ + + + +
+ )} - - - +
+ + + + +
+ {showOverallSwimlane && ( + + )} +
- - + {viewBySwimlaneOptions.length > 0 && ( + <> + + - + - + - + + + + + +
+ {viewByLoadedForTimeFormatted && ( + + )} + {viewByLoadedForTimeFormatted === undefined && ( + + )} + {filterActive === true && viewBySwimlaneFieldName === VIEW_BY_JOB_LABEL && ( + + )} +
- {anomalyChartRecords.length > 0 && selectedCells !== null && ( - - - - - - )}
- + {showViewBySwimlane && ( + <> + +
+ +
+ + )} + + {viewBySwimlaneDataLoading && } -
- {showCharts && } -
+ {!showViewBySwimlane && + !viewBySwimlaneDataLoading && + viewBySwimlaneFieldName !== null && ( + + )} + + )} - 0 && ( + <> + + + + -
+ + + + )} + + + + + + + + + + + + + + + + + {chartsData.seriesToPlot.length > 0 && selectedCells !== undefined && ( + + + + + + )} + + + + +
+ {showCharts && }
- - ); - } - } - ) -); + + +
+
+ + ); + } +} diff --git a/x-pack/legacy/plugins/ml/public/application/explorer/explorer_charts/__snapshots__/explorer_charts_container_service.test.js.snap b/x-pack/legacy/plugins/ml/public/application/explorer/explorer_charts/__snapshots__/explorer_charts_container_service.test.js.snap index df76b049e9837..1c0124b90ae77 100644 --- a/x-pack/legacy/plugins/ml/public/application/explorer/explorer_charts/__snapshots__/explorer_charts_container_service.test.js.snap +++ b/x-pack/legacy/plugins/ml/public/application/explorer/explorer_charts/__snapshots__/explorer_charts_container_service.test.js.snap @@ -1,15 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`explorerChartsContainerService call anomalyChangeListener with actual series config 1`] = ` -Object { - "chartsPerRow": 1, - "seriesToPlot": Array [], - "timeFieldName": "timestamp", - "tooManyBuckets": false, -} -`; - -exports[`explorerChartsContainerService call anomalyChangeListener with actual series config 2`] = ` Object { "chartsPerRow": 1, "seriesToPlot": Array [ @@ -69,7 +60,7 @@ Object { } `; -exports[`explorerChartsContainerService call anomalyChangeListener with actual series config 3`] = ` +exports[`explorerChartsContainerService call anomalyChangeListener with actual series config 2`] = ` Object { "chartsPerRow": 1, "seriesToPlot": Array [ diff --git a/x-pack/legacy/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_distribution.js b/x-pack/legacy/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_distribution.js index 757fd00192fc8..ce819a8d6dc8c 100644 --- a/x-pack/legacy/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_distribution.js +++ b/x-pack/legacy/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_distribution.js @@ -32,7 +32,6 @@ import { LoadingIndicator } from '../../components/loading_indicator/loading_ind import { TimeBuckets } from '../../util/time_buckets'; import { mlFieldFormatService } from '../../services/field_format_service'; import { mlChartTooltipService } from '../../components/chart_tooltip/chart_tooltip_service'; -import { severity$ } from '../../components/controls/select_severity/select_severity'; import { CHART_TYPE } from '../explorer_constants'; @@ -51,6 +50,7 @@ export const ExplorerChartDistribution = injectI18n( class ExplorerChartDistribution extends React.Component { static propTypes = { seriesConfig: PropTypes.object, + severity: PropTypes.number, }; componentDidMount() { @@ -66,6 +66,7 @@ export const ExplorerChartDistribution = injectI18n( const element = this.rootNode; const config = this.props.seriesConfig; + const severity = this.props.severity; if (typeof config === 'undefined' || Array.isArray(config.chartData) === false) { // just return so the empty directive renders without an error later on @@ -400,13 +401,12 @@ export const ExplorerChartDistribution = injectI18n( .on('mouseout', () => mlChartTooltipService.hide()); // Update all dots to new positions. - const threshold = severity$.getValue(); dots .attr('cx', d => lineChartXScale(d.date)) .attr('cy', d => lineChartYScale(d[CHART_Y_ATTRIBUTE])) .attr('class', d => { let markerClass = 'metric-value'; - if (_.has(d, 'anomalyScore') && Number(d.anomalyScore) >= threshold.val) { + if (_.has(d, 'anomalyScore') && Number(d.anomalyScore) >= severity) { markerClass += ' anomaly-marker '; markerClass += getSeverityWithLow(d.anomalyScore).id; } diff --git a/x-pack/legacy/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_single_metric.js b/x-pack/legacy/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_single_metric.js index 5319692b00a38..583375c87007e 100644 --- a/x-pack/legacy/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_single_metric.js +++ b/x-pack/legacy/plugins/ml/public/application/explorer/explorer_charts/explorer_chart_single_metric.js @@ -42,7 +42,6 @@ import { TimeBuckets } from '../../util/time_buckets'; import { mlEscape } from '../../util/string_utils'; import { mlFieldFormatService } from '../../services/field_format_service'; import { mlChartTooltipService } from '../../components/chart_tooltip/chart_tooltip_service'; -import { severity$ } from '../../components/controls/select_severity/select_severity'; import { injectI18n } from '@kbn/i18n/react'; @@ -54,6 +53,7 @@ export const ExplorerChartSingleMetric = injectI18n( static propTypes = { tooManyBuckets: PropTypes.bool, seriesConfig: PropTypes.object, + severity: PropTypes.number, }; componentDidMount() { @@ -69,6 +69,7 @@ export const ExplorerChartSingleMetric = injectI18n( const element = this.rootNode; const config = this.props.seriesConfig; + const severity = this.props.severity; if (typeof config === 'undefined' || Array.isArray(config.chartData) === false) { // just return so the empty directive renders without an error later on @@ -312,13 +313,12 @@ export const ExplorerChartSingleMetric = injectI18n( .on('mouseout', () => mlChartTooltipService.hide()); // Update all dots to new positions. - const threshold = severity$.getValue(); dots .attr('cx', d => lineChartXScale(d.date)) .attr('cy', d => lineChartYScale(d.value)) .attr('class', d => { let markerClass = 'metric-value'; - if (_.has(d, 'anomalyScore') && Number(d.anomalyScore) >= threshold.val) { + if (_.has(d, 'anomalyScore') && Number(d.anomalyScore) >= severity) { markerClass += ` anomaly-marker ${getSeverityWithLow(d.anomalyScore).id}`; } return markerClass; diff --git a/x-pack/legacy/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container.js b/x-pack/legacy/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container.js index 140c5a87056e5..99de38c1e0a84 100644 --- a/x-pack/legacy/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container.js +++ b/x-pack/legacy/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container.js @@ -52,7 +52,7 @@ function getChartId(series) { } // Wrapper for a single explorer chart -function ExplorerChartContainer({ series, tooManyBuckets, wrapLabel }) { +function ExplorerChartContainer({ series, severity, tooManyBuckets, wrapLabel }) { const { detectorLabel, entityFields } = series; const chartType = getChartType(series); @@ -121,10 +121,20 @@ function ExplorerChartContainer({ series, tooManyBuckets, wrapLabel }) { chartType === CHART_TYPE.POPULATION_DISTRIBUTION ) { return ( - + ); } - return ; + return ( + + ); })()} ); @@ -146,7 +156,7 @@ export class ExplorerChartsContainer extends React.Component { } render() { - const { chartsPerRow, seriesToPlot, tooManyBuckets } = this.props; + const { chartsPerRow, seriesToPlot, severity, tooManyBuckets } = this.props; // doesn't allow a setting of `columns={1}` when chartsPerRow would be 1. // If that's the case we trick it doing that with the following settings: @@ -166,6 +176,7 @@ export class ExplorerChartsContainer extends React.Component { > diff --git a/x-pack/legacy/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container.test.js b/x-pack/legacy/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container.test.js index f0b94cb724c57..4b2d307e72c66 100644 --- a/x-pack/legacy/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container.test.js +++ b/x-pack/legacy/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container.test.js @@ -4,6 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ +import React from 'react'; +import { mount, shallow } from 'enzyme'; + +import { I18nProvider } from '@kbn/i18n/react'; + +import { chartLimits } from '../../util/chart_utils'; + +import { getDefaultChartsData } from './explorer_charts_container_service'; +import { ExplorerChartsContainer } from './explorer_charts_container'; + import './explorer_chart_single_metric.test.mocks'; import { chartData } from './__mocks__/mock_chart_data'; import seriesConfig from './__mocks__/mock_series_config_filebeat.json'; @@ -38,17 +48,12 @@ jest.mock( getBasePath: () => { return ''; }, + getInjected: () => true, }), { virtual: true } ); -import { shallowWithIntl, mountWithIntl } from 'test_utils/enzyme_helpers'; -import React from 'react'; - -import { chartLimits } from '../../util/chart_utils'; -import { getDefaultChartsData } from './explorer_charts_container_service'; - -import { ExplorerChartsContainer } from './explorer_charts_container'; +jest.mock('ui/new_platform'); describe('ExplorerChartsContainer', () => { const mockedGetBBox = { x: 0, y: -11.5, width: 12.1875, height: 14.5 }; @@ -58,7 +63,11 @@ describe('ExplorerChartsContainer', () => { afterEach(() => (SVGElement.prototype.getBBox = originalGetBBox)); test('Minimal Initialization', () => { - const wrapper = shallowWithIntl(); + const wrapper = shallow( + + + + ); expect(wrapper.html()).toBe( '
' @@ -78,7 +87,11 @@ describe('ExplorerChartsContainer', () => { chartsPerRow: 1, tooManyBuckets: false, }; - const wrapper = mountWithIntl(); + const wrapper = mount( + + + + ); // We test child components with snapshots separately // so we just do some high level sanity check here. @@ -101,7 +114,11 @@ describe('ExplorerChartsContainer', () => { chartsPerRow: 1, tooManyBuckets: false, }; - const wrapper = mountWithIntl(); + const wrapper = mount( + + + + ); // We test child components with snapshots separately // so we just do some high level sanity check here. diff --git a/x-pack/legacy/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container_service.d.ts b/x-pack/legacy/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container_service.d.ts index ccd52a26f2abc..962072b974867 100644 --- a/x-pack/legacy/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container_service.d.ts +++ b/x-pack/legacy/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container_service.d.ts @@ -13,6 +13,9 @@ export declare interface ExplorerChartsData { export declare const getDefaultChartsData: () => ExplorerChartsData; -export declare const explorerChartsContainerServiceFactory: ( - callback: (data: ExplorerChartsData) => void -) => (anomalyRecords: any[], earliestMs: number, latestMs: number) => void; +export declare const anomalyDataChange: ( + anomalyRecords: any[], + earliestMs: number, + latestMs: number, + severity?: number +) => void; diff --git a/x-pack/legacy/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container_service.js b/x-pack/legacy/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container_service.js index 4aad4fba85746..e0fb97a81f587 100644 --- a/x-pack/legacy/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container_service.js +++ b/x-pack/legacy/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container_service.js @@ -23,8 +23,8 @@ import { } from '../../../../common/util/job_utils'; import { mlResultsService } from '../../services/results_service'; import { mlJobService } from '../../services/job_service'; -import { severity$ } from '../../components/controls/select_severity/select_severity'; import { getChartContainerWidth } from '../legacy_utils'; +import { explorerService } from '../explorer_dashboard_service'; import { CHART_TYPE } from '../explorer_constants'; @@ -38,593 +38,581 @@ export function getDefaultChartsData() { }; } -export function explorerChartsContainerServiceFactory(callback) { - const CHART_MAX_POINTS = 500; - const ANOMALIES_MAX_RESULTS = 500; - const MAX_SCHEDULED_EVENTS = 10; // Max number of scheduled events displayed per bucket. - const ML_TIME_FIELD_NAME = 'timestamp'; - const USE_OVERALL_CHART_LIMITS = false; - const MAX_CHARTS_PER_ROW = 4; - - callback(getDefaultChartsData()); +const CHART_MAX_POINTS = 500; +const ANOMALIES_MAX_RESULTS = 500; +const MAX_SCHEDULED_EVENTS = 10; // Max number of scheduled events displayed per bucket. +const ML_TIME_FIELD_NAME = 'timestamp'; +const USE_OVERALL_CHART_LIMITS = false; +const MAX_CHARTS_PER_ROW = 4; + +// callback(getDefaultChartsData()); + +export const anomalyDataChange = function(anomalyRecords, earliestMs, latestMs, severity = 0) { + const data = getDefaultChartsData(); + + const filteredRecords = anomalyRecords.filter(record => { + return Number(record.record_score) >= severity; + }); + const allSeriesRecords = processRecordsForDisplay(filteredRecords); + // Calculate the number of charts per row, depending on the width available, to a max of 4. + const chartsContainerWidth = getChartContainerWidth(); + let chartsPerRow = Math.min( + Math.max(Math.floor(chartsContainerWidth / 550), 1), + MAX_CHARTS_PER_ROW + ); + if (allSeriesRecords.length === 1) { + chartsPerRow = 1; + } - const anomalyDataChange = function(anomalyRecords, earliestMs, latestMs) { - const data = getDefaultChartsData(); + data.chartsPerRow = chartsPerRow; - const threshold = severity$.getValue(); + // Build the data configs of the anomalies to be displayed. + // TODO - implement paging? + // For now just take first 6 (or 8 if 4 charts per row). + const maxSeriesToPlot = Math.max(chartsPerRow * 2, 6); + const recordsToPlot = allSeriesRecords.slice(0, maxSeriesToPlot); + const seriesConfigs = recordsToPlot.map(buildConfig); - const filteredRecords = anomalyRecords.filter(record => { - return Number(record.record_score) >= threshold.val; - }); - const allSeriesRecords = processRecordsForDisplay(filteredRecords); - // Calculate the number of charts per row, depending on the width available, to a max of 4. - const chartsContainerWidth = getChartContainerWidth(); - let chartsPerRow = Math.min( - Math.max(Math.floor(chartsContainerWidth / 550), 1), - MAX_CHARTS_PER_ROW - ); - if (allSeriesRecords.length === 1) { - chartsPerRow = 1; - } + // Calculate the time range of the charts, which is a function of the chart width and max job bucket span. + data.tooManyBuckets = false; + const chartWidth = Math.floor(chartsContainerWidth / chartsPerRow); + const { chartRange, tooManyBuckets } = calculateChartRange( + seriesConfigs, + earliestMs, + latestMs, + chartWidth, + recordsToPlot, + data.timeFieldName + ); + data.tooManyBuckets = tooManyBuckets; - data.chartsPerRow = chartsPerRow; - - // Build the data configs of the anomalies to be displayed. - // TODO - implement paging? - // For now just take first 6 (or 8 if 4 charts per row). - const maxSeriesToPlot = Math.max(chartsPerRow * 2, 6); - const recordsToPlot = allSeriesRecords.slice(0, maxSeriesToPlot); - const seriesConfigs = recordsToPlot.map(buildConfig); - - // Calculate the time range of the charts, which is a function of the chart width and max job bucket span. - data.tooManyBuckets = false; - const chartWidth = Math.floor(chartsContainerWidth / chartsPerRow); - const { chartRange, tooManyBuckets } = calculateChartRange( - seriesConfigs, - earliestMs, - latestMs, - chartWidth, - recordsToPlot, - data.timeFieldName - ); - data.tooManyBuckets = tooManyBuckets; + // initialize the charts with loading indicators + data.seriesToPlot = seriesConfigs.map(config => ({ + ...config, + loading: true, + chartData: null, + })); - // initialize the charts with loading indicators - data.seriesToPlot = seriesConfigs.map(config => ({ - ...config, - loading: true, - chartData: null, - })); + explorerService.setCharts({ ...data }); - callback(data); + if (seriesConfigs.length === 0) { + return; + } - // Query 1 - load the raw metric data. - function getMetricData(config, range) { - const { jobId, detectorIndex, entityFields, interval } = config; + // Query 1 - load the raw metric data. + function getMetricData(config, range) { + const { jobId, detectorIndex, entityFields, interval } = config; - const job = mlJobService.getJob(jobId); + const job = mlJobService.getJob(jobId); - // If source data can be plotted, use that, otherwise model plot will be available. - const useSourceData = isSourceDataChartableForDetector(job, detectorIndex); - if (useSourceData === true) { - const datafeedQuery = _.get(config, 'datafeedConfig.query', null); - return mlResultsService - .getMetricData( - config.datafeedConfig.indices, - config.entityFields, - datafeedQuery, - config.metricFunction, - config.metricFieldName, - config.timeField, - range.min, - range.max, - config.interval - ) - .toPromise(); - } else { - // Extract the partition, by, over fields on which to filter. - const criteriaFields = []; - const detector = job.analysis_config.detectors[detectorIndex]; - if (_.has(detector, 'partition_field_name')) { - const partitionEntity = _.find(entityFields, { - fieldName: detector.partition_field_name, - }); - if (partitionEntity !== undefined) { - criteriaFields.push( - { fieldName: 'partition_field_name', fieldValue: partitionEntity.fieldName }, - { fieldName: 'partition_field_value', fieldValue: partitionEntity.fieldValue } - ); - } + // If source data can be plotted, use that, otherwise model plot will be available. + const useSourceData = isSourceDataChartableForDetector(job, detectorIndex); + if (useSourceData === true) { + const datafeedQuery = _.get(config, 'datafeedConfig.query', null); + return mlResultsService + .getMetricData( + config.datafeedConfig.indices, + config.entityFields, + datafeedQuery, + config.metricFunction, + config.metricFieldName, + config.timeField, + range.min, + range.max, + config.interval + ) + .toPromise(); + } else { + // Extract the partition, by, over fields on which to filter. + const criteriaFields = []; + const detector = job.analysis_config.detectors[detectorIndex]; + if (_.has(detector, 'partition_field_name')) { + const partitionEntity = _.find(entityFields, { + fieldName: detector.partition_field_name, + }); + if (partitionEntity !== undefined) { + criteriaFields.push( + { fieldName: 'partition_field_name', fieldValue: partitionEntity.fieldName }, + { fieldName: 'partition_field_value', fieldValue: partitionEntity.fieldValue } + ); } + } - if (_.has(detector, 'over_field_name')) { - const overEntity = _.find(entityFields, { fieldName: detector.over_field_name }); - if (overEntity !== undefined) { - criteriaFields.push( - { fieldName: 'over_field_name', fieldValue: overEntity.fieldName }, - { fieldName: 'over_field_value', fieldValue: overEntity.fieldValue } - ); - } + if (_.has(detector, 'over_field_name')) { + const overEntity = _.find(entityFields, { fieldName: detector.over_field_name }); + if (overEntity !== undefined) { + criteriaFields.push( + { fieldName: 'over_field_name', fieldValue: overEntity.fieldName }, + { fieldName: 'over_field_value', fieldValue: overEntity.fieldValue } + ); } + } - if (_.has(detector, 'by_field_name')) { - const byEntity = _.find(entityFields, { fieldName: detector.by_field_name }); - if (byEntity !== undefined) { - criteriaFields.push( - { fieldName: 'by_field_name', fieldValue: byEntity.fieldName }, - { fieldName: 'by_field_value', fieldValue: byEntity.fieldValue } - ); - } + if (_.has(detector, 'by_field_name')) { + const byEntity = _.find(entityFields, { fieldName: detector.by_field_name }); + if (byEntity !== undefined) { + criteriaFields.push( + { fieldName: 'by_field_name', fieldValue: byEntity.fieldName }, + { fieldName: 'by_field_value', fieldValue: byEntity.fieldValue } + ); } - - return new Promise((resolve, reject) => { - const obj = { - success: true, - results: {}, - }; - - return mlResultsService - .getModelPlotOutput( - jobId, - detectorIndex, - criteriaFields, - range.min, - range.max, - interval - ) - .toPromise() - .then(resp => { - // Return data in format required by the explorer charts. - const results = resp.results; - Object.keys(results).forEach(time => { - obj.results[time] = results[time].actual; - }); - resolve(obj); - }) - .catch(resp => { - reject(resp); - }); - }); } - } - // Query 2 - load the anomalies. - // Criteria to return the records for this series are the detector_index plus - // the specific combination of 'entity' fields i.e. the partition / by / over fields. - function getRecordsForCriteria(config, range) { - let criteria = []; - criteria.push({ fieldName: 'detector_index', fieldValue: config.detectorIndex }); - criteria = criteria.concat(config.entityFields); - return mlResultsService - .getRecordsForCriteria( - [config.jobId], - criteria, - 0, - range.min, - range.max, - ANOMALIES_MAX_RESULTS - ) - .toPromise(); - } + return new Promise((resolve, reject) => { + const obj = { + success: true, + results: {}, + }; - // Query 3 - load any scheduled events for the job. - function getScheduledEvents(config, range) { - return mlResultsService - .getScheduledEventsByBucket( - [config.jobId], - range.min, - range.max, - config.interval, - 1, - MAX_SCHEDULED_EVENTS - ) - .toPromise(); + return mlResultsService + .getModelPlotOutput(jobId, detectorIndex, criteriaFields, range.min, range.max, interval) + .toPromise() + .then(resp => { + // Return data in format required by the explorer charts. + const results = resp.results; + Object.keys(results).forEach(time => { + obj.results[time] = results[time].actual; + }); + resolve(obj); + }) + .catch(resp => { + reject(resp); + }); + }); } + } - // Query 4 - load context data distribution - function getEventDistribution(config, range) { - const chartType = getChartType(config); - - let splitField; - let filterField = null; - - // Define splitField and filterField based on chartType - if (chartType === CHART_TYPE.EVENT_DISTRIBUTION) { - splitField = config.entityFields.find(f => f.fieldType === 'by'); - filterField = config.entityFields.find(f => f.fieldType === 'partition'); - } else if (chartType === CHART_TYPE.POPULATION_DISTRIBUTION) { - splitField = config.entityFields.find(f => f.fieldType === 'over'); - filterField = config.entityFields.find(f => f.fieldType === 'partition'); - } + // Query 2 - load the anomalies. + // Criteria to return the records for this series are the detector_index plus + // the specific combination of 'entity' fields i.e. the partition / by / over fields. + function getRecordsForCriteria(config, range) { + let criteria = []; + criteria.push({ fieldName: 'detector_index', fieldValue: config.detectorIndex }); + criteria = criteria.concat(config.entityFields); + return mlResultsService + .getRecordsForCriteria( + [config.jobId], + criteria, + 0, + range.min, + range.max, + ANOMALIES_MAX_RESULTS + ) + .toPromise(); + } - const datafeedQuery = _.get(config, 'datafeedConfig.query', null); - return mlResultsService.getEventDistributionData( - config.datafeedConfig.indices, - splitField, - filterField, - datafeedQuery, - config.metricFunction, - config.metricFieldName, - config.timeField, + // Query 3 - load any scheduled events for the job. + function getScheduledEvents(config, range) { + return mlResultsService + .getScheduledEventsByBucket( + [config.jobId], range.min, range.max, - config.interval - ); + config.interval, + 1, + MAX_SCHEDULED_EVENTS + ) + .toPromise(); + } + + // Query 4 - load context data distribution + function getEventDistribution(config, range) { + const chartType = getChartType(config); + + let splitField; + let filterField = null; + + // Define splitField and filterField based on chartType + if (chartType === CHART_TYPE.EVENT_DISTRIBUTION) { + splitField = config.entityFields.find(f => f.fieldType === 'by'); + filterField = config.entityFields.find(f => f.fieldType === 'partition'); + } else if (chartType === CHART_TYPE.POPULATION_DISTRIBUTION) { + splitField = config.entityFields.find(f => f.fieldType === 'over'); + filterField = config.entityFields.find(f => f.fieldType === 'partition'); } - // first load and wait for required data, - // only after that trigger data processing and page render. - // TODO - if query returns no results e.g. source data has been deleted, - // display a message saying 'No data between earliest/latest'. - const seriesPromises = seriesConfigs.map(seriesConfig => - Promise.all([ - getMetricData(seriesConfig, chartRange), - getRecordsForCriteria(seriesConfig, chartRange), - getScheduledEvents(seriesConfig, chartRange), - getEventDistribution(seriesConfig, chartRange), - ]) + const datafeedQuery = _.get(config, 'datafeedConfig.query', null); + return mlResultsService.getEventDistributionData( + config.datafeedConfig.indices, + splitField, + filterField, + datafeedQuery, + config.metricFunction, + config.metricFieldName, + config.timeField, + range.min, + range.max, + config.interval ); + } - function processChartData(response, seriesIndex) { - const metricData = response[0].results; - const records = response[1].records; - const jobId = seriesConfigs[seriesIndex].jobId; - const scheduledEvents = response[2].events[jobId]; - const eventDistribution = response[3]; - const chartType = getChartType(seriesConfigs[seriesIndex]); - - // Sort records in ascending time order matching up with chart data - records.sort((recordA, recordB) => { - return recordA[ML_TIME_FIELD_NAME] - recordB[ML_TIME_FIELD_NAME]; - }); + // first load and wait for required data, + // only after that trigger data processing and page render. + // TODO - if query returns no results e.g. source data has been deleted, + // display a message saying 'No data between earliest/latest'. + const seriesPromises = seriesConfigs.map(seriesConfig => + Promise.all([ + getMetricData(seriesConfig, chartRange), + getRecordsForCriteria(seriesConfig, chartRange), + getScheduledEvents(seriesConfig, chartRange), + getEventDistribution(seriesConfig, chartRange), + ]) + ); + + function processChartData(response, seriesIndex) { + const metricData = response[0].results; + const records = response[1].records; + const jobId = seriesConfigs[seriesIndex].jobId; + const scheduledEvents = response[2].events[jobId]; + const eventDistribution = response[3]; + const chartType = getChartType(seriesConfigs[seriesIndex]); + + // Sort records in ascending time order matching up with chart data + records.sort((recordA, recordB) => { + return recordA[ML_TIME_FIELD_NAME] - recordB[ML_TIME_FIELD_NAME]; + }); - // Return dataset in format used by the chart. - // i.e. array of Objects with keys date (timestamp), value, - // plus anomalyScore for points with anomaly markers. - let chartData = []; - if (metricData !== undefined) { - if (eventDistribution.length > 0 && records.length > 0) { - const filterField = records[0].by_field_value || records[0].over_field_value; - chartData = eventDistribution.filter(d => d.entity !== filterField); - _.map(metricData, (value, time) => { - // The filtering for rare/event_distribution charts needs to be handled - // differently because of how the source data is structured. - // For rare chart values we are only interested wether a value is either `0` or not, - // `0` acts like a flag in the chart whether to display the dot/marker. - // All other charts (single metric, population) are metric based and with - // those a value of `null` acts as the flag to hide a data point. - if ( - (chartType === CHART_TYPE.EVENT_DISTRIBUTION && value > 0) || - (chartType !== CHART_TYPE.EVENT_DISTRIBUTION && value !== null) - ) { - chartData.push({ - date: +time, - value: value, - entity: filterField, - }); - } - }); - } else { - chartData = _.map(metricData, (value, time) => ({ - date: +time, - value: value, - })); - } + // Return dataset in format used by the chart. + // i.e. array of Objects with keys date (timestamp), value, + // plus anomalyScore for points with anomaly markers. + let chartData = []; + if (metricData !== undefined) { + if (eventDistribution.length > 0 && records.length > 0) { + const filterField = records[0].by_field_value || records[0].over_field_value; + chartData = eventDistribution.filter(d => d.entity !== filterField); + _.map(metricData, (value, time) => { + // The filtering for rare/event_distribution charts needs to be handled + // differently because of how the source data is structured. + // For rare chart values we are only interested wether a value is either `0` or not, + // `0` acts like a flag in the chart whether to display the dot/marker. + // All other charts (single metric, population) are metric based and with + // those a value of `null` acts as the flag to hide a data point. + if ( + (chartType === CHART_TYPE.EVENT_DISTRIBUTION && value > 0) || + (chartType !== CHART_TYPE.EVENT_DISTRIBUTION && value !== null) + ) { + chartData.push({ + date: +time, + value: value, + entity: filterField, + }); + } + }); + } else { + chartData = _.map(metricData, (value, time) => ({ + date: +time, + value: value, + })); } + } - // Iterate through the anomaly records, adding anomalyScore properties - // to the chartData entries for anomalous buckets. - const chartDataForPointSearch = getChartDataForPointSearch(chartData, records[0], chartType); - _.each(records, record => { - // Look for a chart point with the same time as the record. - // If none found, insert a point for anomalies due to a gap in the data. - const recordTime = record[ML_TIME_FIELD_NAME]; - let chartPoint = findChartPointForTime(chartDataForPointSearch, recordTime); - if (chartPoint === undefined) { - chartPoint = { date: new Date(recordTime), value: null }; - chartData.push(chartPoint); - } + // Iterate through the anomaly records, adding anomalyScore properties + // to the chartData entries for anomalous buckets. + const chartDataForPointSearch = getChartDataForPointSearch(chartData, records[0], chartType); + _.each(records, record => { + // Look for a chart point with the same time as the record. + // If none found, insert a point for anomalies due to a gap in the data. + const recordTime = record[ML_TIME_FIELD_NAME]; + let chartPoint = findChartPointForTime(chartDataForPointSearch, recordTime); + if (chartPoint === undefined) { + chartPoint = { date: new Date(recordTime), value: null }; + chartData.push(chartPoint); + } - chartPoint.anomalyScore = record.record_score; + chartPoint.anomalyScore = record.record_score; - if (record.actual !== undefined) { - chartPoint.actual = record.actual; - chartPoint.typical = record.typical; - } else { - const causes = _.get(record, 'causes', []); - if (causes.length > 0) { - chartPoint.byFieldName = record.by_field_name; - chartPoint.numberOfCauses = causes.length; - if (causes.length === 1) { - // If only a single cause, copy actual and typical values to the top level. - const cause = _.first(record.causes); - chartPoint.actual = cause.actual; - chartPoint.typical = cause.typical; - } + if (record.actual !== undefined) { + chartPoint.actual = record.actual; + chartPoint.typical = record.typical; + } else { + const causes = _.get(record, 'causes', []); + if (causes.length > 0) { + chartPoint.byFieldName = record.by_field_name; + chartPoint.numberOfCauses = causes.length; + if (causes.length === 1) { + // If only a single cause, copy actual and typical values to the top level. + const cause = _.first(record.causes); + chartPoint.actual = cause.actual; + chartPoint.typical = cause.typical; } } + } + + if (record.multi_bucket_impact !== undefined) { + chartPoint.multiBucketImpact = record.multi_bucket_impact; + } + }); - if (record.multi_bucket_impact !== undefined) { - chartPoint.multiBucketImpact = record.multi_bucket_impact; + // Add a scheduledEvents property to any points in the chart data set + // which correspond to times of scheduled events for the job. + if (scheduledEvents !== undefined) { + _.each(scheduledEvents, (events, time) => { + const chartPoint = findChartPointForTime(chartDataForPointSearch, Number(time)); + if (chartPoint !== undefined) { + // Note if the scheduled event coincides with an absence of the underlying metric data, + // we don't worry about plotting the event. + chartPoint.scheduledEvents = events; } }); + } - // Add a scheduledEvents property to any points in the chart data set - // which correspond to times of scheduled events for the job. - if (scheduledEvents !== undefined) { - _.each(scheduledEvents, (events, time) => { - const chartPoint = findChartPointForTime(chartDataForPointSearch, Number(time)); - if (chartPoint !== undefined) { - // Note if the scheduled event coincides with an absence of the underlying metric data, - // we don't worry about plotting the event. - chartPoint.scheduledEvents = events; - } - }); - } + return chartData; + } - return chartData; + function getChartDataForPointSearch(chartData, record, chartType) { + if ( + chartType === CHART_TYPE.EVENT_DISTRIBUTION || + chartType === CHART_TYPE.POPULATION_DISTRIBUTION + ) { + return chartData.filter(d => { + return d.entity === (record && (record.by_field_value || record.over_field_value)); + }); } - function getChartDataForPointSearch(chartData, record, chartType) { - if ( - chartType === CHART_TYPE.EVENT_DISTRIBUTION || - chartType === CHART_TYPE.POPULATION_DISTRIBUTION - ) { - return chartData.filter(d => { - return d.entity === (record && (record.by_field_value || record.over_field_value)); - }); - } + return chartData; + } - return chartData; - } + function findChartPointForTime(chartData, time) { + return chartData.find(point => point.date === time); + } - function findChartPointForTime(chartData, time) { - return chartData.find(point => point.date === time); + Promise.all(seriesPromises) + .then(response => { + // calculate an overall min/max for all series + const processedData = response.map(processChartData); + const allDataPoints = _.reduce( + processedData, + (datapoints, series) => { + _.each(series, d => datapoints.push(d)); + return datapoints; + }, + [] + ); + const overallChartLimits = chartLimits(allDataPoints); + + data.seriesToPlot = response.map((d, i) => ({ + ...seriesConfigs[i], + loading: false, + chartData: processedData[i], + plotEarliest: chartRange.min, + plotLatest: chartRange.max, + selectedEarliest: earliestMs, + selectedLatest: latestMs, + chartLimits: USE_OVERALL_CHART_LIMITS ? overallChartLimits : chartLimits(processedData[i]), + })); + explorerService.setCharts({ ...data }); + }) + .catch(error => { + console.error(error); + }); +}; + +function processRecordsForDisplay(anomalyRecords) { + // Aggregate the anomaly data by detector, and entity (by/over/partition). + if (anomalyRecords.length === 0) { + return []; + } + + // Aggregate by job, detector, and analysis fields (partition, by, over). + const aggregatedData = {}; + _.each(anomalyRecords, record => { + // Check if we can plot a chart for this record, depending on whether the source data + // is chartable, and if model plot is enabled for the job. + const job = mlJobService.getJob(record.job_id); + let isChartable = isSourceDataChartableForDetector(job, record.detector_index); + if (isChartable === false) { + // Check if model plot is enabled for this job. + // Need to check the entity fields for the record in case the model plot config has a terms list. + const entityFields = getEntityFieldList(record); + isChartable = isModelPlotEnabled(job, record.detector_index, entityFields); } - Promise.all(seriesPromises) - .then(response => { - // calculate an overall min/max for all series - const processedData = response.map(processChartData); - const allDataPoints = _.reduce( - processedData, - (datapoints, series) => { - _.each(series, d => datapoints.push(d)); - return datapoints; - }, - [] - ); - const overallChartLimits = chartLimits(allDataPoints); - - data.seriesToPlot = response.map((d, i) => ({ - ...seriesConfigs[i], - loading: false, - chartData: processedData[i], - plotEarliest: chartRange.min, - plotLatest: chartRange.max, - selectedEarliest: earliestMs, - selectedLatest: latestMs, - chartLimits: USE_OVERALL_CHART_LIMITS - ? overallChartLimits - : chartLimits(processedData[i]), - })); - callback(data); - }) - .catch(error => { - console.error(error); - }); - }; + if (isChartable === false) { + return; + } + const jobId = record.job_id; + if (aggregatedData[jobId] === undefined) { + aggregatedData[jobId] = {}; + } + const detectorsForJob = aggregatedData[jobId]; - function processRecordsForDisplay(anomalyRecords) { - // Aggregate the anomaly data by detector, and entity (by/over/partition). - if (anomalyRecords.length === 0) { - return []; + const detectorIndex = record.detector_index; + if (detectorsForJob[detectorIndex] === undefined) { + detectorsForJob[detectorIndex] = {}; } - // Aggregate by job, detector, and analysis fields (partition, by, over). - const aggregatedData = {}; - _.each(anomalyRecords, record => { - // Check if we can plot a chart for this record, depending on whether the source data - // is chartable, and if model plot is enabled for the job. - const job = mlJobService.getJob(record.job_id); - let isChartable = isSourceDataChartableForDetector(job, record.detector_index); - if (isChartable === false) { - // Check if model plot is enabled for this job. - // Need to check the entity fields for the record in case the model plot config has a terms list. - const entityFields = getEntityFieldList(record); - isChartable = isModelPlotEnabled(job, record.detector_index, entityFields); - } + // TODO - work out how best to display results from detectors with just an over field. + const firstFieldName = + record.partition_field_name || record.by_field_name || record.over_field_name; + const firstFieldValue = + record.partition_field_value || record.by_field_value || record.over_field_value; + if (firstFieldName !== undefined) { + const groupsForDetector = detectorsForJob[detectorIndex]; - if (isChartable === false) { - return; + if (groupsForDetector[firstFieldName] === undefined) { + groupsForDetector[firstFieldName] = {}; } - const jobId = record.job_id; - if (aggregatedData[jobId] === undefined) { - aggregatedData[jobId] = {}; + const valuesForGroup = groupsForDetector[firstFieldName]; + if (valuesForGroup[firstFieldValue] === undefined) { + valuesForGroup[firstFieldValue] = {}; } - const detectorsForJob = aggregatedData[jobId]; - const detectorIndex = record.detector_index; - if (detectorsForJob[detectorIndex] === undefined) { - detectorsForJob[detectorIndex] = {}; - } + const dataForGroupValue = valuesForGroup[firstFieldValue]; - // TODO - work out how best to display results from detectors with just an over field. - const firstFieldName = - record.partition_field_name || record.by_field_name || record.over_field_name; - const firstFieldValue = - record.partition_field_value || record.by_field_value || record.over_field_value; - if (firstFieldName !== undefined) { - const groupsForDetector = detectorsForJob[detectorIndex]; - - if (groupsForDetector[firstFieldName] === undefined) { - groupsForDetector[firstFieldName] = {}; - } - const valuesForGroup = groupsForDetector[firstFieldName]; - if (valuesForGroup[firstFieldValue] === undefined) { - valuesForGroup[firstFieldValue] = {}; - } - - const dataForGroupValue = valuesForGroup[firstFieldValue]; - - let isSecondSplit = false; - if (record.partition_field_name !== undefined) { - const splitFieldName = record.over_field_name || record.by_field_name; - if (splitFieldName !== undefined) { - isSecondSplit = true; - } + let isSecondSplit = false; + if (record.partition_field_name !== undefined) { + const splitFieldName = record.over_field_name || record.by_field_name; + if (splitFieldName !== undefined) { + isSecondSplit = true; } + } - if (isSecondSplit === false) { - if (dataForGroupValue.maxScoreRecord === undefined) { + if (isSecondSplit === false) { + if (dataForGroupValue.maxScoreRecord === undefined) { + dataForGroupValue.maxScore = record.record_score; + dataForGroupValue.maxScoreRecord = record; + } else { + if (record.record_score > dataForGroupValue.maxScore) { dataForGroupValue.maxScore = record.record_score; dataForGroupValue.maxScoreRecord = record; - } else { - if (record.record_score > dataForGroupValue.maxScore) { - dataForGroupValue.maxScore = record.record_score; - dataForGroupValue.maxScoreRecord = record; - } } - } else { - // Aggregate another level for the over or by field. - const secondFieldName = record.over_field_name || record.by_field_name; - const secondFieldValue = record.over_field_value || record.by_field_value; + } + } else { + // Aggregate another level for the over or by field. + const secondFieldName = record.over_field_name || record.by_field_name; + const secondFieldValue = record.over_field_value || record.by_field_value; - if (dataForGroupValue[secondFieldName] === undefined) { - dataForGroupValue[secondFieldName] = {}; - } + if (dataForGroupValue[secondFieldName] === undefined) { + dataForGroupValue[secondFieldName] = {}; + } - const splitsForGroup = dataForGroupValue[secondFieldName]; - if (splitsForGroup[secondFieldValue] === undefined) { - splitsForGroup[secondFieldValue] = {}; - } + const splitsForGroup = dataForGroupValue[secondFieldName]; + if (splitsForGroup[secondFieldValue] === undefined) { + splitsForGroup[secondFieldValue] = {}; + } - const dataForSplitValue = splitsForGroup[secondFieldValue]; - if (dataForSplitValue.maxScoreRecord === undefined) { + const dataForSplitValue = splitsForGroup[secondFieldValue]; + if (dataForSplitValue.maxScoreRecord === undefined) { + dataForSplitValue.maxScore = record.record_score; + dataForSplitValue.maxScoreRecord = record; + } else { + if (record.record_score > dataForSplitValue.maxScore) { dataForSplitValue.maxScore = record.record_score; dataForSplitValue.maxScoreRecord = record; - } else { - if (record.record_score > dataForSplitValue.maxScore) { - dataForSplitValue.maxScore = record.record_score; - dataForSplitValue.maxScoreRecord = record; - } } } + } + } else { + // Detector with no partition or by field. + const dataForDetector = detectorsForJob[detectorIndex]; + if (dataForDetector.maxScoreRecord === undefined) { + dataForDetector.maxScore = record.record_score; + dataForDetector.maxScoreRecord = record; } else { - // Detector with no partition or by field. - const dataForDetector = detectorsForJob[detectorIndex]; - if (dataForDetector.maxScoreRecord === undefined) { + if (record.record_score > dataForDetector.maxScore) { dataForDetector.maxScore = record.record_score; dataForDetector.maxScoreRecord = record; - } else { - if (record.record_score > dataForDetector.maxScore) { - dataForDetector.maxScore = record.record_score; - dataForDetector.maxScoreRecord = record; - } } } - }); - - console.log('explorer charts aggregatedData is:', aggregatedData); - let recordsForSeries = []; - // Convert to an array of the records with the highest record_score per unique series. - _.each(aggregatedData, detectorsForJob => { - _.each(detectorsForJob, groupsForDetector => { - if (groupsForDetector.maxScoreRecord !== undefined) { - // Detector with no partition / by field. - recordsForSeries.push(groupsForDetector.maxScoreRecord); - } else { - _.each(groupsForDetector, valuesForGroup => { - _.each(valuesForGroup, dataForGroupValue => { - if (dataForGroupValue.maxScoreRecord !== undefined) { - recordsForSeries.push(dataForGroupValue.maxScoreRecord); - } else { - // Second level of aggregation for partition and by/over. - _.each(dataForGroupValue, splitsForGroup => { - _.each(splitsForGroup, dataForSplitValue => { - recordsForSeries.push(dataForSplitValue.maxScoreRecord); - }); + } + }); + + console.log('explorer charts aggregatedData is:', aggregatedData); + let recordsForSeries = []; + // Convert to an array of the records with the highest record_score per unique series. + _.each(aggregatedData, detectorsForJob => { + _.each(detectorsForJob, groupsForDetector => { + if (groupsForDetector.maxScoreRecord !== undefined) { + // Detector with no partition / by field. + recordsForSeries.push(groupsForDetector.maxScoreRecord); + } else { + _.each(groupsForDetector, valuesForGroup => { + _.each(valuesForGroup, dataForGroupValue => { + if (dataForGroupValue.maxScoreRecord !== undefined) { + recordsForSeries.push(dataForGroupValue.maxScoreRecord); + } else { + // Second level of aggregation for partition and by/over. + _.each(dataForGroupValue, splitsForGroup => { + _.each(splitsForGroup, dataForSplitValue => { + recordsForSeries.push(dataForSplitValue.maxScoreRecord); }); - } - }); + }); + } }); - } - }); + }); + } }); - recordsForSeries = _.sortBy(recordsForSeries, 'record_score').reverse(); + }); + recordsForSeries = _.sortBy(recordsForSeries, 'record_score').reverse(); - return recordsForSeries; - } + return recordsForSeries; +} - function calculateChartRange( - seriesConfigs, - earliestMs, - latestMs, - chartWidth, - recordsToPlot, - timeFieldName - ) { - let tooManyBuckets = false; - // Calculate the time range for the charts. - // Fit in as many points in the available container width plotted at the job bucket span. - const midpointMs = Math.ceil((earliestMs + latestMs) / 2); - const maxBucketSpanMs = - Math.max.apply(null, _.pluck(seriesConfigs, 'bucketSpanSeconds')) * 1000; - - const pointsToPlotFullSelection = Math.ceil((latestMs - earliestMs) / maxBucketSpanMs); - - // Optimally space points 5px apart. - const optimumPointSpacing = 5; - const optimumNumPoints = chartWidth / optimumPointSpacing; - - // Increase actual number of points if we can't plot the selected range - // at optimal point spacing. - const plotPoints = Math.max(optimumNumPoints, pointsToPlotFullSelection); - const halfPoints = Math.ceil(plotPoints / 2); - let chartRange = { - min: midpointMs - halfPoints * maxBucketSpanMs, - max: midpointMs + halfPoints * maxBucketSpanMs, - }; - - if (plotPoints > CHART_MAX_POINTS) { - tooManyBuckets = true; - // For each series being plotted, display the record with the highest score if possible. - const maxTimeSpan = maxBucketSpanMs * CHART_MAX_POINTS; - let minMs = recordsToPlot[0][timeFieldName]; - let maxMs = recordsToPlot[0][timeFieldName]; - - _.each(recordsToPlot, record => { - const diffMs = maxMs - minMs; - if (diffMs < maxTimeSpan) { - const recordTime = record[timeFieldName]; - if (recordTime < minMs) { - if (maxMs - recordTime <= maxTimeSpan) { - minMs = recordTime; - } - } +function calculateChartRange( + seriesConfigs, + earliestMs, + latestMs, + chartWidth, + recordsToPlot, + timeFieldName +) { + let tooManyBuckets = false; + // Calculate the time range for the charts. + // Fit in as many points in the available container width plotted at the job bucket span. + const midpointMs = Math.ceil((earliestMs + latestMs) / 2); + const maxBucketSpanMs = Math.max.apply(null, _.pluck(seriesConfigs, 'bucketSpanSeconds')) * 1000; + + const pointsToPlotFullSelection = Math.ceil((latestMs - earliestMs) / maxBucketSpanMs); + + // Optimally space points 5px apart. + const optimumPointSpacing = 5; + const optimumNumPoints = chartWidth / optimumPointSpacing; + + // Increase actual number of points if we can't plot the selected range + // at optimal point spacing. + const plotPoints = Math.max(optimumNumPoints, pointsToPlotFullSelection); + const halfPoints = Math.ceil(plotPoints / 2); + let chartRange = { + min: midpointMs - halfPoints * maxBucketSpanMs, + max: midpointMs + halfPoints * maxBucketSpanMs, + }; - if (recordTime > maxMs) { - if (recordTime - minMs <= maxTimeSpan) { - maxMs = recordTime; - } + if (plotPoints > CHART_MAX_POINTS) { + tooManyBuckets = true; + // For each series being plotted, display the record with the highest score if possible. + const maxTimeSpan = maxBucketSpanMs * CHART_MAX_POINTS; + let minMs = recordsToPlot[0][timeFieldName]; + let maxMs = recordsToPlot[0][timeFieldName]; + + _.each(recordsToPlot, record => { + const diffMs = maxMs - minMs; + if (diffMs < maxTimeSpan) { + const recordTime = record[timeFieldName]; + if (recordTime < minMs) { + if (maxMs - recordTime <= maxTimeSpan) { + minMs = recordTime; } } - }); - if (maxMs - minMs < maxTimeSpan) { - // Expand out to cover as much as the requested time span as possible. - minMs = Math.max(earliestMs, minMs - maxTimeSpan); - maxMs = Math.min(latestMs, maxMs + maxTimeSpan); + if (recordTime > maxMs) { + if (recordTime - minMs <= maxTimeSpan) { + maxMs = recordTime; + } + } } + }); - chartRange = { min: minMs, max: maxMs }; + if (maxMs - minMs < maxTimeSpan) { + // Expand out to cover as much as the requested time span as possible. + minMs = Math.max(earliestMs, minMs - maxTimeSpan); + maxMs = Math.min(latestMs, maxMs + maxTimeSpan); } - return { - chartRange, - tooManyBuckets, - }; + chartRange = { min: minMs, max: maxMs }; } - return anomalyDataChange; + return { + chartRange, + tooManyBuckets, + }; } diff --git a/x-pack/legacy/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container_service.test.js b/x-pack/legacy/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container_service.test.js index 483a359f98e5b..fbbf5eb324095 100644 --- a/x-pack/legacy/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container_service.test.js +++ b/x-pack/legacy/plugins/ml/public/application/explorer/explorer_charts/explorer_charts_container_service.test.js @@ -102,119 +102,79 @@ jest.mock('ui/chrome', () => ({ }), })); -import { - explorerChartsContainerServiceFactory, - getDefaultChartsData, -} from './explorer_charts_container_service'; +jest.mock('../explorer_dashboard_service', () => ({ + explorerService: { + setCharts: jest.fn(), + }, +})); -describe('explorerChartsContainerService', () => { - test('Initialize factory', done => { - explorerChartsContainerServiceFactory(callback); +import { anomalyDataChange, getDefaultChartsData } from './explorer_charts_container_service'; +import { explorerService } from '../explorer_dashboard_service'; - function callback(data) { - expect(data).toEqual(getDefaultChartsData()); - done(); - } +describe('explorerChartsContainerService', () => { + afterEach(() => { + explorerService.setCharts.mockClear(); }); test('call anomalyChangeListener with empty series config', done => { - // callback will be called multiple times. - // the callbackData array contains the expected data values for each consecutive call. - const callbackData = []; - callbackData.push(getDefaultChartsData()); - callbackData.push({ - ...getDefaultChartsData(), - chartsPerRow: 2, + anomalyDataChange([], 1486656000000, 1486670399999); + + setImmediate(() => { + expect(explorerService.setCharts.mock.calls.length).toBe(1); + expect(explorerService.setCharts.mock.calls[0][0]).toStrictEqual({ + ...getDefaultChartsData(), + chartsPerRow: 2, + }); + done(); }); - - const anomalyDataChangeListener = explorerChartsContainerServiceFactory(callback); - - anomalyDataChangeListener([], 1486656000000, 1486670399999); - - function callback(data) { - if (callbackData.length > 0) { - expect(data).toEqual({ - ...callbackData.shift(), - }); - } - if (callbackData.length === 0) { - done(); - } - } }); test('call anomalyChangeListener with actual series config', done => { - let callbackCount = 0; - const expectedTestCount = 3; - - const anomalyDataChangeListener = explorerChartsContainerServiceFactory(callback); + anomalyDataChange(mockAnomalyChartRecords, 1486656000000, 1486670399999); - anomalyDataChangeListener(mockAnomalyChartRecords, 1486656000000, 1486670399999); - - function callback(data) { - callbackCount++; - expect(data).toMatchSnapshot(); - if (callbackCount === expectedTestCount) { - done(); - } - } + setImmediate(() => { + expect(explorerService.setCharts.mock.calls.length).toBe(2); + expect(explorerService.setCharts.mock.calls[0][0]).toMatchSnapshot(); + expect(explorerService.setCharts.mock.calls[1][0]).toMatchSnapshot(); + done(); + }); }); test('filtering should skip values of null', done => { - let callbackCount = 0; - const expectedTestCount = 3; - - const anomalyDataChangeListener = explorerChartsContainerServiceFactory(callback); - const mockAnomalyChartRecordsClone = _.cloneDeep(mockAnomalyChartRecords).map(d => { d.job_id = 'mock-job-id-distribution'; return d; }); - anomalyDataChangeListener(mockAnomalyChartRecordsClone, 1486656000000, 1486670399999); + anomalyDataChange(mockAnomalyChartRecordsClone, 1486656000000, 1486670399999); - function callback(data) { - callbackCount++; + setImmediate(() => { + expect(explorerService.setCharts.mock.calls.length).toBe(2); + expect(explorerService.setCharts.mock.calls[0][0].seriesToPlot.length).toBe(1); + expect(explorerService.setCharts.mock.calls[1][0].seriesToPlot.length).toBe(1); - if (callbackCount === 1) { - expect(data.seriesToPlot).toHaveLength(0); - } - if (callbackCount === 3) { - expect(data.seriesToPlot).toHaveLength(1); - - // the mock source dataset has a length of 115. one data point has a value of `null`, - // and another one `0`. the received dataset should have a length of 114, - // it should remove the datapoint with `null` and keep the one with `0`. - const chartData = data.seriesToPlot[0].chartData; - expect(chartData).toHaveLength(114); - expect(chartData.filter(d => d.value === 0)).toHaveLength(1); - expect(chartData.filter(d => d.value === null)).toHaveLength(0); - } - if (callbackCount === expectedTestCount) { - done(); - } - } + // the mock source dataset has a length of 115. one data point has a value of `null`, + // and another one `0`. the received dataset should have a length of 114, + // it should remove the datapoint with `null` and keep the one with `0`. + const chartData = explorerService.setCharts.mock.calls[1][0].seriesToPlot[0].chartData; + expect(chartData).toHaveLength(114); + expect(chartData.filter(d => d.value === 0)).toHaveLength(1); + expect(chartData.filter(d => d.value === null)).toHaveLength(0); + done(); + }); }); test('field value with trailing dot should not throw an error', done => { - let callbackCount = 0; - const expectedTestCount = 3; - - const anomalyDataChangeListener = explorerChartsContainerServiceFactory(callback); - const mockAnomalyChartRecordsClone = _.cloneDeep(mockAnomalyChartRecords); mockAnomalyChartRecordsClone[1].partition_field_value = 'AAL.'; expect(() => { - anomalyDataChangeListener(mockAnomalyChartRecordsClone, 1486656000000, 1486670399999); + anomalyDataChange(mockAnomalyChartRecordsClone, 1486656000000, 1486670399999); }).not.toThrow(); - function callback() { - callbackCount++; - - if (callbackCount === expectedTestCount) { - done(); - } - } + setImmediate(() => { + expect(explorerService.setCharts.mock.calls.length).toBe(2); + done(); + }); }); }); diff --git a/x-pack/legacy/plugins/ml/public/application/explorer/explorer_constants.ts b/x-pack/legacy/plugins/ml/public/application/explorer/explorer_constants.ts index 66cd98f7ebe29..b084f503272cc 100644 --- a/x-pack/legacy/plugins/ml/public/application/explorer/explorer_constants.ts +++ b/x-pack/legacy/plugins/ml/public/application/explorer/explorer_constants.ts @@ -17,24 +17,15 @@ export const DRAG_SELECT_ACTION = { }; export const EXPLORER_ACTION = { - APP_STATE_SET: 'appStateSet', - APP_STATE_CLEAR_INFLUENCER_FILTER_SETTINGS: 'appStateClearInfluencerFilterSettings', - APP_STATE_CLEAR_SELECTION: 'appStateClearSelection', - APP_STATE_SAVE_SELECTION: 'appStateSaveSelection', - APP_STATE_SAVE_VIEW_BY_SWIMLANE_FIELD_NAME: 'appStateSaveViewBySwimlaneFieldName', - APP_STATE_SAVE_INFLUENCER_FILTER_SETTINGS: 'appStateSaveInfluencerFilterSettings', CLEAR_INFLUENCER_FILTER_SETTINGS: 'clearInfluencerFilterSettings', CLEAR_JOBS: 'clearJobs', - CLEAR_SELECTION: 'clearSelection', - INITIALIZE: 'initialize', JOB_SELECTION_CHANGE: 'jobSelectionChange', - LOAD_JOBS: 'loadJobs', - RESET: 'reset', SET_BOUNDS: 'setBounds', SET_CHARTS: 'setCharts', + SET_EXPLORER_DATA: 'setExplorerData', + SET_FILTER_DATA: 'setFilterData', SET_INFLUENCER_FILTER_SETTINGS: 'setInfluencerFilterSettings', SET_SELECTED_CELLS: 'setSelectedCells', - SET_STATE: 'setState', SET_SWIMLANE_CONTAINER_WIDTH: 'setSwimlaneContainerWidth', SET_SWIMLANE_LIMIT: 'setSwimlaneLimit', SET_VIEW_BY_SWIMLANE_FIELD_NAME: 'setViewBySwimlaneFieldName', diff --git a/x-pack/legacy/plugins/ml/public/application/explorer/explorer_dashboard_service.ts b/x-pack/legacy/plugins/ml/public/application/explorer/explorer_dashboard_service.ts index 713857835b3b9..89e1a908b1ecc 100644 --- a/x-pack/legacy/plugins/ml/public/application/explorer/explorer_dashboard_service.ts +++ b/x-pack/legacy/plugins/ml/public/application/explorer/explorer_dashboard_service.ts @@ -9,30 +9,25 @@ * components in the Explorer dashboard. */ -import { isEqual, pick } from 'lodash'; +import { isEqual } from 'lodash'; -import { from, isObservable, BehaviorSubject, Observable, Subject } from 'rxjs'; -import { distinctUntilChanged, flatMap, map, pairwise, scan } from 'rxjs/operators'; +import { from, isObservable, Observable, Subject } from 'rxjs'; +import { distinctUntilChanged, flatMap, map, scan } from 'rxjs/operators'; import { DeepPartial } from '../../../common/types/common'; -import { jobSelectionActionCreator, loadExplorerData } from './actions'; +import { jobSelectionActionCreator } from './actions'; import { ExplorerChartsData } from './explorer_charts/explorer_charts_container_service'; import { EXPLORER_ACTION } from './explorer_constants'; -import { RestoredAppState, SelectedCells, TimeRangeBounds } from './explorer_utils'; -import { - explorerReducer, - getExplorerDefaultState, - ExplorerAppState, - ExplorerState, -} from './reducers'; +import { AppStateSelectedCells, TimeRangeBounds } from './explorer_utils'; +import { explorerReducer, getExplorerDefaultState, ExplorerState } from './reducers'; export const ALLOW_CELL_RANGE_SELECTION = true; export const dragSelect$ = new Subject(); type ExplorerAction = Action | Observable; -const explorerAction$ = new BehaviorSubject({ type: EXPLORER_ACTION.RESET }); +export const explorerAction$ = new Subject(); export type ActionPayload = any; @@ -51,94 +46,79 @@ const explorerFilteredAction$ = explorerAction$.pipe( // applies action and returns state const explorerState$: Observable = explorerFilteredAction$.pipe( - scan(explorerReducer, getExplorerDefaultState()), - pairwise(), - map(([prev, curr]) => { - if ( - curr.selectedJobs !== null && - curr.bounds !== undefined && - !isEqual(getCompareState(prev), getCompareState(curr)) - ) { - explorerAction$.next(loadExplorerData(curr).pipe(map(d => setStateActionCreator(d)))); - } - return curr; - }) + scan(explorerReducer, getExplorerDefaultState()) ); +interface ExplorerAppState { + mlExplorerSwimlane: { + selectedType?: string; + selectedLanes?: string[]; + selectedTimes?: number[]; + showTopFieldValues?: boolean; + viewByFieldName?: string; + }; + mlExplorerFilter: { + influencersFilterQuery?: unknown; + filterActive?: boolean; + filteredFields?: string[]; + queryString?: string; + }; +} + const explorerAppState$: Observable = explorerState$.pipe( - map((state: ExplorerState) => state.appState), + map( + (state: ExplorerState): ExplorerAppState => { + const appState: ExplorerAppState = { + mlExplorerFilter: {}, + mlExplorerSwimlane: {}, + }; + + if (state.selectedCells !== undefined) { + const swimlaneSelectedCells = state.selectedCells; + appState.mlExplorerSwimlane.selectedType = swimlaneSelectedCells.type; + appState.mlExplorerSwimlane.selectedLanes = swimlaneSelectedCells.lanes; + appState.mlExplorerSwimlane.selectedTimes = swimlaneSelectedCells.times; + appState.mlExplorerSwimlane.showTopFieldValues = swimlaneSelectedCells.showTopFieldValues; + } + + if (state.viewBySwimlaneFieldName !== undefined) { + appState.mlExplorerSwimlane.viewByFieldName = state.viewBySwimlaneFieldName; + } + + if (state.filterActive) { + appState.mlExplorerFilter.influencersFilterQuery = state.influencersFilterQuery; + appState.mlExplorerFilter.filterActive = state.filterActive; + appState.mlExplorerFilter.filteredFields = state.filteredFields; + appState.mlExplorerFilter.queryString = state.queryString; + } + + return appState; + } + ), distinctUntilChanged(isEqual) ); -function getCompareState(state: ExplorerState) { - return pick(state, [ - 'bounds', - 'filterActive', - 'filteredFields', - 'influencersFilterQuery', - 'isAndOperator', - 'noInfluencersConfigured', - 'selectedCells', - 'selectedJobs', - 'swimlaneContainerWidth', - 'swimlaneLimit', - 'tableInterval', - 'tableSeverity', - 'viewBySwimlaneFieldName', - ]); -} - -export const setStateActionCreator = (payload: DeepPartial) => ({ - type: EXPLORER_ACTION.SET_STATE, +const setExplorerDataActionCreator = (payload: DeepPartial) => ({ + type: EXPLORER_ACTION.SET_EXPLORER_DATA, + payload, +}); +const setFilterDataActionCreator = (payload: DeepPartial) => ({ + type: EXPLORER_ACTION.SET_FILTER_DATA, payload, }); - -interface AppStateSelection { - type: string; - lanes: string[]; - times: number[]; - showTopFieldValues: boolean; - viewByFieldName: string; -} // Export observable state and action dispatchers as service export const explorerService = { appState$: explorerAppState$, state$: explorerState$, - appStateClearSelection: () => { - explorerAction$.next({ type: EXPLORER_ACTION.APP_STATE_CLEAR_SELECTION }); - }, - appStateSaveSelection: (payload: AppStateSelection) => { - explorerAction$.next({ type: EXPLORER_ACTION.APP_STATE_SAVE_SELECTION, payload }); - }, clearInfluencerFilterSettings: () => { explorerAction$.next({ type: EXPLORER_ACTION.CLEAR_INFLUENCER_FILTER_SETTINGS }); }, clearJobs: () => { explorerAction$.next({ type: EXPLORER_ACTION.CLEAR_JOBS }); }, - clearSelection: () => { - explorerAction$.next({ type: EXPLORER_ACTION.CLEAR_SELECTION }); - }, - updateJobSelection: (selectedJobIds: string[], restoredAppState: RestoredAppState) => { - explorerAction$.next( - jobSelectionActionCreator( - EXPLORER_ACTION.JOB_SELECTION_CHANGE, - selectedJobIds, - restoredAppState - ) - ); - }, - initialize: (selectedJobIds: string[], restoredAppState: RestoredAppState) => { - explorerAction$.next( - jobSelectionActionCreator(EXPLORER_ACTION.INITIALIZE, selectedJobIds, restoredAppState) - ); - }, - reset: () => { - explorerAction$.next({ type: EXPLORER_ACTION.RESET }); - }, - setAppState: (payload: DeepPartial) => { - explorerAction$.next({ type: EXPLORER_ACTION.APP_STATE_SET, payload }); + updateJobSelection: (selectedJobIds: string[]) => { + explorerAction$.next(jobSelectionActionCreator(selectedJobIds)); }, setBounds: (payload: TimeRangeBounds) => { explorerAction$.next({ type: EXPLORER_ACTION.SET_BOUNDS, payload }); @@ -152,14 +132,17 @@ export const explorerService = { payload, }); }, - setSelectedCells: (payload: SelectedCells) => { + setSelectedCells: (payload: AppStateSelectedCells | undefined) => { explorerAction$.next({ type: EXPLORER_ACTION.SET_SELECTED_CELLS, payload, }); }, - setState: (payload: DeepPartial) => { - explorerAction$.next(setStateActionCreator(payload)); + setExplorerData: (payload: DeepPartial) => { + explorerAction$.next(setExplorerDataActionCreator(payload)); + }, + setFilterData: (payload: DeepPartial) => { + explorerAction$.next(setFilterDataActionCreator(payload)); }, setSwimlaneContainerWidth: (payload: number) => { explorerAction$.next({ diff --git a/x-pack/legacy/plugins/ml/public/application/explorer/explorer_utils.d.ts b/x-pack/legacy/plugins/ml/public/application/explorer/explorer_utils.d.ts index d7873e6d52d78..0ab75b1db2972 100644 --- a/x-pack/legacy/plugins/ml/public/application/explorer/explorer_utils.d.ts +++ b/x-pack/legacy/plugins/ml/public/application/explorer/explorer_utils.d.ts @@ -11,8 +11,7 @@ import { CombinedJob } from '../jobs/new_job/common/job_creator/configs'; import { TimeBucketsInterval } from '../util/time_buckets'; interface ClearedSelectedAnomaliesState { - anomalyChartRecords: []; - selectedCells: null; + selectedCells: undefined; viewByLoadedForTimeFormatted: null; } @@ -37,7 +36,7 @@ export declare const getDefaultSwimlaneData: () => SwimlaneData; export declare const getInfluencers: (selectedJobs: any[]) => string[]; export declare const getSelectionInfluencers: ( - selectedCells: SelectedCells, + selectedCells: AppStateSelectedCells | undefined, fieldName: string ) => any[]; @@ -47,7 +46,7 @@ interface SelectionTimeRange { } export declare const getSelectionTimeRange: ( - selectedCells: SelectedCells, + selectedCells: AppStateSelectedCells | undefined, interval: number, bounds: TimeRangeBounds ) => SelectionTimeRange; @@ -62,7 +61,7 @@ interface ViewBySwimlaneOptionsArgs { filterActive: boolean; filteredFields: any[]; isAndOperator: boolean; - selectedCells: SelectedCells; + selectedCells: AppStateSelectedCells; selectedJobs: ExplorerJob[]; } @@ -94,7 +93,7 @@ declare interface SwimlaneBounds { } export declare const loadAnnotationsTableData: ( - selectedCells: SelectedCells, + selectedCells: AppStateSelectedCells | undefined, selectedJobs: ExplorerJob[], interval: number, bounds: TimeRangeBounds @@ -109,7 +108,7 @@ export declare interface AnomaliesTableData { } export declare const loadAnomaliesTableData: ( - selectedCells: SelectedCells, + selectedCells: AppStateSelectedCells | undefined, selectedJobs: ExplorerJob[], dateFormatTz: any, interval: number, @@ -125,7 +124,7 @@ export declare const loadDataForCharts: ( earliestMs: number, latestMs: number, influencers: any[], - selectedCells: SelectedCells, + selectedCells: AppStateSelectedCells | undefined, influencersFilterQuery: any ) => Promise; @@ -178,25 +177,17 @@ export declare const loadViewByTopFieldValuesForSelectedTime: ( noInfluencersConfigured: boolean ) => Promise; -declare interface FilterData { +export declare interface FilterData { influencersFilterQuery: any; filterActive: boolean; filteredFields: string[]; queryString: string; } -declare interface SelectedCells { +export declare interface AppStateSelectedCells { type: string; lanes: string[]; times: number[]; showTopFieldValues: boolean; viewByFieldName: string; } - -export declare interface RestoredAppState { - selectedCells?: SelectedCells; - filterData: {} | FilterData; - viewBySwimlaneFieldName: string; -} - -export declare const restoreAppState: (appState: any) => RestoredAppState; diff --git a/x-pack/legacy/plugins/ml/public/application/explorer/explorer_utils.js b/x-pack/legacy/plugins/ml/public/application/explorer/explorer_utils.js index b54b691f3aba6..4fb4e7d4df94f 100644 --- a/x-pack/legacy/plugins/ml/public/application/explorer/explorer_utils.js +++ b/x-pack/legacy/plugins/ml/public/application/explorer/explorer_utils.js @@ -53,8 +53,7 @@ export function createJobs(jobs) { export function getClearedSelectedAnomaliesState() { return { - anomalyChartRecords: [], - selectedCells: null, + selectedCells: undefined, viewByLoadedForTimeFormatted: null, }; } @@ -195,7 +194,7 @@ export function getSelectionTimeRange(selectedCells, interval, bounds) { let earliestMs = bounds.min.valueOf(); let latestMs = bounds.max.valueOf(); - if (selectedCells !== null && selectedCells.times !== undefined) { + if (selectedCells !== undefined && selectedCells.times !== undefined) { // time property of the cell data is an array, with the elements being // the start times of the first and last cell selected. earliestMs = @@ -212,7 +211,7 @@ export function getSelectionTimeRange(selectedCells, interval, bounds) { export function getSelectionInfluencers(selectedCells, fieldName) { if ( - selectedCells !== null && + selectedCells !== undefined && selectedCells.type !== SWIMLANE_TYPE.OVERALL && selectedCells.viewByFieldName !== undefined && selectedCells.viewByFieldName !== VIEW_BY_JOB_LABEL @@ -346,7 +345,7 @@ export function getViewBySwimlaneOptions({ if (selectedJobIds.length > 1) { // If more than one job selected, default to job ID. viewBySwimlaneFieldName = VIEW_BY_JOB_LABEL; - } else if (mlJobService.jobs.length > 0) { + } else if (mlJobService.jobs.length > 0 && selectedJobIds.length > 0) { // For a single job, default to the first partition, over, // by or influencer field of the first selected job. const firstSelectedJob = mlJobService.jobs.find(job => { @@ -525,7 +524,7 @@ export function processViewByResults( export function loadAnnotationsTableData(selectedCells, selectedJobs, interval, bounds) { const jobIds = - selectedCells !== null && selectedCells.viewByFieldName === VIEW_BY_JOB_LABEL + selectedCells !== undefined && selectedCells.viewByFieldName === VIEW_BY_JOB_LABEL ? selectedCells.lanes : selectedJobs.map(d => d.id); const timeRange = getSelectionTimeRange(selectedCells, interval, bounds); @@ -587,7 +586,7 @@ export async function loadAnomaliesTableData( influencersFilterQuery ) { const jobIds = - selectedCells !== null && selectedCells.viewByFieldName === VIEW_BY_JOB_LABEL + selectedCells !== undefined && selectedCells.viewByFieldName === VIEW_BY_JOB_LABEL ? selectedCells.lanes : selectedJobs.map(d => d.id); const influencers = getSelectionInfluencers(selectedCells, fieldName); @@ -677,7 +676,7 @@ export async function loadDataForCharts( // Just skip doing the request when this function // is called without the minimum required data. if ( - selectedCells === null && + selectedCells === undefined && influencers.length === 0 && influencersFilterQuery === undefined ) { @@ -705,7 +704,7 @@ export async function loadDataForCharts( } if ( - (selectedCells !== null && Object.keys(selectedCells).length > 0) || + (selectedCells !== undefined && Object.keys(selectedCells).length > 0) || influencersFilterQuery !== undefined ) { console.log('Explorer anomaly charts data set:', resp.records); @@ -879,36 +878,3 @@ export async function loadTopInfluencers( } }); } - -export function restoreAppState(appState) { - // Select any jobs set in the global state (i.e. passed in the URL). - let selectedCells; - let filterData = {}; - - // keep swimlane selection, restore selectedCells from AppState - if (appState.mlExplorerSwimlane.selectedType !== undefined) { - selectedCells = { - type: appState.mlExplorerSwimlane.selectedType, - lanes: appState.mlExplorerSwimlane.selectedLanes, - times: appState.mlExplorerSwimlane.selectedTimes, - showTopFieldValues: appState.mlExplorerSwimlane.showTopFieldValues, - viewByFieldName: appState.mlExplorerSwimlane.viewByFieldName, - }; - } - - // keep influencers filter selection, restore from AppState - if (appState.mlExplorerFilter.influencersFilterQuery !== undefined) { - filterData = { - influencersFilterQuery: appState.mlExplorerFilter.influencersFilterQuery, - filterActive: appState.mlExplorerFilter.filterActive, - filteredFields: appState.mlExplorerFilter.filteredFields, - queryString: appState.mlExplorerFilter.queryString, - }; - } - - return { - filterData, - selectedCells, - viewBySwimlaneFieldName: appState.mlExplorerSwimlane.viewByFieldName, - }; -} diff --git a/x-pack/legacy/plugins/ml/public/application/explorer/hooks/use_selected_cells.ts b/x-pack/legacy/plugins/ml/public/application/explorer/hooks/use_selected_cells.ts new file mode 100644 index 0000000000000..2b3e1c7bd656f --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/application/explorer/hooks/use_selected_cells.ts @@ -0,0 +1,67 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { useUrlState } from '../../util/url_state'; +import { SWIMLANE_TYPE } from '../../explorer/explorer_constants'; +import { AppStateSelectedCells } from '../../explorer/explorer_utils'; + +export const useSelectedCells = (): [ + AppStateSelectedCells | undefined, + (swimlaneSelectedCells: AppStateSelectedCells) => void +] => { + const [appState, setAppState] = useUrlState('_a'); + + let selectedCells: AppStateSelectedCells | undefined; + + // keep swimlane selection, restore selectedCells from AppState + if ( + appState && + appState.mlExplorerSwimlane && + appState.mlExplorerSwimlane.selectedType !== undefined + ) { + selectedCells = { + type: appState.mlExplorerSwimlane.selectedType, + lanes: appState.mlExplorerSwimlane.selectedLanes, + times: appState.mlExplorerSwimlane.selectedTimes, + showTopFieldValues: appState.mlExplorerSwimlane.showTopFieldValues, + viewByFieldName: appState.mlExplorerSwimlane.viewByFieldName, + }; + } + + const setSelectedCells = (swimlaneSelectedCells: AppStateSelectedCells) => { + const mlExplorerSwimlane = { ...appState.mlExplorerSwimlane }; + if (swimlaneSelectedCells !== undefined) { + swimlaneSelectedCells.showTopFieldValues = false; + + const currentSwimlaneType = selectedCells?.type; + const currentShowTopFieldValues = selectedCells?.showTopFieldValues; + const newSwimlaneType = selectedCells?.type; + + if ( + (currentSwimlaneType === SWIMLANE_TYPE.OVERALL && + newSwimlaneType === SWIMLANE_TYPE.VIEW_BY) || + newSwimlaneType === SWIMLANE_TYPE.OVERALL || + currentShowTopFieldValues === true + ) { + swimlaneSelectedCells.showTopFieldValues = true; + } + + mlExplorerSwimlane.selectedType = swimlaneSelectedCells.type; + mlExplorerSwimlane.selectedLanes = swimlaneSelectedCells.lanes; + mlExplorerSwimlane.selectedTimes = swimlaneSelectedCells.times; + mlExplorerSwimlane.showTopFieldValues = swimlaneSelectedCells.showTopFieldValues; + setAppState('mlExplorerSwimlane', mlExplorerSwimlane); + } else { + delete mlExplorerSwimlane.selectedType; + delete mlExplorerSwimlane.selectedLanes; + delete mlExplorerSwimlane.selectedTimes; + delete mlExplorerSwimlane.showTopFieldValues; + setAppState('mlExplorerSwimlane', mlExplorerSwimlane); + } + }; + + return [selectedCells, setSelectedCells]; +}; diff --git a/x-pack/legacy/plugins/ml/public/application/explorer/reducers/app_state_reducer.ts b/x-pack/legacy/plugins/ml/public/application/explorer/reducers/app_state_reducer.ts deleted file mode 100644 index 66e00a41a3f31..0000000000000 --- a/x-pack/legacy/plugins/ml/public/application/explorer/reducers/app_state_reducer.ts +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { cloneDeep } from 'lodash'; - -import { EXPLORER_ACTION } from '../explorer_constants'; -import { Action } from '../explorer_dashboard_service'; - -export interface ExplorerAppState { - mlExplorerSwimlane: { - selectedType?: string; - selectedLanes?: string[]; - selectedTimes?: number[]; - showTopFieldValues?: boolean; - viewByFieldName?: string; - }; - mlExplorerFilter: { - influencersFilterQuery?: unknown; - filterActive?: boolean; - filteredFields?: string[]; - queryString?: string; - }; -} - -export function getExplorerDefaultAppState(): ExplorerAppState { - return { - mlExplorerSwimlane: {}, - mlExplorerFilter: {}, - }; -} - -export const appStateReducer = (state: ExplorerAppState, nextAction: Action) => { - const { type, payload } = nextAction; - - const appState = cloneDeep(state); - - if (appState.mlExplorerSwimlane === undefined) { - appState.mlExplorerSwimlane = {}; - } - if (appState.mlExplorerFilter === undefined) { - appState.mlExplorerFilter = {}; - } - - switch (type) { - case EXPLORER_ACTION.APP_STATE_SET: - return { ...appState, ...payload }; - - case EXPLORER_ACTION.APP_STATE_CLEAR_SELECTION: - delete appState.mlExplorerSwimlane.selectedType; - delete appState.mlExplorerSwimlane.selectedLanes; - delete appState.mlExplorerSwimlane.selectedTimes; - delete appState.mlExplorerSwimlane.showTopFieldValues; - break; - - case EXPLORER_ACTION.APP_STATE_SAVE_SELECTION: - const swimlaneSelectedCells = payload; - appState.mlExplorerSwimlane.selectedType = swimlaneSelectedCells.type; - appState.mlExplorerSwimlane.selectedLanes = swimlaneSelectedCells.lanes; - appState.mlExplorerSwimlane.selectedTimes = swimlaneSelectedCells.times; - appState.mlExplorerSwimlane.showTopFieldValues = swimlaneSelectedCells.showTopFieldValues; - appState.mlExplorerSwimlane.viewByFieldName = swimlaneSelectedCells.viewByFieldName; - break; - - case EXPLORER_ACTION.APP_STATE_SAVE_VIEW_BY_SWIMLANE_FIELD_NAME: - appState.mlExplorerSwimlane.viewByFieldName = payload.viewBySwimlaneFieldName; - break; - - case EXPLORER_ACTION.APP_STATE_SAVE_INFLUENCER_FILTER_SETTINGS: - appState.mlExplorerFilter.influencersFilterQuery = payload.influencersFilterQuery; - appState.mlExplorerFilter.filterActive = payload.filterActive; - appState.mlExplorerFilter.filteredFields = payload.filteredFields; - appState.mlExplorerFilter.queryString = payload.queryString; - break; - - case EXPLORER_ACTION.APP_STATE_CLEAR_INFLUENCER_FILTER_SETTINGS: - delete appState.mlExplorerFilter.influencersFilterQuery; - delete appState.mlExplorerFilter.filterActive; - delete appState.mlExplorerFilter.filteredFields; - delete appState.mlExplorerFilter.queryString; - break; - - default: - } - - return appState; -}; diff --git a/x-pack/legacy/plugins/ml/public/application/explorer/reducers/explorer_reducer/check_selected_cells.ts b/x-pack/legacy/plugins/ml/public/application/explorer/reducers/explorer_reducer/check_selected_cells.ts index 28f04bf65634a..daeb9ae54013c 100644 --- a/x-pack/legacy/plugins/ml/public/application/explorer/reducers/explorer_reducer/check_selected_cells.ts +++ b/x-pack/legacy/plugins/ml/public/application/explorer/reducers/explorer_reducer/check_selected_cells.ts @@ -4,11 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EXPLORER_ACTION, SWIMLANE_TYPE } from '../../explorer_constants'; +import { SWIMLANE_TYPE } from '../../explorer_constants'; import { getClearedSelectedAnomaliesState } from '../../explorer_utils'; -import { appStateReducer } from '../app_state_reducer'; - import { ExplorerState } from './state'; interface SwimlanePoint { @@ -21,18 +19,26 @@ interface SwimlanePoint { // If filter is active - selectedCell may not be available due to swimlane view by change to filter fieldName // Ok to keep cellSelection in this case export const checkSelectedCells = (state: ExplorerState) => { - const { filterActive, selectedCells, viewBySwimlaneData, viewBySwimlaneDataLoading } = state; - - if (viewBySwimlaneDataLoading) { + const { + filterActive, + loading, + selectedCells, + viewBySwimlaneData, + viewBySwimlaneDataLoading, + } = state; + + if (loading || viewBySwimlaneDataLoading) { return {}; } let clearSelection = false; if ( + selectedCells !== undefined && selectedCells !== null && selectedCells.type === SWIMLANE_TYPE.VIEW_BY && viewBySwimlaneData !== undefined && - viewBySwimlaneData.points !== undefined + viewBySwimlaneData.points !== undefined && + viewBySwimlaneData.points.length > 0 ) { clearSelection = filterActive === false && @@ -49,9 +55,6 @@ export const checkSelectedCells = (state: ExplorerState) => { if (clearSelection === true) { return { - appState: appStateReducer(state.appState, { - type: EXPLORER_ACTION.APP_STATE_CLEAR_SELECTION, - }), ...getClearedSelectedAnomaliesState(), }; } diff --git a/x-pack/legacy/plugins/ml/public/application/explorer/reducers/explorer_reducer/clear_influencer_filter_settings.ts b/x-pack/legacy/plugins/ml/public/application/explorer/reducers/explorer_reducer/clear_influencer_filter_settings.ts index 29c077a5cba43..1614da14e355a 100644 --- a/x-pack/legacy/plugins/ml/public/application/explorer/reducers/explorer_reducer/clear_influencer_filter_settings.ts +++ b/x-pack/legacy/plugins/ml/public/application/explorer/reducers/explorer_reducer/clear_influencer_filter_settings.ts @@ -4,24 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EXPLORER_ACTION } from '../../explorer_constants'; import { getClearedSelectedAnomaliesState } from '../../explorer_utils'; -import { appStateReducer } from '../app_state_reducer'; - import { ExplorerState } from './state'; export function clearInfluencerFilterSettings(state: ExplorerState): ExplorerState { - const appStateClearInfluencer = appStateReducer(state.appState, { - type: EXPLORER_ACTION.APP_STATE_CLEAR_INFLUENCER_FILTER_SETTINGS, - }); - const appStateClearSelection = appStateReducer(appStateClearInfluencer, { - type: EXPLORER_ACTION.APP_STATE_CLEAR_SELECTION, - }); - return { ...state, - appState: appStateClearSelection, filterActive: false, filteredFields: [], influencersFilterQuery: undefined, diff --git a/x-pack/legacy/plugins/ml/public/application/explorer/reducers/explorer_reducer/initialize.ts b/x-pack/legacy/plugins/ml/public/application/explorer/reducers/explorer_reducer/initialize.ts deleted file mode 100644 index 8536c8f3e542e..0000000000000 --- a/x-pack/legacy/plugins/ml/public/application/explorer/reducers/explorer_reducer/initialize.ts +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { ActionPayload } from '../../explorer_dashboard_service'; -import { getInfluencers } from '../../explorer_utils'; - -import { getIndexPattern } from './get_index_pattern'; -import { ExplorerState } from './state'; - -export const initialize = (state: ExplorerState, payload: ActionPayload): ExplorerState => { - const { selectedCells, selectedJobs, viewBySwimlaneFieldName, filterData } = payload; - let currentSelectedCells = state.selectedCells; - let currentviewBySwimlaneFieldName = state.viewBySwimlaneFieldName; - - if (viewBySwimlaneFieldName !== undefined) { - currentviewBySwimlaneFieldName = viewBySwimlaneFieldName; - } - - if (selectedCells !== undefined && currentSelectedCells === null) { - currentSelectedCells = selectedCells; - } - - return { - ...state, - indexPattern: getIndexPattern(selectedJobs), - noInfluencersConfigured: getInfluencers(selectedJobs).length === 0, - selectedCells: currentSelectedCells, - selectedJobs, - viewBySwimlaneFieldName: currentviewBySwimlaneFieldName, - ...(filterData.influencersFilterQuery !== undefined ? { ...filterData } : {}), - }; -}; diff --git a/x-pack/legacy/plugins/ml/public/application/explorer/reducers/explorer_reducer/job_selection_change.ts b/x-pack/legacy/plugins/ml/public/application/explorer/reducers/explorer_reducer/job_selection_change.ts index 9fe8ebbb2c481..a26c0564c6b16 100644 --- a/x-pack/legacy/plugins/ml/public/application/explorer/reducers/explorer_reducer/job_selection_change.ts +++ b/x-pack/legacy/plugins/ml/public/application/explorer/reducers/explorer_reducer/job_selection_change.ts @@ -4,27 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EXPLORER_ACTION, VIEW_BY_JOB_LABEL } from '../../explorer_constants'; import { ActionPayload } from '../../explorer_dashboard_service'; -import { - getClearedSelectedAnomaliesState, - getDefaultSwimlaneData, - getInfluencers, -} from '../../explorer_utils'; - -import { appStateReducer } from '../app_state_reducer'; +import { getDefaultSwimlaneData, getInfluencers } from '../../explorer_utils'; import { getIndexPattern } from './get_index_pattern'; -import { getExplorerDefaultState, ExplorerState } from './state'; +import { ExplorerState } from './state'; export const jobSelectionChange = (state: ExplorerState, payload: ActionPayload): ExplorerState => { const { selectedJobs } = payload; const stateUpdate: ExplorerState = { ...state, - appState: appStateReducer(getExplorerDefaultState().appState, { - type: EXPLORER_ACTION.APP_STATE_CLEAR_SELECTION, - }), - ...getClearedSelectedAnomaliesState(), noInfluencersConfigured: getInfluencers(selectedJobs).length === 0, overallSwimlaneData: getDefaultSwimlaneData(), selectedJobs, @@ -32,9 +21,6 @@ export const jobSelectionChange = (state: ExplorerState, payload: ActionPayload) // clear filter if selected jobs have no influencers if (stateUpdate.noInfluencersConfigured === true) { - stateUpdate.appState = appStateReducer(stateUpdate.appState, { - type: EXPLORER_ACTION.APP_STATE_CLEAR_INFLUENCER_FILTER_SETTINGS, - }); const noFilterState = { filterActive: false, filteredFields: [], @@ -51,11 +37,6 @@ export const jobSelectionChange = (state: ExplorerState, payload: ActionPayload) stateUpdate.indexPattern = getIndexPattern(selectedJobs); } - if (selectedJobs.length > 1) { - stateUpdate.viewBySwimlaneFieldName = VIEW_BY_JOB_LABEL; - return stateUpdate; - } - stateUpdate.loading = true; return stateUpdate; }; diff --git a/x-pack/legacy/plugins/ml/public/application/explorer/reducers/explorer_reducer/reducer.ts b/x-pack/legacy/plugins/ml/public/application/explorer/reducers/explorer_reducer/reducer.ts index 1919ce949683f..c31b26b7adb7b 100644 --- a/x-pack/legacy/plugins/ml/public/application/explorer/reducers/explorer_reducer/reducer.ts +++ b/x-pack/legacy/plugins/ml/public/application/explorer/reducers/explorer_reducer/reducer.ts @@ -7,7 +7,7 @@ import { formatHumanReadableDateTime } from '../../../util/date_utils'; import { getDefaultChartsData } from '../../explorer_charts/explorer_charts_container_service'; -import { EXPLORER_ACTION, SWIMLANE_TYPE, VIEW_BY_JOB_LABEL } from '../../explorer_constants'; +import { EXPLORER_ACTION, VIEW_BY_JOB_LABEL } from '../../explorer_constants'; import { Action } from '../../explorer_dashboard_service'; import { getClearedSelectedAnomaliesState, @@ -16,13 +16,11 @@ import { getSwimlaneBucketInterval, getViewBySwimlaneOptions, } from '../../explorer_utils'; -import { appStateReducer } from '../app_state_reducer'; import { checkSelectedCells } from './check_selected_cells'; import { clearInfluencerFilterSettings } from './clear_influencer_filter_settings'; -import { initialize } from './initialize'; import { jobSelectionChange } from './job_selection_change'; -import { getExplorerDefaultState, ExplorerState } from './state'; +import { ExplorerState } from './state'; import { setInfluencerFilterSettings } from './set_influencer_filter_settings'; import { setKqlQueryBarPlaceholder } from './set_kql_query_bar_placeholder'; @@ -40,45 +38,15 @@ export const explorerReducer = (state: ExplorerState, nextAction: Action): Explo nextState = { ...state, ...getClearedSelectedAnomaliesState(), - appState: appStateReducer(state.appState, { - type: EXPLORER_ACTION.APP_STATE_CLEAR_SELECTION, - }), loading: false, selectedJobs: [], }; break; - case EXPLORER_ACTION.CLEAR_SELECTION: - nextState = { - ...state, - ...getClearedSelectedAnomaliesState(), - appState: appStateReducer(state.appState, { - type: EXPLORER_ACTION.APP_STATE_CLEAR_SELECTION, - }), - }; - break; - - case EXPLORER_ACTION.INITIALIZE: - nextState = initialize(state, payload); - break; - case EXPLORER_ACTION.JOB_SELECTION_CHANGE: nextState = jobSelectionChange(state, payload); break; - case EXPLORER_ACTION.APP_STATE_SET: - case EXPLORER_ACTION.APP_STATE_CLEAR_SELECTION: - case EXPLORER_ACTION.APP_STATE_SAVE_SELECTION: - case EXPLORER_ACTION.APP_STATE_SAVE_VIEW_BY_SWIMLANE_FIELD_NAME: - case EXPLORER_ACTION.APP_STATE_SAVE_INFLUENCER_FILTER_SETTINGS: - case EXPLORER_ACTION.APP_STATE_CLEAR_INFLUENCER_FILTER_SETTINGS: - nextState = { ...state, appState: appStateReducer(state.appState, nextAction) }; - break; - - case EXPLORER_ACTION.RESET: - nextState = getExplorerDefaultState(); - break; - case EXPLORER_ACTION.SET_BOUNDS: nextState = { ...state, bounds: payload }; break; @@ -102,44 +70,15 @@ export const explorerReducer = (state: ExplorerState, nextAction: Action): Explo case EXPLORER_ACTION.SET_SELECTED_CELLS: const selectedCells = payload; - selectedCells.showTopFieldValues = false; - - const currentSwimlaneType = state.selectedCells?.type; - const currentShowTopFieldValues = state.selectedCells?.showTopFieldValues; - const newSwimlaneType = selectedCells?.type; - - if ( - (currentSwimlaneType === SWIMLANE_TYPE.OVERALL && - newSwimlaneType === SWIMLANE_TYPE.VIEW_BY) || - newSwimlaneType === SWIMLANE_TYPE.OVERALL || - currentShowTopFieldValues === true - ) { - selectedCells.showTopFieldValues = true; - } - nextState = { ...state, - appState: appStateReducer(state.appState, { - type: EXPLORER_ACTION.APP_STATE_SAVE_SELECTION, - payload, - }), selectedCells, }; break; - case EXPLORER_ACTION.SET_STATE: - if (payload.viewBySwimlaneFieldName) { - nextState = { - ...state, - ...payload, - appState: appStateReducer(state.appState, { - type: EXPLORER_ACTION.APP_STATE_SAVE_VIEW_BY_SWIMLANE_FIELD_NAME, - payload: { viewBySwimlaneFieldName: payload.viewBySwimlaneFieldName }, - }), - }; - } else { - nextState = { ...state, ...payload }; - } + case EXPLORER_ACTION.SET_EXPLORER_DATA: + case EXPLORER_ACTION.SET_FILTER_DATA: + nextState = { ...state, ...payload }; break; case EXPLORER_ACTION.SET_SWIMLANE_CONTAINER_WIDTH: @@ -157,10 +96,6 @@ export const explorerReducer = (state: ExplorerState, nextAction: Action): Explo case EXPLORER_ACTION.SET_SWIMLANE_LIMIT: nextState = { ...state, - appState: appStateReducer(state.appState, { - type: EXPLORER_ACTION.APP_STATE_CLEAR_SELECTION, - }), - ...getClearedSelectedAnomaliesState(), swimlaneLimit: payload, }; break; @@ -180,9 +115,6 @@ export const explorerReducer = (state: ExplorerState, nextAction: Action): Explo nextState = { ...state, ...getClearedSelectedAnomaliesState(), - appState: appStateReducer(state.appState, { - type: EXPLORER_ACTION.APP_STATE_CLEAR_SELECTION, - }), maskAll, viewBySwimlaneFieldName, }; @@ -216,7 +148,7 @@ export const explorerReducer = (state: ExplorerState, nextAction: Action): Explo ); // Does a sanity check on the selected `viewBySwimlaneFieldName` - // and return the available `viewBySwimlaneOptions`. + // and returns the available `viewBySwimlaneOptions`. const { viewBySwimlaneFieldName, viewBySwimlaneOptions } = getViewBySwimlaneOptions({ currentViewBySwimlaneFieldName: nextState.viewBySwimlaneFieldName, filterActive: nextState.filterActive, @@ -238,7 +170,7 @@ export const explorerReducer = (state: ExplorerState, nextAction: Action): Explo ...nextState, swimlaneBucketInterval, viewByLoadedForTimeFormatted: - selectedCells !== null && selectedCells.showTopFieldValues === true + selectedCells !== undefined && selectedCells.showTopFieldValues === true ? formatHumanReadableDateTime(timerange.earliestMs) : null, viewBySwimlaneFieldName, diff --git a/x-pack/legacy/plugins/ml/public/application/explorer/reducers/explorer_reducer/set_influencer_filter_settings.ts b/x-pack/legacy/plugins/ml/public/application/explorer/reducers/explorer_reducer/set_influencer_filter_settings.ts index 76577ae557fe3..8d083a396582a 100644 --- a/x-pack/legacy/plugins/ml/public/application/explorer/reducers/explorer_reducer/set_influencer_filter_settings.ts +++ b/x-pack/legacy/plugins/ml/public/application/explorer/reducers/explorer_reducer/set_influencer_filter_settings.ts @@ -4,11 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EXPLORER_ACTION, VIEW_BY_JOB_LABEL } from '../../explorer_constants'; +import { VIEW_BY_JOB_LABEL } from '../../explorer_constants'; import { ActionPayload } from '../../explorer_dashboard_service'; -import { appStateReducer } from '../app_state_reducer'; - import { ExplorerState } from './state'; export function setInfluencerFilterSettings( @@ -43,21 +41,8 @@ export function setInfluencerFilterSettings( } } - const appState = appStateReducer(state.appState, { - type: EXPLORER_ACTION.APP_STATE_SAVE_INFLUENCER_FILTER_SETTINGS, - payload: { - influencersFilterQuery, - filterActive: true, - filteredFields, - queryString, - tableQueryString, - isAndOperator, - }, - }); - return { ...state, - appState, filterActive: true, filteredFields, influencersFilterQuery, diff --git a/x-pack/legacy/plugins/ml/public/application/explorer/reducers/explorer_reducer/state.ts b/x-pack/legacy/plugins/ml/public/application/explorer/reducers/explorer_reducer/state.ts index ce37605c3a926..0a2dbf5bcff35 100644 --- a/x-pack/legacy/plugins/ml/public/application/explorer/reducers/explorer_reducer/state.ts +++ b/x-pack/legacy/plugins/ml/public/application/explorer/reducers/explorer_reducer/state.ts @@ -15,16 +15,13 @@ import { getDefaultSwimlaneData, AnomaliesTableData, ExplorerJob, + AppStateSelectedCells, SwimlaneData, TimeRangeBounds, } from '../../explorer_utils'; -import { getExplorerDefaultAppState, ExplorerAppState } from '../app_state_reducer'; - export interface ExplorerState { annotationsData: any[]; - anomalyChartRecords: any[]; - appState: ExplorerAppState; bounds: TimeRangeBounds | undefined; chartsData: ExplorerChartsData; fieldFormatsLoading: boolean; @@ -40,15 +37,13 @@ export interface ExplorerState { noInfluencersConfigured: boolean; overallSwimlaneData: SwimlaneData; queryString: string; - selectedCells: any; + selectedCells: AppStateSelectedCells | undefined; selectedJobs: ExplorerJob[] | null; swimlaneBucketInterval: any; swimlaneContainerWidth: number; swimlaneLimit: number; tableData: AnomaliesTableData; - tableInterval: string; tableQueryString: string; - tableSeverity: number; viewByLoadedForTimeFormatted: string | null; viewBySwimlaneData: SwimlaneData; viewBySwimlaneDataLoading: boolean; @@ -63,8 +58,6 @@ function getDefaultIndexPattern() { export function getExplorerDefaultState(): ExplorerState { return { annotationsData: [], - anomalyChartRecords: [], - appState: getExplorerDefaultAppState(), bounds: undefined, chartsData: getDefaultChartsData(), fieldFormatsLoading: false, @@ -80,7 +73,7 @@ export function getExplorerDefaultState(): ExplorerState { noInfluencersConfigured: true, overallSwimlaneData: getDefaultSwimlaneData(), queryString: '', - selectedCells: null, + selectedCells: undefined, selectedJobs: null, swimlaneBucketInterval: undefined, swimlaneContainerWidth: 0, @@ -92,9 +85,7 @@ export function getExplorerDefaultState(): ExplorerState { jobIds: [], showViewSeriesLink: false, }, - tableInterval: 'auto', tableQueryString: '', - tableSeverity: 0, viewByLoadedForTimeFormatted: null, viewBySwimlaneData: getDefaultSwimlaneData(), viewBySwimlaneDataLoading: false, diff --git a/x-pack/legacy/plugins/ml/public/application/explorer/reducers/index.ts b/x-pack/legacy/plugins/ml/public/application/explorer/reducers/index.ts index 98cc07e8f9449..29787365923c8 100644 --- a/x-pack/legacy/plugins/ml/public/application/explorer/reducers/index.ts +++ b/x-pack/legacy/plugins/ml/public/application/explorer/reducers/index.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -export { appStateReducer, getExplorerDefaultAppState, ExplorerAppState } from './app_state_reducer'; export { explorerReducer, getExplorerDefaultState, diff --git a/x-pack/legacy/plugins/ml/public/application/explorer/select_limit/index.js b/x-pack/legacy/plugins/ml/public/application/explorer/select_limit/index.js deleted file mode 100644 index fa1b24e118180..0000000000000 --- a/x-pack/legacy/plugins/ml/public/application/explorer/select_limit/index.js +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import './select_limit_service.js'; diff --git a/x-pack/legacy/plugins/ml/public/application/explorer/select_limit/index.ts b/x-pack/legacy/plugins/ml/public/application/explorer/select_limit/index.ts new file mode 100644 index 0000000000000..5b7040e5c3606 --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/application/explorer/select_limit/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { useSwimlaneLimit, SelectLimit } from './select_limit'; diff --git a/x-pack/legacy/plugins/ml/public/application/explorer/select_limit/select_limit.js b/x-pack/legacy/plugins/ml/public/application/explorer/select_limit/select_limit.js deleted file mode 100644 index 5971e7dcc82be..0000000000000 --- a/x-pack/legacy/plugins/ml/public/application/explorer/select_limit/select_limit.js +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -/* - * React component for rendering a select element with limit options. - */ -import PropTypes from 'prop-types'; -import React, { Component } from 'react'; -import { BehaviorSubject } from 'rxjs'; - -import { EuiSelect } from '@elastic/eui'; - -import { injectObservablesAsProps } from '../../util/observable_utils'; - -const optionsMap = { - '5': 5, - '10': 10, - '25': 25, - '50': 50, -}; - -const LIMIT_OPTIONS = [ - { val: 5, display: '5' }, - { val: 10, display: '10' }, - { val: 25, display: '25' }, - { val: 50, display: '50' }, -]; - -function optionValueToLimit(value) { - // Get corresponding limit object with required display and val properties from the specified value. - let limit = LIMIT_OPTIONS.find(opt => opt.val === value); - - // Default to 10 if supplied value doesn't map to one of the options. - if (limit === undefined) { - limit = LIMIT_OPTIONS[1]; - } - - return limit; -} - -const EUI_OPTIONS = LIMIT_OPTIONS.map(({ display, val }) => ({ - value: display, - text: val, -})); - -export const limit$ = new BehaviorSubject(LIMIT_OPTIONS[1]); - -class SelectLimitUnwrapped extends Component { - onChange = e => { - const valueDisplay = e.target.value; - const limit = optionValueToLimit(optionsMap[valueDisplay]); - limit$.next(limit); - }; - - render() { - return ( - - ); - } -} - -SelectLimitUnwrapped.propTypes = { - limit: PropTypes.object, -}; - -SelectLimitUnwrapped.defaultProps = { - limit: LIMIT_OPTIONS[1], -}; - -const SelectLimit = injectObservablesAsProps( - { - limit: limit$, - }, - SelectLimitUnwrapped -); - -export { SelectLimit }; diff --git a/x-pack/legacy/plugins/ml/public/application/explorer/select_limit/select_limit.test.js b/x-pack/legacy/plugins/ml/public/application/explorer/select_limit/select_limit.test.js deleted file mode 100644 index 60543cfad2de4..0000000000000 --- a/x-pack/legacy/plugins/ml/public/application/explorer/select_limit/select_limit.test.js +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import { shallow } from 'enzyme'; -import { SelectLimit } from './select_limit'; - -describe('SelectLimit', () => { - test('creates correct initial selected value', () => { - const wrapper = shallow(); - const defaultSelectedValue = wrapper.props().limit.display; - expect(defaultSelectedValue).toBe('10'); - }); - - test('state for currently selected value is updated correctly on click', () => { - const wrapper = shallow(); - const select = wrapper.first().shallow(); - - const defaultSelectedValue = wrapper.props().limit.display; - expect(defaultSelectedValue).toBe('10'); - - select.simulate('change', { target: { value: '25' } }); - const updatedSelectedValue = wrapper.props().limit.display; - expect(updatedSelectedValue).toBe('25'); - }); -}); diff --git a/x-pack/legacy/plugins/ml/public/application/explorer/select_limit/select_limit.test.tsx b/x-pack/legacy/plugins/ml/public/application/explorer/select_limit/select_limit.test.tsx new file mode 100644 index 0000000000000..657f1c6c7af2e --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/application/explorer/select_limit/select_limit.test.tsx @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { act } from 'react-dom/test-utils'; +import { shallow } from 'enzyme'; +import { SelectLimit } from './select_limit'; + +jest.useFakeTimers(); + +describe('SelectLimit', () => { + test('creates correct initial selected value', () => { + const wrapper = shallow(); + expect(wrapper.props().value).toEqual(10); + }); + + test('state for currently selected value is updated correctly on click', () => { + const wrapper = shallow(); + expect(wrapper.props().value).toEqual(10); + + act(() => { + wrapper.simulate('change', { target: { value: 25 } }); + }); + wrapper.update(); + + expect(wrapper.props().value).toEqual(10); + }); +}); diff --git a/x-pack/legacy/plugins/ml/public/application/explorer/select_limit/select_limit.tsx b/x-pack/legacy/plugins/ml/public/application/explorer/select_limit/select_limit.tsx new file mode 100644 index 0000000000000..383d07eb7a9f6 --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/application/explorer/select_limit/select_limit.tsx @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/* + * React component for rendering a select element with limit options. + */ +import React from 'react'; +import useObservable from 'react-use/lib/useObservable'; +import { Subject } from 'rxjs'; + +import { EuiSelect } from '@elastic/eui'; + +const limitOptions = [5, 10, 25, 50]; + +const euiOptions = limitOptions.map(limit => ({ + value: limit, + text: `${limit}`, +})); + +export const limit$ = new Subject(); +export const defaultLimit = limitOptions[1]; + +export const useSwimlaneLimit = (): [number, (newLimit: number) => void] => { + const limit = useObservable(limit$, defaultLimit); + + return [limit, (newLimit: number) => limit$.next(newLimit)]; +}; + +export const SelectLimit = () => { + const [limit, setLimit] = useSwimlaneLimit(); + + function onChange(e: React.ChangeEvent) { + setLimit(parseInt(e.target.value, 10)); + } + + return ; +}; diff --git a/x-pack/legacy/plugins/ml/public/application/explorer/select_limit/select_limit_service.js b/x-pack/legacy/plugins/ml/public/application/explorer/select_limit/select_limit_service.js deleted file mode 100644 index dc9d90d3c677e..0000000000000 --- a/x-pack/legacy/plugins/ml/public/application/explorer/select_limit/select_limit_service.js +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -/* - * AngularJS service for storing limit values in AppState. - */ - -import { uiModules } from 'ui/modules'; -const module = uiModules.get('apps/ml'); - -import { subscribeAppStateToObservable } from '../../util/app_state_utils'; -import { limit$ } from './select_limit'; - -module.service('mlSelectLimitService', function(AppState, $rootScope) { - subscribeAppStateToObservable(AppState, 'mlSelectLimit', limit$, () => $rootScope.$applyAsync()); -}); diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/chart_loader/chart_loader.ts b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/chart_loader/chart_loader.ts index 502a88ecf6004..5e92ab67fcc79 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/chart_loader/chart_loader.ts +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/chart_loader/chart_loader.ts @@ -6,13 +6,13 @@ import memoizeOne from 'memoize-one'; import { isEqual } from 'lodash'; -import { IndexPattern } from 'ui/index_patterns'; import { IndexPatternTitle } from '../../../../../../common/types/kibana'; import { Field, SplitField, AggFieldPair } from '../../../../../../common/types/fields'; import { ml } from '../../../../services/ml_api_service'; import { mlResultsService } from '../../../../services/results_service'; import { getCategoryFields as getCategoryFieldsOrig } from './searches'; import { aggFieldPairsCanBeCharted } from '../job_creator/util/general'; +import { IndexPattern } from '../../../../../../../../../../src/plugins/data/public'; type DetectorIndex = number; export interface LineChartPoint { diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/advanced_job_creator.ts b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/advanced_job_creator.ts index d8917db7a33ff..b0eb1b98cd02b 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/advanced_job_creator.ts +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/advanced_job_creator.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IndexPattern } from 'ui/index_patterns'; import { SavedSearchSavedObject } from '../../../../../../common/types/kibana'; import { JobCreator } from './job_creator'; @@ -15,6 +14,7 @@ import { JOB_TYPE } from '../../../../../../common/constants/new_job'; import { getRichDetectors } from './util/general'; import { isValidJson } from '../../../../../../common/util/validation_utils'; import { ml } from '../../../../services/ml_api_service'; +import { IndexPattern } from '../../../../../../../../../../src/plugins/data/public'; export interface RichDetector { agg: Aggregation | null; diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/categorization_job_creator.ts b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/categorization_job_creator.ts new file mode 100644 index 0000000000000..cea99eb5ec64c --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/categorization_job_creator.ts @@ -0,0 +1,159 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { isEqual } from 'lodash'; +import { IndexPattern } from '../../../../../../../../../../src/plugins/data/public'; +import { SavedSearchSavedObject } from '../../../../../../common/types/kibana'; +import { JobCreator } from './job_creator'; +import { Field, Aggregation, mlCategory } from '../../../../../../common/types/fields'; +import { Job, Datafeed, Detector } from './configs'; +import { createBasicDetector } from './util/default_configs'; +import { + JOB_TYPE, + CREATED_BY_LABEL, + DEFAULT_BUCKET_SPAN, + DEFAULT_RARE_BUCKET_SPAN, +} from '../../../../../../common/constants/new_job'; +import { ML_JOB_AGGREGATION } from '../../../../../../common/constants/aggregation_types'; +import { getRichDetectors } from './util/general'; +import { CategorizationExamplesLoader, CategoryExample } from '../results_loader'; +import { CategorizationAnalyzer, getNewJobDefaults } from '../../../../services/ml_server_info'; + +type CategorizationAnalyzerType = CategorizationAnalyzer | null; + +export class CategorizationJobCreator extends JobCreator { + protected _type: JOB_TYPE = JOB_TYPE.CATEGORIZATION; + private _createCountDetector: () => void = () => {}; + private _createRareDetector: () => void = () => {}; + private _examplesLoader: CategorizationExamplesLoader; + private _categoryFieldExamples: CategoryExample[] = []; + private _categoryFieldValid: number = 0; + private _detectorType: ML_JOB_AGGREGATION.COUNT | ML_JOB_AGGREGATION.RARE = + ML_JOB_AGGREGATION.COUNT; + private _categorizationAnalyzer: CategorizationAnalyzerType = null; + private _defaultCategorizationAnalyzer: CategorizationAnalyzerType; + + constructor( + indexPattern: IndexPattern, + savedSearch: SavedSearchSavedObject | null, + query: object + ) { + super(indexPattern, savedSearch, query); + this.createdBy = CREATED_BY_LABEL.CATEGORIZATION; + this._examplesLoader = new CategorizationExamplesLoader(this, indexPattern, query); + + const { anomaly_detectors: anomalyDetectors } = getNewJobDefaults(); + this._defaultCategorizationAnalyzer = anomalyDetectors.categorization_analyzer || null; + } + + public setDefaultDetectorProperties( + count: Aggregation | null, + rare: Aggregation | null, + eventRate: Field | null + ) { + if (count === null || rare === null || eventRate === null) { + return; + } + + this._createCountDetector = () => { + this._createDetector(count, eventRate); + }; + this._createRareDetector = () => { + this._createDetector(rare, eventRate); + }; + } + + private _createDetector(agg: Aggregation, field: Field) { + const dtr: Detector = createBasicDetector(agg, field); + dtr.by_field_name = mlCategory.id; + this._addDetector(dtr, agg, mlCategory); + } + + public setDetectorType(type: ML_JOB_AGGREGATION.COUNT | ML_JOB_AGGREGATION.RARE) { + this._detectorType = type; + this.removeAllDetectors(); + if (type === ML_JOB_AGGREGATION.COUNT) { + this._createCountDetector(); + this.bucketSpan = DEFAULT_BUCKET_SPAN; + } else { + this._createRareDetector(); + this.bucketSpan = DEFAULT_RARE_BUCKET_SPAN; + this.modelPlot = false; + } + } + + public set categorizationFieldName(fieldName: string | null) { + if (fieldName !== null) { + this._job_config.analysis_config.categorization_field_name = fieldName; + this.setDetectorType(this._detectorType); + this.addInfluencer(mlCategory.id); + } else { + delete this._job_config.analysis_config.categorization_field_name; + this._categoryFieldExamples = []; + this._categoryFieldValid = 0; + } + } + + public get categorizationFieldName(): string | null { + return this._job_config.analysis_config.categorization_field_name || null; + } + + public async loadCategorizationFieldExamples() { + const { valid, examples } = await this._examplesLoader.loadExamples(); + this._categoryFieldExamples = examples; + this._categoryFieldValid = valid; + return { valid, examples }; + } + + public get categoryFieldExamples() { + return this._categoryFieldExamples; + } + + public get categoryFieldValid() { + return this._categoryFieldValid; + } + + public get selectedDetectorType() { + return this._detectorType; + } + + public set categorizationAnalyzer(analyzer: CategorizationAnalyzerType) { + this._categorizationAnalyzer = analyzer; + + if ( + analyzer === null || + isEqual(this._categorizationAnalyzer, this._defaultCategorizationAnalyzer) + ) { + delete this._job_config.analysis_config.categorization_analyzer; + } else { + this._job_config.analysis_config.categorization_analyzer = analyzer; + } + } + + public get categorizationAnalyzer() { + return this._categorizationAnalyzer; + } + + public cloneFromExistingJob(job: Job, datafeed: Datafeed) { + this._overrideConfigs(job, datafeed); + this.createdBy = CREATED_BY_LABEL.CATEGORIZATION; + const detectors = getRichDetectors(job, datafeed, this.scriptFields, false); + + const dtr = detectors[0]; + if (detectors.length && dtr.agg !== null && dtr.field !== null) { + this._detectorType = + dtr.agg.id === ML_JOB_AGGREGATION.COUNT + ? ML_JOB_AGGREGATION.COUNT + : ML_JOB_AGGREGATION.RARE; + + const bs = job.analysis_config.bucket_span; + this.setDetectorType(this._detectorType); + // set the bucketspan back to the original value + // as setDetectorType applies a default + this.bucketSpan = bs; + } + } +} diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/index.ts b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/index.ts index 8422223ad91fb..88bacdf49c38a 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/index.ts +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/index.ts @@ -9,11 +9,13 @@ export { SingleMetricJobCreator } from './single_metric_job_creator'; export { MultiMetricJobCreator } from './multi_metric_job_creator'; export { PopulationJobCreator } from './population_job_creator'; export { AdvancedJobCreator } from './advanced_job_creator'; +export { CategorizationJobCreator } from './categorization_job_creator'; export { JobCreatorType, isSingleMetricJobCreator, isMultiMetricJobCreator, isPopulationJobCreator, isAdvancedJobCreator, + isCategorizationJobCreator, } from './type_guards'; export { jobCreatorFactory } from './job_creator_factory'; diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/job_creator.ts b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/job_creator.ts index 4707eff8d844e..513c8239db01e 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/job_creator.ts +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/job_creator.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IndexPattern } from 'ui/index_patterns'; import { SavedSearchSavedObject } from '../../../../../../common/types/kibana'; import { UrlConfig } from '../../../../../../common/types/custom_urls'; import { IndexPatternTitle } from '../../../../../../common/types/kibana'; @@ -24,6 +23,7 @@ import { isSparseDataJob } from './util/general'; import { parseInterval } from '../../../../../../common/util/parse_interval'; import { Calendar } from '../../../../../../common/types/calendars'; import { mlCalendarService } from '../../../../services/calendar_service'; +import { IndexPattern } from '../../../../../../../../../../src/plugins/data/public'; export class JobCreator { protected _type: JOB_TYPE = JOB_TYPE.SINGLE_METRIC; diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/job_creator_factory.ts b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/job_creator_factory.ts index 4ffcd1b06ca47..8655b83a244ad 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/job_creator_factory.ts +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/job_creator_factory.ts @@ -4,12 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IndexPattern } from 'ui/index_patterns'; import { SavedSearchSavedObject } from '../../../../../../common/types/kibana'; import { SingleMetricJobCreator } from './single_metric_job_creator'; import { MultiMetricJobCreator } from './multi_metric_job_creator'; import { PopulationJobCreator } from './population_job_creator'; import { AdvancedJobCreator } from './advanced_job_creator'; +import { IndexPattern } from '../../../../../../../../../../src/plugins/data/public'; +import { CategorizationJobCreator } from './categorization_job_creator'; import { JOB_TYPE } from '../../../../../../common/constants/new_job'; @@ -32,6 +33,9 @@ export const jobCreatorFactory = (jobType: JOB_TYPE) => ( case JOB_TYPE.ADVANCED: jc = AdvancedJobCreator; break; + case JOB_TYPE.CATEGORIZATION: + jc = CategorizationJobCreator; + break; default: jc = SingleMetricJobCreator; break; diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/multi_metric_job_creator.ts b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/multi_metric_job_creator.ts index e86ee09d234f1..947c1bcf6c1a4 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/multi_metric_job_creator.ts +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/multi_metric_job_creator.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IndexPattern } from 'ui/index_patterns'; import { SavedSearchSavedObject } from '../../../../../../common/types/kibana'; import { JobCreator } from './job_creator'; import { @@ -22,6 +21,7 @@ import { } from '../../../../../../common/constants/new_job'; import { ml } from '../../../../services/ml_api_service'; import { getRichDetectors } from './util/general'; +import { IndexPattern } from '../../../../../../../../../../src/plugins/data/public'; export class MultiMetricJobCreator extends JobCreator { // a multi metric job has one optional overall partition field diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/population_job_creator.ts b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/population_job_creator.ts index 8fcd03982424d..9300e53c578e1 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/population_job_creator.ts +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/population_job_creator.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IndexPattern } from 'ui/index_patterns'; import { SavedSearchSavedObject } from '../../../../../../common/types/kibana'; import { JobCreator } from './job_creator'; import { @@ -17,6 +16,7 @@ import { Job, Datafeed, Detector } from './configs'; import { createBasicDetector } from './util/default_configs'; import { JOB_TYPE, CREATED_BY_LABEL } from '../../../../../../common/constants/new_job'; import { getRichDetectors } from './util/general'; +import { IndexPattern } from '../../../../../../../../../../src/plugins/data/public'; export class PopulationJobCreator extends JobCreator { // a population job has one overall over (split) field, which is the same for all detectors diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/single_metric_job_creator.ts b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/single_metric_job_creator.ts index cb8a46ade513c..f98fd4dbe970a 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/single_metric_job_creator.ts +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/single_metric_job_creator.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IndexPattern } from 'ui/index_patterns'; import { SavedSearchSavedObject } from '../../../../../../common/types/kibana'; import { parseInterval } from '../../../../../../common/util/parse_interval'; import { JobCreator } from './job_creator'; @@ -17,6 +16,7 @@ import { } from '../../../../../../common/constants/aggregation_types'; import { JOB_TYPE, CREATED_BY_LABEL } from '../../../../../../common/constants/new_job'; import { getRichDetectors } from './util/general'; +import { IndexPattern } from '../../../../../../../../../../src/plugins/data/public'; export class SingleMetricJobCreator extends JobCreator { protected _type: JOB_TYPE = JOB_TYPE.SINGLE_METRIC; diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/type_guards.ts b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/type_guards.ts index 9feb0416dd267..25ea80e18eeb3 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/type_guards.ts +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/type_guards.ts @@ -8,13 +8,15 @@ import { SingleMetricJobCreator } from './single_metric_job_creator'; import { MultiMetricJobCreator } from './multi_metric_job_creator'; import { PopulationJobCreator } from './population_job_creator'; import { AdvancedJobCreator } from './advanced_job_creator'; +import { CategorizationJobCreator } from './categorization_job_creator'; import { JOB_TYPE } from '../../../../../../common/constants/new_job'; export type JobCreatorType = | SingleMetricJobCreator | MultiMetricJobCreator | PopulationJobCreator - | AdvancedJobCreator; + | AdvancedJobCreator + | CategorizationJobCreator; export function isSingleMetricJobCreator( jobCreator: JobCreatorType @@ -37,3 +39,9 @@ export function isPopulationJobCreator( export function isAdvancedJobCreator(jobCreator: JobCreatorType): jobCreator is AdvancedJobCreator { return jobCreator.type === JOB_TYPE.ADVANCED; } + +export function isCategorizationJobCreator( + jobCreator: JobCreatorType +): jobCreator is CategorizationJobCreator { + return jobCreator.type === JOB_TYPE.CATEGORIZATION; +} diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/util/general.ts b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/util/general.ts index 760dbe447dc89..6443539a9877d 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/util/general.ts +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_creator/util/general.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { i18n } from '@kbn/i18n'; import { Job, Datafeed, Detector } from '../configs'; import { newJobCapsService } from '../../../../../services/new_job_capabilities_service'; import { @@ -18,7 +19,12 @@ import { mlCategory, } from '../../../../../../../common/types/fields'; import { mlJobService } from '../../../../../services/job_service'; -import { JobCreatorType, isMultiMetricJobCreator, isPopulationJobCreator } from '../index'; +import { + JobCreatorType, + isMultiMetricJobCreator, + isPopulationJobCreator, + isCategorizationJobCreator, +} from '../index'; import { CREATED_BY_LABEL, JOB_TYPE } from '../../../../../../../common/constants/new_job'; const getFieldByIdFactory = (scriptFields: Field[]) => (id: string) => { @@ -250,6 +256,8 @@ export function convertToAdvancedJob(jobCreator: JobCreatorType) { jobType = JOB_TYPE.MULTI_METRIC; } else if (isPopulationJobCreator(jobCreator)) { jobType = JOB_TYPE.POPULATION; + } else if (isCategorizationJobCreator(jobCreator)) { + jobType = JOB_TYPE.CATEGORIZATION; } window.location.href = window.location.href.replace(jobType, JOB_TYPE.ADVANCED); @@ -270,3 +278,30 @@ export function advancedStartDatafeed(jobCreator: JobCreatorType) { export function aggFieldPairsCanBeCharted(afs: AggFieldPair[]) { return afs.some(a => a.agg.dslName === null) === false; } + +export function getJobCreatorTitle(jobCreator: JobCreatorType) { + switch (jobCreator.type) { + case JOB_TYPE.SINGLE_METRIC: + return i18n.translate('xpack.ml.newJob.wizard.jobCreatorTitle.singleMetric', { + defaultMessage: 'Single metric', + }); + case JOB_TYPE.MULTI_METRIC: + return i18n.translate('xpack.ml.newJob.wizard.jobCreatorTitle.multiMetric', { + defaultMessage: 'Multi metric', + }); + case JOB_TYPE.POPULATION: + return i18n.translate('xpack.ml.newJob.wizard.jobCreatorTitle.population', { + defaultMessage: 'Population', + }); + case JOB_TYPE.ADVANCED: + return i18n.translate('xpack.ml.newJob.wizard.jobCreatorTitle.advanced', { + defaultMessage: 'Advanced', + }); + case JOB_TYPE.CATEGORIZATION: + return i18n.translate('xpack.ml.newJob.wizard.jobCreatorTitle.categorization', { + defaultMessage: 'Categorization', + }); + default: + return ''; + } +} diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_validator/job_validator.ts b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_validator/job_validator.ts index 3c1f767aeaf9c..976e94b377ae8 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_validator/job_validator.ts +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/job_validator/job_validator.ts @@ -12,10 +12,11 @@ import { basicDatafeedValidation, } from '../../../../../../common/util/job_utils'; import { getNewJobLimits } from '../../../../services/ml_server_info'; -import { JobCreator, JobCreatorType } from '../job_creator'; +import { JobCreator, JobCreatorType, isCategorizationJobCreator } from '../job_creator'; import { populateValidationMessages, checkForExistingJobAndGroupIds } from './util'; import { ExistingJobsAndGroups } from '../../../../services/job_service'; import { cardinalityValidator, CardinalityValidatorResult } from './validators'; +import { CATEGORY_EXAMPLES_ERROR_LIMIT } from '../../../../../../common/constants/new_job'; // delay start of validation to allow the user to make changes // e.g. if they are typing in a new value, try not to validate @@ -51,6 +52,10 @@ export interface BasicValidations { scrollSize: Validation; } +export interface AdvancedValidations { + categorizationFieldValid: Validation; +} + export class JobValidator { private _jobCreator: JobCreatorType; private _validationSummary: ValidationSummary; @@ -71,6 +76,9 @@ export class JobValidator { frequency: { valid: true }, scrollSize: { valid: true }, }; + private _advancedValidations: AdvancedValidations = { + categorizationFieldValid: { valid: true }, + }; private _validating: boolean = false; private _basicValidationResult$ = new ReplaySubject(2); @@ -141,6 +149,7 @@ export class JobValidator { this._lastDatafeedConfig = formattedDatafeedConfig; this._validateTimeout = setTimeout(() => { this._runBasicValidation(); + this._runAdvancedValidation(); this._jobCreatorSubject$.next(this._jobCreator); @@ -195,6 +204,13 @@ export class JobValidator { this._basicValidationResult$.next(this._basicValidations); } + private _runAdvancedValidation() { + if (isCategorizationJobCreator(this._jobCreator)) { + this._advancedValidations.categorizationFieldValid.valid = + this._jobCreator.categoryFieldValid > CATEGORY_EXAMPLES_ERROR_LIMIT; + } + } + private _isOverallBasicValid() { return Object.values(this._basicValidations).some(v => v.valid === false) === false; } @@ -246,4 +262,12 @@ export class JobValidator { public get validating(): boolean { return this._validating; } + + public get categorizationField() { + return this._advancedValidations.categorizationFieldValid.valid; + } + + public set categorizationField(valid: boolean) { + this._advancedValidations.categorizationFieldValid.valid = valid; + } } diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/results_loader/categorization_examples_loader.ts b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/results_loader/categorization_examples_loader.ts new file mode 100644 index 0000000000000..16f127ae3d728 --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/results_loader/categorization_examples_loader.ts @@ -0,0 +1,57 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IndexPattern } from '../../../../../../../../../../src/plugins/data/public'; +import { IndexPatternTitle } from '../../../../../../common/types/kibana'; +import { Token } from '../../../../../../common/types/categories'; +import { CategorizationJobCreator } from '../job_creator'; +import { ml } from '../../../../services/ml_api_service'; +import { NUMBER_OF_CATEGORY_EXAMPLES } from '../../../../../../common/constants/new_job'; + +export interface CategoryExample { + text: string; + tokens: Token[]; +} + +export class CategorizationExamplesLoader { + private _jobCreator: CategorizationJobCreator; + private _indexPatternTitle: IndexPatternTitle = ''; + private _timeFieldName: string = ''; + private _query: object = {}; + + constructor(jobCreator: CategorizationJobCreator, indexPattern: IndexPattern, query: object) { + this._jobCreator = jobCreator; + this._indexPatternTitle = indexPattern.title; + this._query = query; + + if (typeof indexPattern.timeFieldName === 'string') { + this._timeFieldName = indexPattern.timeFieldName; + } + } + + public async loadExamples() { + const analyzer = this._jobCreator.categorizationAnalyzer; + const categorizationFieldName = this._jobCreator.categorizationFieldName; + if (categorizationFieldName === null) { + return { valid: 0, examples: [] }; + } + + const start = Math.floor( + this._jobCreator.start + (this._jobCreator.end - this._jobCreator.start) / 2 + ); + const resp = await ml.jobs.categorizationFieldExamples( + this._indexPatternTitle, + this._query, + NUMBER_OF_CATEGORY_EXAMPLES, + categorizationFieldName, + this._timeFieldName, + start, + 0, + analyzer + ); + return resp; + } +} diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/results_loader/index.ts b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/results_loader/index.ts index ef0b05f73fa31..724c62f22e469 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/results_loader/index.ts +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/common/results_loader/index.ts @@ -5,3 +5,4 @@ */ export { ResultsLoader, Results, ModelItem, Anomaly } from './results_loader'; +export { CategorizationExamplesLoader, CategoryExample } from './categorization_examples_loader'; diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/charts/anomaly_chart/anomaly_chart.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/charts/anomaly_chart/anomaly_chart.tsx index 728229fc3091d..e519b86278ed8 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/charts/anomaly_chart/anomaly_chart.tsx +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/charts/anomaly_chart/anomaly_chart.tsx @@ -7,7 +7,7 @@ import React, { FC } from 'react'; import { Chart, Settings, TooltipType } from '@elastic/charts'; import { ModelItem, Anomaly } from '../../../../common/results_loader'; -import { Anomalies } from './anomalies'; +import { Anomalies } from '../common/anomalies'; import { ModelBounds } from './model_bounds'; import { Line } from './line'; import { Scatter } from './scatter'; diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/charts/anomaly_chart/line.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/charts/anomaly_chart/line.tsx index 3da84da900a2d..ed4f7729ccb26 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/charts/anomaly_chart/line.tsx +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/charts/anomaly_chart/line.tsx @@ -5,8 +5,7 @@ */ import React, { FC } from 'react'; -import { LineSeries, getSpecId, ScaleType, CurveType } from '@elastic/charts'; -import { getCustomColor } from '../common/utils'; +import { LineSeries, ScaleType, CurveType } from '@elastic/charts'; import { seriesStyle, LINE_COLOR } from '../common/settings'; interface Props { @@ -22,7 +21,7 @@ const lineSeriesStyle = { export const Line: FC = ({ chartData }) => { return ( = ({ chartData }) => { yScaleToDataExtent={false} curve={CurveType.CURVE_MONOTONE_X} lineSeriesStyle={lineSeriesStyle} - customSeriesColors={getCustomColor(SPEC_ID, LINE_COLOR)} + customSeriesColors={[LINE_COLOR]} /> ); }; diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/charts/anomaly_chart/model_bounds.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/charts/anomaly_chart/model_bounds.tsx index 0d76b50b80b97..588bb58135643 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/charts/anomaly_chart/model_bounds.tsx +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/charts/anomaly_chart/model_bounds.tsx @@ -5,9 +5,8 @@ */ import React, { FC } from 'react'; -import { getSpecId, ScaleType, AreaSeries, CurveType } from '@elastic/charts'; +import { ScaleType, AreaSeries, CurveType } from '@elastic/charts'; import { ModelItem } from '../../../../common/results_loader'; -import { getCustomColor } from '../common/utils'; import { seriesStyle, MODEL_COLOR } from '../common/settings'; interface Props { @@ -33,7 +32,7 @@ export const ModelBounds: FC = ({ modelData }) => { const model = modelData === undefined ? [] : modelData; return ( = ({ modelData }) => { yScaleToDataExtent={false} curve={CurveType.CURVE_MONOTONE_X} areaSeriesStyle={areaSeriesStyle} - customSeriesColors={getCustomColor(SPEC_ID, MODEL_COLOR)} + customSeriesColors={[MODEL_COLOR]} /> ); }; diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/charts/anomaly_chart/scatter.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/charts/anomaly_chart/scatter.tsx index 3a8fb9dbfb4d7..30148be62b835 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/charts/anomaly_chart/scatter.tsx +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/charts/anomaly_chart/scatter.tsx @@ -5,8 +5,7 @@ */ import React, { FC } from 'react'; -import { LineSeries, getSpecId, ScaleType, CurveType } from '@elastic/charts'; -import { getCustomColor } from '../common/utils'; +import { LineSeries, ScaleType, CurveType } from '@elastic/charts'; import { seriesStyle, LINE_COLOR } from '../common/settings'; interface Props { @@ -30,7 +29,7 @@ const scatterSeriesStyle = { export const Scatter: FC = ({ chartData }) => { return ( = ({ chartData }) => { yScaleToDataExtent={false} curve={CurveType.CURVE_MONOTONE_X} lineSeriesStyle={scatterSeriesStyle} - customSeriesColors={getCustomColor(SPEC_ID, LINE_COLOR)} + customSeriesColors={[LINE_COLOR]} /> ); }; diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/charts/anomaly_chart/anomalies.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/charts/common/anomalies.tsx similarity index 89% rename from x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/charts/anomaly_chart/anomalies.tsx rename to x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/charts/common/anomalies.tsx index c5188d045d84f..1596177daaf03 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/charts/anomaly_chart/anomalies.tsx +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/charts/common/anomalies.tsx @@ -11,7 +11,7 @@ */ import React, { Fragment, FC } from 'react'; -import { AnnotationDomainTypes, getAnnotationId, LineAnnotation } from '@elastic/charts'; +import { AnnotationDomainTypes, LineAnnotation } from '@elastic/charts'; import { Anomaly } from '../../../../common/results_loader'; import { getSeverityColor } from '../../../../../../../../common/util/anomaly_utils'; import { ANOMALY_THRESHOLD } from '../../../../../../../../common/constants/anomalies'; @@ -63,35 +63,35 @@ export const Anomalies: FC = ({ anomalyData }) => { return ( = ({ chartData }) => { return ( - + ); }; diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/charts/common/settings.ts b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/charts/common/settings.ts index b1852cbb259c1..7dec882429dce 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/charts/common/settings.ts +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/charts/common/settings.ts @@ -20,6 +20,7 @@ const themeName = IS_DARK_THEME ? darkTheme : lightTheme; export const LINE_COLOR = themeName.euiColorPrimary; export const MODEL_COLOR = themeName.euiColorPrimary; export const EVENT_RATE_COLOR = themeName.euiColorPrimary; +export const EVENT_RATE_COLOR_WITH_ANOMALIES = themeName.euiColorLightShade; export interface ChartSettings { width: string; diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/charts/common/utils.ts b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/charts/common/utils.ts index 91f4c08c72f7a..4103c1520b5b1 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/charts/common/utils.ts +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/charts/common/utils.ts @@ -3,17 +3,6 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - -import { getSpecId, CustomSeriesColorsMap, DataSeriesColorsValues } from '@elastic/charts'; - -export function getCustomColor(specId: string, color: string): CustomSeriesColorsMap { - const lineDataSeriesColorValues: DataSeriesColorsValues = { - colorValues: [], - specId: getSpecId(specId), - }; - return new Map([[lineDataSeriesColorValues, color]]); -} - export function getYRange(chartData: any[]) { if (chartData.length === 0) { return { min: 0, max: 0 }; diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/charts/event_rate_chart/event_rate_chart.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/charts/event_rate_chart/event_rate_chart.tsx index 7130a24cffb7d..ddbeb3f0f5b04 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/charts/event_rate_chart/event_rate_chart.tsx +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/charts/event_rate_chart/event_rate_chart.tsx @@ -5,30 +5,35 @@ */ import React, { FC } from 'react'; -import { BarSeries, Chart, getSpecId, ScaleType, Settings, TooltipType } from '@elastic/charts'; +import { BarSeries, Chart, ScaleType, Settings, TooltipType } from '@elastic/charts'; import { Axes } from '../common/axes'; -import { getCustomColor } from '../common/utils'; import { LineChartPoint } from '../../../../common/chart_loader'; -import { EVENT_RATE_COLOR } from '../common/settings'; +import { Anomaly } from '../../../../common/results_loader'; +import { EVENT_RATE_COLOR, EVENT_RATE_COLOR_WITH_ANOMALIES } from '../common/settings'; import { LoadingWrapper } from '../loading_wrapper'; +import { Anomalies } from '../common/anomalies'; interface Props { eventRateChartData: LineChartPoint[]; + anomalyData?: Anomaly[]; height: string; width: string; showAxis?: boolean; loading?: boolean; + fadeChart?: boolean; } -const SPEC_ID = 'event_rate'; - export const EventRateChart: FC = ({ eventRateChartData, + anomalyData, height, width, showAxis, loading = false, + fadeChart, }) => { + const barColor = fadeChart ? EVENT_RATE_COLOR_WITH_ANOMALIES : EVENT_RATE_COLOR; + return (
= ({ {showAxis === true && } + diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/charts/loading_wrapper/loading_wrapper.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/charts/loading_wrapper/loading_wrapper.tsx index 1087fdc8d0fce..b8600489a4bd9 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/charts/loading_wrapper/loading_wrapper.tsx +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/charts/loading_wrapper/loading_wrapper.tsx @@ -9,7 +9,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner } from '@elastic/eui'; interface Props { hasData: boolean; - height: string; + height?: string; loading?: boolean; } @@ -31,7 +31,7 @@ export const LoadingWrapper: FC = ({ hasData, loading = false, height, ch diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/common/edit_categorization_analyzer_flyout/edit_categorization_analyzer_flyout.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/common/edit_categorization_analyzer_flyout/edit_categorization_analyzer_flyout.tsx new file mode 100644 index 0000000000000..a44cbf3d0c71a --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/common/edit_categorization_analyzer_flyout/edit_categorization_analyzer_flyout.tsx @@ -0,0 +1,147 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { Fragment, FC, useEffect, useState, useContext } from 'react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { + EuiFlyout, + EuiFlyoutFooter, + EuiFlexGroup, + EuiFlexItem, + EuiButton, + EuiButtonEmpty, + EuiTitle, + EuiFlyoutBody, + EuiSpacer, +} from '@elastic/eui'; +import { MLJobEditor } from '../../../../../jobs_list/components/ml_job_editor'; +import { isValidJson } from '../../../../../../../../common/util/validation_utils'; +import { JobCreatorContext } from '../../job_creator_context'; +import { CategorizationJobCreator } from '../../../../common/job_creator'; +import { getNewJobDefaults } from '../../../../../../services/ml_server_info'; + +const EDITOR_HEIGHT = '800px'; + +export const EditCategorizationAnalyzerFlyout: FC = () => { + const { jobCreator: jc, jobCreatorUpdate } = useContext(JobCreatorContext); + const jobCreator = jc as CategorizationJobCreator; + const [showJsonFlyout, setShowJsonFlyout] = useState(false); + const [saveable, setSaveable] = useState(false); + + const [categorizationAnalyzerString, setCategorizationAnalyzerString] = useState( + JSON.stringify(jobCreator.categorizationAnalyzer, null, 2) + ); + + useEffect(() => { + if (showJsonFlyout === true) { + setCategorizationAnalyzerString(JSON.stringify(jobCreator.categorizationAnalyzer, null, 2)); + } + }, [showJsonFlyout]); + + function toggleJsonFlyout() { + setSaveable(false); + setShowJsonFlyout(!showJsonFlyout); + } + + function onJSONChange(json: string) { + setCategorizationAnalyzerString(json); + const valid = isValidJson(json); + setSaveable(valid); + } + + function onSave() { + jobCreator.categorizationAnalyzer = JSON.parse(categorizationAnalyzerString); + jobCreatorUpdate(); + setShowJsonFlyout(false); + } + + function onUseDefault() { + const { anomaly_detectors: anomalyDetectors } = getNewJobDefaults(); + const analyzerString = JSON.stringify(anomalyDetectors.categorization_analyzer!, null, 2); + onJSONChange(analyzerString); + } + + return ( + + + + {showJsonFlyout === true && ( + setShowJsonFlyout(false)} hideCloseButton size="m"> + + + + + + + setShowJsonFlyout(false)} + flush="left" + > + + + + + + + + + + + + + + + + + + )} + + ); +}; + +const FlyoutButton: FC<{ onClick(): void }> = ({ onClick }) => { + return ( + + + + ); +}; + +const Contents: FC<{ + title: string; + value: string; + onChange(s: string): void; +}> = ({ title, value, onChange }) => { + return ( + + +
{title}
+
+ + +
+ ); +}; diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/common/edit_categorization_analyzer_flyout/index.ts b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/common/edit_categorization_analyzer_flyout/index.ts new file mode 100644 index 0000000000000..5bc89a695b6c8 --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/common/edit_categorization_analyzer_flyout/index.ts @@ -0,0 +1,6 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +export { EditCategorizationAnalyzerFlyout } from './edit_categorization_analyzer_flyout'; diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/additional_section/components/calendars/description.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/additional_section/components/calendars/description.tsx index e11fb615efd70..828c91052b30b 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/additional_section/components/calendars/description.tsx +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/additional_section/components/calendars/description.tsx @@ -10,7 +10,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { EuiDescribedFormGroup, EuiFormRow, EuiLink } from '@elastic/eui'; import { metadata } from 'ui/metadata'; -const docsUrl = `https://www.elastic.co/guide/en/elastic-stack-overview/${metadata.branch}/ml-calendars.html`; +const docsUrl = `https://www.elastic.co/guide/en/machine-learning/${metadata.branch}/ml-calendars.html`; export const Description: FC = memo(({ children }) => { const title = i18n.translate( diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/additional_section/components/custom_urls/description.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/additional_section/components/custom_urls/description.tsx index 89cd5c252c3d6..566bd313dbc6e 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/additional_section/components/custom_urls/description.tsx +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/additional_section/components/custom_urls/description.tsx @@ -10,7 +10,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { EuiDescribedFormGroup, EuiFormRow, EuiLink } from '@elastic/eui'; import { metadata } from 'ui/metadata'; -const docsUrl = `https://www.elastic.co/guide/en/elastic-stack-overview/${metadata.branch}/ml-configuring-url.html`; +const docsUrl = `https://www.elastic.co/guide/en/machine-learning/${metadata.branch}/ml-configuring-url.html`; export const Description: FC = memo(({ children }) => { const title = i18n.translate( diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/advanced_section/components/model_plot/model_plot_switch.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/advanced_section/components/model_plot/model_plot_switch.tsx index 64f9f450ae08d..a034bdcc2900f 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/advanced_section/components/model_plot/model_plot_switch.tsx +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/job_details_step/components/advanced_section/components/model_plot/model_plot_switch.tsx @@ -10,16 +10,29 @@ import { EuiSpacer, EuiSwitch } from '@elastic/eui'; import { JobCreatorContext } from '../../../../../job_creator_context'; import { Description } from './description'; import { MMLCallout } from '../mml_callout'; +import { ML_JOB_AGGREGATION } from '../../../../../../../../../../../common/constants/aggregation_types'; +import { isCategorizationJobCreator } from '../../../../../../../common/job_creator'; export const ModelPlotSwitch: FC = () => { - const { jobCreator, jobCreatorUpdate } = useContext(JobCreatorContext); + const { jobCreator, jobCreatorUpdate, jobCreatorUpdated } = useContext(JobCreatorContext); const [modelPlotEnabled, setModelPlotEnabled] = useState(jobCreator.modelPlot); + const [enabled, setEnabled] = useState(false); useEffect(() => { jobCreator.modelPlot = modelPlotEnabled; jobCreatorUpdate(); }, [modelPlotEnabled]); + useEffect(() => { + const aggs = [ML_JOB_AGGREGATION.RARE]; + // disable model plot switch if the wizard is creating a categorization job + // and a rare detector is being used. + const isRareCategoryJob = + isCategorizationJobCreator(jobCreator) && + jobCreator.aggregations.some(agg => aggs.includes(agg.id)); + setEnabled(isRareCategoryJob === false); + }, [jobCreatorUpdated]); + function toggleModelPlot() { setModelPlotEnabled(!modelPlotEnabled); } @@ -29,6 +42,7 @@ export const ModelPlotSwitch: FC = () => { = ({ setIsValid }) => { - const { jobCreator, jobCreatorUpdate, jobCreatorUpdated } = useContext(JobCreatorContext); - const [bucketSpan, setBucketSpan] = useState(jobCreator.bucketSpan); - - useEffect(() => { - jobCreator.bucketSpan = bucketSpan; - jobCreatorUpdate(); - setIsValid(bucketSpan !== ''); - }, [bucketSpan]); - - useEffect(() => { - setBucketSpan(jobCreator.bucketSpan); - }, [jobCreatorUpdated]); - return ( diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/bucket_span/bucket_span.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/bucket_span/bucket_span.tsx index dfe9272984b81..216561bac2c62 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/bucket_span/bucket_span.tsx +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/bucket_span/bucket_span.tsx @@ -14,9 +14,10 @@ import { BucketSpanEstimator } from '../bucket_span_estimator'; interface Props { setIsValid: (proceed: boolean) => void; + hideEstimateButton?: boolean; } -export const BucketSpan: FC = ({ setIsValid }) => { +export const BucketSpan: FC = ({ setIsValid, hideEstimateButton = false }) => { const { jobCreator, jobCreatorUpdate, @@ -56,9 +57,11 @@ export const BucketSpan: FC = ({ setIsValid }) => { disabled={estimating} />
- - - + {hideEstimateButton === false && ( + + + + )}
); diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_detector/categorization_detector.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_detector/categorization_detector.tsx new file mode 100644 index 0000000000000..96ac1c3f0e325 --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_detector/categorization_detector.tsx @@ -0,0 +1,64 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FC, useContext, useEffect, useState } from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiFlexGroup, EuiSpacer, EuiTitle } from '@elastic/eui'; + +import { ML_JOB_AGGREGATION } from '../../../../../../../../../common/constants/aggregation_types'; +import { JobCreatorContext } from '../../../job_creator_context'; +import { CategorizationJobCreator } from '../../../../../common/job_creator'; +import { CountCard, RareCard } from './detector_cards'; + +export const CategorizationDetector: FC = () => { + const { jobCreator: jc, jobCreatorUpdate, jobCreatorUpdated } = useContext(JobCreatorContext); + const jobCreator = jc as CategorizationJobCreator; + const [categorizationDetectorType, setCategorizationDetectorType] = useState( + jobCreator.selectedDetectorType + ); + + useEffect(() => { + if (categorizationDetectorType !== jobCreator.selectedDetectorType) { + jobCreator.setDetectorType(categorizationDetectorType); + jobCreatorUpdate(); + } + }, [categorizationDetectorType]); + + useEffect(() => { + setCategorizationDetectorType(jobCreator.selectedDetectorType); + }, [jobCreatorUpdated]); + + function onCountSelection() { + setCategorizationDetectorType(ML_JOB_AGGREGATION.COUNT); + } + function onRareSelection() { + setCategorizationDetectorType(ML_JOB_AGGREGATION.RARE); + } + + return ( + <> + +

+ +

+
+ + + + + + + ); +}; diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_detector/detector_cards.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_detector/detector_cards.tsx new file mode 100644 index 0000000000000..68d5fc24a96e3 --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_detector/detector_cards.tsx @@ -0,0 +1,59 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FC } from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; +import { EuiFlexItem, EuiCard } from '@elastic/eui'; + +interface CardProps { + onClick: () => void; + isSelected: boolean; +} + +export const CountCard: FC = ({ onClick, isSelected }) => ( + + + + + } + selectable={{ onClick, isSelected }} + /> + +); + +export const RareCard: FC = ({ onClick, isSelected }) => ( + + + + + } + selectable={{ onClick, isSelected }} + /> + +); diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_detector/index.ts b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_detector/index.ts new file mode 100644 index 0000000000000..6a13d86d0db9a --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_detector/index.ts @@ -0,0 +1,6 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +export { CategorizationDetector } from './categorization_detector'; diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_field/categorization_field.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_field/categorization_field.tsx index f9edf79364c97..7f7659d8bb6fd 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_field/categorization_field.tsx +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_field/categorization_field.tsx @@ -10,23 +10,26 @@ import { CategorizationFieldSelect } from './categorization_field_select'; import { JobCreatorContext } from '../../../job_creator_context'; import { newJobCapsService } from '../../../../../../../services/new_job_capabilities_service'; import { - MultiMetricJobCreator, - PopulationJobCreator, AdvancedJobCreator, + CategorizationJobCreator, + isCategorizationJobCreator, } from '../../../../../common/job_creator'; import { Description } from './description'; export const CategorizationField: FC = () => { const { jobCreator: jc, jobCreatorUpdate, jobCreatorUpdated } = useContext(JobCreatorContext); - const jobCreator = jc as MultiMetricJobCreator | PopulationJobCreator | AdvancedJobCreator; + const jobCreator = jc as AdvancedJobCreator | CategorizationJobCreator; const { catFields } = newJobCapsService; const [categorizationFieldName, setCategorizationFieldName] = useState( jobCreator.categorizationFieldName ); + const isCategorizationJob = isCategorizationJobCreator(jobCreator); useEffect(() => { - jobCreator.categorizationFieldName = categorizationFieldName; - jobCreatorUpdate(); + if (jobCreator.categorizationFieldName !== categorizationFieldName) { + jobCreator.categorizationFieldName = categorizationFieldName; + jobCreatorUpdate(); + } }, [categorizationFieldName]); useEffect(() => { @@ -34,7 +37,7 @@ export const CategorizationField: FC = () => { }, [jobCreatorUpdated]); return ( - + { +interface Props { + isOptional: boolean; +} +export const Description: FC = memo(({ children, isOptional }) => { const title = i18n.translate('xpack.ml.newJob.wizard.pickFieldsStep.categorizationField.title', { defaultMessage: 'Categorization field', }); @@ -18,10 +21,19 @@ export const Description: FC = memo(({ children }) => { idAria="description" title={

{title}

} description={ - + <> + {isOptional ? ( + + ) : ( + + )} + } > diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/categorization_view.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/categorization_view.tsx new file mode 100644 index 0000000000000..5017b0c3239e8 --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/categorization_view.tsx @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FC, useEffect, useState } from 'react'; +import { EuiHorizontalRule } from '@elastic/eui'; + +import { CategorizationDetectors } from './metric_selection'; +import { CategorizationDetectorsSummary } from './metric_selection_summary'; +import { CategorizationSettings } from './settings'; + +interface Props { + isActive: boolean; + setCanProceed?: (proceed: boolean) => void; +} + +export const CategorizationView: FC = ({ isActive, setCanProceed }) => { + const [categoryFieldValid, setCategoryFieldValid] = useState(false); + const [settingsValid, setSettingsValid] = useState(false); + + useEffect(() => { + if (typeof setCanProceed === 'function') { + setCanProceed(categoryFieldValid && settingsValid); + } + }, [categoryFieldValid, settingsValid]); + + return isActive === false ? ( + + ) : ( + <> + + {categoryFieldValid && ( + <> + + + + )} + + ); +}; diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/examples_valid_callout.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/examples_valid_callout.tsx new file mode 100644 index 0000000000000..04934d2dc9a36 --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/examples_valid_callout.tsx @@ -0,0 +1,112 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FC } from 'react'; +import { EuiCallOut, EuiSpacer, EuiCallOutProps } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; + +import { CategorizationAnalyzer } from '../../../../../../../services/ml_server_info'; +import { EditCategorizationAnalyzerFlyout } from '../../../common/edit_categorization_analyzer_flyout'; +import { + NUMBER_OF_CATEGORY_EXAMPLES, + CATEGORY_EXAMPLES_MULTIPLIER, + CATEGORY_EXAMPLES_ERROR_LIMIT, + CATEGORY_EXAMPLES_WARNING_LIMIT, +} from '../../../../../../../../../common/constants/new_job'; + +type CategorizationAnalyzerType = CategorizationAnalyzer | null; + +interface Props { + examplesValid: number; + categorizationAnalyzer: CategorizationAnalyzerType; +} + +export const ExamplesValidCallout: FC = ({ examplesValid, categorizationAnalyzer }) => { + const percentageText = ; + const analyzerUsed = ; + + let color: EuiCallOutProps['color'] = 'success'; + let title = i18n.translate( + 'xpack.ml.newJob.wizard.pickFieldsStep.categorizationFieldCalloutTitle.valid', + { + defaultMessage: 'Selected category field is valid', + } + ); + + if (examplesValid < CATEGORY_EXAMPLES_ERROR_LIMIT) { + color = 'danger'; + title = i18n.translate( + 'xpack.ml.newJob.wizard.pickFieldsStep.categorizationFieldCalloutTitle.invalid', + { + defaultMessage: 'Selected category field is invalid', + } + ); + } else if (examplesValid < CATEGORY_EXAMPLES_WARNING_LIMIT) { + color = 'warning'; + title = i18n.translate( + 'xpack.ml.newJob.wizard.pickFieldsStep.categorizationFieldCalloutTitle.possiblyInvalid', + { + defaultMessage: 'Selected category field is possibly invalid', + } + ); + } + + return ( + + {percentageText} + + {analyzerUsed} + + ); +}; + +const PercentageText: FC<{ examplesValid: number }> = ({ examplesValid }) => ( +
+ +
+); + +const AnalyzerUsed: FC<{ categorizationAnalyzer: CategorizationAnalyzerType }> = ({ + categorizationAnalyzer, +}) => { + let analyzer = ''; + if (typeof categorizationAnalyzer === null) { + return null; + } + + if (typeof categorizationAnalyzer === 'string') { + analyzer = categorizationAnalyzer; + } else { + if (categorizationAnalyzer?.tokenizer !== undefined) { + analyzer = categorizationAnalyzer?.tokenizer!; + } else if (categorizationAnalyzer?.analyzer !== undefined) { + analyzer = categorizationAnalyzer?.analyzer!; + } + } + + return ( + <> +
+ +
+
+ +
+ + ); +}; diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/field_examples.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/field_examples.tsx new file mode 100644 index 0000000000000..7f9b2e43b9005 --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/field_examples.tsx @@ -0,0 +1,65 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FC } from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiBasicTable, EuiText } from '@elastic/eui'; +import { CategoryExample } from '../../../../../common/results_loader'; + +interface Props { + fieldExamples: CategoryExample[] | null; +} + +const TOKEN_HIGHLIGHT_COLOR = '#b0ccf7'; + +export const FieldExamples: FC = ({ fieldExamples }) => { + if (fieldExamples === null || fieldExamples.length === 0) { + return null; + } + + const columns = [ + { + field: 'example', + name: i18n.translate( + 'xpack.ml.newJob.wizard.pickFieldsStep.categorizationFieldExamples.title', + { + defaultMessage: 'Examples', + } + ), + render: (example: any) => ( + + {example} + + ), + }, + ]; + const items = fieldExamples.map((example, i) => { + const txt = []; + let tokenCounter = 0; + let buffer = ''; + let charCount = 0; + while (charCount < example.text.length) { + const token = example.tokens[tokenCounter]; + if (token && charCount === token.start_offset) { + txt.push(buffer); + buffer = ''; + txt.push({token.token}); + charCount += token.end_offset - token.start_offset; + tokenCounter++; + } else { + buffer += example.text[charCount]; + charCount++; + } + } + txt.push(buffer); + return { example: txt }; + }); + return ; +}; + +const Token: FC = ({ children }) => ( + {children} +); diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/index.ts b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/index.ts new file mode 100644 index 0000000000000..f61dfd88a37bb --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { CategorizationView } from './categorization_view'; diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/metric_selection.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/metric_selection.tsx new file mode 100644 index 0000000000000..fda0066f9cd37 --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/metric_selection.tsx @@ -0,0 +1,108 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FC, useContext, useEffect, useState } from 'react'; +import { EuiHorizontalRule } from '@elastic/eui'; + +import { JobCreatorContext } from '../../../job_creator_context'; +import { CategorizationJobCreator } from '../../../../../common/job_creator'; +import { CategorizationField } from '../categorization_field'; +import { CategorizationDetector } from '../categorization_detector'; +import { FieldExamples } from './field_examples'; +import { ExamplesValidCallout } from './examples_valid_callout'; +import { CategoryExample } from '../../../../../common/results_loader'; +import { LoadingWrapper } from '../../../charts/loading_wrapper'; + +interface Props { + setIsValid: (na: boolean) => void; +} + +export const CategorizationDetectors: FC = ({ setIsValid }) => { + const { jobCreator: jc, jobCreatorUpdate, jobCreatorUpdated } = useContext(JobCreatorContext); + const jobCreator = jc as CategorizationJobCreator; + + const [loadingData, setLoadingData] = useState(false); + const [start, setStart] = useState(jobCreator.start); + const [end, setEnd] = useState(jobCreator.end); + const [categorizationAnalyzerString, setCategorizationAnalyzerString] = useState( + JSON.stringify(jobCreator.categorizationAnalyzer) + ); + const [fieldExamples, setFieldExamples] = useState(null); + const [examplesValid, setExamplesValid] = useState(0); + + const [categorizationFieldName, setCategorizationFieldName] = useState( + jobCreator.categorizationFieldName + ); + + useEffect(() => { + if (jobCreator.categorizationFieldName !== categorizationFieldName) { + jobCreator.categorizationFieldName = categorizationFieldName; + jobCreatorUpdate(); + } + loadFieldExamples(); + }, [categorizationFieldName]); + + useEffect(() => { + let updateExamples = false; + if (jobCreator.start !== start || jobCreator.end !== end) { + setStart(jobCreator.start); + setEnd(jobCreator.end); + updateExamples = true; + } + const tempCategorizationAnalyzerString = JSON.stringify(jobCreator.categorizationAnalyzer); + if (tempCategorizationAnalyzerString !== categorizationAnalyzerString) { + setCategorizationAnalyzerString(tempCategorizationAnalyzerString); + updateExamples = true; + } + + if (updateExamples) { + loadFieldExamples(); + } + if (jobCreator.categorizationFieldName !== categorizationFieldName) { + setCategorizationFieldName(jobCreator.categorizationFieldName); + } + }, [jobCreatorUpdated]); + + async function loadFieldExamples() { + if (categorizationFieldName !== null) { + setLoadingData(true); + const { valid, examples } = await jobCreator.loadCategorizationFieldExamples(); + setFieldExamples(examples); + setExamplesValid(valid); + setLoadingData(false); + } else { + setFieldExamples(null); + setExamplesValid(0); + } + setIsValid(categorizationFieldName !== null); + } + + useEffect(() => { + jobCreatorUpdate(); + }, [examplesValid]); + + return ( + <> + + + + {loadingData === true && ( + +
+ + )} + {fieldExamples !== null && loadingData === false && ( + <> + + + + )} + + ); +}; diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/metric_selection_summary.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/metric_selection_summary.tsx new file mode 100644 index 0000000000000..768d8c394fb8f --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/metric_selection_summary.tsx @@ -0,0 +1,78 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FC, useContext, useEffect, useState } from 'react'; +import { JobCreatorContext } from '../../../job_creator_context'; +import { CategorizationJobCreator } from '../../../../../common/job_creator'; +import { Results, Anomaly } from '../../../../../common/results_loader'; +import { LineChartPoint } from '../../../../../common/chart_loader'; +import { EventRateChart } from '../../../charts/event_rate_chart'; +import { TopCategories } from './top_categories'; + +const DTR_IDX = 0; + +export const CategorizationDetectorsSummary: FC = () => { + const { jobCreator: jc, chartLoader, resultsLoader, chartInterval } = useContext( + JobCreatorContext + ); + const jobCreator = jc as CategorizationJobCreator; + + const [loadingData, setLoadingData] = useState(false); + const [anomalyData, setAnomalyData] = useState([]); + const [eventRateChartData, setEventRateChartData] = useState([]); + const [jobIsRunning, setJobIsRunning] = useState(false); + + function setResultsWrapper(results: Results) { + const anomalies = results.anomalies[DTR_IDX]; + if (anomalies !== undefined) { + setAnomalyData(anomalies); + } + } + + function watchProgress(progress: number) { + setJobIsRunning(progress > 0); + } + + useEffect(() => { + // subscribe to progress and results + const resultsSubscription = resultsLoader.subscribeToResults(setResultsWrapper); + jobCreator.subscribeToProgress(watchProgress); + loadChart(); + return () => { + resultsSubscription.unsubscribe(); + }; + }, []); + + async function loadChart() { + setLoadingData(true); + try { + const resp = await chartLoader.loadEventRateChart( + jobCreator.start, + jobCreator.end, + chartInterval.getInterval().asMilliseconds() + ); + setEventRateChartData(resp); + } catch (error) { + setEventRateChartData([]); + } + setLoadingData(false); + } + + return ( + <> + + + + ); +}; diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/settings.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/settings.tsx new file mode 100644 index 0000000000000..55db3d495707d --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/settings.tsx @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FC } from 'react'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; + +import { BucketSpan } from '../bucket_span'; + +interface Props { + setIsValid: (proceed: boolean) => void; +} + +export const CategorizationSettings: FC = ({ setIsValid }) => { + return ( + <> + + + + + + + ); +}; diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/top_categories.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/top_categories.tsx new file mode 100644 index 0000000000000..3bade07250b46 --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/categorization_view/top_categories.tsx @@ -0,0 +1,89 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FC, useContext, useEffect, useState } from 'react'; +import { EuiBasicTable, EuiText } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { JobCreatorContext } from '../../../job_creator_context'; +import { CategorizationJobCreator } from '../../../../../common/job_creator'; +import { Results } from '../../../../../common/results_loader'; +import { ml } from '../../../../../../../services/ml_api_service'; +import { NUMBER_OF_CATEGORY_EXAMPLES } from '../../../../../../../../../common/constants/new_job'; + +export const TopCategories: FC = () => { + const { jobCreator: jc, resultsLoader } = useContext(JobCreatorContext); + const jobCreator = jc as CategorizationJobCreator; + + const [tableRow, setTableRow] = useState>([]); + const [totalCategories, setTotalCategories] = useState(0); + + function setResultsWrapper(results: Results) { + loadTopCats(); + } + + async function loadTopCats() { + const results = await ml.jobs.topCategories(jobCreator.jobId, NUMBER_OF_CATEGORY_EXAMPLES); + setTableRow( + results.categories.map(c => ({ + count: c.count, + example: c.category.examples?.length ? c.category.examples[0] : '', + })) + ); + setTotalCategories(results.total); + } + + useEffect(() => { + // subscribe to result updates + const resultsSubscription = resultsLoader.subscribeToResults(setResultsWrapper); + return () => { + resultsSubscription.unsubscribe(); + }; + }, []); + + const columns = [ + // only include counts if model plot is enabled + ...(jobCreator.modelPlot + ? [ + { + field: 'count', + name: 'count', + width: '100px', + render: (count: any) => ( + + {count} + + ), + }, + ] + : []), + { + field: 'example', + name: 'Example', + render: (example: any) => ( + + {example} + + ), + }, + ]; + + return ( + <> + {totalCategories > 0 && ( + <> +
+ +
+ + + )} + + ); +}; diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/multi_metric_view/settings.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/multi_metric_view/settings.tsx index 5e800de755f26..b28a9d3da81dc 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/multi_metric_view/settings.tsx +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/multi_metric_view/settings.tsx @@ -4,10 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { Fragment, FC, useContext, useEffect, useState } from 'react'; +import React, { Fragment, FC } from 'react'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { JobCreatorContext } from '../../../job_creator_context'; import { BucketSpan } from '../bucket_span'; import { SplitFieldSelector } from '../split_field'; import { Influencers } from '../influencers'; @@ -18,19 +17,6 @@ interface Props { } export const MultiMetricSettings: FC = ({ setIsValid }) => { - const { jobCreator, jobCreatorUpdate, jobCreatorUpdated } = useContext(JobCreatorContext); - const [bucketSpan, setBucketSpan] = useState(jobCreator.bucketSpan); - - useEffect(() => { - jobCreator.bucketSpan = bucketSpan; - jobCreatorUpdate(); - setIsValid(bucketSpan !== ''); - }, [bucketSpan]); - - useEffect(() => { - setBucketSpan(jobCreator.bucketSpan); - }, [jobCreatorUpdated]); - return ( diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/population_view/settings.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/population_view/settings.tsx index 46cdf44ce0f7d..b9de755e6c946 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/population_view/settings.tsx +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/population_view/settings.tsx @@ -4,10 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { Fragment, FC, useContext, useEffect, useState } from 'react'; +import React, { Fragment, FC } from 'react'; import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import { JobCreatorContext } from '../../../job_creator_context'; import { BucketSpan } from '../bucket_span'; import { Influencers } from '../influencers'; @@ -16,19 +15,6 @@ interface Props { } export const PopulationSettings: FC = ({ setIsValid }) => { - const { jobCreator, jobCreatorUpdate, jobCreatorUpdated } = useContext(JobCreatorContext); - const [bucketSpan, setBucketSpan] = useState(jobCreator.bucketSpan); - - useEffect(() => { - jobCreator.bucketSpan = bucketSpan; - jobCreatorUpdate(); - setIsValid(bucketSpan !== ''); - }, [bucketSpan]); - - useEffect(() => { - setBucketSpan(jobCreator.bucketSpan); - }, [jobCreatorUpdated]); - return ( diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/single_metric_view/settings.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/single_metric_view/settings.tsx index c750235051a6f..f8e7275cf15bb 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/single_metric_view/settings.tsx +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/components/single_metric_view/settings.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { Fragment, FC, useContext, useEffect, useState } from 'react'; +import React, { Fragment, FC, useContext } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiFlexGroup, EuiFlexItem, EuiButtonEmpty } from '@elastic/eui'; @@ -19,18 +19,7 @@ interface Props { } export const SingleMetricSettings: FC = ({ setIsValid }) => { - const { jobCreator, jobCreatorUpdate, jobCreatorUpdated } = useContext(JobCreatorContext); - const [bucketSpan, setBucketSpan] = useState(jobCreator.bucketSpan); - - useEffect(() => { - jobCreator.bucketSpan = bucketSpan; - jobCreatorUpdate(); - setIsValid(bucketSpan !== ''); - }, [bucketSpan]); - - useEffect(() => { - setBucketSpan(jobCreator.bucketSpan); - }, [jobCreatorUpdated]); + const { jobCreator } = useContext(JobCreatorContext); const convertToMultiMetric = () => { convertToMultiMetricJob(jobCreator); diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/pick_fields.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/pick_fields.tsx index 795dfc30f954a..bfec49678bc34 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/pick_fields.tsx +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/pick_fields_step/pick_fields.tsx @@ -15,6 +15,7 @@ import { SingleMetricView } from './components/single_metric_view'; import { MultiMetricView } from './components/multi_metric_view'; import { PopulationView } from './components/population_view'; import { AdvancedView } from './components/advanced_view'; +import { CategorizationView } from './components/categorization_view'; import { JsonEditorFlyout, EDITOR_MODE } from '../common/json_editor_flyout'; import { DatafeedPreviewFlyout } from '../common/datafeed_preview_flyout'; @@ -30,7 +31,9 @@ export const PickFieldsStep: FC = ({ setCurrentStep, isCurrentStep }) (jobCreator.type === JOB_TYPE.ADVANCED && jobValidator.modelMemoryLimit.valid)) && jobValidator.bucketSpan.valid && jobValidator.duplicateDetectors.valid && - jobValidator.validating === false; + jobValidator.validating === false && + (jobCreator.type !== JOB_TYPE.CATEGORIZATION || + (jobCreator.type === JOB_TYPE.CATEGORIZATION && jobValidator.categorizationField)); setNextActive(active); }, [jobValidatorUpdated]); @@ -50,6 +53,9 @@ export const PickFieldsStep: FC = ({ setCurrentStep, isCurrentStep }) {jobType === JOB_TYPE.ADVANCED && ( )} + {jobType === JOB_TYPE.CATEGORIZATION && ( + + )} setCurrentStep( diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/summary_step/components/detector_chart/detector_chart.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/summary_step/components/detector_chart/detector_chart.tsx index f72ff6cf985e5..564228604d71e 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/summary_step/components/detector_chart/detector_chart.tsx +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/summary_step/components/detector_chart/detector_chart.tsx @@ -11,6 +11,7 @@ import { SingleMetricView } from '../../../pick_fields_step/components/single_me import { MultiMetricView } from '../../../pick_fields_step/components/multi_metric_view'; import { PopulationView } from '../../../pick_fields_step/components/population_view'; import { AdvancedView } from '../../../pick_fields_step/components/advanced_view'; +import { CategorizationView } from '../../../pick_fields_step/components/categorization_view'; export const DetectorChart: FC = () => { const { jobCreator } = useContext(JobCreatorContext); @@ -21,6 +22,7 @@ export const DetectorChart: FC = () => { {jobCreator.type === JOB_TYPE.MULTI_METRIC && } {jobCreator.type === JOB_TYPE.POPULATION && } {jobCreator.type === JOB_TYPE.ADVANCED && } + {jobCreator.type === JOB_TYPE.CATEGORIZATION && } ); }; diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/wizard_nav/wizard_nav.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/wizard_nav/wizard_nav.tsx index b0c5758fdd2e7..92429cf907592 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/wizard_nav/wizard_nav.tsx +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/components/wizard_nav/wizard_nav.tsx @@ -8,7 +8,13 @@ import React, { FC, Fragment } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; -import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiHorizontalRule } from '@elastic/eui'; +import { + EuiButton, + EuiButtonEmpty, + EuiFlexGroup, + EuiFlexItem, + EuiHorizontalRule, +} from '@elastic/eui'; interface StepsNavProps { previousActive?: boolean; @@ -44,20 +50,23 @@ export const WizardNav: FC = ({ ); export const PreviousButton: FC = ({ previous, previousActive = true }) => ( - - + ); export const NextButton: FC = ({ next, nextActive = true }) => ( diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/index_or_search/preconfigured_job_redirect.ts b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/index_or_search/preconfigured_job_redirect.ts index 8500279e742b7..51fc226751ae2 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/index_or_search/preconfigured_job_redirect.ts +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/index_or_search/preconfigured_job_redirect.ts @@ -32,15 +32,24 @@ function getWizardUrlFromCloningJob(job: CombinedJob) { const created = job?.custom_settings?.created_by; let page = ''; - if (created === CREATED_BY_LABEL.SINGLE_METRIC) { - page = JOB_TYPE.SINGLE_METRIC; - } else if (created === CREATED_BY_LABEL.MULTI_METRIC) { - page = JOB_TYPE.MULTI_METRIC; - } else if (created === CREATED_BY_LABEL.POPULATION) { - page = JOB_TYPE.POPULATION; - } else { - page = JOB_TYPE.ADVANCED; + switch (created) { + case CREATED_BY_LABEL.SINGLE_METRIC: + page = JOB_TYPE.SINGLE_METRIC; + break; + case CREATED_BY_LABEL.MULTI_METRIC: + page = JOB_TYPE.MULTI_METRIC; + break; + case CREATED_BY_LABEL.POPULATION: + page = JOB_TYPE.POPULATION; + break; + case CREATED_BY_LABEL.CATEGORIZATION: + page = JOB_TYPE.CATEGORIZATION; + break; + default: + page = JOB_TYPE.ADVANCED; + break; } + const indexPatternId = getIndexPatternIdFromName(job.datafeed_config.indices[0]); return `jobs/new_job/${page}?index=${indexPatternId}&_g=()`; diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/job_type/page.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/job_type/page.tsx index dbae1948cbe0f..9a44d561c2d94 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/job_type/page.tsx +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/job_type/page.tsx @@ -151,6 +151,22 @@ export const Page: FC = () => { }), id: 'mlJobTypeLinkAdvancedJob', }, + { + href: getUrl('#jobs/new_job/categorization'), + icon: { + type: 'createAdvancedJob', + ariaLabel: i18n.translate('xpack.ml.newJob.wizard.jobType.categorizationAriaLabel', { + defaultMessage: 'Categorization job', + }), + }, + title: i18n.translate('xpack.ml.newJob.wizard.jobType.categorizationTitle', { + defaultMessage: 'Categorization', + }), + description: i18n.translate('xpack.ml.newJob.wizard.jobType.categorizationDescription', { + defaultMessage: 'Group log messages into categories and detect anomalies within them.', + }), + id: 'mlJobTypeLinkCategorizationJob', + }, ]; return ( diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/new_job/page.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/new_job/page.tsx index 79f98c1170ff8..ece43e00f2eb1 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/new_job/page.tsx +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/new_job/page.tsx @@ -5,13 +5,26 @@ */ import React, { FC, useEffect, Fragment } from 'react'; - -import { EuiPage, EuiPageBody, EuiPageContentBody } from '@elastic/eui'; +import { + EuiPage, + EuiPageBody, + EuiPageContent, + EuiPageContentHeader, + EuiPageContentHeaderSection, + EuiTitle, + EuiPageContentBody, +} from '@elastic/eui'; import { toastNotifications } from 'ui/notify'; import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; import { Wizard } from './wizard'; import { WIZARD_STEPS } from '../components/step_types'; -import { jobCreatorFactory, isAdvancedJobCreator } from '../../common/job_creator'; +import { getJobCreatorTitle } from '../../common/job_creator/util/general'; +import { + jobCreatorFactory, + isAdvancedJobCreator, + isCategorizationJobCreator, +} from '../../common/job_creator'; import { JOB_TYPE, DEFAULT_MODEL_MEMORY_LIMIT, @@ -25,6 +38,9 @@ import { getTimeFilterRange } from '../../../../components/full_time_range_selec import { TimeBuckets } from '../../../../util/time_buckets'; import { ExistingJobsAndGroups, mlJobService } from '../../../../services/job_service'; import { expandCombinedJobConfig } from '../../common/job_creator/configs'; +import { newJobCapsService } from '../../../../services/new_job_capabilities_service'; +import { EVENT_RATE_FIELD_ID } from '../../../../../../common/types/fields'; +import { getNewJobDefaults } from '../../../../services/ml_server_info'; const PAGE_WIDTH = 1200; // document.querySelector('.single-metric-job-container').width(); const BAR_TARGET = PAGE_WIDTH > 2000 ? 1000 : PAGE_WIDTH / 2; @@ -37,7 +53,6 @@ export interface PageProps { export const Page: FC = ({ existingJobsAndGroups, jobType }) => { const kibanaContext = useKibanaContext(); - const jobCreator = jobCreatorFactory(jobType)( kibanaContext.currentIndexPattern, kibanaContext.currentSavedSearch, @@ -58,6 +73,7 @@ export const Page: FC = ({ existingJobsAndGroups, jobType }) => { // cloning a job const clonedJob = mlJobService.cloneJob(mlJobService.tempJobCloningObjects.job); const { job, datafeed } = expandCombinedJobConfig(clonedJob); + initCategorizationSettings(); jobCreator.cloneFromExistingJob(job, datafeed); // if we're not skipping the time range, this is a standard job clone, so wipe the jobId @@ -95,7 +111,11 @@ export const Page: FC = ({ existingJobsAndGroups, jobType }) => { // creating a new job jobCreator.bucketSpan = DEFAULT_BUCKET_SPAN; - if (jobCreator.type !== JOB_TYPE.POPULATION && jobCreator.type !== JOB_TYPE.ADVANCED) { + if ( + jobCreator.type !== JOB_TYPE.POPULATION && + jobCreator.type !== JOB_TYPE.ADVANCED && + jobCreator.type !== JOB_TYPE.CATEGORIZATION + ) { // for all other than population or advanced, use 10MB jobCreator.modelMemoryLimit = DEFAULT_MODEL_MEMORY_LIMIT; } @@ -112,6 +132,13 @@ export const Page: FC = ({ existingJobsAndGroups, jobType }) => { // auto set the time range if creating a new advanced job autoSetTimeRange = isAdvancedJobCreator(jobCreator); + initCategorizationSettings(); + if (isCategorizationJobCreator(jobCreator)) { + const { catFields } = newJobCapsService; + if (catFields.length === 1) { + jobCreator.categorizationFieldName = catFields[0].name; + } + } } if (autoSetTimeRange && isAdvancedJobCreator(jobCreator)) { @@ -129,6 +156,20 @@ export const Page: FC = ({ existingJobsAndGroups, jobType }) => { } } + function initCategorizationSettings() { + if (isCategorizationJobCreator(jobCreator)) { + // categorization job will always use a count agg, so give it + // to the job creator now + const count = newJobCapsService.getAggById('count'); + const rare = newJobCapsService.getAggById('rare'); + const eventRate = newJobCapsService.getFieldById(EVENT_RATE_FIELD_ID); + jobCreator.setDefaultDetectorProperties(count, rare, eventRate); + + const { anomaly_detectors: anomalyDetectors } = getNewJobDefaults(); + jobCreator.categorizationAnalyzer = anomalyDetectors.categorization_analyzer!; + } + } + const chartInterval = new TimeBuckets(); chartInterval.setBarTarget(BAR_TARGET); chartInterval.setMaxBars(MAX_BARS); @@ -149,21 +190,39 @@ export const Page: FC = ({ existingJobsAndGroups, jobType }) => { }; }); + const jobCreatorTitle = getJobCreatorTitle(jobCreator); + return ( - - - + + + + +

+ + : {jobCreatorTitle} +

+
+
+
+ + + + +
diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/new_job/wizard_steps.tsx b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/new_job/wizard_steps.tsx index 8e81c05092c98..df21e70a68ed9 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/new_job/wizard_steps.tsx +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/pages/new_job/wizard_steps.tsx @@ -143,8 +143,8 @@ export const WizardSteps: FC = ({ currentStep, setCurrentStep }) => { const Title: FC<{ 'data-test-subj': string }> = ({ 'data-test-subj': dataTestSubj, children }) => { return ( - -

{children}

+ +

{children}

diff --git a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/utils/new_job_utils.ts b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/utils/new_job_utils.ts index 050387e6de263..0f19451b23263 100644 --- a/x-pack/legacy/plugins/ml/public/application/jobs/new_job/utils/new_job_utils.ts +++ b/x-pack/legacy/plugins/ml/public/application/jobs/new_job/utils/new_job_utils.ts @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IndexPattern } from 'ui/index_patterns'; import { esQuery, Query, esKuery } from '../../../../../../../../../src/plugins/data/public'; +import { IIndexPattern } from '../../../../../../../../../src/plugins/data/common/index_patterns'; import { KibanaConfigTypeFix } from '../../../contexts/kibana'; import { SEARCH_QUERY_LANGUAGE } from '../../../../../common/constants/search'; import { SavedSearchSavedObject } from '../../../../../common/types/kibana'; @@ -15,7 +15,7 @@ import { getQueryFromSavedSearch } from '../../../util/index_utils'; export function createSearchItems( kibanaConfig: KibanaConfigTypeFix, - indexPattern: IndexPattern, + indexPattern: IIndexPattern, savedSearch: SavedSearchSavedObject | null ) { // query is only used by the data visualizer as it needs diff --git a/x-pack/legacy/plugins/ml/public/application/management/jobs_list/components/jobs_list_page/jobs_list_page.tsx b/x-pack/legacy/plugins/ml/public/application/management/jobs_list/components/jobs_list_page/jobs_list_page.tsx index a19a27d00e9b0..1591dbcbad6bf 100644 --- a/x-pack/legacy/plugins/ml/public/application/management/jobs_list/components/jobs_list_page/jobs_list_page.tsx +++ b/x-pack/legacy/plugins/ml/public/application/management/jobs_list/components/jobs_list_page/jobs_list_page.tsx @@ -70,8 +70,8 @@ export const JobsListPage: FC = ({ isMlEnabledInSpace }) => { const [currentTabId, setCurrentTabId] = useState(tabs[0].id); // metadata.branch corresponds to the version used in documentation links. - const anomalyDetectionJobsUrl = `https://www.elastic.co/guide/en/elastic-stack-overview/${metadata.branch}/ml-jobs.html`; - const anomalyJobsUrl = `https://www.elastic.co/guide/en/elastic-stack-overview/${metadata.branch}/ml-dfanalytics.html`; + const anomalyDetectionJobsUrl = `https://www.elastic.co/guide/en/machine-learning/${metadata.branch}/ml-jobs.html`; + const anomalyJobsUrl = `https://www.elastic.co/guide/en/machine-learning/${metadata.branch}/ml-dfanalytics.html`; const anomalyDetectionDocsLabel = i18n.translate( 'xpack.ml.management.jobsList.anomalyDetectionDocsLabel', diff --git a/x-pack/legacy/plugins/ml/public/application/routing/routes/explorer.tsx b/x-pack/legacy/plugins/ml/public/application/routing/routes/explorer.tsx index 1b6b91026d6a5..6aaad5294369b 100644 --- a/x-pack/legacy/plugins/ml/public/application/routing/routes/explorer.tsx +++ b/x-pack/legacy/plugins/ml/public/application/routing/routes/explorer.tsx @@ -4,27 +4,33 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { FC, useEffect } from 'react'; +import moment from 'moment'; +import React, { FC, useEffect, useState } from 'react'; +import useObservable from 'react-use/lib/useObservable'; + import { i18n } from '@kbn/i18n'; -import { decode } from 'rison-node'; -import { Subscription } from 'rxjs'; -// @ts-ignore -import queryString from 'query-string'; import { timefilter } from 'ui/timefilter'; + +import { MlJobWithTimeRange } from '../../../../common/types/jobs'; + import { MlRoute, PageLoader, PageProps } from '../router'; +import { useRefresh } from '../use_refresh'; import { useResolver } from '../use_resolver'; import { basicResolvers } from '../resolvers'; import { Explorer } from '../../explorer'; +import { useSelectedCells } from '../../explorer/hooks/use_selected_cells'; import { mlJobService } from '../../services/job_service'; -import { getExplorerDefaultAppState, ExplorerAppState } from '../../explorer/reducers'; +import { ml } from '../../services/ml_api_service'; +import { useExplorerData } from '../../explorer/actions'; import { explorerService } from '../../explorer/explorer_dashboard_service'; -import { jobSelectServiceFactory } from '../../components/job_selector/job_select_service_utils'; -import { subscribeAppStateToObservable } from '../../util/app_state_utils'; - -import { interval$ } from '../../components/controls/select_interval'; -import { severity$ } from '../../components/controls/select_severity'; -import { showCharts$ } from '../../components/controls/checkbox_showcharts'; +import { getDateFormatTz } from '../../explorer/explorer_utils'; +import { useSwimlaneLimit } from '../../explorer/select_limit'; +import { useJobSelection } from '../../components/job_selector/use_job_selection'; +import { useShowCharts } from '../../components/controls/checkbox_showcharts'; +import { useTableInterval } from '../../components/controls/select_interval'; +import { useTableSeverity } from '../../components/controls/select_severity'; +import { useUrlState } from '../../util/url_state'; import { ANOMALY_DETECTION_BREADCRUMB, ML_BREADCRUMB } from '../breadcrumbs'; const breadcrumbs = [ @@ -44,111 +50,140 @@ export const explorerRoute: MlRoute = { breadcrumbs, }; -const PageWrapper: FC = ({ location, config, deps }) => { - const { index } = queryString.parse(location.search); - const { context } = useResolver(index, undefined, config, { +const PageWrapper: FC = ({ config, deps }) => { + const { context, results } = useResolver(undefined, undefined, config, { ...basicResolvers(deps), jobs: mlJobService.loadJobsWrapper, + jobsWithTimeRange: () => ml.jobs.jobsWithTimerange(getDateFormatTz()), }); - const { _a, _g } = queryString.parse(location.search); - let appState: any = {}; - let globalState: any = {}; - try { - appState = decode(_a); - globalState = decode(_g); - } catch (error) { - // eslint-disable-next-line no-console - console.error('Could not parse global or app state'); - } - - if (appState.mlExplorerSwimlane === undefined) { - appState.mlExplorerSwimlane = {}; - } - - if (appState.mlExplorerFilter === undefined) { - appState.mlExplorerFilter = {}; - } - - appState.fetch = () => {}; - appState.on = () => {}; - appState.off = () => {}; - appState.save = () => {}; - globalState.fetch = () => {}; - globalState.on = () => {}; - globalState.off = () => {}; - globalState.save = () => {}; return ( - + ); }; -class AppState { - fetch() {} - on() {} - off() {} - save() {} +interface ExplorerUrlStateManagerProps { + jobsWithTimeRange: MlJobWithTimeRange[]; } -const ExplorerWrapper: FC<{ globalState: any; appState: any }> = ({ globalState, appState }) => { - const subscriptions = new Subscription(); - - const { jobSelectService$, unsubscribeFromGlobalState } = jobSelectServiceFactory(globalState); - appState = getExplorerDefaultAppState(); - const { mlExplorerFilter, mlExplorerSwimlane } = appState; - window.setTimeout(() => { - // Pass the current URL AppState on to anomaly explorer's reactive state. - // After this hand-off, the appState stored in explorerState$ is the single - // source of truth. - explorerService.setAppState({ mlExplorerSwimlane, mlExplorerFilter }); - - // Now that appState in explorerState$ is the single source of truth, - // subscribe to it and update the actual URL appState on changes. - subscriptions.add( - explorerService.appState$.subscribe((appStateIn: ExplorerAppState) => { - // appState.fetch(); - appState.mlExplorerFilter = appStateIn.mlExplorerFilter; - appState.mlExplorerSwimlane = appStateIn.mlExplorerSwimlane; - // appState.save(); - }) - ); - }); +const ExplorerUrlStateManager: FC = ({ jobsWithTimeRange }) => { + const [appState, setAppState] = useUrlState('_a'); + const [globalState] = useUrlState('_g'); + const [lastRefresh, setLastRefresh] = useState(0); - subscriptions.add(subscribeAppStateToObservable(AppState, 'mlShowCharts', showCharts$, () => {})); - subscriptions.add( - subscribeAppStateToObservable(AppState, 'mlSelectInterval', interval$, () => {}) - ); - subscriptions.add( - subscribeAppStateToObservable(AppState, 'mlSelectSeverity', severity$, () => {}) - ); + const { jobIds } = useJobSelection(jobsWithTimeRange, getDateFormatTz()); - if (globalState.time) { - timefilter.setTime({ - from: globalState.time.from, - to: globalState.time.to, - }); - } + const refresh = useRefresh(); + useEffect(() => { + if (refresh !== undefined) { + setLastRefresh(refresh?.lastRefresh); + const activeBounds = timefilter.getActiveBounds(); + if (activeBounds !== undefined) { + explorerService.setBounds(activeBounds); + } + } + }, [refresh?.lastRefresh]); useEffect(() => { - return () => { - subscriptions.unsubscribe(); - unsubscribeFromGlobalState(); - }; - }); + timefilter.enableTimeRangeSelector(); + timefilter.enableAutoRefreshSelector(); + + const viewByFieldName = appState?.mlExplorerSwimlane?.viewByFieldName; + if (viewByFieldName !== undefined) { + explorerService.setViewBySwimlaneFieldName(viewByFieldName); + } + + const filterData = appState?.mlExplorerFilter; + if (filterData !== undefined) { + explorerService.setFilterData(filterData); + } + }, []); + + useEffect(() => { + if (globalState?.time !== undefined) { + timefilter.setTime({ + from: globalState.time.from, + to: globalState.time.to, + }); + explorerService.setBounds({ + min: moment(globalState.time.from), + max: moment(globalState.time.to), + }); + } + }, [globalState?.time?.from, globalState?.time?.to]); + + useEffect(() => { + if (jobIds.length > 0) { + explorerService.updateJobSelection(jobIds); + } else { + explorerService.clearJobs(); + } + }, [JSON.stringify(jobIds)]); + + const [explorerData, loadExplorerData] = useExplorerData(); + useEffect(() => { + if (explorerData !== undefined && Object.keys(explorerData).length > 0) { + explorerService.setExplorerData(explorerData); + } + }, [explorerData]); + + const explorerAppState = useObservable(explorerService.appState$); + useEffect(() => { + if ( + explorerAppState !== undefined && + explorerAppState.mlExplorerSwimlane.viewByFieldName !== undefined + ) { + setAppState(explorerAppState); + } + }, [explorerAppState]); + + const explorerState = useObservable(explorerService.state$); + + const [showCharts] = useShowCharts(); + const [tableInterval] = useTableInterval(); + const [tableSeverity] = useTableSeverity(); + const [swimlaneLimit] = useSwimlaneLimit(); + useEffect(() => { + explorerService.setSwimlaneLimit(swimlaneLimit); + }, [swimlaneLimit]); + + const [selectedCells, setSelectedCells] = useSelectedCells(); + useEffect(() => { + explorerService.setSelectedCells(selectedCells); + }, [JSON.stringify(selectedCells)]); + + const loadExplorerDataConfig = + (explorerState !== undefined && { + bounds: explorerState.bounds, + lastRefresh, + influencersFilterQuery: explorerState.influencersFilterQuery, + noInfluencersConfigured: explorerState.noInfluencersConfigured, + selectedCells, + selectedJobs: explorerState.selectedJobs, + swimlaneBucketInterval: explorerState.swimlaneBucketInterval, + swimlaneLimit: explorerState.swimlaneLimit, + tableInterval: tableInterval.val, + tableSeverity: tableSeverity.val, + viewBySwimlaneFieldName: explorerState.viewBySwimlaneFieldName, + }) || + undefined; + useEffect(() => { + loadExplorerData(loadExplorerDataConfig); + }, [JSON.stringify(loadExplorerDataConfig)]); + + if (explorerState === undefined || refresh === undefined || showCharts === undefined) { + return null; + } return (
diff --git a/x-pack/legacy/plugins/ml/public/application/routing/routes/new_job/wizard.tsx b/x-pack/legacy/plugins/ml/public/application/routing/routes/new_job/wizard.tsx index ea1baefdce0d1..99c0511cd09ce 100644 --- a/x-pack/legacy/plugins/ml/public/application/routing/routes/new_job/wizard.tsx +++ b/x-pack/legacy/plugins/ml/public/application/routing/routes/new_job/wizard.tsx @@ -72,6 +72,16 @@ const advancedBreadcrumbs = [ }, ]; +const categorizationBreadcrumbs = [ + ...baseBreadcrumbs, + { + text: i18n.translate('xpack.ml.jobsBreadcrumbs.categorizationLabel', { + defaultMessage: 'Categorization', + }), + href: '', + }, +]; + export const singleMetricRoute: MlRoute = { path: '/jobs/new_job/single_metric', render: (props, config, deps) => ( @@ -104,6 +114,14 @@ export const advancedRoute: MlRoute = { breadcrumbs: advancedBreadcrumbs, }; +export const categorizationRoute: MlRoute = { + path: '/jobs/new_job/categorization', + render: (props, config, deps) => ( + + ), + breadcrumbs: categorizationBreadcrumbs, +}; + const PageWrapper: FC = ({ location, config, jobType, deps }) => { const { index, savedSearchId } = queryString.parse(location.search); const { context, results } = useResolver(index, savedSearchId, config, { diff --git a/x-pack/legacy/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx b/x-pack/legacy/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx index a40bbfa214b28..cbf54a70ea74f 100644 --- a/x-pack/legacy/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx +++ b/x-pack/legacy/plugins/ml/public/application/routing/routes/timeseriesexplorer.tsx @@ -4,24 +4,37 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { FC, useEffect } from 'react'; -import { i18n } from '@kbn/i18n'; -import { decode } from 'rison-node'; +import { isEqual } from 'lodash'; +import React, { FC, useCallback, useEffect, useState } from 'react'; +import { usePrevious } from 'react-use'; import moment from 'moment'; -import { Subscription } from 'rxjs'; - // @ts-ignore import queryString from 'query-string'; + +import { i18n } from '@kbn/i18n'; + import { timefilter } from 'ui/timefilter'; -import { MlRoute, PageLoader, PageProps } from '../router'; -import { useResolver } from '../use_resolver'; -import { basicResolvers } from '../resolvers'; + +import { MlJobWithTimeRange } from '../../../../common/types/jobs'; + import { TimeSeriesExplorer } from '../../timeseriesexplorer'; +import { getDateFormatTz, TimeRangeBounds } from '../../explorer/explorer_utils'; +import { ml } from '../../services/ml_api_service'; import { mlJobService } from '../../services/job_service'; +import { mlForecastService } from '../../services/forecast_service'; import { APP_STATE_ACTION } from '../../timeseriesexplorer/timeseriesexplorer_constants'; -import { subscribeAppStateToObservable } from '../../util/app_state_utils'; -import { interval$ } from '../../components/controls/select_interval'; -import { severity$ } from '../../components/controls/select_severity'; +import { + createTimeSeriesJobData, + getAutoZoomDuration, +} from '../../timeseriesexplorer/timeseriesexplorer_utils'; +import { useUrlState } from '../../util/url_state'; +import { useTableInterval } from '../../components/controls/select_interval'; +import { useTableSeverity } from '../../components/controls/select_severity'; + +import { MlRoute, PageLoader, PageProps } from '../router'; +import { useRefresh } from '../use_refresh'; +import { useResolver } from '../use_resolver'; +import { basicResolvers } from '../resolvers'; import { ANOMALY_DETECTION_BREADCRUMB, ML_BREADCRUMB } from '../breadcrumbs'; export const timeSeriesExplorerRoute: MlRoute = { @@ -39,105 +52,207 @@ export const timeSeriesExplorerRoute: MlRoute = { ], }; -const PageWrapper: FC = ({ location, config, deps }) => { - const { context } = useResolver('', undefined, config, { +const PageWrapper: FC = ({ config, deps }) => { + const { context, results } = useResolver('', undefined, config, { ...basicResolvers(deps), jobs: mlJobService.loadJobsWrapper, + jobsWithTimeRange: () => ml.jobs.jobsWithTimerange(getDateFormatTz()), }); - const { _a, _g } = queryString.parse(location.search); - let appState: any = {}; - let globalState: any = {}; - try { - appState = decode(_a); - globalState = decode(_g); - } catch (error) { - // eslint-disable-next-line no-console - console.error('Could not parse global or app state'); - } - if (appState.mlTimeSeriesExplorer === undefined) { - appState.mlTimeSeriesExplorer = {}; - } - globalState.fetch = () => {}; - globalState.on = () => {}; - globalState.off = () => {}; - globalState.save = () => {}; return ( - + ); }; -class AppState { - fetch() {} - on() {} - off() {} - save() {} +interface TimeSeriesExplorerUrlStateManager { + config: any; + jobsWithTimeRange: MlJobWithTimeRange[]; } -const TimeSeriesExplorerWrapper: FC<{ globalState: any; appState: any; config: any }> = ({ - globalState, - appState, +const TimeSeriesExplorerUrlStateManager: FC = ({ config, + jobsWithTimeRange, }) => { - if (globalState.time) { - timefilter.setTime({ - from: globalState.time.from, - to: globalState.time.to, - }); - } + const [appState, setAppState] = useUrlState('_a'); + const [globalState, setGlobalState] = useUrlState('_g'); + const [lastRefresh, setLastRefresh] = useState(0); - const subscriptions = new Subscription(); - subscriptions.add( - subscribeAppStateToObservable(AppState, 'mlSelectInterval', interval$, () => {}) - ); - subscriptions.add( - subscribeAppStateToObservable(AppState, 'mlSelectSeverity', severity$, () => {}) - ); + const refresh = useRefresh(); + useEffect(() => { + if (refresh !== undefined) { + setLastRefresh(refresh?.lastRefresh); - const appStateHandler = (action: string, payload: any) => { - switch (action) { - case APP_STATE_ACTION.CLEAR: - delete appState.mlTimeSeriesExplorer.detectorIndex; - delete appState.mlTimeSeriesExplorer.entities; - delete appState.mlTimeSeriesExplorer.forecastId; - break; - - case APP_STATE_ACTION.GET_DETECTOR_INDEX: - return appState.mlTimeSeriesExplorer.detectorIndex; - case APP_STATE_ACTION.SET_DETECTOR_INDEX: - appState.mlTimeSeriesExplorer.detectorIndex = payload; - break; - - case APP_STATE_ACTION.GET_ENTITIES: - return appState.mlTimeSeriesExplorer.entities; - case APP_STATE_ACTION.SET_ENTITIES: - appState.mlTimeSeriesExplorer.entities = payload; - break; - - case APP_STATE_ACTION.GET_FORECAST_ID: - return appState.mlTimeSeriesExplorer.forecastId; - case APP_STATE_ACTION.SET_FORECAST_ID: - appState.mlTimeSeriesExplorer.forecastId = payload; - break; - - case APP_STATE_ACTION.GET_ZOOM: - return appState.mlTimeSeriesExplorer.zoom; - case APP_STATE_ACTION.SET_ZOOM: - appState.mlTimeSeriesExplorer.zoom = payload; - break; - case APP_STATE_ACTION.UNSET_ZOOM: - delete appState.mlTimeSeriesExplorer.zoom; - break; + if (refresh.timeRange !== undefined) { + const { start, end } = refresh.timeRange; + setGlobalState('time', { + from: start, + to: end, + }); + } } - }; + }, [refresh?.lastRefresh]); useEffect(() => { - return () => { - subscriptions.unsubscribe(); + timefilter.enableTimeRangeSelector(); + timefilter.enableAutoRefreshSelector(); + }, []); + + useEffect(() => { + if (globalState?.time !== undefined) { + timefilter.setTime({ + from: globalState.time.from, + to: globalState.time.to, + }); + } + }, [globalState?.time?.from, globalState?.time?.to]); + + let bounds: TimeRangeBounds | undefined; + if (globalState?.time !== undefined) { + bounds = { + min: moment(globalState.time.from), + max: moment(globalState.time.to), }; - }); + } + + const selectedJobIds = globalState?.ml?.jobIds; + // Sort selectedJobIds so we can be sure comparison works when stringifying. + if (Array.isArray(selectedJobIds)) { + selectedJobIds.sort(); + } + + // When changing jobs we'll clear appState (detectorIndex, entities, forecastId). + // To retore settings from the URL on initial load we also need to check against + // `previousSelectedJobIds` to avoid wiping appState. + const previousSelectedJobIds = usePrevious(selectedJobIds); + const isJobChange = !isEqual(previousSelectedJobIds, selectedJobIds); + + // Use a side effect to clear appState when changing jobs. + useEffect(() => { + if (selectedJobIds !== undefined && previousSelectedJobIds !== undefined) { + setLastRefresh(Date.now()); + appStateHandler(APP_STATE_ACTION.CLEAR); + } + }, [JSON.stringify(selectedJobIds)]); + + // Next we get globalState and appState information to pass it on as props later. + // If a job change is going on, we fall back to defaults (as if appState was already cleard), + // otherwise the page could break. + const selectedDetectorIndex = isJobChange + ? 0 + : +appState?.mlTimeSeriesExplorer?.detectorIndex || 0; + const selectedEntities = isJobChange ? undefined : appState?.mlTimeSeriesExplorer?.entities; + const selectedForecastId = isJobChange ? undefined : appState?.mlTimeSeriesExplorer?.forecastId; + const zoom = isJobChange ? undefined : appState?.mlTimeSeriesExplorer?.zoom; + + const selectedJob = selectedJobIds && mlJobService.getJob(selectedJobIds[0]); + + let autoZoomDuration: number | undefined; + if (selectedJobIds !== undefined && selectedJobIds.length === 1 && selectedJob !== undefined) { + autoZoomDuration = getAutoZoomDuration( + createTimeSeriesJobData(mlJobService.jobs), + mlJobService.getJob(selectedJobIds[0]) + ); + } + + const appStateHandler = useCallback( + (action: string, payload?: any) => { + const mlTimeSeriesExplorer = + appState?.mlTimeSeriesExplorer !== undefined ? { ...appState.mlTimeSeriesExplorer } : {}; + + switch (action) { + case APP_STATE_ACTION.CLEAR: + delete mlTimeSeriesExplorer.detectorIndex; + delete mlTimeSeriesExplorer.entities; + delete mlTimeSeriesExplorer.forecastId; + delete mlTimeSeriesExplorer.zoom; + break; + + case APP_STATE_ACTION.SET_DETECTOR_INDEX: + mlTimeSeriesExplorer.detectorIndex = payload; + break; + + case APP_STATE_ACTION.SET_ENTITIES: + mlTimeSeriesExplorer.entities = payload; + break; + + case APP_STATE_ACTION.SET_FORECAST_ID: + mlTimeSeriesExplorer.forecastId = payload; + break; + + case APP_STATE_ACTION.SET_ZOOM: + mlTimeSeriesExplorer.zoom = payload; + break; + + case APP_STATE_ACTION.UNSET_ZOOM: + delete mlTimeSeriesExplorer.zoom; + break; + } + + setAppState('mlTimeSeriesExplorer', mlTimeSeriesExplorer); + }, + [JSON.stringify([appState, globalState])] + ); + + const boundsMinMs = bounds?.min?.valueOf(); + const boundsMaxMs = bounds?.max?.valueOf(); + useEffect(() => { + if ( + autoZoomDuration !== undefined && + boundsMinMs !== undefined && + boundsMaxMs !== undefined && + selectedJob !== undefined && + selectedForecastId !== undefined + ) { + mlForecastService + .getForecastDateRange(selectedJob, selectedForecastId) + .then(resp => { + if (autoZoomDuration === undefined) { + return; + } + + const earliest = moment(resp.earliest || boundsMinMs); + const latest = moment(resp.latest || boundsMaxMs); + + // Set the zoom to centre on the start of the forecast range, depending + // on the time range of the forecast and data. + // const earliestDataDate = first(contextChartData).date; + const zoomLatestMs = Math.min( + earliest.valueOf() + autoZoomDuration / 2, + latest.valueOf() + ); + const zoomEarliestMs = zoomLatestMs - autoZoomDuration; + const zoomState = { + from: moment(zoomEarliestMs).toISOString(), + to: moment(zoomLatestMs).toISOString(), + }; + appStateHandler(APP_STATE_ACTION.SET_ZOOM, zoomState); + + if (earliest.isBefore(moment(boundsMinMs)) || latest.isAfter(moment(boundsMaxMs))) { + const earliestMs = Math.min(earliest.valueOf(), boundsMinMs); + const latestMs = Math.max(latest.valueOf(), boundsMaxMs); + setGlobalState('time', { + from: moment(earliestMs).toISOString(), + to: moment(latestMs).toISOString(), + }); + } + }) + .catch(resp => { + // eslint-disable-next-line no-console + console.error( + 'Time series explorer - error loading time range of forecast from elasticsearch:', + resp + ); + }); + } + }, [selectedForecastId]); + + const [tableInterval] = useTableInterval(); + const [tableSeverity] = useTableSeverity(); const tzConfig = config.get('dateFormat:tz'); const dateFormatTz = tzConfig !== 'Browser' ? tzConfig : moment.tz.guess(); @@ -146,9 +261,20 @@ const TimeSeriesExplorerWrapper: FC<{ globalState: any; appState: any; config: a ); diff --git a/x-pack/legacy/plugins/ml/public/application/routing/use_refresh.ts b/x-pack/legacy/plugins/ml/public/application/routing/use_refresh.ts new file mode 100644 index 0000000000000..f9f3bb66f14f3 --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/application/routing/use_refresh.ts @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { useObservable } from 'react-use'; +import { merge, Observable } from 'rxjs'; +import { map } from 'rxjs/operators'; + +import { annotationsRefresh$ } from '../services/annotations_service'; +import { + mlTimefilterRefresh$, + mlTimefilterTimeChange$, +} from '../services/timefilter_refresh_service'; + +export interface Refresh { + lastRefresh: number; + timeRange?: { start: string; end: string }; +} + +const refresh$: Observable = merge( + mlTimefilterRefresh$, + mlTimefilterTimeChange$, + annotationsRefresh$.pipe(map(d => ({ lastRefresh: d }))) +); + +export const useRefresh = () => { + return useObservable(refresh$); +}; diff --git a/x-pack/legacy/plugins/ml/public/application/routing/use_resolver.ts b/x-pack/legacy/plugins/ml/public/application/routing/use_resolver.ts index f74260c06567e..3716b9715bb5b 100644 --- a/x-pack/legacy/plugins/ml/public/application/routing/use_resolver.ts +++ b/x-pack/legacy/plugins/ml/public/application/routing/use_resolver.ts @@ -60,8 +60,6 @@ export const useResolver = ( } } catch (error) { // quietly fail. Let the resolvers handle the redirection if any fail to resolve - // eslint-disable-next-line no-console - console.error('ML page loading resolver', error); } })(); }, []); diff --git a/x-pack/legacy/plugins/ml/public/application/services/annotations_service.test.tsx b/x-pack/legacy/plugins/ml/public/application/services/annotations_service.test.tsx index d74c3802c2ed2..2ba54d243ed1b 100644 --- a/x-pack/legacy/plugins/ml/public/application/services/annotations_service.test.tsx +++ b/x-pack/legacy/plugins/ml/public/application/services/annotations_service.test.tsx @@ -7,7 +7,7 @@ import mockAnnotations from '../components/annotations/annotations_table/__mocks__/mock_annotations.json'; import { Annotation } from '../../../common/types/annotations'; -import { annotation$, annotationsRefresh$ } from './annotations_service'; +import { annotation$, annotationsRefresh$, annotationsRefreshed } from './annotations_service'; describe('annotations_service', () => { test('annotation$', () => { @@ -34,7 +34,7 @@ describe('annotations_service', () => { expect(subscriber.mock.calls).toHaveLength(1); - annotationsRefresh$.next(true); + annotationsRefreshed(); expect(subscriber.mock.calls).toHaveLength(2); }); diff --git a/x-pack/legacy/plugins/ml/public/application/services/annotations_service.tsx b/x-pack/legacy/plugins/ml/public/application/services/annotations_service.tsx index 6953232f0cc6c..6493770156cb8 100644 --- a/x-pack/legacy/plugins/ml/public/application/services/annotations_service.tsx +++ b/x-pack/legacy/plugins/ml/public/application/services/annotations_service.tsx @@ -48,8 +48,8 @@ export type AnnotationState = Annotation | null; - To add it to a given components state, just use `annotation$.subscribe(annotation => this.setState({ annotation }));` in `componentDidMount()`. - 2. injectObservablesAsProps() from public/utils/observable_utils.tsx, as the name implies, offers - a way to wrap observables into another component which passes on updated values as props. + 2. useObservable() from 'react-use', offers a way to wrap observables + into another component which passes on updated values as props. - To subscribe to updates this way, wrap your component like: @@ -62,10 +62,13 @@ export type AnnotationState = Annotation | null; return {annotation.annotation}; } - export const MyObservableComponent = injectObservablesAsProps( - { annotation: annotaton$ }, - MyOriginalComponent - ); + export const MyObservableComponent = (props) => { + const annotationProp = useObservable(annotation$); + if (annotationProp === undefined) { + return null; + } + return ; + }; */ export const annotation$ = new BehaviorSubject(null); @@ -74,4 +77,5 @@ export const annotation$ = new BehaviorSubject(null); Instead of passing around callbacks or deeply nested props, it can be imported for both angularjs controllers/directives and React components. */ -export const annotationsRefresh$ = new BehaviorSubject(false); +export const annotationsRefresh$ = new BehaviorSubject(Date.now()); +export const annotationsRefreshed = () => annotationsRefresh$.next(Date.now()); diff --git a/x-pack/legacy/plugins/ml/public/application/services/field_format_service.ts b/x-pack/legacy/plugins/ml/public/application/services/field_format_service.ts index a9ecf56c58ea7..11f0d59e04545 100644 --- a/x-pack/legacy/plugins/ml/public/application/services/field_format_service.ts +++ b/x-pack/legacy/plugins/ml/public/application/services/field_format_service.ts @@ -4,10 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IndexPattern } from 'ui/index_patterns'; import { mlFunctionToESAggregation } from '../../../common/util/job_utils'; import { getIndexPatternById, getIndexPatternIdFromName } from '../util/index_utils'; import { mlJobService } from './job_service'; +import { IndexPattern } from '../../../../../../../src/plugins/data/public'; type FormatsByJobId = Record; type IndexPatternIdsByJob = Record; diff --git a/x-pack/legacy/plugins/ml/public/application/services/forecast_service.d.ts b/x-pack/legacy/plugins/ml/public/application/services/forecast_service.d.ts index 19f77d97a5708..8de903a422f34 100644 --- a/x-pack/legacy/plugins/ml/public/application/services/forecast_service.d.ts +++ b/x-pack/legacy/plugins/ml/public/application/services/forecast_service.d.ts @@ -12,6 +12,11 @@ export interface ForecastData { results: any; } +export interface ForecastDateRange { + earliest: number; + latest: number; +} + export const mlForecastService: { getForecastData: ( job: Job, @@ -23,4 +28,6 @@ export const mlForecastService: { interval: string, aggType: any ) => Observable; + + getForecastDateRange: (job: Job, forecastId: string) => Promise; }; diff --git a/x-pack/legacy/plugins/ml/public/application/services/ml_api_service/data_frame_analytics.js b/x-pack/legacy/plugins/ml/public/application/services/ml_api_service/data_frame_analytics.js index 2645e0e3f9c42..6ff0b45454abf 100644 --- a/x-pack/legacy/plugins/ml/public/application/services/ml_api_service/data_frame_analytics.js +++ b/x-pack/legacy/plugins/ml/public/application/services/ml_api_service/data_frame_analytics.js @@ -45,7 +45,7 @@ export const dataFrameAnalytics = { data: evaluateConfig, }); }, - estimateDataFrameAnalyticsMemoryUsage(jobConfig) { + explainDataFrameAnalytics(jobConfig) { return http({ url: `${basePath}/data_frame/analytics/_explain`, method: 'POST', diff --git a/x-pack/legacy/plugins/ml/public/application/services/ml_api_service/index.d.ts b/x-pack/legacy/plugins/ml/public/application/services/ml_api_service/index.d.ts index ad600ad2cbd71..bca32e9528f64 100644 --- a/x-pack/legacy/plugins/ml/public/application/services/ml_api_service/index.d.ts +++ b/x-pack/legacy/plugins/ml/public/application/services/ml_api_service/index.d.ts @@ -6,19 +6,22 @@ import { Observable } from 'rxjs'; import { Annotation } from '../../../../common/types/annotations'; +import { Dictionary } from '../../../../common/types/common'; import { AggFieldNamePair } from '../../../../common/types/fields'; +import { Category } from '../../../../common/types/categories'; import { ExistingJobsAndGroups } from '../job_service'; import { PrivilegesResponse } from '../../../../common/types/privileges'; -import { MlSummaryJobs } from '../../../../common/types/jobs'; +import { MlJobWithTimeRange, MlSummaryJobs } from '../../../../common/types/jobs'; import { MlServerDefaults, MlServerLimits } from '../ml_server_info'; import { ES_AGGREGATION } from '../../../../common/constants/aggregation_types'; import { DataFrameAnalyticsStats } from '../../data_frame_analytics/pages/analytics_management/components/analytics_list/common'; import { JobMessage } from '../../../../common/types/audit_message'; import { DataFrameAnalyticsConfig } from '../../data_frame_analytics/common/analytics'; import { DeepPartial } from '../../../../common/types/common'; +import { PartitionFieldsDefinition } from '../results_service/result_service_rx'; import { annotations } from './annotations'; import { Calendar, CalendarId, UpdateCalendar } from '../../../../common/types/calendars'; -import { CombinedJob } from '../../jobs/new_job/common/job_creator/configs'; +import { CombinedJob, JobId } from '../../jobs/new_job/common/job_creator/configs'; // TODO This is not a complete representation of all methods of `ml.*`. // It just satisfies needs for other parts of the code area which use @@ -89,9 +92,7 @@ declare interface Ml { getDataFrameAnalyticsStats(analyticsId?: string): Promise; createDataFrameAnalytics(analyticsId: string, analyticsConfig: any): Promise; evaluateDataFrameAnalytics(evaluateConfig: any): Promise; - estimateDataFrameAnalyticsMemoryUsage( - jobConfig: DeepPartial - ): Promise; + explainDataFrameAnalytics(jobConfig: DeepPartial): Promise; deleteDataFrameAnalytics(analyticsId: string): Promise; startDataFrameAnalytics(analyticsId: string): Promise; stopDataFrameAnalytics( @@ -108,7 +109,7 @@ declare interface Ml { checkManageMLPrivileges(): Promise; getJobStats(obj: object): Promise; getDatafeedStats(obj: object): Promise; - esSearch(obj: object): any; + esSearch(obj: object): Promise; esSearch$(obj: object): Observable; getIndices(): Promise; dataRecognizerModuleJobsExist(obj: { moduleId: string }): Promise; @@ -124,10 +125,20 @@ declare interface Ml { results: { getMaxAnomalyScore: (jobIds: string[], earliestMs: number, latestMs: number) => Promise; + fetchPartitionFieldsValues: ( + jobId: JobId, + searchTerm: Record, + criteriaFields: Array<{ fieldName: string; fieldValue: any }>, + earliestMs: number, + latestMs: number + ) => Observable; }; jobs: { jobsSummary(jobIds: string[]): Promise; + jobsWithTimerange( + dateFormatTz: string + ): Promise<{ jobs: MlJobWithTimeRange[]; jobsMap: Dictionary }>; jobs(jobIds: string[]): Promise; groups(): Promise; updateGroups(updatedJobs: string[]): Promise; @@ -165,6 +176,20 @@ declare interface Ml { start: number, end: number ): Promise<{ progress: number; isRunning: boolean; isJobClosed: boolean }>; + categorizationFieldExamples( + indexPatternTitle: string, + query: object, + size: number, + field: string, + timeField: string | undefined, + start: number, + end: number, + analyzer: any + ): Promise<{ valid: number; examples: any[] }>; + topCategories( + jobId: string, + count: number + ): Promise<{ total: number; categories: Array<{ count?: number; category: Category }> }>; }; estimateBucketSpan(data: BucketSpanEstimatorData): Promise; diff --git a/x-pack/legacy/plugins/ml/public/application/services/ml_api_service/jobs.js b/x-pack/legacy/plugins/ml/public/application/services/ml_api_service/jobs.js index 4bec070b2cfdf..05d98dc1a1e64 100644 --- a/x-pack/legacy/plugins/ml/public/application/services/ml_api_service/jobs.js +++ b/x-pack/legacy/plugins/ml/public/application/services/ml_api_service/jobs.js @@ -206,4 +206,41 @@ export const jobs = { }, }); }, + + categorizationFieldExamples( + indexPatternTitle, + query, + size, + field, + timeField, + start, + end, + analyzer + ) { + return http({ + url: `${basePath}/jobs/categorization_field_examples`, + method: 'POST', + data: { + indexPatternTitle, + query, + size, + field, + timeField, + start, + end, + analyzer, + }, + }); + }, + + topCategories(jobId, count) { + return http({ + url: `${basePath}/jobs/top_categories`, + method: 'POST', + data: { + jobId, + count, + }, + }); + }, }; diff --git a/x-pack/legacy/plugins/ml/public/application/services/ml_api_service/results.js b/x-pack/legacy/plugins/ml/public/application/services/ml_api_service/results.js index ed01fa268500f..38ae777106680 100644 --- a/x-pack/legacy/plugins/ml/public/application/services/ml_api_service/results.js +++ b/x-pack/legacy/plugins/ml/public/application/services/ml_api_service/results.js @@ -75,4 +75,17 @@ export const results = { }, }); }, + + fetchPartitionFieldsValues(jobId, searchTerm, criteriaFields, earliestMs, latestMs) { + return http$(`${basePath}/results/partition_fields_values`, { + method: 'POST', + body: { + jobId, + searchTerm, + criteriaFields, + earliestMs, + latestMs, + }, + }); + }, }; diff --git a/x-pack/legacy/plugins/ml/public/application/services/ml_server_info.ts b/x-pack/legacy/plugins/ml/public/application/services/ml_server_info.ts index 95d670eda8a4f..6bf5a7b0c9743 100644 --- a/x-pack/legacy/plugins/ml/public/application/services/ml_server_info.ts +++ b/x-pack/legacy/plugins/ml/public/application/services/ml_server_info.ts @@ -11,10 +11,18 @@ export interface MlServerDefaults { categorization_examples_limit?: number; model_memory_limit?: string; model_snapshot_retention_days?: number; + categorization_analyzer?: CategorizationAnalyzer; }; datafeeds: { scroll_size?: number }; } +export interface CategorizationAnalyzer { + char_filter?: any[]; + tokenizer?: string; + filter?: any[]; + analyzer?: string; +} + export interface MlServerLimits { max_model_memory_limit?: string; } diff --git a/x-pack/legacy/plugins/ml/public/application/services/new_job_capabilities._service.test.ts b/x-pack/legacy/plugins/ml/public/application/services/new_job_capabilities._service.test.ts index 91c945ea8ff29..5aeed3587b5a0 100644 --- a/x-pack/legacy/plugins/ml/public/application/services/new_job_capabilities._service.test.ts +++ b/x-pack/legacy/plugins/ml/public/application/services/new_job_capabilities._service.test.ts @@ -5,7 +5,7 @@ */ import { newJobCapsService } from './new_job_capabilities_service'; -import { IndexPattern } from 'ui/index_patterns'; +import { IndexPattern } from '../../../../../../../src/plugins/data/public'; // there is magic happening here. starting the include name with `mock..` // ensures it can be lazily loaded by the jest.mock function below. diff --git a/x-pack/legacy/plugins/ml/public/application/services/new_job_capabilities_service.ts b/x-pack/legacy/plugins/ml/public/application/services/new_job_capabilities_service.ts index 9d5c33d6cfc5c..051973d35d8de 100644 --- a/x-pack/legacy/plugins/ml/public/application/services/new_job_capabilities_service.ts +++ b/x-pack/legacy/plugins/ml/public/application/services/new_job_capabilities_service.ts @@ -14,7 +14,7 @@ import { } from '../../../common/types/fields'; import { ES_FIELD_TYPES, - IndexPattern, + IIndexPattern, IndexPatternsContract, } from '../../../../../../../src/plugins/data/public'; import { ml } from './ml_api_service'; @@ -30,7 +30,7 @@ export function loadNewJobCapabilities( return new Promise(async (resolve, reject) => { if (indexPatternId !== undefined) { // index pattern is being used - const indexPattern: IndexPattern = await indexPatterns.get(indexPatternId); + const indexPattern: IIndexPattern = await indexPatterns.get(indexPatternId); await newJobCapsService.initializeFromIndexPattern(indexPattern); resolve(newJobCapsService.newJobCaps); } else if (savedSearchId !== undefined) { @@ -89,7 +89,7 @@ class NewJobCapsService { } public async initializeFromIndexPattern( - indexPattern: IndexPattern, + indexPattern: IIndexPattern, includeEventRateField = true, removeTextFields = true ) { diff --git a/x-pack/legacy/plugins/ml/public/application/services/results_service/index.ts b/x-pack/legacy/plugins/ml/public/application/services/results_service/index.ts index 9ab14aa7495a7..9d21cbc76ba3a 100644 --- a/x-pack/legacy/plugins/ml/public/application/services/results_service/index.ts +++ b/x-pack/legacy/plugins/ml/public/application/services/results_service/index.ts @@ -9,6 +9,7 @@ import { getModelPlotOutput, getRecordsForCriteria, getScheduledEventsByBucket, + fetchPartitionFieldsValues, } from './result_service_rx'; import { getEventDistributionData, @@ -42,6 +43,7 @@ export const mlResultsService = { getEventDistributionData, getModelPlotOutput, getRecordMaxScoreByTime, + fetchPartitionFieldsValues, }; type time = string; diff --git a/x-pack/legacy/plugins/ml/public/application/services/results_service/result_service_rx.ts b/x-pack/legacy/plugins/ml/public/application/services/results_service/result_service_rx.ts index 2341ae15a3378..299dfe0167694 100644 --- a/x-pack/legacy/plugins/ml/public/application/services/results_service/result_service_rx.ts +++ b/x-pack/legacy/plugins/ml/public/application/services/results_service/result_service_rx.ts @@ -14,7 +14,9 @@ import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import _ from 'lodash'; +import { Dictionary } from '../../../../common/types/common'; import { ML_MEDIAN_PERCENTS } from '../../../../common/util/job_utils'; +import { JobId } from '../../jobs/new_job/common/job_creator/configs'; import { ml } from '../ml_api_service'; import { ML_RESULTS_INDEX_PATTERN } from '../../../../common/constants/index_patterns'; import { CriteriaField } from './index'; @@ -27,6 +29,23 @@ export interface MetricData extends ResultResponse { results: Record; } +export interface FieldDefinition { + /** + * Partition field name. + */ + name: string | number; + /** + * Partitions field distinct values. + */ + values: any[]; +} + +type FieldTypes = 'partition_field' | 'over_field' | 'by_field'; + +export type PartitionFieldsDefinition = { + [field in FieldTypes]: FieldDefinition; +}; + export function getMetricData( index: string, entityFields: any[], @@ -532,3 +551,19 @@ export function getScheduledEventsByBucket( }) ); } + +export function fetchPartitionFieldsValues( + jobId: JobId, + searchTerm: Dictionary, + criteriaFields: Array<{ fieldName: string; fieldValue: any }>, + earliestMs: number, + latestMs: number +) { + return ml.results.fetchPartitionFieldsValues( + jobId, + searchTerm, + criteriaFields, + earliestMs, + latestMs + ); +} diff --git a/x-pack/legacy/plugins/ml/public/application/services/timefilter_refresh_service.tsx b/x-pack/legacy/plugins/ml/public/application/services/timefilter_refresh_service.tsx index 2085c2a5dc77f..86c07a3577f7b 100644 --- a/x-pack/legacy/plugins/ml/public/application/services/timefilter_refresh_service.tsx +++ b/x-pack/legacy/plugins/ml/public/application/services/timefilter_refresh_service.tsx @@ -6,4 +6,7 @@ import { Subject } from 'rxjs'; -export const mlTimefilterRefresh$ = new Subject(); +import { Refresh } from '../routing/use_refresh'; + +export const mlTimefilterRefresh$ = new Subject>(); +export const mlTimefilterTimeChange$ = new Subject>(); diff --git a/x-pack/legacy/plugins/ml/public/application/settings/calendars/list/__snapshots__/header.test.js.snap b/x-pack/legacy/plugins/ml/public/application/settings/calendars/list/__snapshots__/header.test.js.snap index 2d32650a4b788..0b39841ed61e4 100644 --- a/x-pack/legacy/plugins/ml/public/application/settings/calendars/list/__snapshots__/header.test.js.snap +++ b/x-pack/legacy/plugins/ml/public/application/settings/calendars/list/__snapshots__/header.test.js.snap @@ -89,7 +89,7 @@ exports[`CalendarListsHeader renders header 1`] = ` Object { "br":
, "learnMoreLink": , "learnMoreLink": { - let $scope; - let $compile; - let $element; - - beforeEach(ngMock.module('kibana')); - beforeEach(() => { - ngMock.inject(function($injector) { - $compile = $injector.get('$compile'); - const $rootScope = $injector.get('$rootScope'); - $scope = $rootScope.$new(); - }); - }); - - afterEach(() => { - $scope.$destroy(); - }); - - it('Initialize Time Series Explorer Directive', done => { - ngMock.inject(function() { - expect(() => { - $element = $compile('')($scope); - }).to.not.throwError(); - - // directive has scope: false - const scope = $element.isolateScope(); - expect(scope).to.eql(undefined); - done(); - }); - }); -}); diff --git a/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/components/entity_control/entity_control.js b/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/components/entity_control/entity_control.js deleted file mode 100644 index 97d6e41792787..0000000000000 --- a/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/components/entity_control/entity_control.js +++ /dev/null @@ -1,120 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import PropTypes from 'prop-types'; -import React from 'react'; -import { FormattedMessage, injectI18n } from '@kbn/i18n/react'; - -import { EuiComboBox, EuiFlexItem, EuiFormRow, EuiToolTip } from '@elastic/eui'; - -function getEntityControlOptions(entity) { - if (!Array.isArray(entity.fieldValues)) { - return []; - } - - return entity.fieldValues.map(value => { - return { label: value }; - }); -} - -export const EntityControl = injectI18n( - class EntityControl extends React.Component { - static propTypes = { - entity: PropTypes.object.isRequired, - entityFieldValueChanged: PropTypes.func.isRequired, - forceSelection: PropTypes.bool.isRequired, - }; - - state = { - selectedOptions: undefined, - }; - - constructor(props) { - super(props); - } - - componentDidUpdate() { - const { entity, forceSelection } = this.props; - const { selectedOptions } = this.state; - - const fieldValue = entity.fieldValue; - - if ( - (selectedOptions === undefined && fieldValue.length > 0) || - (Array.isArray(selectedOptions) && - fieldValue.length > 0 && - selectedOptions[0].label !== fieldValue) - ) { - this.setState({ - selectedOptions: [{ label: fieldValue }], - }); - } else if (Array.isArray(selectedOptions) && fieldValue.length === 0) { - this.setState({ - selectedOptions: undefined, - }); - } - - if (forceSelection && this.inputRef) { - this.inputRef.focus(); - } - } - - onChange = selectedOptions => { - const options = selectedOptions.length > 0 ? selectedOptions : undefined; - this.setState({ - selectedOptions: options, - }); - - const fieldValue = - Array.isArray(options) && options[0].label.length > 0 ? options[0].label : ''; - this.props.entityFieldValueChanged(this.props.entity, fieldValue); - }; - - render() { - const { entity, intl, forceSelection } = this.props; - const { selectedOptions } = this.state; - const options = getEntityControlOptions(entity); - - const control = ( - { - if (input) { - this.inputRef = input; - } - }} - style={{ minWidth: '300px' }} - placeholder={intl.formatMessage({ - id: 'xpack.ml.timeSeriesExplorer.enterValuePlaceholder', - defaultMessage: 'Enter value', - })} - singleSelection={{ asPlainText: true }} - options={options} - selectedOptions={selectedOptions} - onChange={this.onChange} - isClearable={false} - /> - ); - - const selectMessage = ( - - ); - - return ( - - - - {control} - - - - ); - } - } -); diff --git a/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/components/entity_control/entity_control.tsx b/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/components/entity_control/entity_control.tsx new file mode 100644 index 0000000000000..df5412e609a9c --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/components/entity_control/entity_control.tsx @@ -0,0 +1,144 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { Component } from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; + +import { + EuiComboBox, + EuiComboBoxOptionProps, + EuiFlexItem, + EuiFormRow, + EuiToolTip, +} from '@elastic/eui'; + +export interface Entity { + fieldName: string; + fieldValue: any; + fieldValues: any; +} + +interface EntityControlProps { + entity: Entity; + entityFieldValueChanged: (entity: Entity, fieldValue: any) => void; + isLoading: boolean; + onSearchChange: (entity: Entity, queryTerm: string) => void; + forceSelection: boolean; + options: EuiComboBoxOptionProps[]; +} + +interface EntityControlState { + selectedOptions: EuiComboBoxOptionProps[] | undefined; + isLoading: boolean; + options: EuiComboBoxOptionProps[] | undefined; +} + +export class EntityControl extends Component { + inputRef: any; + + state = { + selectedOptions: undefined, + options: undefined, + isLoading: false, + }; + + componentDidUpdate(prevProps: EntityControlProps) { + const { entity, forceSelection, isLoading, options } = this.props; + const { selectedOptions } = this.state; + + const { fieldValue } = entity; + + let selectedOptionsUpdate: EuiComboBoxOptionProps[] | undefined = selectedOptions; + if ( + (selectedOptions === undefined && fieldValue.length > 0) || + (Array.isArray(selectedOptions) && + // @ts-ignore + selectedOptions[0].label !== fieldValue && + fieldValue.length > 0) + ) { + selectedOptionsUpdate = [{ label: fieldValue }]; + } else if (Array.isArray(selectedOptions) && fieldValue.length === 0) { + selectedOptionsUpdate = undefined; + } + + if (prevProps.isLoading === true && isLoading === false) { + this.setState({ + isLoading: false, + options, + selectedOptions: selectedOptionsUpdate, + }); + } + + if (forceSelection && this.inputRef) { + this.inputRef.focus(); + } + } + + onChange = (selectedOptions: EuiComboBoxOptionProps[]) => { + const options = selectedOptions.length > 0 ? selectedOptions : undefined; + this.setState({ + selectedOptions: options, + }); + + const fieldValue = + Array.isArray(options) && options[0].label.length > 0 ? options[0].label : ''; + this.props.entityFieldValueChanged(this.props.entity, fieldValue); + }; + + onSearchChange = (searchValue: string) => { + this.setState({ + isLoading: true, + options: [], + }); + this.props.onSearchChange(this.props.entity, searchValue); + }; + + render() { + const { entity, forceSelection } = this.props; + const { isLoading, options, selectedOptions } = this.state; + + const control = ( + { + if (input) { + this.inputRef = input; + } + }} + style={{ minWidth: '300px' }} + placeholder={i18n.translate('xpack.ml.timeSeriesExplorer.enterValuePlaceholder', { + defaultMessage: 'Enter value', + })} + singleSelection={{ asPlainText: true }} + options={options} + selectedOptions={selectedOptions} + onChange={this.onChange} + onSearchChange={this.onSearchChange} + isClearable={false} + /> + ); + + const selectMessage = ( + + ); + + return ( + + + + {control} + + + + ); + } +} diff --git a/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/components/entity_control/index.js b/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/components/entity_control/index.ts similarity index 100% rename from x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/components/entity_control/index.js rename to x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/components/entity_control/index.ts diff --git a/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/components/forecasting_modal/forecasting_modal.js b/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/components/forecasting_modal/forecasting_modal.js index 9a9b7fa3f02e2..9aafab12a7156 100644 --- a/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/components/forecasting_modal/forecasting_modal.js +++ b/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/components/forecasting_modal/forecasting_modal.js @@ -61,7 +61,7 @@ export const ForecastingModal = injectI18n( job: PropTypes.object, detectorIndex: PropTypes.number, entities: PropTypes.array, - loadForForecastId: PropTypes.func, + setForecastId: PropTypes.func, }; constructor(props) { @@ -81,7 +81,7 @@ export const ForecastingModal = injectI18n( }; viewForecast = forecastId => { - this.props.loadForForecastId(forecastId); + this.props.setForecastId(forecastId); this.closeModal(); }; @@ -279,7 +279,7 @@ export const ForecastingModal = injectI18n( this.setState({ jobClosingState: PROGRESS_STATES.DONE, }); - this.props.loadForForecastId(forecastId); + this.props.setForecastId(forecastId); this.closeAfterRunningForecast(); }) .catch(response => { @@ -297,10 +297,10 @@ export const ForecastingModal = injectI18n( this.setState({ jobClosingState: PROGRESS_STATES.ERROR, }); - this.props.loadForForecastId(forecastId); + this.props.setForecastId(forecastId); }); } else { - this.props.loadForForecastId(forecastId); + this.props.setForecastId(forecastId); this.closeAfterRunningForecast(); } } else { @@ -327,7 +327,7 @@ export const ForecastingModal = injectI18n( ); // Try and load any results which may have been created. - this.props.loadForForecastId(forecastId); + this.props.setForecastId(forecastId); this.setState({ forecastProgress: PROGRESS_STATES.ERROR }); clearInterval(this.forecastChecker); } diff --git a/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.js b/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.js index 2e0fc44a158ea..d8e9e4379395a 100644 --- a/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.js +++ b/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.js @@ -11,7 +11,7 @@ import PropTypes from 'prop-types'; import React from 'react'; - +import useObservable from 'react-use/lib/useObservable'; import _ from 'lodash'; import d3 from 'd3'; import moment from 'moment'; @@ -23,7 +23,6 @@ import { getMultiBucketImpactLabel, } from '../../../../../common/util/anomaly_utils'; import { annotation$ } from '../../../services/annotations_service'; -import { injectObservablesAsProps } from '../../../util/observable_utils'; import { formatValue } from '../../../formatters/format_value'; import { LINE_CHART_ANOMALY_RADIUS, @@ -97,16 +96,16 @@ const TimeseriesChartIntl = injectI18n( static propTypes = { annotation: PropTypes.object, autoZoomDuration: PropTypes.number, + bounds: PropTypes.object, contextAggregationInterval: PropTypes.object, contextChartData: PropTypes.array, contextForecastData: PropTypes.array, contextChartSelected: PropTypes.func.isRequired, - detectorIndex: PropTypes.string, + detectorIndex: PropTypes.number, focusAggregationInterval: PropTypes.object, focusAnnotationData: PropTypes.array, focusChartData: PropTypes.array, focusForecastData: PropTypes.array, - skipRefresh: PropTypes.bool.isRequired, modelPlotEnabled: PropTypes.bool.isRequired, renderFocusChartOnly: PropTypes.bool.isRequired, selectedJob: PropTypes.object, @@ -114,7 +113,6 @@ const TimeseriesChartIntl = injectI18n( showModelBounds: PropTypes.bool.isRequired, svgWidth: PropTypes.number.isRequired, swimlaneData: PropTypes.array, - timefilter: PropTypes.object.isRequired, zoomFrom: PropTypes.object, zoomTo: PropTypes.object, zoomFromFocusLoaded: PropTypes.object, @@ -234,10 +232,6 @@ const TimeseriesChartIntl = injectI18n( } componentDidUpdate() { - if (this.props.skipRefresh) { - return; - } - if (this.props.renderFocusChartOnly === false) { this.renderChart(); this.drawContextChartSelection(); @@ -445,8 +439,6 @@ const TimeseriesChartIntl = injectI18n( }; this.selectedBounds = newSelectedBounds; } else { - // Don't set the brush if the selection is the full context chart domain. - this.setBrushVisibility(false); const contextXScaleDomain = this.contextXScale.domain(); const newSelectedBounds = { min: moment(new Date(contextXScaleDomain[0])), @@ -889,13 +881,12 @@ const TimeseriesChartIntl = injectI18n( } createZoomInfoElements(zoomGroup, fcsWidth) { - const { autoZoomDuration, modelPlotEnabled, timefilter, intl } = this.props; + const { autoZoomDuration, bounds, modelPlotEnabled, intl } = this.props; const setZoomInterval = this.setZoomInterval.bind(this); // Create zoom duration links applicable for the current time span. // Don't add links for any durations which would give a brush extent less than 10px. - const bounds = timefilter.getActiveBounds(); const boundsSecs = bounds.max.unix() - bounds.min.unix(); const minSecs = (10 / this.vizWidth) * boundsSecs; @@ -970,7 +961,7 @@ const TimeseriesChartIntl = injectI18n( } drawContextElements(cxtGroup, cxtWidth, cxtChartHeight, swlHeight) { - const { contextChartData, contextForecastData, modelPlotEnabled, timefilter } = this.props; + const { bounds, contextChartData, contextForecastData, modelPlotEnabled } = this.props; const data = contextChartData; @@ -1036,7 +1027,6 @@ const TimeseriesChartIntl = injectI18n( .attr('y2', cxtChartHeight + swlHeight); // Add x axis. - const bounds = timefilter.getActiveBounds(); const timeBuckets = new TimeBuckets(); timeBuckets.setInterval('auto'); timeBuckets.setBounds(bounds); @@ -1364,13 +1354,12 @@ const TimeseriesChartIntl = injectI18n( }; calculateContextXAxisDomain = () => { - const { contextAggregationInterval, swimlaneData, timefilter } = this.props; + const { bounds, contextAggregationInterval, swimlaneData } = this.props; // Calculates the x axis domain for the context elements. // Elasticsearch aggregation returns points at start of bucket, // so set the x-axis min to the start of the first aggregation interval, // and the x-axis max to the end of the last aggregation interval. // Context chart and swimlane use the same aggregation interval. - const bounds = timefilter.getActiveBounds(); let earliest = bounds.min.valueOf(); if (swimlaneData !== undefined && swimlaneData.length > 0) { @@ -1408,9 +1397,8 @@ const TimeseriesChartIntl = injectI18n( }; setZoomInterval(ms) { - const { timefilter, zoomTo } = this.props; + const { bounds, zoomTo } = this.props; - const bounds = timefilter.getActiveBounds(); const minBoundsMs = bounds.min.valueOf(); const maxBoundsMs = bounds.max.valueOf(); @@ -1525,12 +1513,12 @@ const TimeseriesChartIntl = injectI18n( } else { tooltipData.push({ name: intl.formatMessage({ - id: 'xpack.ml.timeSeriesExplorer.timeSeriesChart.modelPlotEnabled.valueLabel', - defaultMessage: 'value', + id: 'xpack.ml.timeSeriesExplorer.timeSeriesChart.modelPlotEnabled.actualLabel', + defaultMessage: 'actual', }), - value: formatValue(marker.value, marker.function, fieldFormat), + value: formatValue(marker.actual, marker.function, fieldFormat), seriesKey, - yAccessor: 'value', + yAccessor: 'actual', }); tooltipData.push({ name: intl.formatMessage({ @@ -1728,7 +1716,10 @@ const TimeseriesChartIntl = injectI18n( } ); -export const TimeseriesChart = injectObservablesAsProps( - { annotation: annotation$ }, - TimeseriesChartIntl -); +export const TimeseriesChart = props => { + const annotationProp = useObservable(annotation$); + if (annotationProp === undefined) { + return null; + } + return ; +}; diff --git a/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.test.js b/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.test.js index fb52d191013f7..cc77ad9f1a985 100644 --- a/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.test.js +++ b/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/components/timeseries_chart/timeseries_chart.test.js @@ -46,7 +46,6 @@ function getTimeseriesChartPropsMock() { showModelBounds: true, svgWidth: 1600, timefilter: {}, - skipRefresh: false, }; } diff --git a/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.d.ts b/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.d.ts index ac4bc6186e5b4..3edbbc1af2323 100644 --- a/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.d.ts +++ b/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.d.ts @@ -10,6 +10,12 @@ import { FC } from 'react'; declare const TimeSeriesExplorer: FC<{ appStateHandler: (action: string, payload: any) => void; dateFormatTz: string; - globalState: any; + selectedJobIds: string[]; + selectedDetectorIndex: number; + selectedEntities: any[]; + selectedForecastId: string; + setGlobalState: (arg: any) => void; + tableInterval: string; + tableSeverity: number; timefilter: Timefilter; }>; diff --git a/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js b/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js index 0f9ef2b54fdc2..807a368fc9b34 100644 --- a/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js +++ b/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer.js @@ -8,7 +8,7 @@ * React component for rendering Single Metric Viewer. */ -import { chain, difference, each, find, first, get, has, isEqual, without } from 'lodash'; +import { debounce, difference, each, find, get, has, isEqual, without } from 'lodash'; import moment from 'moment-timezone'; import { Subject, Subscription, forkJoin } from 'rxjs'; import { map, debounceTime, switchMap, tap, withLatestFrom } from 'rxjs/operators'; @@ -36,42 +36,34 @@ import { toastNotifications } from 'ui/notify'; import { ResizeChecker } from '../../../../../../../src/plugins/kibana_utils/public'; import { ANOMALIES_TABLE_DEFAULT_QUERY_SIZE } from '../../../common/constants/search'; -import { parseInterval } from '../../../common/util/parse_interval'; import { isModelPlotEnabled, isSourceDataChartableForDetector, - isTimeSeriesViewJob, isTimeSeriesViewDetector, mlFunctionToESAggregation, } from '../../../common/util/job_utils'; -import { ChartTooltip } from '../components/chart_tooltip'; -import { - jobSelectServiceFactory, - setGlobalState, - getSelectedJobIds, -} from '../components/job_selector/job_select_service_utils'; import { AnnotationFlyout } from '../components/annotations/annotation_flyout'; import { AnnotationsTable } from '../components/annotations/annotations_table'; import { AnomaliesTable } from '../components/anomalies_table/anomalies_table'; +import { ChartTooltip } from '../components/chart_tooltip'; import { EntityControl } from './components/entity_control'; import { ForecastingModal } from './components/forecasting_modal/forecasting_modal'; import { JobSelector } from '../components/job_selector'; +import { getTimeRangeFromSelection } from '../components/job_selector/job_select_service_utils'; import { LoadingIndicator } from '../components/loading_indicator/loading_indicator'; import { NavigationMenu } from '../components/navigation_menu'; -import { severity$, SelectSeverity } from '../components/controls/select_severity/select_severity'; -import { interval$, SelectInterval } from '../components/controls/select_interval/select_interval'; +import { SelectInterval } from '../components/controls/select_interval/select_interval'; +import { SelectSeverity } from '../components/controls/select_severity/select_severity'; import { TimeseriesChart } from './components/timeseries_chart/timeseries_chart'; import { TimeseriesexplorerNoJobsFound } from './components/timeseriesexplorer_no_jobs_found'; import { TimeseriesexplorerNoChartData } from './components/timeseriesexplorer_no_chart_data'; -import { annotationsRefresh$ } from '../services/annotations_service'; import { ml } from '../services/ml_api_service'; import { mlFieldFormatService } from '../services/field_format_service'; import { mlForecastService } from '../services/forecast_service'; import { mlJobService } from '../services/job_service'; import { mlResultsService } from '../services/results_service'; -import { mlTimefilterRefresh$ } from '../services/timefilter_refresh_service'; import { getBoundsRoundedToInterval } from '../util/time_buckets'; @@ -86,7 +78,6 @@ import { calculateDefaultFocusRange, calculateInitialFocusRange, createTimeSeriesJobData, - getAutoZoomDuration, processForecastResults, processMetricPlotResults, processRecordScoreResults, @@ -102,33 +93,57 @@ const allValuesLabel = i18n.translate('xpack.ml.timeSeriesExplorer.allPartitionV defaultMessage: 'all', }); +function getEntityControlOptions(fieldValues) { + if (!Array.isArray(fieldValues)) { + return []; + } + + return fieldValues.map(value => { + return { label: value }; + }); +} + +function getViewableDetectors(selectedJob) { + const jobDetectors = selectedJob.analysis_config.detectors; + const viewableDetectors = []; + each(jobDetectors, (dtr, index) => { + if (isTimeSeriesViewDetector(selectedJob, index)) { + viewableDetectors.push({ + index, + detector_description: dtr.detector_description, + }); + } + }); + return viewableDetectors; +} + function getTimeseriesexplorerDefaultState() { return { chartDetails: undefined, + contextAggregationInterval: undefined, contextChartData: undefined, contextForecastData: undefined, // Not chartable if e.g. model plot with terms for a varp detector dataNotChartable: false, - detectorId: undefined, - detectors: [], - entities: [], + entitiesLoading: false, + entityValues: {}, focusAnnotationData: [], focusChartData: undefined, focusForecastData: undefined, fullRefresh: true, hasResults: false, - jobs: [], // Counter to keep track of what data sets have been loaded. loadCounter: 0, loading: false, modelPlotEnabled: false, - selectedJob: undefined, // Toggles display of annotations in the focus chart showAnnotations: mlAnnotationsEnabled, showAnnotationsCheckbox: mlAnnotationsEnabled, // Toggles display of forecast data in the focus chart showForecast: true, showForecastCheckbox: false, + // Toggles display of model bounds in the focus chart + showModelBounds: true, showModelBoundsCheckbox: false, svgWidth: 0, tableData: undefined, @@ -136,9 +151,6 @@ function getTimeseriesexplorerDefaultState() { zoomTo: undefined, zoomFromFocusLoaded: undefined, zoomToFocusLoaded: undefined, - - // Toggles display of model bounds in the focus chart - showModelBounds: true, }; } @@ -174,26 +186,23 @@ const containerPadding = 24; export class TimeSeriesExplorer extends React.Component { static propTypes = { appStateHandler: PropTypes.func.isRequired, + autoZoomDuration: PropTypes.number, + bounds: PropTypes.object, dateFormatTz: PropTypes.string.isRequired, - globalState: PropTypes.object.isRequired, - timefilter: PropTypes.object.isRequired, + jobsWithTimeRange: PropTypes.array.isRequired, + lastRefresh: PropTypes.number.isRequired, + selectedJobIds: PropTypes.arrayOf(PropTypes.string), + selectedDetectorIndex: PropTypes.number, + selectedEntities: PropTypes.object, + selectedForecastId: PropTypes.string, + tableInterval: PropTypes.string, + tableSeverity: PropTypes.number, }; state = getTimeseriesexplorerDefaultState(); subscriptions = new Subscription(); - _criteriaFields = null; - - constructor(props) { - super(props); - const { jobSelectService$, unsubscribeFromGlobalState } = jobSelectServiceFactory( - props.globalState - ); - this.jobSelectService$ = jobSelectService$; - this.unsubscribeFromGlobalState = unsubscribeFromGlobalState; - } - resizeRef = createRef(); resizeChecker = undefined; resizeHandler = () => { @@ -209,13 +218,10 @@ export class TimeSeriesExplorer extends React.Component { contextChart$ = new Subject(); detectorIndexChangeHandler = e => { + const { appStateHandler } = this.props; const id = e.target.value; if (id !== undefined) { - this.setState({ detectorId: id }, () => { - this.updateControlsForDetector(() => - this.loadEntityValues(() => this.saveSeriesPropertiesAndRefresh()) - ); - }); + appStateHandler(APP_STATE_ACTION.SET_DETECTOR_INDEX, +id); } }; @@ -245,7 +251,7 @@ export class TimeSeriesExplorer extends React.Component { previousShowModelBounds = undefined; tableFilter = (field, value, operator) => { - const { entities } = this.state; + const entities = this.getControlsForDetector(); const entity = entities.find(({ fieldName }) => fieldName === field); if (entity === undefined) { @@ -272,35 +278,14 @@ export class TimeSeriesExplorer extends React.Component { }; appStateHandler(APP_STATE_ACTION.SET_ENTITIES, resultEntities); - - this.updateControlsForDetector(() => { - this.refresh(); - }); }; contextChartSelectedInitCallDone = false; - /** - * Gets default range from component state. - */ - getDefaultRangeFromState() { - const { - autoZoomDuration, - contextAggregationInterval, - contextChartData, - contextForecastData, - } = this.state; - - return calculateDefaultFocusRange( - autoZoomDuration, - contextAggregationInterval, - contextChartData, - contextForecastData - ); - } - getFocusAggregationInterval(selection) { - const { jobs, selectedJob } = this.state; + const { selectedJobIds } = this.props; + const jobs = createTimeSeriesJobData(mlJobService.jobs); + const selectedJob = mlJobService.getJob(selectedJobIds[0]); // Calculate the aggregation interval for the focus chart. const bounds = { min: moment(selection.from), max: moment(selection.to) }; @@ -312,13 +297,13 @@ export class TimeSeriesExplorer extends React.Component { * Gets focus data for the current component state/ */ getFocusData(selection) { - const { detectorId, entities, modelPlotEnabled, selectedJob } = this.state; - - const { appStateHandler } = this.props; + const { selectedJobIds, selectedForecastId, selectedDetectorIndex } = this.props; + const { modelPlotEnabled } = this.state; + const selectedJob = mlJobService.getJob(selectedJobIds[0]); + const entityControls = this.getControlsForDetector(); // Calculate the aggregation interval for the focus chart. const bounds = { min: moment(selection.from), max: moment(selection.to) }; - const focusAggregationInterval = this.getFocusAggregationInterval(selection); // Ensure the search bounds align to the bucketing interval so that the first and last buckets are complete. @@ -327,12 +312,12 @@ export class TimeSeriesExplorer extends React.Component { const searchBounds = getBoundsRoundedToInterval(bounds, focusAggregationInterval, false); return getFocusData( - this._criteriaFields, - +detectorId, + this.getCriteriaFields(selectedDetectorIndex, entityControls), + selectedDetectorIndex, focusAggregationInterval, - appStateHandler(APP_STATE_ACTION.GET_FORECAST_ID), + selectedForecastId, modelPlotEnabled, - entities.filter(entity => entity.fieldValue.length > 0), + entityControls.filter(entity => entity.fieldValue.length > 0), searchBounds, selectedJob, TIME_FIELD_NAME @@ -345,10 +330,10 @@ export class TimeSeriesExplorer extends React.Component { entityFieldValueChanged = (entity, fieldValue) => { const { appStateHandler } = this.props; - const { entities } = this.state; + const entityControls = this.getControlsForDetector(); const resultEntities = { - ...entities.reduce((appStateEntities, appStateEntity) => { + ...entityControls.reduce((appStateEntities, appStateEntity) => { appStateEntities[appStateEntity.fieldName] = appStateEntity.fieldValue; return appStateEntities; }, {}), @@ -356,23 +341,33 @@ export class TimeSeriesExplorer extends React.Component { }; appStateHandler(APP_STATE_ACTION.SET_ENTITIES, resultEntities); + }; - this.updateControlsForDetector(() => { - this.refresh(); + entityFieldSearchChanged = debounce((entity, queryTerm) => { + const entityControls = this.getControlsForDetector(); + this.loadEntityValues(entityControls, { + [entity.fieldType]: queryTerm, }); - }; + }, 500); loadAnomaliesTableData = (earliestMs, latestMs) => { - const { dateFormatTz } = this.props; - const { selectedJob } = this.state; + const { + dateFormatTz, + selectedDetectorIndex, + selectedJobIds, + tableInterval, + tableSeverity, + } = this.props; + const selectedJob = mlJobService.getJob(selectedJobIds[0]); + const entityControls = this.getControlsForDetector(); return ml.results .getAnomaliesTableData( [selectedJob.job_id], - this._criteriaFields, + this.getCriteriaFields(selectedDetectorIndex, entityControls), [], - interval$.getValue().val, - severity$.getValue().val, + tableInterval, + tableSeverity, earliestMs, latestMs, dateFormatTz, @@ -419,132 +414,80 @@ export class TimeSeriesExplorer extends React.Component { ); }; - loadEntityValues = (callback = () => {}) => { - const { timefilter } = this.props; - const { detectorId, entities, selectedJob } = this.state; + /** + * Loads available entity values. + * @param {Array} entities - Entity controls configuration + * @param {Object} searchTerm - Search term for partition, e.g. { partition_field: 'partition' } + */ + loadEntityValues = async (entities, searchTerm = {}) => { + this.setState({ entitiesLoading: true }); + + const { bounds, selectedJobIds, selectedDetectorIndex } = this.props; + const selectedJob = mlJobService.getJob(selectedJobIds[0]); // Populate the entity input datalists with the values from the top records by score // for the selected detector across the full time range. No need to pass through finish(). - const bounds = timefilter.getActiveBounds(); - const detectorIndex = +detectorId; + const detectorIndex = selectedDetectorIndex; - mlResultsService - .getRecordsForCriteria( - [selectedJob.job_id], - [{ fieldName: 'detector_index', fieldValue: detectorIndex }], - 0, + const { + partition_field: partitionField, + over_field: overField, + by_field: byField, + } = await mlResultsService + .fetchPartitionFieldsValues( + selectedJob.job_id, + searchTerm, + [ + { + fieldName: 'detector_index', + fieldValue: detectorIndex, + }, + ], bounds.min.valueOf(), - bounds.max.valueOf(), - ANOMALIES_TABLE_DEFAULT_QUERY_SIZE + bounds.max.valueOf() ) - .toPromise() - .then(resp => { - if (resp.records && resp.records.length > 0) { - const firstRec = resp.records[0]; - - this.setState( - { - entities: entities.map(entity => { - const newEntity = { ...entity }; - if (firstRec.partition_field_name === newEntity.fieldName) { - newEntity.fieldValues = chain(resp.records) - .pluck('partition_field_value') - .uniq() - .value(); - } - if (firstRec.over_field_name === newEntity.fieldName) { - newEntity.fieldValues = chain(resp.records) - .pluck('over_field_value') - .uniq() - .value(); - } - if (firstRec.by_field_name === newEntity.fieldName) { - newEntity.fieldValues = chain(resp.records) - .pluck('by_field_value') - .uniq() - .value(); - } - return newEntity; - }), - }, - callback - ); - } - }); - }; - - loadForForecastId = forecastId => { - const { appStateHandler, timefilter } = this.props; - const { autoZoomDuration, contextChartData, selectedJob } = this.state; - - mlForecastService - .getForecastDateRange(selectedJob, forecastId) - .then(resp => { - const bounds = timefilter.getActiveBounds(); - const earliest = moment(resp.earliest || timefilter.getTime().from); - const latest = moment(resp.latest || timefilter.getTime().to); - - // Store forecast ID in the appState. - appStateHandler(APP_STATE_ACTION.SET_FORECAST_ID, forecastId); - - // Set the zoom to centre on the start of the forecast range, depending - // on the time range of the forecast and data. - const earliestDataDate = first(contextChartData).date; - const zoomLatestMs = Math.min(earliest + autoZoomDuration / 2, latest.valueOf()); - const zoomEarliestMs = Math.max( - zoomLatestMs - autoZoomDuration, - earliestDataDate.getTime() - ); + .toPromise(); - const zoomState = { - from: moment(zoomEarliestMs).toISOString(), - to: moment(zoomLatestMs).toISOString(), - }; - appStateHandler(APP_STATE_ACTION.SET_ZOOM, zoomState); + const entityValues = {}; + entities.forEach(entity => { + let fieldValues; - // Ensure the forecast data will be shown if hidden previously. - this.setState({ showForecast: true }); + if (partitionField?.name === entity.fieldName) { + fieldValues = partitionField.values; + } + if (overField?.name === entity.fieldName) { + fieldValues = overField.values; + } + if (byField?.name === entity.fieldName) { + fieldValues = byField.values; + } + entityValues[entity.fieldName] = fieldValues; + }); - if (earliest.isBefore(bounds.min) || latest.isAfter(bounds.max)) { - const earliestMs = Math.min(earliest.valueOf(), bounds.min.valueOf()); - const latestMs = Math.max(latest.valueOf(), bounds.max.valueOf()); + this.setState({ entitiesLoading: false, entityValues }); + }; - timefilter.setTime({ - from: moment(earliestMs).toISOString(), - to: moment(latestMs).toISOString(), - }); - } else { - // Refresh to show the requested forecast data. - this.refresh(); - } - }) - .catch(resp => { - console.log( - 'Time series explorer - error loading time range of forecast from elasticsearch:', - resp - ); - }); + setForecastId = forecastId => { + this.props.appStateHandler(APP_STATE_ACTION.SET_FORECAST_ID, forecastId); }; - refresh = (fullRefresh = true) => { - // Skip the refresh if: - // a) The global state's `skipRefresh` was set to true by the job selector to avoid race conditions - // when loading the Single Metric Viewer after a job/group and time range update. - // b) A 'soft' refresh without a full page reload is already happening. - if ( - get(this.props.globalState, 'ml.skipRefresh') || - (this.state.loading && fullRefresh === false) - ) { + loadSingleMetricData = (fullRefresh = true) => { + const { + autoZoomDuration, + bounds, + selectedDetectorIndex, + selectedForecastId, + selectedJobIds, + zoom, + } = this.props; + + if (selectedJobIds === undefined) { return; } - const { appStateHandler, timefilter } = this.props; - const { - detectorId: currentDetectorId, - entities: currentEntities, - loadCounter: currentLoadCounter, - selectedJob: currentSelectedJob, - } = this.state; + const { loadCounter: currentLoadCounter } = this.state; + + const currentSelectedJob = mlJobService.getJob(selectedJobIds[0]); if (currentSelectedJob === undefined) { return; @@ -554,6 +497,7 @@ export class TimeSeriesExplorer extends React.Component { // Only when `fullRefresh` is true we'll reset all data // and show the loading spinner within the page. + const entityControls = this.getControlsForDetector(); this.setState( { fullRefresh, @@ -568,8 +512,8 @@ export class TimeSeriesExplorer extends React.Component { focusForecastData: undefined, modelPlotEnabled: isModelPlotEnabled( currentSelectedJob, - +currentDetectorId, - currentEntities + selectedDetectorIndex, + entityControls ), hasResults: false, dataNotChartable: false, @@ -577,15 +521,11 @@ export class TimeSeriesExplorer extends React.Component { : {}), }, () => { - const { - detectorId, - entities, - loadCounter, - jobs, - modelPlotEnabled, - selectedJob, - } = this.state; - const detectorIndex = +detectorId; + const { loadCounter, modelPlotEnabled } = this.state; + + const jobs = createTimeSeriesJobData(mlJobService.jobs); + const selectedJob = mlJobService.getJob(selectedJobIds[0]); + const detectorIndex = selectedDetectorIndex; let awaitingCount = 3; @@ -605,19 +545,16 @@ export class TimeSeriesExplorer extends React.Component { // Set zoomFrom/zoomTo attributes in scope which will result in the metric chart automatically // selecting the specified range in the context chart, and so loading that date range in the focus chart. if (stateUpdate.contextChartData.length) { - // Calculate the 'auto' zoom duration which shows data at bucket span granularity. - stateUpdate.autoZoomDuration = getAutoZoomDuration(jobs, selectedJob); - // Check for a zoom parameter in the appState (URL). let focusRange = calculateInitialFocusRange( - appStateHandler(APP_STATE_ACTION.GET_ZOOM), + zoom, stateUpdate.contextAggregationInterval, - timefilter + bounds ); if (focusRange === undefined) { focusRange = calculateDefaultFocusRange( - stateUpdate.autoZoomDuration, + autoZoomDuration, stateUpdate.contextAggregationInterval, stateUpdate.contextChartData, stateUpdate.contextForecastData @@ -632,7 +569,7 @@ export class TimeSeriesExplorer extends React.Component { } }; - const nonBlankEntities = currentEntities.filter(entity => { + const nonBlankEntities = entityControls.filter(entity => { return entity.fieldValue.length > 0; }); @@ -650,8 +587,6 @@ export class TimeSeriesExplorer extends React.Component { return; } - const bounds = timefilter.getActiveBounds(); - // Calculate the aggregation interval for the context chart. // Context chart swimlane will display bucket anomaly score at the same interval. stateUpdate.contextAggregationInterval = calculateAggregationInterval( @@ -702,7 +637,7 @@ export class TimeSeriesExplorer extends React.Component { mlResultsService .getRecordMaxScoreByTime( selectedJob.job_id, - this._criteriaFields, + this.getCriteriaFields(detectorIndex, entityControls), searchBounds.min.valueOf(), searchBounds.max.valueOf(), stateUpdate.contextAggregationInterval.expression @@ -724,7 +659,7 @@ export class TimeSeriesExplorer extends React.Component { .getChartDetails( selectedJob, detectorIndex, - entities, + entityControls, searchBounds.min.valueOf(), searchBounds.max.valueOf() ) @@ -740,8 +675,7 @@ export class TimeSeriesExplorer extends React.Component { }); // Plus query for forecast data if there is a forecastId stored in the appState. - const forecastId = appStateHandler(APP_STATE_ACTION.GET_FORECAST_ID); - if (forecastId !== undefined) { + if (selectedForecastId !== undefined) { awaitingCount++; let aggType = undefined; const detector = selectedJob.analysis_config.detectors[detectorIndex]; @@ -753,7 +687,7 @@ export class TimeSeriesExplorer extends React.Component { .getForecastData( selectedJob, detectorIndex, - forecastId, + selectedForecastId, nonBlankEntities, searchBounds.min.valueOf(), searchBounds.max.valueOf(), @@ -767,13 +701,11 @@ export class TimeSeriesExplorer extends React.Component { }) .catch(resp => { console.log( - `Time series explorer - error loading data for forecast ID ${forecastId}`, + `Time series explorer - error loading data for forecast ID ${selectedForecastId}`, resp ); }); } - - this.loadEntityValues(); } ); }; @@ -782,25 +714,39 @@ export class TimeSeriesExplorer extends React.Component { * Updates local state of detector related controls from the global state. * @param callback to invoke after a state update. */ - updateControlsForDetector = (callback = () => {}) => { - const { appStateHandler } = this.props; - const { detectorId, selectedJob } = this.state; + getControlsForDetector = () => { + const { selectedDetectorIndex, selectedEntities, selectedJobIds } = this.props; + const selectedJob = mlJobService.getJob(selectedJobIds[0]); + + const entities = []; + + if (selectedJob === undefined) { + return entities; + } + // Update the entity dropdown control(s) according to the partitioning fields for the selected detector. - const detectorIndex = +detectorId; + const detectorIndex = selectedDetectorIndex; const detector = selectedJob.analysis_config.detectors[detectorIndex]; - const entities = []; - const entitiesState = appStateHandler(APP_STATE_ACTION.GET_ENTITIES); + const entitiesState = selectedEntities; const partitionFieldName = get(detector, 'partition_field_name'); const overFieldName = get(detector, 'over_field_name'); const byFieldName = get(detector, 'by_field_name'); if (partitionFieldName !== undefined) { const partitionFieldValue = get(entitiesState, partitionFieldName, ''); - entities.push({ fieldName: partitionFieldName, fieldValue: partitionFieldValue }); + entities.push({ + fieldType: 'partition_field', + fieldName: partitionFieldName, + fieldValue: partitionFieldValue, + }); } if (overFieldName !== undefined) { const overFieldValue = get(entitiesState, overFieldName, ''); - entities.push({ fieldName: overFieldName, fieldValue: overFieldValue }); + entities.push({ + fieldType: 'over_field', + fieldName: overFieldName, + fieldValue: overFieldValue, + }); } // For jobs with by and over fields, don't add the 'by' field as this @@ -810,12 +756,10 @@ export class TimeSeriesExplorer extends React.Component { // from filter for the anomaly records. if (byFieldName !== undefined && overFieldName === undefined) { const byFieldValue = get(entitiesState, byFieldName, ''); - entities.push({ fieldName: byFieldName, fieldValue: byFieldValue }); + entities.push({ fieldType: 'by_field', fieldName: byFieldName, fieldValue: byFieldValue }); } - this.updateCriteriaFields(detectorIndex, entities); - - this.setState({ entities }, callback); + return entities; }; /** @@ -823,10 +767,10 @@ export class TimeSeriesExplorer extends React.Component { * @param detectorIndex * @param entities */ - updateCriteriaFields(detectorIndex, entities) { + getCriteriaFields(detectorIndex, entities) { // Only filter on the entity if the field has a value. const nonBlankEntities = entities.filter(entity => entity.fieldValue.length > 0); - this._criteriaFields = [ + return [ { fieldName: 'detector_index', fieldValue: detectorIndex, @@ -835,47 +779,21 @@ export class TimeSeriesExplorer extends React.Component { ]; } - loadForJobId(jobId, jobs) { - const { appStateHandler } = this.props; - - // Validation that the ID is for a time series job must already have been performed. - // Check if the job was created since the page was first loaded. - let jobPickerSelectedJob = find(jobs, { id: jobId }); - if (jobPickerSelectedJob === undefined) { - const newJobs = []; - each(mlJobService.jobs, job => { - if (isTimeSeriesViewJob(job) === true) { - const bucketSpan = parseInterval(job.analysis_config.bucket_span); - newJobs.push({ - id: job.job_id, - selected: false, - bucketSpanSeconds: bucketSpan.asSeconds(), - }); - } - }); - this.setState({ jobs: newJobs }); - jobPickerSelectedJob = find(newJobs, { id: jobId }); - } + loadForJobId(jobId) { + const { appStateHandler, selectedDetectorIndex } = this.props; const selectedJob = mlJobService.getJob(jobId); - // Read the detector index and entities out of the AppState. - const jobDetectors = selectedJob.analysis_config.detectors; - const viewableDetectors = []; - each(jobDetectors, (dtr, index) => { - if (isTimeSeriesViewDetector(selectedJob, index)) { - viewableDetectors.push({ - index: '' + index, - detector_description: dtr.detector_description, - }); - } - }); - const detectors = viewableDetectors; + if (selectedJob === undefined) { + return; + } + + const detectors = getViewableDetectors(selectedJob); // Check the supplied index is valid. - const appStateDtrIdx = appStateHandler(APP_STATE_ACTION.GET_DETECTOR_INDEX); - let detectorIndex = appStateDtrIdx !== undefined ? appStateDtrIdx : +viewableDetectors[0].index; - if (find(viewableDetectors, { index: '' + detectorIndex }) === undefined) { + const appStateDtrIdx = selectedDetectorIndex; + let detectorIndex = appStateDtrIdx !== undefined ? appStateDtrIdx : detectors[0].index; + if (find(detectors, { index: detectorIndex }) === undefined) { const warningText = i18n.translate( 'xpack.ml.timeSeriesExplorer.requestedDetectorIndexNotValidWarningMessage', { @@ -887,179 +805,22 @@ export class TimeSeriesExplorer extends React.Component { } ); toastNotifications.addWarning(warningText); - detectorIndex = +viewableDetectors[0].index; - appStateHandler(APP_STATE_ACTION.SET_DETECTOR_INDEX, detectorIndex); + detectorIndex = detectors[0].index; } - // Store the detector index as a string so it can be used as ng-model in a select control. - const detectorId = '' + detectorIndex; + const detectorId = detectorIndex; - this.setState({ detectorId, detectors, selectedJob }, () => { - this.updateControlsForDetector(() => { - // Populate the map of jobs / detectors / field formatters for the selected IDs and refresh. - mlFieldFormatService - .populateFormats([jobId]) - .catch(err => { - console.log('Error populating field formats:', err); - }) - // Load the data - if the FieldFormats failed to populate - // the default formatting will be used for metric values. - .then(() => { - this.refresh(); - }); - }); + if (detectorId !== selectedDetectorIndex) { + appStateHandler(APP_STATE_ACTION.SET_DETECTOR_INDEX, detectorId); + } + + // Populate the map of jobs / detectors / field formatters for the selected IDs and refresh. + mlFieldFormatService.populateFormats([jobId]).catch(err => { + console.log('Error populating field formats:', err); }); } - saveSeriesPropertiesAndRefresh = () => { - const { appStateHandler } = this.props; - const { detectorId, entities } = this.state; - - appStateHandler(APP_STATE_ACTION.SET_DETECTOR_INDEX, +detectorId); - appStateHandler( - APP_STATE_ACTION.SET_ENTITIES, - entities.reduce((appStateEntities, entity) => { - appStateEntities[entity.fieldName] = entity.fieldValue; - return appStateEntities; - }, {}) - ); - - this.refresh(); - }; - componentDidMount() { - const { appStateHandler, globalState, timefilter } = this.props; - - this.setState({ jobs: [] }); - - // Get the job info needed by the visualization, then do the first load. - if (mlJobService.jobs.length > 0) { - const jobs = createTimeSeriesJobData(mlJobService.jobs); - this.setState({ jobs }); - } else { - this.setState({ loading: false }); - } - - // Reload the anomalies table if the Interval or Threshold controls are changed. - const tableControlsListener = () => { - const { zoomFrom, zoomTo } = this.state; - if (zoomFrom !== undefined && zoomTo !== undefined) { - this.loadAnomaliesTableData(zoomFrom.getTime(), zoomTo.getTime()).subscribe(res => - this.setState(res) - ); - } - }; - - this.subscriptions.add(annotationsRefresh$.subscribe(this.refresh)); - this.subscriptions.add(interval$.subscribe(tableControlsListener)); - this.subscriptions.add(severity$.subscribe(tableControlsListener)); - this.subscriptions.add( - mlTimefilterRefresh$.subscribe(() => { - this.refresh(true); - }) - ); - - // Listen for changes to job selection. - this.subscriptions.add( - this.jobSelectService$.subscribe(({ selection: selectedJobIds }) => { - const jobs = createTimeSeriesJobData(mlJobService.jobs); - - this.contextChartSelectedInitCallDone = false; - this.setState({ fullRefresh: false, loading: true, showForecastCheckbox: false }); - - const timeSeriesJobIds = jobs.map(j => j.id); - - // Check if any of the jobs set in the URL are not time series jobs - // (e.g. if switching to this view straight from the Anomaly Explorer). - const invalidIds = difference(selectedJobIds, timeSeriesJobIds); - selectedJobIds = without(selectedJobIds, ...invalidIds); - if (invalidIds.length > 0) { - let warningText = i18n.translate( - 'xpack.ml.timeSeriesExplorer.canNotViewRequestedJobsWarningMessage', - { - defaultMessage: `You can't view requested {invalidIdsCount, plural, one {job} other {jobs}} {invalidIds} in this dashboard`, - values: { - invalidIdsCount: invalidIds.length, - invalidIds, - }, - } - ); - if (selectedJobIds.length === 0 && timeSeriesJobIds.length > 0) { - warningText += i18n.translate('xpack.ml.timeSeriesExplorer.autoSelectingFirstJobText', { - defaultMessage: ', auto selecting first job', - }); - } - toastNotifications.addWarning(warningText); - } - - if (selectedJobIds.length > 1) { - // if more than one job or a group has been loaded from the URL - if (selectedJobIds.length > 1) { - // if more than one job, select the first job from the selection. - toastNotifications.addWarning( - i18n.translate('xpack.ml.timeSeriesExplorer.youCanViewOneJobAtTimeWarningMessage', { - defaultMessage: 'You can only view one job at a time in this dashboard', - }) - ); - - setGlobalState(globalState, { selectedIds: [selectedJobIds[0]] }); - this.jobSelectService$.next({ selection: [selectedJobIds[0]], resetSelection: true }); - } else { - // if a group has been loaded - if (selectedJobIds.length > 0) { - // if the group contains valid jobs, select the first - toastNotifications.addWarning( - i18n.translate('xpack.ml.timeSeriesExplorer.youCanViewOneJobAtTimeWarningMessage', { - defaultMessage: 'You can only view one job at a time in this dashboard', - }) - ); - - setGlobalState(globalState, { selectedIds: [selectedJobIds[0]] }); - this.jobSelectService$.next({ selection: [selectedJobIds[0]], resetSelection: true }); - } else if (jobs.length > 0) { - // if there are no valid jobs in the group but there are valid jobs - // in the list of all jobs, select the first - setGlobalState(globalState, { selectedIds: [jobs[0].id] }); - this.jobSelectService$.next({ selection: [jobs[0].id], resetSelection: true }); - } else { - // if there are no valid jobs left. - this.setState({ loading: false }); - } - } - } else if (invalidIds.length > 0 && selectedJobIds.length > 0) { - // if some ids have been filtered out because they were invalid. - // refresh the URL with the first valid id - setGlobalState(globalState, { selectedIds: [selectedJobIds[0]] }); - this.jobSelectService$.next({ selection: [selectedJobIds[0]], resetSelection: true }); - } else if (selectedJobIds.length > 0) { - // normal behavior. a job ID has been loaded from the URL - if ( - this.state.selectedJob !== undefined && - selectedJobIds[0] !== this.state.selectedJob.job_id - ) { - // Clear the detectorIndex, entities and forecast info. - appStateHandler(APP_STATE_ACTION.CLEAR); - } - this.loadForJobId(selectedJobIds[0], jobs); - } else { - if (selectedJobIds.length === 0 && jobs.length > 0) { - // no jobs were loaded from the URL, so add the first job - // from the full jobs list. - setGlobalState(globalState, { selectedIds: [jobs[0].id] }); - this.jobSelectService$.next({ selection: [jobs[0].id], resetSelection: true }); - } else { - // Jobs exist, but no time series jobs. - this.setState({ loading: false }); - } - } - }) - ); - - timefilter.enableTimeRangeSelector(); - timefilter.enableAutoRefreshSelector(); - - this.subscriptions.add(timefilter.getTimeUpdate$().subscribe(() => this.refresh(false))); - // Required to redraw the time series chart when the container is resized. this.resizeChecker = new ResizeChecker(this.resizeRef.current); this.resizeChecker.on('resize', () => { @@ -1094,23 +855,6 @@ export class TimeSeriesExplorer extends React.Component { return; } - const defaultRange = this.getDefaultRangeFromState(); - - if ( - (selection.from.getTime() !== defaultRange[0].getTime() || - selection.to.getTime() !== defaultRange[1].getTime()) && - isNaN(Date.parse(selection.from)) === false && - isNaN(Date.parse(selection.to)) === false - ) { - const zoomState = { - from: selection.from.toISOString(), - to: selection.to.toISOString(), - }; - appStateHandler(APP_STATE_ACTION.SET_ZOOM, zoomState); - } else { - appStateHandler(APP_STATE_ACTION.UNSET_ZOOM); - } - if ( (this.contextChartSelectedInitCallDone === false && focusChartData === undefined) || zoomFromFocusLoaded.getTime() !== selection.from.getTime() || @@ -1125,7 +869,9 @@ export class TimeSeriesExplorer extends React.Component { } }), switchMap(selection => { - const { jobs, selectedJob } = this.state; + const { selectedJobIds } = this.props; + const jobs = createTimeSeriesJobData(mlJobService.jobs); + const selectedJob = mlJobService.getJob(selectedJobIds[0]); // Calculate the aggregation interval for the focus chart. const bounds = { min: moment(selection.from), max: moment(selection.to) }; @@ -1168,39 +914,267 @@ export class TimeSeriesExplorer extends React.Component { ...refreshFocusData, ...tableData, }); + const zoomState = { + from: selection.from.toISOString(), + to: selection.to.toISOString(), + }; + this.props.appStateHandler(APP_STATE_ACTION.SET_ZOOM, zoomState); }) ); + + this.componentDidUpdate(); + } + + /** + * returns true/false if setGlobalState has been triggered + * or returns the job id which should be loaded. + */ + checkJobSelection() { + const { jobsWithTimeRange, selectedJobIds, setGlobalState } = this.props; + + const jobs = createTimeSeriesJobData(mlJobService.jobs); + const timeSeriesJobIds = jobs.map(j => j.id); + + // Check if any of the jobs set in the URL are not time series jobs + // (e.g. if switching to this view straight from the Anomaly Explorer). + const invalidIds = difference(selectedJobIds, timeSeriesJobIds); + const validSelectedJobIds = without(selectedJobIds, ...invalidIds); + if (invalidIds.length > 0) { + let warningText = i18n.translate( + 'xpack.ml.timeSeriesExplorer.canNotViewRequestedJobsWarningMessage', + { + defaultMessage: `You can't view requested {invalidIdsCount, plural, one {job} other {jobs}} {invalidIds} in this dashboard`, + values: { + invalidIdsCount: invalidIds.length, + invalidIds, + }, + } + ); + if (validSelectedJobIds.length === 0 && timeSeriesJobIds.length > 0) { + warningText += i18n.translate('xpack.ml.timeSeriesExplorer.autoSelectingFirstJobText', { + defaultMessage: ', auto selecting first job', + }); + } + toastNotifications.addWarning(warningText); + } + + if (validSelectedJobIds.length > 1) { + // if more than one job or a group has been loaded from the URL + if (validSelectedJobIds.length > 1) { + // if more than one job, select the first job from the selection. + toastNotifications.addWarning( + i18n.translate('xpack.ml.timeSeriesExplorer.youCanViewOneJobAtTimeWarningMessage', { + defaultMessage: 'You can only view one job at a time in this dashboard', + }) + ); + setGlobalState('ml', { jobIds: [validSelectedJobIds[0]] }); + return true; + } else { + // if a group has been loaded + if (selectedJobIds.length > 0) { + // if the group contains valid jobs, select the first + toastNotifications.addWarning( + i18n.translate('xpack.ml.timeSeriesExplorer.youCanViewOneJobAtTimeWarningMessage', { + defaultMessage: 'You can only view one job at a time in this dashboard', + }) + ); + setGlobalState('ml', { jobIds: [validSelectedJobIds[0]] }); + return true; + } else if (jobs.length > 0) { + // if there are no valid jobs in the group but there are valid jobs + // in the list of all jobs, select the first + const jobIds = [jobs[0].id]; + const time = getTimeRangeFromSelection(jobsWithTimeRange, jobIds); + setGlobalState({ + ...{ ml: { jobIds } }, + ...(time !== undefined ? { time } : {}), + }); + return true; + } else { + // if there are no valid jobs left. + return false; + } + } + } else if (invalidIds.length > 0 && validSelectedJobIds.length > 0) { + // if some ids have been filtered out because they were invalid. + // refresh the URL with the first valid id + setGlobalState('ml', { jobIds: [validSelectedJobIds[0]] }); + return true; + } else if (validSelectedJobIds.length > 0) { + // normal behavior. a job ID has been loaded from the URL + // Clear the detectorIndex, entities and forecast info. + return validSelectedJobIds[0]; + } else { + if (validSelectedJobIds.length === 0 && jobs.length > 0) { + // no jobs were loaded from the URL, so add the first job + // from the full jobs list. + const jobIds = [jobs[0].id]; + const time = getTimeRangeFromSelection(jobsWithTimeRange, jobIds); + setGlobalState({ + ...{ ml: { jobIds } }, + ...(time !== undefined ? { time } : {}), + }); + return true; + } else { + // Jobs exist, but no time series jobs. + return false; + } + } + } + + componentDidUpdate(previousProps) { + if ( + previousProps === undefined || + !isEqual(previousProps.selectedJobIds, this.props.selectedJobIds) + ) { + const update = this.checkJobSelection(); + // - true means a setGlobalState got triggered and + // we'll just wait for the next React render. + // - false means there are either no jobs or no time based jobs present. + // - if we get back a string it means we got back a job id we can load. + if (update === true) { + return; + } else if (update === false) { + this.setState({ loading: false }); + return; + } else if (typeof update === 'string') { + this.contextChartSelectedInitCallDone = false; + this.setState({ fullRefresh: false, loading: true }, () => { + this.loadForJobId(update); + }); + } + } + + if ( + this.props.bounds !== undefined && + this.props.selectedJobIds !== undefined && + (previousProps === undefined || + !isEqual(previousProps.selectedJobIds, this.props.selectedJobIds) || + previousProps.selectedDetectorIndex !== this.props.selectedDetectorIndex || + !isEqual(previousProps.selectedEntities, this.props.selectedEntities)) + ) { + const entityControls = this.getControlsForDetector(); + this.loadEntityValues(entityControls); + } + + if ( + previousProps === undefined || + previousProps.selectedForecastId !== this.props.selectedForecastId + ) { + if (this.props.selectedForecastId !== undefined) { + // Ensure the forecast data will be shown if hidden previously. + this.setState({ showForecast: true }); + } + } + + if ( + previousProps === undefined || + !isEqual(previousProps.bounds, this.props.bounds) || + !isEqual(previousProps.lastRefresh, this.props.lastRefresh) || + !isEqual(previousProps.selectedDetectorIndex, this.props.selectedDetectorIndex) || + !isEqual(previousProps.selectedEntities, this.props.selectedEntities) || + !isEqual(previousProps.selectedForecastId, this.props.selectedForecastId) || + !isEqual(previousProps.selectedJobIds, this.props.selectedJobIds) || + !isEqual(previousProps.zoom, this.props.zoom) + ) { + const fullRefresh = + previousProps === undefined || + !isEqual(previousProps.bounds, this.props.bounds) || + !isEqual(previousProps.lastRefresh, this.props.lastRefresh) || + !isEqual(previousProps.selectedDetectorIndex, this.props.selectedDetectorIndex) || + !isEqual(previousProps.selectedEntities, this.props.selectedEntities) || + !isEqual(previousProps.selectedForecastId, this.props.selectedForecastId) || + !isEqual(previousProps.selectedJobIds, this.props.selectedJobIds); + this.loadSingleMetricData(fullRefresh); + } + + if (previousProps === undefined) { + return; + } + + // Reload the anomalies table if the Interval or Threshold controls are changed. + const tableControlsListener = () => { + const { zoomFrom, zoomTo } = this.state; + if (zoomFrom !== undefined && zoomTo !== undefined) { + this.loadAnomaliesTableData(zoomFrom.getTime(), zoomTo.getTime()).subscribe(res => + this.setState(res) + ); + } + }; + + if ( + previousProps.tableInterval !== this.props.tableInterval || + previousProps.tableSeverity !== this.props.tableSeverity + ) { + tableControlsListener(); + } + + if ( + this.props.autoZoomDuration === undefined || + this.props.selectedForecastId !== undefined || + this.state.contextAggregationInterval === undefined || + this.state.contextChartData === undefined || + this.state.contextChartData.length === 0 + ) { + return; + } + + const defaultRange = calculateDefaultFocusRange( + this.props.autoZoomDuration, + this.state.contextAggregationInterval, + this.state.contextChartData, + this.state.contextForecastData + ); + + const selection = { + from: this.state.zoomFrom, + to: this.state.zoomTo, + }; + + if ( + (selection.from.getTime() !== defaultRange[0].getTime() || + selection.to.getTime() !== defaultRange[1].getTime()) && + isNaN(Date.parse(selection.from)) === false && + isNaN(Date.parse(selection.to)) === false + ) { + const zoomState = { + from: selection.from.toISOString(), + to: selection.to.toISOString(), + }; + this.props.appStateHandler(APP_STATE_ACTION.SET_ZOOM, zoomState); + } } componentWillUnmount() { this.subscriptions.unsubscribe(); this.resizeChecker.destroy(); - this.unsubscribeFromGlobalState(); } render() { - const { dateFormatTz, globalState, timefilter } = this.props; - const { autoZoomDuration, + bounds, + dateFormatTz, + lastRefresh, + selectedDetectorIndex, + selectedJobIds, + } = this.props; + + const { chartDetails, contextAggregationInterval, contextChartData, contextForecastData, dataNotChartable, - detectors, - detectorId, - entities, + entityValues, focusAggregationInterval, focusAnnotationData, focusChartData, focusForecastData, fullRefresh, hasResults, - jobs, loading, modelPlotEnabled, - selectedJob, showAnnotations, showAnnotationsCheckbox, showForecast, @@ -1216,11 +1190,6 @@ export class TimeSeriesExplorer extends React.Component { zoomToFocusLoaded, } = this.state; - const fieldNamesWithEmptyValues = entities - .filter(({ fieldValue }) => !fieldValue) - .map(({ fieldName }) => fieldName); - const arePartitioningFieldsProvided = fieldNamesWithEmptyValues.length === 0; - const chartProps = { modelPlotEnabled, contextChartData, @@ -1232,7 +1201,6 @@ export class TimeSeriesExplorer extends React.Component { focusChartData, focusForecastData, focusAggregationInterval, - skipRefresh: loading || !!get(this.props.globalState, 'ml.skipRefresh'), svgWidth, zoomFrom, zoomTo, @@ -1241,17 +1209,14 @@ export class TimeSeriesExplorer extends React.Component { autoZoomDuration, }; - const { jobIds: selectedJobIds, selectedGroups } = getSelectedJobIds(globalState); const jobSelectorProps = { dateFormatTz, - globalState, - jobSelectService$: this.jobSelectService$, - selectedJobIds, - selectedGroups, singleSelection: true, timeseriesOnly: true, }; + const jobs = createTimeSeriesJobData(mlJobService.jobs); + if (jobs.length === 0) { return ( @@ -1260,7 +1225,27 @@ export class TimeSeriesExplorer extends React.Component { ); } - const detectorSelectOptions = detectors.map(d => ({ + if ( + selectedJobIds === undefined || + selectedJobIds.length > 1 || + selectedDetectorIndex === undefined || + mlJobService.getJob(selectedJobIds[0]) === undefined + ) { + return ( + + ); + } + + const selectedJob = mlJobService.getJob(selectedJobIds[0]); + const entityControls = this.getControlsForDetector(); + + const fieldNamesWithEmptyValues = entityControls + .filter(({ fieldValue }) => !fieldValue) + .map(({ fieldName }) => fieldName); + + const arePartitioningFieldsProvided = fieldNamesWithEmptyValues.length === 0; + + const detectorSelectOptions = getViewableDetectors(selectedJob).map(d => ({ value: d.index, text: d.detector_description, })); @@ -1273,12 +1258,14 @@ export class TimeSeriesExplorer extends React.Component { isEqual(this.previousChartProps.focusAnnotationData, chartProps.focusAnnotationData) && this.previousShowAnnotations === showAnnotations && this.previousShowForecast === showForecast && - this.previousShowModelBounds === showModelBounds + this.previousShowModelBounds === showModelBounds && + this.previousLastRefresh === lastRefresh ) { renderFocusChartOnly = false; } this.previousChartProps = chartProps; + this.previousLastRefresh = lastRefresh; this.previousShowAnnotations = showAnnotations; this.previousShowForecast = showForecast; this.previousShowModelBounds = showModelBounds; @@ -1325,12 +1312,12 @@ export class TimeSeriesExplorer extends React.Component { > - {entities.map(entity => { + {entityControls.map(entity => { const entityKey = `${entity.fieldName}`; const forceSelection = !hasEmptyFieldValues && !entity.fieldValue; hasEmptyFieldValues = !hasEmptyFieldValues && forceSelection; @@ -1338,8 +1325,11 @@ export class TimeSeriesExplorer extends React.Component { ); })} @@ -1348,9 +1338,9 @@ export class TimeSeriesExplorer extends React.Component { @@ -1373,7 +1363,7 @@ export class TimeSeriesExplorer extends React.Component { hasResults === false && ( )} @@ -1381,159 +1371,161 @@ export class TimeSeriesExplorer extends React.Component { jobs.length > 0 && (fullRefresh === false || loading === false) && hasResults === true && ( - - {/* Make sure ChartTooltip is inside this plain wrapping element so positioning can be infered correctly. */} +
+ {/* Make sure ChartTooltip is inside this plain wrapping element without padding so positioning can be infered correctly. */} - - {i18n.translate('xpack.ml.timeSeriesExplorer.singleTimeSeriesAnalysisTitle', { - defaultMessage: 'Single time series analysis of {functionLabel}', - values: { functionLabel: chartDetails.functionLabel }, - })} - -   - {chartDetails.entityData.count === 1 && ( - - {chartDetails.entityData.entities.length > 0 && '('} - {chartDetails.entityData.entities - .map(entity => { - return `${entity.fieldName}: ${entity.fieldValue}`; - }) - .join(', ')} - {chartDetails.entityData.entities.length > 0 && ')'} - - )} - {chartDetails.entityData.count !== 1 && ( - - {chartDetails.entityData.entities.map((countData, i) => { - return ( - - {i18n.translate( - 'xpack.ml.timeSeriesExplorer.countDataInChartDetailsDescription', - { - defaultMessage: - '{openBrace}{cardinalityValue} distinct {fieldName} {cardinality, plural, one {} other { values}}{closeBrace}', - values: { - openBrace: i === 0 ? '(' : '', - closeBrace: - i === chartDetails.entityData.entities.length - 1 ? ')' : '', - cardinalityValue: - countData.cardinality === 0 - ? allValuesLabel - : countData.cardinality, - cardinality: countData.cardinality, - fieldName: countData.fieldName, - }, - } - )} - {i !== chartDetails.entityData.entities.length - 1 ? ', ' : ''} - - ); + + + {i18n.translate('xpack.ml.timeSeriesExplorer.singleTimeSeriesAnalysisTitle', { + defaultMessage: 'Single time series analysis of {functionLabel}', + values: { functionLabel: chartDetails.functionLabel }, })} - )} - - {showModelBoundsCheckbox && ( - - + {chartDetails.entityData.entities.length > 0 && '('} + {chartDetails.entityData.entities + .map(entity => { + return `${entity.fieldName}: ${entity.fieldValue}`; + }) + .join(', ')} + {chartDetails.entityData.entities.length > 0 && ')'} + + )} + {chartDetails.entityData.count !== 1 && ( + + {chartDetails.entityData.entities.map((countData, i) => { + return ( + + {i18n.translate( + 'xpack.ml.timeSeriesExplorer.countDataInChartDetailsDescription', + { + defaultMessage: + '{openBrace}{cardinalityValue} distinct {fieldName} {cardinality, plural, one {} other { values}}{closeBrace}', + values: { + openBrace: i === 0 ? '(' : '', + closeBrace: + i === chartDetails.entityData.entities.length - 1 ? ')' : '', + cardinalityValue: + countData.cardinality === 0 + ? allValuesLabel + : countData.cardinality, + cardinality: countData.cardinality, + fieldName: countData.fieldName, + }, + } + )} + {i !== chartDetails.entityData.entities.length - 1 ? ', ' : ''} + + ); + })} + + )} + + {showModelBoundsCheckbox && ( + + + + )} + + {showAnnotationsCheckbox && ( + + + + )} + + {showForecastCheckbox && ( + + + + )} + +
+ +
+ {showAnnotations && focusAnnotationData.length > 0 && ( +
+ + {i18n.translate('xpack.ml.timeSeriesExplorer.annotationsTitle', { + defaultMessage: 'Annotations', })} - checked={showModelBounds} - onChange={this.toggleShowModelBoundsHandler} + + - + +
)} - - {showAnnotationsCheckbox && ( - - + + {i18n.translate('xpack.ml.timeSeriesExplorer.anomaliesTitle', { + defaultMessage: 'Anomalies', + })} + + + + + > + + - )} - - {showForecastCheckbox && ( - - + + > + + - )} - -
- -
- {showAnnotations && focusAnnotationData.length > 0 && ( -
- - {i18n.translate('xpack.ml.timeSeriesExplorer.annotationsTitle', { - defaultMessage: 'Annotations', - })} - - - -
- )} - - - {i18n.translate('xpack.ml.timeSeriesExplorer.anomaliesTitle', { - defaultMessage: 'Anomalies', - })} - - - - - - - - - - - - - - - + + + +
)} - {arePartitioningFieldsProvided && jobs.length > 0 && ( - + {arePartitioningFieldsProvided && jobs.length > 0 && hasResults === true && ( + )}
); diff --git a/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_constants.ts b/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_constants.ts index 29a5facf64c0f..a801a1c5ce6f5 100644 --- a/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_constants.ts +++ b/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_constants.ts @@ -10,13 +10,9 @@ export const APP_STATE_ACTION = { CLEAR: 'CLEAR', - GET_DETECTOR_INDEX: 'GET_DETECTOR_INDEX', SET_DETECTOR_INDEX: 'SET_DETECTOR_INDEX', - GET_ENTITIES: 'GET_ENTITIES', SET_ENTITIES: 'SET_ENTITIES', - GET_FORECAST_ID: 'GET_FORECAST_ID', SET_FORECAST_ID: 'SET_FORECAST_ID', - GET_ZOOM: 'GET_ZOOM', SET_ZOOM: 'SET_ZOOM', UNSET_ZOOM: 'UNSET_ZOOM', }; diff --git a/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/timeseriesexplorer_utils.d.ts b/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/timeseriesexplorer_utils.d.ts index 1528ac887ad76..1b7a740d90dde 100644 --- a/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/timeseriesexplorer_utils.d.ts +++ b/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/timeseriesexplorer_utils.d.ts @@ -46,7 +46,7 @@ export function calculateDefaultFocusRange( export function calculateInitialFocusRange( zoomState: any, contextAggregationInterval: any, - timefilter: any + bounds: any ): any; export function getAutoZoomDuration(jobs: any, selectedJob: any): any; diff --git a/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/timeseriesexplorer_utils.js b/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/timeseriesexplorer_utils.js index 8e8b31ede86a8..b4706e6f609dc 100644 --- a/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/timeseriesexplorer_utils.js +++ b/x-pack/legacy/plugins/ml/public/application/timeseriesexplorer/timeseriesexplorer_utils/timeseriesexplorer_utils.js @@ -340,14 +340,13 @@ export function calculateDefaultFocusRange( return [new Date(rangeEarliestMs), new Date(rangeLatestMs)]; } -export function calculateInitialFocusRange(zoomState, contextAggregationInterval, timefilter) { +export function calculateInitialFocusRange(zoomState, contextAggregationInterval, bounds) { if (zoomState !== undefined) { // Check that the zoom times are valid. // zoomFrom must be at or after context chart search bounds earliest, // zoomTo must be at or before context chart search bounds latest. const zoomFrom = moment(zoomState.from, 'YYYY-MM-DDTHH:mm:ss.SSSZ', true); const zoomTo = moment(zoomState.to, 'YYYY-MM-DDTHH:mm:ss.SSSZ', true); - const bounds = timefilter.getActiveBounds(); const searchBounds = getBoundsRoundedToInterval(bounds, contextAggregationInterval, true); const earliest = searchBounds.min; const latest = searchBounds.max; diff --git a/x-pack/legacy/plugins/ml/public/application/util/__snapshots__/observable_utils.test.tsx.snap b/x-pack/legacy/plugins/ml/public/application/util/__snapshots__/observable_utils.test.tsx.snap deleted file mode 100644 index b93a4702b7c3d..0000000000000 --- a/x-pack/legacy/plugins/ml/public/application/util/__snapshots__/observable_utils.test.tsx.snap +++ /dev/null @@ -1,13 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`observable_utils injectObservablesAsProps() 1`] = ` - -`; - -exports[`observable_utils injectObservablesAsProps() 2`] = ` - -`; diff --git a/x-pack/legacy/plugins/ml/public/application/util/__tests__/app_state_utils.js b/x-pack/legacy/plugins/ml/public/application/util/__tests__/app_state_utils.js deleted file mode 100644 index 2ab428f979f53..0000000000000 --- a/x-pack/legacy/plugins/ml/public/application/util/__tests__/app_state_utils.js +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import ngMock from 'ng_mock'; - -import { BehaviorSubject } from 'rxjs'; - -import { initializeAppState, subscribeAppStateToObservable } from '../app_state_utils'; - -describe('ML - initializeAppState', () => { - let AppState; - - beforeEach( - ngMock.module('kibana', stateManagementConfigProvider => { - stateManagementConfigProvider.enable(); - }) - ); - - beforeEach( - ngMock.inject($injector => { - AppState = $injector.get('AppState'); - }) - ); - - it('Throws an error when called without arguments.', () => { - expect(() => initializeAppState()).to.throwError(); - }); - - it('Initializes an appstate, gets a test value.', () => { - const appState = initializeAppState(AppState, 'mlTest', { value: 10 }); - expect(appState.mlTest.value).to.be(10); - }); -}); - -describe('ML - subscribeAppStateToObservable', () => { - let AppState; - let $rootScope; - - beforeEach( - ngMock.module('kibana', stateManagementConfigProvider => { - stateManagementConfigProvider.enable(); - }) - ); - - beforeEach( - ngMock.inject($injector => { - AppState = $injector.get('AppState'); - $rootScope = $injector.get('$rootScope'); - }) - ); - - it('Initializes a custom state store, sets and gets a test value using events.', done => { - const o$ = new BehaviorSubject({ value: 10 }); - - subscribeAppStateToObservable(AppState, 'mlTest', o$, () => $rootScope.$applyAsync()); - - o$.subscribe(payload => { - const appState = new AppState(); - appState.fetch(); - - expect(payload.value).to.be(10); - expect(appState.mlTest.value).to.be(10); - - done(); - }); - }); -}); diff --git a/x-pack/legacy/plugins/ml/public/application/util/app_state_utils.d.ts b/x-pack/legacy/plugins/ml/public/application/util/app_state_utils.d.ts deleted file mode 100644 index 454ea55210dcc..0000000000000 --- a/x-pack/legacy/plugins/ml/public/application/util/app_state_utils.d.ts +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { Observable } from 'rxjs'; - -export const initializeAppState: (AppState: any, stateName: any, defaultState: any) => any; - -export const subscribeAppStateToObservable: ( - AppState: any, - appStateName: string, - o$: Observable, - callback: (payload: any) => void -) => any; diff --git a/x-pack/legacy/plugins/ml/public/application/util/app_state_utils.js b/x-pack/legacy/plugins/ml/public/application/util/app_state_utils.js deleted file mode 100644 index 2875a6fa3ce19..0000000000000 --- a/x-pack/legacy/plugins/ml/public/application/util/app_state_utils.js +++ /dev/null @@ -1,70 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { cloneDeep, isEqual } from 'lodash'; - -import { distinctUntilChanged } from 'rxjs/operators'; - -function hasEqualKeys(a, b) { - return isEqual(Object.keys(a).sort(), Object.keys(b).sort()); -} - -export function initializeAppState(AppState, stateName, defaultState) { - const appState = new AppState(); - appState.fetch(); - - // Store the state to the AppState so that it's - // restored on page refresh. - if (appState[stateName] === undefined) { - appState[stateName] = cloneDeep(defaultState); - appState.save(); - } - - // if defaultState isn't defined or if defaultState matches the current value - // stored in the URL in appState then return appState as is. - if (defaultState === undefined || appState[stateName] === defaultState) { - return appState; - } - - // If defaultState is defined, check if the keys of the defaultState - // match the one from appState, if not, fall back to the defaultState. - // If we didn't do this, the structure of an out-of-date appState - // might break some follow up code. Note that this will not catch any - // deeper nested inconsistencies. this does two checks: - // - if defaultState is an object, check if current appState has the same keys. - // - if it's not an object, check if defaultState and current appState are of the same type. - if ( - (typeof defaultState === 'object' && !hasEqualKeys(defaultState, appState[stateName])) || - typeof defaultState !== typeof appState[stateName] - ) { - appState[stateName] = cloneDeep(defaultState); - appState.save(); - } - - return appState; -} - -// Some components like the show-chart-checkbox or severity/interval-dropdowns -// emit their state change to an observable. This utility function can be used -// to persist these state changes to AppState and save the state to the url. -// distinctUntilChanged() makes sure the callback is only triggered upon changes -// of the state and filters consecutive triggers of the same value. -export function subscribeAppStateToObservable(AppState, appStateName, o$, callback) { - const appState = initializeAppState(AppState, appStateName, o$.getValue()); - - o$.next(appState[appStateName]); - - const subscription = o$.pipe(distinctUntilChanged()).subscribe(payload => { - appState.fetch(); - appState[appStateName] = payload; - appState.save(); - if (typeof callback === 'function') { - callback(payload); - } - }); - - return subscription; -} diff --git a/x-pack/legacy/plugins/ml/public/application/util/custom_url_utils.test.ts b/x-pack/legacy/plugins/ml/public/application/util/custom_url_utils.test.ts index a7afee237dba9..8cdaa192fcbc9 100644 --- a/x-pack/legacy/plugins/ml/public/application/util/custom_url_utils.test.ts +++ b/x-pack/legacy/plugins/ml/public/application/util/custom_url_utils.test.ts @@ -345,6 +345,98 @@ describe('ML - custom URL utils', () => { ); }); + test('returns expected URL for APM', () => { + const urlConfig = { + url_name: 'APM', + time_range: '2h', + url_value: + 'apm#/traces?rangeFrom=$earliest$&rangeTo=$latest$&kuery=trace.id:"$trace.id$" and transaction.name:"$transaction.name$"&_g=()', + }; + + const testRecords = { + job_id: 'abnormal_trace_durations_nodejs', + result_type: 'record', + probability: 0.025597710862701226, + multi_bucket_impact: 5, + record_score: 13.124152090331723, + initial_record_score: 13.124152090331723, + bucket_span: 900, + detector_index: 0, + is_interim: false, + timestamp: 1573339500000, + by_field_name: 'transaction.name', + by_field_value: 'GET /test-data', + function: 'high_mean', + function_description: 'mean', + typical: [802.0600710562369], + actual: [761.1531339031332], + field_name: 'transaction.duration.us', + influencers: [ + { + influencer_field_name: 'transaction.name', + influencer_field_values: ['GET /test-data'], + }, + { + influencer_field_name: 'trace.id', + influencer_field_values: [ + '000a09d58a428f38550e7e87637733c1', + '0039c771d8bbadf6137767d3aeb89f96', + '01279ed5bb9f4249e3822d16dec7f2f2', + ], + }, + { + influencer_field_name: 'service.name', + influencer_field_values: ['example-service'], + }, + ], + 'trace.id': [ + '000a09d58a428f38550e7e87637733c1', + '0039c771d8bbadf6137767d3aeb89f96', + '01279ed5bb9f4249e3822d16dec7f2f2', + ], + 'service.name': ['example-service'], + 'transaction.name': ['GET /test-data'], + earliest: '2019-11-09T20:45:00.000Z', + latest: '2019-11-10T01:00:00.000Z', + }; + + expect(getUrlForRecord(urlConfig, testRecords)).toBe( + 'apm#/traces?rangeFrom=2019-11-09T20:45:00.000Z&rangeTo=2019-11-10T01:00:00.000Z&kuery=(trace.id:"000a09d58a428f38550e7e87637733c1" OR trace.id:"0039c771d8bbadf6137767d3aeb89f96" OR trace.id:"01279ed5bb9f4249e3822d16dec7f2f2") AND transaction.name:"GET%20%2Ftest-data"&_g=()' + ); + }); + + test('removes an empty path component with a trailing slash', () => { + const urlConfig = { + url_name: 'APM', + time_range: '2h', + url_value: + 'apm#/services/$service.name$/transactions?rangeFrom=$earliest$&rangeTo=$latest$&refreshPaused=true&refreshInterval=0&kuery=&transactionType=request', + }; + + const testRecords = { + job_id: 'decreased_throughput_jsbase', + result_type: 'record', + probability: 8.91350850732573e-9, + multi_bucket_impact: 5, + record_score: 93.63625728951217, + initial_record_score: 93.63625728951217, + bucket_span: 900, + detector_index: 0, + is_interim: false, + timestamp: 1573266600000, + function: 'low_count', + function_description: 'count', + typical: [100615.66506877479], + actual: [25251], + earliest: '2019-11-09T00:30:00.000Z', + latest: '2019-11-09T04:45:00.000Z', + }; + + expect(getUrlForRecord(urlConfig, testRecords)).toBe( + 'apm#/services/transactions?rangeFrom=2019-11-09T00:30:00.000Z&rangeTo=2019-11-09T04:45:00.000Z&refreshPaused=true&refreshInterval=0&kuery=&transactionType=request' + ); + }); + test('returns expected URL for other type URL', () => { expect(getUrlForRecord(TEST_OTHER_URL, TEST_RECORD)).toBe( 'http://airlinecodes.info/airline-code-AAL' diff --git a/x-pack/legacy/plugins/ml/public/application/util/custom_url_utils.ts b/x-pack/legacy/plugins/ml/public/application/util/custom_url_utils.ts index e2f2dc0ad0fe8..7774f6dec0c95 100644 --- a/x-pack/legacy/plugins/ml/public/application/util/custom_url_utils.ts +++ b/x-pack/legacy/plugins/ml/public/application/util/custom_url_utils.ts @@ -97,7 +97,11 @@ export function openCustomUrlWindow(fullUrl: string, urlConfig: UrlConfig) { // a Kibana Discover or Dashboard page running on the same server as this ML plugin. function isKibanaUrl(urlConfig: UrlConfig) { const urlValue = urlConfig.url_value; - return urlValue.startsWith('kibana#/discover') || urlValue.startsWith('kibana#/dashboard'); + return ( + urlValue.startsWith('kibana#/discover') || + urlValue.startsWith('kibana#/dashboard') || + urlValue.startsWith('apm#/') + ); } /** @@ -136,13 +140,14 @@ function buildKibanaUrl(urlConfig: UrlConfig, record: CustomUrlAnomalyRecordDoc) commonEscapeCallback ); - return str.replace(/\$([^?&$\'"]+)\$/g, (match, name: string) => { + // Looking for a $token$ with an optional trailing slash + return str.replace(/\$([^?&$\'"]+)\$(\/)?/g, (match, name: string, slash: string = '') => { // Use lodash get to allow nested JSON fields to be retrieved. let tokenValue: string | string[] | undefined = get(record, name); tokenValue = Array.isArray(tokenValue) ? tokenValue[0] : tokenValue; - // If property not found string is not replaced. - return tokenValue === undefined ? match : getResultTokenValue(tokenValue); + // If property not found token is replaced with an empty string. + return tokenValue === undefined ? '' : getResultTokenValue(tokenValue) + slash; }); }; @@ -155,7 +160,7 @@ function buildKibanaUrl(urlConfig: UrlConfig, record: CustomUrlAnomalyRecordDoc) commonEscapeCallback ); return str.replace( - /(.+query:')([^']*)('.+)/, + /(.+query:'|.+&kuery=)([^']*)(['&].+)/, (fullMatch, prefix: string, queryString: string, postfix: string) => { const [resultPrefix, resultPostfix] = [prefix, postfix].map(replaceSingleTokenValues); @@ -170,28 +175,39 @@ function buildKibanaUrl(urlConfig: UrlConfig, record: CustomUrlAnomalyRecordDoc) const queryParts: string[] = []; const joinOperator = ' AND '; - for (let i = 0; i < queryFields.length; i++) { + fieldsLoop: for (let i = 0; i < queryFields.length; i++) { const field = queryFields[i]; // Use lodash get to allow nested JSON fields to be retrieved. - const tokenValues: string[] | string | null = get(record, field) || null; + let tokenValues: string[] | string | null = get(record, field) || null; if (tokenValues === null) { continue; } + tokenValues = Array.isArray(tokenValues) ? tokenValues : [tokenValues]; + // Create a pair `influencerField:value`. // In cases where there are multiple influencer field values for an anomaly // combine values with OR operator e.g. `(influencerField:value or influencerField:another_value)`. - let result = (Array.isArray(tokenValues) ? tokenValues : [tokenValues]) - .map(value => `${field}:"${getResultTokenValue(value)}"`) - .join(' OR '); - result = tokenValues.length > 1 ? `(${result})` : result; - - // Build up a URL string which is not longer than the allowed length and isn't corrupted by invalid query. - availableCharactersLeft -= result.length - (i === 0 ? 0 : joinOperator.length); - - if (availableCharactersLeft <= 0) { - break; - } else { - queryParts.push(result); + let result = ''; + for (let j = 0; j < tokenValues.length; j++) { + const part = `${j > 0 ? ' OR ' : ''}${field}:"${getResultTokenValue( + tokenValues[j] + )}"`; + + // Build up a URL string which is not longer than the allowed length and isn't corrupted by invalid query. + if (availableCharactersLeft < part.length) { + if (result.length > 0) { + queryParts.push(j > 0 ? `(${result})` : result); + } + break fieldsLoop; + } + + result += part; + + availableCharactersLeft -= result.length; + } + + if (result.length > 0) { + queryParts.push(tokenValues.length > 1 ? `(${result})` : result); } } diff --git a/x-pack/legacy/plugins/ml/public/application/util/field_types_utils.test.ts b/x-pack/legacy/plugins/ml/public/application/util/field_types_utils.test.ts index 2abb8097598d2..14150edb977bd 100644 --- a/x-pack/legacy/plugins/ml/public/application/util/field_types_utils.test.ts +++ b/x-pack/legacy/plugins/ml/public/application/util/field_types_utils.test.ts @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { FieldType } from 'ui/index_patterns'; -import { KBN_FIELD_TYPES } from '../../../../../../../src/plugins/data/public'; +import { IFieldType, KBN_FIELD_TYPES } from '../../../../../../../src/plugins/data/public'; import { ML_JOB_FIELD_TYPES } from '../../../common/constants/field_types'; import { kbnTypeToMLJobType, @@ -16,7 +15,7 @@ import { describe('ML - field type utils', () => { describe('kbnTypeToMLJobType', () => { test('returns correct ML_JOB_FIELD_TYPES for KBN_FIELD_TYPES', () => { - const field: FieldType = { + const field: IFieldType = { type: KBN_FIELD_TYPES.NUMBER, name: KBN_FIELD_TYPES.NUMBER, aggregatable: true, @@ -37,7 +36,7 @@ describe('ML - field type utils', () => { }); test('returns ML_JOB_FIELD_TYPES.KEYWORD for aggregatable KBN_FIELD_TYPES.STRING', () => { - const field: FieldType = { + const field: IFieldType = { type: KBN_FIELD_TYPES.STRING, name: KBN_FIELD_TYPES.STRING, aggregatable: true, @@ -46,7 +45,7 @@ describe('ML - field type utils', () => { }); test('returns ML_JOB_FIELD_TYPES.TEXT for non-aggregatable KBN_FIELD_TYPES.STRING', () => { - const field: FieldType = { + const field: IFieldType = { type: KBN_FIELD_TYPES.STRING, name: KBN_FIELD_TYPES.STRING, aggregatable: false, @@ -55,7 +54,7 @@ describe('ML - field type utils', () => { }); test('returns undefined for non-aggregatable "foo"', () => { - const field: FieldType = { + const field: IFieldType = { type: 'foo', name: 'foo', aggregatable: false, diff --git a/x-pack/legacy/plugins/ml/public/application/util/field_types_utils.ts b/x-pack/legacy/plugins/ml/public/application/util/field_types_utils.ts index e2b876aa8dbcd..e8fe2ffb1fed9 100644 --- a/x-pack/legacy/plugins/ml/public/application/util/field_types_utils.ts +++ b/x-pack/legacy/plugins/ml/public/application/util/field_types_utils.ts @@ -5,15 +5,14 @@ */ import { i18n } from '@kbn/i18n'; -import { FieldType } from 'ui/index_patterns'; import { ML_JOB_FIELD_TYPES } from '../../../common/constants/field_types'; -import { KBN_FIELD_TYPES } from '../../../../../../../src/plugins/data/public'; +import { IFieldType, KBN_FIELD_TYPES } from '../../../../../../../src/plugins/data/public'; // convert kibana types to ML Job types // this is needed because kibana types only have string and not text and keyword. // and we can't use ES_FIELD_TYPES because it has no NUMBER type -export function kbnTypeToMLJobType(field: FieldType) { +export function kbnTypeToMLJobType(field: IFieldType) { // Return undefined if not one of the supported data visualizer field types. let type; switch (field.type) { diff --git a/x-pack/legacy/plugins/ml/public/application/util/index_utils.ts b/x-pack/legacy/plugins/ml/public/application/util/index_utils.ts index 2b8838c04cf69..2e176b0044314 100644 --- a/x-pack/legacy/plugins/ml/public/application/util/index_utils.ts +++ b/x-pack/legacy/plugins/ml/public/application/util/index_utils.ts @@ -8,7 +8,11 @@ import { toastNotifications } from 'ui/notify'; import { i18n } from '@kbn/i18n'; import chrome from 'ui/chrome'; import { Query } from 'src/plugins/data/public'; -import { IndexPattern, IndexPatternsContract } from '../../../../../../../src/plugins/data/public'; +import { + IndexPattern, + IIndexPattern, + IndexPatternsContract, +} from '../../../../../../../src/plugins/data/public'; import { IndexPatternSavedObject, SavedSearchSavedObject } from '../../../common/types/kibana'; let indexPatternCache: IndexPatternSavedObject[] = []; @@ -71,7 +75,7 @@ export function getIndexPatternIdFromName(name: string) { } export async function getIndexPatternAndSavedSearch(savedSearchId: string) { - const resp: { savedSearch: SavedSearchSavedObject | null; indexPattern: IndexPattern | null } = { + const resp: { savedSearch: SavedSearchSavedObject | null; indexPattern: IIndexPattern | null } = { savedSearch: null, indexPattern: null, }; diff --git a/x-pack/legacy/plugins/ml/public/application/util/observable_utils.test.tsx b/x-pack/legacy/plugins/ml/public/application/util/observable_utils.test.tsx deleted file mode 100644 index c95824fc5dc4d..0000000000000 --- a/x-pack/legacy/plugins/ml/public/application/util/observable_utils.test.tsx +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { shallow } from 'enzyme'; -import React, { ComponentType } from 'react'; -import { BehaviorSubject } from 'rxjs'; - -import { injectObservablesAsProps } from './observable_utils'; - -interface Props { - testProp: string; -} - -describe('observable_utils', () => { - test('injectObservablesAsProps()', () => { - // an observable that allows us to trigger updating some text. - const observable$ = new BehaviorSubject('initial text'); - - // a simple stateless component that just renders some text - const TestComponent: React.FC = ({ testProp }) => { - return {testProp}; - }; - - // injectObservablesAsProps wraps the observable in a new component - const ObservableComponent = injectObservablesAsProps( - { testProp: observable$ }, - (TestComponent as any) as ComponentType - ); - - const wrapper = shallow(); - - // the component should render with "initial text" - expect(wrapper).toMatchSnapshot(); - - observable$.next('updated text'); - - // the component should render with "updated text" - expect(wrapper).toMatchSnapshot(); - }); -}); diff --git a/x-pack/legacy/plugins/ml/public/application/util/observable_utils.tsx b/x-pack/legacy/plugins/ml/public/application/util/observable_utils.tsx deleted file mode 100644 index 4b8027260ab9a..0000000000000 --- a/x-pack/legacy/plugins/ml/public/application/util/observable_utils.tsx +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { isEqual } from 'lodash'; -import React, { Component, ComponentType } from 'react'; -import { BehaviorSubject, Subscription } from 'rxjs'; -import { distinctUntilChanged } from 'rxjs/operators'; -import { Dictionary } from '../../../common/types/common'; - -// Sets up a ObservableComponent which subscribes to given observable updates and -// and passes them on as prop values to the given WrappedComponent. -// This give us the benefit of abstracting away the need to set up subscribers and callbacks, -// and the passed down props can be used in pure/functional components without -// the need for their own state management. -export function injectObservablesAsProps( - observables: Dictionary>, - WrappedComponent: ComponentType -): ComponentType { - const observableKeys = Object.keys(observables); - - class ObservableComponent extends Component { - public state = observableKeys.reduce((reducedState: Dictionary, key: string) => { - reducedState[key] = observables[key].value; - return reducedState; - }, {}); - - public subscriptions = {} as Dictionary; - - public componentDidMount() { - observableKeys.forEach(k => { - this.subscriptions[k] = observables[k] - .pipe(distinctUntilChanged(isEqual)) - .subscribe(v => this.setState({ [k]: v })); - }); - } - - public componentWillUnmount() { - Object.keys(this.subscriptions).forEach((key: string) => - this.subscriptions[key].unsubscribe() - ); - } - - public render() { - // All injected observables are expected to provide initial state. - // If an observable has undefined as its current value, rendering - // the wrapped component will be skipped. - if ( - Object.keys(this.state) - .map(k => this.state[k]) - .some(v => v === undefined) - ) { - return null; - } - - return ( - - {this.props.children} - - ); - } - } - - return ObservableComponent as ComponentType; -} diff --git a/x-pack/legacy/plugins/ml/public/application/util/url_state.ts b/x-pack/legacy/plugins/ml/public/application/util/url_state.ts new file mode 100644 index 0000000000000..4402155815a5b --- /dev/null +++ b/x-pack/legacy/plugins/ml/public/application/util/url_state.ts @@ -0,0 +1,91 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { useCallback } from 'react'; +import { isEqual } from 'lodash'; +// @ts-ignore +import queryString from 'query-string'; +import { decode, encode } from 'rison-node'; +import { useHistory, useLocation } from 'react-router-dom'; + +import { Dictionary } from '../../../common/types/common'; + +import { getNestedProperty } from './object_utils'; + +export type SetUrlState = (attribute: string | Dictionary, value?: any) => void; +export type UrlState = [Dictionary, SetUrlState]; + +function getUrlState(search: string) { + const urlState: Dictionary = {}; + const parsedQueryString = queryString.parse(search); + + try { + Object.keys(parsedQueryString).forEach(a => { + urlState[a] = decode(parsedQueryString[a]) as Dictionary; + }); + } catch (error) { + // eslint-disable-next-line no-console + console.error('Could not read url state', error); + } + + return urlState; +} + +// Compared to the original appState/globalState, +// this no longer makes use of fetch/save methods. +// - Reading from `location.search` is the successor of `fetch`. +// - `history.push()` is the successor of `save`. +// - The exposed state and set call make use of the above and make sure that +// different urlStates(e.g. `_a` / `_g`) don't overwrite each other. +export const useUrlState = (accessor: string): UrlState => { + const history = useHistory(); + const { search } = useLocation(); + + const setUrlState = useCallback( + (attribute: string | Dictionary, value?: any) => { + const urlState = getUrlState(search); + const parsedQueryString = queryString.parse(search); + + if (!Object.prototype.hasOwnProperty.call(urlState, accessor)) { + urlState[accessor] = {}; + } + + if (typeof attribute === 'string') { + if (isEqual(getNestedProperty(urlState, `${accessor}.${attribute}`), value)) { + return; + } + + urlState[accessor][attribute] = value; + } else { + const attributes = attribute; + Object.keys(attributes).forEach(a => { + urlState[accessor][a] = attributes[a]; + }); + } + + try { + const oldLocationSearch = queryString.stringify(parsedQueryString, { encode: false }); + + Object.keys(urlState).forEach(a => { + parsedQueryString[a] = encode(urlState[a]); + }); + const newLocationSearch = queryString.stringify(parsedQueryString, { encode: false }); + + if (oldLocationSearch !== newLocationSearch) { + history.push({ + search: queryString.stringify(parsedQueryString), + }); + } + } catch (error) { + // eslint-disable-next-line no-console + console.error('Could not save url state', error); + } + }, + [search] + ); + + return [getUrlState(search)[accessor], setUrlState]; +}; diff --git a/x-pack/legacy/plugins/ml/server/client/elasticsearch_ml.js b/x-pack/legacy/plugins/ml/server/client/elasticsearch_ml.js index 5d38de4a6ba87..cf13d329182ba 100644 --- a/x-pack/legacy/plugins/ml/server/client/elasticsearch_ml.js +++ b/x-pack/legacy/plugins/ml/server/client/elasticsearch_ml.js @@ -168,7 +168,7 @@ export const elasticsearchJsPlugin = (Client, config, components) => { method: 'POST', }); - ml.estimateDataFrameAnalyticsMemoryUsage = ca({ + ml.explainDataFrameAnalytics = ca({ urls: [ { fmt: '/_ml/data_frame/analytics/_explain', @@ -753,4 +753,29 @@ export const elasticsearchJsPlugin = (Client, config, components) => { ], method: 'GET', }); + + ml.categories = ca({ + urls: [ + { + fmt: '/_xpack/ml/anomaly_detectors/<%=jobId%>/results/categories/<%=categoryId%>', + req: { + jobId: { + type: 'string', + }, + categoryId: { + type: 'string', + }, + }, + }, + { + fmt: '/_xpack/ml/anomaly_detectors/<%=jobId%>/results/categories', + req: { + jobId: { + type: 'string', + }, + }, + }, + ], + method: 'GET', + }); }; diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/__tests__/data_recognizer.js b/x-pack/legacy/plugins/ml/server/models/data_recognizer/__tests__/data_recognizer.js index cb268ffede7fa..9c5048daeee3f 100644 --- a/x-pack/legacy/plugins/ml/server/models/data_recognizer/__tests__/data_recognizer.js +++ b/x-pack/legacy/plugins/ml/server/models/data_recognizer/__tests__/data_recognizer.js @@ -12,6 +12,8 @@ describe('ML - data recognizer', () => { const moduleIds = [ 'apache_ecs', + 'apm_jsbase', + 'apm_nodejs', 'apm_transaction', 'auditbeat_process_docker_ecs', 'auditbeat_process_hosts_ecs', diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/logo.json b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/logo.json new file mode 100644 index 0000000000000..3905c809fbd7a --- /dev/null +++ b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/logo.json @@ -0,0 +1,3 @@ +{ + "icon": "apmApp" +} diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/manifest.json b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/manifest.json new file mode 100644 index 0000000000000..e463b34be0fc2 --- /dev/null +++ b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/manifest.json @@ -0,0 +1,53 @@ +{ + "id": "apm_jsbase", + "title": "APM: RUM Javascript", + "description": "Detect problematic spans and identify user agents that are potentially causing issues.", + "type": "APM data", + "logoFile": "logo.json", + "defaultIndexPattern": "apm-*", + "query": { + "bool": { + "filter": [{ "term": { "agent.name": "js-base" } }] + } + }, + "jobs": [ + { + "id": "abnormal_span_durations_jsbase", + "file": "abnormal_span_durations_jsbase.json" + }, + { + "id": "anomalous_error_rate_for_user_agents_jsbase", + "file": "anomalous_error_rate_for_user_agents_jsbase.json" + }, + { + "id": "decreased_throughput_jsbase", + "file": "decreased_throughput_jsbase.json" + }, + { + "id": "high_count_by_user_agent_jsbase", + "file": "high_count_by_user_agent_jsbase.json" + } + ], + "datafeeds": [ + { + "id": "datafeed-abnormal_span_durations_jsbase", + "file": "datafeed_abnormal_span_durations_jsbase.json", + "job_id": "abnormal_span_durations_jsbase" + }, + { + "id": "datafeed-anomalous_error_rate_for_user_agents_jsbase", + "file": "datafeed_anomalous_error_rate_for_user_agents_jsbase.json", + "job_id": "anomalous_error_rate_for_user_agents_jsbase" + }, + { + "id": "datafeed-decreased_throughput_jsbase", + "file": "datafeed_decreased_throughput_jsbase.json", + "job_id": "decreased_throughput_jsbase" + }, + { + "id": "datafeed-high_count_by_user_agent_jsbase", + "file": "datafeed_high_count_by_user_agent_jsbase.json", + "job_id": "high_count_by_user_agent_jsbase" + } + ] +} diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/abnormal_span_durations_jsbase.json b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/abnormal_span_durations_jsbase.json new file mode 100644 index 0000000000000..e0b51a4dcd05e --- /dev/null +++ b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/abnormal_span_durations_jsbase.json @@ -0,0 +1,41 @@ +{ + "job_type": "anomaly_detector", + "groups": [ + "apm" + ], + "description": "APM JSBase: Looks for spans that are taking longer than usual to process.", + "analysis_config": { + "bucket_span": "15m", + "detectors": [ + { + "detector_description": "increased span duration", + "function": "high_mean", + "field_name": "span.duration.us", + "partition_field_name": "span.type" + } + ], + "influencers": [ + "span.type", + "trace.id", + "span.name", + "service.name" + ] + }, + "allow_lazy_open": true, + "analysis_limits": { + "model_memory_limit": "128mb" + }, + "data_description": { + "time_field": "@timestamp" + }, + "custom_settings": { + "created_by": "ml-module-apm-jsbase", + "custom_urls": [ + { + "url_name": "APM", + "time_range": "2h", + "url_value": "apm#/traces?rangeFrom=$earliest$&rangeTo=$latest$&kuery=trace.id:\"$trace.id$\"&_g=()" + } + ] + } +} diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/anomalous_error_rate_for_user_agents_jsbase.json b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/anomalous_error_rate_for_user_agents_jsbase.json new file mode 100644 index 0000000000000..66fd9858c6885 --- /dev/null +++ b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/anomalous_error_rate_for_user_agents_jsbase.json @@ -0,0 +1,40 @@ +{ + "job_type": "anomaly_detector", + "groups": [ + "apm" + ], + "description": "APM JSBase: Detects user agents that are encountering errors at an above normal rate. This can help detect browser compatibility issues.", + "analysis_config": { + "bucket_span": "15m", + "detectors": [ + { + "detector_description": "high error rate for user agent", + "function": "high_non_zero_count", + "partition_field_name": "user_agent.name" + } + ], + "influencers": [ + "user_agent.name", + "error.exception.message.keyword", + "error.page.url", + "service.name" + ] + }, + "allow_lazy_open": true, + "analysis_limits": { + "model_memory_limit": "32mb" + }, + "data_description": { + "time_field": "@timestamp" + }, + "custom_settings": { + "created_by": "ml-module-apm-jsbase", + "custom_urls": [ + { + "url_name": "APM", + "time_range": "2h", + "url_value": "apm#/services/$service.name$/errors?rangeFrom=$earliest$&rangeTo=$latest$&refreshPaused=true&refreshInterval=0&kuery=user_agent.name:\"$user_agent.name$\"&_g=()" + } + ] + } +} diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/datafeed_abnormal_span_durations_jsbase.json b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/datafeed_abnormal_span_durations_jsbase.json new file mode 100644 index 0000000000000..7ecbe2890b826 --- /dev/null +++ b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/datafeed_abnormal_span_durations_jsbase.json @@ -0,0 +1,15 @@ +{ + "job_id": "JOB_ID", + "indices": [ + "INDEX_PATTERN_NAME" + ], + "max_empty_searches": 10, + "query": { + "bool": { + "must": [ + { "bool": { "filter": { "term": { "agent.name": "js-base" } } } }, + { "bool": { "filter": { "term": { "processor.event": "span" } } } } + ] + } + } +} diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/datafeed_anomalous_error_rate_for_user_agents_jsbase.json b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/datafeed_anomalous_error_rate_for_user_agents_jsbase.json new file mode 100644 index 0000000000000..fbfedcbf47561 --- /dev/null +++ b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/datafeed_anomalous_error_rate_for_user_agents_jsbase.json @@ -0,0 +1,15 @@ +{ + "job_id": "JOB_ID", + "indices": [ + "INDEX_PATTERN_NAME" + ], + "max_empty_searches": 10, + "query": { + "bool": { + "must": [ + { "bool": { "filter": { "term": { "agent.name": "js-base" } } } }, + { "exists": { "field": "user_agent.name" } } + ] + } + } +} diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/datafeed_decreased_throughput_jsbase.json b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/datafeed_decreased_throughput_jsbase.json new file mode 100644 index 0000000000000..48cba1f157815 --- /dev/null +++ b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/datafeed_decreased_throughput_jsbase.json @@ -0,0 +1,27 @@ +{ + "job_id": "JOB_ID", + "indices": [ + "INDEX_PATTERN_NAME" + ], + "max_empty_searches": 10, + "query": { + "bool": { + "filter": { "term": { "agent.name": "js-base" } } + } + }, + "aggregations": { + "buckets": { + "date_histogram": { + "field": "@timestamp", + "fixed_interval": "900000ms" + }, + "aggregations": { + "@timestamp": { + "max": { + "field": "@timestamp" + } + } + } + } + } +} diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/datafeed_high_count_by_user_agent_jsbase.json b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/datafeed_high_count_by_user_agent_jsbase.json new file mode 100644 index 0000000000000..18ca6b1389287 --- /dev/null +++ b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/datafeed_high_count_by_user_agent_jsbase.json @@ -0,0 +1,16 @@ +{ + "job_id": "JOB_ID", + "indices": [ + "INDEX_PATTERN_NAME" + ], + "max_empty_searches": 10, + "query": { + "bool": { + "must": [ + { "bool": { "filter": { "term": { "agent.name": "js-base" } } } }, + { "bool": { "filter": [{ "exists": { "field": "user_agent.name" } }] } }, + { "bool": { "filter": { "term": { "processor.event": "transaction" } } } } + ] + } + } +} diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/decreased_throughput_jsbase.json b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/decreased_throughput_jsbase.json new file mode 100644 index 0000000000000..4bc8757f19dc9 --- /dev/null +++ b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/decreased_throughput_jsbase.json @@ -0,0 +1,37 @@ +{ + "job_type": "anomaly_detector", + "groups": [ + "apm" + ], + "description": "APM JSBase: Identifies periods during which the application is processing fewer requests than normal.", + "analysis_config": { + "summary_count_field_name": "doc_count", + "bucket_span": "15m", + "detectors": [ + { + "detector_description": "low throughput", + "function": "low_count" + } + ], + "influencers": [ + "service.name" + ] + }, + "allow_lazy_open": true, + "analysis_limits": { + "model_memory_limit": "10mb" + }, + "data_description": { + "time_field": "@timestamp" + }, + "custom_settings": { + "created_by": "ml-module-apm-jsbase", + "custom_urls": [ + { + "url_name": "APM", + "time_range": "2h", + "url_value": "apm#/services?rangeFrom=$earliest$&rangeTo=$latest$&refreshPaused=true&refreshInterval=0&kuery=&transactionType=request" + } + ] + } +} diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/high_count_by_user_agent_jsbase.json b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/high_count_by_user_agent_jsbase.json new file mode 100644 index 0000000000000..7e1316359eabb --- /dev/null +++ b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_jsbase/ml/high_count_by_user_agent_jsbase.json @@ -0,0 +1,38 @@ +{ + "job_type": "anomaly_detector", + "groups": [ + "apm" + ], + "description": "APM JSBase: Detects user agents that are making requests at a suspiciously high rate. This is useful in identifying bots.", + "analysis_config": { + "bucket_span": "15m", + "detectors": [ + { + "detector_description": "high request rate for user agent", + "function": "high_non_zero_count", + "partition_field_name": "user_agent.name" + } + ], + "influencers": [ + "user_agent.name", + "service.name" + ] + }, + "allow_lazy_open": true, + "analysis_limits": { + "model_memory_limit": "32mb" + }, + "data_description": { + "time_field": "@timestamp" + }, + "custom_settings": { + "created_by": "ml-module-apm-jsbase", + "custom_urls": [ + { + "url_name": "APM", + "time_range": "2h", + "url_value": "apm#/services/$service.name$/transactions?rangeFrom=$earliest$&rangeTo=$latest$&refreshPaused=true&refreshInterval=0&kuery=user_agent.name:\"$user_agent.name$\"&_g=()" + } + ] + } +} diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/logo.json b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/logo.json new file mode 100644 index 0000000000000..3905c809fbd7a --- /dev/null +++ b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/logo.json @@ -0,0 +1,3 @@ +{ + "icon": "apmApp" +} diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/manifest.json b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/manifest.json new file mode 100644 index 0000000000000..1865a33a1d301 --- /dev/null +++ b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/manifest.json @@ -0,0 +1,42 @@ +{ + "id": "apm_nodejs", + "title": "APM: NodeJS", + "description": "Detect abnormal traces, anomalous spans, and identify periods of decreased throughput.", + "type": "APM data", + "logoFile": "logo.json", + "defaultIndexPattern": "apm-*", + "query": { + "bool": { "filter": [{ "term": { "agent.name": "nodejs" } }] } + }, + "jobs": [ + { + "id": "abnormal_span_durations_nodejs", + "file": "abnormal_span_durations_nodejs.json" + }, + { + "id": "abnormal_trace_durations_nodejs", + "file": "abnormal_trace_durations_nodejs.json" + }, + { + "id": "decreased_throughput_nodejs", + "file": "decreased_throughput_nodejs.json" + } + ], + "datafeeds": [ + { + "id": "datafeed-abnormal_span_durations_nodejs", + "file": "datafeed_abnormal_span_durations_nodejs.json", + "job_id": "abnormal_span_durations_nodejs" + }, + { + "id": "datafeed-abnormal_trace_durations_nodejs", + "file": "datafeed_abnormal_trace_durations_nodejs.json", + "job_id": "abnormal_trace_durations_nodejs" + }, + { + "id": "datafeed-decreased_throughput_nodejs", + "file": "datafeed_decreased_throughput_nodejs.json", + "job_id": "decreased_throughput_nodejs" + } + ] +} diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/ml/abnormal_span_durations_nodejs.json b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/ml/abnormal_span_durations_nodejs.json new file mode 100644 index 0000000000000..1a8318437790e --- /dev/null +++ b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/ml/abnormal_span_durations_nodejs.json @@ -0,0 +1,41 @@ +{ + "job_type": "anomaly_detector", + "groups": [ + "apm" + ], + "description": "APM NodeJS: Looks for spans that are taking longer than usual to process.", + "analysis_config": { + "bucket_span": "15m", + "detectors": [ + { + "detector_description": "increased span duration", + "function": "high_mean", + "field_name": "span.duration.us", + "partition_field_name": "span.type" + } + ], + "influencers": [ + "span.type", + "trace.id", + "span.name", + "service.name" + ] + }, + "allow_lazy_open": true, + "analysis_limits": { + "model_memory_limit": "128mb" + }, + "data_description": { + "time_field": "@timestamp" + }, + "custom_settings": { + "created_by": "ml-module-apm-nodejs", + "custom_urls": [ + { + "url_name": "APM", + "time_range": "2h", + "url_value": "apm#/traces?rangeFrom=$earliest$&rangeTo=$latest$&kuery=trace.id:\"$trace.id$\"&_g=()" + } + ] + } +} diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/ml/abnormal_trace_durations_nodejs.json b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/ml/abnormal_trace_durations_nodejs.json new file mode 100644 index 0000000000000..875b49e895a00 --- /dev/null +++ b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/ml/abnormal_trace_durations_nodejs.json @@ -0,0 +1,40 @@ +{ + "job_type": "anomaly_detector", + "groups": [ + "apm" + ], + "description": "APM NodeJS: Identifies trace transactions that are processing more slowly than usual.", + "analysis_config": { + "bucket_span": "15m", + "detectors": [ + { + "detector_description": "increased trace duration", + "function": "high_mean", + "field_name": "transaction.duration.us", + "by_field_name": "transaction.name" + } + ], + "influencers": [ + "transaction.name", + "trace.id", + "service.name" + ] + }, + "allow_lazy_open": true, + "analysis_limits": { + "model_memory_limit": "256mb" + }, + "data_description": { + "time_field": "@timestamp" + }, + "custom_settings": { + "created_by": "ml-module-apm-nodejs", + "custom_urls": [ + { + "url_name": "APM", + "time_range": "2h", + "url_value": "apm#/traces?rangeFrom=$earliest$&rangeTo=$latest$&kuery=trace.id:\"$trace.id$\" and transaction.name:\"$transaction.name$\"&_g=()" + } + ] + } +} diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/ml/datafeed_abnormal_span_durations_nodejs.json b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/ml/datafeed_abnormal_span_durations_nodejs.json new file mode 100644 index 0000000000000..3e4f4877bd042 --- /dev/null +++ b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/ml/datafeed_abnormal_span_durations_nodejs.json @@ -0,0 +1,15 @@ +{ + "job_id": "JOB_ID", + "indices": [ + "INDEX_PATTERN_NAME" + ], + "max_empty_searches": 10, + "query": { + "bool": { + "must": [ + { "bool": { "filter": { "term": { "agent.name": "nodejs" } } } }, + { "bool": { "filter": { "term": { "processor.event": "span" } } } } + ] + } + } +} diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/ml/datafeed_abnormal_trace_durations_nodejs.json b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/ml/datafeed_abnormal_trace_durations_nodejs.json new file mode 100644 index 0000000000000..d87f809a49940 --- /dev/null +++ b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/ml/datafeed_abnormal_trace_durations_nodejs.json @@ -0,0 +1,13 @@ +{ + "job_id": "JOB_ID", + "indices": [ + "INDEX_PATTERN_NAME" + ], + "max_empty_searches": 10, + "query": { + "bool": { + "must_not": [{ "exists": { "field": "parent.id" } }], + "must": [{ "bool": { "filter": { "term": { "agent.name": "nodejs" } } } }] + } + } +} diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/ml/datafeed_decreased_throughput_nodejs.json b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/ml/datafeed_decreased_throughput_nodejs.json new file mode 100644 index 0000000000000..451957c327dd0 --- /dev/null +++ b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/ml/datafeed_decreased_throughput_nodejs.json @@ -0,0 +1,27 @@ +{ + "job_id": "JOB_ID", + "indices": [ + "INDEX_PATTERN_NAME" + ], + "max_empty_searches": 10, + "query": { + "bool": { + "filter": { "term": { "agent.name": "nodejs" } } + } + }, + "aggregations": { + "buckets": { + "date_histogram": { + "field": "@timestamp", + "fixed_interval": "900000ms" + }, + "aggregations": { + "@timestamp": { + "max": { + "field": "@timestamp" + } + } + } + } + } +} diff --git a/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/ml/decreased_throughput_nodejs.json b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/ml/decreased_throughput_nodejs.json new file mode 100644 index 0000000000000..f63c6289a5cd9 --- /dev/null +++ b/x-pack/legacy/plugins/ml/server/models/data_recognizer/modules/apm_nodejs/ml/decreased_throughput_nodejs.json @@ -0,0 +1,38 @@ +{ + "job_type": "anomaly_detector", + "groups": [ + "apm" + ], + "description": "APM NodeJS: Identifies periods during which the application is processing fewer requests than normal.", + "analysis_config": { + "summary_count_field_name": "doc_count", + "bucket_span": "15m", + "detectors": [ + { + "detector_description": "low throughput", + "function": "low_count" + } + ], + "influencers": [ + "transaction.name", + "service.name" + ] + }, + "allow_lazy_open": true, + "analysis_limits": { + "model_memory_limit": "10mb" + }, + "data_description": { + "time_field": "@timestamp" + }, + "custom_settings": { + "created_by": "ml-module-apm-nodejs", + "custom_urls": [ + { + "url_name": "APM", + "time_range": "2h", + "url_value": "apm#/services?rangeFrom=$earliest$&rangeTo=$latest$&refreshPaused=true&refreshInterval=0&kuery=&transactionType=request" + } + ] + } +} diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/index.js b/x-pack/legacy/plugins/ml/server/models/job_service/index.js index 78d099dad6606..186bcbae84546 100644 --- a/x-pack/legacy/plugins/ml/server/models/job_service/index.js +++ b/x-pack/legacy/plugins/ml/server/models/job_service/index.js @@ -8,7 +8,7 @@ import { datafeedsProvider } from './datafeeds'; import { jobsProvider } from './jobs'; import { groupsProvider } from './groups'; import { newJobCapsProvider } from './new_job_caps'; -import { newJobChartsProvider } from './new_job'; +import { newJobChartsProvider, categorizationExamplesProvider } from './new_job'; export function jobServiceProvider(callWithRequest, request) { return { @@ -17,5 +17,6 @@ export function jobServiceProvider(callWithRequest, request) { ...groupsProvider(callWithRequest), ...newJobCapsProvider(callWithRequest, request), ...newJobChartsProvider(callWithRequest, request), + ...categorizationExamplesProvider(callWithRequest, request), }; } diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/new_job/categorization.ts b/x-pack/legacy/plugins/ml/server/models/job_service/new_job/categorization.ts new file mode 100644 index 0000000000000..34e871a936088 --- /dev/null +++ b/x-pack/legacy/plugins/ml/server/models/job_service/new_job/categorization.ts @@ -0,0 +1,294 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ML_RESULTS_INDEX_PATTERN } from '../../../../common/constants/index_patterns'; +import { CATEGORY_EXAMPLES_MULTIPLIER } from '../../../../common/constants/new_job'; +import { CategoryId, Category, Token } from '../../../../common/types/categories'; +import { callWithRequestType } from '../../../../common/types/kibana'; + +export function categorizationExamplesProvider(callWithRequest: callWithRequestType) { + async function categorizationExamples( + indexPatternTitle: string, + query: any, + size: number, + categorizationFieldName: string, + timeField: string | undefined, + start: number, + end: number, + analyzer?: any + ) { + if (timeField !== undefined) { + const range = { + range: { + [timeField]: { + gte: start, + format: 'epoch_millis', + }, + }, + }; + + if (query.bool === undefined) { + query.bool = {}; + } + if (query.bool.filter === undefined) { + query.bool.filter = range; + } else { + if (Array.isArray(query.bool.filter)) { + query.bool.filter.push(range); + } else { + query.bool.filter.range = range; + } + } + } + + const results = await callWithRequest('search', { + index: indexPatternTitle, + size, + body: { + _source: categorizationFieldName, + query, + }, + }); + const examples: string[] = results.hits?.hits + ?.map((doc: any) => doc._source[categorizationFieldName]) + .filter((example: string | undefined) => example !== undefined); + + let tokens: Token[] = []; + try { + const { tokens: tempTokens } = await callWithRequest('indices.analyze', { + body: { + ...getAnalyzer(analyzer), + text: examples, + }, + }); + tokens = tempTokens; + } catch (error) { + // fail silently, the tokens could not be loaded + // an empty list of tokens will be returned for each example + } + + const lengths = examples.map(e => e.length); + const sumLengths = lengths.map((s => (a: number) => (s += a))(0)); + + const tokensPerExample: Token[][] = examples.map(e => []); + + tokens.forEach((t, i) => { + for (let g = 0; g < sumLengths.length; g++) { + if (t.start_offset <= sumLengths[g] + g) { + const offset = g > 0 ? sumLengths[g - 1] + g : 0; + tokensPerExample[g].push({ + ...t, + start_offset: t.start_offset - offset, + end_offset: t.end_offset - offset, + }); + break; + } + } + }); + + return examples.map((e, i) => ({ text: e, tokens: tokensPerExample[i] })); + } + + function getAnalyzer(analyzer: any) { + if (typeof analyzer === 'object' && analyzer.tokenizer !== undefined) { + return analyzer; + } else { + return { analyzer: 'standard' }; + } + } + + async function validateCategoryExamples( + indexPatternTitle: string, + query: any, + size: number, + categorizationFieldName: string, + timeField: string | undefined, + start: number, + end: number, + analyzer?: any + ) { + const examples = await categorizationExamples( + indexPatternTitle, + query, + size * CATEGORY_EXAMPLES_MULTIPLIER, + categorizationFieldName, + timeField, + start, + end, + analyzer + ); + + const sortedExamples = examples + .map((e, i) => ({ ...e, origIndex: i })) + .sort((a, b) => b.tokens.length - a.tokens.length); + const validExamples = sortedExamples.filter(e => e.tokens.length > 1); + + return { + valid: sortedExamples.length === 0 ? 0 : validExamples.length / sortedExamples.length, + examples: sortedExamples + .filter( + (e, i) => + i / CATEGORY_EXAMPLES_MULTIPLIER - Math.floor(i / CATEGORY_EXAMPLES_MULTIPLIER) === 0 + ) + .sort((a, b) => a.origIndex - b.origIndex) + .map(e => ({ text: e.text, tokens: e.tokens })), + }; + } + + async function getTotalCategories(jobId: string): Promise<{ total: number }> { + const totalResp = await callWithRequest('search', { + index: ML_RESULTS_INDEX_PATTERN, + size: 0, + body: { + query: { + bool: { + filter: [ + { + term: { + job_id: jobId, + }, + }, + { + exists: { + field: 'category_id', + }, + }, + ], + }, + }, + }, + }); + return totalResp?.hits?.total?.value ?? 0; + } + + async function getTopCategoryCounts(jobId: string, numberOfCategories: number) { + const top = await callWithRequest('search', { + index: ML_RESULTS_INDEX_PATTERN, + size: 0, + body: { + query: { + bool: { + filter: [ + { + term: { + job_id: jobId, + }, + }, + { + term: { + result_type: 'model_plot', + }, + }, + { + term: { + by_field_name: 'mlcategory', + }, + }, + ], + }, + }, + aggs: { + cat_count: { + terms: { + field: 'by_field_value', + size: numberOfCategories, + }, + }, + }, + }, + }); + + const catCounts: Array<{ + id: CategoryId; + count: number; + }> = top.aggregations?.cat_count?.buckets.map((c: any) => ({ + id: c.key, + count: c.doc_count, + })); + return catCounts || []; + } + + async function getCategories( + jobId: string, + catIds: CategoryId[], + size: number + ): Promise { + const categoryFilter = catIds.length + ? { + terms: { + category_id: catIds, + }, + } + : { + exists: { + field: 'category_id', + }, + }; + const result = await callWithRequest('search', { + index: ML_RESULTS_INDEX_PATTERN, + size, + body: { + query: { + bool: { + filter: [ + { + term: { + job_id: jobId, + }, + }, + categoryFilter, + ], + }, + }, + }, + }); + + return result.hits.hits?.map((c: { _source: Category }) => c._source) || []; + } + + async function topCategories(jobId: string, numberOfCategories: number) { + const catCounts = await getTopCategoryCounts(jobId, numberOfCategories); + const categories = await getCategories( + jobId, + catCounts.map(c => c.id), + catCounts.length || numberOfCategories + ); + + const catsById = categories.reduce((p, c) => { + p[c.category_id] = c; + return p; + }, {} as { [id: number]: Category }); + + const total = await getTotalCategories(jobId); + + if (catCounts.length) { + return { + total, + categories: catCounts.map(({ id, count }) => { + return { + count, + category: catsById[id] ?? null, + }; + }), + }; + } else { + return { + total, + categories: categories.map(category => { + return { + category, + }; + }), + }; + } + } + + return { + categorizationExamples, + validateCategoryExamples, + topCategories, + }; +} diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/new_job/charts.ts b/x-pack/legacy/plugins/ml/server/models/job_service/new_job/charts.ts index a8b6ba494a070..88ae8caa91e4a 100644 --- a/x-pack/legacy/plugins/ml/server/models/job_service/new_job/charts.ts +++ b/x-pack/legacy/plugins/ml/server/models/job_service/new_job/charts.ts @@ -6,7 +6,7 @@ import { newJobLineChartProvider } from './line_chart'; import { newJobPopulationChartProvider } from './population_chart'; -export type callWithRequestType = (action: string, params: any) => Promise; +import { callWithRequestType } from '../../../../common/types/kibana'; export function newJobChartsProvider(callWithRequest: callWithRequestType) { const { newJobLineChart } = newJobLineChartProvider(callWithRequest); diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/new_job/index.ts b/x-pack/legacy/plugins/ml/server/models/job_service/new_job/index.ts index 758b834ed7b3a..da23efa67d0b5 100644 --- a/x-pack/legacy/plugins/ml/server/models/job_service/new_job/index.ts +++ b/x-pack/legacy/plugins/ml/server/models/job_service/new_job/index.ts @@ -5,3 +5,4 @@ */ export { newJobChartsProvider } from './charts'; +export { categorizationExamplesProvider } from './categorization'; diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/new_job/line_chart.ts b/x-pack/legacy/plugins/ml/server/models/job_service/new_job/line_chart.ts index 5bb0f39982146..c1a5ad5e38ecc 100644 --- a/x-pack/legacy/plugins/ml/server/models/job_service/new_job/line_chart.ts +++ b/x-pack/legacy/plugins/ml/server/models/job_service/new_job/line_chart.ts @@ -6,10 +6,9 @@ import { get } from 'lodash'; import { AggFieldNamePair, EVENT_RATE_FIELD_ID } from '../../../../common/types/fields'; +import { callWithRequestType } from '../../../../common/types/kibana'; import { ML_MEDIAN_PERCENTS } from '../../../../common/util/job_utils'; -export type callWithRequestType = (action: string, params: any) => Promise; - type DtrIndex = number; type TimeStamp = number; type Value = number | undefined | null; diff --git a/x-pack/legacy/plugins/ml/server/models/job_service/new_job/population_chart.ts b/x-pack/legacy/plugins/ml/server/models/job_service/new_job/population_chart.ts index 812a135f6cf08..ee35f13c44ee6 100644 --- a/x-pack/legacy/plugins/ml/server/models/job_service/new_job/population_chart.ts +++ b/x-pack/legacy/plugins/ml/server/models/job_service/new_job/population_chart.ts @@ -6,10 +6,9 @@ import { get } from 'lodash'; import { AggFieldNamePair, EVENT_RATE_FIELD_ID } from '../../../../common/types/fields'; +import { callWithRequestType } from '../../../../common/types/kibana'; import { ML_MEDIAN_PERCENTS } from '../../../../common/util/job_utils'; -export type callWithRequestType = (action: string, params: any) => Promise; - const OVER_FIELD_EXAMPLES_COUNT = 40; type DtrIndex = number; diff --git a/x-pack/legacy/plugins/ml/server/models/job_validation/messages.js b/x-pack/legacy/plugins/ml/server/models/job_validation/messages.js index 4cd6dbc787d28..2c0c218bf86b5 100644 --- a/x-pack/legacy/plugins/ml/server/models/job_validation/messages.js +++ b/x-pack/legacy/plugins/ml/server/models/job_validation/messages.js @@ -14,7 +14,7 @@ export const getMessages = () => { return messages; } - const createJobsDocsUrl = `https://www.elastic.co/guide/en/elastic-stack-overview/{{version}}/create-jobs.html`; + const createJobsDocsUrl = `https://www.elastic.co/guide/en/machine-learning/{{version}}/create-jobs.html`; return (messages = { field_not_aggregatable: { @@ -26,7 +26,7 @@ export const getMessages = () => { }, }), url: - 'https://www.elastic.co/guide/en/elastic-stack-overview/{{version}}/ml-configuring-aggregation.html', + 'https://www.elastic.co/guide/en/machine-learning/{{version}}/ml-configuring-aggregation.html', }, fields_not_aggregatable: { status: 'ERROR', @@ -34,7 +34,7 @@ export const getMessages = () => { defaultMessage: 'One of the detector fields is not an aggregatable field.', }), url: - 'https://www.elastic.co/guide/en/elastic-stack-overview/{{version}}/ml-configuring-aggregation.html', + 'https://www.elastic.co/guide/en/machine-learning/{{version}}/ml-configuring-aggregation.html', }, cardinality_by_field: { status: 'WARNING', @@ -112,7 +112,7 @@ export const getMessages = () => { } ), url: - 'https://www.elastic.co/guide/en/elastic-stack-overview/{{version}}/ml-configuring-categories.html', + 'https://www.elastic.co/guide/en/machine-learning/{{version}}/ml-configuring-categories.html', }, categorization_filters_invalid: { status: 'ERROR', diff --git a/x-pack/legacy/plugins/ml/server/models/results_service/get_partition_fields_values.ts b/x-pack/legacy/plugins/ml/server/models/results_service/get_partition_fields_values.ts new file mode 100644 index 0000000000000..00e3002a7fc71 --- /dev/null +++ b/x-pack/legacy/plugins/ml/server/models/results_service/get_partition_fields_values.ts @@ -0,0 +1,161 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import Boom from 'boom'; +import { ML_RESULTS_INDEX_PATTERN } from '../../../common/constants/index_patterns'; +import { callWithRequestType } from '../../../common/types/kibana'; + +interface CriteriaField { + fieldName: string; + fieldValue: any; +} + +const PARTITION_FIELDS = ['partition_field', 'over_field', 'by_field'] as const; + +type PartitionFieldsType = typeof PARTITION_FIELDS[number]; + +type SearchTerm = + | { + [key in PartitionFieldsType]?: string; + } + | undefined; + +/** + * Gets an object for aggregation query to retrieve field name and values. + * @param fieldType - Field type + * @param query - Optional query string for partition value + * @returns {Object} + */ +function getFieldAgg(fieldType: PartitionFieldsType, query?: string) { + const AGG_SIZE = 100; + + const fieldNameKey = `${fieldType}_name`; + const fieldValueKey = `${fieldType}_value`; + + return { + [fieldNameKey]: { + terms: { + field: fieldNameKey, + }, + }, + [fieldValueKey]: { + filter: { + wildcard: { + [fieldValueKey]: { + value: query ? `*${query}*` : '*', + }, + }, + }, + aggs: { + values: { + terms: { + size: AGG_SIZE, + field: fieldValueKey, + }, + }, + }, + }, + }; +} + +/** + * Gets formatted result for particular field from aggregation response. + * @param fieldType - Field type + * @param aggs - Aggregation response + */ +function getFieldObject(fieldType: PartitionFieldsType, aggs: any) { + const fieldNameKey = `${fieldType}_name`; + const fieldValueKey = `${fieldType}_value`; + + return aggs[fieldNameKey].buckets.length > 0 + ? { + [fieldType]: { + name: aggs[fieldNameKey].buckets[0].key, + values: aggs[fieldValueKey].values.buckets.map(({ key }: any) => key), + }, + } + : {}; +} + +export const getPartitionFieldsValuesFactory = (callWithRequest: callWithRequestType) => + /** + * Gets the record of partition fields with possible values that fit the provided queries. + * @param jobId - Job ID + * @param searchTerm - object of queries for partition fields, e.g. { partition_field: 'query' } + * @param criteriaFields - key - value pairs of the term field, e.g. { detector_index: 0 } + * @param earliestMs + * @param latestMs + */ + async function getPartitionFieldsValues( + jobId: string, + searchTerm: SearchTerm = {}, + criteriaFields: CriteriaField[], + earliestMs: number, + latestMs: number + ) { + const jobsResponse = await callWithRequest('ml.jobs', { jobId: [jobId] }); + if (jobsResponse.count === 0 || jobsResponse.jobs === undefined) { + throw Boom.notFound(`Job with the id "${jobId}" not found`); + } + + const job = jobsResponse.jobs[0]; + + const isModelPlotEnabled = job?.model_plot_config?.enabled; + + const resp = await callWithRequest('search', { + index: ML_RESULTS_INDEX_PATTERN, + size: 0, + body: { + query: { + bool: { + filter: [ + ...criteriaFields.map(({ fieldName, fieldValue }) => { + return { + term: { + [fieldName]: fieldValue, + }, + }; + }), + { + term: { + job_id: jobId, + }, + }, + { + range: { + timestamp: { + gte: earliestMs, + lte: latestMs, + format: 'epoch_millis', + }, + }, + }, + { + term: { + result_type: isModelPlotEnabled ? 'model_plot' : 'record', + }, + }, + ], + }, + }, + aggs: { + ...PARTITION_FIELDS.reduce((acc, key) => { + return { + ...acc, + ...getFieldAgg(key, searchTerm[key]), + }; + }, {}), + }, + }, + }); + + return PARTITION_FIELDS.reduce((acc, key) => { + return { + ...acc, + ...getFieldObject(key, resp.aggregations), + }; + }, {}); + }; diff --git a/x-pack/legacy/plugins/ml/server/models/results_service/results_service.js b/x-pack/legacy/plugins/ml/server/models/results_service/results_service.js index c779664978f63..3ee5d04186ff1 100644 --- a/x-pack/legacy/plugins/ml/server/models/results_service/results_service.js +++ b/x-pack/legacy/plugins/ml/server/models/results_service/results_service.js @@ -10,6 +10,7 @@ import moment from 'moment'; import { buildAnomalyTableItems } from './build_anomaly_table_items'; import { ML_RESULTS_INDEX_PATTERN } from '../../../common/constants/index_patterns'; import { ANOMALIES_TABLE_DEFAULT_QUERY_SIZE } from '../../../common/constants/search'; +import { getPartitionFieldsValuesFactory } from './get_partition_fields_values'; // Service for carrying out Elasticsearch queries to obtain data for the // ML Results dashboards. @@ -408,5 +409,6 @@ export function resultsServiceProvider(callWithRequest) { getCategoryExamples, getLatestBucketTimestampByJob, getMaxAnomalyScore, + getPartitionFieldsValues: getPartitionFieldsValuesFactory(callWithRequest), }; } diff --git a/x-pack/legacy/plugins/ml/server/routes/anomaly_detectors.js b/x-pack/legacy/plugins/ml/server/routes/anomaly_detectors.js index 4205027eb5ce2..54d447c288151 100644 --- a/x-pack/legacy/plugins/ml/server/routes/anomaly_detectors.js +++ b/x-pack/legacy/plugins/ml/server/routes/anomaly_detectors.js @@ -181,4 +181,22 @@ export function jobRoutes({ commonRouteConfig, elasticsearchPlugin, route }) { ...commonRouteConfig, }, }); + + route({ + method: 'GET', + path: '/api/ml/anomaly_detectors/{jobId}/results/categories/{categoryId}', + handler(request, reply) { + const callWithRequest = callWithRequestFactory(elasticsearchPlugin, request); + const options = { + jobId: request.params.jobId, + categoryId: request.params.categoryId, + }; + return callWithRequest('ml.categories', options) + .then(resp => reply(resp)) + .catch(resp => reply(wrapError(resp))); + }, + config: { + ...commonRouteConfig, + }, + }); } diff --git a/x-pack/legacy/plugins/ml/server/routes/data_frame_analytics.ts b/x-pack/legacy/plugins/ml/server/routes/data_frame_analytics.ts index 2f8db45d9739e..7b855e5f87cbf 100644 --- a/x-pack/legacy/plugins/ml/server/routes/data_frame_analytics.ts +++ b/x-pack/legacy/plugins/ml/server/routes/data_frame_analytics.ts @@ -164,7 +164,7 @@ export function dataFrameAnalyticsRoutes({ xpackMainPlugin, router }: RouteIniti licensePreRoutingFactory(xpackMainPlugin, async (context, request, response) => { try { const results = await context.ml!.mlClient.callAsCurrentUser( - 'ml.estimateDataFrameAnalyticsMemoryUsage', + 'ml.explainDataFrameAnalytics', { body: request.body, } diff --git a/x-pack/legacy/plugins/ml/server/routes/job_service.js b/x-pack/legacy/plugins/ml/server/routes/job_service.js index 78c57670b7ab3..a83b4fa403f65 100644 --- a/x-pack/legacy/plugins/ml/server/routes/job_service.js +++ b/x-pack/legacy/plugins/ml/server/routes/job_service.js @@ -270,4 +270,50 @@ export function jobServiceRoutes({ commonRouteConfig, elasticsearchPlugin, route ...commonRouteConfig, }, }); + + route({ + method: 'POST', + path: '/api/ml/jobs/categorization_field_examples', + handler(request) { + const callWithRequest = callWithRequestFactory(elasticsearchPlugin, request); + const { validateCategoryExamples } = jobServiceProvider(callWithRequest); + const { + indexPatternTitle, + timeField, + query, + size, + field, + start, + end, + analyzer, + } = request.payload; + return validateCategoryExamples( + indexPatternTitle, + query, + size, + field, + timeField, + start, + end, + analyzer + ).catch(resp => wrapError(resp)); + }, + config: { + ...commonRouteConfig, + }, + }); + + route({ + method: 'POST', + path: '/api/ml/jobs/top_categories', + handler(request) { + const callWithRequest = callWithRequestFactory(elasticsearchPlugin, request); + const { topCategories } = jobServiceProvider(callWithRequest); + const { jobId, count } = request.payload; + return topCategories(jobId, count).catch(resp => wrapError(resp)); + }, + config: { + ...commonRouteConfig, + }, + }); } diff --git a/x-pack/legacy/plugins/ml/server/routes/results_service.js b/x-pack/legacy/plugins/ml/server/routes/results_service.js index 6d456e6a0412e..a658729e85083 100644 --- a/x-pack/legacy/plugins/ml/server/routes/results_service.js +++ b/x-pack/legacy/plugins/ml/server/routes/results_service.js @@ -55,6 +55,12 @@ function getMaxAnomalyScore(callWithRequest, payload) { return rs.getMaxAnomalyScore(jobIds, earliestMs, latestMs); } +function getPartitionFieldsValues(callWithRequest, payload) { + const rs = resultsServiceProvider(callWithRequest); + const { jobId, searchTerm, criteriaFields, earliestMs, latestMs } = payload; + return rs.getPartitionFieldsValues(jobId, searchTerm, criteriaFields, earliestMs, latestMs); +} + export function resultsServiceRoutes({ commonRouteConfig, elasticsearchPlugin, route }) { route({ method: 'POST', @@ -103,4 +109,18 @@ export function resultsServiceRoutes({ commonRouteConfig, elasticsearchPlugin, r ...commonRouteConfig, }, }); + + route({ + method: 'POST', + path: '/api/ml/results/partition_fields_values', + handler(request) { + const callWithRequest = callWithRequestFactory(elasticsearchPlugin, request); + return getPartitionFieldsValues(callWithRequest, request.payload).catch(resp => + wrapError(resp) + ); + }, + config: { + ...commonRouteConfig, + }, + }); } diff --git a/x-pack/legacy/plugins/monitoring/index.js b/x-pack/legacy/plugins/monitoring/index.js index 3d98b11c2045b..8e0201bea710b 100644 --- a/x-pack/legacy/plugins/monitoring/index.js +++ b/x-pack/legacy/plugins/monitoring/index.js @@ -22,7 +22,7 @@ export const monitoring = kibana => id: 'monitoring', configPrefix: 'xpack.monitoring', publicDir: resolve(__dirname, 'public'), - init(server, _options) { + init(server) { const configs = [ 'xpack.monitoring.ui.enabled', 'xpack.monitoring.kibana.collection.enabled', diff --git a/x-pack/legacy/plugins/monitoring/public/components/apm/instance/instance.js b/x-pack/legacy/plugins/monitoring/public/components/apm/instance/instance.js index 47e8bc3b46474..cb7187a8c0753 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/apm/instance/instance.js +++ b/x-pack/legacy/plugins/monitoring/public/components/apm/instance/instance.js @@ -14,8 +14,10 @@ import { EuiPageBody, EuiFlexGroup, EuiPageContent, + EuiScreenReaderOnly, } from '@elastic/eui'; import { Status } from './status'; +import { FormattedMessage } from '@kbn/i18n/react'; export function ApmServerInstance({ summary, metrics, ...props }) { const seriesToShow = [ @@ -45,6 +47,14 @@ export function ApmServerInstance({ summary, metrics, ...props }) { return ( + +

+ +

+
diff --git a/x-pack/legacy/plugins/monitoring/public/components/apm/instances/instances.js b/x-pack/legacy/plugins/monitoring/public/components/apm/instances/instances.js index cd9ee6c9433d0..f3a888bf9e905 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/apm/instances/instances.js +++ b/x-pack/legacy/plugins/monitoring/public/components/apm/instances/instances.js @@ -8,7 +8,14 @@ import React, { Fragment } from 'react'; import moment from 'moment'; import { uniq, get } from 'lodash'; import { EuiMonitoringTable } from '../../table'; -import { EuiLink, EuiPage, EuiPageBody, EuiPageContent, EuiSpacer } from '@elastic/eui'; +import { + EuiLink, + EuiPage, + EuiPageBody, + EuiPageContent, + EuiSpacer, + EuiScreenReaderOnly, +} from '@elastic/eui'; import { Status } from './status'; import { formatMetric } from '../../../lib/format_number'; import { formatTimestampToDuration } from '../../../../common'; @@ -16,6 +23,7 @@ import { i18n } from '@kbn/i18n'; import { APM_SYSTEM_ID } from '../../../../common/constants'; import { ListingCallOut } from '../../setup_mode/listing_callout'; import { SetupModeBadge } from '../../setup_mode/badge'; +import { FormattedMessage } from '@kbn/i18n/react'; function getColumns(setupMode) { return [ @@ -133,6 +141,14 @@ export function ApmServerInstances({ apms, setupMode }) { return ( + +

+ +

+
diff --git a/x-pack/legacy/plugins/monitoring/public/components/apm/overview/index.js b/x-pack/legacy/plugins/monitoring/public/components/apm/overview/index.js index 8ef996d4d725d..c053edc805611 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/apm/overview/index.js +++ b/x-pack/legacy/plugins/monitoring/public/components/apm/overview/index.js @@ -14,8 +14,10 @@ import { EuiPageBody, EuiPanel, EuiPageContent, + EuiScreenReaderOnly, } from '@elastic/eui'; import { Status } from '../instances/status'; +import { FormattedMessage } from '@kbn/i18n/react'; export function ApmOverview({ stats, metrics, ...props }) { const seriesToShow = [ @@ -45,6 +47,14 @@ export function ApmOverview({ stats, metrics, ...props }) { return ( + +

+ +

+
diff --git a/x-pack/legacy/plugins/monitoring/public/components/beats/beat/beat.js b/x-pack/legacy/plugins/monitoring/public/components/beats/beat/beat.js index f3391965be145..3fe211c0f2edc 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/beats/beat/beat.js +++ b/x-pack/legacy/plugins/monitoring/public/components/beats/beat/beat.js @@ -15,9 +15,11 @@ import { EuiSpacer, EuiPageContent, EuiPanel, + EuiScreenReaderOnly, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { SummaryStatus } from '../../summary_status'; +import { FormattedMessage } from '@kbn/i18n/react'; export function Beat({ summary, metrics, ...props }) { const metricsToShow = [ @@ -137,6 +139,11 @@ export function Beat({ summary, metrics, ...props }) { + +

+ +

+
{metricsToShow.map((metric, index) => ( diff --git a/x-pack/legacy/plugins/monitoring/public/components/beats/listing/listing.js b/x-pack/legacy/plugins/monitoring/public/components/beats/listing/listing.js index 66af12af3db8f..dfc9117ef48bc 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/beats/listing/listing.js +++ b/x-pack/legacy/plugins/monitoring/public/components/beats/listing/listing.js @@ -6,7 +6,14 @@ import React, { PureComponent } from 'react'; import { uniq, get } from 'lodash'; -import { EuiPage, EuiPageBody, EuiPageContent, EuiSpacer, EuiLink } from '@elastic/eui'; +import { + EuiPage, + EuiPageBody, + EuiPageContent, + EuiSpacer, + EuiLink, + EuiScreenReaderOnly, +} from '@elastic/eui'; import { Stats } from 'plugins/monitoring/components/beats'; import { formatMetric } from 'plugins/monitoring/lib/format_number'; import { EuiMonitoringTable } from 'plugins/monitoring/components/table'; @@ -14,6 +21,7 @@ import { i18n } from '@kbn/i18n'; import { BEATS_SYSTEM_ID } from '../../../../common/constants'; import { ListingCallOut } from '../../setup_mode/listing_callout'; import { SetupModeBadge } from '../../setup_mode/badge'; +import { FormattedMessage } from '@kbn/i18n/react'; export class Listing extends PureComponent { getColumns() { @@ -139,6 +147,14 @@ export class Listing extends PureComponent { return ( + +

+ +

+
diff --git a/x-pack/legacy/plugins/monitoring/public/components/beats/overview/__snapshots__/overview.test.js.snap b/x-pack/legacy/plugins/monitoring/public/components/beats/overview/__snapshots__/overview.test.js.snap index 8065cb8a3fa30..22d012a2ccca7 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/beats/overview/__snapshots__/overview.test.js.snap +++ b/x-pack/legacy/plugins/monitoring/public/components/beats/overview/__snapshots__/overview.test.js.snap @@ -3,6 +3,15 @@ exports[`Overview that overview page renders normally 1`] = ` + +

+ +

+
-

+

-

+ -

+

-

+ -

+

-

+ + +

+ +

+
-

+

-

+
@@ -45,12 +46,12 @@ function renderLatestActive(latestActive, latestTypes, latestVersions) { -

+

-

+
@@ -59,12 +60,12 @@ function renderLatestActive(latestActive, latestTypes, latestVersions) { -

+

-

+
@@ -109,6 +110,14 @@ export function BeatsOverview({ return ( + +

+ +

+
{renderLatestActive(latestActive, latestTypes, latestVersions)} diff --git a/x-pack/legacy/plugins/monitoring/public/components/chart/chart_target.js b/x-pack/legacy/plugins/monitoring/public/components/chart/chart_target.js index c3ca241c73cc0..5c9cddf9c2902 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/chart/chart_target.js +++ b/x-pack/legacy/plugins/monitoring/public/components/chart/chart_target.js @@ -46,7 +46,7 @@ export class ChartTarget extends React.Component { return seriesToShow.some(id => id.toLowerCase() === metric.id.toLowerCase()); }; } - return _metric => true; + return () => true; } UNSAFE_componentWillReceiveProps(newProps) { diff --git a/x-pack/legacy/plugins/monitoring/public/components/chart/chart_target.test.js b/x-pack/legacy/plugins/monitoring/public/components/chart/chart_target.test.js index bf0d5fdd24879..56eb52fa86235 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/chart/chart_target.test.js +++ b/x-pack/legacy/plugins/monitoring/public/components/chart/chart_target.test.js @@ -38,8 +38,8 @@ const props = { max: 1562962539851, }, hasLegend: true, - onBrush: _ => void 0, - tickFormatter: _ => void 0, + onBrush: () => void 0, + tickFormatter: () => void 0, updateLegend: () => void 0, }; diff --git a/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/__tests__/__snapshots__/helpers.test.js.snap b/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/__tests__/__snapshots__/helpers.test.js.snap new file mode 100644 index 0000000000000..ea9d312413168 --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/__tests__/__snapshots__/helpers.test.js.snap @@ -0,0 +1,48 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`Bytes Usage should format correctly with only usedBytes 1`] = ` + +
+ 50.0 B +
+
+`; + +exports[`Bytes Usage should format correctly with used and max bytes 1`] = ` + +
+ 50.0 B / 100.0 B +
+
+`; + +exports[`BytesPercentageUsage should format correctly with used bytes and max bytes 1`] = ` + +
+ 50.00% +
+
+
+ 50.0 B / 100.0 B +
+
+
+`; + +exports[`BytesPercentageUsage should return zero bytes if both parameters are not present 1`] = ` +
+ 0 +
+`; diff --git a/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/__tests__/helpers.test.js b/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/__tests__/helpers.test.js new file mode 100644 index 0000000000000..fea8f0001540a --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/__tests__/helpers.test.js @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { renderWithIntl } from '../../../../../../../../test_utils/enzyme_helpers'; +import { BytesUsage, BytesPercentageUsage } from '../helpers'; + +describe('Bytes Usage', () => { + it('should format correctly with used and max bytes', () => { + const props = { + usedBytes: 50, + maxBytes: 100, + }; + expect(renderWithIntl()).toMatchSnapshot(); + }); + + it('should format correctly with only usedBytes', () => { + const props = { + usedBytes: 50, + }; + expect(renderWithIntl()).toMatchSnapshot(); + }); +}); + +describe('BytesPercentageUsage', () => { + it('should format correctly with used bytes and max bytes', () => { + const props = { + usedBytes: 50, + maxBytes: 100, + }; + expect(renderWithIntl()).toMatchSnapshot(); + }); + it('should return zero bytes if both parameters are not present', () => { + const props = { + usedBytes: 50, + }; + expect(renderWithIntl()).toMatchSnapshot(); + }); +}); diff --git a/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/alerts_panel.js b/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/alerts_panel.js index e65aec8602f40..33b26c7ec56e0 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/alerts_panel.js +++ b/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/alerts_panel.js @@ -91,12 +91,12 @@ export function AlertsPanel({ alerts, changeUrl }) { -

+

-

+
diff --git a/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/apm_panel.js b/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/apm_panel.js index 3ba04359c2672..84dc13e9da1de 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/apm_panel.js +++ b/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/apm_panel.js @@ -8,11 +8,7 @@ import React from 'react'; import moment from 'moment'; import { get } from 'lodash'; import { formatMetric } from 'plugins/monitoring/lib/format_number'; -import { - ClusterItemContainer, - BytesPercentageUsage, - DisabledIfNoDataAndInSetupModeLink, -} from './helpers'; +import { ClusterItemContainer, BytesUsage, DisabledIfNoDataAndInSetupModeLink } from './helpers'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; import { @@ -153,7 +149,7 @@ export function ApmPanel(props) { /> - +
diff --git a/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/elasticsearch_panel.js b/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/elasticsearch_panel.js index fc23110f940e8..7b08c89f53881 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/elasticsearch_panel.js +++ b/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/elasticsearch_panel.js @@ -10,7 +10,6 @@ import { formatNumber } from 'plugins/monitoring/lib/format_number'; import { ClusterItemContainer, HealthStatusIndicator, - BytesUsage, BytesPercentageUsage, DisabledIfNoDataAndInSetupModeLink, } from './helpers'; @@ -291,7 +290,7 @@ export function ElasticsearchPanel(props) { /> - diff --git a/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/helpers.js b/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/helpers.js index ae7cc1b4e965c..0d9290225cd5f 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/helpers.js +++ b/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/helpers.js @@ -6,7 +6,7 @@ import React from 'react'; import { get } from 'lodash'; -import { formatBytesUsage, formatPercentageUsage } from 'plugins/monitoring/lib/format_number'; +import { formatBytesUsage, formatPercentageUsage, formatNumber } from '../../../lib/format_number'; import { EuiSpacer, EuiFlexItem, @@ -88,10 +88,13 @@ export function BytesUsage({ usedBytes, maxBytes }) { if (usedBytes && maxBytes) { return ( - {formatPercentageUsage(usedBytes, maxBytes)} - - {formatBytesUsage(usedBytes, maxBytes)} - + {formatBytesUsage(usedBytes, maxBytes)} + + ); + } else if (usedBytes) { + return ( + + {formatNumber(usedBytes, 'byte')} ); } diff --git a/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/index.js b/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/index.js index 3014a74160107..cad4bbf411c34 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/index.js +++ b/x-pack/legacy/plugins/monitoring/public/components/cluster/overview/index.js @@ -11,9 +11,10 @@ import { LogstashPanel } from './logstash_panel'; import { AlertsPanel } from './alerts_panel'; import { BeatsPanel } from './beats_panel'; -import { EuiPage, EuiPageBody } from '@elastic/eui'; +import { EuiPage, EuiPageBody, EuiScreenReaderOnly } from '@elastic/eui'; import { ApmPanel } from './apm_panel'; import { STANDALONE_CLUSTER_CLUSTER_UUID } from '../../../../common/constants'; +import { FormattedMessage } from '@kbn/i18n/react'; export function Overview(props) { const isFromStandaloneCluster = props.cluster.cluster_uuid === STANDALONE_CLUSTER_CLUSTER_UUID; @@ -21,6 +22,14 @@ export function Overview(props) { return ( + +

+ +

+
{!isFromStandaloneCluster ? ( diff --git a/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/ccr/__snapshots__/ccr.test.js.snap b/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/ccr/__snapshots__/ccr.test.js.snap index bb765d0ab7b4e..37e0039c94ec4 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/ccr/__snapshots__/ccr.test.js.snap +++ b/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/ccr/__snapshots__/ccr.test.js.snap @@ -3,6 +3,15 @@ exports[`Ccr that it renders normally 1`] = ` + +

+ +

+
+ +

+ +

+
{this.renderTable()} diff --git a/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/ccr_shard/__snapshots__/ccr_shard.test.js.snap b/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/ccr_shard/__snapshots__/ccr_shard.test.js.snap index 3d03d13dde1e1..02e42b8647c03 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/ccr_shard/__snapshots__/ccr_shard.test.js.snap +++ b/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/ccr_shard/__snapshots__/ccr_shard.test.js.snap @@ -145,9 +145,9 @@ exports[`CcrShard that it renders normally 1`] = ` -

+

September 27, 2018 9:32:09 AM -

+
-

{formatDateTimeLocal(timestamp)}

+

{formatDateTimeLocal(timestamp)}

{JSON.stringify(stat, null, 2)} diff --git a/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/index/advanced.js b/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/index/advanced.js index 9e4c70307bec7..a0a339fabef92 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/index/advanced.js +++ b/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/index/advanced.js @@ -13,9 +13,11 @@ import { EuiSpacer, EuiFlexGrid, EuiFlexItem, + EuiScreenReaderOnly, } from '@elastic/eui'; import { IndexDetailStatus } from '../index_detail_status'; import { MonitoringTimeseriesContainer } from '../../chart'; +import { FormattedMessage } from '@kbn/i18n/react'; export const AdvancedIndex = ({ indexSummary, metrics, ...props }) => { const metricsToShow = [ @@ -35,6 +37,14 @@ export const AdvancedIndex = ({ indexSummary, metrics, ...props }) => { return ( + +

+ +

+
diff --git a/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/indices/indices.js b/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/indices/indices.js index 232815e930388..f8dd7b0af7a17 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/indices/indices.js +++ b/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/indices/indices.js @@ -19,6 +19,7 @@ import { EuiPanel, EuiSwitch, EuiSpacer, + EuiScreenReaderOnly, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -130,6 +131,14 @@ export const ElasticsearchIndices = ({ return ( + +

+ +

+
diff --git a/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/node/advanced.js b/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/node/advanced.js index cc4d25c2e2eda..6fea34ed9c901 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/node/advanced.js +++ b/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/node/advanced.js @@ -13,9 +13,11 @@ import { EuiSpacer, EuiFlexGrid, EuiFlexItem, + EuiScreenReaderOnly, } from '@elastic/eui'; import { NodeDetailStatus } from '../node_detail_status'; import { MonitoringTimeseriesContainer } from '../../chart'; +import { FormattedMessage } from '@kbn/i18n/react'; export const AdvancedNode = ({ nodeSummary, metrics, ...props }) => { const metricsToShow = [ @@ -39,6 +41,14 @@ export const AdvancedNode = ({ nodeSummary, metrics, ...props }) => { return ( + +

+ +

+
diff --git a/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/node/node.js b/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/node/node.js index b585de23c6254..e8b3ef7e680f8 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/node/node.js +++ b/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/node/node.js @@ -13,11 +13,13 @@ import { EuiFlexGrid, EuiFlexItem, EuiPanel, + EuiScreenReaderOnly, } from '@elastic/eui'; import { NodeDetailStatus } from '../node_detail_status'; import { Logs } from '../../logs/'; import { MonitoringTimeseriesContainer } from '../../chart'; import { ShardAllocation } from '../shard_allocation/shard_allocation'; +import { FormattedMessage } from '@kbn/i18n/react'; export const Node = ({ nodeSummary, @@ -38,10 +40,17 @@ export const Node = ({ metrics.node_latency, metrics.node_segment_count, ]; - return ( + +

+ +

+
diff --git a/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/nodes/nodes.js b/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/nodes/nodes.js index 428da4ca913aa..d9cf29f73ce0d 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/nodes/nodes.js +++ b/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/nodes/nodes.js @@ -23,10 +23,12 @@ import { EuiCallOut, EuiButton, EuiText, + EuiScreenReaderOnly, } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import _ from 'lodash'; import { ELASTICSEARCH_SYSTEM_ID } from '../../../../common/constants'; +import { FormattedMessage } from '@kbn/i18n/react'; import { ListingCallOut } from '../../setup_mode/listing_callout'; const getNodeTooltip = node => { @@ -399,6 +401,14 @@ export function ElasticsearchNodes({ clusterStatus, showCgroupMetricsElasticsear return ( + +

+ +

+
{renderClusterStatus()} {setupModeCallout} diff --git a/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/shard_allocation/components/assigned.js b/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/shard_allocation/components/assigned.js index 012bc81135e34..897c20edc3f74 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/shard_allocation/components/assigned.js +++ b/x-pack/legacy/plugins/monitoring/public/components/elasticsearch/shard_allocation/components/assigned.js @@ -46,6 +46,8 @@ export class Assigned extends React.Component { // TODO: redesign for shard allocation, possibly giving shard display the // ability to use the euiLink CSS class (blue link text instead of white link text) + // Disabling eslint because EuiKeyboardAccessible does it for us + /* eslint-disable jsx-a11y/click-events-have-key-events */ const name = ( @@ -53,6 +55,7 @@ export class Assigned extends React.Component { ); + /* eslint-enable jsx-a11y/click-events-have-key-events */ const master = data.node_type === 'master' ? : null; const shards = sortBy(data.children, 'shard').map(this.createShard); diff --git a/x-pack/legacy/plugins/monitoring/public/components/kibana/instances/instances.js b/x-pack/legacy/plugins/monitoring/public/components/kibana/instances/instances.js index b9cd53ef9fe12..053130076fa77 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/kibana/instances/instances.js +++ b/x-pack/legacy/plugins/monitoring/public/components/kibana/instances/instances.js @@ -13,6 +13,7 @@ import { EuiSpacer, EuiLink, EuiCallOut, + EuiScreenReaderOnly, } from '@elastic/eui'; import { capitalize, get } from 'lodash'; import { ClusterStatus } from '../cluster_status'; @@ -247,6 +248,14 @@ export class KibanaInstances extends PureComponent { return ( + +

+ +

+
diff --git a/x-pack/legacy/plugins/monitoring/public/components/license/index.js b/x-pack/legacy/plugins/monitoring/public/components/license/index.js index 0e92cda66bc62..75534da6fbef3 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/license/index.js +++ b/x-pack/legacy/plugins/monitoring/public/components/license/index.js @@ -15,6 +15,7 @@ import { EuiLink, EuiFlexGroup, EuiFlexItem, + EuiScreenReaderOnly, } from '@elastic/eui'; import { LicenseStatus, AddLicense } from 'plugins/xpack_main/components'; import { FormattedMessage } from '@kbn/i18n/react'; @@ -61,6 +62,11 @@ export function License(props) { const { status, type, isExpired, expiryDate } = props; return ( + +

+ +

+
diff --git a/x-pack/legacy/plugins/monitoring/public/components/logstash/listing/listing.js b/x-pack/legacy/plugins/monitoring/public/components/logstash/listing/listing.js index 073fc7fc61bcc..7efe586347970 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/logstash/listing/listing.js +++ b/x-pack/legacy/plugins/monitoring/public/components/logstash/listing/listing.js @@ -6,7 +6,15 @@ import React, { PureComponent } from 'react'; import { get } from 'lodash'; -import { EuiPage, EuiLink, EuiPageBody, EuiPageContent, EuiPanel, EuiSpacer } from '@elastic/eui'; +import { + EuiPage, + EuiLink, + EuiPageBody, + EuiPageContent, + EuiPanel, + EuiSpacer, + EuiScreenReaderOnly, +} from '@elastic/eui'; import { formatPercentageUsage, formatNumber } from '../../../lib/format_number'; import { ClusterStatus } from '..//cluster_status'; import { EuiMonitoringTable } from '../../table'; @@ -165,6 +173,14 @@ export class Listing extends PureComponent { return ( + +

+ +

+
diff --git a/x-pack/legacy/plugins/monitoring/public/components/logstash/overview/overview.js b/x-pack/legacy/plugins/monitoring/public/components/logstash/overview/overview.js index 9ed644aa043d6..ed9769b460d92 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/logstash/overview/overview.js +++ b/x-pack/legacy/plugins/monitoring/public/components/logstash/overview/overview.js @@ -13,9 +13,11 @@ import { EuiSpacer, EuiFlexGrid, EuiFlexItem, + EuiScreenReaderOnly, } from '@elastic/eui'; import { ClusterStatus } from '../cluster_status'; import { MonitoringTimeseriesContainer } from '../../chart'; +import { FormattedMessage } from '@kbn/i18n/react'; export class Overview extends PureComponent { render() { @@ -29,6 +31,14 @@ export class Overview extends PureComponent { return ( + +

+ +

+
diff --git a/x-pack/legacy/plugins/monitoring/public/components/logstash/pipeline_listing/pipeline_listing.js b/x-pack/legacy/plugins/monitoring/public/components/logstash/pipeline_listing/pipeline_listing.js index 32bd1303cdfa0..72ef7a3b0ec5c 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/logstash/pipeline_listing/pipeline_listing.js +++ b/x-pack/legacy/plugins/monitoring/public/components/logstash/pipeline_listing/pipeline_listing.js @@ -16,12 +16,14 @@ import { EuiSpacer, EuiFlexGroup, EuiFlexItem, + EuiScreenReaderOnly, } from '@elastic/eui'; import { formatMetric } from '../../../lib/format_number'; import { ClusterStatus } from '../cluster_status'; import { Sparkline } from 'plugins/monitoring/components/sparkline'; import { EuiMonitoringSSPTable } from '../../table'; import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; export class PipelineListing extends Component { tooltipXValueFormatter(xValue, dateFormat) { @@ -148,6 +150,14 @@ export class PipelineListing extends Component { return ( + +

+ +

+
{this.renderStats()} diff --git a/x-pack/legacy/plugins/monitoring/public/components/logstash/pipeline_viewer/views/__test__/__snapshots__/pipeline_viewer.test.js.snap b/x-pack/legacy/plugins/monitoring/public/components/logstash/pipeline_viewer/views/__test__/__snapshots__/pipeline_viewer.test.js.snap index b19560df7e951..6b46371e0ee50 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/logstash/pipeline_viewer/views/__test__/__snapshots__/pipeline_viewer.test.js.snap +++ b/x-pack/legacy/plugins/monitoring/public/components/logstash/pipeline_viewer/views/__test__/__snapshots__/pipeline_viewer.test.js.snap @@ -3,6 +3,15 @@ exports[`PipelineViewer component passes expected props 1`] = ` + +

+ +

+
+ +

+ +

+
-

+

Filters -

+
diff --git a/x-pack/legacy/plugins/monitoring/public/components/logstash/pipeline_viewer/views/detail_drawer.js b/x-pack/legacy/plugins/monitoring/public/components/logstash/pipeline_viewer/views/detail_drawer.js index cd9fe5229b640..73faa81af7b55 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/logstash/pipeline_viewer/views/detail_drawer.js +++ b/x-pack/legacy/plugins/monitoring/public/components/logstash/pipeline_viewer/views/detail_drawer.js @@ -164,7 +164,7 @@ function renderPluginBasicStats(vertex, timeseriesTooltipXValueFormatter) { ); } -function renderIfBasicStats(_vertex) { +function renderIfBasicStats() { return (

+ +

+ +

+ -

{title}

+

{title}

diff --git a/x-pack/legacy/plugins/monitoring/public/components/no_data/__tests__/__snapshots__/no_data.test.js.snap b/x-pack/legacy/plugins/monitoring/public/components/no_data/__tests__/__snapshots__/no_data.test.js.snap index cf096ed1f7914..95dd580775f31 100644 --- a/x-pack/legacy/plugins/monitoring/public/components/no_data/__tests__/__snapshots__/no_data.test.js.snap +++ b/x-pack/legacy/plugins/monitoring/public/components/no_data/__tests__/__snapshots__/no_data.test.js.snap @@ -4,6 +4,11 @@ exports[`NoData should show a default message if reason is unknown 1`] = `
+

+ No monitoring data found. +

+

+ No monitoring data found. +

+ +

+ +

+
+ +

+ +

+
{ + _api; // to fix eslint const $http = $injector.get('$http'); const globalState = $injector.get('globalState'); const timeBounds = timefilter.getBounds(); diff --git a/x-pack/legacy/plugins/monitoring/public/views/logstash/node/pipelines/index.js b/x-pack/legacy/plugins/monitoring/public/views/logstash/node/pipelines/index.js index bc5ac75931fce..7bfcddf8f283a 100644 --- a/x-pack/legacy/plugins/monitoring/public/views/logstash/node/pipelines/index.js +++ b/x-pack/legacy/plugins/monitoring/public/views/logstash/node/pipelines/index.js @@ -23,6 +23,7 @@ import { DetailStatus } from '../../../../components/logstash/detail_status'; import { CODE_PATH_LOGSTASH } from '../../../../../common/constants'; const getPageData = ($injector, _api = undefined, routeOptions = {}) => { + _api; // fixing eslint const $route = $injector.get('$route'); const $http = $injector.get('$http'); const globalState = $injector.get('globalState'); diff --git a/x-pack/legacy/plugins/monitoring/public/views/logstash/pipelines/index.js b/x-pack/legacy/plugins/monitoring/public/views/logstash/pipelines/index.js index ce49fc2fac97b..03cf7383d1d02 100644 --- a/x-pack/legacy/plugins/monitoring/public/views/logstash/pipelines/index.js +++ b/x-pack/legacy/plugins/monitoring/public/views/logstash/pipelines/index.js @@ -23,6 +23,7 @@ import { CODE_PATH_LOGSTASH } from '../../../../common/constants'; */ const getPageData = ($injector, _api = undefined, routeOptions = {}) => { + _api; // to fix eslint const $http = $injector.get('$http'); const globalState = $injector.get('globalState'); const Private = $injector.get('Private'); diff --git a/x-pack/legacy/plugins/monitoring/server/cloud/cloud_service.js b/x-pack/legacy/plugins/monitoring/server/cloud/cloud_service.js index dcc8ad8fc8b64..2a408f5295aef 100644 --- a/x-pack/legacy/plugins/monitoring/server/cloud/cloud_service.js +++ b/x-pack/legacy/plugins/monitoring/server/cloud/cloud_service.js @@ -48,7 +48,7 @@ export class CloudService { * @param {Object} _request 'request' HTTP handler. * @return {Promise} Never {@code null} {@code CloudServiceResponse}. */ - _checkIfService(_request) { + _checkIfService() { return Promise.reject(new Error('not implemented')); } diff --git a/x-pack/legacy/plugins/monitoring/server/kibana_monitoring/__tests__/bulk_uploader.js b/x-pack/legacy/plugins/monitoring/server/kibana_monitoring/__tests__/bulk_uploader.js index fff0b742925c8..ef7d3f1224fab 100644 --- a/x-pack/legacy/plugins/monitoring/server/kibana_monitoring/__tests__/bulk_uploader.js +++ b/x-pack/legacy/plugins/monitoring/server/kibana_monitoring/__tests__/bulk_uploader.js @@ -209,7 +209,7 @@ describe('BulkUploader', () => { }, CHECK_DELAY); }); - it('refetches UsageCollectors if uploading to local cluster was not successful', done => { + it('stops refetching UsageCollectors if uploading to local cluster was not successful', async () => { const usageCollectorFetch = sinon .stub() .returns({ type: 'type_usage_collector_test', result: { testData: 12345 } }); @@ -227,12 +227,52 @@ describe('BulkUploader', () => { uploader._onPayload = async () => ({ took: 0, ignored: true, errors: false }); - uploader.start(collectors); - setTimeout(() => { - uploader.stop(); - expect(usageCollectorFetch.callCount).to.be.greaterThan(1); - done(); - }, CHECK_DELAY); + await uploader._fetchAndUpload(uploader.filterCollectorSet(collectors)); + await uploader._fetchAndUpload(uploader.filterCollectorSet(collectors)); + await uploader._fetchAndUpload(uploader.filterCollectorSet(collectors)); + + expect(uploader._holdSendingUsage).to.eql(true); + expect(usageCollectorFetch.callCount).to.eql(1); + }); + + it('fetches UsageCollectors once uploading to local cluster is successful again', async () => { + const usageCollectorFetch = sinon + .stub() + .returns({ type: 'type_usage_collector_test', result: { usageData: 12345 } }); + + const statsCollectorFetch = sinon + .stub() + .returns({ type: 'type_stats_collector_test', result: { statsData: 12345 } }); + + const collectors = new MockCollectorSet(server, [ + { + fetch: statsCollectorFetch, + isReady: () => true, + formatForBulkUpload: result => result, + isUsageCollector: false, + }, + { + fetch: usageCollectorFetch, + isReady: () => true, + formatForBulkUpload: result => result, + isUsageCollector: true, + }, + ]); + + const uploader = new BulkUploader({ ...server, interval: FETCH_INTERVAL }); + let bulkIgnored = true; + uploader._onPayload = async () => ({ took: 0, ignored: bulkIgnored, errors: false }); + + await uploader._fetchAndUpload(uploader.filterCollectorSet(collectors)); + expect(uploader._holdSendingUsage).to.eql(true); + + bulkIgnored = false; + await uploader._fetchAndUpload(uploader.filterCollectorSet(collectors)); + await uploader._fetchAndUpload(uploader.filterCollectorSet(collectors)); + + expect(uploader._holdSendingUsage).to.eql(false); + expect(usageCollectorFetch.callCount).to.eql(2); + expect(statsCollectorFetch.callCount).to.eql(3); }); it('calls UsageCollectors if last reported exceeds during a _usageInterval', done => { diff --git a/x-pack/legacy/plugins/monitoring/server/kibana_monitoring/bulk_uploader.js b/x-pack/legacy/plugins/monitoring/server/kibana_monitoring/bulk_uploader.js index 2d81cb23b6b3b..5e0d8aa4be1fd 100644 --- a/x-pack/legacy/plugins/monitoring/server/kibana_monitoring/bulk_uploader.js +++ b/x-pack/legacy/plugins/monitoring/server/kibana_monitoring/bulk_uploader.js @@ -40,8 +40,14 @@ export class BulkUploader { } this._timer = null; + // Hold sending and fetching usage until monitoring.bulk is successful. This means that we + // send usage data on the second tick. But would save a lot of bandwidth fetching usage on + // every tick when ES is failing or monitoring is disabled. + this._holdSendingUsage = false; this._interval = interval; this._lastFetchUsageTime = null; + // Limit sending and fetching usage to once per day once usage is successfully stored + // into the monitoring indices. this._usageInterval = TELEMETRY_COLLECTION_INTERVAL; this._log = { @@ -65,6 +71,29 @@ export class BulkUploader { }); } + filterCollectorSet(usageCollection) { + const successfulUploadInLastDay = + this._lastFetchUsageTime && this._lastFetchUsageTime + this._usageInterval > Date.now(); + + return usageCollection.getFilteredCollectorSet(c => { + // this is internal bulk upload, so filter out API-only collectors + if (c.ignoreForInternalUploader) { + return false; + } + // Only collect usage data at the same interval as telemetry would (default to once a day) + if (usageCollection.isUsageCollector(c)) { + if (this._holdSendingUsage) { + return false; + } + if (successfulUploadInLastDay) { + return false; + } + } + + return true; + }); + } + /* * Start the interval timer * @param {usageCollection} usageCollection object to use for initial the fetch/upload and fetch/uploading on interval @@ -72,31 +101,15 @@ export class BulkUploader { */ start(usageCollection) { this._log.info('Starting monitoring stats collection'); - const filterCollectorSet = _usageCollection => { - const successfulUploadInLastDay = - this._lastFetchUsageTime && this._lastFetchUsageTime + this._usageInterval > Date.now(); - - return _usageCollection.getFilteredCollectorSet(c => { - // this is internal bulk upload, so filter out API-only collectors - if (c.ignoreForInternalUploader) { - return false; - } - // Only collect usage data at the same interval as telemetry would (default to once a day) - if (successfulUploadInLastDay && _usageCollection.isUsageCollector(c)) { - return false; - } - return true; - }); - }; if (this._timer) { clearInterval(this._timer); } else { - this._fetchAndUpload(filterCollectorSet(usageCollection)); // initial fetch + this._fetchAndUpload(this.filterCollectorSet(usageCollection)); // initial fetch } this._timer = setInterval(() => { - this._fetchAndUpload(filterCollectorSet(usageCollection)); + this._fetchAndUpload(this.filterCollectorSet(usageCollection)); }, this._interval); } @@ -146,12 +159,17 @@ export class BulkUploader { const sendSuccessful = !result.ignored && !result.errors; if (!sendSuccessful && hasUsageCollectors) { this._lastFetchUsageTime = null; + this._holdSendingUsage = true; this._log.debug( 'Resetting lastFetchWithUsage because uploading to the cluster was not successful.' ); } - if (sendSuccessful && hasUsageCollectors) { - this._lastFetchUsageTime = Date.now(); + + if (sendSuccessful) { + this._holdSendingUsage = false; + if (hasUsageCollectors) { + this._lastFetchUsageTime = Date.now(); + } } this._log.debug(`Uploaded bulk stats payload to the local cluster`); } catch (err) { diff --git a/x-pack/legacy/plugins/monitoring/server/lib/cluster/get_index_patterns.js b/x-pack/legacy/plugins/monitoring/server/lib/cluster/get_index_patterns.js index 823495298e1e6..6e8eb3e3c71d3 100644 --- a/x-pack/legacy/plugins/monitoring/server/lib/cluster/get_index_patterns.js +++ b/x-pack/legacy/plugins/monitoring/server/lib/cluster/get_index_patterns.js @@ -13,9 +13,7 @@ import { INDEX_ALERTS, } from '../../../common/constants'; -export function getIndexPatterns(server, additionalPatterns = {}) { - // wildcard means to search _all_ clusters - const ccs = '*'; +export function getIndexPatterns(server, additionalPatterns = {}, ccs = '*') { const config = server.config(); const esIndexPattern = prefixIndexPattern(config, INDEX_PATTERN_ELASTICSEARCH, ccs); const kbnIndexPattern = prefixIndexPattern(config, INDEX_PATTERN_KIBANA, ccs); diff --git a/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/__test__/get_paginated_nodes.test.js b/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/__test__/get_paginated_nodes.test.js index 57fdbd5cc6238..c08ae91769b9d 100644 --- a/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/__test__/get_paginated_nodes.test.js +++ b/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/__test__/get_paginated_nodes.test.js @@ -58,7 +58,7 @@ describe('getPaginatedNodes', () => { }, }, }; - const shardStats = { + const nodesShardCount = { nodes: { 1: { shardCount: 10, @@ -78,7 +78,7 @@ describe('getPaginatedNodes', () => { pagination, sort, queryText, - { clusterStats, shardStats } + { clusterStats, nodesShardCount } ); expect(nodes).toEqual({ pageOfNodes: [ @@ -98,7 +98,7 @@ describe('getPaginatedNodes', () => { pagination, { ...sort, field: 'foo', direction: 'desc' }, queryText, - { clusterStats, shardStats } + { clusterStats, nodesShardCount } ); expect(nodes).toEqual({ pageOfNodes: [ @@ -118,7 +118,7 @@ describe('getPaginatedNodes', () => { pagination, sort, 'tw', - { clusterStats, shardStats } + { clusterStats, nodesShardCount } ); expect(nodes).toEqual({ pageOfNodes: [{ name: 'two', uuid: 2, isOnline: false, shardCount: 5, foo: 12 }], diff --git a/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/get_nodes.js b/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/get_nodes.js index 4bfd0090fced0..7581a32590971 100644 --- a/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/get_nodes.js +++ b/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/get_nodes.js @@ -29,7 +29,7 @@ import { LISTING_METRICS_NAMES, LISTING_METRICS_PATHS } from './nodes_listing_me * @param {Object} shardStats: per-node information about shards * @return {Array} node info combined with metrics for each node from handle_response */ -export async function getNodes(req, esIndexPattern, pageOfNodes, clusterStats, shardStats) { +export async function getNodes(req, esIndexPattern, pageOfNodes, clusterStats, nodesShardCount) { checkParam(esIndexPattern, 'esIndexPattern in getNodes'); const start = moment.utc(req.payload.timeRange.min).valueOf(); @@ -104,5 +104,9 @@ export async function getNodes(req, esIndexPattern, pageOfNodes, clusterStats, s const { callWithRequest } = req.server.plugins.elasticsearch.getCluster('monitoring'); const response = await callWithRequest(req, 'search', params); - return handleResponse(response, clusterStats, shardStats, pageOfNodes, { min, max, bucketSize }); + return handleResponse(response, clusterStats, nodesShardCount, pageOfNodes, { + min, + max, + bucketSize, + }); } diff --git a/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/get_paginated_nodes.js b/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/get_paginated_nodes.js index 15084d952b343..0023b9515ad1c 100644 --- a/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/get_paginated_nodes.js +++ b/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/get_paginated_nodes.js @@ -35,7 +35,7 @@ export async function getPaginatedNodes( pagination, sort, queryText, - { clusterStats, shardStats } + { clusterStats, nodesShardCount } ) { const config = req.server.config(); const size = config.get('xpack.monitoring.max_bucket_size'); @@ -45,7 +45,7 @@ export async function getPaginatedNodes( const clusterState = get(clusterStats, 'cluster_state', { nodes: {} }); for (const node of nodes) { node.isOnline = !isUndefined(get(clusterState, ['nodes', node.uuid])); - node.shardCount = get(shardStats, `nodes[${node.uuid}].shardCount`, 0); + node.shardCount = get(nodesShardCount, `nodes[${node.uuid}].shardCount`, 0); } // `metricSet` defines a list of metrics that are sortable in the UI diff --git a/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/handle_response.js b/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/handle_response.js index 55072a1086641..651fd20d77554 100644 --- a/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/handle_response.js +++ b/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/handle_response.js @@ -13,17 +13,23 @@ import { uncovertMetricNames } from '../../convert_metric_names'; * Process the response from the get_nodes query * @param {Object} response: response data from get_nodes * @param {Object} clusterStats: cluster stats from cluster state document - * @param {Object} shardStats: per-node information about shards + * @param {Object} nodesShardCount: per-node information about shards * @param {Object} timeOptions: min, max, and bucketSize needed for date histogram creation * @return {Array} node info combined with metrics for each node */ -export function handleResponse(response, clusterStats, shardStats, pageOfNodes, timeOptions = {}) { +export function handleResponse( + response, + clusterStats, + nodesShardCount, + pageOfNodes, + timeOptions = {} +) { if (!get(response, 'hits.hits')) { return []; } const nodeHits = get(response, 'hits.hits', []); - const nodesInfo = mapNodesInfo(nodeHits, clusterStats, shardStats); + const nodesInfo = mapNodesInfo(nodeHits, clusterStats, nodesShardCount); /* * Every node bucket is an object with a field for nodeId and fields for diff --git a/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/map_nodes_info.js b/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/map_nodes_info.js index 23ee614d48ec4..3c719c2ddfbf8 100644 --- a/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/map_nodes_info.js +++ b/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/nodes/get_nodes/map_nodes_info.js @@ -10,10 +10,10 @@ import { calculateNodeType, getNodeTypeClassLabel } from '../'; /** * @param {Array} nodeHits: info about each node from the hits in the get_nodes query * @param {Object} clusterStats: cluster stats from cluster state document - * @param {Object} shardStats: per-node information about shards + * @param {Object} nodesShardCount: per-node information about shards * @return {Object} summarized info about each node keyed by nodeId */ -export function mapNodesInfo(nodeHits, clusterStats, shardStats) { +export function mapNodesInfo(nodeHits, clusterStats, nodesShardCount) { const clusterState = get(clusterStats, 'cluster_state', { nodes: {} }); return nodeHits.reduce((prev, node) => { @@ -35,7 +35,7 @@ export function mapNodesInfo(nodeHits, clusterStats, shardStats) { isOnline, nodeTypeLabel: nodeTypeLabel, nodeTypeClass: nodeTypeClass, - shardCount: get(shardStats, `nodes[${sourceNode.uuid}].shardCount`, 0), + shardCount: get(nodesShardCount, `nodes[${sourceNode.uuid}].shardCount`, 0), }, }; }, {}); diff --git a/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/shards/get_indices_unassigned_shard_stats.js b/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/shards/get_indices_unassigned_shard_stats.js new file mode 100644 index 0000000000000..e8d484e7021f4 --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/shards/get_indices_unassigned_shard_stats.js @@ -0,0 +1,93 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { get } from 'lodash'; +import { checkParam } from '../../error_missing_required'; +import { createQuery } from '../../create_query'; +import { ElasticsearchMetric } from '../../metrics'; +import { calculateIndicesTotals } from './calculate_shard_stat_indices_totals'; + +async function getUnassignedShardData(req, esIndexPattern, cluster) { + const config = req.server.config(); + const maxBucketSize = config.get('xpack.monitoring.max_bucket_size'); + const metric = ElasticsearchMetric.getMetricFields(); + + const params = { + index: esIndexPattern, + size: 0, + ignoreUnavailable: true, + body: { + sort: { timestamp: { order: 'desc' } }, + query: createQuery({ + type: 'shards', + clusterUuid: cluster.cluster_uuid, + metric, + filters: [{ term: { state_uuid: get(cluster, 'cluster_state.state_uuid') } }], + }), + aggs: { + indices: { + terms: { + field: 'shard.index', + size: maxBucketSize, + }, + aggs: { + state: { + filter: { + terms: { + 'shard.state': ['UNASSIGNED', 'INITIALIZING'], + }, + }, + aggs: { + primary: { + terms: { + field: 'shard.primary', + size: 2, + }, + }, + }, + }, + }, + }, + }, + }, + }; + + const { callWithRequest } = req.server.plugins.elasticsearch.getCluster('monitoring'); + return await callWithRequest(req, 'search', params); +} + +export async function getIndicesUnassignedShardStats(req, esIndexPattern, cluster) { + checkParam(esIndexPattern, 'esIndexPattern in elasticsearch/getShardStats'); + + const response = await getUnassignedShardData(req, esIndexPattern, cluster); + const indices = get(response, 'aggregations.indices.buckets', []).reduce((accum, bucket) => { + const index = bucket.key; + const states = get(bucket, 'state.primary.buckets', []); + const unassignedReplica = states + .filter(state => state.key_as_string === 'false') + .reduce((total, state) => total + state.doc_count, 0); + const unassignedPrimary = states + .filter(state => state.key_as_string === 'true') + .reduce((total, state) => total + state.doc_count, 0); + + let status = 'green'; + if (unassignedReplica > 0) { + status = 'yellow'; + } + if (unassignedPrimary > 0) { + status = 'red'; + } + + accum[index] = { + unassigned: { primary: unassignedPrimary, replica: unassignedReplica }, + status, + }; + return accum; + }, {}); + + const indicesTotals = calculateIndicesTotals(indices); + return { indices, indicesTotals }; +} diff --git a/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/shards/get_indices_unassigned_shard_stats.test.js b/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/shards/get_indices_unassigned_shard_stats.test.js new file mode 100644 index 0000000000000..a899b48cdd434 --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/shards/get_indices_unassigned_shard_stats.test.js @@ -0,0 +1,59 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { getIndicesUnassignedShardStats } from './get_indices_unassigned_shard_stats'; + +describe('getIndicesUnassignedShardStats', () => { + it('should return the unassigned shard stats for indices', async () => { + const indices = { + 12345: { status: 'red', unassigned: { primary: 1, replica: 0 } }, + 6789: { status: 'yellow', unassigned: { primary: 0, replica: 1 } }, + absdf82: { status: 'green', unassigned: { primary: 0, replica: 0 } }, + }; + + const req = { + server: { + config: () => ({ + get: () => {}, + }), + plugins: { + elasticsearch: { + getCluster: () => ({ + callWithRequest: () => ({ + aggregations: { + indices: { + buckets: Object.keys(indices).map(id => ({ + key: id, + state: { + primary: { + buckets: + indices[id].unassigned.primary || indices[id].unassigned.replica + ? [ + { + key_as_string: indices[id].unassigned.primary + ? 'true' + : 'false', + doc_count: 1, + }, + ] + : [], + }, + }, + })), + }, + }, + }), + }), + }, + }, + }, + }; + const esIndexPattern = '*'; + const cluster = {}; + const stats = await getIndicesUnassignedShardStats(req, esIndexPattern, cluster); + expect(stats.indices).toEqual(indices); + }); +}); diff --git a/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/shards/get_nodes_shard_count.js b/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/shards/get_nodes_shard_count.js new file mode 100644 index 0000000000000..c11bd4aead693 --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/shards/get_nodes_shard_count.js @@ -0,0 +1,53 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { get } from 'lodash'; +import { checkParam } from '../../error_missing_required'; +import { createQuery } from '../../create_query'; +import { ElasticsearchMetric } from '../../metrics'; + +async function getShardCountPerNode(req, esIndexPattern, cluster) { + const config = req.server.config(); + const maxBucketSize = config.get('xpack.monitoring.max_bucket_size'); + const metric = ElasticsearchMetric.getMetricFields(); + + const params = { + index: esIndexPattern, + size: 0, + ignoreUnavailable: true, + body: { + sort: { timestamp: { order: 'desc' } }, + query: createQuery({ + type: 'shards', + clusterUuid: cluster.cluster_uuid, + metric, + filters: [{ term: { state_uuid: get(cluster, 'cluster_state.state_uuid') } }], + }), + aggs: { + nodes: { + terms: { + field: 'shard.node', + size: maxBucketSize, + }, + }, + }, + }, + }; + + const { callWithRequest } = req.server.plugins.elasticsearch.getCluster('monitoring'); + return await callWithRequest(req, 'search', params); +} + +export async function getNodesShardCount(req, esIndexPattern, cluster) { + checkParam(esIndexPattern, 'esIndexPattern in elasticsearch/getShardStats'); + + const response = await getShardCountPerNode(req, esIndexPattern, cluster); + const nodes = get(response, 'aggregations.nodes.buckets', []).reduce((accum, bucket) => { + accum[bucket.key] = { shardCount: bucket.doc_count }; + return accum; + }, {}); + return { nodes }; +} diff --git a/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/shards/get_nodes_shard_count.test.js b/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/shards/get_nodes_shard_count.test.js new file mode 100644 index 0000000000000..023f12db1bf46 --- /dev/null +++ b/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/shards/get_nodes_shard_count.test.js @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { getNodesShardCount } from './get_nodes_shard_count'; + +describe('getNodeShardCount', () => { + it('should return the shard count per node', async () => { + const nodes = { + 12345: { shardCount: 10 }, + 6789: { shardCount: 1 }, + absdf82: { shardCount: 20 }, + }; + + const req = { + server: { + config: () => ({ + get: () => {}, + }), + plugins: { + elasticsearch: { + getCluster: () => ({ + callWithRequest: () => ({ + aggregations: { + nodes: { + buckets: Object.keys(nodes).map(id => ({ + key: id, + doc_count: nodes[id].shardCount, + })), + }, + }, + }), + }), + }, + }, + }, + }; + const esIndexPattern = '*'; + const cluster = {}; + const counts = await getNodesShardCount(req, esIndexPattern, cluster); + expect(counts.nodes).toEqual(nodes); + }); +}); diff --git a/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/shards/get_shard_stat_aggs.js b/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/shards/get_shard_stat_aggs.js index a718ef8569dbf..eddd50612cdb1 100644 --- a/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/shards/get_shard_stat_aggs.js +++ b/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/shards/get_shard_stat_aggs.js @@ -8,7 +8,7 @@ * @param {Object} config - Kibana config service * @param {Boolean} includeNodes - whether to add the aggs for node shards */ -export function getShardAggs(config, includeNodes) { +export function getShardAggs(config, includeNodes, includeIndices) { const maxBucketSize = config.get('xpack.monitoring.max_bucket_size'); const aggSize = 10; const indicesAgg = { @@ -40,7 +40,7 @@ export function getShardAggs(config, includeNodes) { }; return { - ...{ indices: indicesAgg }, + ...{ indices: includeIndices ? indicesAgg : undefined }, ...{ nodes: includeNodes ? nodesAgg : undefined }, }; } diff --git a/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/shards/get_shard_stats.js b/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/shards/get_shard_stats.js index c77e03673bb4c..132e9d6b01dbe 100644 --- a/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/shards/get_shard_stats.js +++ b/x-pack/legacy/plugins/monitoring/server/lib/elasticsearch/shards/get_shard_stats.js @@ -21,11 +21,11 @@ export function handleResponse(resp, includeNodes, includeIndices, cluster) { if (buckets && buckets.length !== 0) { indices = buckets.reduce(normalizeIndexShards, {}); indicesTotals = calculateIndicesTotals(indices); + } - if (includeNodes) { - const masterNode = get(cluster, 'cluster_state.master_node'); - nodes = resp.aggregations.nodes.buckets.reduce(normalizeNodeShards(masterNode), {}); - } + if (includeNodes) { + const masterNode = get(cluster, 'cluster_state.master_node'); + nodes = resp.aggregations.nodes.buckets.reduce(normalizeNodeShards(masterNode), {}); } return { @@ -39,12 +39,19 @@ export function getShardStats( req, esIndexPattern, cluster, - { includeNodes = false, includeIndices = false } = {} + { includeNodes = false, includeIndices = false, indexName = null, nodeUuid = null } = {} ) { checkParam(esIndexPattern, 'esIndexPattern in elasticsearch/getShardStats'); const config = req.server.config(); const metric = ElasticsearchMetric.getMetricFields(); + const filters = [{ term: { state_uuid: get(cluster, 'cluster_state.state_uuid') } }]; + if (indexName) { + filters.push({ term: { 'shard.index': indexName } }); + } + if (nodeUuid) { + filters.push({ term: { 'shard.node': nodeUuid } }); + } const params = { index: esIndexPattern, size: 0, @@ -55,10 +62,10 @@ export function getShardStats( type: 'shards', clusterUuid: cluster.cluster_uuid, metric, - filters: [{ term: { state_uuid: get(cluster, 'cluster_state.state_uuid') } }], + filters, }), aggs: { - ...getShardAggs(config, includeNodes), + ...getShardAggs(config, includeNodes, includeIndices), }, }, }; diff --git a/x-pack/legacy/plugins/monitoring/server/lib/metrics/elasticsearch/classes.js b/x-pack/legacy/plugins/monitoring/server/lib/metrics/elasticsearch/classes.js index 755b89a0409d0..d3a43ed651827 100644 --- a/x-pack/legacy/plugins/monitoring/server/lib/metrics/elasticsearch/classes.js +++ b/x-pack/legacy/plugins/monitoring/server/lib/metrics/elasticsearch/classes.js @@ -116,7 +116,7 @@ export class LatencyMetric extends ElasticsearchMetric { }, }; - this.calculation = (bucket, _key, _metric, _bucketSizeInSeconds) => { + this.calculation = bucket => { const timeInMillisDeriv = _.get(bucket, 'event_time_in_millis_deriv.normalized_value', null); const totalEventsDeriv = _.get(bucket, 'event_total_deriv.normalized_value', null); diff --git a/x-pack/legacy/plugins/monitoring/server/lib/metrics/logstash/classes.js b/x-pack/legacy/plugins/monitoring/server/lib/metrics/logstash/classes.js index c76edf9061396..eddcfabe83b1b 100644 --- a/x-pack/legacy/plugins/monitoring/server/lib/metrics/logstash/classes.js +++ b/x-pack/legacy/plugins/monitoring/server/lib/metrics/logstash/classes.js @@ -90,7 +90,7 @@ export class LogstashEventsLatencyClusterMetric extends LogstashClusterMetric { }, }; - this.calculation = (bucket, _key, _metric, _bucketSizeInSeconds) => { + this.calculation = bucket => { const timeInMillisDeriv = _.get(bucket, 'events_time_in_millis_deriv.normalized_value', null); const totalEventsDeriv = _.get(bucket, 'events_total_deriv.normalized_value', null); @@ -189,7 +189,7 @@ export class LogstashEventsLatencyMetric extends LogstashMetric { }, }; - this.calculation = (bucket, _key, _metric, _bucketSizeInSeconds) => { + this.calculation = bucket => { const timeInMillisDeriv = _.get(bucket, 'events_time_in_millis_deriv.normalized_value', null); const totalEventsDeriv = _.get(bucket, 'events_total_deriv.normalized_value', null); diff --git a/x-pack/legacy/plugins/monitoring/server/routes/api/v1/elasticsearch/index_detail.js b/x-pack/legacy/plugins/monitoring/server/routes/api/v1/elasticsearch/index_detail.js index e6380a724590e..c32e25d9f20d1 100644 --- a/x-pack/legacy/plugins/monitoring/server/routes/api/v1/elasticsearch/index_detail.js +++ b/x-pack/legacy/plugins/monitoring/server/routes/api/v1/elasticsearch/index_detail.js @@ -60,6 +60,7 @@ export function esIndexRoute(server) { const shardStats = await getShardStats(req, esIndexPattern, cluster, { includeNodes: true, includeIndices: true, + indexName: indexUuid, }); const indexSummary = await getIndexSummary(req, esIndexPattern, shardStats, { clusterUuid, diff --git a/x-pack/legacy/plugins/monitoring/server/routes/api/v1/elasticsearch/indices.js b/x-pack/legacy/plugins/monitoring/server/routes/api/v1/elasticsearch/indices.js index c8cf4bd29e26d..241b54fbf0c2a 100644 --- a/x-pack/legacy/plugins/monitoring/server/routes/api/v1/elasticsearch/indices.js +++ b/x-pack/legacy/plugins/monitoring/server/routes/api/v1/elasticsearch/indices.js @@ -8,10 +8,10 @@ import Joi from 'joi'; import { getClusterStats } from '../../../../lib/cluster/get_cluster_stats'; import { getClusterStatus } from '../../../../lib/cluster/get_cluster_status'; import { getIndices } from '../../../../lib/elasticsearch/indices'; -import { getShardStats } from '../../../../lib/elasticsearch/shards'; import { handleError } from '../../../../lib/errors/handle_error'; import { prefixIndexPattern } from '../../../../lib/ccs_utils'; import { INDEX_PATTERN_ELASTICSEARCH } from '../../../../../common/constants'; +import { getIndicesUnassignedShardStats } from '../../../../lib/elasticsearch/shards/get_indices_unassigned_shard_stats'; export function esIndicesRoute(server) { server.route({ @@ -43,13 +43,20 @@ export function esIndicesRoute(server) { try { const clusterStats = await getClusterStats(req, esIndexPattern, clusterUuid); - const shardStats = await getShardStats(req, esIndexPattern, clusterStats, { - includeIndices: true, - }); - const indices = await getIndices(req, esIndexPattern, showSystemIndices, shardStats); + const indicesUnassignedShardStats = await getIndicesUnassignedShardStats( + req, + esIndexPattern, + clusterStats + ); + const indices = await getIndices( + req, + esIndexPattern, + showSystemIndices, + indicesUnassignedShardStats + ); return { - clusterStatus: getClusterStatus(clusterStats, shardStats), + clusterStatus: getClusterStatus(clusterStats, indicesUnassignedShardStats), indices, }; } catch (err) { diff --git a/x-pack/legacy/plugins/monitoring/server/routes/api/v1/elasticsearch/ml_jobs.js b/x-pack/legacy/plugins/monitoring/server/routes/api/v1/elasticsearch/ml_jobs.js index 1876f751dd166..de3b9863d9141 100644 --- a/x-pack/legacy/plugins/monitoring/server/routes/api/v1/elasticsearch/ml_jobs.js +++ b/x-pack/legacy/plugins/monitoring/server/routes/api/v1/elasticsearch/ml_jobs.js @@ -8,10 +8,10 @@ import Joi from 'joi'; import { getClusterStats } from '../../../../lib/cluster/get_cluster_stats'; import { getClusterStatus } from '../../../../lib/cluster/get_cluster_status'; import { getMlJobs } from '../../../../lib/elasticsearch/get_ml_jobs'; -import { getShardStats } from '../../../../lib/elasticsearch/shards'; import { handleError } from '../../../../lib/errors/handle_error'; import { prefixIndexPattern } from '../../../../lib/ccs_utils'; import { INDEX_PATTERN_ELASTICSEARCH } from '../../../../../common/constants'; +import { getIndicesUnassignedShardStats } from '../../../../lib/elasticsearch/shards/get_indices_unassigned_shard_stats'; export function mlJobRoute(server) { server.route({ @@ -39,11 +39,15 @@ export function mlJobRoute(server) { try { const clusterStats = await getClusterStats(req, esIndexPattern, clusterUuid); - const shardStats = await getShardStats(req, esIndexPattern, clusterStats); + const indicesUnassignedShardStats = await getIndicesUnassignedShardStats( + req, + esIndexPattern, + clusterStats + ); const rows = await getMlJobs(req, esIndexPattern); return { - clusterStatus: getClusterStatus(clusterStats, shardStats), + clusterStatus: getClusterStatus(clusterStats, indicesUnassignedShardStats), rows, }; } catch (err) { diff --git a/x-pack/legacy/plugins/monitoring/server/routes/api/v1/elasticsearch/node_detail.js b/x-pack/legacy/plugins/monitoring/server/routes/api/v1/elasticsearch/node_detail.js index 5da2e7128e7e4..10226d74ed001 100644 --- a/x-pack/legacy/plugins/monitoring/server/routes/api/v1/elasticsearch/node_detail.js +++ b/x-pack/legacy/plugins/monitoring/server/routes/api/v1/elasticsearch/node_detail.js @@ -78,6 +78,7 @@ export function esNodeRoute(server) { const shardStats = await getShardStats(req, esIndexPattern, cluster, { includeIndices: true, includeNodes: true, + nodeUuid, }); const nodeSummary = await getNodeSummary(req, esIndexPattern, clusterState, shardStats, { clusterUuid, diff --git a/x-pack/legacy/plugins/monitoring/server/routes/api/v1/elasticsearch/nodes.js b/x-pack/legacy/plugins/monitoring/server/routes/api/v1/elasticsearch/nodes.js index 88e65332603ad..fb2d04ecc041d 100644 --- a/x-pack/legacy/plugins/monitoring/server/routes/api/v1/elasticsearch/nodes.js +++ b/x-pack/legacy/plugins/monitoring/server/routes/api/v1/elasticsearch/nodes.js @@ -8,12 +8,13 @@ import Joi from 'joi'; import { getClusterStats } from '../../../../lib/cluster/get_cluster_stats'; import { getClusterStatus } from '../../../../lib/cluster/get_cluster_status'; import { getNodes } from '../../../../lib/elasticsearch/nodes'; -import { getShardStats } from '../../../../lib/elasticsearch/shards'; +import { getNodesShardCount } from '../../../../lib/elasticsearch/shards/get_nodes_shard_count'; import { handleError } from '../../../../lib/errors/handle_error'; import { prefixIndexPattern } from '../../../../lib/ccs_utils'; import { INDEX_PATTERN_ELASTICSEARCH } from '../../../../../common/constants'; import { getPaginatedNodes } from '../../../../lib/elasticsearch/nodes/get_nodes/get_paginated_nodes'; import { LISTING_METRICS_NAMES } from '../../../../lib/elasticsearch/nodes/get_nodes/nodes_listing_metrics'; +import { getIndicesUnassignedShardStats } from '../../../../lib/elasticsearch/shards/get_indices_unassigned_shard_stats'; export function esNodesRoute(server) { server.route({ @@ -53,10 +54,13 @@ export function esNodesRoute(server) { try { const clusterStats = await getClusterStats(req, esIndexPattern, clusterUuid); - const shardStats = await getShardStats(req, esIndexPattern, clusterStats, { - includeNodes: true, - }); - const clusterStatus = getClusterStatus(clusterStats, shardStats); + const nodesShardCount = await getNodesShardCount(req, esIndexPattern, clusterStats); + const indicesUnassignedShardStats = await getIndicesUnassignedShardStats( + req, + esIndexPattern, + clusterStats + ); + const clusterStatus = getClusterStatus(clusterStats, indicesUnassignedShardStats); const metricSet = LISTING_METRICS_NAMES; const { pageOfNodes, totalNodeCount } = await getPaginatedNodes( @@ -69,11 +73,17 @@ export function esNodesRoute(server) { queryText, { clusterStats, - shardStats, + nodesShardCount, } ); - const nodes = await getNodes(req, esIndexPattern, pageOfNodes, clusterStats, shardStats); + const nodes = await getNodes( + req, + esIndexPattern, + pageOfNodes, + clusterStats, + nodesShardCount + ); return { clusterStatus, nodes, totalNodeCount }; } catch (err) { throw handleError(err, req); diff --git a/x-pack/legacy/plugins/monitoring/server/routes/api/v1/elasticsearch/overview.js b/x-pack/legacy/plugins/monitoring/server/routes/api/v1/elasticsearch/overview.js index 9022471dfb7f8..b0045502fa228 100644 --- a/x-pack/legacy/plugins/monitoring/server/routes/api/v1/elasticsearch/overview.js +++ b/x-pack/legacy/plugins/monitoring/server/routes/api/v1/elasticsearch/overview.js @@ -9,7 +9,6 @@ import { getClusterStats } from '../../../../lib/cluster/get_cluster_stats'; import { getClusterStatus } from '../../../../lib/cluster/get_cluster_status'; import { getLastRecovery } from '../../../../lib/elasticsearch/get_last_recovery'; import { getMetrics } from '../../../../lib/details/get_metrics'; -import { getShardStats } from '../../../../lib/elasticsearch/shards'; import { handleError } from '../../../../lib/errors/handle_error'; import { prefixIndexPattern } from '../../../../lib/ccs_utils'; import { metricSet } from './metric_set_overview'; @@ -18,6 +17,7 @@ import { INDEX_PATTERN_FILEBEAT, } from '../../../../../common/constants'; import { getLogs } from '../../../../lib/logs'; +import { getIndicesUnassignedShardStats } from '../../../../lib/elasticsearch/shards/get_indices_unassigned_shard_stats'; export function esOverviewRoute(server) { server.route({ @@ -54,10 +54,14 @@ export function esOverviewRoute(server) { getLastRecovery(req, esIndexPattern), getLogs(config, req, filebeatIndexPattern, { clusterUuid, start, end }), ]); - const shardStats = await getShardStats(req, esIndexPattern, clusterStats); + const indicesUnassignedShardStats = await getIndicesUnassignedShardStats( + req, + esIndexPattern, + clusterStats + ); return { - clusterStatus: getClusterStatus(clusterStats, shardStats), + clusterStatus: getClusterStatus(clusterStats, indicesUnassignedShardStats), metrics, logs, shardActivity, diff --git a/x-pack/legacy/plugins/monitoring/server/routes/api/v1/setup/cluster_setup_status.js b/x-pack/legacy/plugins/monitoring/server/routes/api/v1/setup/cluster_setup_status.js index 334fcc4ccc26f..2b6f3b6e71d0f 100644 --- a/x-pack/legacy/plugins/monitoring/server/routes/api/v1/setup/cluster_setup_status.js +++ b/x-pack/legacy/plugins/monitoring/server/routes/api/v1/setup/cluster_setup_status.js @@ -34,6 +34,7 @@ export function clusterSetupStatusRoute(server) { skipLiveData: Joi.boolean().default(false), }), payload: Joi.object({ + ccs: Joi.string().optional(), timeRange: Joi.object({ min: Joi.date().required(), max: Joi.date().required(), @@ -49,7 +50,7 @@ export function clusterSetupStatusRoute(server) { // the monitoring data. `try/catch` makes it a little more explicit. try { await verifyMonitoringAuth(req); - const indexPatterns = getIndexPatterns(server); + const indexPatterns = getIndexPatterns(server, {}, req.payload.ccs); status = await getCollectionStatus( req, indexPatterns, diff --git a/x-pack/legacy/plugins/monitoring/server/routes/api/v1/setup/node_setup_status.js b/x-pack/legacy/plugins/monitoring/server/routes/api/v1/setup/node_setup_status.js index 01715497cbec4..2a615b887500d 100644 --- a/x-pack/legacy/plugins/monitoring/server/routes/api/v1/setup/node_setup_status.js +++ b/x-pack/legacy/plugins/monitoring/server/routes/api/v1/setup/node_setup_status.js @@ -34,6 +34,7 @@ export function nodeSetupStatusRoute(server) { skipLiveData: Joi.boolean().default(false), }), payload: Joi.object({ + ccs: Joi.string().optional(), timeRange: Joi.object({ min: Joi.date().required(), max: Joi.date().required(), @@ -49,7 +50,7 @@ export function nodeSetupStatusRoute(server) { // the monitoring data. `try/catch` makes it a little more explicit. try { await verifyMonitoringAuth(req); - const indexPatterns = getIndexPatterns(server); + const indexPatterns = getIndexPatterns(server, {}, req.payload.ccs); status = await getCollectionStatus( req, indexPatterns, diff --git a/x-pack/legacy/plugins/monitoring/server/telemetry_collection/__tests__/fixtures/beats_stats_results.json b/x-pack/legacy/plugins/monitoring/server/telemetry_collection/__tests__/fixtures/beats_stats_results.json index 1f17a7b78a29e..584618057256a 100644 --- a/x-pack/legacy/plugins/monitoring/server/telemetry_collection/__tests__/fixtures/beats_stats_results.json +++ b/x-pack/legacy/plugins/monitoring/server/telemetry_collection/__tests__/fixtures/beats_stats_results.json @@ -2,6 +2,36 @@ { "hits": { "hits": [ + { + "_source" : { + "cluster_uuid": "W7hppdX7R229Oy3KQbZrTw", + "type": "beats_state", + "beats_state" : { + "state" : { + "functionbeat" : { + "functions": { + "count": 1 + } + } + } + } + } + }, + { + "_source" : { + "cluster_uuid": "W7hppdX7R229Oy3KQbZrTw", + "type": "beats_state", + "beats_state" : { + "state" : { + "functionbeat" : { + "functions": { + "count": 3 + } + } + } + } + } + }, { "_source" : { "cluster_uuid": "W7hppdX7R229Oy3KQbZrTw", diff --git a/x-pack/legacy/plugins/monitoring/server/telemetry_collection/__tests__/get_beats_stats.js b/x-pack/legacy/plugins/monitoring/server/telemetry_collection/__tests__/get_beats_stats.js index 7734441a302c3..522be71555fba 100644 --- a/x-pack/legacy/plugins/monitoring/server/telemetry_collection/__tests__/get_beats_stats.js +++ b/x-pack/legacy/plugins/monitoring/server/telemetry_collection/__tests__/get_beats_stats.js @@ -168,6 +168,11 @@ describe('Get Beats Stats', () => { }, monitors: 3, }, + functionbeat: { + functions: { + count: 4, + }, + }, }, FlV4ckTxQ0a78hmBkzzc9A: { count: 405, diff --git a/x-pack/legacy/plugins/monitoring/server/telemetry_collection/get_beats_stats.js b/x-pack/legacy/plugins/monitoring/server/telemetry_collection/get_beats_stats.js index 94f710d51cc35..5722228b60207 100644 --- a/x-pack/legacy/plugins/monitoring/server/telemetry_collection/get_beats_stats.js +++ b/x-pack/legacy/plugins/monitoring/server/telemetry_collection/get_beats_stats.js @@ -138,6 +138,23 @@ export function processResults( } } + const functionbeatState = get(hit, '_source.beats_state.state.functionbeat'); + if (functionbeatState !== undefined) { + if (!clusters[clusterUuid].hasOwnProperty('functionbeat')) { + clusters[clusterUuid].functionbeat = { + functions: { + count: 0, + }, + }; + } + + clusters[clusterUuid].functionbeat.functions.count += get( + functionbeatState, + 'functions.count', + 0 + ); + } + const stateHost = get(hit, '_source.beats_state.state.host'); if (stateHost !== undefined) { const hostMap = clusterArchitectureMaps[clusterUuid]; diff --git a/x-pack/legacy/plugins/oss_telemetry/index.ts b/x-pack/legacy/plugins/oss_telemetry/index.ts index 8b16c7cf13cad..fce861c7d3f46 100644 --- a/x-pack/legacy/plugins/oss_telemetry/index.ts +++ b/x-pack/legacy/plugins/oss_telemetry/index.ts @@ -4,10 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Logger, PluginInitializerContext } from 'kibana/server'; +import { Logger, PluginInitializerContext, CoreStart } from 'kibana/server'; +import { Legacy } from 'kibana'; import { PLUGIN_ID } from './constants'; import { OssTelemetryPlugin } from './server/plugin'; import { LegacyPluginInitializer } from '../../../../src/legacy/plugin_discovery/types'; +import { getTaskManagerSetup, getTaskManagerStart } from '../task_manager/server'; export const ossTelemetry: LegacyPluginInitializer = kibana => { return new kibana.Plugin({ @@ -15,7 +17,7 @@ export const ossTelemetry: LegacyPluginInitializer = kibana => { require: ['elasticsearch', 'xpack_main'], configPrefix: 'xpack.oss_telemetry', - init(server) { + init(server: Legacy.Server) { const plugin = new OssTelemetryPlugin({ logger: { get: () => @@ -27,14 +29,24 @@ export const ossTelemetry: LegacyPluginInitializer = kibana => { } as Logger), }, } as PluginInitializerContext); - plugin.setup(server.newPlatform.setup.core, { + + const deps = { usageCollection: server.newPlatform.setup.plugins.usageCollection, - taskManager: server.plugins.task_manager, __LEGACY: { config: server.config(), xpackMainStatus: ((server.plugins.xpack_main as unknown) as { status: any }).status .plugin, }, + }; + + plugin.setup(server.newPlatform.setup.core, { + ...deps, + taskManager: getTaskManagerSetup(server), + }); + + plugin.start((server.newPlatform.setup.core as unknown) as CoreStart, { + ...deps, + taskManager: getTaskManagerStart(server), }); }, }); diff --git a/x-pack/legacy/plugins/oss_telemetry/server/lib/collectors/index.ts b/x-pack/legacy/plugins/oss_telemetry/server/lib/collectors/index.ts index 3b47099fdc462..9d547c1b22099 100644 --- a/x-pack/legacy/plugins/oss_telemetry/server/lib/collectors/index.ts +++ b/x-pack/legacy/plugins/oss_telemetry/server/lib/collectors/index.ts @@ -5,8 +5,8 @@ */ import { registerVisualizationsCollector } from './visualizations/register_usage_collector'; -import { OssTelemetrySetupDependencies } from '../../plugin'; +import { OssTelemetryStartDependencies } from '../../plugin'; -export function registerCollectors(deps: OssTelemetrySetupDependencies) { +export function registerCollectors(deps: OssTelemetryStartDependencies) { registerVisualizationsCollector(deps.usageCollection, deps.taskManager); } diff --git a/x-pack/legacy/plugins/oss_telemetry/server/lib/collectors/visualizations/get_usage_collector.test.ts b/x-pack/legacy/plugins/oss_telemetry/server/lib/collectors/visualizations/get_usage_collector.test.ts index ec35266646650..ce106d1a64fd6 100644 --- a/x-pack/legacy/plugins/oss_telemetry/server/lib/collectors/visualizations/get_usage_collector.test.ts +++ b/x-pack/legacy/plugins/oss_telemetry/server/lib/collectors/visualizations/get_usage_collector.test.ts @@ -4,29 +4,37 @@ * you may not use this file except in compliance with the Elastic License. */ -import { getMockTaskFetch, getMockTaskManager } from '../../../../test_utils'; +import { + getMockTaskFetch, + getMockThrowingTaskFetch, + getMockTaskInstance, +} from '../../../../test_utils'; +import { taskManagerMock } from '../../../../../../../plugins/task_manager/server/task_manager.mock'; import { getUsageCollector } from './get_usage_collector'; describe('getVisualizationsCollector#fetch', () => { test('can return empty stats', async () => { - const { type, fetch } = getUsageCollector(getMockTaskManager()); + const { type, fetch } = getUsageCollector(taskManagerMock.start(getMockTaskFetch())); expect(type).toBe('visualization_types'); const fetchResult = await fetch(); expect(fetchResult).toEqual({}); }); test('provides known stats', async () => { - const mockTaskFetch = getMockTaskFetch([ - { - state: { - runs: 1, - stats: { comic_books: { total: 16, max: 12, min: 2, avg: 6 } }, - }, - taskType: 'test', - params: {}, - }, - ]); - const { type, fetch } = getUsageCollector(getMockTaskManager(mockTaskFetch)); + const { type, fetch } = getUsageCollector( + taskManagerMock.start( + getMockTaskFetch([ + getMockTaskInstance({ + state: { + runs: 1, + stats: { comic_books: { total: 16, max: 12, min: 2, avg: 6 } }, + }, + taskType: 'test', + params: {}, + }), + ]) + ) + ); expect(type).toBe('visualization_types'); const fetchResult = await fetch(); expect(fetchResult).toEqual({ comic_books: { avg: 6, max: 12, min: 2, total: 16 } }); @@ -34,20 +42,21 @@ describe('getVisualizationsCollector#fetch', () => { describe('Error handling', () => { test('Silently handles Task Manager NotInitialized', async () => { - const mockTaskFetch = jest.fn(() => { - throw new Error('NotInitialized taskManager is still waiting for plugins to load'); - }); - const { fetch } = getUsageCollector(getMockTaskManager(mockTaskFetch)); + const { fetch } = getUsageCollector( + taskManagerMock.start( + getMockThrowingTaskFetch( + new Error('NotInitialized taskManager is still waiting for plugins to load') + ) + ) + ); const result = await fetch(); expect(result).toBe(undefined); }); // In real life, the CollectorSet calls fetch and handles errors test('defers the errors', async () => { - const mockTaskFetch = jest.fn(() => { - throw new Error('BOOM'); - }); - - const { fetch } = getUsageCollector(getMockTaskManager(mockTaskFetch)); + const { fetch } = getUsageCollector( + taskManagerMock.start(getMockThrowingTaskFetch(new Error('BOOM'))) + ); await expect(fetch()).rejects.toThrowErrorMatchingInlineSnapshot(`"BOOM"`); }); }); diff --git a/x-pack/legacy/plugins/oss_telemetry/server/lib/collectors/visualizations/get_usage_collector.ts b/x-pack/legacy/plugins/oss_telemetry/server/lib/collectors/visualizations/get_usage_collector.ts index 680cb97e0fda3..bc0d10860a667 100644 --- a/x-pack/legacy/plugins/oss_telemetry/server/lib/collectors/visualizations/get_usage_collector.ts +++ b/x-pack/legacy/plugins/oss_telemetry/server/lib/collectors/visualizations/get_usage_collector.ts @@ -5,15 +5,15 @@ */ import { get } from 'lodash'; -import { PluginSetupContract as TaskManagerPluginSetupContract } from '../../../../../task_manager/plugin'; import { PLUGIN_ID, VIS_TELEMETRY_TASK, VIS_USAGE_TYPE } from '../../../../constants'; +import { TaskManagerStartContract } from '../../../../../../../plugins/task_manager/server'; -async function isTaskManagerReady(taskManager: TaskManagerPluginSetupContract | undefined) { +async function isTaskManagerReady(taskManager?: TaskManagerStartContract) { const result = await fetch(taskManager); return result !== null; } -async function fetch(taskManager: TaskManagerPluginSetupContract | undefined) { +async function fetch(taskManager?: TaskManagerStartContract) { if (!taskManager) { return null; } @@ -38,7 +38,7 @@ async function fetch(taskManager: TaskManagerPluginSetupContract | undefined) { return docs; } -export function getUsageCollector(taskManager: TaskManagerPluginSetupContract | undefined) { +export function getUsageCollector(taskManager?: TaskManagerStartContract) { let isCollectorReady = false; async function determineIfTaskManagerIsReady() { let isReady = false; diff --git a/x-pack/legacy/plugins/oss_telemetry/server/lib/collectors/visualizations/register_usage_collector.ts b/x-pack/legacy/plugins/oss_telemetry/server/lib/collectors/visualizations/register_usage_collector.ts index 1a47f68adcc58..657f1c725f4e0 100644 --- a/x-pack/legacy/plugins/oss_telemetry/server/lib/collectors/visualizations/register_usage_collector.ts +++ b/x-pack/legacy/plugins/oss_telemetry/server/lib/collectors/visualizations/register_usage_collector.ts @@ -5,12 +5,12 @@ */ import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; -import { PluginSetupContract as TaskManagerPluginSetupContract } from '../../../../../task_manager/plugin'; +import { TaskManagerStartContract } from '../../../../../../../plugins/task_manager/server'; import { getUsageCollector } from './get_usage_collector'; export function registerVisualizationsCollector( collectorSet: UsageCollectionSetup, - taskManager: TaskManagerPluginSetupContract | undefined + taskManager?: TaskManagerStartContract ): void { const collector = collectorSet.makeUsageCollector(getUsageCollector(taskManager)); collectorSet.registerCollector(collector); diff --git a/x-pack/legacy/plugins/oss_telemetry/server/lib/tasks/index.ts b/x-pack/legacy/plugins/oss_telemetry/server/lib/tasks/index.ts index cb6b4eab09741..cf7295f67a231 100644 --- a/x-pack/legacy/plugins/oss_telemetry/server/lib/tasks/index.ts +++ b/x-pack/legacy/plugins/oss_telemetry/server/lib/tasks/index.ts @@ -5,12 +5,15 @@ */ import { CoreSetup, Logger } from 'kibana/server'; -import { PluginSetupContract as TaskManagerPluginSetupContract } from '../../../../task_manager/plugin'; import { PLUGIN_ID, VIS_TELEMETRY_TASK } from '../../../constants'; import { visualizationsTaskRunner } from './visualizations/task_runner'; import KbnServer from '../../../../../../../src/legacy/server/kbn_server'; import { LegacyConfig } from '../../plugin'; -import { TaskInstance } from '../../../../task_manager'; +import { + TaskInstance, + TaskManagerStartContract, + TaskManagerSetupContract, +} from '../../../../../../plugins/task_manager/server'; export function registerTasks({ taskManager, @@ -18,7 +21,7 @@ export function registerTasks({ elasticsearch, config, }: { - taskManager?: TaskManagerPluginSetupContract; + taskManager?: TaskManagerSetupContract; logger: Logger; elasticsearch: CoreSetup['elasticsearch']; config: LegacyConfig; @@ -46,7 +49,7 @@ export function scheduleTasks({ xpackMainStatus, logger, }: { - taskManager?: TaskManagerPluginSetupContract; + taskManager?: TaskManagerStartContract; xpackMainStatus: { kbnServer: KbnServer }; logger: Logger; }) { diff --git a/x-pack/legacy/plugins/oss_telemetry/server/lib/tasks/visualizations/task_runner.test.ts b/x-pack/legacy/plugins/oss_telemetry/server/lib/tasks/visualizations/task_runner.test.ts index 0663a5bd330ca..ef03e857de8ef 100644 --- a/x-pack/legacy/plugins/oss_telemetry/server/lib/tasks/visualizations/task_runner.test.ts +++ b/x-pack/legacy/plugins/oss_telemetry/server/lib/tasks/visualizations/task_runner.test.ts @@ -12,7 +12,7 @@ import { getMockTaskInstance, } from '../../../../test_utils'; import { visualizationsTaskRunner } from './task_runner'; -import { TaskInstance } from '../../../../../task_manager'; +import { TaskInstance } from '../../../../../../../plugins/task_manager/server'; describe('visualizationsTaskRunner', () => { let mockTaskInstance: TaskInstance; diff --git a/x-pack/legacy/plugins/oss_telemetry/server/lib/tasks/visualizations/task_runner.ts b/x-pack/legacy/plugins/oss_telemetry/server/lib/tasks/visualizations/task_runner.ts index 9d8f76f6a10dc..0b7b301df12bf 100644 --- a/x-pack/legacy/plugins/oss_telemetry/server/lib/tasks/visualizations/task_runner.ts +++ b/x-pack/legacy/plugins/oss_telemetry/server/lib/tasks/visualizations/task_runner.ts @@ -8,7 +8,7 @@ import _, { countBy, groupBy, mapValues } from 'lodash'; import { APICaller, CoreSetup } from 'kibana/server'; import { getNextMidnight } from '../../get_next_midnight'; import { VisState } from '../../../../../../../../src/legacy/core_plugins/visualizations/public'; -import { TaskInstance } from '../../../../../task_manager'; +import { TaskInstance } from '../../../../../../../plugins/task_manager/server'; import { ESSearchHit } from '../../../../../apm/typings/elasticsearch'; import { LegacyConfig } from '../../../plugin'; diff --git a/x-pack/legacy/plugins/oss_telemetry/server/plugin.ts b/x-pack/legacy/plugins/oss_telemetry/server/plugin.ts index f661311fc24b8..0aac319cf5818 100644 --- a/x-pack/legacy/plugins/oss_telemetry/server/plugin.ts +++ b/x-pack/legacy/plugins/oss_telemetry/server/plugin.ts @@ -4,8 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ -import { CoreSetup, Logger, Plugin, PluginInitializerContext } from 'kibana/server'; -import { PluginSetupContract as TaskManagerPluginSetupContract } from '../../task_manager/plugin'; +import { CoreSetup, CoreStart, Logger, Plugin, PluginInitializerContext } from 'kibana/server'; +import { + TaskManagerSetupContract, + TaskManagerStartContract, +} from '../../../../plugins/task_manager/server'; import { registerCollectors } from './lib/collectors'; import { registerTasks, scheduleTasks } from './lib/tasks'; import KbnServer from '../../../../../src/legacy/server/kbn_server'; @@ -15,13 +18,18 @@ export interface LegacyConfig { get: (key: string) => string | number | boolean; } -export interface OssTelemetrySetupDependencies { +interface OssTelemetryDependencies { usageCollection: UsageCollectionSetup; __LEGACY: { config: LegacyConfig; xpackMainStatus: { kbnServer: KbnServer }; }; - taskManager?: TaskManagerPluginSetupContract; +} +export interface OssTelemetrySetupDependencies extends OssTelemetryDependencies { + taskManager?: TaskManagerSetupContract; +} +export interface OssTelemetryStartDependencies extends OssTelemetryDependencies { + taskManager?: TaskManagerStartContract; } export class OssTelemetryPlugin implements Plugin { @@ -32,19 +40,20 @@ export class OssTelemetryPlugin implements Plugin { } public setup(core: CoreSetup, deps: OssTelemetrySetupDependencies) { - registerCollectors(deps); registerTasks({ taskManager: deps.taskManager, logger: this.logger, elasticsearch: core.elasticsearch, config: deps.__LEGACY.config, }); + } + + public start(core: CoreStart, deps: OssTelemetryStartDependencies) { + registerCollectors(deps); scheduleTasks({ taskManager: deps.taskManager, xpackMainStatus: deps.__LEGACY.xpackMainStatus, logger: this.logger, }); } - - public start() {} } diff --git a/x-pack/legacy/plugins/oss_telemetry/test_utils/index.ts b/x-pack/legacy/plugins/oss_telemetry/test_utils/index.ts index 04e248d28b577..0695fda3c2c94 100644 --- a/x-pack/legacy/plugins/oss_telemetry/test_utils/index.ts +++ b/x-pack/legacy/plugins/oss_telemetry/test_utils/index.ts @@ -6,13 +6,28 @@ import { APICaller, CoreSetup } from 'kibana/server'; -import { TaskInstance } from '../../task_manager'; -import { PluginSetupContract as TaskManagerPluginSetupContract } from '../../task_manager/plugin'; +import { + ConcreteTaskInstance, + TaskStatus, + TaskManagerStartContract, + // eslint-disable-next-line @kbn/eslint/no-restricted-paths +} from '../../../../plugins/task_manager/server'; -export const getMockTaskInstance = (): TaskInstance => ({ +export const getMockTaskInstance = ( + overrides: Partial = {} +): ConcreteTaskInstance => ({ state: { runs: 0, stats: {} }, taskType: 'test', params: {}, + id: '', + scheduledAt: new Date(), + attempts: 1, + status: TaskStatus.Idle, + runAt: new Date(), + startedAt: null, + retryAt: null, + ownerId: null, + ...overrides, }); const defaultMockSavedObjects = [ @@ -38,8 +53,24 @@ export const getMockCallWithInternal = (hits: unknown[] = defaultMockSavedObject }) as unknown) as APICaller; }; -export const getMockTaskFetch = (docs: TaskInstance[] = defaultMockTaskDocs) => { - return () => Promise.resolve({ docs }); +export const getMockTaskFetch = ( + docs: ConcreteTaskInstance[] = defaultMockTaskDocs +): Partial> => { + return { + fetch: jest.fn(fetchOpts => { + return Promise.resolve({ docs, searchAfter: [] }); + }), + } as Partial>; +}; + +export const getMockThrowingTaskFetch = ( + throws: Error +): Partial> => { + return { + fetch: jest.fn(fetchOpts => { + throw throws; + }), + } as Partial>; }; export const getMockConfig = () => { @@ -48,13 +79,6 @@ export const getMockConfig = () => { }; }; -export const getMockTaskManager = (fetch: any = getMockTaskFetch()) => - (({ - registerTaskDefinitions: () => undefined, - ensureScheduled: () => Promise.resolve(), - fetch, - } as unknown) as TaskManagerPluginSetupContract); - export const getCluster = () => ({ callWithInternalUser: getMockCallWithInternal(), }); diff --git a/x-pack/legacy/plugins/reporting/config.ts b/x-pack/legacy/plugins/reporting/config.ts index 8b1d6f6f19805..34fc1f452fbc0 100644 --- a/x-pack/legacy/plugins/reporting/config.ts +++ b/x-pack/legacy/plugins/reporting/config.ts @@ -14,7 +14,7 @@ export async function config(Joi: any) { enabled: Joi.boolean().default(true), kibanaServer: Joi.object({ protocol: Joi.string().valid(['http', 'https']), - hostname: Joi.string(), + hostname: Joi.string().invalid('0'), port: Joi.number().integer(), }).default(), queue: Joi.object({ diff --git a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/decrypt_job_headers.test.ts b/x-pack/legacy/plugins/reporting/export_types/common/execute_job/decrypt_job_headers.test.ts index 9ba7cfbcac1d8..1b7ba3c90bab1 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/decrypt_job_headers.test.ts +++ b/x-pack/legacy/plugins/reporting/export_types/common/execute_job/decrypt_job_headers.test.ts @@ -43,7 +43,7 @@ describe('headers', () => { }; const encryptedHeaders = await encryptHeaders(headers); - const { decryptedHeaders } = await decryptJobHeaders({ + const decryptedHeaders = await decryptJobHeaders({ job: { title: 'cool-job-bro', type: 'csv', diff --git a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/decrypt_job_headers.ts b/x-pack/legacy/plugins/reporting/export_types/common/execute_job/decrypt_job_headers.ts index 486181117bbfb..436b2c2dab1ad 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/decrypt_job_headers.ts +++ b/x-pack/legacy/plugins/reporting/export_types/common/execute_job/decrypt_job_headers.ts @@ -17,22 +17,18 @@ export const decryptJobHeaders = async < JobParamsType, JobDocPayloadType extends HasEncryptedHeaders >({ - job, server, + job, logger, }: { - job: JobDocPayloadType; server: ServerFacade; - logger: Logger; -}): Promise<{ job: JobDocPayloadType; - server: ServerFacade; - decryptedHeaders: Record; -}> => { + logger: Logger; +}): Promise> => { const crypto: CryptoFactory = cryptoFactory(server); try { const decryptedHeaders: Record = await crypto.decrypt(job.headers); - return { job, decryptedHeaders, server }; + return decryptedHeaders; } catch (err) { logger.error(err); diff --git a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_conditional_headers.test.ts b/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_conditional_headers.test.ts index 66990c1f37df4..070bdb4314af9 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_conditional_headers.test.ts +++ b/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_conditional_headers.test.ts @@ -27,7 +27,7 @@ describe('conditions', () => { baz: 'quix', }; - const { conditionalHeaders } = await getConditionalHeaders({ + const conditionalHeaders = await getConditionalHeaders({ job: {} as JobDocPayload, filteredHeaders: permittedHeaders, server: mockServer, @@ -44,7 +44,7 @@ describe('conditions', () => { baz: 'quix', }; - const { conditionalHeaders } = await getConditionalHeaders({ + const conditionalHeaders = await getConditionalHeaders({ job: {} as JobDocPayload, filteredHeaders: permittedHeaders, server: mockServer, @@ -65,7 +65,7 @@ describe('conditions', () => { baz: 'quix', }; - const { conditionalHeaders } = await getConditionalHeaders({ + const conditionalHeaders = await getConditionalHeaders({ job: {} as JobDocPayload, filteredHeaders: permittedHeaders, server: mockServer, @@ -82,7 +82,7 @@ describe('conditions', () => { baz: 'quix', }; - const { conditionalHeaders } = await getConditionalHeaders({ + const conditionalHeaders = await getConditionalHeaders({ job: {} as JobDocPayload, filteredHeaders: permittedHeaders, server: mockServer, @@ -97,7 +97,7 @@ describe('conditions', () => { baz: 'quix', }; - const { conditionalHeaders } = await getConditionalHeaders({ + const conditionalHeaders = await getConditionalHeaders({ job: {} as JobDocPayload, filteredHeaders: permittedHeaders, server: mockServer, @@ -120,7 +120,7 @@ describe('conditions', () => { baz: 'quix', }; - const { conditionalHeaders } = await getConditionalHeaders({ + const conditionalHeaders = await getConditionalHeaders({ job: {} as JobDocPayload, filteredHeaders: permittedHeaders, server: mockServer, @@ -137,7 +137,7 @@ describe('conditions', () => { baz: 'quix', }; - const { conditionalHeaders } = await getConditionalHeaders({ + const conditionalHeaders = await getConditionalHeaders({ job: {} as JobDocPayload, filteredHeaders: permittedHeaders, server: mockServer, @@ -153,7 +153,7 @@ test('uses basePath from job when creating saved object service', async () => { baz: 'quix', }; - const { conditionalHeaders } = await getConditionalHeaders({ + const conditionalHeaders = await getConditionalHeaders({ job: {} as JobDocPayload, filteredHeaders: permittedHeaders, server: mockServer, @@ -180,7 +180,7 @@ test(`uses basePath from server if job doesn't have a basePath when creating sav baz: 'quix', }; - const { conditionalHeaders } = await getConditionalHeaders({ + const conditionalHeaders = await getConditionalHeaders({ job: {} as JobDocPayload, filteredHeaders: permittedHeaders, server: mockServer, @@ -203,7 +203,7 @@ test(`uses basePath from server if job doesn't have a basePath when creating sav describe('config formatting', () => { test(`lowercases server.host`, async () => { mockServer = createMockServer({ settings: { 'server.host': 'COOL-HOSTNAME' } }); - const { conditionalHeaders } = await getConditionalHeaders({ + const conditionalHeaders = await getConditionalHeaders({ job: {} as JobDocPayload, filteredHeaders: {}, server: mockServer, @@ -215,7 +215,7 @@ describe('config formatting', () => { mockServer = createMockServer({ settings: { 'xpack.reporting.kibanaServer.hostname': 'GREAT-HOSTNAME' }, }); - const { conditionalHeaders } = await getConditionalHeaders({ + const conditionalHeaders = await getConditionalHeaders({ job: { title: 'cool-job-bro', type: 'csv', diff --git a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_conditional_headers.ts b/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_conditional_headers.ts index 14c092ccfb4a6..975060a8052f0 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_conditional_headers.ts +++ b/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_conditional_headers.ts @@ -6,13 +6,13 @@ import { ConditionalHeaders, ServerFacade } from '../../../types'; export const getConditionalHeaders = ({ + server, job, filteredHeaders, - server, }: { + server: ServerFacade; job: JobDocPayloadType; filteredHeaders: Record; - server: ServerFacade; }) => { const config = server.config(); const [hostname, port, basePath, protocol] = [ @@ -32,5 +32,5 @@ export const getConditionalHeaders = ({ }, }; - return { job, conditionalHeaders, server }; + return conditionalHeaders; }; diff --git a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_custom_logo.test.ts b/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_custom_logo.test.ts index 4fca05337ea0c..ff2c44026315d 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_custom_logo.test.ts +++ b/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_custom_logo.test.ts @@ -19,7 +19,7 @@ test(`gets logo from uiSettings`, async () => { baz: 'quix', }; - const { conditionalHeaders } = await getConditionalHeaders({ + const conditionalHeaders = await getConditionalHeaders({ job: {} as JobDocPayloadPDF, filteredHeaders: permittedHeaders, server: mockServer, diff --git a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_custom_logo.ts b/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_custom_logo.ts index 9b64e896dad18..0059276f6df71 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_custom_logo.ts +++ b/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_custom_logo.ts @@ -9,13 +9,13 @@ import { ConditionalHeaders, ServerFacade } from '../../../types'; import { JobDocPayloadPDF } from '../../printable_pdf/types'; // Logo is PDF only export const getCustomLogo = async ({ + server, job, conditionalHeaders, - server, }: { + server: ServerFacade; job: JobDocPayloadPDF; conditionalHeaders: ConditionalHeaders; - server: ServerFacade; }) => { const serverBasePath: string = server.config().get('server.basePath'); @@ -38,12 +38,8 @@ export const getCustomLogo = async ({ }; const savedObjects = server.savedObjects; - const savedObjectsClient = savedObjects.getScopedSavedObjectsClient(fakeRequest); - const uiSettings = server.uiSettingsServiceFactory({ savedObjectsClient }); - - const logo = await uiSettings.get(UI_SETTINGS_CUSTOM_PDF_LOGO); - - return { job, conditionalHeaders, logo, server }; + const logo: string = await uiSettings.get(UI_SETTINGS_CUSTOM_PDF_LOGO); + return { conditionalHeaders, logo }; }; diff --git a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_full_urls.test.ts b/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_full_urls.test.ts index 60735b4abd446..9b2a065427f70 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_full_urls.test.ts +++ b/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_full_urls.test.ts @@ -22,29 +22,26 @@ beforeEach(() => { }); test(`fails if no URL is passed`, async () => { - await expect( + const fn = () => getFullUrls({ job: {}, server: mockServer, - } as FullUrlsOpts) - ).rejects.toMatchInlineSnapshot( - `[Error: No valid URL fields found in Job Params! Expected \`job.relativeUrl\` or \`job.objects[{ relativeUrl }]\`]` + } as FullUrlsOpts); + expect(fn).toThrowErrorMatchingInlineSnapshot( + `"No valid URL fields found in Job Params! Expected \`job.relativeUrl: string\` or \`job.relativeUrls: string[]\`"` ); }); test(`fails if URLs are file-protocols for PNGs`, async () => { const forceNow = '2000-01-01T00:00:00.000Z'; const relativeUrl = 'file://etc/passwd/#/something'; - await expect( + const fn = () => getFullUrls({ - job: { - relativeUrl, - forceNow, - }, + job: { relativeUrl, forceNow }, server: mockServer, - } as FullUrlsOpts) - ).rejects.toMatchInlineSnapshot( - `[Error: Found invalid URL(s), all URLs must be relative: ${relativeUrl}]` + } as FullUrlsOpts); + expect(fn).toThrowErrorMatchingInlineSnapshot( + `"Found invalid URL(s), all URLs must be relative: file://etc/passwd/#/something"` ); }); @@ -52,36 +49,29 @@ test(`fails if URLs are absolute for PNGs`, async () => { const forceNow = '2000-01-01T00:00:00.000Z'; const relativeUrl = 'http://169.254.169.254/latest/meta-data/iam/security-credentials/profileName/#/something'; - await expect( + const fn = () => getFullUrls({ - job: { - relativeUrl, - forceNow, - }, + job: { relativeUrl, forceNow }, server: mockServer, - } as FullUrlsOpts) - ).rejects.toMatchInlineSnapshot( - `[Error: Found invalid URL(s), all URLs must be relative: ${relativeUrl}]` + } as FullUrlsOpts); + expect(fn).toThrowErrorMatchingInlineSnapshot( + `"Found invalid URL(s), all URLs must be relative: http://169.254.169.254/latest/meta-data/iam/security-credentials/profileName/#/something"` ); }); test(`fails if URLs are file-protocols for PDF`, async () => { const forceNow = '2000-01-01T00:00:00.000Z'; const relativeUrl = 'file://etc/passwd/#/something'; - await expect( + const fn = () => getFullUrls({ job: { - objects: [ - { - relativeUrl, - }, - ], + relativeUrls: [relativeUrl], forceNow, }, server: mockServer, - } as FullUrlsOpts) - ).rejects.toMatchInlineSnapshot( - `[Error: Found invalid URL(s), all URLs must be relative: ${relativeUrl}]` + } as FullUrlsOpts); + expect(fn).toThrowErrorMatchingInlineSnapshot( + `"Found invalid URL(s), all URLs must be relative: file://etc/passwd/#/something"` ); }); @@ -89,70 +79,52 @@ test(`fails if URLs are absolute for PDF`, async () => { const forceNow = '2000-01-01T00:00:00.000Z'; const relativeUrl = 'http://169.254.169.254/latest/meta-data/iam/security-credentials/profileName/#/something'; - await expect( + const fn = () => getFullUrls({ job: { - objects: [ - { - relativeUrl, - }, - ], + relativeUrls: [relativeUrl], forceNow, }, server: mockServer, - } as FullUrlsOpts) - ).rejects.toMatchInlineSnapshot( - `[Error: Found invalid URL(s), all URLs must be relative: ${relativeUrl}]` + } as FullUrlsOpts); + expect(fn).toThrowErrorMatchingInlineSnapshot( + `"Found invalid URL(s), all URLs must be relative: http://169.254.169.254/latest/meta-data/iam/security-credentials/profileName/#/something"` ); }); test(`fails if any URLs are absolute or file's for PDF`, async () => { const forceNow = '2000-01-01T00:00:00.000Z'; - const objects = [ - { - relativeUrl: '/app/kibana#/something_aaa', - }, - { - relativeUrl: - 'http://169.254.169.254/latest/meta-data/iam/security-credentials/profileName/#/something', - }, - { - relativeUrl: 'file://etc/passwd/#/something', - }, + const relativeUrls = [ + '/app/kibana#/something_aaa', + 'http://169.254.169.254/latest/meta-data/iam/security-credentials/profileName/#/something', + 'file://etc/passwd/#/something', ]; - await expect( + + const fn = () => getFullUrls({ - job: { - objects, - forceNow, - }, + job: { relativeUrls, forceNow }, server: mockServer, - } as FullUrlsOpts) - ).rejects.toMatchInlineSnapshot( - `[Error: Found invalid URL(s), all URLs must be relative: ${objects[1].relativeUrl} ${objects[2].relativeUrl}]` + } as FullUrlsOpts); + expect(fn).toThrowErrorMatchingInlineSnapshot( + `"Found invalid URL(s), all URLs must be relative: http://169.254.169.254/latest/meta-data/iam/security-credentials/profileName/#/something file://etc/passwd/#/something"` ); }); test(`fails if URL does not route to a visualization`, async () => { - await expect( + const fn = () => getFullUrls({ - job: { - relativeUrl: '/app/phoney', - }, + job: { relativeUrl: '/app/phoney' }, server: mockServer, - } as FullUrlsOpts) - ).rejects.toMatchInlineSnapshot( - `[Error: No valid hash in the URL! A hash is expected for the application to route to the intended visualization.]` + } as FullUrlsOpts); + expect(fn).toThrowErrorMatchingInlineSnapshot( + `"No valid hash in the URL! A hash is expected for the application to route to the intended visualization."` ); }); test(`adds forceNow to hash's query, if it exists`, async () => { const forceNow = '2000-01-01T00:00:00.000Z'; - const { urls } = await getFullUrls({ - job: { - relativeUrl: '/app/kibana#/something', - forceNow, - }, + const urls = await getFullUrls({ + job: { relativeUrl: '/app/kibana#/something', forceNow }, server: mockServer, } as FullUrlsOpts); @@ -164,11 +136,8 @@ test(`adds forceNow to hash's query, if it exists`, async () => { test(`appends forceNow to hash's query, if it exists`, async () => { const forceNow = '2000-01-01T00:00:00.000Z'; - const { urls } = await getFullUrls({ - job: { - relativeUrl: '/app/kibana#/something?_g=something', - forceNow, - }, + const urls = await getFullUrls({ + job: { relativeUrl: '/app/kibana#/something?_g=something', forceNow }, server: mockServer, } as FullUrlsOpts); @@ -178,10 +147,8 @@ test(`appends forceNow to hash's query, if it exists`, async () => { }); test(`doesn't append forceNow query to url, if it doesn't exists`, async () => { - const { urls } = await getFullUrls({ - job: { - relativeUrl: '/app/kibana#/something', - }, + const urls = await getFullUrls({ + job: { relativeUrl: '/app/kibana#/something' }, server: mockServer, } as FullUrlsOpts); @@ -190,13 +157,13 @@ test(`doesn't append forceNow query to url, if it doesn't exists`, async () => { test(`adds forceNow to each of multiple urls`, async () => { const forceNow = '2000-01-01T00:00:00.000Z'; - const { urls } = await getFullUrls({ + const urls = await getFullUrls({ job: { - objects: [ - { relativeUrl: '/app/kibana#/something_aaa' }, - { relativeUrl: '/app/kibana#/something_bbb' }, - { relativeUrl: '/app/kibana#/something_ccc' }, - { relativeUrl: '/app/kibana#/something_ddd' }, + relativeUrls: [ + '/app/kibana#/something_aaa', + '/app/kibana#/something_bbb', + '/app/kibana#/something_ccc', + '/app/kibana#/something_ddd', ], forceNow, }, diff --git a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_full_urls.ts b/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_full_urls.ts index 59c748aba9662..ca64d8632dbfe 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_full_urls.ts +++ b/x-pack/legacy/plugins/reporting/export_types/common/execute_job/get_full_urls.ts @@ -12,7 +12,7 @@ import { } from 'url'; import { getAbsoluteUrlFactory } from '../../../common/get_absolute_url'; import { validateUrls } from '../../../common/validate_urls'; -import { ServerFacade, ConditionalHeaders } from '../../../types'; +import { ServerFacade } from '../../../types'; import { JobDocPayloadPNG } from '../../png/types'; import { JobDocPayloadPDF } from '../../printable_pdf/types'; @@ -20,18 +20,15 @@ function isPngJob(job: JobDocPayloadPNG | JobDocPayloadPDF): job is JobDocPayloa return (job as JobDocPayloadPNG).relativeUrl !== undefined; } function isPdfJob(job: JobDocPayloadPNG | JobDocPayloadPDF): job is JobDocPayloadPDF { - return (job as JobDocPayloadPDF).objects !== undefined; + return (job as JobDocPayloadPDF).relativeUrls !== undefined; } -export async function getFullUrls({ - job, +export function getFullUrls({ server, - ...mergeValues // pass-throughs + job, }: { - job: JobDocPayloadPDF | JobDocPayloadPNG; server: ServerFacade; - conditionalHeaders: ConditionalHeaders; - logo?: string; + job: JobDocPayloadPDF | JobDocPayloadPNG; }) { const config = server.config(); @@ -48,10 +45,10 @@ export async function getFullUrls({ if (isPngJob(job)) { relativeUrls = [job.relativeUrl]; } else if (isPdfJob(job)) { - relativeUrls = job.objects.map(obj => obj.relativeUrl); + relativeUrls = job.relativeUrls; } else { throw new Error( - `No valid URL fields found in Job Params! Expected \`job.relativeUrl\` or \`job.objects[{ relativeUrl }]\`` + `No valid URL fields found in Job Params! Expected \`job.relativeUrl: string\` or \`job.relativeUrls: string[]\`` ); } @@ -96,5 +93,5 @@ export async function getFullUrls({ }); }); - return { job, server, urls, ...mergeValues }; + return urls; } diff --git a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/omit_blacklisted_headers.test.ts b/x-pack/legacy/plugins/reporting/export_types/common/execute_job/omit_blacklisted_headers.test.ts index 4d4b0c8ade3f6..f446369fec78c 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/omit_blacklisted_headers.test.ts +++ b/x-pack/legacy/plugins/reporting/export_types/common/execute_job/omit_blacklisted_headers.test.ts @@ -4,14 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { createMockServer } from '../../../test_helpers/create_mock_server'; import { omitBlacklistedHeaders } from './index'; -let mockServer: any; -beforeEach(() => { - mockServer = createMockServer(''); -}); - test(`omits blacklisted headers`, async () => { const permittedHeaders = { foo: 'bar', @@ -27,7 +21,7 @@ test(`omits blacklisted headers`, async () => { 'transfer-encoding': '', }; - const { filteredHeaders } = await omitBlacklistedHeaders({ + const filteredHeaders = await omitBlacklistedHeaders({ job: { title: 'cool-job-bro', type: 'csv', @@ -41,7 +35,6 @@ test(`omits blacklisted headers`, async () => { ...permittedHeaders, ...blacklistedHeaders, }, - server: mockServer, }); expect(filteredHeaders).toEqual(permittedHeaders); diff --git a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/omit_blacklisted_headers.ts b/x-pack/legacy/plugins/reporting/export_types/common/execute_job/omit_blacklisted_headers.ts index 1bd52a0f1b2d2..cbebd6bc21b0e 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/execute_job/omit_blacklisted_headers.ts +++ b/x-pack/legacy/plugins/reporting/export_types/common/execute_job/omit_blacklisted_headers.ts @@ -5,20 +5,17 @@ */ import { omit } from 'lodash'; import { KBN_SCREENSHOT_HEADER_BLACKLIST } from '../../../common/constants'; -import { ServerFacade } from '../../../types'; export const omitBlacklistedHeaders = ({ job, decryptedHeaders, - server, }: { job: JobDocPayloadType; decryptedHeaders: Record; - server: ServerFacade; }) => { const filteredHeaders: Record = omit( decryptedHeaders, KBN_SCREENSHOT_HEADER_BLACKLIST ); - return { job, filteredHeaders, server }; + return filteredHeaders; }; diff --git a/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/index.ts b/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/index.ts index 152ef32e331b9..b83021d5e38dd 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/index.ts +++ b/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/index.ts @@ -5,21 +5,9 @@ */ import * as Rx from 'rxjs'; -import { first, mergeMap } from 'rxjs/operators'; -import { - ServerFacade, - CaptureConfig, - HeadlessChromiumDriverFactory, - HeadlessChromiumDriver as HeadlessBrowser, -} from '../../../../types'; -import { - ElementsPositionAndAttribute, - ScreenshotResults, - ScreenshotObservableOpts, - TimeRange, -} from './types'; - -import { checkForToastMessage } from './check_for_toast'; +import { first, mergeMap, toArray } from 'rxjs/operators'; +import { ServerFacade, CaptureConfig, HeadlessChromiumDriverFactory } from '../../../../types'; +import { ScreenshotResults, ScreenshotObservableOpts } from './types'; import { injectCustomCss } from './inject_css'; import { openUrl } from './open_url'; import { waitForRenderComplete } from './wait_for_render'; @@ -28,6 +16,7 @@ import { waitForElementsToBeInDOM } from './wait_for_dom_elements'; import { getTimeRange } from './get_time_range'; import { getElementPositionAndAttributes } from './get_element_position_data'; import { getScreenshots } from './get_screenshots'; +import { scanPage } from './scan_page'; import { skipTelemetry } from './skip_telemetry'; export function screenshotsObservableFactory( @@ -39,108 +28,68 @@ export function screenshotsObservableFactory( return function screenshotsObservable({ logger, - url, + urls, conditionalHeaders, layout, browserTimezone, - }: ScreenshotObservableOpts): Rx.Observable { - const create$ = browserDriverFactory.create({ - viewport: layout.getBrowserViewport(), - browserTimezone, - }); + }: ScreenshotObservableOpts): Rx.Observable { + const create$ = browserDriverFactory.createPage( + { viewport: layout.getBrowserViewport(), browserTimezone }, + logger + ); - // @ts-ignore this needs to be refactored to use less random type declaration and instead rely on structures that work with inference TODO - return create$.pipe( - mergeMap(({ driver$, exit$ }) => { - const screenshot$ = driver$.pipe( - mergeMap( - (browser: HeadlessBrowser) => openUrl(browser, url, conditionalHeaders, logger), - browser => browser - ), - mergeMap( - (browser: HeadlessBrowser) => skipTelemetry(browser, logger), - browser => browser - ), - mergeMap( - (browser: HeadlessBrowser) => { - logger.debug( - 'waiting for elements or items count attribute; or not found to interrupt' - ); + return Rx.from(urls).pipe( + mergeMap(url => { + return create$.pipe( + mergeMap(({ driver, exit$ }) => { + const screenshot$ = Rx.of(driver).pipe( + mergeMap(() => openUrl(driver, url, conditionalHeaders, logger)), + mergeMap(() => skipTelemetry(driver, logger)), + mergeMap(() => scanPage(driver, layout, logger)), + mergeMap(() => getNumberOfItems(driver, layout, logger)), + mergeMap(async itemsCount => { + const viewport = layout.getViewport(itemsCount); + await Promise.all([ + driver.setViewport(viewport, logger), + waitForElementsToBeInDOM(driver, itemsCount, layout, logger), + ]); + }), + mergeMap(async () => { + // Waiting till _after_ elements have rendered before injecting our CSS + // allows for them to be displayed properly in many cases + await injectCustomCss(driver, layout, logger); - // the dashboard is using the `itemsCountAttribute` attribute to let us - // know how many items to expect since gridster incrementally adds panels - // we have to use this hint to wait for all of them - const renderSuccess = browser.waitForSelector( - `${layout.selectors.renderComplete},[${layout.selectors.itemsCountAttribute}]`, - {}, - logger - ); - const renderError = checkForToastMessage(browser, layout, logger); - return Rx.race(Rx.from(renderSuccess), Rx.from(renderError)); - }, - browser => browser - ), - mergeMap( - (browser: HeadlessBrowser) => getNumberOfItems(browser, layout, logger), - (browser, itemsCount: number) => ({ browser, itemsCount }) - ), - mergeMap( - async ({ browser, itemsCount }) => { - logger.debug('setting viewport'); - const viewport = layout.getViewport(itemsCount); - return await browser.setViewport(viewport, logger); - }, - ({ browser, itemsCount }) => ({ browser, itemsCount }) - ), - mergeMap( - ({ browser, itemsCount }) => - waitForElementsToBeInDOM(browser, itemsCount, layout, logger), - ({ browser }) => browser - ), - mergeMap( - browser => { - // Waiting till _after_ elements have rendered before injecting our CSS - // allows for them to be displayed properly in many cases - return injectCustomCss(browser, layout, logger); - }, - browser => browser - ), - mergeMap( - async browser => { - if (layout.positionElements) { - // position panel elements for print layout - return await layout.positionElements(browser, logger); - } - }, - browser => browser - ), - mergeMap( - (browser: HeadlessBrowser) => { - return waitForRenderComplete(captureConfig, browser, layout, logger); - }, - browser => browser - ), - mergeMap( - browser => getTimeRange(browser, layout, logger), - (browser, timeRange: TimeRange | null) => ({ browser, timeRange }) - ), - mergeMap( - ({ browser }) => getElementPositionAndAttributes(browser, layout), - ({ browser, timeRange }, elementsPositionAndAttributes: ElementsPositionAndAttribute[]) => { - return { browser, timeRange, elementsPositionAndAttributes }; - } // prettier-ignore - ), - mergeMap( - ({ browser, elementsPositionAndAttributes }) => { - return getScreenshots({ browser, elementsPositionAndAttributes, logger }); - }, - ({ timeRange }, screenshots) => ({ timeRange, screenshots }) - ) - ); + if (layout.positionElements) { + // position panel elements for print layout + await layout.positionElements(driver, logger); + } - return Rx.race(screenshot$, exit$); + await waitForRenderComplete(captureConfig, driver, layout, logger); + }), + mergeMap(() => getTimeRange(driver, layout, logger)), + mergeMap( + async (timeRange): Promise => { + const elementsPositionAndAttributes = await getElementPositionAndAttributes( + driver, + layout + ); + const screenshots = await getScreenshots({ + browser: driver, + elementsPositionAndAttributes, + logger, + }); + + return { timeRange, screenshots }; + } + ) + ); + + return Rx.race(screenshot$, exit$); + }) + ); }), - first() + first(), + toArray() ); }; } diff --git a/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/scan_page.ts b/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/scan_page.ts new file mode 100644 index 0000000000000..81ff01bb204b8 --- /dev/null +++ b/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/scan_page.ts @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as Rx from 'rxjs'; +import { HeadlessChromiumDriver } from '../../../../server/browsers/chromium/driver'; +import { LevelLogger } from '../../../../server/lib'; +import { LayoutInstance } from '../../layouts/layout'; +import { checkForToastMessage } from './check_for_toast'; + +export function scanPage( + browser: HeadlessChromiumDriver, + layout: LayoutInstance, + logger: LevelLogger +) { + logger.debug('waiting for elements or items count attribute; or not found to interrupt'); + + // the dashboard is using the `itemsCountAttribute` attribute to let us + // know how many items to expect since gridster incrementally adds panels + // we have to use this hint to wait for all of them + const renderSuccess = browser.waitForSelector( + `${layout.selectors.renderComplete},[${layout.selectors.itemsCountAttribute}]`, + {}, + logger + ); + const renderError = checkForToastMessage(browser, layout, logger); + return Rx.race(Rx.from(renderSuccess), Rx.from(renderError)); +} diff --git a/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/types.ts b/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/types.ts index 4f461ef4ec5f9..78cd42f0cae2f 100644 --- a/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/types.ts +++ b/x-pack/legacy/plugins/reporting/export_types/common/lib/screenshots/types.ts @@ -10,7 +10,7 @@ import { LayoutInstance } from '../../layouts/layout'; export interface ScreenshotObservableOpts { logger: LevelLogger; - url: string; + urls: string[]; conditionalHeaders: ConditionalHeaders; layout: LayoutInstance; browserTimezone: string; @@ -36,6 +36,6 @@ export interface Screenshot { } export interface ScreenshotResults { - timeRange: TimeRange; + timeRange: TimeRange | null; screenshots: Screenshot[]; } diff --git a/x-pack/legacy/plugins/reporting/export_types/csv/types.d.ts b/x-pack/legacy/plugins/reporting/export_types/csv/types.d.ts index 5070c69553cfa..9d1eb9cc1bd4d 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv/types.d.ts +++ b/x-pack/legacy/plugins/reporting/export_types/csv/types.d.ts @@ -6,10 +6,21 @@ import { JobDocPayload, JobParamPostPayload, ConditionalHeaders, RequestFacade } from '../../types'; +interface DocValueField { + field: string; + format: string; +} + +interface SortOptions { + order: string; + unmapped_type: string; +} + export interface JobParamPostPayloadDiscoverCsv extends JobParamPostPayload { state?: { query: any; - sort: any[]; + sort: Array>; + docvalue_fields: DocValueField[]; }; } diff --git a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/generate_csv_search.ts b/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/generate_csv_search.ts index ba29c3ef1ec3f..7e12adefca38d 100644 --- a/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/generate_csv_search.ts +++ b/x-pack/legacy/plugins/reporting/export_types/csv_from_savedobject/server/lib/generate_csv_search.ts @@ -84,10 +84,11 @@ export async function generateCsvSearch( let payloadQuery: QueryFilter | undefined; let payloadSort: any[] = []; + let docValueFields: any[] | undefined; if (jobParams.post && jobParams.post.state) { ({ post: { - state: { query: payloadQuery, sort: payloadSort = [] }, + state: { query: payloadQuery, sort: payloadSort = [], docvalue_fields: docValueFields }, }, } = jobParams); } @@ -119,7 +120,15 @@ export async function generateCsvSearch( }, }; }, {}); - const docValueFields = indexPatternTimeField ? [indexPatternTimeField] : undefined; + + if (indexPatternTimeField) { + if (docValueFields) { + docValueFields = [indexPatternTimeField].concat(docValueFields); + } else { + docValueFields = [indexPatternTimeField]; + } + } + const searchRequest: SearchRequest = { index: esIndex, body: { diff --git a/x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.test.js b/x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.test.js index 0888d287af07c..1be65722fa668 100644 --- a/x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.test.js +++ b/x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.test.js @@ -27,7 +27,7 @@ beforeEach(() => { 'server.port': 5601, }; mockServer = { - expose: () => {}, + expose: () => {}, // NOTE: this is for oncePerServer config: memoize(() => ({ get: jest.fn() })), info: { protocol: 'http', diff --git a/x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.ts b/x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.ts index b289ae45dde67..c2fda05fbe3e9 100644 --- a/x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.ts +++ b/x-pack/legacy/plugins/reporting/export_types/png/server/execute_job/index.ts @@ -5,7 +5,7 @@ */ import * as Rx from 'rxjs'; -import { mergeMap, catchError, map, takeUntil } from 'rxjs/operators'; +import { catchError, map, mergeMap, takeUntil } from 'rxjs/operators'; import { PLUGIN_ID, PNG_JOB_TYPE } from '../../../../common/constants'; import { ServerFacade, @@ -32,18 +32,14 @@ export const executeJobFactory: QueuedPngExecutorFactory = function executeJobFa const generatePngObservable = generatePngObservableFactory(server, browserDriverFactory); const logger = LevelLogger.createForServer(server, [PLUGIN_ID, PNG_JOB_TYPE, 'execute']); - return function executeJob( - jobId: string, - jobToExecute: JobDocPayloadPNG, - cancellationToken: any - ) { + return function executeJob(jobId: string, job: JobDocPayloadPNG, cancellationToken: any) { const jobLogger = logger.clone([jobId]); - const process$ = Rx.of({ job: jobToExecute, server, logger }).pipe( - mergeMap(decryptJobHeaders), - map(omitBlacklistedHeaders), - map(getConditionalHeaders), - mergeMap(getFullUrls), - mergeMap(({ job, conditionalHeaders, urls }) => { + const process$ = Rx.of(1).pipe( + mergeMap(() => decryptJobHeaders({ server, job, logger })), + map(decryptedHeaders => omitBlacklistedHeaders({ job, decryptedHeaders })), + map(filteredHeaders => getConditionalHeaders({ server, job, filteredHeaders })), + mergeMap(conditionalHeaders => { + const urls = getFullUrls({ server, job }); const hashUrl = urls[0]; return generatePngObservable( jobLogger, diff --git a/x-pack/legacy/plugins/reporting/export_types/png/server/lib/generate_png.ts b/x-pack/legacy/plugins/reporting/export_types/png/server/lib/generate_png.ts index e2b1474515786..600762c451a79 100644 --- a/x-pack/legacy/plugins/reporting/export_types/png/server/lib/generate_png.ts +++ b/x-pack/legacy/plugins/reporting/export_types/png/server/lib/generate_png.ts @@ -32,19 +32,17 @@ export function generatePngObservableFactory( const layout = new PreserveLayout(layoutParams.dimensions); const screenshots$ = screenshotsObservable({ logger, - url, + urls: [url], conditionalHeaders, layout, browserTimezone, }).pipe( - map(urlScreenshots => { - if (urlScreenshots.screenshots.length !== 1) { - throw new Error( - `Expected there to be 1 screenshot, but there are ${urlScreenshots.screenshots.length}` - ); + map(([{ screenshots }]) => { + if (screenshots.length !== 1) { + throw new Error(`Expected there to be 1 screenshot, but there are ${screenshots.length}`); } - return urlScreenshots.screenshots[0].base64EncodedData; + return screenshots[0].base64EncodedData; }) ); diff --git a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/create_job/compatibility_shim.js b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/create_job/compatibility_shim.js deleted file mode 100644 index a3afd070603b0..0000000000000 --- a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/create_job/compatibility_shim.js +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { uriEncode } from '../lib/uri_encode'; - -/* - * TODO: Kibana 8.0: - * Remove support for parsing Saved Object details from objectType / savedObjectId - * Including support for determining the Report title from objectType / savedObjectId - * - * - `objectType` is optional, but helps differentiate the type of report in the job listing - * - `title` must be explicitly passed - * - `relativeUrls` array OR `relativeUrl` string must be passed - */ - -const getSavedObjectTitle = async (objectType, savedObjectId, savedObjectsClient) => { - const savedObject = await savedObjectsClient.get(objectType, savedObjectId); - return savedObject.attributes.title; -}; - -const getSavedObjectRelativeUrl = (objectType, savedObjectId, queryString) => { - const appPrefixes = { - dashboard: '/dashboard/', - visualization: '/visualize/edit/', - search: '/discover/', - }; - - const appPrefix = appPrefixes[objectType]; - if (!appPrefix) throw new Error('Unexpected app type: ' + objectType); - - const hash = appPrefix + uriEncode.string(savedObjectId, true); - - return `/app/kibana#${hash}?${queryString || ''}`; -}; - -export function compatibilityShimFactory(server, logger) { - return function compatibilityShimFactory(createJobFn) { - return async function( - { - savedObjectId, // deprecating - queryString, // deprecating - browserTimezone, - objectType, - title, - relativeUrls, - layout, - }, - headers, - request - ) { - // input validation and deprecation logging - if (savedObjectId) { - if (typeof savedObjectId !== 'string') { - throw new Error('Invalid savedObjectId (deprecated). String is expected.'); - } - if (relativeUrls) { - throw new Error(`savedObjectId should not be provided if relativeUrls are provided`); - } - } else { - if (!relativeUrls) { - throw new Error(`Either relativeUrls or savedObjectId must be provided`); - } - if (!Array.isArray(relativeUrls)) { - throw new Error('Invalid relativeUrls. String[] is expected.'); - } - relativeUrls.forEach(url => { - if (typeof url !== 'string') { - throw new Error('Invalid Relative URL in relativeUrls. String is expected.'); - } - }); - } - - let kibanaRelativeUrls; - if (relativeUrls) { - kibanaRelativeUrls = relativeUrls; - } else { - kibanaRelativeUrls = [getSavedObjectRelativeUrl(objectType, savedObjectId, queryString)]; - logger.warning( - `The relativeUrls have been derived from saved object parameters. ` + - `This functionality will be removed with the next major version.` - ); - } - - let reportTitle; - try { - if (title) { - reportTitle = title; - } else { - if (objectType && savedObjectId) { - reportTitle = await getSavedObjectTitle( - objectType, - savedObjectId, - request.getSavedObjectsClient() - ); - logger.warning( - `The title has been derived from saved object parameters. This ` + - `functionality will be removed with the next major version.` - ); - } else { - logger.warning( - `A title parameter should be provided with the job generation ` + - `request. Please use Kibana to regenerate your POST URL to have a ` + - `title included in the PDF.` - ); - } - } - } catch (err) { - logger.error(err); // 404 for the savedObjectId, etc - throw err; - } - - const transformedJobParams = { - objectType, - title: reportTitle, - relativeUrls: kibanaRelativeUrls, - browserTimezone, - layout, - }; - - return await createJobFn(transformedJobParams, headers, request); - }; - }; -} diff --git a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/create_job/compatibility_shim.test.js b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/create_job/compatibility_shim.test.js deleted file mode 100644 index b3b8bca1d8955..0000000000000 --- a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/create_job/compatibility_shim.test.js +++ /dev/null @@ -1,297 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { once } from 'lodash'; -import { compatibilityShimFactory } from './compatibility_shim'; - -const createMockServer = () => { - return { - expose: jest.fn(), //fool once_per_server - log: jest.fn(), - }; -}; - -const createMockLogger = () => ({ - warning: jest.fn(), - error: jest.fn(), -}); - -const createMockRequest = () => { - return { - getSavedObjectsClient: once(function() { - return { - get: jest.fn(), - }; - }), - }; -}; - -test(`passes title through if provided`, async () => { - const mockLogger = createMockLogger(); - const compatibilityShim = compatibilityShimFactory(createMockServer(), mockLogger); - const title = 'test title'; - - const createJobMock = jest.fn(); - await compatibilityShim(createJobMock)( - { title, relativeUrls: ['/something'] }, - null, - createMockRequest() - ); - - expect(mockLogger.warning.mock.calls.length).toBe(0); - expect(mockLogger.error.mock.calls.length).toBe(0); - - expect(createJobMock.mock.calls.length).toBe(1); - expect(createJobMock.mock.calls[0][0].title).toBe(title); -}); - -test(`gets the title from the savedObject`, async () => { - const mockLogger = createMockLogger(); - const compatibilityShim = compatibilityShimFactory(createMockServer(), mockLogger); - - const createJobMock = jest.fn(); - const mockRequest = createMockRequest(); - const title = 'savedTitle'; - mockRequest.getSavedObjectsClient().get.mockReturnValue({ - attributes: { - title, - }, - }); - - await compatibilityShim(createJobMock)( - { objectType: 'search', savedObjectId: 'abc' }, - null, - mockRequest - ); - - expect(mockLogger.warning.mock.calls.length).toBe(2); - expect(mockLogger.warning.mock.calls[0][0]).toEqual( - 'The relativeUrls have been derived from saved object parameters. This functionality will be removed with the next major version.' - ); - expect(mockLogger.error.mock.calls.length).toBe(0); - - expect(createJobMock.mock.calls.length).toBe(1); - expect(createJobMock.mock.calls[0][0].title).toBe(title); -}); - -test(`passes the objectType and savedObjectId to the savedObjectsClient`, async () => { - const mockLogger = createMockLogger(); - const compatibilityShim = compatibilityShimFactory(createMockServer(), mockLogger); - - const createJobMock = jest.fn(); - const mockRequest = createMockRequest(); - mockRequest.getSavedObjectsClient().get.mockReturnValue({ - attributes: { - title: '', - }, - }); - - const objectType = 'search'; - const savedObjectId = 'abc'; - await compatibilityShim(createJobMock)({ objectType, savedObjectId }, null, mockRequest); - - expect(mockLogger.warning.mock.calls.length).toBe(2); - expect(mockLogger.warning.mock.calls[0][0]).toEqual( - 'The relativeUrls have been derived from saved object parameters. This functionality will be removed with the next major version.' - ); - expect(mockLogger.warning.mock.calls[1][0]).toEqual( - 'The title has been derived from saved object parameters. This functionality will be removed with the next major version.' - ); - expect(mockLogger.error.mock.calls.length).toBe(0); - - const getMock = mockRequest.getSavedObjectsClient().get.mock; - expect(getMock.calls.length).toBe(1); - expect(getMock.calls[0][0]).toBe(objectType); - expect(getMock.calls[0][1]).toBe(savedObjectId); -}); - -test(`logs no warnings when title and relativeUrls is passed`, async () => { - const mockLogger = createMockLogger(); - const compatibilityShim = compatibilityShimFactory(createMockServer(), mockLogger); - - const createJobMock = jest.fn(); - const mockRequest = createMockRequest(); - - await compatibilityShim(createJobMock)( - { title: 'Phenomenal Dashboard', relativeUrls: ['/abc', '/def'] }, - null, - mockRequest - ); - - expect(mockLogger.warning.mock.calls.length).toBe(0); - expect(mockLogger.error.mock.calls.length).toBe(0); -}); - -test(`logs warning if title can not be provided`, async () => { - const mockLogger = createMockLogger(); - const compatibilityShim = compatibilityShimFactory(createMockServer(), mockLogger); - - const createJobMock = jest.fn(); - const mockRequest = createMockRequest(); - await compatibilityShim(createJobMock)({ relativeUrls: ['/abc'] }, null, mockRequest); - - expect(mockLogger.warning.mock.calls.length).toBe(1); - expect(mockLogger.warning.mock.calls[0][0]).toEqual( - `A title parameter should be provided with the job generation request. Please ` + - `use Kibana to regenerate your POST URL to have a title included in the PDF.` - ); -}); - -test(`logs deprecations when generating the title/relativeUrl using the savedObject`, async () => { - const mockLogger = createMockLogger(); - const compatibilityShim = compatibilityShimFactory(createMockServer(), mockLogger); - - const createJobMock = jest.fn(); - const mockRequest = createMockRequest(); - mockRequest.getSavedObjectsClient().get.mockReturnValue({ - attributes: { - title: '', - }, - }); - - await compatibilityShim(createJobMock)( - { objectType: 'search', savedObjectId: 'abc' }, - null, - mockRequest - ); - - expect(mockLogger.warning.mock.calls.length).toBe(2); - expect(mockLogger.warning.mock.calls[0][0]).toEqual( - 'The relativeUrls have been derived from saved object parameters. This functionality will be removed with the next major version.' - ); - expect(mockLogger.warning.mock.calls[1][0]).toEqual( - 'The title has been derived from saved object parameters. This functionality will be removed with the next major version.' - ); -}); - -test(`passes objectType through`, async () => { - const mockLogger = createMockLogger(); - const compatibilityShim = compatibilityShimFactory(createMockServer(), mockLogger); - - const createJobMock = jest.fn(); - const mockRequest = createMockRequest(); - - const objectType = 'foo'; - await compatibilityShim(createJobMock)( - { title: 'test', relativeUrls: ['/something'], objectType }, - null, - mockRequest - ); - - expect(mockLogger.warning.mock.calls.length).toBe(0); - expect(mockLogger.error.mock.calls.length).toBe(0); - - expect(createJobMock.mock.calls.length).toBe(1); - expect(createJobMock.mock.calls[0][0].objectType).toBe(objectType); -}); - -test(`passes the relativeUrls through`, async () => { - const mockLogger = createMockLogger(); - const compatibilityShim = compatibilityShimFactory(createMockServer(), mockLogger); - - const createJobMock = jest.fn(); - - const relativeUrls = ['/app/kibana#something', '/app/kibana#something-else']; - await compatibilityShim(createJobMock)({ title: 'test', relativeUrls }, null, null); - - expect(mockLogger.warning.mock.calls.length).toBe(0); - expect(mockLogger.error.mock.calls.length).toBe(0); - - expect(createJobMock.mock.calls.length).toBe(1); - expect(createJobMock.mock.calls[0][0].relativeUrls).toBe(relativeUrls); -}); - -const testSavedObjectRelativeUrl = (objectType, expectedUrl) => { - test(`generates the saved object relativeUrl for ${objectType}`, async () => { - const mockLogger = createMockLogger(); - const compatibilityShim = compatibilityShimFactory(createMockServer(), mockLogger); - const createJobMock = jest.fn(); - - await compatibilityShim(createJobMock)( - { title: 'test', objectType, savedObjectId: 'abc' }, - null, - null - ); - - expect(mockLogger.warning.mock.calls.length).toBe(1); - expect(mockLogger.warning.mock.calls[0][0]).toEqual( - 'The relativeUrls have been derived from saved object parameters. This functionality will be removed with the next major version.' - ); - expect(mockLogger.error.mock.calls.length).toBe(0); - - expect(createJobMock.mock.calls.length).toBe(1); - expect(createJobMock.mock.calls[0][0].relativeUrls).toEqual([expectedUrl]); - }); -}; - -testSavedObjectRelativeUrl('search', '/app/kibana#/discover/abc?'); -testSavedObjectRelativeUrl('visualization', '/app/kibana#/visualize/edit/abc?'); -testSavedObjectRelativeUrl('dashboard', '/app/kibana#/dashboard/abc?'); - -test(`appends the queryString to the relativeUrl when generating from the savedObject`, async () => { - const mockLogger = createMockLogger(); - const compatibilityShim = compatibilityShimFactory(createMockServer(), mockLogger); - const createJobMock = jest.fn(); - - await compatibilityShim(createJobMock)( - { title: 'test', objectType: 'search', savedObjectId: 'abc', queryString: 'foo=bar' }, - null, - null - ); - - expect(mockLogger.warning.mock.calls.length).toBe(1); - expect(mockLogger.warning.mock.calls[0][0]).toEqual( - 'The relativeUrls have been derived from saved object parameters. This functionality will be removed with the next major version.' - ); - expect(mockLogger.error.mock.calls.length).toBe(0); - - expect(createJobMock.mock.calls.length).toBe(1); - expect(createJobMock.mock.calls[0][0].relativeUrls).toEqual([ - '/app/kibana#/discover/abc?foo=bar', - ]); -}); - -test(`throw an Error if the objectType, savedObjectId and relativeUrls are provided`, async () => { - const mockLogger = createMockLogger(); - const compatibilityShim = compatibilityShimFactory(createMockServer(), mockLogger); - const createJobMock = jest.fn(); - - const promise = compatibilityShim(createJobMock)( - { - title: 'test', - objectType: 'something', - relativeUrls: ['/something'], - savedObjectId: 'abc', - }, - null, - null - ); - - await expect(promise).rejects.toBeDefined(); -}); - -test(`passes headers and request through`, async () => { - const mockLogger = createMockLogger(); - const compatibilityShim = compatibilityShimFactory(createMockServer(), mockLogger); - - const createJobMock = jest.fn(); - - const headers = {}; - const request = createMockRequest(); - - await compatibilityShim(createJobMock)( - { title: 'test', relativeUrls: ['/something'] }, - headers, - request - ); - - expect(mockLogger.warning.mock.calls.length).toBe(0); - expect(mockLogger.error.mock.calls.length).toBe(0); - - expect(createJobMock.mock.calls.length).toBe(1); - expect(createJobMock.mock.calls[0][1]).toBe(headers); - expect(createJobMock.mock.calls[0][2]).toBe(request); -}); diff --git a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/create_job/index.ts b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/create_job/index.ts index d17713057abc1..a8cc71175cffe 100644 --- a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/create_job/index.ts +++ b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/create_job/index.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { PLUGIN_ID, PDF_JOB_TYPE } from '../../../../common/constants'; import { CreateJobFactory, ESQueueCreateJobFn, @@ -13,29 +12,16 @@ import { ConditionalHeaders, } from '../../../../types'; import { validateUrls } from '../../../../common/validate_urls'; -import { LevelLogger } from '../../../../server/lib'; import { cryptoFactory } from '../../../../server/lib/crypto'; import { JobParamsPDF } from '../../types'; -// @ts-ignore untyped module -import { compatibilityShimFactory } from './compatibility_shim'; - -interface CreateJobFnOpts { - objectType: any; - title: string; - relativeUrls: string[]; - browserTimezone: string; - layout: any; -} export const createJobFactory: CreateJobFactory> = function createJobFactoryFn(server: ServerFacade) { - const logger = LevelLogger.createForServer(server, [PLUGIN_ID, PDF_JOB_TYPE, 'create']); - const compatibilityShim = compatibilityShimFactory(server, logger); const crypto = cryptoFactory(server); - return compatibilityShim(async function createJobFn( - { objectType, title, relativeUrls, browserTimezone, layout }: CreateJobFnOpts, + return async function createJobFn( + { title, relativeUrls, browserTimezone, layout, objectType }: JobParamsPDF, headers: ConditionalHeaders['headers'], request: RequestFacade ) { @@ -44,14 +30,14 @@ export const createJobFactory: CreateJobFactory ({ relativeUrl: u })), - headers: serializedEncryptedHeaders, - browserTimezone, - layout, basePath: request.getBasePath(), + browserTimezone, forceNow: new Date().toISOString(), + headers: serializedEncryptedHeaders, + layout, + relativeUrls, + title, + objectType, }; - }); + }; }; diff --git a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/execute_job/index.test.js b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/execute_job/index.test.js index db7b599a1aaab..ddbee6e9d54a4 100644 --- a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/execute_job/index.test.js +++ b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/execute_job/index.test.js @@ -27,7 +27,8 @@ beforeEach(() => { 'server.port': 5601, }; mockServer = { - expose: () => {}, + expose: jest.fn(), + log: jest.fn(), config: memoize(() => ({ get: jest.fn() })), info: { protocol: 'http', @@ -71,7 +72,7 @@ test(`passes browserTimezone to generatePdf`, async () => { const browserTimezone = 'UTC'; await executeJob( 'pdfJobId', - { objects: [], browserTimezone, headers: encryptedHeaders }, + { relativeUrls: [], browserTimezone, headers: encryptedHeaders }, cancellationToken ); @@ -96,7 +97,7 @@ test(`returns content_type of application/pdf`, async () => { const { content_type: contentType } = await executeJob( 'pdfJobId', - { objects: [], timeRange: {}, headers: encryptedHeaders }, + { relativeUrls: [], timeRange: {}, headers: encryptedHeaders }, cancellationToken ); expect(contentType).toBe('application/pdf'); @@ -112,7 +113,7 @@ test(`returns content of generatePdf getBuffer base64 encoded`, async () => { const encryptedHeaders = await encryptHeaders({}); const { content } = await executeJob( 'pdfJobId', - { objects: [], timeRange: {}, headers: encryptedHeaders }, + { relativeUrls: [], timeRange: {}, headers: encryptedHeaders }, cancellationToken ); diff --git a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/execute_job/index.ts b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/execute_job/index.ts index e2b3183464cf2..d85207e671212 100644 --- a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/execute_job/index.ts +++ b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/execute_job/index.ts @@ -5,7 +5,7 @@ */ import * as Rx from 'rxjs'; -import { mergeMap, catchError, map, takeUntil } from 'rxjs/operators'; +import { catchError, map, mergeMap, takeUntil } from 'rxjs/operators'; import { ServerFacade, ExecuteJobFactory, @@ -33,33 +33,28 @@ export const executeJobFactory: QueuedPdfExecutorFactory = function executeJobFa const generatePdfObservable = generatePdfObservableFactory(server, browserDriverFactory); const logger = LevelLogger.createForServer(server, [PLUGIN_ID, PDF_JOB_TYPE, 'execute']); - return function executeJob( - jobId: string, - jobToExecute: JobDocPayloadPDF, - cancellationToken: any - ) { + return function executeJob(jobId: string, job: JobDocPayloadPDF, cancellationToken: any) { const jobLogger = logger.clone([jobId]); - const process$ = Rx.of({ job: jobToExecute, server, logger }).pipe( - mergeMap(decryptJobHeaders), - map(omitBlacklistedHeaders), - map(getConditionalHeaders), - mergeMap(getCustomLogo), - mergeMap(getFullUrls), - mergeMap( - ({ job, conditionalHeaders, logo, urls }): Rx.Observable => { - const { browserTimezone, layout } = jobToExecute; - return generatePdfObservable( - jobLogger, - job.title, - urls, - browserTimezone, - conditionalHeaders, - layout, - logo - ); - } - ), + const process$ = Rx.of(1).pipe( + mergeMap(() => decryptJobHeaders({ server, job, logger })), + map(decryptedHeaders => omitBlacklistedHeaders({ job, decryptedHeaders })), + map(filteredHeaders => getConditionalHeaders({ server, job, filteredHeaders })), + mergeMap(conditionalHeaders => getCustomLogo({ server, job, conditionalHeaders })), + mergeMap(({ logo, conditionalHeaders }) => { + const urls = getFullUrls({ server, job }); + + const { browserTimezone, layout, title } = job; + return generatePdfObservable( + jobLogger, + title, + urls, + browserTimezone, + conditionalHeaders, + layout, + logo + ); + }), map((buffer: Buffer) => ({ content_type: 'application/pdf', content: buffer.toString('base64'), @@ -72,7 +67,6 @@ export const executeJobFactory: QueuedPdfExecutorFactory = function executeJobFa ); const stop$ = Rx.fromEventPattern(cancellationToken.on); - return process$.pipe(takeUntil(stop$)).toPromise(); }; }; diff --git a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/lib/generate_pdf.ts b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/lib/generate_pdf.ts index 898a13a2dfe80..9a8db308bea79 100644 --- a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/lib/generate_pdf.ts +++ b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/server/lib/generate_pdf.ts @@ -5,7 +5,7 @@ */ import * as Rx from 'rxjs'; -import { toArray, mergeMap } from 'rxjs/operators'; +import { mergeMap } from 'rxjs/operators'; import { groupBy } from 'lodash'; import { LevelLogger } from '../../../../server/lib'; import { ServerFacade, HeadlessChromiumDriverFactory, ConditionalHeaders } from '../../../../types'; @@ -31,7 +31,6 @@ export function generatePdfObservableFactory( browserDriverFactory: HeadlessChromiumDriverFactory ) { const screenshotsObservable = screenshotsObservableFactory(server, browserDriverFactory); - const captureConcurrency = 1; return function generatePdfObservable( logger: LevelLogger, @@ -41,15 +40,16 @@ export function generatePdfObservableFactory( conditionalHeaders: ConditionalHeaders, layoutParams: LayoutParams, logo?: string - ) { + ): Rx.Observable { const layout = createLayout(server, layoutParams) as LayoutInstance; - const screenshots$ = Rx.from(urls).pipe( - mergeMap( - url => screenshotsObservable({ logger, url, conditionalHeaders, layout, browserTimezone }), - captureConcurrency - ), - toArray(), - mergeMap(async (urlScreenshots: ScreenshotResults[]) => { + const screenshots$ = screenshotsObservable({ + logger, + urls, + conditionalHeaders, + layout, + browserTimezone, + }).pipe( + mergeMap(async urlScreenshots => { const pdfOutput = pdf.create(layout, logo); if (title) { @@ -68,8 +68,7 @@ export function generatePdfObservableFactory( }); pdfOutput.generate(); - const buffer = await pdfOutput.getBuffer(); - return buffer; + return await pdfOutput.getBuffer(); }) ); diff --git a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/types.d.ts b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/types.d.ts index b51a2d4d5711c..0a9dcfe986ca6 100644 --- a/x-pack/legacy/plugins/reporting/export_types/printable_pdf/types.d.ts +++ b/x-pack/legacy/plugins/reporting/export_types/printable_pdf/types.d.ts @@ -9,7 +9,7 @@ import { JobDocPayload, ServerFacade, RequestFacade } from '../../types'; // Job params: structure of incoming user request data, after being parsed from RISON export interface JobParamsPDF { - objectType: string; + objectType: string; // visualization, dashboard, etc. Used for job info & telemetry title: string; relativeUrls: string[]; browserTimezone: string; @@ -22,7 +22,5 @@ export interface JobDocPayloadPDF extends JobDocPayload { browserTimezone: string; forceNow?: string; layout: LayoutParams; - objects: Array<{ - relativeUrl: string; - }>; + relativeUrls: string[]; } diff --git a/x-pack/legacy/plugins/reporting/index.test.js b/x-pack/legacy/plugins/reporting/index.test.js index f1b471071153c..0d9a717bd7d81 100644 --- a/x-pack/legacy/plugins/reporting/index.test.js +++ b/x-pack/legacy/plugins/reporting/index.test.js @@ -16,6 +16,7 @@ jest.mock('os', () => { return os; }); +// eslint-disable-next-line jest/valid-describe const describeWithContext = describe.each([ [{ dev: false, dist: false }], [{ dev: true, dist: false }], diff --git a/x-pack/legacy/plugins/reporting/index.ts b/x-pack/legacy/plugins/reporting/index.ts index c0c9e458132f0..ef0ab37738362 100644 --- a/x-pack/legacy/plugins/reporting/index.ts +++ b/x-pack/legacy/plugins/reporting/index.ts @@ -59,7 +59,7 @@ export const reporting = (kibana: any) => { defaultMessage: `Custom image to use in the PDF's footer`, }), type: 'image', - options: { + validation: { maxSize: { length: kbToBase64Length(200), description: '200 kB', @@ -94,7 +94,7 @@ export const reporting = (kibana: any) => { const { xpack_main: xpackMainPlugin } = server.plugins; mirrorPluginStatus(xpackMainPlugin, this); const checkLicense = checkLicenseFactory(exportTypesRegistry); - xpackMainPlugin.status.once('green', () => { + (xpackMainPlugin as any).status.once('green', () => { // Register a function that is called whenever the xpack info changes, // to re-compute the license check results for this plugin xpackMainPlugin.info.feature(this.id).registerLicenseCheckResultsGenerator(checkLicense); diff --git a/x-pack/legacy/plugins/reporting/public/components/report_listing.tsx b/x-pack/legacy/plugins/reporting/public/components/report_listing.tsx index 9783372aa29c4..320f6220aa996 100644 --- a/x-pack/legacy/plugins/reporting/public/components/report_listing.tsx +++ b/x-pack/legacy/plugins/reporting/public/components/report_listing.tsx @@ -425,7 +425,7 @@ class ReportListingUi extends Component { return { id: job._id, type: source.jobtype, - object_type: source.payload.type, + object_type: source.payload.objectType, object_title: source.payload.title, created_by: source.created_by, created_at: source.created_at, diff --git a/x-pack/legacy/plugins/reporting/server/browsers/chromium/driver_factory/index.ts b/x-pack/legacy/plugins/reporting/server/browsers/chromium/driver_factory/index.ts index daa7df343f8aa..6fa46b893de8c 100644 --- a/x-pack/legacy/plugins/reporting/server/browsers/chromium/driver_factory/index.ts +++ b/x-pack/legacy/plugins/reporting/server/browsers/chromium/driver_factory/index.ts @@ -31,10 +31,11 @@ type queueTimeout = number; export class HeadlessChromiumDriverFactory { private binaryPath: binaryPath; - private logger: Logger; private browserConfig: BrowserConfig; private queueTimeout: queueTimeout; private networkPolicy: NetworkPolicy; + private userDataDir: string; + private getChromiumArgs: (viewport: BrowserConfig['viewport']) => string[]; constructor( binaryPath: binaryPath, @@ -46,23 +47,30 @@ export class HeadlessChromiumDriverFactory { this.binaryPath = binaryPath; this.browserConfig = browserConfig; this.queueTimeout = queueTimeout; - this.logger = logger; this.networkPolicy = networkPolicy; + + this.userDataDir = fs.mkdtempSync(path.join(os.tmpdir(), 'chromium-')); + this.getChromiumArgs = (viewport: BrowserConfig['viewport']) => + args({ + userDataDir: this.userDataDir, + viewport, + disableSandbox: this.browserConfig.disableSandbox, + proxy: this.browserConfig.proxy, + }); } type = 'chromium'; - test({ viewport }: { viewport: BrowserConfig['viewport'] }, logger: Logger) { - const userDataDir = fs.mkdtempSync(path.join(os.tmpdir(), 'chromium-')); + test(logger: Logger) { const chromiumArgs = args({ - userDataDir, - viewport, + userDataDir: this.userDataDir, + viewport: { width: 800, height: 600 }, disableSandbox: this.browserConfig.disableSandbox, proxy: this.browserConfig.proxy, }); return puppeteerLaunch({ - userDataDir, + userDataDir: this.userDataDir, executablePath: this.binaryPath, ignoreHTTPSErrors: true, args: chromiumArgs, @@ -76,33 +84,25 @@ export class HeadlessChromiumDriverFactory { }); } - create({ - viewport, - browserTimezone, - }: { - viewport: BrowserConfig['viewport']; - browserTimezone: string; - }): Rx.Observable<{ - driver$: Rx.Observable; - exit$: Rx.Observable; - }> { + /* + * Return an observable to objects which will drive screenshot capture for a page + */ + createPage( + { viewport, browserTimezone }: { viewport: BrowserConfig['viewport']; browserTimezone: string }, + pLogger: Logger + ): Rx.Observable<{ driver: HeadlessChromiumDriver; exit$: Rx.Observable }> { return Rx.Observable.create(async (observer: InnerSubscriber) => { - this.logger.debug(`Creating browser driver factory`); + const logger = pLogger.clone(['browser-driver']); + logger.info(`Creating browser page driver`); - const userDataDir = fs.mkdtempSync(path.join(os.tmpdir(), 'chromium-')); - const chromiumArgs = args({ - userDataDir, - viewport, - disableSandbox: this.browserConfig.disableSandbox, - proxy: this.browserConfig.proxy, - }); + const chromiumArgs = this.getChromiumArgs(viewport); let browser: Browser; let page: Page; try { browser = await puppeteerLaunch({ pipe: !this.browserConfig.inspect, - userDataDir, + userDataDir: this.userDataDir, executablePath: this.binaryPath, ignoreHTTPSErrors: true, args: chromiumArgs, @@ -119,7 +119,7 @@ export class HeadlessChromiumDriverFactory { // "TimeoutError: waiting for selector ".application" failed: timeout 30000ms exceeded" page.setDefaultTimeout(this.queueTimeout); - this.logger.debug(`Browser driver factory created`); + logger.debug(`Browser page driver created`); } catch (err) { observer.error(new Error(`Error spawning Chromium browser: [${err}]`)); throw err; @@ -130,12 +130,12 @@ export class HeadlessChromiumDriverFactory { await browser.close(); }, }; - const { terminate$ } = safeChildProcess(this.logger, childProcess); + const { terminate$ } = safeChildProcess(logger, childProcess); // this is adding unsubscribe logic to our observer // so that if our observer unsubscribes, we terminate our child-process observer.add(() => { - this.logger.debug(`The browser process observer has unsubscribed. Closing the browser...`); + logger.debug(`The browser process observer has unsubscribed. Closing the browser...`); childProcess.kill(); // ignore async }); @@ -144,7 +144,7 @@ export class HeadlessChromiumDriverFactory { terminate$ .pipe( tap(signal => { - this.logger.debug(`Termination signal received: ${signal}`); + logger.debug(`Termination signal received: ${signal}`); }), ignoreElements() ) @@ -152,33 +152,40 @@ export class HeadlessChromiumDriverFactory { ); // taps the browser log streams and combine them to Kibana logs - this.getBrowserLogger(page).subscribe(); - this.getProcessLogger(browser).subscribe(); + this.getBrowserLogger(page, logger).subscribe(); + this.getProcessLogger(browser, logger).subscribe(); + + // HeadlessChromiumDriver: object to "drive" a browser page + const driver = new HeadlessChromiumDriver(page, { + inspect: this.browserConfig.inspect, + networkPolicy: this.networkPolicy, + }); - const driver$ = Rx.of(new HeadlessChromiumDriver(page, { inspect: this.browserConfig.inspect, networkPolicy: this.networkPolicy })); // prettier-ignore + // Rx.Observable: stream to interrupt page capture const exit$ = this.getPageExit(browser, page); - observer.next({ driver$, exit$ }); + observer.next({ driver, exit$ }); // unsubscribe logic makes a best-effort attempt to delete the user data directory used by chromium observer.add(() => { - this.logger.debug(`deleting chromium user data directory at [${userDataDir}]`); + const userDataDir = this.userDataDir; + logger.debug(`deleting chromium user data directory at [${userDataDir}]`); // the unsubscribe function isn't `async` so we're going to make our best effort at // deleting the userDataDir and if it fails log an error. del(userDataDir).catch(error => { - this.logger.error(`error deleting user data directory at [${userDataDir}]: [${error}]`); + logger.error(`error deleting user data directory at [${userDataDir}]: [${error}]`); }); }); }); } - getBrowserLogger(page: Page): Rx.Observable { + getBrowserLogger(page: Page, logger: Logger): Rx.Observable { const consoleMessages$ = Rx.fromEvent(page, 'console').pipe( map(line => { if (line.type() === 'error') { - this.logger.error(line.text(), ['headless-browser-console']); + logger.error(line.text(), ['headless-browser-console']); } else { - this.logger.debug(line.text(), [`headless-browser-console:${line.type()}`]); + logger.debug(line.text(), [`headless-browser-console:${line.type()}`]); } }) ); @@ -187,7 +194,7 @@ export class HeadlessChromiumDriverFactory { map(req => { const failure = req.failure && req.failure(); if (failure) { - this.logger.warning( + logger.warning( `Request to [${req.url()}] failed! [${failure.errorText}]. This error will be ignored.` ); } @@ -197,7 +204,7 @@ export class HeadlessChromiumDriverFactory { return Rx.merge(consoleMessages$, pageRequestFailed$); } - getProcessLogger(browser: Browser) { + getProcessLogger(browser: Browser, logger: Logger): Rx.Observable { const childProcess = browser.process(); // NOTE: The browser driver can not observe stdout and stderr of the child process // Puppeteer doesn't give a handle to the original ChildProcess object @@ -206,7 +213,7 @@ export class HeadlessChromiumDriverFactory { // just log closing of the process const processClose$ = Rx.fromEvent(childProcess, 'close').pipe( tap(() => { - this.logger.debug('child process closed', ['headless-browser-process']); + logger.debug('child process closed', ['headless-browser-process']); }) ); diff --git a/x-pack/legacy/plugins/reporting/server/browsers/index.ts b/x-pack/legacy/plugins/reporting/server/browsers/index.ts index a5ecc405bf9c5..402fabea56c84 100644 --- a/x-pack/legacy/plugins/reporting/server/browsers/index.ts +++ b/x-pack/legacy/plugins/reporting/server/browsers/index.ts @@ -3,6 +3,7 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ + import * as chromiumDefinition from './chromium'; export { ensureAllBrowsersDownloaded } from './download'; diff --git a/x-pack/legacy/plugins/reporting/server/lib/check_license.js b/x-pack/legacy/plugins/reporting/server/lib/check_license.js deleted file mode 100644 index 5a784f9913352..0000000000000 --- a/x-pack/legacy/plugins/reporting/server/lib/check_license.js +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -const messages = { - getUnavailable: () => { - return 'You cannot use Reporting because license information is not available at this time.'; - }, - getExpired: license => { - return `You cannot use Reporting because your ${license.getType()} license has expired.`; - }, -}; - -const makeManagementFeature = exportTypes => { - return { - id: 'management', - checkLicense: license => { - if (!license) { - return { - showLinks: true, - enableLinks: false, - message: messages.getUnavailable(), - }; - } - - if (!license.isActive()) { - return { - showLinks: true, - enableLinks: false, - message: messages.getExpired(license), - }; - } - - const validJobTypes = exportTypes - .filter(exportType => license.isOneOf(exportType.validLicenses)) - .map(exportType => exportType.jobType); - - return { - showLinks: validJobTypes.length > 0, - enableLinks: validJobTypes.length > 0, - jobTypes: validJobTypes, - }; - }, - }; -}; - -const makeExportTypeFeature = exportType => { - return { - id: exportType.id, - checkLicense: license => { - if (!license) { - return { - showLinks: true, - enableLinks: false, - message: messages.getUnavailable(), - }; - } - - if (!license.isOneOf(exportType.validLicenses)) { - return { - showLinks: false, - enableLinks: false, - message: `Your ${license.getType()} license does not support ${ - exportType.name - } Reporting. Please upgrade your license.`, - }; - } - - if (!license.isActive()) { - return { - showLinks: true, - enableLinks: false, - message: messages.getExpired(license), - }; - } - - return { - showLinks: true, - enableLinks: true, - }; - }, - }; -}; - -export function checkLicenseFactory(exportTypesRegistry) { - return function checkLicense(xpackLicenseInfo) { - const license = - xpackLicenseInfo === null || !xpackLicenseInfo.isAvailable() - ? null - : xpackLicenseInfo.license; - - const exportTypes = Array.from(exportTypesRegistry.getAll()); - const reportingFeatures = [ - ...exportTypes.map(makeExportTypeFeature), - makeManagementFeature(exportTypes), - ]; - - return reportingFeatures.reduce((result, feature) => { - result[feature.id] = feature.checkLicense(license); - return result; - }, {}); - }; -} diff --git a/x-pack/legacy/plugins/reporting/server/lib/check_license.ts b/x-pack/legacy/plugins/reporting/server/lib/check_license.ts new file mode 100644 index 0000000000000..02e1196f1d00d --- /dev/null +++ b/x-pack/legacy/plugins/reporting/server/lib/check_license.ts @@ -0,0 +1,115 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { XPackInfo } from '../../../xpack_main/server/lib/xpack_info'; +import { XPackInfoLicense } from '../../../xpack_main/server/lib/xpack_info_license'; +import { ExportTypesRegistry, ExportTypeDefinition } from '../../types'; + +interface LicenseCheckResult { + showLinks: boolean; + enableLinks: boolean; + message?: string; +} + +const messages = { + getUnavailable: () => { + return 'You cannot use Reporting because license information is not available at this time.'; + }, + getExpired: (license: XPackInfoLicense) => { + return `You cannot use Reporting because your ${license.getType()} license has expired.`; + }, +}; + +const makeManagementFeature = ( + exportTypes: Array> +) => { + return { + id: 'management', + checkLicense: (license: XPackInfoLicense | null) => { + if (!license) { + return { + showLinks: true, + enableLinks: false, + message: messages.getUnavailable(), + }; + } + + if (!license.isActive()) { + return { + showLinks: true, + enableLinks: false, + message: messages.getExpired(license), + }; + } + + const validJobTypes = exportTypes + .filter(exportType => license.isOneOf(exportType.validLicenses)) + .map(exportType => exportType.jobType); + + return { + showLinks: validJobTypes.length > 0, + enableLinks: validJobTypes.length > 0, + jobTypes: validJobTypes, + }; + }, + }; +}; + +const makeExportTypeFeature = ( + exportType: ExportTypeDefinition +) => { + return { + id: exportType.id, + checkLicense: (license: XPackInfoLicense | null) => { + if (!license) { + return { + showLinks: true, + enableLinks: false, + message: messages.getUnavailable(), + }; + } + + if (!license.isOneOf(exportType.validLicenses)) { + return { + showLinks: false, + enableLinks: false, + message: `Your ${license.getType()} license does not support ${ + exportType.name + } Reporting. Please upgrade your license.`, + }; + } + + if (!license.isActive()) { + return { + showLinks: true, + enableLinks: false, + message: messages.getExpired(license), + }; + } + + return { + showLinks: true, + enableLinks: true, + }; + }, + }; +}; + +export function checkLicenseFactory(exportTypesRegistry: ExportTypesRegistry) { + return function checkLicense(xpackInfo: XPackInfo) { + const license = xpackInfo === null || !xpackInfo.isAvailable() ? null : xpackInfo.license; + const exportTypes = Array.from(exportTypesRegistry.getAll()); + const reportingFeatures = [ + ...exportTypes.map(makeExportTypeFeature), + makeManagementFeature(exportTypes), + ]; + + return reportingFeatures.reduce((result, feature) => { + result[feature.id] = feature.checkLicense(license); + return result; + }, {} as Record); + }; +} diff --git a/x-pack/legacy/plugins/reporting/server/lib/esqueue/helpers/create_index.js b/x-pack/legacy/plugins/reporting/server/lib/esqueue/helpers/create_index.js index 9e00f0447e99e..670c2907fb832 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/esqueue/helpers/create_index.js +++ b/x-pack/legacy/plugins/reporting/server/lib/esqueue/helpers/create_index.js @@ -15,7 +15,7 @@ const schema = { properties: { /** * Type of object that is triggering this report. Should be either search, visualization or dashboard. - * Used for phone home stats only. + * Used for job listing and telemetry stats only. */ objectType: { type: 'text', diff --git a/x-pack/legacy/plugins/reporting/server/lib/esqueue/job.js b/x-pack/legacy/plugins/reporting/server/lib/esqueue/job.js index cded6d2ce89a8..a7d8f4df3fd54 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/esqueue/job.js +++ b/x-pack/legacy/plugins/reporting/server/lib/esqueue/job.js @@ -57,7 +57,7 @@ export class Job extends events.EventEmitter { meta: { // We are copying these values out of payload because these fields are indexed and can be aggregated on // for tracking stats, while payload contents are not. - objectType: payload.type, + objectType: payload.objectType, layout: payload.layout ? payload.layout.id : 'none', }, payload: this.payload, diff --git a/x-pack/legacy/plugins/reporting/server/lib/index.ts b/x-pack/legacy/plugins/reporting/server/lib/index.ts index 50d1a276b6b5d..0a2db749cb954 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/index.ts +++ b/x-pack/legacy/plugins/reporting/server/lib/index.ts @@ -5,7 +5,6 @@ */ export { getExportTypesRegistry } from './export_types_registry'; -// @ts-ignore untyped module export { checkLicenseFactory } from './check_license'; export { LevelLogger } from './level_logger'; export { cryptoFactory } from './crypto'; diff --git a/x-pack/legacy/plugins/reporting/server/lib/validate/__tests__/validate_config.js b/x-pack/legacy/plugins/reporting/server/lib/validate/__tests__/validate_config.js deleted file mode 100644 index 8b5d6f4591ff5..0000000000000 --- a/x-pack/legacy/plugins/reporting/server/lib/validate/__tests__/validate_config.js +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import sinon from 'sinon'; -import { validateConfig } from '../validate_config'; - -// FAILING: https://github.com/elastic/kibana/issues/51373 -describe.skip('Reporting: Validate config', () => { - const logger = { - warning: sinon.spy(), - }; - - beforeEach(() => { - logger.warning.resetHistory(); - }); - - [undefined, null].forEach(value => { - it(`should log a warning and set xpack.reporting.encryptionKey if encryptionKey is ${value}`, () => { - const config = { - get: sinon.stub().returns(value), - set: sinon.stub(), - }; - - expect(() => validateConfig(config, logger)).not.to.throwError(); - - sinon.assert.calledWith(config.set, 'xpack.reporting.encryptionKey'); - sinon.assert.calledWithMatch(logger.warning, /Generating a random key/); - sinon.assert.calledWithMatch(logger.warning, /please set xpack.reporting.encryptionKey/); - }); - }); -}); diff --git a/x-pack/legacy/plugins/reporting/server/lib/validate/__tests__/validate_encryption_key.js b/x-pack/legacy/plugins/reporting/server/lib/validate/__tests__/validate_encryption_key.js new file mode 100644 index 0000000000000..10980f702d849 --- /dev/null +++ b/x-pack/legacy/plugins/reporting/server/lib/validate/__tests__/validate_encryption_key.js @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; +import sinon from 'sinon'; +import { validateEncryptionKey } from '../validate_encryption_key'; + +describe('Reporting: Validate config', () => { + const logger = { + warning: sinon.spy(), + }; + + beforeEach(() => { + logger.warning.resetHistory(); + }); + + [undefined, null].forEach(value => { + it(`should log a warning and set xpack.reporting.encryptionKey if encryptionKey is ${value}`, () => { + const config = { + get: sinon.stub().returns(value), + set: sinon.stub(), + }; + + expect(() => validateEncryptionKey({ config: () => config }, logger)).not.to.throwError(); + + sinon.assert.calledWith(config.set, 'xpack.reporting.encryptionKey'); + sinon.assert.calledWithMatch(logger.warning, /Generating a random key/); + sinon.assert.calledWithMatch(logger.warning, /please set xpack.reporting.encryptionKey/); + }); + }); +}); diff --git a/x-pack/legacy/plugins/reporting/server/lib/validate/__tests__/validate_server_host.ts b/x-pack/legacy/plugins/reporting/server/lib/validate/__tests__/validate_server_host.ts new file mode 100644 index 0000000000000..04f998fd3e5a5 --- /dev/null +++ b/x-pack/legacy/plugins/reporting/server/lib/validate/__tests__/validate_server_host.ts @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; +import sinon from 'sinon'; +import { ServerFacade } from '../../../../types'; +import { validateServerHost } from '../validate_server_host'; + +const configKey = 'xpack.reporting.kibanaServer.hostname'; + +describe('Reporting: Validate server host setting', () => { + it(`should log a warning and set ${configKey} if server.host is "0"`, () => { + const getStub = sinon.stub(); + getStub.withArgs('server.host').returns('0'); + getStub.withArgs(configKey).returns(undefined); + const config = { + get: getStub, + set: sinon.stub(), + }; + + expect(() => + validateServerHost(({ config: () => config } as unknown) as ServerFacade) + ).to.throwError(); + + sinon.assert.calledWith(config.set, configKey); + }); +}); diff --git a/x-pack/legacy/plugins/reporting/server/lib/validate/index.ts b/x-pack/legacy/plugins/reporting/server/lib/validate/index.ts index 672f90358aba4..79a64bd82d022 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/validate/index.ts +++ b/x-pack/legacy/plugins/reporting/server/lib/validate/index.ts @@ -4,11 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ +import { i18n } from '@kbn/i18n'; import { ServerFacade, Logger } from '../../../types'; import { HeadlessChromiumDriverFactory } from '../../browsers/chromium/driver_factory'; import { validateBrowser } from './validate_browser'; -import { validateConfig } from './validate_config'; +import { validateEncryptionKey } from './validate_encryption_key'; import { validateMaxContentLength } from './validate_max_content_length'; +import { validateServerHost } from './validate_server_host'; export async function runValidations( server: ServerFacade, @@ -18,13 +20,23 @@ export async function runValidations( try { await Promise.all([ validateBrowser(server, browserFactory, logger), - validateConfig(server, logger), + validateEncryptionKey(server, logger), validateMaxContentLength(server, logger), + validateServerHost(server), ]); - logger.debug(`Reporting plugin self-check ok!`); + logger.debug( + i18n.translate('xpack.reporting.selfCheck.ok', { + defaultMessage: `Reporting plugin self-check ok!`, + }) + ); } catch (err) { logger.warning( - `Reporting plugin self-check failed. Please check the Kibana Reporting settings. ${err}` + i18n.translate('xpack.reporting.selfCheck.warning', { + defaultMessage: `Reporting plugin self-check generated a warning: {err}`, + values: { + err, + }, + }) ); } } diff --git a/x-pack/legacy/plugins/reporting/server/lib/validate/validate_browser.ts b/x-pack/legacy/plugins/reporting/server/lib/validate/validate_browser.ts index 031709c85284c..89c49123e85bf 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/validate/validate_browser.ts +++ b/x-pack/legacy/plugins/reporting/server/lib/validate/validate_browser.ts @@ -18,14 +18,12 @@ export const validateBrowser = async ( logger: Logger ) => { if (browserFactory.type === BROWSER_TYPE) { - return browserFactory - .test({ viewport: { width: 800, height: 600 } }, logger) - .then((browser: Browser | null) => { - if (browser && browser.close) { - browser.close(); - } else { - throw new Error('Could not close browser client handle!'); - } - }); + return browserFactory.test(logger).then((browser: Browser | null) => { + if (browser && browser.close) { + browser.close(); + } else { + throw new Error('Could not close browser client handle!'); + } + }); } }; diff --git a/x-pack/legacy/plugins/reporting/server/lib/validate/validate_config.ts b/x-pack/legacy/plugins/reporting/server/lib/validate/validate_config.ts deleted file mode 100644 index a1eb7be6ecae4..0000000000000 --- a/x-pack/legacy/plugins/reporting/server/lib/validate/validate_config.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import crypto from 'crypto'; -import { ServerFacade, Logger } from '../../../types'; - -export function validateConfig(serverFacade: ServerFacade, logger: Logger) { - const config = serverFacade.config(); - - const encryptionKey = config.get('xpack.reporting.encryptionKey'); - if (encryptionKey == null) { - logger.warning( - `Generating a random key for xpack.reporting.encryptionKey. To prevent pending reports from failing on restart, please set ` + - `xpack.reporting.encryptionKey in kibana.yml` - ); - - // @ts-ignore: No set() method on KibanaConfig, just get() and has() - config.set('xpack.reporting.encryptionKey', crypto.randomBytes(16).toString('hex')); // update config in memory to contain a usable encryption key - } -} diff --git a/x-pack/legacy/plugins/reporting/server/lib/validate/validate_encryption_key.ts b/x-pack/legacy/plugins/reporting/server/lib/validate/validate_encryption_key.ts new file mode 100644 index 0000000000000..e0af94cbdc29c --- /dev/null +++ b/x-pack/legacy/plugins/reporting/server/lib/validate/validate_encryption_key.ts @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import crypto from 'crypto'; +import { ServerFacade, Logger } from '../../../types'; + +export function validateEncryptionKey(serverFacade: ServerFacade, logger: Logger) { + const config = serverFacade.config(); + + const encryptionKey = config.get('xpack.reporting.encryptionKey'); + if (encryptionKey == null) { + // TODO this should simply throw an error and let the handler conver it to a warning mesasge. See validateServerHost. + logger.warning( + i18n.translate('xpack.reporting.selfCheckEncryptionKey.warning', { + defaultMessage: + `Generating a random key for {setting}. To prevent pending reports ` + + `from failing on restart, please set {setting} in kibana.yml`, + values: { + setting: 'xpack.reporting.encryptionKey', + }, + }) + ); + + // @ts-ignore: No set() method on KibanaConfig, just get() and has() + config.set('xpack.reporting.encryptionKey', crypto.randomBytes(16).toString('hex')); // update config in memory to contain a usable encryption key + } +} diff --git a/x-pack/legacy/plugins/reporting/server/lib/validate/validate_max_content_length.ts b/x-pack/legacy/plugins/reporting/server/lib/validate/validate_max_content_length.ts index ca38ce5d635c6..f91cd40bfd3c7 100644 --- a/x-pack/legacy/plugins/reporting/server/lib/validate/validate_max_content_length.ts +++ b/x-pack/legacy/plugins/reporting/server/lib/validate/validate_max_content_length.ts @@ -25,6 +25,7 @@ export async function validateMaxContentLength(server: ServerFacade, logger: Log const kibanaMaxContentBytes: number = config.get(KIBANA_MAX_SIZE_BYTES_PATH); if (kibanaMaxContentBytes > elasticSearchMaxContentBytes) { + // TODO this should simply throw an error and let the handler conver it to a warning mesasge. See validateServerHost. logger.warning( `${KIBANA_MAX_SIZE_BYTES_PATH} (${kibanaMaxContentBytes}) is higher than ElasticSearch's ${ES_MAX_SIZE_BYTES_PATH} (${elasticSearchMaxContentBytes}). ` + `Please set ${ES_MAX_SIZE_BYTES_PATH} in ElasticSearch to match, or lower your ${KIBANA_MAX_SIZE_BYTES_PATH} in Kibana to avoid this warning.` diff --git a/x-pack/legacy/plugins/reporting/server/lib/validate/validate_server_host.ts b/x-pack/legacy/plugins/reporting/server/lib/validate/validate_server_host.ts new file mode 100644 index 0000000000000..f4f4d61246b6a --- /dev/null +++ b/x-pack/legacy/plugins/reporting/server/lib/validate/validate_server_host.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ServerFacade } from '../../../types'; + +const configKey = 'xpack.reporting.kibanaServer.hostname'; + +export function validateServerHost(serverFacade: ServerFacade) { + const config = serverFacade.config(); + + const serverHost = config.get('server.host'); + const reportingKibanaHostName = config.get(configKey); + + if (!reportingKibanaHostName && serverHost === '0') { + // @ts-ignore: No set() method on KibanaConfig, just get() and has() + config.set(configKey, '0.0.0.0'); // update config in memory to allow Reporting to work + + throw new Error( + `Found 'server.host: "0"' in settings. This is incompatible with Reporting. ` + + `To enable Reporting to work, '${configKey}: 0.0.0.0' is being automatically to the configuration. ` + + `You can change to 'server.host: 0.0.0.0' or add '${configKey}: 0.0.0.0' in kibana.yml to prevent this message.` + ); + } +} diff --git a/x-pack/legacy/plugins/reporting/server/routes/generation.ts b/x-pack/legacy/plugins/reporting/server/routes/generation.ts index 7bed7bc5773e4..73450b7641c8e 100644 --- a/x-pack/legacy/plugins/reporting/server/routes/generation.ts +++ b/x-pack/legacy/plugins/reporting/server/routes/generation.ts @@ -17,7 +17,6 @@ import { import { registerGenerateFromJobParams } from './generate_from_jobparams'; import { registerGenerateCsvFromSavedObject } from './generate_from_savedobject'; import { registerGenerateCsvFromSavedObjectImmediate } from './generate_from_savedobject_immediate'; -import { registerLegacy } from './legacy'; import { createQueueFactory, enqueueJobFactory } from '../lib'; export function registerJobGenerationRoutes( @@ -73,7 +72,6 @@ export function registerJobGenerationRoutes( } registerGenerateFromJobParams(server, handler, handleError); - registerLegacy(server, handler, handleError); // Register beta panel-action download-related API's if (config.get('xpack.reporting.csv.enablePanelActionDownload')) { diff --git a/x-pack/legacy/plugins/reporting/server/routes/legacy.ts b/x-pack/legacy/plugins/reporting/server/routes/legacy.ts deleted file mode 100644 index 011ac4a02bbf9..0000000000000 --- a/x-pack/legacy/plugins/reporting/server/routes/legacy.ts +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import querystring from 'querystring'; -import { API_BASE_URL } from '../../common/constants'; -import { ServerFacade, RequestFacade, ReportingResponseToolkit } from '../../types'; -import { - getRouteConfigFactoryReportingPre, - GetRouteConfigFactoryFn, -} from './lib/route_config_factories'; -import { HandlerErrorFunction, HandlerFunction } from './types'; - -const getStaticFeatureConfig = (getRouteConfig: GetRouteConfigFactoryFn, featureId: string) => - getRouteConfig(() => featureId); - -const BASE_GENERATE = `${API_BASE_URL}/generate`; - -export function registerLegacy( - server: ServerFacade, - handler: HandlerFunction, - handleError: HandlerErrorFunction -) { - const getRouteConfig = getRouteConfigFactoryReportingPre(server); - - function createLegacyPdfRoute({ path, objectType }: { path: string; objectType: string }) { - const exportTypeId = 'printablePdf'; - server.route({ - path, - method: 'POST', - options: getStaticFeatureConfig(getRouteConfig, exportTypeId), - handler: async (request: RequestFacade, h: ReportingResponseToolkit) => { - const message = `The following URL is deprecated and will stop working in the next major version: ${request.url.path}`; - server.log(['warning', 'reporting', 'deprecation'], message); - - try { - const savedObjectId = request.params.savedId; - const queryString = querystring.stringify(request.query); - - return await handler( - exportTypeId, - { - objectType, - savedObjectId, - queryString, - }, - request, - h - ); - } catch (err) { - throw handleError(exportTypeId, err); - } - }, - }); - } - - createLegacyPdfRoute({ - path: `${BASE_GENERATE}/visualization/{savedId}`, - objectType: 'visualization', - }); - - createLegacyPdfRoute({ - path: `${BASE_GENERATE}/search/{savedId}`, - objectType: 'search', - }); - - createLegacyPdfRoute({ - path: `${BASE_GENERATE}/dashboard/{savedId}`, - objectType: 'dashboard', - }); -} diff --git a/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_create_logistics.test.js b/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_create_logistics.test.js index 063f48dce337d..c960eabb37dcb 100644 --- a/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_create_logistics.test.js +++ b/x-pack/legacy/plugins/rollup/__jest__/client_integration/job_create_logistics.test.js @@ -12,7 +12,7 @@ import { MONTH, YEAR, } from '../../../../../../src/plugins/es_ui_shared/public/components/cron_editor'; -import { INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE } from '../../../../../../src/legacy/ui/public/index_patterns'; +import { indexPatterns } from '../../../../../../src/plugins/data/public'; import { setupEnvironment, pageHelpers } from './helpers'; jest.mock('ui/new_platform'); @@ -154,7 +154,7 @@ describe('Create Rollup Job, step 1: Logistics', () => { ); }; - [...INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE, ','].reduce((promise, char) => { + [...indexPatterns.ILLEGAL_CHARACTERS_VISIBLE, ','].reduce((promise, char) => { return promise.then(() => expectInvalidChar(char)); }, Promise.resolve()); }); diff --git a/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/steps/step_logistics.js b/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/steps/step_logistics.js index 75c70ab6ce20a..8cba18804b1c6 100644 --- a/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/steps/step_logistics.js +++ b/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/steps/step_logistics.js @@ -26,12 +26,13 @@ import { // eslint-disable-next-line @kbn/eslint/no-restricted-paths import { CronEditor } from '../../../../../../../../../src/plugins/es_ui_shared/public/components/cron_editor'; -import { INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE } from 'ui/index_patterns'; import { INDEX_ILLEGAL_CHARACTERS_VISIBLE } from 'ui/indices'; import { logisticalDetailsUrl, cronUrl } from '../../../services'; import { StepError } from './components'; -const indexPatternIllegalCharacters = INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE.join(' '); +import { indexPatterns } from '../../../../../../../../../src/plugins/data/public'; + +const indexPatternIllegalCharacters = indexPatterns.ILLEGAL_CHARACTERS_VISIBLE.join(' '); const indexIllegalCharacters = INDEX_ILLEGAL_CHARACTERS_VISIBLE.join(' '); export class StepLogisticsUi extends Component { diff --git a/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/steps_config/validate_index_pattern.js b/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/steps_config/validate_index_pattern.js index 8082814abba51..206cc325813c0 100644 --- a/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/steps_config/validate_index_pattern.js +++ b/x-pack/legacy/plugins/rollup/public/crud_app/sections/job_create/steps_config/validate_index_pattern.js @@ -7,8 +7,7 @@ import React from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; -import { INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE } from 'ui/index_patterns'; - +import { indexPatterns } from '../../../../../../../../../src/plugins/data/public'; export function validateIndexPattern(indexPattern, rollupIndex) { if (!indexPattern || !indexPattern.trim()) { return [ @@ -28,7 +27,7 @@ export function validateIndexPattern(indexPattern, rollupIndex) { ]; } - const illegalCharacters = INDEX_PATTERN_ILLEGAL_CHARACTERS_VISIBLE.reduce((chars, char) => { + const illegalCharacters = indexPatterns.ILLEGAL_CHARACTERS_VISIBLE.reduce((chars, char) => { if (indexPattern.includes(char)) { chars.push(char); } diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/highlight_details_flyout/highlight_details_flyout.test.tsx b/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/highlight_details_flyout/highlight_details_flyout.test.tsx index cf8052cbf3580..30a4c1dce1dff 100644 --- a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/highlight_details_flyout/highlight_details_flyout.test.tsx +++ b/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/highlight_details_flyout/highlight_details_flyout.test.tsx @@ -11,16 +11,8 @@ describe('Highlight Details Flyout', () => { it('renders', async () => { const props: Props = { onClose: () => {}, - shard: { - aggregations: [], - id: ['test', 'test', 'test'], - searches: [], - color: '#fff', - time: 123, - relative: 100, - }, + shardName: '[test][test]', operation: { - parent: null, breakdown: [ { color: 'test', @@ -48,8 +40,7 @@ describe('Highlight Details Flyout', () => { query_type: 'test', selfTime: 100, time: 100, - children: [], - timePercentage: 100, + timePercentage: '100', hasChildren: false, visible: true, absoluteColor: '123', diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/highlight_details_flyout/highlight_details_flyout.tsx b/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/highlight_details_flyout/highlight_details_flyout.tsx index 6ba39d15b8341..40c17df95456a 100644 --- a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/highlight_details_flyout/highlight_details_flyout.tsx +++ b/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/highlight_details_flyout/highlight_details_flyout.tsx @@ -17,11 +17,11 @@ import { import { msToPretty } from '../../utils'; import { HighlightDetailsTable } from './highlight_details_table'; -import { Operation, Shard } from '../../types'; +import { Operation } from '../../types'; export interface Props { - operation: Operation; - shard: Shard; + operation: Omit; + shardName: string; indexName: string; onClose: () => void; } @@ -39,14 +39,12 @@ const FlyoutEntry = ({ ); -export const HighlightDetailsFlyout = ({ indexName, operation, shard, onClose }: Props) => { +export const HighlightDetailsFlyout = ({ indexName, operation, shardName, onClose }: Props) => { return ( onClose()}> {indexName} - - [{/* shard id */ shard.id[0]}][{/* shard number */ shard.id[2]}] - + {shardName} diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/highlight_details_flyout/highlight_details_table.tsx b/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/highlight_details_flyout/highlight_details_table.tsx index 4bfa7365de1ef..f41088b7c9b78 100644 --- a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/highlight_details_flyout/highlight_details_table.tsx +++ b/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/highlight_details_flyout/highlight_details_table.tsx @@ -36,7 +36,7 @@ export const HighlightDetailsTable = ({ breakdown }: Props) => { { name: 'Percentage', render: (item: BreakdownItem) => ( - + ), }, ]; diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/percentage_badge.tsx b/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/percentage_badge.tsx index 4b53b2e3c18c5..e39e37e8656db 100644 --- a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/percentage_badge.tsx +++ b/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/percentage_badge.tsx @@ -9,7 +9,7 @@ import { EuiBadge } from '@elastic/eui'; import classNames from 'classnames'; interface Props { - timePercentage: number; + timePercentage: string; label: string; valueType?: 'percent' | 'time'; } diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/__tests__/fixtures/normalize_times.ts b/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/__tests__/fixtures/normalize_times.ts index 714b856b54c68..f26de49138f12 100644 --- a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/__tests__/fixtures/normalize_times.ts +++ b/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/__tests__/fixtures/normalize_times.ts @@ -1,802 +1,809 @@ export const inputTimes = [ { - type:"BooleanQuery", - description:"hour:1 hour:2 #MatchNoDocsQuery[\"User requested \"match_none\" query.\"]", - time:0.447365, - breakdown:[ + type: 'BooleanQuery', + description: 'hour:1 hour:2 #MatchNoDocsQuery["User requested "match_none" query."]', + time: 0.447365, + breakdown: [ { - key:"create_weight", - time:401690, - relative:"89.8", - color:"#feb6b6", - tip:"The time taken to create the Weight object, which holds temporary information during scoring." + key: 'create_weight', + time: 401690, + relative: '89.8', + color: '#feb6b6', + tip: + 'The time taken to create the Weight object, which holds temporary information during scoring.', }, { - key:"build_scorer", - time:45672, - relative:"10.2", - color:"#f6eeee", - tip:"The time taken to create the Scoring object, which is later used to execute the actual scoring of each doc." + key: 'build_scorer', + time: 45672, + relative: '10.2', + color: '#f6eeee', + tip: + 'The time taken to create the Scoring object, which is later used to execute the actual scoring of each doc.', }, { - key:"build_scorer_count", - time:2, - relative:0, - color:"#f5f5f5", - tip:"" + key: 'build_scorer_count', + time: 2, + relative: 0, + color: '#f5f5f5', + tip: '', }, { - key:"create_weight_count", - time:1, - relative:0, - color:"#f5f5f5", - tip:"" + key: 'create_weight_count', + time: 1, + relative: 0, + color: '#f5f5f5', + tip: '', }, { - key:"next_doc", - time:0, - relative:"0.0", - color:"#f5f5f5", - tip:"The time taken to advance the iterator to the next matching document." + key: 'next_doc', + time: 0, + relative: '0.0', + color: '#f5f5f5', + tip: 'The time taken to advance the iterator to the next matching document.', }, { - key:"match", - time:0, - relative:"0.0", - color:"#f5f5f5", - tip:"The time taken to execute a secondary, more precise scoring phase (used by phrase queries)." + key: 'match', + time: 0, + relative: '0.0', + color: '#f5f5f5', + tip: + 'The time taken to execute a secondary, more precise scoring phase (used by phrase queries).', }, { - key:"match_count", - time:0, - relative:0, - color:"#f5f5f5", - tip:"" + key: 'match_count', + time: 0, + relative: 0, + color: '#f5f5f5', + tip: '', }, { - key:"next_doc_count", - time:0, - relative:0, - color:"#f5f5f5", - tip:"" + key: 'next_doc_count', + time: 0, + relative: 0, + color: '#f5f5f5', + tip: '', }, { - key:"score_count", - time:0, - relative:0, - color:"#f5f5f5", - tip:"" + key: 'score_count', + time: 0, + relative: 0, + color: '#f5f5f5', + tip: '', }, { - key:"score", - time:0, - relative:"0.0", - color:"#f5f5f5", - tip:"The time taken in actually scoring the document against the query." + key: 'score', + time: 0, + relative: '0.0', + color: '#f5f5f5', + tip: 'The time taken in actually scoring the document against the query.', }, { - key:"advance", - time:0, - relative:"0.0", - color:"#f5f5f5", - tip:"The time taken to advance the iterator to the next document." + key: 'advance', + time: 0, + relative: '0.0', + color: '#f5f5f5', + tip: 'The time taken to advance the iterator to the next document.', }, { - key:"advance_count", - time:0, - relative:0, - color:"#f5f5f5", - tip:"" - } + key: 'advance_count', + time: 0, + relative: 0, + color: '#f5f5f5', + tip: '', + }, ], - "children":[ + children: [ { - type:"TermQuery", - description:"hour:1", - time:0.192502, - breakdown:[ + type: 'TermQuery', + description: 'hour:1', + time: 0.192502, + breakdown: [ { - key:"create_weight", - time:190989, - relative:"99.2", - color:"#ffb0b0", - tip:"The time taken to create the Weight object, which holds temporary information during scoring." + key: 'create_weight', + time: 190989, + relative: '99.2', + color: '#ffb0b0', + tip: + 'The time taken to create the Weight object, which holds temporary information during scoring.', }, { - key:"build_scorer", - time:1510, - relative:"0.8", - color:"#f5f4f4", - tip:"The time taken to create the Scoring object, which is later used to execute the actual scoring of each doc." + key: 'build_scorer', + time: 1510, + relative: '0.8', + color: '#f5f4f4', + tip: + 'The time taken to create the Scoring object, which is later used to execute the actual scoring of each doc.', }, { - key:"build_scorer_count", - time:2, - relative:0, - color:"#f5f5f5", - tip:"" + key: 'build_scorer_count', + time: 2, + relative: 0, + color: '#f5f5f5', + tip: '', }, { - key:"create_weight_count", - time:1, - relative:0, - color:"#f5f5f5", - tip:"" + key: 'create_weight_count', + time: 1, + relative: 0, + color: '#f5f5f5', + tip: '', }, { - key:"next_doc", - time:0, - relative:"0.0", - color:"#f5f5f5", - tip:"The time taken to advance the iterator to the next matching document." + key: 'next_doc', + time: 0, + relative: '0.0', + color: '#f5f5f5', + tip: 'The time taken to advance the iterator to the next matching document.', }, { - key:"match", - time:0, - relative:"0.0", - color:"#f5f5f5", - tip:"The time taken to execute a secondary, more precise scoring phase (used by phrase queries)." + key: 'match', + time: 0, + relative: '0.0', + color: '#f5f5f5', + tip: + 'The time taken to execute a secondary, more precise scoring phase (used by phrase queries).', }, { - key:"match_count", - time:0, - relative:0, - color:"#f5f5f5", - tip:"" + key: 'match_count', + time: 0, + relative: 0, + color: '#f5f5f5', + tip: '', }, { - key:"next_doc_count", - time:0, - relative:0, - color:"#f5f5f5", - tip:"" + key: 'next_doc_count', + time: 0, + relative: 0, + color: '#f5f5f5', + tip: '', }, { - key:"score_count", - time:0, - relative:0, - color:"#f5f5f5", - tip:"" + key: 'score_count', + time: 0, + relative: 0, + color: '#f5f5f5', + tip: '', }, { - key:"score", - time:0, - relative:"0.0", - color:"#f5f5f5", - tip:"The time taken in actually scoring the document against the query." + key: 'score', + time: 0, + relative: '0.0', + color: '#f5f5f5', + tip: 'The time taken in actually scoring the document against the query.', }, { - key:"advance", - time:0, - relative:"0.0", - color:"#f5f5f5", - tip:"The time taken to advance the iterator to the next document." + key: 'advance', + time: 0, + relative: '0.0', + color: '#f5f5f5', + tip: 'The time taken to advance the iterator to the next document.', }, { - key:"advance_count", - time:0, - relative:0, - color:"#f5f5f5", - tip:"" - } - ], - id:"3339dca6-c34a-49f3-a534-27e46f238bcd", - parentId:"f1e689b1-dafe-4c2b-9a4d-9bd8f1a53803", - childrenIds:[ - + key: 'advance_count', + time: 0, + relative: 0, + color: '#f5f5f5', + tip: '', + }, ], - selfTime:0.192502 + id: '3339dca6-c34a-49f3-a534-27e46f238bcd', + parentId: 'f1e689b1-dafe-4c2b-9a4d-9bd8f1a53803', + childrenIds: [], + selfTime: 0.192502, }, { - type:"TermQuery", - description:"hour:2", - time:0.162608, - breakdown:[ + type: 'TermQuery', + description: 'hour:2', + time: 0.162608, + breakdown: [ { - key:"create_weight", - time:162016, - relative:"99.6", - color:"#ffafaf", - tip:"The time taken to create the Weight object, which holds temporary information during scoring." + key: 'create_weight', + time: 162016, + relative: '99.6', + color: '#ffafaf', + tip: + 'The time taken to create the Weight object, which holds temporary information during scoring.', }, { - key:"build_scorer", - time:589, - relative:"0.4", - color:"#f5f5f5", - tip:"The time taken to create the Scoring object, which is later used to execute the actual scoring of each doc." + key: 'build_scorer', + time: 589, + relative: '0.4', + color: '#f5f5f5', + tip: + 'The time taken to create the Scoring object, which is later used to execute the actual scoring of each doc.', }, { - key:"build_scorer_count", - time:2, - relative:0, - color:"#f5f5f5", - tip:"" + key: 'build_scorer_count', + time: 2, + relative: 0, + color: '#f5f5f5', + tip: '', }, { - key:"create_weight_count", - time:1, - relative:0, - color:"#f5f5f5", - tip:"" + key: 'create_weight_count', + time: 1, + relative: 0, + color: '#f5f5f5', + tip: '', }, { - key:"next_doc", - time:0, - relative:"0.0", - color:"#f5f5f5", - tip:"The time taken to advance the iterator to the next matching document." + key: 'next_doc', + time: 0, + relative: '0.0', + color: '#f5f5f5', + tip: 'The time taken to advance the iterator to the next matching document.', }, { - key:"match", - time:0, - relative:"0.0", - color:"#f5f5f5", - tip:"The time taken to execute a secondary, more precise scoring phase (used by phrase queries)." + key: 'match', + time: 0, + relative: '0.0', + color: '#f5f5f5', + tip: + 'The time taken to execute a secondary, more precise scoring phase (used by phrase queries).', }, { - key:"match_count", - time:0, - relative:0, - color:"#f5f5f5", - tip:"" + key: 'match_count', + time: 0, + relative: 0, + color: '#f5f5f5', + tip: '', }, { - key:"next_doc_count", - time:0, - relative:0, - color:"#f5f5f5", - tip:"" + key: 'next_doc_count', + time: 0, + relative: 0, + color: '#f5f5f5', + tip: '', }, { - key:"score_count", - time:0, - relative:0, - color:"#f5f5f5", - tip:"" + key: 'score_count', + time: 0, + relative: 0, + color: '#f5f5f5', + tip: '', }, { - key:"score", - time:0, - relative:"0.0", - color:"#f5f5f5", - tip:"The time taken in actually scoring the document against the query." + key: 'score', + time: 0, + relative: '0.0', + color: '#f5f5f5', + tip: 'The time taken in actually scoring the document against the query.', }, { - key:"advance", - time:0, - relative:"0.0", - color:"#f5f5f5", - tip:"The time taken to advance the iterator to the next document." + key: 'advance', + time: 0, + relative: '0.0', + color: '#f5f5f5', + tip: 'The time taken to advance the iterator to the next document.', }, { - key:"advance_count", - time:0, - relative:0, - color:"#f5f5f5", - tip:"" - } - ], - id:"9b75ecdd-a1da-45eb-8d13-5bc5f472dba3", - parentId:"f1e689b1-dafe-4c2b-9a4d-9bd8f1a53803", - childrenIds:[ - + key: 'advance_count', + time: 0, + relative: 0, + color: '#f5f5f5', + tip: '', + }, ], - selfTime:0.162608 + id: '9b75ecdd-a1da-45eb-8d13-5bc5f472dba3', + parentId: 'f1e689b1-dafe-4c2b-9a4d-9bd8f1a53803', + childrenIds: [], + selfTime: 0.162608, }, { - type:"MatchNoDocsQuery", - description:"MatchNoDocsQuery[\"User requested \"match_none\" query.\"]", - time:0.03517, - breakdown:[ + type: 'MatchNoDocsQuery', + description: 'MatchNoDocsQuery["User requested "match_none" query."]', + time: 0.03517, + breakdown: [ { - key:"build_scorer", - time:32522, - relative:"92.5", - color:"#feb4b4", - tip:"The time taken to create the Scoring object, which is later used to execute the actual scoring of each doc." + key: 'build_scorer', + time: 32522, + relative: '92.5', + color: '#feb4b4', + tip: + 'The time taken to create the Scoring object, which is later used to execute the actual scoring of each doc.', }, { - key:"create_weight", - time:2645, - relative:"7.5", - color:"#f6f0f0", - tip:"The time taken to create the Weight object, which holds temporary information during scoring." + key: 'create_weight', + time: 2645, + relative: '7.5', + color: '#f6f0f0', + tip: + 'The time taken to create the Weight object, which holds temporary information during scoring.', }, { - key:"build_scorer_count", - time:2, - relative:0, - color:"#f5f5f5", - tip:"" + key: 'build_scorer_count', + time: 2, + relative: 0, + color: '#f5f5f5', + tip: '', }, { - key:"create_weight_count", - time:1, - relative:0, - color:"#f5f5f5", - tip:"" + key: 'create_weight_count', + time: 1, + relative: 0, + color: '#f5f5f5', + tip: '', }, { - key:"next_doc", - time:0, - relative:"0.0", - color:"#f5f5f5", - tip:"The time taken to advance the iterator to the next matching document." + key: 'next_doc', + time: 0, + relative: '0.0', + color: '#f5f5f5', + tip: 'The time taken to advance the iterator to the next matching document.', }, { - key:"match", - time:0, - relative:"0.0", - color:"#f5f5f5", - tip:"The time taken to execute a secondary, more precise scoring phase (used by phrase queries)." + key: 'match', + time: 0, + relative: '0.0', + color: '#f5f5f5', + tip: + 'The time taken to execute a secondary, more precise scoring phase (used by phrase queries).', }, { - key:"match_count", - time:0, - relative:0, - color:"#f5f5f5", - tip:"" + key: 'match_count', + time: 0, + relative: 0, + color: '#f5f5f5', + tip: '', }, { - key:"next_doc_count", - time:0, - relative:0, - color:"#f5f5f5", - tip:"" + key: 'next_doc_count', + time: 0, + relative: 0, + color: '#f5f5f5', + tip: '', }, { - key:"score_count", - time:0, - relative:0, - color:"#f5f5f5", - tip:"" + key: 'score_count', + time: 0, + relative: 0, + color: '#f5f5f5', + tip: '', }, { - key:"score", - time:0, - relative:"0.0", - color:"#f5f5f5", - tip:"The time taken in actually scoring the document against the query." + key: 'score', + time: 0, + relative: '0.0', + color: '#f5f5f5', + tip: 'The time taken in actually scoring the document against the query.', }, { - key:"advance", - time:0, - relative:"0.0", - color:"#f5f5f5", - tip:"The time taken to advance the iterator to the next document." + key: 'advance', + time: 0, + relative: '0.0', + color: '#f5f5f5', + tip: 'The time taken to advance the iterator to the next document.', }, { - key:"advance_count", - time:0, - relative:0, - color:"#f5f5f5", - tip:"" - } - ], - id:"ddf5aa3e-4b22-4332-9d5e-79a6ae0cc9cb", - parentId:"f1e689b1-dafe-4c2b-9a4d-9bd8f1a53803", - childrenIds:[ - + key: 'advance_count', + time: 0, + relative: 0, + color: '#f5f5f5', + tip: '', + }, ], - selfTime:0.03517 - } + id: 'ddf5aa3e-4b22-4332-9d5e-79a6ae0cc9cb', + parentId: 'f1e689b1-dafe-4c2b-9a4d-9bd8f1a53803', + childrenIds: [], + selfTime: 0.03517, + }, ], - id:"f1e689b1-dafe-4c2b-9a4d-9bd8f1a53803", - childrenIds:[ - "3339dca6-c34a-49f3-a534-27e46f238bcd", - "9b75ecdd-a1da-45eb-8d13-5bc5f472dba3", - "ddf5aa3e-4b22-4332-9d5e-79a6ae0cc9cb" + id: 'f1e689b1-dafe-4c2b-9a4d-9bd8f1a53803', + childrenIds: [ + '3339dca6-c34a-49f3-a534-27e46f238bcd', + '9b75ecdd-a1da-45eb-8d13-5bc5f472dba3', + 'ddf5aa3e-4b22-4332-9d5e-79a6ae0cc9cb', ], - hasChildren:true, - selfTime:0.057085 - } + hasChildren: true, + selfTime: 0.057085, + }, ]; - export const normalizedTimes = [ { - type:"BooleanQuery", - description:"hour:1 hour:2 #MatchNoDocsQuery[\"User requested \"match_none\" query.\"]", - time:0.447365, - breakdown:[ + type: 'BooleanQuery', + description: 'hour:1 hour:2 #MatchNoDocsQuery["User requested "match_none" query."]', + time: 0.447365, + breakdown: [ { - key:"create_weight", - time:401690, - relative:"89.8", - color:"#feb6b6", - tip:"The time taken to create the Weight object, which holds temporary information during scoring." + key: 'create_weight', + time: 401690, + relative: '89.8', + color: '#feb6b6', + tip: + 'The time taken to create the Weight object, which holds temporary information during scoring.', }, { - key:"build_scorer", - time:45672, - relative:"10.2", - color:"#f6eeee", - tip:"The time taken to create the Scoring object, which is later used to execute the actual scoring of each doc." + key: 'build_scorer', + time: 45672, + relative: '10.2', + color: '#f6eeee', + tip: + 'The time taken to create the Scoring object, which is later used to execute the actual scoring of each doc.', }, { - key:"build_scorer_count", - time:2, - relative:0, - color:"#f5f5f5", - tip:"" + key: 'build_scorer_count', + time: 2, + relative: 0, + color: '#f5f5f5', + tip: '', }, { - key:"create_weight_count", - time:1, - relative:0, - color:"#f5f5f5", - tip:"" + key: 'create_weight_count', + time: 1, + relative: 0, + color: '#f5f5f5', + tip: '', }, { - key:"next_doc", - time:0, - relative:"0.0", - color:"#f5f5f5", - tip:"The time taken to advance the iterator to the next matching document." + key: 'next_doc', + time: 0, + relative: '0.0', + color: '#f5f5f5', + tip: 'The time taken to advance the iterator to the next matching document.', }, { - key:"match", - time:0, - relative:"0.0", - color:"#f5f5f5", - tip:"The time taken to execute a secondary, more precise scoring phase (used by phrase queries)." + key: 'match', + time: 0, + relative: '0.0', + color: '#f5f5f5', + tip: + 'The time taken to execute a secondary, more precise scoring phase (used by phrase queries).', }, { - key:"match_count", - time:0, - relative:0, - color:"#f5f5f5", - tip:"" + key: 'match_count', + time: 0, + relative: 0, + color: '#f5f5f5', + tip: '', }, { - key:"next_doc_count", - time:0, - relative:0, - color:"#f5f5f5", - tip:"" + key: 'next_doc_count', + time: 0, + relative: 0, + color: '#f5f5f5', + tip: '', }, { - key:"score_count", - time:0, - relative:0, - color:"#f5f5f5", - tip:"" + key: 'score_count', + time: 0, + relative: 0, + color: '#f5f5f5', + tip: '', }, { - key:"score", - time:0, - relative:"0.0", - color:"#f5f5f5", - tip:"The time taken in actually scoring the document against the query." + key: 'score', + time: 0, + relative: '0.0', + color: '#f5f5f5', + tip: 'The time taken in actually scoring the document against the query.', }, { - key:"advance", - time:0, - relative:"0.0", - color:"#f5f5f5", - tip:"The time taken to advance the iterator to the next document." + key: 'advance', + time: 0, + relative: '0.0', + color: '#f5f5f5', + tip: 'The time taken to advance the iterator to the next document.', }, { - key:"advance_count", - time:0, - relative:0, - color:"#f5f5f5", - tip:"" - } + key: 'advance_count', + time: 0, + relative: 0, + color: '#f5f5f5', + tip: '', + }, ], - "children":[ + children: [ { - type:"TermQuery", - description:"hour:1", - time:0.192502, - breakdown:[ + type: 'TermQuery', + description: 'hour:1', + time: 0.192502, + breakdown: [ { - key:"create_weight", - time:190989, - relative:"99.2", - color:"#ffb0b0", - tip:"The time taken to create the Weight object, which holds temporary information during scoring." + key: 'create_weight', + time: 190989, + relative: '99.2', + color: '#ffb0b0', + tip: + 'The time taken to create the Weight object, which holds temporary information during scoring.', }, { - key:"build_scorer", - time:1510, - relative:"0.8", - color:"#f5f4f4", - tip:"The time taken to create the Scoring object, which is later used to execute the actual scoring of each doc." + key: 'build_scorer', + time: 1510, + relative: '0.8', + color: '#f5f4f4', + tip: + 'The time taken to create the Scoring object, which is later used to execute the actual scoring of each doc.', }, { - key:"build_scorer_count", - time:2, - relative:0, - color:"#f5f5f5", - tip:"" + key: 'build_scorer_count', + time: 2, + relative: 0, + color: '#f5f5f5', + tip: '', }, { - key:"create_weight_count", - time:1, - relative:0, - color:"#f5f5f5", - tip:"" + key: 'create_weight_count', + time: 1, + relative: 0, + color: '#f5f5f5', + tip: '', }, { - key:"next_doc", - time:0, - relative:"0.0", - color:"#f5f5f5", - tip:"The time taken to advance the iterator to the next matching document." + key: 'next_doc', + time: 0, + relative: '0.0', + color: '#f5f5f5', + tip: 'The time taken to advance the iterator to the next matching document.', }, { - key:"match", - time:0, - relative:"0.0", - color:"#f5f5f5", - tip:"The time taken to execute a secondary, more precise scoring phase (used by phrase queries)." + key: 'match', + time: 0, + relative: '0.0', + color: '#f5f5f5', + tip: + 'The time taken to execute a secondary, more precise scoring phase (used by phrase queries).', }, { - key:"match_count", - time:0, - relative:0, - color:"#f5f5f5", - tip:"" + key: 'match_count', + time: 0, + relative: 0, + color: '#f5f5f5', + tip: '', }, { - key:"next_doc_count", - time:0, - relative:0, - color:"#f5f5f5", - tip:"" + key: 'next_doc_count', + time: 0, + relative: 0, + color: '#f5f5f5', + tip: '', }, { - key:"score_count", - time:0, - relative:0, - color:"#f5f5f5", - tip:"" + key: 'score_count', + time: 0, + relative: 0, + color: '#f5f5f5', + tip: '', }, { - key:"score", - time:0, - relative:"0.0", - color:"#f5f5f5", - tip:"The time taken in actually scoring the document against the query." + key: 'score', + time: 0, + relative: '0.0', + color: '#f5f5f5', + tip: 'The time taken in actually scoring the document against the query.', }, { - key:"advance", - time:0, - relative:"0.0", - color:"#f5f5f5", - tip:"The time taken to advance the iterator to the next document." + key: 'advance', + time: 0, + relative: '0.0', + color: '#f5f5f5', + tip: 'The time taken to advance the iterator to the next document.', }, { - key:"advance_count", - time:0, - relative:0, - color:"#f5f5f5", - tip:"" - } - ], - id:"3339dca6-c34a-49f3-a534-27e46f238bcd", - parentId:"f1e689b1-dafe-4c2b-9a4d-9bd8f1a53803", - childrenIds:[ - + key: 'advance_count', + time: 0, + relative: 0, + color: '#f5f5f5', + tip: '', + }, ], - selfTime:0.192502, - timePercentage:"43.03", - absoluteColor:"#f9d7d7", - depth:1 + id: '3339dca6-c34a-49f3-a534-27e46f238bcd', + parentId: 'f1e689b1-dafe-4c2b-9a4d-9bd8f1a53803', + childrenIds: [], + selfTime: 0.192502, + timePercentage: '43.03', + absoluteColor: '#f9d7d7', }, { - type:"TermQuery", - description:"hour:2", - time:0.162608, - breakdown:[ + type: 'TermQuery', + description: 'hour:2', + time: 0.162608, + breakdown: [ { - key:"create_weight", - time:162016, - relative:"99.6", - color:"#ffafaf", - tip:"The time taken to create the Weight object, which holds temporary information during scoring." + key: 'create_weight', + time: 162016, + relative: '99.6', + color: '#ffafaf', + tip: + 'The time taken to create the Weight object, which holds temporary information during scoring.', }, { - key:"build_scorer", - time:589, - relative:"0.4", - color:"#f5f5f5", - tip:"The time taken to create the Scoring object, which is later used to execute the actual scoring of each doc." + key: 'build_scorer', + time: 589, + relative: '0.4', + color: '#f5f5f5', + tip: + 'The time taken to create the Scoring object, which is later used to execute the actual scoring of each doc.', }, { - key:"build_scorer_count", - time:2, - relative:0, - color:"#f5f5f5", - tip:"" + key: 'build_scorer_count', + time: 2, + relative: 0, + color: '#f5f5f5', + tip: '', }, { - key:"create_weight_count", - time:1, - relative:0, - color:"#f5f5f5", - tip:"" + key: 'create_weight_count', + time: 1, + relative: 0, + color: '#f5f5f5', + tip: '', }, { - key:"next_doc", - time:0, - relative:"0.0", - color:"#f5f5f5", - tip:"The time taken to advance the iterator to the next matching document." + key: 'next_doc', + time: 0, + relative: '0.0', + color: '#f5f5f5', + tip: 'The time taken to advance the iterator to the next matching document.', }, { - key:"match", - time:0, - relative:"0.0", - color:"#f5f5f5", - tip:"The time taken to execute a secondary, more precise scoring phase (used by phrase queries)." + key: 'match', + time: 0, + relative: '0.0', + color: '#f5f5f5', + tip: + 'The time taken to execute a secondary, more precise scoring phase (used by phrase queries).', }, { - key:"match_count", - time:0, - relative:0, - color:"#f5f5f5", - tip:"" + key: 'match_count', + time: 0, + relative: 0, + color: '#f5f5f5', + tip: '', }, { - key:"next_doc_count", - time:0, - relative:0, - color:"#f5f5f5", - tip:"" + key: 'next_doc_count', + time: 0, + relative: 0, + color: '#f5f5f5', + tip: '', }, { - key:"score_count", - time:0, - relative:0, - color:"#f5f5f5", - tip:"" + key: 'score_count', + time: 0, + relative: 0, + color: '#f5f5f5', + tip: '', }, { - key:"score", - time:0, - relative:"0.0", - color:"#f5f5f5", - tip:"The time taken in actually scoring the document against the query." + key: 'score', + time: 0, + relative: '0.0', + color: '#f5f5f5', + tip: 'The time taken in actually scoring the document against the query.', }, { - key:"advance", - time:0, - relative:"0.0", - color:"#f5f5f5", - tip:"The time taken to advance the iterator to the next document." + key: 'advance', + time: 0, + relative: '0.0', + color: '#f5f5f5', + tip: 'The time taken to advance the iterator to the next document.', }, { - key:"advance_count", - time:0, - relative:0, - color:"#f5f5f5", - tip:"" - } - ], - id:"9b75ecdd-a1da-45eb-8d13-5bc5f472dba3", - parentId:"f1e689b1-dafe-4c2b-9a4d-9bd8f1a53803", - childrenIds:[ - + key: 'advance_count', + time: 0, + relative: 0, + color: '#f5f5f5', + tip: '', + }, ], - selfTime:0.162608, - timePercentage:"36.35", - absoluteColor:"#f9dcdc", - depth:1 + id: '9b75ecdd-a1da-45eb-8d13-5bc5f472dba3', + parentId: 'f1e689b1-dafe-4c2b-9a4d-9bd8f1a53803', + childrenIds: [], + selfTime: 0.162608, + timePercentage: '36.35', + absoluteColor: '#f9dcdc', }, { - type:"MatchNoDocsQuery", - description:"MatchNoDocsQuery[\"User requested \"match_none\" query.\"]", - time:0.03517, - breakdown:[ + type: 'MatchNoDocsQuery', + description: 'MatchNoDocsQuery["User requested "match_none" query."]', + time: 0.03517, + breakdown: [ { - key:"build_scorer", - time:32522, - relative:"92.5", - color:"#feb4b4", - tip:"The time taken to create the Scoring object, which is later used to execute the actual scoring of each doc." + key: 'build_scorer', + time: 32522, + relative: '92.5', + color: '#feb4b4', + tip: + 'The time taken to create the Scoring object, which is later used to execute the actual scoring of each doc.', }, { - key:"create_weight", - time:2645, - relative:"7.5", - color:"#f6f0f0", - tip:"The time taken to create the Weight object, which holds temporary information during scoring." + key: 'create_weight', + time: 2645, + relative: '7.5', + color: '#f6f0f0', + tip: + 'The time taken to create the Weight object, which holds temporary information during scoring.', }, { - key:"build_scorer_count", - time:2, - relative:0, - color:"#f5f5f5", - tip:"" + key: 'build_scorer_count', + time: 2, + relative: 0, + color: '#f5f5f5', + tip: '', }, { - key:"create_weight_count", - time:1, - relative:0, - color:"#f5f5f5", - tip:"" + key: 'create_weight_count', + time: 1, + relative: 0, + color: '#f5f5f5', + tip: '', }, { - key:"next_doc", - time:0, - relative:"0.0", - color:"#f5f5f5", - tip:"The time taken to advance the iterator to the next matching document." + key: 'next_doc', + time: 0, + relative: '0.0', + color: '#f5f5f5', + tip: 'The time taken to advance the iterator to the next matching document.', }, { - key:"match", - time:0, - relative:"0.0", - color:"#f5f5f5", - tip:"The time taken to execute a secondary, more precise scoring phase (used by phrase queries)." + key: 'match', + time: 0, + relative: '0.0', + color: '#f5f5f5', + tip: + 'The time taken to execute a secondary, more precise scoring phase (used by phrase queries).', }, { - key:"match_count", - time:0, - relative:0, - color:"#f5f5f5", - tip:"" + key: 'match_count', + time: 0, + relative: 0, + color: '#f5f5f5', + tip: '', }, { - key:"next_doc_count", - time:0, - relative:0, - color:"#f5f5f5", - tip:"" + key: 'next_doc_count', + time: 0, + relative: 0, + color: '#f5f5f5', + tip: '', }, { - key:"score_count", - time:0, - relative:0, - color:"#f5f5f5", - tip:"" + key: 'score_count', + time: 0, + relative: 0, + color: '#f5f5f5', + tip: '', }, { - key:"score", - time:0, - relative:"0.0", - color:"#f5f5f5", - tip:"The time taken in actually scoring the document against the query." + key: 'score', + time: 0, + relative: '0.0', + color: '#f5f5f5', + tip: 'The time taken in actually scoring the document against the query.', }, { - key:"advance", - time:0, - relative:"0.0", - color:"#f5f5f5", - tip:"The time taken to advance the iterator to the next document." + key: 'advance', + time: 0, + relative: '0.0', + color: '#f5f5f5', + tip: 'The time taken to advance the iterator to the next document.', }, { - key:"advance_count", - time:0, - relative:0, - color:"#f5f5f5", - tip:"" - } - ], - id:"ddf5aa3e-4b22-4332-9d5e-79a6ae0cc9cb", - parentId:"f1e689b1-dafe-4c2b-9a4d-9bd8f1a53803", - childrenIds:[ - + key: 'advance_count', + time: 0, + relative: 0, + color: '#f5f5f5', + tip: '', + }, ], - selfTime:0.03517, - timePercentage:"7.86", - absoluteColor:"#f6efef", - depth:1 - } + id: 'ddf5aa3e-4b22-4332-9d5e-79a6ae0cc9cb', + parentId: 'f1e689b1-dafe-4c2b-9a4d-9bd8f1a53803', + childrenIds: [], + selfTime: 0.03517, + timePercentage: '7.86', + absoluteColor: '#f6efef', + }, ], - id:"f1e689b1-dafe-4c2b-9a4d-9bd8f1a53803", - childrenIds:[ - "3339dca6-c34a-49f3-a534-27e46f238bcd", - "9b75ecdd-a1da-45eb-8d13-5bc5f472dba3", - "ddf5aa3e-4b22-4332-9d5e-79a6ae0cc9cb" + id: 'f1e689b1-dafe-4c2b-9a4d-9bd8f1a53803', + childrenIds: [ + '3339dca6-c34a-49f3-a534-27e46f238bcd', + '9b75ecdd-a1da-45eb-8d13-5bc5f472dba3', + 'ddf5aa3e-4b22-4332-9d5e-79a6ae0cc9cb', ], - hasChildren:true, - selfTime:0.057085, - timePercentage:"100.00", - absoluteColor:"#ffafaf", - depth:0 - } + hasChildren: true, + selfTime: 0.057085, + timePercentage: '100.00', + absoluteColor: '#ffafaf', + }, ]; diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/__tests__/fixtures/search_response.ts b/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/__tests__/fixtures/search_response.ts index b4483cc0fc58e..335c6addd40d7 100644 --- a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/__tests__/fixtures/search_response.ts +++ b/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/__tests__/fixtures/search_response.ts @@ -182,6 +182,114 @@ export const searchResponse: any = [ create_weight_count: 1, build_scorer: 17681, }, + children: [ + { + type: 'DocValuesFieldExistsQuery', + description: 'DocValuesFieldExistsQuery [field=_primary_term]', + time_in_nanos: 19795, + breakdown: { + set_min_competitive_score_count: 0, + match_count: 0, + shallow_advance_count: 0, + set_min_competitive_score: 0, + next_doc: 600, + match: 0, + next_doc_count: 5, + score_count: 0, + compute_max_score_count: 0, + compute_max_score: 0, + advance: 378, + advance_count: 3, + score: 0, + build_scorer_count: 6, + create_weight: 1121, + shallow_advance: 0, + create_weight_count: 1, + build_scorer: 17681, + }, + children: [ + { + type: 'DocValuesFieldExistsQuery', + description: 'DocValuesFieldExistsQuery [field=_primary_term]', + time_in_nanos: 19795, + breakdown: { + set_min_competitive_score_count: 0, + match_count: 0, + shallow_advance_count: 0, + set_min_competitive_score: 0, + next_doc: 600, + match: 0, + next_doc_count: 5, + score_count: 0, + compute_max_score_count: 0, + compute_max_score: 0, + advance: 378, + advance_count: 3, + score: 0, + build_scorer_count: 6, + create_weight: 1121, + shallow_advance: 0, + create_weight_count: 1, + build_scorer: 17681, + }, + children: [ + { + type: 'DocValuesFieldExistsQuery', + description: 'DocValuesFieldExistsQuery [field=_primary_term]', + time_in_nanos: 19795, + breakdown: { + set_min_competitive_score_count: 0, + match_count: 0, + shallow_advance_count: 0, + set_min_competitive_score: 0, + next_doc: 600, + match: 0, + next_doc_count: 5, + score_count: 0, + compute_max_score_count: 0, + compute_max_score: 0, + advance: 378, + advance_count: 3, + score: 0, + build_scorer_count: 6, + create_weight: 1121, + shallow_advance: 0, + create_weight_count: 1, + build_scorer: 17681, + }, + children: [ + { + type: 'DocValuesFieldExistsQuery', + description: 'DocValuesFieldExistsQuery [field=_primary_term]', + time_in_nanos: 19795, + breakdown: { + set_min_competitive_score_count: 0, + match_count: 0, + shallow_advance_count: 0, + set_min_competitive_score: 0, + next_doc: 600, + match: 0, + next_doc_count: 5, + score_count: 0, + compute_max_score_count: 0, + compute_max_score: 0, + advance: 378, + advance_count: 3, + score: 0, + build_scorer_count: 6, + create_weight: 1121, + shallow_advance: 0, + create_weight_count: 1, + build_scorer: 17681, + }, + }, + ], + }, + ], + }, + ], + }, + ], }, ], }, diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/__tests__/init_data.test.ts b/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/__tests__/init_data.test.ts index 6cd19947a26bc..78247f0ccf31b 100644 --- a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/__tests__/init_data.test.ts +++ b/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/__tests__/init_data.test.ts @@ -5,6 +5,9 @@ */ import { ShardSerialized } from '../../../types'; +jest.mock('../constants', () => ({ + MAX_TREE_DEPTH: 3, +})); import { initDataFor } from '../init_data'; import { searchResponse } from './fixtures/search_response'; @@ -15,6 +18,16 @@ describe('ProfileTree init data', () => { const input: ShardSerialized[] = searchResponse as any; const actual = initDataFor('searches')(input); + /* prettier-ignore */ + expect( + actual[1].shards[0].searches![0] + .treeRoot! // level 0 + .children[0] // level 1 + .children[0] // level 2 + .children[0] // level 3 -- Max level! + .children, // level 4 (nothing here!) + ).toEqual([]); + expect(actual[0].name).toEqual(processedResponseWithFirstShard[0].name); const expectedFirstShard = processedResponseWithFirstShard[0].shards[0]; expect(actual[0].shards[0]).toEqual(expectedFirstShard); diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/__tests__/unsafe_utils.test.ts b/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/__tests__/unsafe_utils.test.ts index 17c7051f09769..ca423310e8375 100644 --- a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/__tests__/unsafe_utils.test.ts +++ b/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/__tests__/unsafe_utils.test.ts @@ -17,13 +17,16 @@ describe('normalizeBreakdown', function() { }); }); -describe('normalizeTimes', function() { +describe('normalizeTime', function() { it('returns correct normalization', function() { const totalTime = 0.447365; // Deep clone the object to preserve the original const input = JSON.parse(JSON.stringify(inputTimes)); - util.normalizeTimes(input, totalTime, 0); + + // Simulate recursive application to the tree. + input.forEach((i: any) => util.normalizeTime(i, totalTime)); + input[0].children.forEach((i: any) => util.normalizeTime(i, totalTime)); // Modifies in place, so inputTimes will change expect(input).to.eql(normalizedTimes); diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/constants.ts b/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/constants.ts new file mode 100644 index 0000000000000..b5122f93f1981 --- /dev/null +++ b/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/constants.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +// Prevent recursive data structures from blowing up the JS call stack. +export const MAX_TREE_DEPTH = 40; diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/init_data.ts b/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/init_data.ts index 642b2b741abf0..af24a8936c915 100644 --- a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/init_data.ts +++ b/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/init_data.ts @@ -4,18 +4,15 @@ * you may not use this file except in compliance with the Elastic License. */ -import { produce } from 'immer'; +import cloneDeep from 'lodash.clonedeep'; import { flow } from 'fp-ts/lib/function'; import { Targets, Shard, ShardSerialized } from '../../types'; -import { calcTimes, normalizeTimes, initTree, normalizeIndices, sortIndices } from './unsafe_utils'; +import { calcTimes, initTree, normalizeIndices, sortIndices } from './unsafe_utils'; import { IndexMap } from './types'; /** * Functions prefixed with "mutate" change values by reference. Be careful when using these! - * - * It's recommended to us immer's `produce` functions to ensure immutability. */ - export function mutateAggsTimesTree(shard: Shard) { if (shard.aggregations == null) { shard.time = 0; @@ -26,8 +23,7 @@ export function mutateAggsTimesTree(shard: Shard) { shardTime += totalTime; } for (const agg of shard.aggregations!) { - normalizeTimes([agg], shardTime, 0); - initTree([agg], 0); + initTree([agg], shardTime); } shard.time = shardTime; } @@ -43,66 +39,76 @@ export function mutateSearchTimesTree(shard: Shard) { shard.rewrite_time += search.rewrite_time!; const totalTime = calcTimes(search.query!); shardTime += totalTime; - normalizeTimes(search.query!, totalTime, 0); - initTree(search.query!, 0); + initTree(search.query!, totalTime); search.treeRoot = search.query![0]; + // Remove this object. search.query = null as any; } shard.time = shardTime; } const initShards = (data: ShardSerialized[]) => - produce(data, draft => { - return draft.map(s => { - const idMatch = s.id.match(/\[([^\]\[]*?)\]/g) || []; - const ids = idMatch.map(id => { - return id.replace('[', '').replace(']', ''); - }); - return { - ...s, - id: ids, - time: 0, - color: '', - relative: 0, - }; + data.map(s => { + const idMatch = s.id.match(/\[([^\]\[]*?)\]/g) || []; + const ids = idMatch.map(id => { + return id.replace('[', '').replace(']', ''); }); + return { + ...s, + id: ids, + time: 0, + color: '', + relative: 0, + }; }); -export const calculateShardValues = (target: Targets) => (data: Shard[]) => - produce(data, draft => { - for (const shard of draft) { - if (target === 'searches') { - mutateSearchTimesTree(shard); - } else if (target === 'aggregations') { - mutateAggsTimesTree(shard); - } +export const calculateShardValues = (target: Targets) => (data: Shard[]) => { + const mutateTimesTree = + target === 'searches' + ? mutateSearchTimesTree + : target === 'aggregations' + ? mutateAggsTimesTree + : null; + + if (mutateTimesTree) { + for (const shard of data) { + mutateTimesTree(shard); } - }); + } -export const initIndices = (data: Shard[]) => - produce(data, doNotChange => { - const indices: IndexMap = {}; + return data; +}; - for (const shard of doNotChange) { - if (!indices[shard.id[1]]) { - indices[shard.id[1]] = { - shards: [], - time: 0, - name: shard.id[1], - visible: false, - }; - } - indices[shard.id[1]].shards.push(shard); - indices[shard.id[1]].time += shard.time; +export const initIndices = (data: Shard[]) => { + const indices: IndexMap = {}; + + for (const shard of data) { + if (!indices[shard.id[1]]) { + indices[shard.id[1]] = { + shards: [], + time: 0, + name: shard.id[1], + visible: false, + }; } + indices[shard.id[1]].shards.push(shard); + indices[shard.id[1]].time += shard.time; + } - return indices; - }); + return indices; +}; -export const normalize = (target: Targets) => (data: IndexMap) => - produce(data, draft => { - normalizeIndices(draft, target); - }); +export const normalize = (target: Targets) => (data: IndexMap) => { + normalizeIndices(data, target); + return data; +}; export const initDataFor = (target: Targets) => - flow(initShards, calculateShardValues(target), initIndices, normalize(target), sortIndices); + flow( + cloneDeep, + initShards, + calculateShardValues(target), + initIndices, + normalize(target), + sortIndices + ); diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/shard_details/shard_details.tsx b/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/shard_details/shard_details.tsx index eca2d1994f8c5..5ca8ad4ecd979 100644 --- a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/shard_details/shard_details.tsx +++ b/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/shard_details/shard_details.tsx @@ -38,7 +38,7 @@ export const ShardDetails = ({ index, shard, operations }: Props) => { diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/shard_details/shard_details_tree_node.tsx b/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/shard_details/shard_details_tree_node.tsx index 75da10f8aca2e..1d8f915d3d47d 100644 --- a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/shard_details/shard_details_tree_node.tsx +++ b/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/shard_details/shard_details_tree_node.tsx @@ -28,8 +28,7 @@ const limitString = (string: string, limit: number) => `${string.slice(0, limit)}${string.length > limit ? '...' : ''}`; /** - * This is a component that recursively iterates over data to render out a tree like - * structure in a flatly. + * This component recursively renders a tree */ export const ShardDetailsTreeNode = ({ operation, index, shard }: Props) => { const [childrenVisible, setChildrenVisible] = useState(hasVisibleChild(operation)); @@ -106,7 +105,7 @@ export const ShardDetailsTreeNode = ({ operation, index, shard }: Props) => {
{childrenVisible && operation.hasChildren && - operation.children.flatMap((childOp, idx) => ( + operation.children.map((childOp, idx) => ( ))} diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/unsafe_utils.ts b/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/unsafe_utils.ts index fc21c5da37764..2201ad42070b7 100644 --- a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/unsafe_utils.ts +++ b/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/components/profile_tree/unsafe_utils.ts @@ -4,13 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import { produce } from 'immer'; import { i18n } from '@kbn/i18n'; import tinycolor from 'tinycolor2'; import _ from 'lodash'; import { BreakdownItem, Index, Operation, Shard, Targets } from '../../types'; import { IndexMap } from './types'; +import { MAX_TREE_DEPTH } from './constants'; export const comparator = (v1: number, v2: number) => { if (v1 < v2) { @@ -53,16 +53,16 @@ function getToolTip(key: string) { } } -export function timeInMilliseconds(data: any) { +export function timeInMilliseconds(data: any): number { if (data.time_in_nanos) { return data.time_in_nanos / 1000000; } if (typeof data.time === 'string') { - return data.time.replace('ms', ''); + return Number(data.time.replace('ms', '')); } - return data.time; + return Number(data.time); } export function calcTimes(data: any[], parentId?: string) { @@ -117,21 +117,6 @@ export function normalizeBreakdown(breakdown: Record) { }); } -export function normalizeTimes(data: any[], totalTime: number, depth: number) { - // Second pass to normalize - for (const child of data) { - child.timePercentage = ((timeInMilliseconds(child) / totalTime) * 100).toFixed(2); - child.absoluteColor = tinycolor.mix('#F5F5F5', '#FFAFAF', child.timePercentage).toHexString(); - child.depth = depth; - - if (child.children != null && child.children.length !== 0) { - normalizeTimes(child.children, totalTime, depth + 1); - } - } - - data.sort((a, b) => comparator(timeInMilliseconds(a), timeInMilliseconds(b))); -} - export function normalizeIndices(indices: IndexMap, target: Targets) { // Sort the shards per-index let sortQueryComponents; @@ -167,7 +152,26 @@ export function normalizeIndices(indices: IndexMap, target: Targets) { } } -export function initTree(data: Operation[], depth = 0, parent: Operation | null = null) { +export function normalizeTime(operation: Operation, totalTime: number) { + operation.timePercentage = ((timeInMilliseconds(operation) / totalTime) * 100).toFixed(2); + operation.absoluteColor = tinycolor + .mix('#F5F5F5', '#FFAFAF', +operation.timePercentage) + .toHexString(); +} + +export function initTree( + data: Operation[], + totalTime: number, + depth = 0, + parent: Operation | null = null +) { + if (MAX_TREE_DEPTH + 1 === depth) { + if (parent) { + parent!.hasChildren = false; + parent!.children = []; + } + return; + } for (const child of data) { // For bwc of older profile responses if (!child.description) { @@ -178,44 +182,28 @@ export function initTree(data: Operation[], depth = 0, parent: Operation | nu child.query_type = null; } - // Use named function for tests. + normalizeTime(child, totalTime); child.parent = parent; child.time = timeInMilliseconds(child); child.lucene = child.description; child.query_type = child.type!.split('.').pop()!; - child.visible = child.timePercentage > 20; + child.visible = +child.timePercentage > 20; child.depth = depth; if (child.children != null && child.children.length !== 0) { - initTree(child.children, depth + 1, child); + initTree(child.children, totalTime, depth + 1, child); } } -} - -export function closeNode(node: Operation) { - const closeDraft = (draft: Operation) => { - draft.visible = false; - if (draft.children == null || draft.children.length === 0) { - return; - } - - for (const child of draft.children) { - closeDraft(child); - } - }; - return produce(node, draft => { - closeDraft(draft); - }); + data.sort((a, b) => comparator(timeInMilliseconds(a), timeInMilliseconds(b))); } -export const sortIndices = (data: IndexMap) => - produce(data, doNotChange => { - const sortedIndices: Index[] = []; - for (const index of Object.values(doNotChange)) { - sortedIndices.push(index); - } - // And now sort the indices themselves - sortedIndices.sort((a, b) => comparator(a.time, b.time)); - return sortedIndices; - }); +export const sortIndices = (data: IndexMap) => { + const sortedIndices: Index[] = []; + for (const index of Object.values(data)) { + sortedIndices.push(index); + } + // And now sort the indices themselves + sortedIndices.sort((a, b) => comparator(a.time, b.time)); + return sortedIndices; +}; diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/store/reducer.test.ts b/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/store/reducer.test.ts new file mode 100644 index 0000000000000..14424a1c88529 --- /dev/null +++ b/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/store/reducer.test.ts @@ -0,0 +1,60 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { reducer } from './reducer'; +import { initialState, State } from './store'; + +describe('Searchprofiler store reducer', () => { + let state: State; + + beforeEach(() => { + state = initialState; + }); + + it('profiles as expected', () => { + const nextState = reducer(state, { type: 'setProfiling', value: true }); + + expect(nextState).toEqual({ + ...state, + pristine: false, + profiling: true, + } as State); + + const finalState = reducer(nextState, { type: 'setProfiling', value: false }); + + expect(finalState).toEqual({ + ...nextState, + pristine: false, + profiling: false, + } as State); + }); + + it('highlights as expected', () => { + const op = { children: null } as any; + const shard = { id: ['a', 'b', 'c'] } as any; + const nextState = reducer(state, { + type: 'setHighlightDetails', + value: { operation: op, indexName: 'test', shard }, + }); + + expect(nextState).toEqual({ + ...state, + highlightDetails: { + operation: { + /* .children no longer defined */ + }, + shardName: '[a][c]', + indexName: 'test', + }, + }); + + const finalState = reducer(state, { + type: 'setHighlightDetails', + value: null, + }); + + expect(finalState).toEqual({ ...state, highlightDetails: null }); + }); +}); diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/store/reducer.ts b/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/store/reducer.ts index 615511786afd1..394110ac49524 100644 --- a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/store/reducer.ts +++ b/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/store/reducer.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { produce } from 'immer'; import { Reducer } from 'react'; import { State } from './store'; @@ -17,36 +16,49 @@ export type Action = | { type: 'setActiveTab'; value: Targets | null } | { type: 'setCurrentResponse'; value: ShardSerialized[] | null }; -export const reducer: Reducer = (state, action) => - produce(state, draft => { - if (action.type === 'setProfiling') { - draft.pristine = false; - draft.profiling = action.value; - if (draft.profiling) { - draft.currentResponse = null; - draft.highlightDetails = null; - } - return; - } +export const reducer: Reducer = (state, action) => { + const nextState = { ...state }; - if (action.type === 'setHighlightDetails') { - draft.highlightDetails = action.value; - return; + if (action.type === 'setProfiling') { + nextState.pristine = false; + nextState.profiling = action.value; + if (nextState.profiling) { + nextState.currentResponse = null; + nextState.highlightDetails = null; } + return nextState; + } - if (action.type === 'setActiveTab') { - draft.activeTab = action.value; - return; + if (action.type === 'setHighlightDetails') { + if (action.value) { + const value = action.value; + // Exclude children to avoid unnecessary work copying a recursive structure. + const { children, parent, ...restOfOperation } = value.operation; + nextState.highlightDetails = { + indexName: value.indexName, + operation: Object.freeze(restOfOperation), + // prettier-ignore + shardName: `[${/* shard id */value.shard.id[0]}][${/* shard number */value.shard.id[2] }]` + }; + } else { + nextState.highlightDetails = null; } + return nextState; + } + + if (action.type === 'setActiveTab') { + nextState.activeTab = action.value; + return nextState; + } - if (action.type === 'setCurrentResponse') { - draft.currentResponse = action.value; - if (draft.currentResponse) { - // Default to the searches tab - draft.activeTab = 'searches'; - } - return; + if (action.type === 'setCurrentResponse') { + nextState.currentResponse = action.value; + if (nextState.currentResponse) { + // Default to the searches tab + nextState.activeTab = 'searches'; } + return nextState; + } - throw new Error(`Unknown action: ${action}`); - }); + throw new Error(`Unknown action: ${action}`); +}; diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/store/store.ts b/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/store/store.ts index 7008854a16285..45194ff917c93 100644 --- a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/store/store.ts +++ b/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/store/store.ts @@ -5,13 +5,20 @@ */ import { useReducer } from 'react'; import { reducer } from './reducer'; -import { ShardSerialized, Targets } from '../types'; -import { OnHighlightChangeArgs } from '../components/profile_tree'; +import { Operation, ShardSerialized, Targets } from '../types'; + +export type OperationNoChildParent = Omit; + +interface HighlightDetails { + indexName: string; + operation: OperationNoChildParent; + shardName: string; +} export interface State { profiling: boolean; pristine: boolean; - highlightDetails: OnHighlightChangeArgs | null; + highlightDetails: HighlightDetails | null; activeTab: Targets | null; currentResponse: ShardSerialized[] | null; } diff --git a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/types.ts b/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/types.ts index 2b4dc01c45d6f..9866f8d5b1ccb 100644 --- a/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/types.ts +++ b/x-pack/legacy/plugins/searchprofiler/public/np_ready/application/types.ts @@ -42,7 +42,7 @@ export interface Operation { hasChildren: boolean; visible: boolean; selfTime: number; - timePercentage: number; + timePercentage: string; absoluteColor: string; time: number; diff --git a/x-pack/legacy/plugins/security/README.md b/x-pack/legacy/plugins/security/README.md index b4786a2df6c52..068f19ba9482b 100644 --- a/x-pack/legacy/plugins/security/README.md +++ b/x-pack/legacy/plugins/security/README.md @@ -1,7 +1,3 @@ # Kibana Security Plugin -1. Install the Security plugin on Kibana `bin/kibana plugin --install kibana/security/latest` -1. Modify [kibana.yml](https://github.com/elastic/kibana/blob/master/config/kibana.yml) and add `xpack.security.encryptionKey: "something_at_least_32_characters"` -1. Make sure that the following config options are also set: `elasticsearch.username`, `elasticsearch.password`, `server.ssl.certificate`, and `server.ssl.key` (see [Configuring Kibana to Work with Shield](https://www.elastic.co/guide/en/kibana/current/production.html#configuring-kibana-shield)) - -Once done, open up the following url (assuming standard kibana config): [https://localhost:5601](https://localhost:5601). +See [Configuring security in Kibana](https://www.elastic.co/guide/en/kibana/current/using-kibana-with-security.html). diff --git a/x-pack/legacy/plugins/security/common/model.ts b/x-pack/legacy/plugins/security/common/model.ts index 90e6a5403dfe8..733e89f774db8 100644 --- a/x-pack/legacy/plugins/security/common/model.ts +++ b/x-pack/legacy/plugins/security/common/model.ts @@ -11,12 +11,17 @@ export { BuiltinESPrivileges, EditUser, FeaturesPrivileges, + InlineRoleTemplate, + InvalidRoleTemplate, KibanaPrivileges, RawKibanaFeaturePrivileges, RawKibanaPrivileges, Role, RoleIndexPrivilege, RoleKibanaPrivilege, + RoleMapping, + RoleTemplate, + StoredRoleTemplate, User, canUserChangePassword, getUserDisplayName, diff --git a/x-pack/legacy/plugins/security/index.js b/x-pack/legacy/plugins/security/index.js index 6ee8b5f8b2b10..bc403b803b8df 100644 --- a/x-pack/legacy/plugins/security/index.js +++ b/x-pack/legacy/plugins/security/index.js @@ -28,17 +28,10 @@ export const security = kibana => enabled: Joi.boolean().default(true), cookieName: HANDLED_IN_NEW_PLATFORM, encryptionKey: HANDLED_IN_NEW_PLATFORM, - session: Joi.object({ - idleTimeout: HANDLED_IN_NEW_PLATFORM, - lifespan: HANDLED_IN_NEW_PLATFORM, - }).default(), + session: HANDLED_IN_NEW_PLATFORM, secureCookies: HANDLED_IN_NEW_PLATFORM, loginAssistanceMessage: HANDLED_IN_NEW_PLATFORM, - authorization: Joi.object({ - legacyFallback: Joi.object({ - enabled: Joi.boolean().default(true), // deprecated - }).default(), - }).default(), + authorization: HANDLED_IN_NEW_PLATFORM, audit: Joi.object({ enabled: Joi.boolean().default(false), }).default(), @@ -46,13 +39,6 @@ export const security = kibana => }).default(); }, - deprecations: function({ rename, unused }) { - return [ - unused('authorization.legacyFallback.enabled'), - rename('sessionTimeout', 'session.idleTimeout'), - ]; - }, - uiExports: { chromeNavControls: [], managementSections: ['plugins/security/views/management'], diff --git a/x-pack/legacy/plugins/security/public/lib/api.ts b/x-pack/legacy/plugins/security/public/lib/api.ts index ffa08ca44f376..c5c6994bf4be3 100644 --- a/x-pack/legacy/plugins/security/public/lib/api.ts +++ b/x-pack/legacy/plugins/security/public/lib/api.ts @@ -5,16 +5,12 @@ */ import { kfetch } from 'ui/kfetch'; -import { AuthenticatedUser, Role, User, EditUser } from '../../common/model'; +import { Role, User, EditUser } from '../../common/model'; const usersUrl = '/internal/security/users'; const rolesUrl = '/api/security/role'; export class UserAPIClient { - public async getCurrentUser(): Promise { - return await kfetch({ pathname: `/internal/security/me` }); - } - public async getUsers(): Promise { return await kfetch({ pathname: usersUrl }); } diff --git a/x-pack/legacy/plugins/security/public/lib/role_mappings_api.ts b/x-pack/legacy/plugins/security/public/lib/role_mappings_api.ts new file mode 100644 index 0000000000000..b8bcba91388b5 --- /dev/null +++ b/x-pack/legacy/plugins/security/public/lib/role_mappings_api.ts @@ -0,0 +1,58 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { CoreSetup } from 'src/core/public'; +import { RoleMapping } from '../../common/model'; + +interface CheckRoleMappingFeaturesResponse { + canManageRoleMappings: boolean; + canUseInlineScripts: boolean; + canUseStoredScripts: boolean; + hasCompatibleRealms: boolean; +} + +type DeleteRoleMappingsResponse = Array<{ + name: string; + success: boolean; + error?: Error; +}>; + +export class RoleMappingsAPI { + constructor(private readonly http: CoreSetup['http']) {} + + public async checkRoleMappingFeatures(): Promise { + return this.http.get(`/internal/security/_check_role_mapping_features`); + } + + public async getRoleMappings(): Promise { + return this.http.get(`/internal/security/role_mapping`); + } + + public async getRoleMapping(name: string): Promise { + return this.http.get(`/internal/security/role_mapping/${encodeURIComponent(name)}`); + } + + public async saveRoleMapping(roleMapping: RoleMapping) { + const payload = { ...roleMapping }; + delete payload.name; + + return this.http.post( + `/internal/security/role_mapping/${encodeURIComponent(roleMapping.name)}`, + { body: JSON.stringify(payload) } + ); + } + + public async deleteRoleMappings(names: string[]): Promise { + return Promise.all( + names.map(name => + this.http + .delete(`/internal/security/role_mapping/${encodeURIComponent(name)}`) + .then(() => ({ success: true, name })) + .catch(error => ({ success: false, name, error })) + ) + ); + } +} diff --git a/x-pack/legacy/plugins/security/public/services/shield_user.js b/x-pack/legacy/plugins/security/public/services/shield_user.js deleted file mode 100644 index 14a79f267ca75..0000000000000 --- a/x-pack/legacy/plugins/security/public/services/shield_user.js +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import 'angular-resource'; -import angular from 'angular'; -import { uiModules } from 'ui/modules'; - -const module = uiModules.get('security', ['ngResource']); -module.service('ShieldUser', ($resource, chrome) => { - const baseUrl = chrome.addBasePath('/internal/security/users/:username'); - const ShieldUser = $resource( - baseUrl, - { - username: '@username', - }, - { - changePassword: { - method: 'POST', - url: `${baseUrl}/password`, - transformRequest: ({ password, newPassword }) => angular.toJson({ password, newPassword }), - }, - getCurrent: { - method: 'GET', - url: chrome.addBasePath('/internal/security/me'), - }, - } - ); - - return ShieldUser; -}); diff --git a/x-pack/legacy/plugins/security/public/views/account/account.js b/x-pack/legacy/plugins/security/public/views/account/account.js index db971bd97eab7..70a7b8dce727e 100644 --- a/x-pack/legacy/plugins/security/public/views/account/account.js +++ b/x-pack/legacy/plugins/security/public/views/account/account.js @@ -6,22 +6,13 @@ import routes from 'ui/routes'; import template from './account.html'; -import '../../services/shield_user'; import { i18n } from '@kbn/i18n'; import { I18nContext } from 'ui/i18n'; +import { npSetup } from 'ui/new_platform'; import { AccountManagementPage } from './components'; import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; -const renderReact = (elem, user) => { - render( - - - , - elem - ); -}; - routes.when('/account', { template, k7Breadcrumbs: () => [ @@ -31,13 +22,8 @@ routes.when('/account', { }), }, ], - resolve: { - user(ShieldUser) { - return ShieldUser.getCurrent().$promise; - }, - }, controllerAs: 'accountController', - controller($scope, $route) { + controller($scope) { $scope.$on('$destroy', () => { const elem = document.getElementById('userProfileReactRoot'); if (elem) { @@ -45,8 +31,12 @@ routes.when('/account', { } }); $scope.$$postDigest(() => { - const elem = document.getElementById('userProfileReactRoot'); - renderReact(elem, $route.current.locals.user); + render( + + + , + document.getElementById('userProfileReactRoot') + ); }); }, }); diff --git a/x-pack/legacy/plugins/security/public/views/account/components/account_management_page.test.tsx b/x-pack/legacy/plugins/security/public/views/account/components/account_management_page.test.tsx index 176b05f455439..366842e58e9e4 100644 --- a/x-pack/legacy/plugins/security/public/views/account/components/account_management_page.test.tsx +++ b/x-pack/legacy/plugins/security/public/views/account/components/account_management_page.test.tsx @@ -4,8 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ import React from 'react'; -import { mountWithIntl } from 'test_utils/enzyme_helpers'; +import { act } from '@testing-library/react'; +import { mountWithIntl, nextTick } from 'test_utils/enzyme_helpers'; +import { securityMock } from '../../../../../../../plugins/security/public/mocks'; import { AccountManagementPage } from './account_management_page'; +import { AuthenticatedUser } from '../../../../common/model'; jest.mock('ui/kfetch'); @@ -32,10 +35,24 @@ const createUser = ({ withFullName = true, withEmail = true, realm = 'native' }: }; }; +function getSecuritySetupMock({ currentUser }: { currentUser: AuthenticatedUser }) { + const securitySetupMock = securityMock.createSetup(); + securitySetupMock.authc.getCurrentUser.mockResolvedValue(currentUser); + return securitySetupMock; +} + describe('', () => { - it(`displays users full name, username, and email address`, () => { + it(`displays users full name, username, and email address`, async () => { const user = createUser(); - const wrapper = mountWithIntl(); + const wrapper = mountWithIntl( + + ); + + await act(async () => { + await nextTick(); + wrapper.update(); + }); + expect(wrapper.find('EuiText[data-test-subj="userDisplayName"]').text()).toEqual( user.full_name ); @@ -43,28 +60,60 @@ describe('', () => { expect(wrapper.find('[data-test-subj="email"]').text()).toEqual(user.email); }); - it(`displays username when full_name is not provided`, () => { + it(`displays username when full_name is not provided`, async () => { const user = createUser({ withFullName: false }); - const wrapper = mountWithIntl(); + const wrapper = mountWithIntl( + + ); + + await act(async () => { + await nextTick(); + wrapper.update(); + }); + expect(wrapper.find('EuiText[data-test-subj="userDisplayName"]').text()).toEqual(user.username); }); - it(`displays a placeholder when no email address is provided`, () => { + it(`displays a placeholder when no email address is provided`, async () => { const user = createUser({ withEmail: false }); - const wrapper = mountWithIntl(); + const wrapper = mountWithIntl( + + ); + + await act(async () => { + await nextTick(); + wrapper.update(); + }); + expect(wrapper.find('[data-test-subj="email"]').text()).toEqual('no email address'); }); - it(`displays change password form for users in the native realm`, () => { + it(`displays change password form for users in the native realm`, async () => { const user = createUser(); - const wrapper = mountWithIntl(); + const wrapper = mountWithIntl( + + ); + + await act(async () => { + await nextTick(); + wrapper.update(); + }); + expect(wrapper.find('EuiFieldText[data-test-subj="currentPassword"]')).toHaveLength(1); expect(wrapper.find('EuiFieldText[data-test-subj="newPassword"]')).toHaveLength(1); }); - it(`does not display change password form for users in the saml realm`, () => { + it(`does not display change password form for users in the saml realm`, async () => { const user = createUser({ realm: 'saml' }); - const wrapper = mountWithIntl(); + const wrapper = mountWithIntl( + + ); + + await act(async () => { + await nextTick(); + wrapper.update(); + }); + expect(wrapper.find('EuiFieldText[data-test-subj="currentPassword"]')).toHaveLength(0); expect(wrapper.find('EuiFieldText[data-test-subj="newPassword"]')).toHaveLength(0); }); diff --git a/x-pack/legacy/plugins/security/public/views/account/components/account_management_page.tsx b/x-pack/legacy/plugins/security/public/views/account/components/account_management_page.tsx index 2ed057ad73a12..6abee73e0b353 100644 --- a/x-pack/legacy/plugins/security/public/views/account/components/account_management_page.tsx +++ b/x-pack/legacy/plugins/security/public/views/account/components/account_management_page.tsx @@ -4,29 +4,41 @@ * you may not use this file except in compliance with the Elastic License. */ import { EuiPage, EuiPageBody, EuiPanel, EuiSpacer, EuiText } from '@elastic/eui'; -import React from 'react'; +import React, { useEffect, useState } from 'react'; +import { SecurityPluginSetup } from '../../../../../../../plugins/security/public'; import { getUserDisplayName, AuthenticatedUser } from '../../../../common/model'; import { ChangePassword } from './change_password'; import { PersonalInfo } from './personal_info'; interface Props { - user: AuthenticatedUser; + securitySetup: SecurityPluginSetup; } -export const AccountManagementPage: React.FC = props => ( - - - - -

{getUserDisplayName(props.user)}

-
+export const AccountManagementPage = (props: Props) => { + const [currentUser, setCurrentUser] = useState(null); + useEffect(() => { + props.securitySetup.authc.getCurrentUser().then(setCurrentUser); + }, [props]); - + if (!currentUser) { + return null; + } - + return ( + + + + +

{getUserDisplayName(currentUser)}

+
- -
-
-
-); + + + + + +
+
+
+ ); +}; diff --git a/x-pack/legacy/plugins/security/public/views/management/_index.scss b/x-pack/legacy/plugins/security/public/views/management/_index.scss index 104fed5980543..78b53845071e4 100644 --- a/x-pack/legacy/plugins/security/public/views/management/_index.scss +++ b/x-pack/legacy/plugins/security/public/views/management/_index.scss @@ -1,3 +1,4 @@ @import './change_password_form/index'; @import './edit_role/index'; -@import './edit_user/index'; \ No newline at end of file +@import './edit_user/index'; +@import './role_mappings/edit_role_mapping/index'; \ No newline at end of file diff --git a/x-pack/legacy/plugins/security/public/views/management/breadcrumbs.ts b/x-pack/legacy/plugins/security/public/views/management/breadcrumbs.ts index 7d345ac13dc41..4ab7e45e84849 100644 --- a/x-pack/legacy/plugins/security/public/views/management/breadcrumbs.ts +++ b/x-pack/legacy/plugins/security/public/views/management/breadcrumbs.ts @@ -86,3 +86,30 @@ export function getApiKeysBreadcrumbs() { }, ]; } + +export function getRoleMappingBreadcrumbs() { + return [ + MANAGEMENT_BREADCRUMB, + { + text: i18n.translate('xpack.security.roleMapping.breadcrumb', { + defaultMessage: 'Role Mappings', + }), + href: '#/management/security/role_mappings', + }, + ]; +} + +export function getEditRoleMappingBreadcrumbs($route: Record) { + const { name } = $route.current.params; + return [ + ...getRoleMappingBreadcrumbs(), + { + text: + name || + i18n.translate('xpack.security.roleMappings.createBreadcrumb', { + defaultMessage: 'Create', + }), + href: `#/management/security/role_mappings/edit/${name}`, + }, + ]; +} diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_matrix.tsx b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_matrix.tsx index 92dace65d466c..962487312c83d 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_matrix.tsx +++ b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_matrix.tsx @@ -23,7 +23,7 @@ import { import { FormattedMessage, InjectedIntl } from '@kbn/i18n/react'; import React, { Component, Fragment } from 'react'; import { Space } from '../../../../../../../../../spaces/common/model/space'; -import { SpaceAvatar } from '../../../../../../../../../spaces/public/components'; +import { SpaceAvatar } from '../../../../../../../../../spaces/public/space_avatar'; import { Feature } from '../../../../../../../../../../../plugins/features/public'; import { FeaturesPrivileges, Role } from '../../../../../../../../common/model'; import { CalculatedPrivilege } from '../../../../../../../lib/kibana_privilege_calculator'; diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_space_table.tsx b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_space_table.tsx index e54b5ff9c45da..65a3df9fb47a1 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_space_table.tsx +++ b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/privilege_space_table.tsx @@ -14,7 +14,7 @@ import { import { FormattedMessage, InjectedIntl } from '@kbn/i18n/react'; import _ from 'lodash'; import React, { Component } from 'react'; -import { getSpaceColor } from '../../../../../../../../../spaces/public/lib/space_attributes'; +import { getSpaceColor } from '../../../../../../../../../spaces/public/space_avatar'; import { Space } from '../../../../../../../../../spaces/common/model/space'; import { FeaturesPrivileges, diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/space_selector.tsx b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/space_selector.tsx index e6e206e5fc7f4..0eb9cf0b0ee9d 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/space_selector.tsx +++ b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/privileges/kibana/space_aware_privilege_section/space_selector.tsx @@ -8,7 +8,7 @@ import { EuiComboBox, EuiComboBoxOptionProps, EuiHealth, EuiHighlight } from '@e import { InjectedIntl } from '@kbn/i18n/react'; import React, { Component } from 'react'; import { Space } from '../../../../../../../../../spaces/common/model/space'; -import { getSpaceColor } from '../../../../../../../../../spaces/public/lib/space_attributes'; +import { getSpaceColor } from '../../../../../../../../../spaces/public/space_avatar'; const spaceToOption = (space?: Space, currentSelection?: 'global' | 'spaces') => { if (!space) { diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/spaces_popover_list/spaces_popover_list.tsx b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/spaces_popover_list/spaces_popover_list.tsx index 1ab2a27220eee..a99e389044eaa 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/components/spaces_popover_list/spaces_popover_list.tsx +++ b/x-pack/legacy/plugins/security/public/views/management/edit_role/components/spaces_popover_list/spaces_popover_list.tsx @@ -14,9 +14,9 @@ import { } from '@elastic/eui'; import { FormattedMessage, InjectedIntl } from '@kbn/i18n/react'; import React, { Component } from 'react'; -import { SPACE_SEARCH_COUNT_THRESHOLD } from '../../../../../../../spaces/common/constants'; -import { Space } from '../../../../../../../spaces/common/model/space'; -import { SpaceAvatar } from '../../../../../../../spaces/public/components'; +import { SpaceAvatar } from '../../../../../../../spaces/public/space_avatar'; +import { SPACE_SEARCH_COUNT_THRESHOLD } from '../../../../../../../../../plugins/spaces/common/constants'; +import { Space } from '../../../../../../../../../plugins/spaces/common/model/space'; interface Props { spaces: Space[]; diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_role/index.js b/x-pack/legacy/plugins/security/public/views/management/edit_role/index.js index 09c612526918f..27c9beb4ba828 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_role/index.js +++ b/x-pack/legacy/plugins/security/public/views/management/edit_role/index.js @@ -11,10 +11,10 @@ import { kfetch } from 'ui/kfetch'; import { fatalError, toastNotifications } from 'ui/notify'; import { npStart } from 'ui/new_platform'; import template from 'plugins/security/views/management/edit_role/edit_role.html'; -import 'plugins/security/services/shield_user'; import 'plugins/security/services/shield_role'; import 'plugins/security/services/shield_indices'; import { xpackInfo } from 'plugins/xpack_main/services/xpack_info'; +import { UserAPIClient } from '../../../lib/api'; import { ROLES_PATH, CLONE_ROLES_PATH, EDIT_ROLES_PATH } from '../management_urls'; import { getEditRoleBreadcrumbs, getCreateRoleBreadcrumbs } from '../breadcrumbs'; @@ -69,9 +69,8 @@ const routeDefinition = action => ({ return role.then(res => res.toJSON()); }, - users(ShieldUser) { - // $promise is used here because the result is an ngResource, not a promise itself - return ShieldUser.query().$promise.then(users => _.map(users, 'username')); + users() { + return new UserAPIClient().getUsers().then(users => _.map(users, 'username')); }, indexPatterns() { return npStart.plugins.data.indexPatterns.getTitles(); diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_user/components/edit_user_page.test.tsx b/x-pack/legacy/plugins/security/public/views/management/edit_user/components/edit_user_page.test.tsx index 5c71d0da3954a..639646ce48e22 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_user/components/edit_user_page.test.tsx +++ b/x-pack/legacy/plugins/security/public/views/management/edit_user/components/edit_user_page.test.tsx @@ -4,38 +4,42 @@ * you may not use this file except in compliance with the Elastic License. */ -import { mountWithIntl } from 'test_utils/enzyme_helpers'; +import { act } from '@testing-library/react'; +import { mountWithIntl, nextTick } from 'test_utils/enzyme_helpers'; import { EditUserPage } from './edit_user_page'; import React from 'react'; +import { securityMock } from '../../../../../../../../plugins/security/public/mocks'; import { UserAPIClient } from '../../../../lib/api'; import { User, Role } from '../../../../../common/model'; import { ReactWrapper } from 'enzyme'; +import { mockAuthenticatedUser } from '../../../../../../../../plugins/security/common/model/authenticated_user.mock'; jest.mock('ui/kfetch'); -const buildClient = () => { - const apiClient = new UserAPIClient(); +const createUser = (username: string) => { + const user: User = { + username, + full_name: 'my full name', + email: 'foo@bar.com', + roles: ['idk', 'something'], + enabled: true, + }; - const createUser = (username: string) => { - const user: User = { - username, - full_name: 'my full name', - email: 'foo@bar.com', - roles: ['idk', 'something'], - enabled: true, + if (username === 'reserved_user') { + user.metadata = { + _reserved: true, }; + } - if (username === 'reserved_user') { - user.metadata = { - _reserved: true, - }; - } + return user; +}; - return Promise.resolve(user); - }; +const buildClient = () => { + const apiClient = new UserAPIClient(); - apiClient.getUser = jest.fn().mockImplementation(createUser); - apiClient.getCurrentUser = jest.fn().mockImplementation(() => createUser('current_user')); + apiClient.getUser = jest + .fn() + .mockImplementation(async (username: string) => createUser(username)); apiClient.getRoles = jest.fn().mockImplementation(() => { return Promise.resolve([ @@ -63,6 +67,14 @@ const buildClient = () => { return apiClient; }; +function buildSecuritySetup() { + const securitySetupMock = securityMock.createSetup(); + securitySetupMock.authc.getCurrentUser.mockResolvedValue( + mockAuthenticatedUser(createUser('current_user')) + ); + return securitySetupMock; +} + function expectSaveButton(wrapper: ReactWrapper) { expect(wrapper.find('EuiButton[data-test-subj="userFormSaveButton"]')).toHaveLength(1); } @@ -74,10 +86,12 @@ function expectMissingSaveButton(wrapper: ReactWrapper) { describe('EditUserPage', () => { it('allows reserved users to be viewed', async () => { const apiClient = buildClient(); + const securitySetup = buildSecuritySetup(); const wrapper = mountWithIntl( path} intl={null as any} /> @@ -86,17 +100,19 @@ describe('EditUserPage', () => { await waitForRender(wrapper); expect(apiClient.getUser).toBeCalledTimes(1); - expect(apiClient.getCurrentUser).toBeCalledTimes(1); + expect(securitySetup.authc.getCurrentUser).toBeCalledTimes(1); expectMissingSaveButton(wrapper); }); it('allows new users to be created', async () => { const apiClient = buildClient(); + const securitySetup = buildSecuritySetup(); const wrapper = mountWithIntl( path} intl={null as any} /> @@ -105,17 +121,19 @@ describe('EditUserPage', () => { await waitForRender(wrapper); expect(apiClient.getUser).toBeCalledTimes(0); - expect(apiClient.getCurrentUser).toBeCalledTimes(0); + expect(securitySetup.authc.getCurrentUser).toBeCalledTimes(0); expectSaveButton(wrapper); }); it('allows existing users to be edited', async () => { const apiClient = buildClient(); + const securitySetup = buildSecuritySetup(); const wrapper = mountWithIntl( path} intl={null as any} /> @@ -124,16 +142,15 @@ describe('EditUserPage', () => { await waitForRender(wrapper); expect(apiClient.getUser).toBeCalledTimes(1); - expect(apiClient.getCurrentUser).toBeCalledTimes(1); + expect(securitySetup.authc.getCurrentUser).toBeCalledTimes(1); expectSaveButton(wrapper); }); }); async function waitForRender(wrapper: ReactWrapper) { - await Promise.resolve(); - await Promise.resolve(); - await Promise.resolve(); - await Promise.resolve(); - wrapper.update(); + await act(async () => { + await nextTick(); + wrapper.update(); + }); } diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_user/components/edit_user_page.tsx b/x-pack/legacy/plugins/security/public/views/management/edit_user/components/edit_user_page.tsx index 91f5f048adc6d..bbffe07485f8d 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_user/components/edit_user_page.tsx +++ b/x-pack/legacy/plugins/security/public/views/management/edit_user/components/edit_user_page.tsx @@ -28,6 +28,7 @@ import { } from '@elastic/eui'; import { toastNotifications } from 'ui/notify'; import { FormattedMessage, injectI18n, InjectedIntl } from '@kbn/i18n/react'; +import { SecurityPluginSetup } from '../../../../../../../../plugins/security/public'; import { UserValidator, UserValidationResult } from '../../../../lib/validate_user'; import { User, EditUser, Role } from '../../../../../common/model'; import { USERS_PATH } from '../../../../views/management/management_urls'; @@ -40,6 +41,7 @@ interface Props { intl: InjectedIntl; changeUrl: (path: string) => void; apiClient: UserAPIClient; + securitySetup: SecurityPluginSetup; } interface State { @@ -82,7 +84,7 @@ class EditUserPageUI extends Component { } public async componentDidMount() { - const { username, apiClient } = this.props; + const { username, apiClient, securitySetup } = this.props; let { user, currentUser } = this.state; if (username) { try { @@ -91,7 +93,7 @@ class EditUserPageUI extends Component { password: '', confirmPassword: '', }; - currentUser = await apiClient.getCurrentUser(); + currentUser = await securitySetup.authc.getCurrentUser(); } catch (err) { toastNotifications.addDanger({ title: this.props.intl.formatMessage({ diff --git a/x-pack/legacy/plugins/security/public/views/management/edit_user/edit_user.js b/x-pack/legacy/plugins/security/public/views/management/edit_user/edit_user.js index bd9d6f2b1ca35..ab218022c6ee6 100644 --- a/x-pack/legacy/plugins/security/public/views/management/edit_user/edit_user.js +++ b/x-pack/legacy/plugins/security/public/views/management/edit_user/edit_user.js @@ -7,7 +7,6 @@ import routes from 'ui/routes'; import template from 'plugins/security/views/management/edit_user/edit_user.html'; import 'angular-resource'; import 'ui/angular_ui_select'; -import 'plugins/security/services/shield_user'; import 'plugins/security/services/shield_role'; import { EDIT_USERS_PATH } from '../management_urls'; import { EditUserPage } from './components'; @@ -15,12 +14,18 @@ import { UserAPIClient } from '../../../lib/api'; import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import { I18nContext } from 'ui/i18n'; +import { npSetup } from 'ui/new_platform'; import { getEditUserBreadcrumbs, getCreateUserBreadcrumbs } from '../breadcrumbs'; const renderReact = (elem, changeUrl, username) => { render( - + , elem ); diff --git a/x-pack/legacy/plugins/security/public/views/management/management.js b/x-pack/legacy/plugins/security/public/views/management/management.js index db2175e91c5de..f0369f232aeba 100644 --- a/x-pack/legacy/plugins/security/public/views/management/management.js +++ b/x-pack/legacy/plugins/security/public/views/management/management.js @@ -11,12 +11,14 @@ import 'plugins/security/views/management/roles_grid/roles'; import 'plugins/security/views/management/api_keys_grid/api_keys'; import 'plugins/security/views/management/edit_user/edit_user'; import 'plugins/security/views/management/edit_role/index'; +import 'plugins/security/views/management/role_mappings/role_mappings_grid'; +import 'plugins/security/views/management/role_mappings/edit_role_mapping'; import routes from 'ui/routes'; import { xpackInfo } from 'plugins/xpack_main/services/xpack_info'; -import '../../services/shield_user'; -import { ROLES_PATH, USERS_PATH, API_KEYS_PATH } from './management_urls'; +import { ROLES_PATH, USERS_PATH, API_KEYS_PATH, ROLE_MAPPINGS_PATH } from './management_urls'; import { management } from 'ui/management'; +import { npSetup } from 'ui/new_platform'; import { i18n } from '@kbn/i18n'; import { toastNotifications } from 'ui/notify'; @@ -36,13 +38,25 @@ routes }) .defaults(/\/management/, { resolve: { - securityManagementSection: function(ShieldUser) { + securityManagementSection: function() { const showSecurityLinks = xpackInfo.get('features.security.showLinks'); + const showRoleMappingsManagementLink = xpackInfo.get( + 'features.security.showRoleMappingsManagement' + ); function deregisterSecurity() { management.deregister('security'); } + function deregisterRoleMappingsManagement() { + if (management.hasItem('security')) { + const security = management.getSection('security'); + if (security.hasItem('roleMappings')) { + security.deregister('roleMappings'); + } + } + } + function ensureSecurityRegistered() { const registerSecurity = () => management.register('security', { @@ -88,17 +102,31 @@ routes url: `#${API_KEYS_PATH}`, }); } + + if (showRoleMappingsManagementLink && !security.hasItem('roleMappings')) { + security.register('roleMappings', { + name: 'securityRoleMappingLink', + order: 30, + display: i18n.translate('xpack.security.management.roleMappingsTitle', { + defaultMessage: 'Role Mappings', + }), + url: `#${ROLE_MAPPINGS_PATH}`, + }); + } } if (!showSecurityLinks) { deregisterSecurity(); } else { - // getCurrent will reject if there is no authenticated user, so we prevent them from seeing the security - // management screens - // - // $promise is used here because the result is an ngResource, not a promise itself - return ShieldUser.getCurrent() - .$promise.then(ensureSecurityRegistered) + if (!showRoleMappingsManagementLink) { + deregisterRoleMappingsManagement(); + } + + // getCurrentUser will reject if there is no authenticated user, so we prevent them from + // seeing the security management screens. + return npSetup.plugins.security.authc + .getCurrentUser() + .then(ensureSecurityRegistered) .catch(deregisterSecurity); } }, diff --git a/x-pack/legacy/plugins/security/public/views/management/management_urls.ts b/x-pack/legacy/plugins/security/public/views/management/management_urls.ts index ea0cba9f7f3a7..881740c0b2895 100644 --- a/x-pack/legacy/plugins/security/public/views/management/management_urls.ts +++ b/x-pack/legacy/plugins/security/public/views/management/management_urls.ts @@ -12,3 +12,13 @@ export const CLONE_ROLES_PATH = `${ROLES_PATH}/clone`; export const USERS_PATH = `${SECURITY_PATH}/users`; export const EDIT_USERS_PATH = `${USERS_PATH}/edit`; export const API_KEYS_PATH = `${SECURITY_PATH}/api_keys`; +export const ROLE_MAPPINGS_PATH = `${SECURITY_PATH}/role_mappings`; +export const CREATE_ROLE_MAPPING_PATH = `${ROLE_MAPPINGS_PATH}/edit`; + +export const getEditRoleHref = (roleName: string) => + `#${EDIT_ROLES_PATH}/${encodeURIComponent(roleName)}`; + +export const getCreateRoleMappingHref = () => `#${CREATE_ROLE_MAPPING_PATH}`; + +export const getEditRoleMappingHref = (roleMappingName: string) => + `#${CREATE_ROLE_MAPPING_PATH}/${encodeURIComponent(roleMappingName)}`; diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/components/delete_provider/delete_provider.test.tsx b/x-pack/legacy/plugins/security/public/views/management/role_mappings/components/delete_provider/delete_provider.test.tsx new file mode 100644 index 0000000000000..b826d68053e27 --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/management/role_mappings/components/delete_provider/delete_provider.test.tsx @@ -0,0 +1,301 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { mountWithIntl, nextTick } from 'test_utils/enzyme_helpers'; +import { DeleteProvider } from '.'; +import { RoleMappingsAPI } from '../../../../../lib/role_mappings_api'; +import { RoleMapping } from '../../../../../../common/model'; +import { EuiConfirmModal } from '@elastic/eui'; +import { findTestSubject } from 'test_utils/find_test_subject'; +import { act } from '@testing-library/react'; +import { toastNotifications } from 'ui/notify'; + +jest.mock('ui/notify', () => { + return { + toastNotifications: { + addError: jest.fn(), + addSuccess: jest.fn(), + addDanger: jest.fn(), + }, + }; +}); + +describe('DeleteProvider', () => { + beforeEach(() => { + jest.resetAllMocks(); + }); + + it('allows a single role mapping to be deleted', async () => { + const props = { + roleMappingsAPI: ({ + deleteRoleMappings: jest.fn().mockReturnValue( + Promise.resolve([ + { + name: 'delete-me', + success: true, + }, + ]) + ), + } as unknown) as RoleMappingsAPI, + }; + + const roleMappingsToDelete = [ + { + name: 'delete-me', + }, + ] as RoleMapping[]; + + const onSuccess = jest.fn(); + + const wrapper = mountWithIntl( + + {onDelete => ( + + )} + + ); + + await act(async () => { + wrapper.find('#invoker').simulate('click'); + await nextTick(); + wrapper.update(); + }); + + const { title, confirmButtonText } = wrapper.find(EuiConfirmModal).props(); + expect(title).toMatchInlineSnapshot(`"Delete role mapping 'delete-me'?"`); + expect(confirmButtonText).toMatchInlineSnapshot(`"Delete role mapping"`); + + await act(async () => { + findTestSubject(wrapper, 'confirmModalConfirmButton').simulate('click'); + await nextTick(); + wrapper.update(); + }); + + expect(props.roleMappingsAPI.deleteRoleMappings).toHaveBeenCalledWith(['delete-me']); + + const notifications = toastNotifications as jest.Mocked; + expect(notifications.addError).toHaveBeenCalledTimes(0); + expect(notifications.addDanger).toHaveBeenCalledTimes(0); + expect(notifications.addSuccess).toHaveBeenCalledTimes(1); + expect(notifications.addSuccess.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Object { + "data-test-subj": "deletedRoleMappingSuccessToast", + "title": "Deleted role mapping 'delete-me'", + }, + ] + `); + }); + + it('allows multiple role mappings to be deleted', async () => { + const props = { + roleMappingsAPI: ({ + deleteRoleMappings: jest.fn().mockReturnValue( + Promise.resolve([ + { + name: 'delete-me', + success: true, + }, + { + name: 'delete-me-too', + success: true, + }, + ]) + ), + } as unknown) as RoleMappingsAPI, + }; + + const roleMappingsToDelete = [ + { + name: 'delete-me', + }, + { + name: 'delete-me-too', + }, + ] as RoleMapping[]; + + const onSuccess = jest.fn(); + + const wrapper = mountWithIntl( + + {onDelete => ( + + )} + + ); + + await act(async () => { + wrapper.find('#invoker').simulate('click'); + await nextTick(); + wrapper.update(); + }); + + const { title, confirmButtonText } = wrapper.find(EuiConfirmModal).props(); + expect(title).toMatchInlineSnapshot(`"Delete 2 role mappings?"`); + expect(confirmButtonText).toMatchInlineSnapshot(`"Delete role mappings"`); + + await act(async () => { + findTestSubject(wrapper, 'confirmModalConfirmButton').simulate('click'); + await nextTick(); + wrapper.update(); + }); + + expect(props.roleMappingsAPI.deleteRoleMappings).toHaveBeenCalledWith([ + 'delete-me', + 'delete-me-too', + ]); + const notifications = toastNotifications as jest.Mocked; + expect(notifications.addError).toHaveBeenCalledTimes(0); + expect(notifications.addDanger).toHaveBeenCalledTimes(0); + expect(notifications.addSuccess).toHaveBeenCalledTimes(1); + expect(notifications.addSuccess.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Object { + "data-test-subj": "deletedRoleMappingSuccessToast", + "title": "Deleted 2 role mappings", + }, + ] + `); + }); + + it('handles mixed success/failure conditions', async () => { + const props = { + roleMappingsAPI: ({ + deleteRoleMappings: jest.fn().mockReturnValue( + Promise.resolve([ + { + name: 'delete-me', + success: true, + }, + { + name: 'i-wont-work', + success: false, + error: new Error('something went wrong. sad.'), + }, + ]) + ), + } as unknown) as RoleMappingsAPI, + }; + + const roleMappingsToDelete = [ + { + name: 'delete-me', + }, + { + name: 'i-wont-work', + }, + ] as RoleMapping[]; + + const onSuccess = jest.fn(); + + const wrapper = mountWithIntl( + + {onDelete => ( + + )} + + ); + + await act(async () => { + wrapper.find('#invoker').simulate('click'); + await nextTick(); + wrapper.update(); + }); + + await act(async () => { + findTestSubject(wrapper, 'confirmModalConfirmButton').simulate('click'); + await nextTick(); + wrapper.update(); + }); + + expect(props.roleMappingsAPI.deleteRoleMappings).toHaveBeenCalledWith([ + 'delete-me', + 'i-wont-work', + ]); + + const notifications = toastNotifications as jest.Mocked; + expect(notifications.addError).toHaveBeenCalledTimes(0); + expect(notifications.addSuccess).toHaveBeenCalledTimes(1); + expect(notifications.addSuccess.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + Object { + "data-test-subj": "deletedRoleMappingSuccessToast", + "title": "Deleted role mapping 'delete-me'", + }, + ] + `); + + expect(notifications.addDanger).toHaveBeenCalledTimes(1); + expect(notifications.addDanger.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + "Error deleting role mapping 'i-wont-work'", + ] + `); + }); + + it('handles errors calling the API', async () => { + const props = { + roleMappingsAPI: ({ + deleteRoleMappings: jest.fn().mockImplementation(() => { + throw new Error('AHHHHH'); + }), + } as unknown) as RoleMappingsAPI, + }; + + const roleMappingsToDelete = [ + { + name: 'delete-me', + }, + ] as RoleMapping[]; + + const onSuccess = jest.fn(); + + const wrapper = mountWithIntl( + + {onDelete => ( + + )} + + ); + + await act(async () => { + wrapper.find('#invoker').simulate('click'); + await nextTick(); + wrapper.update(); + }); + + await act(async () => { + findTestSubject(wrapper, 'confirmModalConfirmButton').simulate('click'); + await nextTick(); + wrapper.update(); + }); + + expect(props.roleMappingsAPI.deleteRoleMappings).toHaveBeenCalledWith(['delete-me']); + + const notifications = toastNotifications as jest.Mocked; + expect(notifications.addDanger).toHaveBeenCalledTimes(0); + expect(notifications.addSuccess).toHaveBeenCalledTimes(0); + + expect(notifications.addError).toHaveBeenCalledTimes(1); + expect(notifications.addError.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + [Error: AHHHHH], + Object { + "title": "Error deleting role mappings", + }, + ] + `); + }); +}); diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/components/delete_provider/delete_provider.tsx b/x-pack/legacy/plugins/security/public/views/management/role_mappings/components/delete_provider/delete_provider.tsx new file mode 100644 index 0000000000000..2072cedeab462 --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/management/role_mappings/components/delete_provider/delete_provider.tsx @@ -0,0 +1,198 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { Fragment, useRef, useState, ReactElement } from 'react'; +import { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui'; +import { toastNotifications } from 'ui/notify'; +import { i18n } from '@kbn/i18n'; +import { RoleMapping } from '../../../../../../common/model'; +import { RoleMappingsAPI } from '../../../../../lib/role_mappings_api'; + +interface Props { + roleMappingsAPI: RoleMappingsAPI; + children: (deleteMappings: DeleteRoleMappings) => ReactElement; +} + +export type DeleteRoleMappings = ( + roleMappings: RoleMapping[], + onSuccess?: OnSuccessCallback +) => void; + +type OnSuccessCallback = (deletedRoleMappings: string[]) => void; + +export const DeleteProvider: React.FunctionComponent = ({ roleMappingsAPI, children }) => { + const [roleMappings, setRoleMappings] = useState([]); + const [isModalOpen, setIsModalOpen] = useState(false); + const [isDeleteInProgress, setIsDeleteInProgress] = useState(false); + + const onSuccessCallback = useRef(null); + + const deleteRoleMappingsPrompt: DeleteRoleMappings = ( + roleMappingsToDelete, + onSuccess = () => undefined + ) => { + if (!roleMappingsToDelete || !roleMappingsToDelete.length) { + throw new Error('No Role Mappings specified for delete'); + } + setIsModalOpen(true); + setRoleMappings(roleMappingsToDelete); + onSuccessCallback.current = onSuccess; + }; + + const closeModal = () => { + setIsModalOpen(false); + setRoleMappings([]); + }; + + const deleteRoleMappings = async () => { + let result; + + setIsDeleteInProgress(true); + + try { + result = await roleMappingsAPI.deleteRoleMappings(roleMappings.map(rm => rm.name)); + } catch (e) { + toastNotifications.addError(e, { + title: i18n.translate( + 'xpack.security.management.roleMappings.deleteRoleMapping.unknownError', + { + defaultMessage: 'Error deleting role mappings', + } + ), + }); + setIsDeleteInProgress(false); + return; + } + + setIsDeleteInProgress(false); + + closeModal(); + + const successfulDeletes = result.filter(res => res.success); + const erroredDeletes = result.filter(res => !res.success); + + // Surface success notifications + if (successfulDeletes.length > 0) { + const hasMultipleSuccesses = successfulDeletes.length > 1; + const successMessage = hasMultipleSuccesses + ? i18n.translate( + 'xpack.security.management.roleMappings.deleteRoleMapping.successMultipleNotificationTitle', + { + defaultMessage: 'Deleted {count} role mappings', + values: { count: successfulDeletes.length }, + } + ) + : i18n.translate( + 'xpack.security.management.roleMappings.deleteRoleMapping.successSingleNotificationTitle', + { + defaultMessage: "Deleted role mapping '{name}'", + values: { name: successfulDeletes[0].name }, + } + ); + toastNotifications.addSuccess({ + title: successMessage, + 'data-test-subj': 'deletedRoleMappingSuccessToast', + }); + if (onSuccessCallback.current) { + onSuccessCallback.current(successfulDeletes.map(({ name }) => name)); + } + } + + // Surface error notifications + if (erroredDeletes.length > 0) { + const hasMultipleErrors = erroredDeletes.length > 1; + const errorMessage = hasMultipleErrors + ? i18n.translate( + 'xpack.security.management.roleMappings.deleteRoleMapping.errorMultipleNotificationTitle', + { + defaultMessage: 'Error deleting {count} role mappings', + values: { + count: erroredDeletes.length, + }, + } + ) + : i18n.translate( + 'xpack.security.management.roleMappings.deleteRoleMapping.errorSingleNotificationTitle', + { + defaultMessage: "Error deleting role mapping '{name}'", + values: { name: erroredDeletes[0].name }, + } + ); + toastNotifications.addDanger(errorMessage); + } + }; + + const renderModal = () => { + if (!isModalOpen) { + return null; + } + + const isSingle = roleMappings.length === 1; + + return ( + + + {!isSingle ? ( + +

+ {i18n.translate( + 'xpack.security.management.roleMappings.deleteRoleMapping.confirmModal.deleteMultipleListDescription', + { defaultMessage: 'You are about to delete these role mappings:' } + )} +

+
    + {roleMappings.map(({ name }) => ( +
  • {name}
  • + ))} +
+
+ ) : null} +
+
+ ); + }; + + return ( + + {children(deleteRoleMappingsPrompt)} + {renderModal()} + + ); +}; diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/components/delete_provider/index.ts b/x-pack/legacy/plugins/security/public/views/management/role_mappings/components/delete_provider/index.ts new file mode 100644 index 0000000000000..7e8b5a99c3bf5 --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/management/role_mappings/components/delete_provider/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { DeleteProvider } from './delete_provider'; diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/components/index.ts b/x-pack/legacy/plugins/security/public/views/management/role_mappings/components/index.ts new file mode 100644 index 0000000000000..315c1f7ec2baf --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/management/role_mappings/components/index.ts @@ -0,0 +1,10 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export * from './delete_provider'; +export * from './no_compatible_realms'; +export * from './permission_denied'; +export * from './section_loading'; diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/components/no_compatible_realms/index.ts b/x-pack/legacy/plugins/security/public/views/management/role_mappings/components/no_compatible_realms/index.ts new file mode 100644 index 0000000000000..fb2e5b40c1941 --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/management/role_mappings/components/no_compatible_realms/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { NoCompatibleRealms } from './no_compatible_realms'; diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/components/no_compatible_realms/no_compatible_realms.tsx b/x-pack/legacy/plugins/security/public/views/management/role_mappings/components/no_compatible_realms/no_compatible_realms.tsx new file mode 100644 index 0000000000000..969832b3ecbae --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/management/role_mappings/components/no_compatible_realms/no_compatible_realms.tsx @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { EuiCallOut, EuiLink } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { documentationLinks } from '../../services/documentation_links'; + +export const NoCompatibleRealms: React.FunctionComponent = () => ( + + } + color="warning" + iconType="alert" + > + + + + ), + }} + /> + +); diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/components/permission_denied/index.ts b/x-pack/legacy/plugins/security/public/views/management/role_mappings/components/permission_denied/index.ts new file mode 100644 index 0000000000000..8b0bc67f3f777 --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/management/role_mappings/components/permission_denied/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { PermissionDenied } from './permission_denied'; diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/components/permission_denied/permission_denied.tsx b/x-pack/legacy/plugins/security/public/views/management/role_mappings/components/permission_denied/permission_denied.tsx new file mode 100644 index 0000000000000..1a32645eaedb9 --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/management/role_mappings/components/permission_denied/permission_denied.tsx @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { EuiEmptyPrompt, EuiFlexGroup, EuiPageContent } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import React from 'react'; + +export const PermissionDenied = () => ( + + + + + + } + body={ +

+ +

+ } + /> +
+
+); diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/components/section_loading/index.ts b/x-pack/legacy/plugins/security/public/views/management/role_mappings/components/section_loading/index.ts new file mode 100644 index 0000000000000..f59aa7a22d7c2 --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/management/role_mappings/components/section_loading/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { SectionLoading } from './section_loading'; diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/components/section_loading/section_loading.test.tsx b/x-pack/legacy/plugins/security/public/views/management/role_mappings/components/section_loading/section_loading.test.tsx new file mode 100644 index 0000000000000..300f6ca0e1f72 --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/management/role_mappings/components/section_loading/section_loading.test.tsx @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { shallowWithIntl } from 'test_utils/enzyme_helpers'; +import { SectionLoading } from '.'; + +describe('SectionLoading', () => { + it('renders the default loading message', () => { + const wrapper = shallowWithIntl(); + expect(wrapper.props().body).toMatchInlineSnapshot(` + + + + `); + }); + + it('renders the custom message when provided', () => { + const custom =
hold your horses
; + const wrapper = shallowWithIntl({custom}); + expect(wrapper.props().body).toMatchInlineSnapshot(` + +
+ hold your horses +
+
+ `); + }); +}); diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/components/section_loading/section_loading.tsx b/x-pack/legacy/plugins/security/public/views/management/role_mappings/components/section_loading/section_loading.tsx new file mode 100644 index 0000000000000..8ae87127ed3b2 --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/management/role_mappings/components/section_loading/section_loading.tsx @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { EuiEmptyPrompt, EuiLoadingSpinner, EuiText } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; + +interface Props { + children?: React.ReactChild; +} +export const SectionLoading = (props: Props) => { + return ( + } + body={ + + {props.children || ( + + )} + + } + data-test-subj="sectionLoading" + /> + ); +}; diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/_index.scss b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/_index.scss new file mode 100644 index 0000000000000..80e08ebcf1226 --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/_index.scss @@ -0,0 +1 @@ +@import './components/rule_editor_panel/index'; \ No newline at end of file diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/edit_role_mapping_page.test.tsx b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/edit_role_mapping_page.test.tsx new file mode 100644 index 0000000000000..375a8d9f374a8 --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/edit_role_mapping_page.test.tsx @@ -0,0 +1,341 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { mountWithIntl, nextTick } from 'test_utils/enzyme_helpers'; +import { findTestSubject } from 'test_utils/find_test_subject'; + +// brace/ace uses the Worker class, which is not currently provided by JSDOM. +// This is not required for the tests to pass, but it rather suppresses lengthy +// warnings in the console which adds unnecessary noise to the test output. +import 'test_utils/stub_web_worker'; + +import { RoleMappingsAPI } from '../../../../../lib/role_mappings_api'; +import { EditRoleMappingPage } from '.'; +import { NoCompatibleRealms, SectionLoading, PermissionDenied } from '../../components'; +import { VisualRuleEditor } from './rule_editor_panel/visual_rule_editor'; +import { JSONRuleEditor } from './rule_editor_panel/json_rule_editor'; +import { EuiComboBox } from '@elastic/eui'; + +jest.mock('../../../../../lib/roles_api', () => { + return { + RolesApi: { + getRoles: () => Promise.resolve([{ name: 'foo_role' }, { name: 'bar role' }]), + }, + }; +}); + +describe('EditRoleMappingPage', () => { + it('allows a role mapping to be created', async () => { + const roleMappingsAPI = ({ + saveRoleMapping: jest.fn().mockResolvedValue(null), + checkRoleMappingFeatures: jest.fn().mockResolvedValue({ + canManageRoleMappings: true, + hasCompatibleRealms: true, + canUseInlineScripts: true, + canUseStoredScripts: true, + }), + } as unknown) as RoleMappingsAPI; + + const wrapper = mountWithIntl(); + + await nextTick(); + wrapper.update(); + + findTestSubject(wrapper, 'roleMappingFormNameInput').simulate('change', { + target: { value: 'my-role-mapping' }, + }); + + (wrapper + .find(EuiComboBox) + .filter('[data-test-subj="roleMappingFormRoleComboBox"]') + .props() as any).onChange([{ label: 'foo_role' }]); + + findTestSubject(wrapper, 'roleMappingsAddRuleButton').simulate('click'); + + findTestSubject(wrapper, 'saveRoleMappingButton').simulate('click'); + + expect(roleMappingsAPI.saveRoleMapping).toHaveBeenCalledWith({ + name: 'my-role-mapping', + enabled: true, + roles: ['foo_role'], + role_templates: [], + rules: { + all: [{ field: { username: '*' } }], + }, + metadata: {}, + }); + }); + + it('allows a role mapping to be updated', async () => { + const roleMappingsAPI = ({ + saveRoleMapping: jest.fn().mockResolvedValue(null), + getRoleMapping: jest.fn().mockResolvedValue({ + name: 'foo', + role_templates: [ + { + template: { id: 'foo' }, + }, + ], + enabled: true, + rules: { + any: [{ field: { 'metadata.someCustomOption': [false, true, 'asdf'] } }], + }, + metadata: { + foo: 'bar', + bar: 'baz', + }, + }), + checkRoleMappingFeatures: jest.fn().mockResolvedValue({ + canManageRoleMappings: true, + hasCompatibleRealms: true, + canUseInlineScripts: true, + canUseStoredScripts: true, + }), + } as unknown) as RoleMappingsAPI; + + const wrapper = mountWithIntl( + + ); + + await nextTick(); + wrapper.update(); + + findTestSubject(wrapper, 'switchToRolesButton').simulate('click'); + + (wrapper + .find(EuiComboBox) + .filter('[data-test-subj="roleMappingFormRoleComboBox"]') + .props() as any).onChange([{ label: 'foo_role' }]); + + findTestSubject(wrapper, 'roleMappingsAddRuleButton').simulate('click'); + wrapper.find('button[id="addRuleOption"]').simulate('click'); + + findTestSubject(wrapper, 'saveRoleMappingButton').simulate('click'); + + expect(roleMappingsAPI.saveRoleMapping).toHaveBeenCalledWith({ + name: 'foo', + enabled: true, + roles: ['foo_role'], + role_templates: [], + rules: { + any: [ + { field: { 'metadata.someCustomOption': [false, true, 'asdf'] } }, + { field: { username: '*' } }, + ], + }, + metadata: { + foo: 'bar', + bar: 'baz', + }, + }); + }); + + it('renders a permission denied message when unauthorized to manage role mappings', async () => { + const roleMappingsAPI = ({ + checkRoleMappingFeatures: jest.fn().mockResolvedValue({ + canManageRoleMappings: false, + hasCompatibleRealms: true, + }), + } as unknown) as RoleMappingsAPI; + + const wrapper = mountWithIntl(); + expect(wrapper.find(SectionLoading)).toHaveLength(1); + expect(wrapper.find(PermissionDenied)).toHaveLength(0); + + await nextTick(); + wrapper.update(); + + expect(wrapper.find(SectionLoading)).toHaveLength(0); + expect(wrapper.find(NoCompatibleRealms)).toHaveLength(0); + expect(wrapper.find(PermissionDenied)).toHaveLength(1); + }); + + it('renders a warning when there are no compatible realms enabled', async () => { + const roleMappingsAPI = ({ + checkRoleMappingFeatures: jest.fn().mockResolvedValue({ + canManageRoleMappings: true, + hasCompatibleRealms: false, + }), + } as unknown) as RoleMappingsAPI; + + const wrapper = mountWithIntl(); + expect(wrapper.find(SectionLoading)).toHaveLength(1); + expect(wrapper.find(NoCompatibleRealms)).toHaveLength(0); + + await nextTick(); + wrapper.update(); + + expect(wrapper.find(SectionLoading)).toHaveLength(0); + expect(wrapper.find(NoCompatibleRealms)).toHaveLength(1); + }); + + it('renders a warning when editing a mapping with a stored role template, when stored scripts are disabled', async () => { + const roleMappingsAPI = ({ + getRoleMapping: jest.fn().mockResolvedValue({ + name: 'foo', + role_templates: [ + { + template: { id: 'foo' }, + }, + ], + enabled: true, + rules: { + field: { username: '*' }, + }, + }), + checkRoleMappingFeatures: jest.fn().mockResolvedValue({ + canManageRoleMappings: true, + hasCompatibleRealms: true, + canUseInlineScripts: true, + canUseStoredScripts: false, + }), + } as unknown) as RoleMappingsAPI; + + const wrapper = mountWithIntl( + + ); + + expect(findTestSubject(wrapper, 'roleMappingInlineScriptsDisabled')).toHaveLength(0); + expect(findTestSubject(wrapper, 'roleMappingStoredScriptsDisabled')).toHaveLength(0); + + await nextTick(); + wrapper.update(); + + expect(findTestSubject(wrapper, 'roleMappingInlineScriptsDisabled')).toHaveLength(0); + expect(findTestSubject(wrapper, 'roleMappingStoredScriptsDisabled')).toHaveLength(1); + }); + + it('renders a warning when editing a mapping with an inline role template, when inline scripts are disabled', async () => { + const roleMappingsAPI = ({ + getRoleMapping: jest.fn().mockResolvedValue({ + name: 'foo', + role_templates: [ + { + template: { source: 'foo' }, + }, + ], + enabled: true, + rules: { + field: { username: '*' }, + }, + }), + checkRoleMappingFeatures: jest.fn().mockResolvedValue({ + canManageRoleMappings: true, + hasCompatibleRealms: true, + canUseInlineScripts: false, + canUseStoredScripts: true, + }), + } as unknown) as RoleMappingsAPI; + + const wrapper = mountWithIntl( + + ); + + expect(findTestSubject(wrapper, 'roleMappingInlineScriptsDisabled')).toHaveLength(0); + expect(findTestSubject(wrapper, 'roleMappingStoredScriptsDisabled')).toHaveLength(0); + + await nextTick(); + wrapper.update(); + + expect(findTestSubject(wrapper, 'roleMappingInlineScriptsDisabled')).toHaveLength(1); + expect(findTestSubject(wrapper, 'roleMappingStoredScriptsDisabled')).toHaveLength(0); + }); + + it('renders the visual editor by default for simple rule sets', async () => { + const roleMappingsAPI = ({ + getRoleMapping: jest.fn().mockResolvedValue({ + name: 'foo', + roles: ['superuser'], + enabled: true, + rules: { + all: [ + { + field: { + username: '*', + }, + }, + { + field: { + dn: null, + }, + }, + { + field: { + realm: ['ldap', 'pki', null, 12], + }, + }, + ], + }, + }), + checkRoleMappingFeatures: jest.fn().mockResolvedValue({ + canManageRoleMappings: true, + hasCompatibleRealms: true, + canUseInlineScripts: true, + canUseStoredScripts: true, + }), + } as unknown) as RoleMappingsAPI; + + const wrapper = mountWithIntl( + + ); + + await nextTick(); + wrapper.update(); + + expect(wrapper.find(VisualRuleEditor)).toHaveLength(1); + expect(wrapper.find(JSONRuleEditor)).toHaveLength(0); + }); + + it('renders the JSON editor by default for complex rule sets', async () => { + const createRule = (depth: number): Record => { + if (depth > 0) { + const rule = { + all: [ + { + field: { + username: '*', + }, + }, + ], + } as Record; + + const subRule = createRule(depth - 1); + if (subRule) { + rule.all.push(subRule); + } + + return rule; + } + return null as any; + }; + + const roleMappingsAPI = ({ + getRoleMapping: jest.fn().mockResolvedValue({ + name: 'foo', + roles: ['superuser'], + enabled: true, + rules: createRule(10), + }), + checkRoleMappingFeatures: jest.fn().mockResolvedValue({ + canManageRoleMappings: true, + hasCompatibleRealms: true, + canUseInlineScripts: true, + canUseStoredScripts: true, + }), + } as unknown) as RoleMappingsAPI; + + const wrapper = mountWithIntl( + + ); + + await nextTick(); + wrapper.update(); + + expect(wrapper.find(VisualRuleEditor)).toHaveLength(0); + expect(wrapper.find(JSONRuleEditor)).toHaveLength(1); + }); +}); diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/edit_role_mapping_page.tsx b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/edit_role_mapping_page.tsx new file mode 100644 index 0000000000000..b8a75a4ad9fdf --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/edit_role_mapping_page.tsx @@ -0,0 +1,332 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { Component, Fragment } from 'react'; +import { + EuiForm, + EuiPageContent, + EuiSpacer, + EuiText, + EuiTitle, + EuiFlexGroup, + EuiFlexItem, + EuiButtonEmpty, + EuiButton, + EuiLink, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { toastNotifications } from 'ui/notify'; +import { RoleMapping } from '../../../../../../common/model'; +import { RoleMappingsAPI } from '../../../../../lib/role_mappings_api'; +import { RuleEditorPanel } from './rule_editor_panel'; +import { + NoCompatibleRealms, + PermissionDenied, + DeleteProvider, + SectionLoading, +} from '../../components'; +import { ROLE_MAPPINGS_PATH } from '../../../management_urls'; +import { validateRoleMappingForSave } from '../services/role_mapping_validation'; +import { MappingInfoPanel } from './mapping_info_panel'; +import { documentationLinks } from '../../services/documentation_links'; + +interface State { + loadState: 'loading' | 'permissionDenied' | 'ready' | 'saveInProgress'; + roleMapping: RoleMapping | null; + hasCompatibleRealms: boolean; + canUseStoredScripts: boolean; + canUseInlineScripts: boolean; + formError: { + isInvalid: boolean; + error?: string; + }; + validateForm: boolean; + rulesValid: boolean; +} + +interface Props { + name?: string; + roleMappingsAPI: RoleMappingsAPI; +} + +export class EditRoleMappingPage extends Component { + constructor(props: any) { + super(props); + this.state = { + loadState: 'loading', + roleMapping: null, + hasCompatibleRealms: true, + canUseStoredScripts: true, + canUseInlineScripts: true, + rulesValid: true, + validateForm: false, + formError: { + isInvalid: false, + }, + }; + } + + public componentDidMount() { + this.loadAppData(); + } + + public render() { + const { loadState } = this.state; + + if (loadState === 'permissionDenied') { + return ; + } + + if (loadState === 'loading') { + return ( + + + + ); + } + + return ( +
+ + {this.getFormTitle()} + + this.setState({ roleMapping })} + mode={this.editingExistingRoleMapping() ? 'edit' : 'create'} + validateForm={this.state.validateForm} + canUseInlineScripts={this.state.canUseInlineScripts} + canUseStoredScripts={this.state.canUseStoredScripts} + /> + + + this.setState({ + roleMapping: { + ...this.state.roleMapping!, + rules, + }, + }) + } + /> + + {this.getFormButtons()} + +
+ ); + } + + private getFormTitle = () => { + return ( + + +

+ {this.editingExistingRoleMapping() ? ( + + ) : ( + + )} +

+
+ +

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

+
+ {!this.state.hasCompatibleRealms && ( + <> + + + + )} +
+ ); + }; + + private getFormButtons = () => { + return ( + + + + + + + + + + + + + {this.editingExistingRoleMapping() && ( + + + {deleteRoleMappingsPrompt => { + return ( + + deleteRoleMappingsPrompt([this.state.roleMapping!], () => + this.backToRoleMappingsList() + ) + } + color="danger" + > + + + ); + }} + + + )} + + ); + }; + + private onRuleValidityChange = (rulesValid: boolean) => { + this.setState({ + rulesValid, + }); + }; + + private saveRoleMapping = () => { + if (!this.state.roleMapping) { + return; + } + + const { isInvalid } = validateRoleMappingForSave(this.state.roleMapping); + if (isInvalid) { + this.setState({ validateForm: true }); + return; + } + + const roleMappingName = this.state.roleMapping.name; + + this.setState({ + loadState: 'saveInProgress', + }); + + this.props.roleMappingsAPI + .saveRoleMapping(this.state.roleMapping) + .then(() => { + toastNotifications.addSuccess({ + title: i18n.translate('xpack.security.management.editRoleMapping.saveSuccess', { + defaultMessage: `Saved role mapping '{roleMappingName}'`, + values: { + roleMappingName, + }, + }), + 'data-test-subj': 'savedRoleMappingSuccessToast', + }); + this.backToRoleMappingsList(); + }) + .catch(e => { + toastNotifications.addError(e, { + title: i18n.translate('xpack.security.management.editRoleMapping.saveError', { + defaultMessage: `Error saving role mapping`, + }), + toastMessage: e?.body?.message, + }); + + this.setState({ + loadState: 'saveInProgress', + }); + }); + }; + + private editingExistingRoleMapping = () => typeof this.props.name === 'string'; + + private async loadAppData() { + try { + const [features, roleMapping] = await Promise.all([ + this.props.roleMappingsAPI.checkRoleMappingFeatures(), + this.editingExistingRoleMapping() + ? this.props.roleMappingsAPI.getRoleMapping(this.props.name!) + : Promise.resolve({ + name: '', + enabled: true, + metadata: {}, + role_templates: [], + roles: [], + rules: {}, + }), + ]); + + const { + canManageRoleMappings, + canUseStoredScripts, + canUseInlineScripts, + hasCompatibleRealms, + } = features; + + const loadState: State['loadState'] = canManageRoleMappings ? 'ready' : 'permissionDenied'; + + this.setState({ + loadState, + hasCompatibleRealms, + canUseStoredScripts, + canUseInlineScripts, + roleMapping, + }); + } catch (e) { + toastNotifications.addDanger({ + title: i18n.translate( + 'xpack.security.management.editRoleMapping.table.fetchingRoleMappingsErrorMessage', + { + defaultMessage: 'Error loading role mapping editor: {message}', + values: { message: e?.body?.message ?? '' }, + } + ), + 'data-test-subj': 'errorLoadingRoleMappingEditorToast', + }); + this.backToRoleMappingsList(); + } + } + + private backToRoleMappingsList = () => { + window.location.hash = ROLE_MAPPINGS_PATH; + }; +} diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/index.ts b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/index.ts new file mode 100644 index 0000000000000..6758033f92d98 --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { EditRoleMappingPage } from './edit_role_mapping_page'; diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/mapping_info_panel/index.ts b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/mapping_info_panel/index.ts new file mode 100644 index 0000000000000..5042499bf00ac --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/mapping_info_panel/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { MappingInfoPanel } from './mapping_info_panel'; diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/mapping_info_panel/mapping_info_panel.test.tsx b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/mapping_info_panel/mapping_info_panel.test.tsx new file mode 100644 index 0000000000000..d821b33ace6a7 --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/mapping_info_panel/mapping_info_panel.test.tsx @@ -0,0 +1,220 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { mountWithIntl } from 'test_utils/enzyme_helpers'; +import { MappingInfoPanel } from '.'; +import { RoleMapping } from '../../../../../../../common/model'; +import { findTestSubject } from 'test_utils/find_test_subject'; +import { RoleSelector } from '../role_selector'; +import { RoleTemplateEditor } from '../role_selector/role_template_editor'; + +jest.mock('../../../../../../lib/roles_api', () => { + return { + RolesApi: { + getRoles: () => Promise.resolve([{ name: 'foo_role' }, { name: 'bar role' }]), + }, + }; +}); + +describe('MappingInfoPanel', () => { + it('renders when creating a role mapping, default to the "roles" view', () => { + const props = { + roleMapping: { + name: 'my role mapping', + enabled: true, + roles: [], + role_templates: [], + rules: {}, + metadata: {}, + } as RoleMapping, + mode: 'create', + } as MappingInfoPanel['props']; + + const wrapper = mountWithIntl(); + + // Name input validation + const { value: nameInputValue, readOnly: nameInputReadOnly } = findTestSubject( + wrapper, + 'roleMappingFormNameInput' + ) + .find('input') + .props(); + + expect(nameInputValue).toEqual(props.roleMapping.name); + expect(nameInputReadOnly).toEqual(false); + + // Enabled switch validation + const { checked: enabledInputValue } = wrapper + .find('EuiSwitch[data-test-subj="roleMappingsEnabledSwitch"]') + .props(); + + expect(enabledInputValue).toEqual(props.roleMapping.enabled); + + // Verify "roles" mode + expect(wrapper.find(RoleSelector).props()).toMatchObject({ + mode: 'roles', + }); + }); + + it('renders the role templates view if templates are provided', () => { + const props = { + roleMapping: { + name: 'my role mapping', + enabled: true, + roles: [], + role_templates: [ + { + template: { + source: '', + }, + }, + ], + rules: {}, + metadata: {}, + } as RoleMapping, + mode: 'edit', + } as MappingInfoPanel['props']; + + const wrapper = mountWithIntl(); + + expect(wrapper.find(RoleSelector).props()).toMatchObject({ + mode: 'templates', + }); + }); + + it('renders a blank inline template by default when switching from roles to role templates', () => { + const props = { + roleMapping: { + name: 'my role mapping', + enabled: true, + roles: ['foo_role'], + role_templates: [], + rules: {}, + metadata: {}, + } as RoleMapping, + mode: 'create' as any, + onChange: jest.fn(), + canUseInlineScripts: true, + canUseStoredScripts: false, + validateForm: false, + }; + + const wrapper = mountWithIntl(); + + findTestSubject(wrapper, 'switchToRoleTemplatesButton').simulate('click'); + + expect(props.onChange).toHaveBeenCalledWith({ + name: 'my role mapping', + enabled: true, + roles: [], + role_templates: [ + { + template: { source: '' }, + }, + ], + rules: {}, + metadata: {}, + }); + + wrapper.setProps({ roleMapping: props.onChange.mock.calls[0][0] }); + + expect(wrapper.find(RoleTemplateEditor)).toHaveLength(1); + }); + + it('renders a blank stored template by default when switching from roles to role templates and inline scripts are disabled', () => { + const props = { + roleMapping: { + name: 'my role mapping', + enabled: true, + roles: ['foo_role'], + role_templates: [], + rules: {}, + metadata: {}, + } as RoleMapping, + mode: 'create' as any, + onChange: jest.fn(), + canUseInlineScripts: false, + canUseStoredScripts: true, + validateForm: false, + }; + + const wrapper = mountWithIntl(); + + findTestSubject(wrapper, 'switchToRoleTemplatesButton').simulate('click'); + + expect(props.onChange).toHaveBeenCalledWith({ + name: 'my role mapping', + enabled: true, + roles: [], + role_templates: [ + { + template: { id: '' }, + }, + ], + rules: {}, + metadata: {}, + }); + + wrapper.setProps({ roleMapping: props.onChange.mock.calls[0][0] }); + + expect(wrapper.find(RoleTemplateEditor)).toHaveLength(1); + }); + + it('does not create a blank role template if no script types are enabled', () => { + const props = { + roleMapping: { + name: 'my role mapping', + enabled: true, + roles: ['foo_role'], + role_templates: [], + rules: {}, + metadata: {}, + } as RoleMapping, + mode: 'create' as any, + onChange: jest.fn(), + canUseInlineScripts: false, + canUseStoredScripts: false, + validateForm: false, + }; + + const wrapper = mountWithIntl(); + + findTestSubject(wrapper, 'switchToRoleTemplatesButton').simulate('click'); + + wrapper.update(); + + expect(props.onChange).not.toHaveBeenCalled(); + expect(wrapper.find(RoleTemplateEditor)).toHaveLength(0); + }); + + it('renders the name input as readonly when editing an existing role mapping', () => { + const props = { + roleMapping: { + name: 'my role mapping', + enabled: true, + roles: [], + role_templates: [], + rules: {}, + metadata: {}, + } as RoleMapping, + mode: 'edit', + } as MappingInfoPanel['props']; + + const wrapper = mountWithIntl(); + + // Name input validation + const { value: nameInputValue, readOnly: nameInputReadOnly } = findTestSubject( + wrapper, + 'roleMappingFormNameInput' + ) + .find('input') + .props(); + + expect(nameInputValue).toEqual(props.roleMapping.name); + expect(nameInputReadOnly).toEqual(true); + }); +}); diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/mapping_info_panel/mapping_info_panel.tsx b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/mapping_info_panel/mapping_info_panel.tsx new file mode 100644 index 0000000000000..a02b4fc1709f0 --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/mapping_info_panel/mapping_info_panel.tsx @@ -0,0 +1,323 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { Component, ChangeEvent, Fragment } from 'react'; +import { + EuiPanel, + EuiTitle, + EuiText, + EuiSpacer, + EuiDescribedFormGroup, + EuiFormRow, + EuiFieldText, + EuiLink, + EuiIcon, + EuiSwitch, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { RoleMapping } from '../../../../../../../common/model'; +import { + validateRoleMappingName, + validateRoleMappingRoles, + validateRoleMappingRoleTemplates, +} from '../../services/role_mapping_validation'; +import { RoleSelector } from '../role_selector'; +import { documentationLinks } from '../../../services/documentation_links'; + +interface Props { + roleMapping: RoleMapping; + onChange: (roleMapping: RoleMapping) => void; + mode: 'create' | 'edit'; + validateForm: boolean; + canUseInlineScripts: boolean; + canUseStoredScripts: boolean; +} + +interface State { + rolesMode: 'roles' | 'templates'; +} + +export class MappingInfoPanel extends Component { + constructor(props: Props) { + super(props); + this.state = { + rolesMode: + props.roleMapping.role_templates && props.roleMapping.role_templates.length > 0 + ? 'templates' + : 'roles', + }; + } + public render() { + return ( + + +

+ +

+
+ + {this.getRoleMappingName()} + {this.getEnabledSwitch()} + {this.getRolesOrRoleTemplatesSelector()} +
+ ); + } + + private getRoleMappingName = () => { + return ( + + + + } + description={ + + } + fullWidth + > + + } + fullWidth + {...(this.props.validateForm && validateRoleMappingName(this.props.roleMapping))} + > + + + + ); + }; + + private getRolesOrRoleTemplatesSelector = () => { + if (this.state.rolesMode === 'roles') { + return this.getRolesSelector(); + } + return this.getRoleTemplatesSelector(); + }; + + private getRolesSelector = () => { + const validationFunction = () => { + if (!this.props.validateForm) { + return {}; + } + return validateRoleMappingRoles(this.props.roleMapping); + }; + return ( + + + + } + description={ + + + + + + { + this.onRolesModeChange('templates'); + }} + > + + {' '} + + + + + } + fullWidth + > + + this.props.onChange(roleMapping)} + /> + + + ); + }; + + private getRoleTemplatesSelector = () => { + const validationFunction = () => { + if (!this.props.validateForm) { + return {}; + } + return validateRoleMappingRoleTemplates(this.props.roleMapping); + }; + return ( + + + + } + description={ + + + {' '} + + + + + + { + this.onRolesModeChange('roles'); + }} + data-test-subj="switchToRolesButton" + > + + {' '} + + + + + } + fullWidth + > + + this.props.onChange(roleMapping)} + /> + + + ); + }; + + private getEnabledSwitch = () => { + return ( + + + + } + description={ + + } + fullWidth + > + + } + fullWidth + > + + } + showLabel={false} + data-test-subj="roleMappingsEnabledSwitch" + checked={this.props.roleMapping.enabled} + onChange={e => { + this.props.onChange({ + ...this.props.roleMapping, + enabled: e.target.checked, + }); + }} + /> + + + ); + }; + + private onNameChange = (e: ChangeEvent) => { + const name = e.target.value; + + this.props.onChange({ + ...this.props.roleMapping, + name, + }); + }; + + private onRolesModeChange = (rolesMode: State['rolesMode']) => { + const canUseTemplates = this.props.canUseInlineScripts || this.props.canUseStoredScripts; + if (rolesMode === 'templates' && canUseTemplates) { + // Create blank template as a starting point + const defaultTemplate = this.props.canUseInlineScripts + ? { + template: { source: '' }, + } + : { + template: { id: '' }, + }; + this.props.onChange({ + ...this.props.roleMapping, + roles: [], + role_templates: [defaultTemplate], + }); + } + this.setState({ rolesMode }); + }; +} diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/role_selector/add_role_template_button.test.tsx b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/role_selector/add_role_template_button.test.tsx new file mode 100644 index 0000000000000..230664f6fc997 --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/role_selector/add_role_template_button.test.tsx @@ -0,0 +1,71 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { shallowWithIntl, mountWithIntl } from 'test_utils/enzyme_helpers'; +import { AddRoleTemplateButton } from './add_role_template_button'; + +describe('AddRoleTemplateButton', () => { + it('renders a warning instead of a button if all script types are disabled', () => { + const wrapper = shallowWithIntl( + + ); + + expect(wrapper).toMatchInlineSnapshot(` + + } + > +

+ +

+
+ `); + }); + + it(`asks for an inline template to be created if both script types are enabled`, () => { + const onClickHandler = jest.fn(); + const wrapper = mountWithIntl( + + ); + wrapper.simulate('click'); + expect(onClickHandler).toHaveBeenCalledTimes(1); + expect(onClickHandler).toHaveBeenCalledWith('inline'); + }); + + it(`asks for a stored template to be created if inline scripts are disabled`, () => { + const onClickHandler = jest.fn(); + const wrapper = mountWithIntl( + + ); + wrapper.simulate('click'); + expect(onClickHandler).toHaveBeenCalledTimes(1); + expect(onClickHandler).toHaveBeenCalledWith('stored'); + }); +}); diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/role_selector/add_role_template_button.tsx b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/role_selector/add_role_template_button.tsx new file mode 100644 index 0000000000000..5a78e399bacc7 --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/role_selector/add_role_template_button.tsx @@ -0,0 +1,67 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { EuiButtonEmpty, EuiCallOut } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; + +interface Props { + canUseStoredScripts: boolean; + canUseInlineScripts: boolean; + onClick: (templateType: 'inline' | 'stored') => void; +} + +export const AddRoleTemplateButton = (props: Props) => { + if (!props.canUseStoredScripts && !props.canUseInlineScripts) { + return ( + + } + > +

+ +

+
+ ); + } + + const addRoleTemplate = ( + + ); + if (props.canUseInlineScripts) { + return ( + props.onClick('inline')} + data-test-subj="addRoleTemplateButton" + > + {addRoleTemplate} + + ); + } + + return ( + props.onClick('stored')} + data-test-subj="addRoleTemplateButton" + > + {addRoleTemplate} + + ); +}; diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/role_selector/index.tsx b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/role_selector/index.tsx new file mode 100644 index 0000000000000..0011f6ea77bc6 --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/role_selector/index.tsx @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { RoleSelector } from './role_selector'; diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/role_selector/role_selector.test.tsx b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/role_selector/role_selector.test.tsx new file mode 100644 index 0000000000000..89815c50e5547 --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/role_selector/role_selector.test.tsx @@ -0,0 +1,136 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { mountWithIntl } from 'test_utils/enzyme_helpers'; +import { findTestSubject } from 'test_utils/find_test_subject'; +import { EuiComboBox } from '@elastic/eui'; +import { RoleSelector } from './role_selector'; +import { RoleMapping } from '../../../../../../../common/model'; +import { RoleTemplateEditor } from './role_template_editor'; +import { AddRoleTemplateButton } from './add_role_template_button'; + +jest.mock('../../../../../../lib/roles_api', () => { + return { + RolesApi: { + getRoles: () => Promise.resolve([{ name: 'foo_role' }, { name: 'bar role' }]), + }, + }; +}); + +describe('RoleSelector', () => { + it('allows roles to be selected, removing any previously selected role templates', () => { + const props = { + roleMapping: { + roles: [] as string[], + role_templates: [ + { + template: { source: '' }, + }, + ], + } as RoleMapping, + canUseStoredScripts: true, + canUseInlineScripts: true, + onChange: jest.fn(), + mode: 'roles', + } as RoleSelector['props']; + + const wrapper = mountWithIntl(); + (wrapper.find(EuiComboBox).props() as any).onChange([{ label: 'foo_role' }]); + + expect(props.onChange).toHaveBeenCalledWith({ + roles: ['foo_role'], + role_templates: [], + }); + }); + + it('allows role templates to be created, removing any previously selected roles', () => { + const props = { + roleMapping: { + roles: ['foo_role'], + role_templates: [] as any, + } as RoleMapping, + canUseStoredScripts: true, + canUseInlineScripts: true, + onChange: jest.fn(), + mode: 'templates', + } as RoleSelector['props']; + + const wrapper = mountWithIntl(); + + wrapper.find(AddRoleTemplateButton).simulate('click'); + + expect(props.onChange).toHaveBeenCalledWith({ + roles: [], + role_templates: [ + { + template: { source: '' }, + }, + ], + }); + }); + + it('allows role templates to be edited', () => { + const props = { + roleMapping: { + roles: [] as string[], + role_templates: [ + { + template: { source: 'foo_role' }, + }, + ], + } as RoleMapping, + canUseStoredScripts: true, + canUseInlineScripts: true, + onChange: jest.fn(), + mode: 'templates', + } as RoleSelector['props']; + + const wrapper = mountWithIntl(); + + wrapper + .find(RoleTemplateEditor) + .props() + .onChange({ + template: { source: '{{username}}_role' }, + }); + + expect(props.onChange).toHaveBeenCalledWith({ + roles: [], + role_templates: [ + { + template: { source: '{{username}}_role' }, + }, + ], + }); + }); + + it('allows role templates to be deleted', () => { + const props = { + roleMapping: { + roles: [] as string[], + role_templates: [ + { + template: { source: 'foo_role' }, + }, + ], + } as RoleMapping, + canUseStoredScripts: true, + canUseInlineScripts: true, + onChange: jest.fn(), + mode: 'templates', + } as RoleSelector['props']; + + const wrapper = mountWithIntl(); + + findTestSubject(wrapper, 'deleteRoleTemplateButton').simulate('click'); + + expect(props.onChange).toHaveBeenCalledWith({ + roles: [], + role_templates: [], + }); + }); +}); diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/role_selector/role_selector.tsx b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/role_selector/role_selector.tsx new file mode 100644 index 0000000000000..6b92d6b4907f1 --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/role_selector/role_selector.tsx @@ -0,0 +1,132 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { Fragment } from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiComboBox, EuiFormRow, EuiHorizontalRule } from '@elastic/eui'; +import { RoleMapping, Role } from '../../../../../../../common/model'; +import { RolesApi } from '../../../../../../lib/roles_api'; +import { AddRoleTemplateButton } from './add_role_template_button'; +import { RoleTemplateEditor } from './role_template_editor'; + +interface Props { + roleMapping: RoleMapping; + canUseInlineScripts: boolean; + canUseStoredScripts: boolean; + mode: 'roles' | 'templates'; + onChange: (roleMapping: RoleMapping) => void; +} + +interface State { + roles: Role[]; +} + +export class RoleSelector extends React.Component { + constructor(props: Props) { + super(props); + + this.state = { roles: [] }; + } + + public async componentDidMount() { + const roles = await RolesApi.getRoles(); + this.setState({ roles }); + } + + public render() { + const { mode } = this.props; + return ( + + {mode === 'roles' ? this.getRoleComboBox() : this.getRoleTemplates()} + + ); + } + + private getRoleComboBox = () => { + const { roles = [] } = this.props.roleMapping; + return ( + ({ label: r.name }))} + selectedOptions={roles!.map(r => ({ label: r }))} + onChange={selectedOptions => { + this.props.onChange({ + ...this.props.roleMapping, + roles: selectedOptions.map(so => so.label), + role_templates: [], + }); + }} + /> + ); + }; + + private getRoleTemplates = () => { + const { role_templates: roleTemplates = [] } = this.props.roleMapping; + return ( +
+ {roleTemplates.map((rt, index) => ( + + { + const templates = [...(this.props.roleMapping.role_templates || [])]; + templates.splice(index, 1, updatedTemplate); + this.props.onChange({ + ...this.props.roleMapping, + role_templates: templates, + }); + }} + onDelete={() => { + const templates = [...(this.props.roleMapping.role_templates || [])]; + templates.splice(index, 1); + this.props.onChange({ + ...this.props.roleMapping, + role_templates: templates, + }); + }} + /> + + + ))} + { + switch (type) { + case 'inline': { + const templates = this.props.roleMapping.role_templates || []; + this.props.onChange({ + ...this.props.roleMapping, + roles: [], + role_templates: [...templates, { template: { source: '' } }], + }); + break; + } + case 'stored': { + const templates = this.props.roleMapping.role_templates || []; + this.props.onChange({ + ...this.props.roleMapping, + roles: [], + role_templates: [...templates, { template: { id: '' } }], + }); + break; + } + default: + throw new Error(`Unsupported template type: ${type}`); + } + }} + /> +
+ ); + }; +} diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/role_selector/role_template_editor.test.tsx b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/role_selector/role_template_editor.test.tsx new file mode 100644 index 0000000000000..6d4af97e12def --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/role_selector/role_template_editor.test.tsx @@ -0,0 +1,117 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { mountWithIntl } from 'test_utils/enzyme_helpers'; +import { RoleTemplateEditor } from './role_template_editor'; +import { findTestSubject } from 'test_utils/find_test_subject'; + +describe('RoleTemplateEditor', () => { + it('allows inline templates to be edited', () => { + const props = { + roleTemplate: { + template: { + source: '{{username}}_foo', + }, + }, + onChange: jest.fn(), + onDelete: jest.fn(), + canUseStoredScripts: true, + canUseInlineScripts: true, + }; + + const wrapper = mountWithIntl(); + (wrapper + .find('EuiFieldText[data-test-subj="roleTemplateSourceEditor"]') + .props() as any).onChange({ target: { value: 'new_script' } }); + + expect(props.onChange).toHaveBeenCalledWith({ + template: { + source: 'new_script', + }, + }); + }); + + it('warns when editing inline scripts when they are disabled', () => { + const props = { + roleTemplate: { + template: { + source: '{{username}}_foo', + }, + }, + onChange: jest.fn(), + onDelete: jest.fn(), + canUseStoredScripts: true, + canUseInlineScripts: false, + }; + + const wrapper = mountWithIntl(); + expect(findTestSubject(wrapper, 'roleMappingInlineScriptsDisabled')).toHaveLength(1); + expect(findTestSubject(wrapper, 'roleMappingStoredScriptsDisabled')).toHaveLength(0); + expect(findTestSubject(wrapper, 'roleMappingInvalidRoleTemplate')).toHaveLength(0); + }); + + it('warns when editing stored scripts when they are disabled', () => { + const props = { + roleTemplate: { + template: { + id: '{{username}}_foo', + }, + }, + onChange: jest.fn(), + onDelete: jest.fn(), + canUseStoredScripts: false, + canUseInlineScripts: true, + }; + + const wrapper = mountWithIntl(); + expect(findTestSubject(wrapper, 'roleMappingInlineScriptsDisabled')).toHaveLength(0); + expect(findTestSubject(wrapper, 'roleMappingStoredScriptsDisabled')).toHaveLength(1); + expect(findTestSubject(wrapper, 'roleMappingInvalidRoleTemplate')).toHaveLength(0); + }); + + it('allows template types to be changed', () => { + const props = { + roleTemplate: { + template: { + source: '{{username}}_foo', + }, + }, + onChange: jest.fn(), + onDelete: jest.fn(), + canUseStoredScripts: true, + canUseInlineScripts: true, + }; + + const wrapper = mountWithIntl(); + (wrapper + .find('EuiComboBox[data-test-subj="roleMappingsFormTemplateType"]') + .props() as any).onChange('stored'); + + expect(props.onChange).toHaveBeenCalledWith({ + template: { + id: '', + }, + }); + }); + + it('warns when an invalid role template is specified', () => { + const props = { + roleTemplate: { + template: `This is a string instead of an object if the template was stored in an unparsable format in ES`, + }, + onChange: jest.fn(), + onDelete: jest.fn(), + canUseStoredScripts: true, + canUseInlineScripts: true, + }; + + const wrapper = mountWithIntl(); + expect(findTestSubject(wrapper, 'roleMappingInvalidRoleTemplate')).toHaveLength(1); + expect(findTestSubject(wrapper, 'roleTemplateSourceEditor')).toHaveLength(0); + expect(findTestSubject(wrapper, 'roleTemplateScriptIdEditor')).toHaveLength(0); + }); +}); diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/role_selector/role_template_editor.tsx b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/role_selector/role_template_editor.tsx new file mode 100644 index 0000000000000..4b8d34d271996 --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/role_selector/role_template_editor.tsx @@ -0,0 +1,254 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { Fragment } from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { + EuiButtonEmpty, + EuiFlexGroup, + EuiFlexItem, + EuiFormRow, + EuiFieldText, + EuiCallOut, + EuiText, + EuiSwitch, + EuiSpacer, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { RoleTemplate } from '../../../../../../../common/model'; +import { + isInlineRoleTemplate, + isStoredRoleTemplate, + isInvalidRoleTemplate, +} from '../../services/role_template_type'; +import { RoleTemplateTypeSelect } from './role_template_type_select'; + +interface Props { + roleTemplate: RoleTemplate; + canUseInlineScripts: boolean; + canUseStoredScripts: boolean; + onChange: (roleTemplate: RoleTemplate) => void; + onDelete: (roleTemplate: RoleTemplate) => void; +} + +export const RoleTemplateEditor = ({ + roleTemplate, + onChange, + onDelete, + canUseInlineScripts, + canUseStoredScripts, +}: Props) => { + return ( + + {getTemplateConfigurationFields()} + {getEditorForTemplate()} + + + + + onDelete(roleTemplate)} + data-test-subj="deleteRoleTemplateButton" + > + + + + + + + ); + + function getTemplateFormatSwitch() { + const returnsJsonLabel = i18n.translate( + 'xpack.security.management.editRoleMapping.roleTemplateReturnsJson', + { + defaultMessage: 'Returns JSON', + } + ); + + return ( + + { + onChange({ + ...roleTemplate, + format: e.target.checked ? 'json' : 'string', + }); + }} + /> + + ); + } + + function getTemplateConfigurationFields() { + const templateTypeComboBox = ( + + + } + > + + + + ); + + const templateFormatSwitch = {getTemplateFormatSwitch()}; + + return ( + + + {templateTypeComboBox} + {templateFormatSwitch} + + + ); + } + + function getEditorForTemplate() { + if (isInlineRoleTemplate(roleTemplate)) { + const extraProps: Record = {}; + if (!canUseInlineScripts) { + extraProps.isInvalid = true; + extraProps.error = ( + + + + ); + } + const example = '{{username}}_role'; + return ( + + + + } + helpText={ + + } + {...extraProps} + > + { + onChange({ + ...roleTemplate, + template: { + source: e.target.value, + }, + }); + }} + /> + + + + ); + } + + if (isStoredRoleTemplate(roleTemplate)) { + const extraProps: Record = {}; + if (!canUseStoredScripts) { + extraProps.isInvalid = true; + extraProps.error = ( + + + + ); + } + return ( + + + + } + helpText={ + + } + {...extraProps} + > + { + onChange({ + ...roleTemplate, + template: { + id: e.target.value, + }, + }); + }} + /> + + + + ); + } + + if (isInvalidRoleTemplate(roleTemplate)) { + return ( + + + } + > + + + + ); + } + + throw new Error(`Unable to determine role template type`); + } +}; diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/role_selector/role_template_type_select.tsx b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/role_selector/role_template_type_select.tsx new file mode 100644 index 0000000000000..4a06af0fb436b --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/role_selector/role_template_type_select.tsx @@ -0,0 +1,77 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiComboBox } from '@elastic/eui'; +import { RoleTemplate } from '../../../../../../../common/model'; +import { isInlineRoleTemplate, isStoredRoleTemplate } from '../../services/role_template_type'; + +const templateTypeOptions = [ + { + id: 'inline', + label: i18n.translate( + 'xpack.security.management.editRoleMapping.roleTemplate.inlineTypeLabel', + { defaultMessage: 'Role template' } + ), + }, + { + id: 'stored', + label: i18n.translate( + 'xpack.security.management.editRoleMapping.roleTemplate.storedTypeLabel', + { defaultMessage: 'Stored script' } + ), + }, +]; + +interface Props { + roleTemplate: RoleTemplate; + onChange: (roleTempplate: RoleTemplate) => void; + canUseStoredScripts: boolean; + canUseInlineScripts: boolean; +} + +export const RoleTemplateTypeSelect = (props: Props) => { + const availableOptions = templateTypeOptions.filter( + ({ id }) => + (id === 'inline' && props.canUseInlineScripts) || + (id === 'stored' && props.canUseStoredScripts) + ); + + const selectedOptions = templateTypeOptions.filter( + ({ id }) => + (id === 'inline' && isInlineRoleTemplate(props.roleTemplate)) || + (id === 'stored' && isStoredRoleTemplate(props.roleTemplate)) + ); + + return ( + { + const [{ id }] = selected; + if (id === 'inline') { + props.onChange({ + ...props.roleTemplate, + template: { + source: '', + }, + }); + } else { + props.onChange({ + ...props.roleTemplate, + template: { + id: '', + }, + }); + } + }} + isClearable={false} + /> + ); +}; diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/_index.scss b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/_index.scss new file mode 100644 index 0000000000000..de64b80599720 --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/_index.scss @@ -0,0 +1,7 @@ +.secRoleMapping__ruleEditorGroup--even { + background-color: $euiColorLightestShade; +} + +.secRoleMapping__ruleEditorGroup--odd { + background-color: $euiColorEmptyShade; +} \ No newline at end of file diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/add_rule_button.test.tsx b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/add_rule_button.test.tsx new file mode 100644 index 0000000000000..917b822acef3f --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/add_rule_button.test.tsx @@ -0,0 +1,55 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { AddRuleButton } from './add_rule_button'; +import { mountWithIntl } from 'test_utils/enzyme_helpers'; +import { findTestSubject } from 'test_utils/find_test_subject'; +import { FieldRule, AllRule } from '../../../model'; + +describe('AddRuleButton', () => { + it('allows a field rule to be created', () => { + const props = { + onClick: jest.fn(), + }; + + const wrapper = mountWithIntl(); + findTestSubject(wrapper, 'roleMappingsAddRuleButton').simulate('click'); + expect(findTestSubject(wrapper, 'addRuleContextMenu')).toHaveLength(1); + + // EUI renders this ID twice, so we need to target the button itself + wrapper.find('button[id="addRuleOption"]').simulate('click'); + + expect(props.onClick).toHaveBeenCalledTimes(1); + + const [newRule] = props.onClick.mock.calls[0]; + expect(newRule).toBeInstanceOf(FieldRule); + expect(newRule.toRaw()).toEqual({ + field: { username: '*' }, + }); + }); + + it('allows a rule group to be created', () => { + const props = { + onClick: jest.fn(), + }; + + const wrapper = mountWithIntl(); + findTestSubject(wrapper, 'roleMappingsAddRuleButton').simulate('click'); + expect(findTestSubject(wrapper, 'addRuleContextMenu')).toHaveLength(1); + + // EUI renders this ID twice, so we need to target the button itself + wrapper.find('button[id="addRuleGroupOption"]').simulate('click'); + + expect(props.onClick).toHaveBeenCalledTimes(1); + + const [newRule] = props.onClick.mock.calls[0]; + expect(newRule).toBeInstanceOf(AllRule); + expect(newRule.toRaw()).toEqual({ + all: [{ field: { username: '*' } }], + }); + }); +}); diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/add_rule_button.tsx b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/add_rule_button.tsx new file mode 100644 index 0000000000000..100c0dd3eeaee --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/add_rule_button.tsx @@ -0,0 +1,81 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useState } from 'react'; +import { EuiButtonEmpty, EuiPopover, EuiContextMenuPanel, EuiContextMenuItem } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { Rule, FieldRule, AllRule } from '../../../model'; + +interface Props { + onClick: (newRule: Rule) => void; +} + +export const AddRuleButton = (props: Props) => { + const [isMenuOpen, setIsMenuOpen] = useState(false); + + const button = ( + { + setIsMenuOpen(!isMenuOpen); + }} + > + + + ); + + const options = [ + { + setIsMenuOpen(false); + props.onClick(new FieldRule('username', '*')); + }} + > + + , + { + setIsMenuOpen(false); + props.onClick(new AllRule([new FieldRule('username', '*')])); + }} + > + + , + ]; + + return ( + setIsMenuOpen(false)} + panelPaddingSize="none" + withTitle + anchorPosition="downLeft" + > + + + ); +}; diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/field_rule_editor.test.tsx b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/field_rule_editor.test.tsx new file mode 100644 index 0000000000000..8d5d5c99ee99d --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/field_rule_editor.test.tsx @@ -0,0 +1,230 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { FieldRuleEditor } from './field_rule_editor'; +import { mountWithIntl } from 'test_utils/enzyme_helpers'; +import { FieldRule } from '../../../model'; +import { findTestSubject } from 'test_utils/find_test_subject'; +import { ReactWrapper } from 'enzyme'; + +function assertField(wrapper: ReactWrapper, index: number, field: string) { + const isFirst = index === 0; + if (isFirst) { + expect( + wrapper.find(`EuiComboBox[data-test-subj~="fieldRuleEditorField-${index}"]`).props() + ).toMatchObject({ + selectedOptions: [{ label: field }], + }); + + expect(findTestSubject(wrapper, `fieldRuleEditorField-${index}-combo`)).toHaveLength(1); + expect(findTestSubject(wrapper, `fieldRuleEditorField-${index}-expression`)).toHaveLength(0); + } else { + expect( + wrapper.find(`EuiExpression[data-test-subj~="fieldRuleEditorField-${index}"]`).props() + ).toMatchObject({ + value: field, + }); + + expect(findTestSubject(wrapper, `fieldRuleEditorField-${index}-combo`)).toHaveLength(0); + expect(findTestSubject(wrapper, `fieldRuleEditorField-${index}-expression`)).toHaveLength(1); + } +} + +function assertValueType(wrapper: ReactWrapper, index: number, type: string) { + const valueTypeField = findTestSubject(wrapper, `fieldRuleEditorValueType-${index}`); + expect(valueTypeField.props()).toMatchObject({ value: type }); +} + +function assertValue(wrapper: ReactWrapper, index: number, value: any) { + const valueField = findTestSubject(wrapper, `fieldRuleEditorValue-${index}`); + expect(valueField.props()).toMatchObject({ value }); +} + +describe('FieldRuleEditor', () => { + it('can render a text-based field rule', () => { + const props = { + rule: new FieldRule('username', '*'), + onChange: jest.fn(), + onDelete: jest.fn(), + }; + + const wrapper = mountWithIntl(); + assertField(wrapper, 0, 'username'); + assertValueType(wrapper, 0, 'text'); + assertValue(wrapper, 0, '*'); + }); + + it('can render a number-based field rule', () => { + const props = { + rule: new FieldRule('username', 12), + onChange: jest.fn(), + onDelete: jest.fn(), + }; + + const wrapper = mountWithIntl(); + assertField(wrapper, 0, 'username'); + assertValueType(wrapper, 0, 'number'); + assertValue(wrapper, 0, 12); + }); + + it('can render a null-based field rule', () => { + const props = { + rule: new FieldRule('username', null), + onChange: jest.fn(), + onDelete: jest.fn(), + }; + + const wrapper = mountWithIntl(); + assertField(wrapper, 0, 'username'); + assertValueType(wrapper, 0, 'null'); + assertValue(wrapper, 0, '-- null --'); + }); + + it('can render a boolean-based field rule (true)', () => { + const props = { + rule: new FieldRule('username', true), + onChange: jest.fn(), + onDelete: jest.fn(), + }; + + const wrapper = mountWithIntl(); + assertField(wrapper, 0, 'username'); + assertValueType(wrapper, 0, 'boolean'); + assertValue(wrapper, 0, 'true'); + }); + + it('can render a boolean-based field rule (false)', () => { + const props = { + rule: new FieldRule('username', false), + onChange: jest.fn(), + onDelete: jest.fn(), + }; + + const wrapper = mountWithIntl(); + assertField(wrapper, 0, 'username'); + assertValueType(wrapper, 0, 'boolean'); + assertValue(wrapper, 0, 'false'); + }); + + it('can render with alternate values specified', () => { + const props = { + rule: new FieldRule('username', ['*', 12, null, true, false]), + onChange: jest.fn(), + onDelete: jest.fn(), + }; + + const wrapper = mountWithIntl(); + expect(findTestSubject(wrapper, 'addAlternateValueButton')).toHaveLength(1); + + assertField(wrapper, 0, 'username'); + assertValueType(wrapper, 0, 'text'); + assertValue(wrapper, 0, '*'); + + assertField(wrapper, 1, 'username'); + assertValueType(wrapper, 1, 'number'); + assertValue(wrapper, 1, 12); + + assertField(wrapper, 2, 'username'); + assertValueType(wrapper, 2, 'null'); + assertValue(wrapper, 2, '-- null --'); + + assertField(wrapper, 3, 'username'); + assertValueType(wrapper, 3, 'boolean'); + assertValue(wrapper, 3, 'true'); + + assertField(wrapper, 4, 'username'); + assertValueType(wrapper, 4, 'boolean'); + assertValue(wrapper, 4, 'false'); + }); + + it('allows alternate values to be added when "allowAdd" is set to true', () => { + const props = { + rule: new FieldRule('username', null), + onChange: jest.fn(), + onDelete: jest.fn(), + }; + + const wrapper = mountWithIntl(); + findTestSubject(wrapper, 'addAlternateValueButton').simulate('click'); + expect(props.onChange).toHaveBeenCalledTimes(1); + const [updatedRule] = props.onChange.mock.calls[0]; + expect(updatedRule.toRaw()).toEqual({ + field: { + username: [null, '*'], + }, + }); + }); + + it('allows values to be deleted; deleting all values invokes "onDelete"', () => { + const props = { + rule: new FieldRule('username', ['*', 12, null]), + onChange: jest.fn(), + onDelete: jest.fn(), + }; + + const wrapper = mountWithIntl(); + + expect(findTestSubject(wrapper, `fieldRuleEditorDeleteValue`)).toHaveLength(3); + findTestSubject(wrapper, `fieldRuleEditorDeleteValue-0`).simulate('click'); + + expect(props.onChange).toHaveBeenCalledTimes(1); + const [updatedRule1] = props.onChange.mock.calls[0]; + expect(updatedRule1.toRaw()).toEqual({ + field: { + username: [12, null], + }, + }); + + props.onChange.mockReset(); + + // simulate updated rule being fed back in + wrapper.setProps({ rule: updatedRule1 }); + + expect(findTestSubject(wrapper, `fieldRuleEditorDeleteValue`)).toHaveLength(2); + findTestSubject(wrapper, `fieldRuleEditorDeleteValue-1`).simulate('click'); + + expect(props.onChange).toHaveBeenCalledTimes(1); + const [updatedRule2] = props.onChange.mock.calls[0]; + expect(updatedRule2.toRaw()).toEqual({ + field: { + username: [12], + }, + }); + + props.onChange.mockReset(); + + // simulate updated rule being fed back in + wrapper.setProps({ rule: updatedRule2 }); + + expect(findTestSubject(wrapper, `fieldRuleEditorDeleteValue`)).toHaveLength(1); + findTestSubject(wrapper, `fieldRuleEditorDeleteValue-0`).simulate('click'); + + expect(props.onChange).toHaveBeenCalledTimes(0); + expect(props.onDelete).toHaveBeenCalledTimes(1); + }); + + it('allows field data types to be changed', () => { + const props = { + rule: new FieldRule('username', '*'), + onChange: jest.fn(), + onDelete: jest.fn(), + }; + + const wrapper = mountWithIntl(); + + const { onChange } = findTestSubject(wrapper, `fieldRuleEditorValueType-0`).props(); + onChange!({ target: { value: 'number' } as any } as any); + + expect(props.onChange).toHaveBeenCalledTimes(1); + const [updatedRule] = props.onChange.mock.calls[0]; + expect(updatedRule.toRaw()).toEqual({ + field: { + username: 0, + }, + }); + }); +}); diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/field_rule_editor.tsx b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/field_rule_editor.tsx new file mode 100644 index 0000000000000..52cf70dbd12bd --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/field_rule_editor.tsx @@ -0,0 +1,380 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { Component, ChangeEvent } from 'react'; +import { + EuiButtonIcon, + EuiExpression, + EuiFlexGroup, + EuiFlexItem, + EuiFormRow, + EuiFieldText, + EuiComboBox, + EuiSelect, + EuiFieldNumber, + EuiIcon, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FieldRule, FieldRuleValue } from '../../../model'; + +interface Props { + rule: FieldRule; + onChange: (rule: FieldRule) => void; + onDelete: () => void; +} + +const userFields = [ + { + name: 'username', + }, + { + name: 'dn', + }, + { + name: 'groups', + }, + { + name: 'realm', + }, +]; + +const fieldOptions = userFields.map(f => ({ label: f.name })); + +type ComparisonOption = 'text' | 'number' | 'null' | 'boolean'; +const comparisonOptions: Record< + ComparisonOption, + { id: ComparisonOption; defaultValue: FieldRuleValue } +> = { + text: { + id: 'text', + defaultValue: '*', + }, + number: { + id: 'number', + defaultValue: 0, + }, + null: { + id: 'null', + defaultValue: null, + }, + boolean: { + id: 'boolean', + defaultValue: true, + }, +}; + +export class FieldRuleEditor extends Component { + public render() { + const { field, value } = this.props.rule; + + const content = Array.isArray(value) + ? value.map((v, index) => this.renderFieldRow(field, value, index)) + : [this.renderFieldRow(field, value, 0)]; + + return ( + + {content.map((row, index) => { + return {row}; + })} + + ); + } + + private renderFieldRow = (field: string, ruleValue: FieldRuleValue, valueIndex: number) => { + const isPrimaryRow = valueIndex === 0; + + let renderAddValueButton = true; + let rowRuleValue: FieldRuleValue = ruleValue; + if (Array.isArray(ruleValue)) { + renderAddValueButton = ruleValue.length - 1 === valueIndex; + rowRuleValue = ruleValue[valueIndex]; + } + + const comparisonType = this.getComparisonType(rowRuleValue); + + return ( + + + {isPrimaryRow ? ( + + + + ) : ( + + + + )} + + + {this.renderFieldTypeInput(comparisonType.id, valueIndex)} + + + {this.renderFieldValueInput(comparisonType.id, rowRuleValue, valueIndex)} + + + + {renderAddValueButton ? ( + + ) : ( + + )} + + + + + this.onRemoveAlternateValue(valueIndex)} + /> + + + + ); + }; + + private renderFieldTypeInput = (inputType: ComparisonOption, valueIndex: number) => { + return ( + + + this.onComparisonTypeChange(valueIndex, e.target.value as ComparisonOption) + } + /> + + ); + }; + + private renderFieldValueInput = ( + fieldType: ComparisonOption, + rowRuleValue: FieldRuleValue, + valueIndex: number + ) => { + const inputField = this.getInputFieldForType(fieldType, rowRuleValue, valueIndex); + + return ( + + {inputField} + + ); + }; + + private getInputFieldForType = ( + fieldType: ComparisonOption, + rowRuleValue: FieldRuleValue, + valueIndex: number + ) => { + const isNullValue = rowRuleValue === null; + + const commonProps = { + 'data-test-subj': `fieldRuleEditorValue-${valueIndex}`, + }; + + switch (fieldType) { + case 'boolean': + return ( + + ); + case 'text': + case 'null': + return ( + + ); + case 'number': + return ( + + ); + default: + throw new Error(`Unsupported input field type: ${fieldType}`); + } + }; + + private onAddAlternateValue = () => { + const { field, value } = this.props.rule; + const nextValue = Array.isArray(value) ? [...value] : [value]; + nextValue.push('*'); + this.props.onChange(new FieldRule(field, nextValue)); + }; + + private onRemoveAlternateValue = (index: number) => { + const { field, value } = this.props.rule; + + if (!Array.isArray(value) || value.length === 1) { + // Only one value left. Delete entire rule instead. + this.props.onDelete(); + return; + } + const nextValue = [...value]; + nextValue.splice(index, 1); + this.props.onChange(new FieldRule(field, nextValue)); + }; + + private onFieldChange = ([newField]: Array<{ label: string }>) => { + if (!newField) { + return; + } + + const { value } = this.props.rule; + this.props.onChange(new FieldRule(newField.label, value)); + }; + + private onAddField = (newField: string) => { + const { value } = this.props.rule; + this.props.onChange(new FieldRule(newField, value)); + }; + + private onValueChange = (index: number) => (e: ChangeEvent) => { + const { field, value } = this.props.rule; + let nextValue; + if (Array.isArray(value)) { + nextValue = [...value]; + nextValue.splice(index, 1, e.target.value); + } else { + nextValue = e.target.value; + } + this.props.onChange(new FieldRule(field, nextValue)); + }; + + private onNumericValueChange = (index: number) => (e: ChangeEvent) => { + const { field, value } = this.props.rule; + let nextValue; + if (Array.isArray(value)) { + nextValue = [...value]; + nextValue.splice(index, 1, parseFloat(e.target.value)); + } else { + nextValue = parseFloat(e.target.value); + } + this.props.onChange(new FieldRule(field, nextValue)); + }; + + private onBooleanValueChange = (index: number) => (e: ChangeEvent) => { + const boolValue = e.target.value === 'true'; + + const { field, value } = this.props.rule; + let nextValue; + if (Array.isArray(value)) { + nextValue = [...value]; + nextValue.splice(index, 1, boolValue); + } else { + nextValue = boolValue; + } + this.props.onChange(new FieldRule(field, nextValue)); + }; + + private onComparisonTypeChange = (index: number, newType: ComparisonOption) => { + const comparison = comparisonOptions[newType]; + if (!comparison) { + throw new Error(`Unexpected comparison type: ${newType}`); + } + const { field, value } = this.props.rule; + let nextValue = value; + if (Array.isArray(value)) { + nextValue = [...value]; + nextValue.splice(index, 1, comparison.defaultValue as any); + } else { + nextValue = comparison.defaultValue; + } + this.props.onChange(new FieldRule(field, nextValue)); + }; + + private getComparisonType(ruleValue: FieldRuleValue) { + const valueType = typeof ruleValue; + if (valueType === 'string' || valueType === 'undefined') { + return comparisonOptions.text; + } + if (valueType === 'number') { + return comparisonOptions.number; + } + if (valueType === 'boolean') { + return comparisonOptions.boolean; + } + if (ruleValue === null) { + return comparisonOptions.null; + } + throw new Error(`Unable to detect comparison type for rule value [${ruleValue}]`); + } +} diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/index.tsx b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/index.tsx new file mode 100644 index 0000000000000..dc09cb1e591fa --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/index.tsx @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { RuleEditorPanel } from './rule_editor_panel'; diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/json_rule_editor.test.tsx b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/json_rule_editor.test.tsx new file mode 100644 index 0000000000000..8a9b37ab0f406 --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/json_rule_editor.test.tsx @@ -0,0 +1,164 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import 'brace'; +import 'brace/mode/json'; + +// brace/ace uses the Worker class, which is not currently provided by JSDOM. +// This is not required for the tests to pass, but it rather suppresses lengthy +// warnings in the console which adds unnecessary noise to the test output. +import 'test_utils/stub_web_worker'; + +import React from 'react'; +import { act } from 'react-dom/test-utils'; +import { mountWithIntl } from 'test_utils/enzyme_helpers'; +import { JSONRuleEditor } from './json_rule_editor'; +import { EuiCodeEditor } from '@elastic/eui'; +import { AllRule, AnyRule, FieldRule, ExceptAnyRule, ExceptAllRule } from '../../../model'; + +describe('JSONRuleEditor', () => { + it('renders an empty rule set', () => { + const props = { + rules: null, + onChange: jest.fn(), + onValidityChange: jest.fn(), + }; + const wrapper = mountWithIntl(); + + expect(props.onChange).not.toHaveBeenCalled(); + expect(props.onValidityChange).not.toHaveBeenCalled(); + + expect(wrapper.find(EuiCodeEditor).props().value).toMatchInlineSnapshot(`"{}"`); + }); + + it('renders a rule set', () => { + const props = { + rules: new AllRule([ + new AnyRule([new FieldRule('username', '*')]), + new ExceptAnyRule([ + new FieldRule('metadata.foo.bar', '*'), + new AllRule([new FieldRule('realm', 'special-one')]), + ]), + new ExceptAllRule([new FieldRule('realm', '*')]), + ]), + onChange: jest.fn(), + onValidityChange: jest.fn(), + }; + const wrapper = mountWithIntl(); + + const { value } = wrapper.find(EuiCodeEditor).props(); + expect(JSON.parse(value)).toEqual({ + all: [ + { + any: [{ field: { username: '*' } }], + }, + { + except: { + any: [ + { field: { 'metadata.foo.bar': '*' } }, + { + all: [{ field: { realm: 'special-one' } }], + }, + ], + }, + }, + { + except: { + all: [{ field: { realm: '*' } }], + }, + }, + ], + }); + }); + + it('notifies when input contains invalid JSON', () => { + const props = { + rules: null, + onChange: jest.fn(), + onValidityChange: jest.fn(), + }; + const wrapper = mountWithIntl(); + + const allRule = JSON.stringify(new AllRule().toRaw()); + act(() => { + wrapper + .find(EuiCodeEditor) + .props() + .onChange(allRule + ', this makes invalid JSON'); + }); + + expect(props.onValidityChange).toHaveBeenCalledTimes(1); + expect(props.onValidityChange).toHaveBeenCalledWith(false); + expect(props.onChange).not.toHaveBeenCalled(); + }); + + it('notifies when input contains an invalid rule set, even if it is valid JSON', () => { + const props = { + rules: null, + onChange: jest.fn(), + onValidityChange: jest.fn(), + }; + const wrapper = mountWithIntl(); + + const invalidRule = JSON.stringify({ + all: [ + { + field: { + foo: {}, + }, + }, + ], + }); + + act(() => { + wrapper + .find(EuiCodeEditor) + .props() + .onChange(invalidRule); + }); + + expect(props.onValidityChange).toHaveBeenCalledTimes(1); + expect(props.onValidityChange).toHaveBeenCalledWith(false); + expect(props.onChange).not.toHaveBeenCalled(); + }); + + it('fires onChange when a valid rule set is provided after being previously invalidated', () => { + const props = { + rules: null, + onChange: jest.fn(), + onValidityChange: jest.fn(), + }; + const wrapper = mountWithIntl(); + + const allRule = JSON.stringify(new AllRule().toRaw()); + act(() => { + wrapper + .find(EuiCodeEditor) + .props() + .onChange(allRule + ', this makes invalid JSON'); + }); + + expect(props.onValidityChange).toHaveBeenCalledTimes(1); + expect(props.onValidityChange).toHaveBeenCalledWith(false); + expect(props.onChange).not.toHaveBeenCalled(); + + props.onValidityChange.mockReset(); + + act(() => { + wrapper + .find(EuiCodeEditor) + .props() + .onChange(allRule); + }); + + expect(props.onValidityChange).toHaveBeenCalledTimes(1); + expect(props.onValidityChange).toHaveBeenCalledWith(true); + + expect(props.onChange).toHaveBeenCalledTimes(1); + const [updatedRule] = props.onChange.mock.calls[0]; + expect(JSON.stringify(updatedRule.toRaw())).toEqual(allRule); + }); +}); diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/json_rule_editor.tsx b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/json_rule_editor.tsx new file mode 100644 index 0000000000000..371fb59f7a5d1 --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/json_rule_editor.tsx @@ -0,0 +1,127 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useState, Fragment } from 'react'; + +import 'brace/mode/json'; +import 'brace/theme/github'; +import { EuiCodeEditor, EuiFormRow, EuiButton, EuiSpacer, EuiLink, EuiText } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; +import { Rule, RuleBuilderError, generateRulesFromRaw } from '../../../model'; +import { documentationLinks } from '../../../services/documentation_links'; + +interface Props { + rules: Rule | null; + onChange: (updatedRules: Rule | null) => void; + onValidityChange: (isValid: boolean) => void; +} + +export const JSONRuleEditor = (props: Props) => { + const [rawRules, setRawRules] = useState( + JSON.stringify(props.rules ? props.rules.toRaw() : {}, null, 2) + ); + + const [ruleBuilderError, setRuleBuilderError] = useState(null); + + function onRulesChange(updatedRules: string) { + setRawRules(updatedRules); + // Fire onChange only if rules are valid + try { + const ruleJSON = JSON.parse(updatedRules); + props.onChange(generateRulesFromRaw(ruleJSON).rules); + props.onValidityChange(true); + setRuleBuilderError(null); + } catch (e) { + if (e instanceof RuleBuilderError) { + setRuleBuilderError(e); + } else { + setRuleBuilderError(null); + } + props.onValidityChange(false); + } + } + + function reformatRules() { + try { + const ruleJSON = JSON.parse(rawRules); + setRawRules(JSON.stringify(ruleJSON, null, 2)); + } catch (ignore) { + // ignore + } + } + + return ( + + + + + + + + + +

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

+
+
+
+ ); +}; diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/rule_editor_panel.test.tsx b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/rule_editor_panel.test.tsx new file mode 100644 index 0000000000000..809264183d30c --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/rule_editor_panel.test.tsx @@ -0,0 +1,114 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { mountWithIntl } from 'test_utils/enzyme_helpers'; +import { RuleEditorPanel } from '.'; +import { VisualRuleEditor } from './visual_rule_editor'; +import { JSONRuleEditor } from './json_rule_editor'; +import { findTestSubject } from 'test_utils/find_test_subject'; + +// brace/ace uses the Worker class, which is not currently provided by JSDOM. +// This is not required for the tests to pass, but it rather suppresses lengthy +// warnings in the console which adds unnecessary noise to the test output. +import 'test_utils/stub_web_worker'; +import { AllRule, FieldRule } from '../../../model'; +import { EuiErrorBoundary } from '@elastic/eui'; + +describe('RuleEditorPanel', () => { + it('renders the visual editor when no rules are defined', () => { + const props = { + rawRules: {}, + onChange: jest.fn(), + onValidityChange: jest.fn(), + validateForm: false, + }; + const wrapper = mountWithIntl(); + expect(wrapper.find(VisualRuleEditor)).toHaveLength(1); + expect(wrapper.find(JSONRuleEditor)).toHaveLength(0); + }); + + it('allows switching to the JSON editor, carrying over rules', () => { + const props = { + rawRules: { + all: [ + { + field: { + username: ['*'], + }, + }, + ], + }, + onChange: jest.fn(), + onValidityChange: jest.fn(), + validateForm: false, + }; + const wrapper = mountWithIntl(); + expect(wrapper.find(VisualRuleEditor)).toHaveLength(1); + expect(wrapper.find(JSONRuleEditor)).toHaveLength(0); + + findTestSubject(wrapper, 'roleMappingsJSONRuleEditorButton').simulate('click'); + + expect(wrapper.find(VisualRuleEditor)).toHaveLength(0); + + const jsonEditor = wrapper.find(JSONRuleEditor); + expect(jsonEditor).toHaveLength(1); + const { rules } = jsonEditor.props(); + expect(rules!.toRaw()).toEqual(props.rawRules); + }); + + it('allows switching to the visual editor, carrying over rules', () => { + const props = { + rawRules: { + field: { username: '*' }, + }, + onChange: jest.fn(), + onValidityChange: jest.fn(), + validateForm: false, + }; + const wrapper = mountWithIntl(); + + findTestSubject(wrapper, 'roleMappingsJSONRuleEditorButton').simulate('click'); + + expect(wrapper.find(VisualRuleEditor)).toHaveLength(0); + expect(wrapper.find(JSONRuleEditor)).toHaveLength(1); + + const jsonEditor = wrapper.find(JSONRuleEditor); + expect(jsonEditor).toHaveLength(1); + const { rules: initialRules, onChange } = jsonEditor.props(); + expect(initialRules?.toRaw()).toEqual({ + field: { username: '*' }, + }); + + onChange(new AllRule([new FieldRule('otherRule', 12)])); + + findTestSubject(wrapper, 'roleMappingsVisualRuleEditorButton').simulate('click'); + + expect(wrapper.find(VisualRuleEditor)).toHaveLength(1); + expect(wrapper.find(JSONRuleEditor)).toHaveLength(0); + + expect(props.onChange).toHaveBeenCalledTimes(1); + const [rules] = props.onChange.mock.calls[0]; + expect(rules).toEqual({ + all: [{ field: { otherRule: 12 } }], + }); + }); + + it('catches errors thrown by child components', () => { + const props = { + rawRules: {}, + onChange: jest.fn(), + onValidityChange: jest.fn(), + validateForm: false, + }; + const wrapper = mountWithIntl(); + + wrapper.find(VisualRuleEditor).simulateError(new Error('Something awful happened here.')); + + expect(wrapper.find(VisualRuleEditor)).toHaveLength(0); + expect(wrapper.find(EuiErrorBoundary)).toHaveLength(1); + }); +}); diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/rule_editor_panel.tsx b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/rule_editor_panel.tsx new file mode 100644 index 0000000000000..4aab49b2b2efc --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/rule_editor_panel.tsx @@ -0,0 +1,298 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { Component, Fragment } from 'react'; +import { + EuiSpacer, + EuiConfirmModal, + EuiOverlayMask, + EuiCallOut, + EuiErrorBoundary, + EuiIcon, + EuiLink, + EuiFlexGroup, + EuiFlexItem, + EuiText, + EuiFormRow, + EuiPanel, + EuiTitle, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; +import { RoleMapping } from '../../../../../../../common/model'; +import { VisualRuleEditor } from './visual_rule_editor'; +import { JSONRuleEditor } from './json_rule_editor'; +import { VISUAL_MAX_RULE_DEPTH } from '../../services/role_mapping_constants'; +import { Rule, generateRulesFromRaw } from '../../../model'; +import { validateRoleMappingRules } from '../../services/role_mapping_validation'; +import { documentationLinks } from '../../../services/documentation_links'; + +interface Props { + rawRules: RoleMapping['rules']; + onChange: (rawRules: RoleMapping['rules']) => void; + onValidityChange: (isValid: boolean) => void; + validateForm: boolean; +} + +interface State { + rules: Rule | null; + maxDepth: number; + isRuleValid: boolean; + showConfirmModeChange: boolean; + showVisualEditorDisabledAlert: boolean; + mode: 'visual' | 'json'; +} + +export class RuleEditorPanel extends Component { + constructor(props: Props) { + super(props); + this.state = { + ...this.initializeFromRawRules(props.rawRules), + isRuleValid: true, + showConfirmModeChange: false, + showVisualEditorDisabledAlert: false, + }; + } + + public render() { + const validationResult = + this.props.validateForm && + validateRoleMappingRules({ rules: this.state.rules ? this.state.rules.toRaw() : {} }); + + let validationWarning = null; + if (validationResult && validationResult.error) { + validationWarning = ( + + + + ); + } + + return ( + + +

+ +

+
+ + + +

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

+
+
+ + + + + {validationWarning} + {this.getEditor()} + + {this.getModeToggle()} + {this.getConfirmModeChangePrompt()} + + + + +
+
+ ); + } + + private initializeFromRawRules = (rawRules: Props['rawRules']) => { + const { rules, maxDepth } = generateRulesFromRaw(rawRules); + const mode: State['mode'] = maxDepth >= VISUAL_MAX_RULE_DEPTH ? 'json' : 'visual'; + return { + rules, + mode, + maxDepth, + }; + }; + + private getModeToggle() { + if (this.state.mode === 'json' && this.state.maxDepth > VISUAL_MAX_RULE_DEPTH) { + return ( + + + + ); + } + + // Don't offer swith if no rules are present yet + if (this.state.mode === 'visual' && this.state.rules === null) { + return null; + } + + switch (this.state.mode) { + case 'visual': + return ( + { + this.trySwitchEditorMode('json'); + }} + > + + {' '} + + + + ); + case 'json': + return ( + { + this.trySwitchEditorMode('visual'); + }} + > + + {' '} + + + + ); + default: + throw new Error(`Unexpected rule editor mode: ${this.state.mode}`); + } + } + + private getEditor() { + switch (this.state.mode) { + case 'visual': + return ( + this.trySwitchEditorMode('json')} + /> + ); + case 'json': + return ( + + ); + default: + throw new Error(`Unexpected rule editor mode: ${this.state.mode}`); + } + } + + private getConfirmModeChangePrompt = () => { + if (!this.state.showConfirmModeChange) { + return null; + } + return ( + + + } + onCancel={() => this.setState({ showConfirmModeChange: false })} + onConfirm={() => { + this.setState({ mode: 'visual', showConfirmModeChange: false }); + this.onValidityChange(true); + }} + cancelButtonText={ + + } + confirmButtonText={ + + } + > +

+ +

+
+
+ ); + }; + + private onRuleChange = (updatedRule: Rule | null) => { + const raw = updatedRule ? updatedRule.toRaw() : {}; + this.props.onChange(raw); + this.setState({ + ...generateRulesFromRaw(raw), + }); + }; + + private onValidityChange = (isRuleValid: boolean) => { + this.setState({ isRuleValid }); + this.props.onValidityChange(isRuleValid); + }; + + private trySwitchEditorMode = (newMode: State['mode']) => { + switch (newMode) { + case 'visual': { + if (this.state.isRuleValid) { + this.setState({ mode: newMode }); + this.onValidityChange(true); + } else { + this.setState({ showConfirmModeChange: true }); + } + break; + } + case 'json': + this.setState({ mode: newMode }); + this.onValidityChange(true); + break; + default: + throw new Error(`Unexpected rule editor mode: ${this.state.mode}`); + } + }; +} diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/rule_group_editor.test.tsx b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/rule_group_editor.test.tsx new file mode 100644 index 0000000000000..3e0e0e386e98c --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/rule_group_editor.test.tsx @@ -0,0 +1,149 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { RuleGroupEditor } from './rule_group_editor'; +import { shallowWithIntl, mountWithIntl, nextTick } from 'test_utils/enzyme_helpers'; +import { AllRule, FieldRule, AnyRule, ExceptAnyRule } from '../../../model'; +import { FieldRuleEditor } from './field_rule_editor'; +import { AddRuleButton } from './add_rule_button'; +import { EuiContextMenuItem } from '@elastic/eui'; +import { findTestSubject } from 'test_utils/find_test_subject'; + +describe('RuleGroupEditor', () => { + it('renders an empty group', () => { + const props = { + rule: new AllRule([]), + allowAdd: true, + ruleDepth: 0, + onChange: jest.fn(), + onDelete: jest.fn(), + }; + const wrapper = shallowWithIntl(); + expect(wrapper.find(RuleGroupEditor)).toHaveLength(0); + expect(wrapper.find(FieldRuleEditor)).toHaveLength(0); + expect(wrapper.find(AddRuleButton)).toHaveLength(1); + }); + + it('allows the group type to be changed, maintaining child rules', async () => { + const props = { + rule: new AllRule([new FieldRule('username', '*')]), + allowAdd: true, + ruleDepth: 0, + onChange: jest.fn(), + onDelete: jest.fn(), + }; + const wrapper = mountWithIntl(); + expect(wrapper.find(RuleGroupEditor)).toHaveLength(1); + expect(wrapper.find(FieldRuleEditor)).toHaveLength(1); + expect(wrapper.find(AddRuleButton)).toHaveLength(1); + expect(findTestSubject(wrapper, 'deleteRuleGroupButton')).toHaveLength(1); + + const anyRule = new AnyRule(); + + findTestSubject(wrapper, 'ruleGroupTitle').simulate('click'); + await nextTick(); + wrapper.update(); + + const anyRuleOption = wrapper.find(EuiContextMenuItem).filterWhere(menuItem => { + return menuItem.text() === anyRule.getDisplayTitle(); + }); + + anyRuleOption.simulate('click'); + + expect(props.onChange).toHaveBeenCalledTimes(1); + const [newRule] = props.onChange.mock.calls[0]; + expect(newRule).toBeInstanceOf(AnyRule); + expect(newRule.toRaw()).toEqual(new AnyRule([new FieldRule('username', '*')]).toRaw()); + }); + + it('warns when changing group types which would invalidate child rules', async () => { + const props = { + rule: new AllRule([new ExceptAnyRule([new FieldRule('my_custom_field', 'foo*')])]), + allowAdd: true, + ruleDepth: 0, + onChange: jest.fn(), + onDelete: jest.fn(), + }; + const wrapper = mountWithIntl(); + expect(wrapper.find(RuleGroupEditor)).toHaveLength(2); + expect(wrapper.find(FieldRuleEditor)).toHaveLength(1); + expect(wrapper.find(AddRuleButton)).toHaveLength(2); + expect(findTestSubject(wrapper, 'deleteRuleGroupButton')).toHaveLength(2); + + const anyRule = new AnyRule(); + + findTestSubject(wrapper, 'ruleGroupTitle') + .first() + .simulate('click'); + await nextTick(); + wrapper.update(); + + const anyRuleOption = wrapper.find(EuiContextMenuItem).filterWhere(menuItem => { + return menuItem.text() === anyRule.getDisplayTitle(); + }); + + anyRuleOption.simulate('click'); + + expect(props.onChange).toHaveBeenCalledTimes(0); + expect(findTestSubject(wrapper, 'confirmRuleChangeModal')).toHaveLength(1); + findTestSubject(wrapper, 'confirmModalConfirmButton').simulate('click'); + + expect(props.onChange).toHaveBeenCalledTimes(1); + const [newRule] = props.onChange.mock.calls[0]; + expect(newRule).toBeInstanceOf(AnyRule); + + // new rule should a defaulted field sub rule, as the existing rules are not valid for the new type + expect(newRule.toRaw()).toEqual(new AnyRule([new FieldRule('username', '*')]).toRaw()); + }); + + it('does not change groups when canceling the confirmation', async () => { + const props = { + rule: new AllRule([new ExceptAnyRule([new FieldRule('username', '*')])]), + allowAdd: true, + ruleDepth: 0, + onChange: jest.fn(), + onDelete: jest.fn(), + }; + const wrapper = mountWithIntl(); + expect(wrapper.find(RuleGroupEditor)).toHaveLength(2); + expect(wrapper.find(FieldRuleEditor)).toHaveLength(1); + expect(wrapper.find(AddRuleButton)).toHaveLength(2); + expect(findTestSubject(wrapper, 'deleteRuleGroupButton')).toHaveLength(2); + + const anyRule = new AnyRule(); + + findTestSubject(wrapper, 'ruleGroupTitle') + .first() + .simulate('click'); + await nextTick(); + wrapper.update(); + + const anyRuleOption = wrapper.find(EuiContextMenuItem).filterWhere(menuItem => { + return menuItem.text() === anyRule.getDisplayTitle(); + }); + + anyRuleOption.simulate('click'); + + expect(props.onChange).toHaveBeenCalledTimes(0); + expect(findTestSubject(wrapper, 'confirmRuleChangeModal')).toHaveLength(1); + findTestSubject(wrapper, 'confirmModalCancelButton').simulate('click'); + + expect(props.onChange).toHaveBeenCalledTimes(0); + }); + + it('hides the add rule button when instructed to', () => { + const props = { + rule: new AllRule([]), + allowAdd: false, + ruleDepth: 0, + onChange: jest.fn(), + onDelete: jest.fn(), + }; + const wrapper = shallowWithIntl(); + expect(wrapper.find(AddRuleButton)).toHaveLength(0); + }); +}); diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/rule_group_editor.tsx b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/rule_group_editor.tsx new file mode 100644 index 0000000000000..6fb33db179e8a --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/rule_group_editor.tsx @@ -0,0 +1,136 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { Component, Fragment } from 'react'; +import { + EuiPanel, + EuiFlexGroup, + EuiFlexItem, + EuiHorizontalRule, + EuiButtonEmpty, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { AddRuleButton } from './add_rule_button'; +import { RuleGroupTitle } from './rule_group_title'; +import { FieldRuleEditor } from './field_rule_editor'; +import { RuleGroup, Rule, FieldRule } from '../../../model'; +import { isRuleGroup } from '../../services/is_rule_group'; + +interface Props { + rule: RuleGroup; + allowAdd: boolean; + parentRule?: RuleGroup; + ruleDepth: number; + onChange: (rule: RuleGroup) => void; + onDelete: () => void; +} +export class RuleGroupEditor extends Component { + public render() { + return ( + + + + + + + + + + + + + + + {this.renderSubRules()} + {this.props.allowAdd && ( + + + + )} + + + ); + } + + private renderSubRules = () => { + return this.props.rule.getRules().map((subRule, subRuleIndex, rules) => { + const isLastRule = subRuleIndex === rules.length - 1; + const divider = isLastRule ? null : ( + + + + ); + + if (isRuleGroup(subRule)) { + return ( + + + { + const updatedRule = this.props.rule.clone() as RuleGroup; + updatedRule.replaceRule(subRuleIndex, updatedSubRule); + this.props.onChange(updatedRule); + }} + onDelete={() => { + const updatedRule = this.props.rule.clone() as RuleGroup; + updatedRule.removeRule(subRuleIndex); + this.props.onChange(updatedRule); + }} + /> + + {divider} + + ); + } + + return ( + + + { + const updatedRule = this.props.rule.clone() as RuleGroup; + updatedRule.replaceRule(subRuleIndex, updatedSubRule); + this.props.onChange(updatedRule); + }} + onDelete={() => { + const updatedRule = this.props.rule.clone() as RuleGroup; + updatedRule.removeRule(subRuleIndex); + this.props.onChange(updatedRule); + }} + /> + + {divider} + + ); + }); + }; + + private onAddRuleClick = (newRule: Rule) => { + const updatedRule = this.props.rule.clone() as RuleGroup; + updatedRule.addRule(newRule); + this.props.onChange(updatedRule); + }; +} diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/rule_group_title.tsx b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/rule_group_title.tsx new file mode 100644 index 0000000000000..e46893afd4d86 --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/rule_group_title.tsx @@ -0,0 +1,143 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useState } from 'react'; +import { + EuiPopover, + EuiContextMenuPanel, + EuiContextMenuItem, + EuiLink, + EuiIcon, + EuiOverlayMask, + EuiConfirmModal, +} from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { + RuleGroup, + AllRule, + AnyRule, + ExceptAllRule, + ExceptAnyRule, + FieldRule, +} from '../../../model'; + +interface Props { + rule: RuleGroup; + readonly?: boolean; + parentRule?: RuleGroup; + onChange: (rule: RuleGroup) => void; +} + +const rules = [new AllRule(), new AnyRule()]; +const exceptRules = [new ExceptAllRule(), new ExceptAnyRule()]; + +export const RuleGroupTitle = (props: Props) => { + const [isMenuOpen, setIsMenuOpen] = useState(false); + + const [showConfirmChangeModal, setShowConfirmChangeModal] = useState(false); + const [pendingNewRule, setPendingNewRule] = useState(null); + + const canUseExcept = props.parentRule && props.parentRule.canContainRules(exceptRules); + + const availableRuleTypes = [...rules, ...(canUseExcept ? exceptRules : [])]; + + const onChange = (newRule: RuleGroup) => { + const currentSubRules = props.rule.getRules(); + const areSubRulesValid = newRule.canContainRules(currentSubRules); + if (areSubRulesValid) { + const clone = newRule.clone() as RuleGroup; + currentSubRules.forEach(subRule => clone.addRule(subRule)); + + props.onChange(clone); + setIsMenuOpen(false); + } else { + setPendingNewRule(newRule); + setShowConfirmChangeModal(true); + } + }; + + const changeRuleDiscardingSubRules = (newRule: RuleGroup) => { + // Ensure a default sub rule is present when not carrying over the original sub rules + const newRuleInstance = newRule.clone() as RuleGroup; + if (newRuleInstance.getRules().length === 0) { + newRuleInstance.addRule(new FieldRule('username', '*')); + } + + props.onChange(newRuleInstance); + setIsMenuOpen(false); + }; + + const ruleButton = ( + setIsMenuOpen(!isMenuOpen)} data-test-subj="ruleGroupTitle"> + {props.rule.getDisplayTitle()} + + ); + + const ruleTypeSelector = ( + setIsMenuOpen(false)}> + { + const isSelected = rt.getDisplayTitle() === props.rule.getDisplayTitle(); + const icon = isSelected ? 'check' : 'empty'; + return ( + onChange(rt as RuleGroup)}> + {rt.getDisplayTitle()} + + ); + })} + /> + + ); + + const confirmChangeModal = showConfirmChangeModal ? ( + + + } + onCancel={() => { + setShowConfirmChangeModal(false); + setPendingNewRule(null); + }} + onConfirm={() => { + setShowConfirmChangeModal(false); + changeRuleDiscardingSubRules(pendingNewRule!); + setPendingNewRule(null); + }} + cancelButtonText={ + + } + confirmButtonText={ + + } + > +

+ +

+
+
+ ) : null; + + return ( +

+ {ruleTypeSelector} + {confirmChangeModal} +

+ ); +}; diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/visual_rule_editor.test.tsx b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/visual_rule_editor.test.tsx new file mode 100644 index 0000000000000..7c63613ee1cc9 --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/visual_rule_editor.test.tsx @@ -0,0 +1,126 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { mountWithIntl } from 'test_utils/enzyme_helpers'; +import { VisualRuleEditor } from './visual_rule_editor'; +import { findTestSubject } from 'test_utils/find_test_subject'; +import { AnyRule, AllRule, FieldRule, ExceptAnyRule, ExceptAllRule } from '../../../model'; +import { RuleGroupEditor } from './rule_group_editor'; +import { FieldRuleEditor } from './field_rule_editor'; + +describe('VisualRuleEditor', () => { + it('renders an empty prompt when no rules are defined', () => { + const props = { + rules: null, + maxDepth: 0, + onSwitchEditorMode: jest.fn(), + onChange: jest.fn(), + }; + const wrapper = mountWithIntl(); + + findTestSubject(wrapper, 'roleMappingsAddRuleButton').simulate('click'); + expect(props.onChange).toHaveBeenCalledTimes(1); + const [newRule] = props.onChange.mock.calls[0]; + expect(newRule.toRaw()).toEqual({ + all: [{ field: { username: '*' } }], + }); + }); + + it('adds a rule group when the "Add rules" button is clicked', () => { + const props = { + rules: null, + maxDepth: 0, + onSwitchEditorMode: jest.fn(), + onChange: jest.fn(), + }; + const wrapper = mountWithIntl(); + expect(findTestSubject(wrapper, 'roleMappingsNoRulesDefined')).toHaveLength(1); + expect(findTestSubject(wrapper, 'roleMappingsRulesTooComplex')).toHaveLength(0); + }); + + it('clicking the add button when no rules are defined populates an initial rule set', () => { + const props = { + rules: null, + maxDepth: 0, + onSwitchEditorMode: jest.fn(), + onChange: jest.fn(), + }; + const wrapper = mountWithIntl(); + findTestSubject(wrapper, 'roleMappingsAddRuleButton').simulate('click'); + + expect(props.onChange).toHaveBeenCalledTimes(1); + const [newRule] = props.onChange.mock.calls[0]; + expect(newRule).toBeInstanceOf(AllRule); + expect(newRule.toRaw()).toEqual({ + all: [ + { + field: { + username: '*', + }, + }, + ], + }); + }); + + it('renders a nested rule set', () => { + const props = { + rules: new AllRule([ + new AnyRule([new FieldRule('username', '*')]), + new ExceptAnyRule([ + new FieldRule('metadata.foo.bar', '*'), + new AllRule([new FieldRule('realm', 'special-one')]), + ]), + new ExceptAllRule([new FieldRule('realm', '*')]), + ]), + maxDepth: 4, + onSwitchEditorMode: jest.fn(), + onChange: jest.fn(), + }; + const wrapper = mountWithIntl(); + + expect(wrapper.find(RuleGroupEditor)).toHaveLength(5); + expect(wrapper.find(FieldRuleEditor)).toHaveLength(4); + expect(findTestSubject(wrapper, 'roleMappingsRulesTooComplex')).toHaveLength(0); + }); + + it('warns when the rule set is too complex', () => { + const props = { + rules: new AllRule([ + new AnyRule([ + new AllRule([ + new AnyRule([ + new AllRule([ + new AnyRule([ + new AllRule([ + new AnyRule([ + new AllRule([ + new AnyRule([ + new AllRule([ + new AnyRule([ + new AnyRule([ + new AllRule([new AnyRule([new FieldRule('username', '*')])]), + ]), + ]), + ]), + ]), + ]), + ]), + ]), + ]), + ]), + ]), + ]), + ]), + ]), + maxDepth: 11, + onSwitchEditorMode: jest.fn(), + onChange: jest.fn(), + }; + const wrapper = mountWithIntl(); + expect(findTestSubject(wrapper, 'roleMappingsRulesTooComplex')).toHaveLength(1); + }); +}); diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/visual_rule_editor.tsx b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/visual_rule_editor.tsx new file mode 100644 index 0000000000000..214c583189fb8 --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/components/rule_editor_panel/visual_rule_editor.tsx @@ -0,0 +1,143 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { Component, Fragment } from 'react'; +import { EuiEmptyPrompt, EuiCallOut, EuiSpacer, EuiButton } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { FieldRuleEditor } from './field_rule_editor'; +import { RuleGroupEditor } from './rule_group_editor'; +import { VISUAL_MAX_RULE_DEPTH } from '../../services/role_mapping_constants'; +import { Rule, FieldRule, RuleGroup, AllRule } from '../../../model'; +import { isRuleGroup } from '../../services/is_rule_group'; + +interface Props { + rules: Rule | null; + maxDepth: number; + onChange: (rules: Rule | null) => void; + onSwitchEditorMode: () => void; +} + +export class VisualRuleEditor extends Component { + public render() { + if (this.props.rules) { + const rules = this.renderRule(this.props.rules, this.onRuleChange); + return ( + + {this.getRuleDepthWarning()} + {rules} + + ); + } + + return ( + + + + } + titleSize="s" + body={ +
+ +
+ } + data-test-subj="roleMappingsNoRulesDefined" + actions={ + { + this.props.onChange(new AllRule([new FieldRule('username', '*')])); + }} + > + + + } + /> + ); + } + + private canUseVisualEditor = () => this.props.maxDepth < VISUAL_MAX_RULE_DEPTH; + + private getRuleDepthWarning = () => { + if (this.canUseVisualEditor()) { + return null; + } + return ( + + + } + data-test-subj="roleMappingsRulesTooComplex" + > +

+ +

+ + + + +
+ +
+ ); + }; + + private onRuleChange = (updatedRule: Rule) => { + this.props.onChange(updatedRule); + }; + + private onRuleDelete = () => { + this.props.onChange(null); + }; + + private renderRule = (rule: Rule, onChange: (updatedRule: Rule) => void) => { + return this.getEditorForRuleType(rule, onChange); + }; + + private getEditorForRuleType(rule: Rule, onChange: (updatedRule: Rule) => void) { + if (isRuleGroup(rule)) { + return ( + onChange(value)} + onDelete={this.onRuleDelete} + /> + ); + } + return ( + onChange(value)} + onDelete={this.onRuleDelete} + /> + ); + } +} diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/edit_role_mapping.html b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/edit_role_mapping.html new file mode 100644 index 0000000000000..ca8ab9c35c49b --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/edit_role_mapping.html @@ -0,0 +1,3 @@ + +
+ diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/index.tsx b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/index.tsx new file mode 100644 index 0000000000000..b064a4dc50a22 --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/index.tsx @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React from 'react'; +import { render, unmountComponentAtNode } from 'react-dom'; +import routes from 'ui/routes'; +import { I18nContext } from 'ui/i18n'; +import { npSetup } from 'ui/new_platform'; +import { RoleMappingsAPI } from '../../../../lib/role_mappings_api'; +// @ts-ignore +import template from './edit_role_mapping.html'; +import { CREATE_ROLE_MAPPING_PATH } from '../../management_urls'; +import { getEditRoleMappingBreadcrumbs } from '../../breadcrumbs'; +import { EditRoleMappingPage } from './components'; + +routes.when(`${CREATE_ROLE_MAPPING_PATH}/:name?`, { + template, + k7Breadcrumbs: getEditRoleMappingBreadcrumbs, + controller($scope, $route) { + $scope.$$postDigest(() => { + const domNode = document.getElementById('editRoleMappingReactRoot'); + + const { name } = $route.current.params; + + render( + + + , + domNode + ); + + // unmount react on controller destroy + $scope.$on('$destroy', () => { + if (domNode) { + unmountComponentAtNode(domNode); + } + }); + }); + }, +}); diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/services/is_rule_group.ts b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/services/is_rule_group.ts new file mode 100644 index 0000000000000..60a879c6c29df --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/services/is_rule_group.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Rule, FieldRule } from '../../model'; + +export function isRuleGroup(rule: Rule) { + return !(rule instanceof FieldRule); +} diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/services/role_mapping_constants.ts b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/services/role_mapping_constants.ts new file mode 100644 index 0000000000000..28010013c9f4f --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/services/role_mapping_constants.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const VISUAL_MAX_RULE_DEPTH = 5; diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/services/role_mapping_validation.test.ts b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/services/role_mapping_validation.test.ts new file mode 100644 index 0000000000000..9614c4338b631 --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/services/role_mapping_validation.test.ts @@ -0,0 +1,151 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { + validateRoleMappingName, + validateRoleMappingRoles, + validateRoleMappingRoleTemplates, + validateRoleMappingRules, + validateRoleMappingForSave, +} from './role_mapping_validation'; +import { RoleMapping } from '../../../../../../common/model'; + +describe('validateRoleMappingName', () => { + it('requires a value', () => { + expect(validateRoleMappingName({ name: '' } as RoleMapping)).toMatchInlineSnapshot(` + Object { + "error": "Name is required.", + "isInvalid": true, + } + `); + }); +}); + +describe('validateRoleMappingRoles', () => { + it('requires a value', () => { + expect(validateRoleMappingRoles(({ roles: [] } as unknown) as RoleMapping)) + .toMatchInlineSnapshot(` + Object { + "error": "At least one role is required.", + "isInvalid": true, + } + `); + }); +}); + +describe('validateRoleMappingRoleTemplates', () => { + it('requires a value', () => { + expect(validateRoleMappingRoleTemplates(({ role_templates: [] } as unknown) as RoleMapping)) + .toMatchInlineSnapshot(` + Object { + "error": "At least one role template is required.", + "isInvalid": true, + } + `); + }); +}); + +describe('validateRoleMappingRules', () => { + it('requires at least one rule', () => { + expect(validateRoleMappingRules({ rules: {} } as RoleMapping)).toMatchInlineSnapshot(` + Object { + "error": "At least one rule is required.", + "isInvalid": true, + } + `); + }); + + // more exhaustive testing is done in other unit tests + it('requires rules to be valid', () => { + expect(validateRoleMappingRules(({ rules: { something: [] } } as unknown) as RoleMapping)) + .toMatchInlineSnapshot(` + Object { + "error": "Unknown rule type: something.", + "isInvalid": true, + } + `); + }); +}); + +describe('validateRoleMappingForSave', () => { + it('fails if the role mapping is missing a name', () => { + expect( + validateRoleMappingForSave(({ + enabled: true, + roles: ['superuser'], + rules: { field: { username: '*' } }, + } as unknown) as RoleMapping) + ).toMatchInlineSnapshot(` + Object { + "error": "Name is required.", + "isInvalid": true, + } + `); + }); + + it('fails if the role mapping is missing rules', () => { + expect( + validateRoleMappingForSave(({ + name: 'foo', + enabled: true, + roles: ['superuser'], + rules: {}, + } as unknown) as RoleMapping) + ).toMatchInlineSnapshot(` + Object { + "error": "At least one rule is required.", + "isInvalid": true, + } + `); + }); + + it('fails if the role mapping is missing both roles and templates', () => { + expect( + validateRoleMappingForSave(({ + name: 'foo', + enabled: true, + roles: [], + role_templates: [], + rules: { field: { username: '*' } }, + } as unknown) as RoleMapping) + ).toMatchInlineSnapshot(` + Object { + "error": "At least one role is required.", + "isInvalid": true, + } + `); + }); + + it('validates a correct role mapping using role templates', () => { + expect( + validateRoleMappingForSave(({ + name: 'foo', + enabled: true, + roles: [], + role_templates: [{ template: { id: 'foo' } }], + rules: { field: { username: '*' } }, + } as unknown) as RoleMapping) + ).toMatchInlineSnapshot(` + Object { + "isInvalid": false, + } + `); + }); + + it('validates a correct role mapping using roles', () => { + expect( + validateRoleMappingForSave(({ + name: 'foo', + enabled: true, + roles: ['superuser'], + rules: { field: { username: '*' } }, + } as unknown) as RoleMapping) + ).toMatchInlineSnapshot(` + Object { + "isInvalid": false, + } + `); + }); +}); diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/services/role_mapping_validation.ts b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/services/role_mapping_validation.ts new file mode 100644 index 0000000000000..5916d6fd9e189 --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/services/role_mapping_validation.ts @@ -0,0 +1,93 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import { RoleMapping } from '../../../../../../common/model'; +import { generateRulesFromRaw } from '../../model'; + +interface ValidationResult { + isInvalid: boolean; + error?: string; +} + +export function validateRoleMappingName({ name }: RoleMapping): ValidationResult { + if (!name) { + return invalid( + i18n.translate('xpack.security.role_mappings.validation.invalidName', { + defaultMessage: 'Name is required.', + }) + ); + } + return valid(); +} + +export function validateRoleMappingRoles({ roles }: RoleMapping): ValidationResult { + if (roles && !roles.length) { + return invalid( + i18n.translate('xpack.security.role_mappings.validation.invalidRoles', { + defaultMessage: 'At least one role is required.', + }) + ); + } + return valid(); +} + +export function validateRoleMappingRoleTemplates({ + role_templates: roleTemplates, +}: RoleMapping): ValidationResult { + if (roleTemplates && !roleTemplates.length) { + return invalid( + i18n.translate('xpack.security.role_mappings.validation.invalidRoleTemplates', { + defaultMessage: 'At least one role template is required.', + }) + ); + } + return valid(); +} + +export function validateRoleMappingRules({ rules }: Pick): ValidationResult { + try { + const { rules: parsedRules } = generateRulesFromRaw(rules); + if (!parsedRules) { + return invalid( + i18n.translate('xpack.security.role_mappings.validation.invalidRoleRule', { + defaultMessage: 'At least one rule is required.', + }) + ); + } + } catch (e) { + return invalid(e.message); + } + + return valid(); +} + +export function validateRoleMappingForSave(roleMapping: RoleMapping): ValidationResult { + const { isInvalid: isNameInvalid, error: nameError } = validateRoleMappingName(roleMapping); + const { isInvalid: areRolesInvalid, error: rolesError } = validateRoleMappingRoles(roleMapping); + const { + isInvalid: areRoleTemplatesInvalid, + error: roleTemplatesError, + } = validateRoleMappingRoleTemplates(roleMapping); + + const { isInvalid: areRulesInvalid, error: rulesError } = validateRoleMappingRules(roleMapping); + + const canSave = + !isNameInvalid && (!areRolesInvalid || !areRoleTemplatesInvalid) && !areRulesInvalid; + + if (canSave) { + return valid(); + } + return invalid(nameError || rulesError || rolesError || roleTemplatesError); +} + +function valid() { + return { isInvalid: false }; +} + +function invalid(error?: string) { + return { isInvalid: true, error }; +} diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/services/role_template_type.test.ts b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/services/role_template_type.test.ts new file mode 100644 index 0000000000000..8e1f47a4157ae --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/services/role_template_type.test.ts @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + isStoredRoleTemplate, + isInlineRoleTemplate, + isInvalidRoleTemplate, +} from './role_template_type'; +import { RoleTemplate } from '../../../../../../common/model'; + +describe('#isStoredRoleTemplate', () => { + it('returns true for stored templates, false otherwise', () => { + expect(isStoredRoleTemplate({ template: { id: '' } })).toEqual(true); + expect(isStoredRoleTemplate({ template: { source: '' } })).toEqual(false); + expect(isStoredRoleTemplate({ template: 'asdf' })).toEqual(false); + expect(isStoredRoleTemplate({} as RoleTemplate)).toEqual(false); + }); +}); + +describe('#isInlineRoleTemplate', () => { + it('returns true for inline templates, false otherwise', () => { + expect(isInlineRoleTemplate({ template: { source: '' } })).toEqual(true); + expect(isInlineRoleTemplate({ template: { id: '' } })).toEqual(false); + expect(isInlineRoleTemplate({ template: 'asdf' })).toEqual(false); + expect(isInlineRoleTemplate({} as RoleTemplate)).toEqual(false); + }); +}); + +describe('#isInvalidRoleTemplate', () => { + it('returns true for invalid templates, false otherwise', () => { + expect(isInvalidRoleTemplate({ template: 'asdf' })).toEqual(true); + expect(isInvalidRoleTemplate({} as RoleTemplate)).toEqual(true); + expect(isInvalidRoleTemplate({ template: { source: '' } })).toEqual(false); + expect(isInvalidRoleTemplate({ template: { id: '' } })).toEqual(false); + }); +}); diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/services/role_template_type.ts b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/services/role_template_type.ts new file mode 100644 index 0000000000000..90d8d1a09e587 --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/management/role_mappings/edit_role_mapping/services/role_template_type.ts @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + RoleTemplate, + StoredRoleTemplate, + InlineRoleTemplate, + InvalidRoleTemplate, +} from '../../../../../../common/model'; + +export function isStoredRoleTemplate( + roleMappingTemplate: RoleTemplate +): roleMappingTemplate is StoredRoleTemplate { + return ( + roleMappingTemplate.template != null && + roleMappingTemplate.template.hasOwnProperty('id') && + typeof ((roleMappingTemplate as unknown) as StoredRoleTemplate).template.id === 'string' + ); +} + +export function isInlineRoleTemplate( + roleMappingTemplate: RoleTemplate +): roleMappingTemplate is InlineRoleTemplate { + return ( + roleMappingTemplate.template != null && + roleMappingTemplate.template.hasOwnProperty('source') && + typeof ((roleMappingTemplate as unknown) as InlineRoleTemplate).template.source === 'string' + ); +} + +export function isInvalidRoleTemplate( + roleMappingTemplate: RoleTemplate +): roleMappingTemplate is InvalidRoleTemplate { + return !isStoredRoleTemplate(roleMappingTemplate) && !isInlineRoleTemplate(roleMappingTemplate); +} diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/model/__snapshots__/rule_builder.test.ts.snap b/x-pack/legacy/plugins/security/public/views/management/role_mappings/model/__snapshots__/rule_builder.test.ts.snap new file mode 100644 index 0000000000000..1c61383b951ae --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/management/role_mappings/model/__snapshots__/rule_builder.test.ts.snap @@ -0,0 +1,9 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`generateRulesFromRaw "field" does not support a value of () => null 1`] = `"Invalid value type for field. Expected one of null, string, number, or boolean, but found function ()."`; + +exports[`generateRulesFromRaw "field" does not support a value of [object Object] 1`] = `"Invalid value type for field. Expected one of null, string, number, or boolean, but found object ({})."`; + +exports[`generateRulesFromRaw "field" does not support a value of [object Object],,,() => null 1`] = `"Invalid value type for field. Expected one of null, string, number, or boolean, but found object ([{},null,[],null])."`; + +exports[`generateRulesFromRaw "field" does not support a value of undefined 1`] = `"Invalid value type for field. Expected one of null, string, number, or boolean, but found undefined ()."`; diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/model/all_rule.test.ts b/x-pack/legacy/plugins/security/public/views/management/role_mappings/model/all_rule.test.ts new file mode 100644 index 0000000000000..ddf3b4499f73b --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/management/role_mappings/model/all_rule.test.ts @@ -0,0 +1,64 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { AllRule, AnyRule, FieldRule, ExceptAllRule, ExceptAnyRule, RuleGroup } from '.'; + +describe('All rule', () => { + it('can be constructed without sub rules', () => { + const rule = new AllRule(); + expect(rule.getRules()).toHaveLength(0); + }); + + it('can be constructed with sub rules', () => { + const rule = new AllRule([new AnyRule()]); + expect(rule.getRules()).toHaveLength(1); + }); + + it('can accept rules of any type', () => { + const subRules = [ + new AllRule(), + new AnyRule(), + new FieldRule('username', '*'), + new ExceptAllRule(), + new ExceptAnyRule(), + ]; + + const rule = new AllRule() as RuleGroup; + expect(rule.canContainRules(subRules)).toEqual(true); + subRules.forEach(sr => rule.addRule(sr)); + expect(rule.getRules()).toEqual([...subRules]); + }); + + it('can replace an existing rule', () => { + const rule = new AllRule([new AnyRule()]); + const newRule = new FieldRule('username', '*'); + rule.replaceRule(0, newRule); + expect(rule.getRules()).toEqual([newRule]); + }); + + it('can remove an existing rule', () => { + const rule = new AllRule([new AnyRule()]); + rule.removeRule(0); + expect(rule.getRules()).toHaveLength(0); + }); + + it('can covert itself into a raw representation', () => { + const rule = new AllRule([new AnyRule()]); + expect(rule.toRaw()).toEqual({ + all: [{ any: [] }], + }); + }); + + it('can clone itself', () => { + const subRules = [new AnyRule()]; + const rule = new AllRule(subRules); + const clone = rule.clone(); + + expect(clone.toRaw()).toEqual(rule.toRaw()); + expect(clone.getRules()).toEqual(rule.getRules()); + expect(clone.getRules()).not.toBe(rule.getRules()); + }); +}); diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/model/all_rule.ts b/x-pack/legacy/plugins/security/public/views/management/role_mappings/model/all_rule.ts new file mode 100644 index 0000000000000..ecea27a7fb87f --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/management/role_mappings/model/all_rule.ts @@ -0,0 +1,62 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import { RuleGroup } from './rule_group'; +import { Rule } from './rule'; + +/** + * Represents a group of rules which must all evaluate to true. + */ +export class AllRule extends RuleGroup { + constructor(private rules: Rule[] = []) { + super(); + } + + /** {@see RuleGroup.getRules} */ + public getRules() { + return [...this.rules]; + } + + /** {@see RuleGroup.getDisplayTitle} */ + public getDisplayTitle() { + return i18n.translate('xpack.security.management.editRoleMapping.allRule.displayTitle', { + defaultMessage: 'All are true', + }); + } + + /** {@see RuleGroup.replaceRule} */ + public replaceRule(ruleIndex: number, rule: Rule) { + this.rules.splice(ruleIndex, 1, rule); + } + + /** {@see RuleGroup.removeRule} */ + public removeRule(ruleIndex: number) { + this.rules.splice(ruleIndex, 1); + } + + /** {@see RuleGroup.addRule} */ + public addRule(rule: Rule) { + this.rules.push(rule); + } + + /** {@see RuleGroup.canContainRules} */ + public canContainRules() { + return true; + } + + /** {@see RuleGroup.clone} */ + public clone() { + return new AllRule(this.rules.map(r => r.clone())); + } + + /** {@see RuleGroup.toRaw} */ + public toRaw() { + return { + all: [...this.rules.map(rule => rule.toRaw())], + }; + } +} diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/model/any_rule.test.ts b/x-pack/legacy/plugins/security/public/views/management/role_mappings/model/any_rule.test.ts new file mode 100644 index 0000000000000..767aa075755af --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/management/role_mappings/model/any_rule.test.ts @@ -0,0 +1,65 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { AllRule, AnyRule, FieldRule, ExceptAllRule, ExceptAnyRule, RuleGroup } from '.'; + +describe('Any rule', () => { + it('can be constructed without sub rules', () => { + const rule = new AnyRule(); + expect(rule.getRules()).toHaveLength(0); + }); + + it('can be constructed with sub rules', () => { + const rule = new AnyRule([new AllRule()]); + expect(rule.getRules()).toHaveLength(1); + }); + + it('can accept non-except rules', () => { + const subRules = [new AllRule(), new AnyRule(), new FieldRule('username', '*')]; + + const rule = new AnyRule() as RuleGroup; + expect(rule.canContainRules(subRules)).toEqual(true); + subRules.forEach(sr => rule.addRule(sr)); + expect(rule.getRules()).toEqual([...subRules]); + }); + + it('cannot accept except rules', () => { + const subRules = [new ExceptAllRule(), new ExceptAnyRule()]; + + const rule = new AnyRule() as RuleGroup; + expect(rule.canContainRules(subRules)).toEqual(false); + }); + + it('can replace an existing rule', () => { + const rule = new AnyRule([new AllRule()]); + const newRule = new FieldRule('username', '*'); + rule.replaceRule(0, newRule); + expect(rule.getRules()).toEqual([newRule]); + }); + + it('can remove an existing rule', () => { + const rule = new AnyRule([new AllRule()]); + rule.removeRule(0); + expect(rule.getRules()).toHaveLength(0); + }); + + it('can covert itself into a raw representation', () => { + const rule = new AnyRule([new AllRule()]); + expect(rule.toRaw()).toEqual({ + any: [{ all: [] }], + }); + }); + + it('can clone itself', () => { + const subRules = [new AllRule()]; + const rule = new AnyRule(subRules); + const clone = rule.clone(); + + expect(clone.toRaw()).toEqual(rule.toRaw()); + expect(clone.getRules()).toEqual(rule.getRules()); + expect(clone.getRules()).not.toBe(rule.getRules()); + }); +}); diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/model/any_rule.ts b/x-pack/legacy/plugins/security/public/views/management/role_mappings/model/any_rule.ts new file mode 100644 index 0000000000000..6a4f2eaf1b362 --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/management/role_mappings/model/any_rule.ts @@ -0,0 +1,67 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import { RuleGroup } from './rule_group'; +import { Rule } from './rule'; +import { ExceptAllRule } from './except_all_rule'; +import { ExceptAnyRule } from './except_any_rule'; + +/** + * Represents a group of rules in which at least one must evaluate to true. + */ +export class AnyRule extends RuleGroup { + constructor(private rules: Rule[] = []) { + super(); + } + + /** {@see RuleGroup.getRules} */ + public getRules() { + return [...this.rules]; + } + + /** {@see RuleGroup.getDisplayTitle} */ + public getDisplayTitle() { + return i18n.translate('xpack.security.management.editRoleMapping.anyRule.displayTitle', { + defaultMessage: 'Any are true', + }); + } + + /** {@see RuleGroup.replaceRule} */ + public replaceRule(ruleIndex: number, rule: Rule) { + this.rules.splice(ruleIndex, 1, rule); + } + + /** {@see RuleGroup.removeRule} */ + public removeRule(ruleIndex: number) { + this.rules.splice(ruleIndex, 1); + } + + /** {@see RuleGroup.addRule} */ + public addRule(rule: Rule) { + this.rules.push(rule); + } + + /** {@see RuleGroup.canContainRules} */ + public canContainRules(rules: Rule[]) { + const forbiddenRules = [ExceptAllRule, ExceptAnyRule]; + return rules.every( + candidate => !forbiddenRules.some(forbidden => candidate instanceof forbidden) + ); + } + + /** {@see RuleGroup.clone} */ + public clone() { + return new AnyRule(this.rules.map(r => r.clone())); + } + + /** {@see RuleGroup.toRaw} */ + public toRaw() { + return { + any: [...this.rules.map(rule => rule.toRaw())], + }; + } +} diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/model/except_all_rule.test.ts b/x-pack/legacy/plugins/security/public/views/management/role_mappings/model/except_all_rule.test.ts new file mode 100644 index 0000000000000..7a00e5b19638f --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/management/role_mappings/model/except_all_rule.test.ts @@ -0,0 +1,64 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { AllRule, AnyRule, FieldRule, ExceptAllRule, ExceptAnyRule, RuleGroup } from '.'; + +describe('Except All rule', () => { + it('can be constructed without sub rules', () => { + const rule = new ExceptAllRule(); + expect(rule.getRules()).toHaveLength(0); + }); + + it('can be constructed with sub rules', () => { + const rule = new ExceptAllRule([new AnyRule()]); + expect(rule.getRules()).toHaveLength(1); + }); + + it('can accept rules of any type', () => { + const subRules = [ + new AllRule(), + new AnyRule(), + new FieldRule('username', '*'), + new ExceptAllRule(), + new ExceptAnyRule(), + ]; + + const rule = new ExceptAllRule() as RuleGroup; + expect(rule.canContainRules(subRules)).toEqual(true); + subRules.forEach(sr => rule.addRule(sr)); + expect(rule.getRules()).toEqual([...subRules]); + }); + + it('can replace an existing rule', () => { + const rule = new ExceptAllRule([new AnyRule()]); + const newRule = new FieldRule('username', '*'); + rule.replaceRule(0, newRule); + expect(rule.getRules()).toEqual([newRule]); + }); + + it('can remove an existing rule', () => { + const rule = new ExceptAllRule([new AnyRule()]); + rule.removeRule(0); + expect(rule.getRules()).toHaveLength(0); + }); + + it('can covert itself into a raw representation', () => { + const rule = new ExceptAllRule([new AnyRule()]); + expect(rule.toRaw()).toEqual({ + except: { all: [{ any: [] }] }, + }); + }); + + it('can clone itself', () => { + const subRules = [new AllRule()]; + const rule = new ExceptAllRule(subRules); + const clone = rule.clone(); + + expect(clone.toRaw()).toEqual(rule.toRaw()); + expect(clone.getRules()).toEqual(rule.getRules()); + expect(clone.getRules()).not.toBe(rule.getRules()); + }); +}); diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/model/except_all_rule.ts b/x-pack/legacy/plugins/security/public/views/management/role_mappings/model/except_all_rule.ts new file mode 100644 index 0000000000000..a67c2622a2533 --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/management/role_mappings/model/except_all_rule.ts @@ -0,0 +1,66 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import { RuleGroup } from './rule_group'; +import { Rule } from './rule'; + +/** + * Represents a group of rules in which at least one must evaluate to false. + */ +export class ExceptAllRule extends RuleGroup { + constructor(private rules: Rule[] = []) { + super(); + } + + /** {@see RuleGroup.getRules} */ + public getRules() { + return [...this.rules]; + } + + /** {@see RuleGroup.getDisplayTitle} */ + public getDisplayTitle() { + return i18n.translate('xpack.security.management.editRoleMapping.exceptAllRule.displayTitle', { + defaultMessage: 'Any are false', + }); + } + + /** {@see RuleGroup.replaceRule} */ + public replaceRule(ruleIndex: number, rule: Rule) { + this.rules.splice(ruleIndex, 1, rule); + } + + /** {@see RuleGroup.removeRule} */ + public removeRule(ruleIndex: number) { + this.rules.splice(ruleIndex, 1); + } + + /** {@see RuleGroup.addRule} */ + public addRule(rule: Rule) { + this.rules.push(rule); + } + + /** {@see RuleGroup.canContainRules} */ + public canContainRules() { + return true; + } + + /** {@see RuleGroup.clone} */ + public clone() { + return new ExceptAllRule(this.rules.map(r => r.clone())); + } + + /** {@see RuleGroup.toRaw} */ + public toRaw() { + const rawRule = { + all: [...this.rules.map(rule => rule.toRaw())], + }; + + return { + except: rawRule, + }; + } +} diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/model/except_any_rule.test.ts b/x-pack/legacy/plugins/security/public/views/management/role_mappings/model/except_any_rule.test.ts new file mode 100644 index 0000000000000..e4e182ce88d8d --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/management/role_mappings/model/except_any_rule.test.ts @@ -0,0 +1,65 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { AllRule, AnyRule, FieldRule, ExceptAllRule, ExceptAnyRule, RuleGroup } from '.'; + +describe('Except Any rule', () => { + it('can be constructed without sub rules', () => { + const rule = new ExceptAnyRule(); + expect(rule.getRules()).toHaveLength(0); + }); + + it('can be constructed with sub rules', () => { + const rule = new ExceptAnyRule([new AllRule()]); + expect(rule.getRules()).toHaveLength(1); + }); + + it('can accept non-except rules', () => { + const subRules = [new AllRule(), new AnyRule(), new FieldRule('username', '*')]; + + const rule = new ExceptAnyRule() as RuleGroup; + expect(rule.canContainRules(subRules)).toEqual(true); + subRules.forEach(sr => rule.addRule(sr)); + expect(rule.getRules()).toEqual([...subRules]); + }); + + it('cannot accept except rules', () => { + const subRules = [new ExceptAllRule(), new ExceptAnyRule()]; + + const rule = new ExceptAnyRule() as RuleGroup; + expect(rule.canContainRules(subRules)).toEqual(false); + }); + + it('can replace an existing rule', () => { + const rule = new ExceptAnyRule([new AllRule()]); + const newRule = new FieldRule('username', '*'); + rule.replaceRule(0, newRule); + expect(rule.getRules()).toEqual([newRule]); + }); + + it('can remove an existing rule', () => { + const rule = new ExceptAnyRule([new AllRule()]); + rule.removeRule(0); + expect(rule.getRules()).toHaveLength(0); + }); + + it('can covert itself into a raw representation', () => { + const rule = new ExceptAnyRule([new AllRule()]); + expect(rule.toRaw()).toEqual({ + except: { any: [{ all: [] }] }, + }); + }); + + it('can clone itself', () => { + const subRules = [new AllRule()]; + const rule = new ExceptAnyRule(subRules); + const clone = rule.clone(); + + expect(clone.toRaw()).toEqual(rule.toRaw()); + expect(clone.getRules()).toEqual(rule.getRules()); + expect(clone.getRules()).not.toBe(rule.getRules()); + }); +}); diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/model/except_any_rule.ts b/x-pack/legacy/plugins/security/public/views/management/role_mappings/model/except_any_rule.ts new file mode 100644 index 0000000000000..12ec3fe85b80d --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/management/role_mappings/model/except_any_rule.ts @@ -0,0 +1,70 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import { RuleGroup } from './rule_group'; +import { Rule } from './rule'; +import { ExceptAllRule } from './except_all_rule'; + +/** + * Represents a group of rules in which none can evaluate to true (all must evaluate to false). + */ +export class ExceptAnyRule extends RuleGroup { + constructor(private rules: Rule[] = []) { + super(); + } + + /** {@see RuleGroup.getRules} */ + public getRules() { + return [...this.rules]; + } + + /** {@see RuleGroup.getDisplayTitle} */ + public getDisplayTitle() { + return i18n.translate('xpack.security.management.editRoleMapping.exceptAnyRule.displayTitle', { + defaultMessage: 'All are false', + }); + } + + /** {@see RuleGroup.replaceRule} */ + public replaceRule(ruleIndex: number, rule: Rule) { + this.rules.splice(ruleIndex, 1, rule); + } + + /** {@see RuleGroup.removeRule} */ + public removeRule(ruleIndex: number) { + this.rules.splice(ruleIndex, 1); + } + + /** {@see RuleGroup.addRule} */ + public addRule(rule: Rule) { + this.rules.push(rule); + } + + /** {@see RuleGroup.canContainRules} */ + public canContainRules(rules: Rule[]) { + const forbiddenRules = [ExceptAllRule, ExceptAnyRule]; + return rules.every( + candidate => !forbiddenRules.some(forbidden => candidate instanceof forbidden) + ); + } + + /** {@see RuleGroup.clone} */ + public clone() { + return new ExceptAnyRule(this.rules.map(r => r.clone())); + } + + /** {@see RuleGroup.toRaw} */ + public toRaw() { + const rawRule = { + any: [...this.rules.map(rule => rule.toRaw())], + }; + + return { + except: rawRule, + }; + } +} diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/model/field_rule.test.ts b/x-pack/legacy/plugins/security/public/views/management/role_mappings/model/field_rule.test.ts new file mode 100644 index 0000000000000..3447e81707002 --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/management/role_mappings/model/field_rule.test.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { FieldRule } from '.'; + +describe('FieldRule', () => { + ['*', 1, null, true, false].forEach(value => { + it(`can convert itself to raw form with a single value of ${value}`, () => { + const rule = new FieldRule('username', value); + expect(rule.toRaw()).toEqual({ + field: { + username: value, + }, + }); + }); + }); + + it('can convert itself to raw form with an array of values', () => { + const values = ['*', 1, null, true, false]; + const rule = new FieldRule('username', values); + const raw = rule.toRaw(); + expect(raw).toEqual({ + field: { + username: ['*', 1, null, true, false], + }, + }); + + // shoud not be the same array instance + expect(raw.field.username).not.toBe(values); + }); + + it('can clone itself', () => { + const values = ['*', 1, null]; + const rule = new FieldRule('username', values); + + const clone = rule.clone(); + expect(clone.field).toEqual(rule.field); + expect(clone.value).toEqual(rule.value); + expect(clone.value).not.toBe(rule.value); + expect(clone.toRaw()).toEqual(rule.toRaw()); + }); +}); diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/model/field_rule.ts b/x-pack/legacy/plugins/security/public/views/management/role_mappings/model/field_rule.ts new file mode 100644 index 0000000000000..3e6a0e1e7ecb3 --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/management/role_mappings/model/field_rule.ts @@ -0,0 +1,47 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import { Rule } from './rule'; + +/** The allowed types for field rule values */ +export type FieldRuleValue = + | string + | number + | null + | boolean + | Array; + +/** + * Represents a single field rule. + * Ex: "username = 'foo'" + */ +export class FieldRule extends Rule { + constructor(public readonly field: string, public readonly value: FieldRuleValue) { + super(); + } + + /** {@see Rule.getDisplayTitle} */ + public getDisplayTitle() { + return i18n.translate('xpack.security.management.editRoleMapping.fieldRule.displayTitle', { + defaultMessage: 'The following is true', + }); + } + + /** {@see Rule.clone} */ + public clone() { + return new FieldRule(this.field, Array.isArray(this.value) ? [...this.value] : this.value); + } + + /** {@see Rule.toRaw} */ + public toRaw() { + return { + field: { + [this.field]: Array.isArray(this.value) ? [...this.value] : this.value, + }, + }; + } +} diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/model/index.ts b/x-pack/legacy/plugins/security/public/views/management/role_mappings/model/index.ts new file mode 100644 index 0000000000000..cbc970f02b03e --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/management/role_mappings/model/index.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { AllRule } from './all_rule'; +export { AnyRule } from './any_rule'; +export { Rule } from './rule'; +export { RuleGroup } from './rule_group'; +export { ExceptAllRule } from './except_all_rule'; +export { ExceptAnyRule } from './except_any_rule'; +export { FieldRule, FieldRuleValue } from './field_rule'; +export { generateRulesFromRaw } from './rule_builder'; +export { RuleBuilderError } from './rule_builder_error'; diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/model/rule.ts b/x-pack/legacy/plugins/security/public/views/management/role_mappings/model/rule.ts new file mode 100644 index 0000000000000..5cab2f1736e94 --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/management/role_mappings/model/rule.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/** + * Represents a Role Mapping rule. + */ +export abstract class Rule { + /** + * Converts this rule into a raw object for use in the persisted Role Mapping. + */ + abstract toRaw(): Record; + + /** + * The display title for this rule. + */ + abstract getDisplayTitle(): string; + + /** + * Returns a new instance of this rule. + */ + abstract clone(): Rule; +} diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/model/rule_builder.test.ts b/x-pack/legacy/plugins/security/public/views/management/role_mappings/model/rule_builder.test.ts new file mode 100644 index 0000000000000..ebd48f6d15d99 --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/management/role_mappings/model/rule_builder.test.ts @@ -0,0 +1,343 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { generateRulesFromRaw, FieldRule } from '.'; +import { RoleMapping } from '../../../../../common/model'; +import { RuleBuilderError } from './rule_builder_error'; + +describe('generateRulesFromRaw', () => { + it('returns null for an empty rule set', () => { + expect(generateRulesFromRaw({})).toEqual({ + rules: null, + maxDepth: 0, + }); + }); + + it('returns a correctly parsed rule set', () => { + const rawRules: RoleMapping['rules'] = { + all: [ + { + except: { + all: [ + { + field: { username: '*' }, + }, + ], + }, + }, + { + any: [ + { + field: { dn: '*' }, + }, + ], + }, + ], + }; + + const { rules, maxDepth } = generateRulesFromRaw(rawRules); + + expect(rules).toMatchInlineSnapshot(` + AllRule { + "rules": Array [ + ExceptAllRule { + "rules": Array [ + FieldRule { + "field": "username", + "value": "*", + }, + ], + }, + AnyRule { + "rules": Array [ + FieldRule { + "field": "dn", + "value": "*", + }, + ], + }, + ], + } + `); + expect(maxDepth).toEqual(3); + }); + + it('does not support multiple rules at the root level', () => { + expect(() => { + generateRulesFromRaw({ + all: [ + { + field: { username: '*' }, + }, + ], + any: [ + { + field: { username: '*' }, + }, + ], + }); + }).toThrowError('Expected a single rule definition, but found 2.'); + }); + + it('provides a rule trace describing the location of the error', () => { + try { + generateRulesFromRaw({ + all: [ + { + field: { username: '*' }, + }, + { + any: [ + { + field: { username: '*' }, + }, + { + except: { field: { username: '*' } }, + }, + ], + }, + ], + }); + throw new Error(`Expected generateRulesFromRaw to throw error.`); + } catch (e) { + if (e instanceof RuleBuilderError) { + expect(e.message).toEqual(`"except" rule can only exist within an "all" rule.`); + expect(e.ruleTrace).toEqual(['all', '[1]', 'any', '[1]', 'except']); + } else { + throw e; + } + } + }); + + it('calculates the max depth of the rule tree', () => { + const rules = { + all: [ + // depth = 1 + { + // depth = 2 + all: [ + // depth = 3 + { + any: [ + // depth == 4 + { field: { username: 'foo' } }, + ], + }, + { except: { field: { username: 'foo' } } }, + ], + }, + { + // depth = 2 + any: [ + { + // depth = 3 + all: [ + { + // depth = 4 + any: [ + { + // depth = 5 + all: [ + { + // depth = 6 + all: [ + // depth = 7 + { + except: { + field: { username: 'foo' }, + }, + }, + ], + }, + ], + }, + ], + }, + ], + }, + ], + }, + ], + }; + + expect(generateRulesFromRaw(rules).maxDepth).toEqual(7); + }); + + describe('"any"', () => { + it('expects an array value', () => { + expect(() => { + generateRulesFromRaw({ + any: { + field: { username: '*' }, + } as any, + }); + }).toThrowError('Expected an array of rules, but found object.'); + }); + + it('expects each entry to be an object with a single property', () => { + expect(() => { + generateRulesFromRaw({ + any: [ + { + any: [{ field: { foo: 'bar' } }], + all: [{ field: { foo: 'bar' } }], + } as any, + ], + }); + }).toThrowError('Expected a single rule definition, but found 2.'); + }); + }); + + describe('"all"', () => { + it('expects an array value', () => { + expect(() => { + generateRulesFromRaw({ + all: { + field: { username: '*' }, + } as any, + }); + }).toThrowError('Expected an array of rules, but found object.'); + }); + + it('expects each entry to be an object with a single property', () => { + expect(() => { + generateRulesFromRaw({ + all: [ + { + field: { username: '*' }, + any: [{ field: { foo: 'bar' } }], + } as any, + ], + }); + }).toThrowError('Expected a single rule definition, but found 2.'); + }); + }); + + describe('"field"', () => { + it(`expects an object value`, () => { + expect(() => { + generateRulesFromRaw({ + field: [ + { + username: '*', + }, + ], + }); + }).toThrowError('Expected an object, but found array.'); + }); + + it(`expects an single property in its object value`, () => { + expect(() => { + generateRulesFromRaw({ + field: { + username: '*', + dn: '*', + }, + }); + }).toThrowError('Expected a single field, but found 2.'); + }); + + it('accepts an array of possible values', () => { + const { rules } = generateRulesFromRaw({ + field: { + username: [0, '*', null, 'foo', true, false], + }, + }); + + expect(rules).toBeInstanceOf(FieldRule); + expect((rules as FieldRule).field).toEqual('username'); + expect((rules as FieldRule).value).toEqual([0, '*', null, 'foo', true, false]); + }); + + [{}, () => null, undefined, [{}, undefined, [], () => null]].forEach(invalidValue => { + it(`does not support a value of ${invalidValue}`, () => { + expect(() => { + generateRulesFromRaw({ + field: { + username: invalidValue, + }, + }); + }).toThrowErrorMatchingSnapshot(); + }); + }); + }); + + describe('"except"', () => { + it(`expects an object value`, () => { + expect(() => { + generateRulesFromRaw({ + all: [ + { + except: [ + { + field: { username: '*' }, + }, + ], + }, + ], + } as any); + }).toThrowError('Expected an object, but found array.'); + }); + + it(`can only be nested inside an "all" clause`, () => { + expect(() => { + generateRulesFromRaw({ + any: [ + { + except: { + field: { + username: '*', + }, + }, + }, + ], + }); + }).toThrowError(`"except" rule can only exist within an "all" rule.`); + + expect(() => { + generateRulesFromRaw({ + except: { + field: { + username: '*', + }, + }, + }); + }).toThrowError(`"except" rule can only exist within an "all" rule.`); + }); + + it('converts an "except field" rule into an equivilent "except all" rule', () => { + expect( + generateRulesFromRaw({ + all: [ + { + except: { + field: { + username: '*', + }, + }, + }, + ], + }) + ).toMatchInlineSnapshot(` + Object { + "maxDepth": 2, + "rules": AllRule { + "rules": Array [ + ExceptAllRule { + "rules": Array [ + FieldRule { + "field": "username", + "value": "*", + }, + ], + }, + ], + }, + } + `); + }); + }); +}); diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/model/rule_builder.ts b/x-pack/legacy/plugins/security/public/views/management/role_mappings/model/rule_builder.ts new file mode 100644 index 0000000000000..fe344b2ae38dd --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/management/role_mappings/model/rule_builder.ts @@ -0,0 +1,203 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import { RoleMapping } from '../../../../../common/model'; +import { FieldRule, FieldRuleValue } from './field_rule'; +import { AllRule } from './all_rule'; +import { AnyRule } from './any_rule'; +import { Rule } from './rule'; +import { ExceptAllRule } from './except_all_rule'; +import { ExceptAnyRule } from './except_any_rule'; +import { RuleBuilderError } from '.'; + +interface RuleBuilderResult { + /** The maximum rule depth within the parsed rule set. */ + maxDepth: number; + + /** The parsed rule set. */ + rules: Rule | null; +} + +/** + * Given a set of raw rules, this constructs a class based tree for consumption by the Role Management UI. + * This also performs validation on the raw rule set, as it is possible to enter raw JSON in the JSONRuleEditor, + * so we have no guarantees that the rule set is valid ahead of time. + * + * @param rawRules the raw rules to translate. + */ +export function generateRulesFromRaw(rawRules: RoleMapping['rules'] = {}): RuleBuilderResult { + return parseRawRules(rawRules, null, [], 0); +} + +function parseRawRules( + rawRules: RoleMapping['rules'], + parentRuleType: string | null, + ruleTrace: string[], + depth: number +): RuleBuilderResult { + const entries = Object.entries(rawRules); + if (!entries.length) { + return { + rules: null, + maxDepth: 0, + }; + } + if (entries.length > 1) { + throw new RuleBuilderError( + i18n.translate('xpack.security.management.editRoleMapping.ruleBuilder.expectSingleRule', { + defaultMessage: `Expected a single rule definition, but found {numberOfRules}.`, + values: { numberOfRules: entries.length }, + }), + ruleTrace + ); + } + + const rule = entries[0]; + const [ruleType, ruleDefinition] = rule; + return createRuleForType(ruleType, ruleDefinition, parentRuleType, ruleTrace, depth + 1); +} + +function createRuleForType( + ruleType: string, + ruleDefinition: any, + parentRuleType: string | null, + ruleTrace: string[] = [], + depth: number +): RuleBuilderResult { + const isRuleNegated = parentRuleType === 'except'; + + const currentRuleTrace = [...ruleTrace, ruleType]; + + switch (ruleType) { + case 'field': { + assertIsObject(ruleDefinition, currentRuleTrace); + + const entries = Object.entries(ruleDefinition); + if (entries.length !== 1) { + throw new RuleBuilderError( + i18n.translate( + 'xpack.security.management.editRoleMapping.ruleBuilder.expectedSingleFieldRule', + { + defaultMessage: `Expected a single field, but found {count}.`, + values: { count: entries.length }, + } + ), + currentRuleTrace + ); + } + + const [field, value] = entries[0] as [string, FieldRuleValue]; + const values = Array.isArray(value) ? value : [value]; + values.forEach(fieldValue => { + const valueType = typeof fieldValue; + if (fieldValue !== null && !['string', 'number', 'boolean'].includes(valueType)) { + throw new RuleBuilderError( + i18n.translate( + 'xpack.security.management.editRoleMapping.ruleBuilder.invalidFieldValueType', + { + defaultMessage: `Invalid value type for field. Expected one of null, string, number, or boolean, but found {valueType} ({value}).`, + values: { valueType, value: JSON.stringify(value) }, + } + ), + currentRuleTrace + ); + } + }); + + const fieldRule = new FieldRule(field, value); + return { + rules: isRuleNegated ? new ExceptAllRule([fieldRule]) : fieldRule, + maxDepth: depth, + }; + } + case 'any': // intentional fall-through to 'all', as validation logic is identical + case 'all': { + if (ruleDefinition != null && !Array.isArray(ruleDefinition)) { + throw new RuleBuilderError( + i18n.translate( + 'xpack.security.management.editRoleMapping.ruleBuilder.expectedArrayForGroupRule', + { + defaultMessage: `Expected an array of rules, but found {type}.`, + values: { type: typeof ruleDefinition }, + } + ), + currentRuleTrace + ); + } + + const subRulesResults = ((ruleDefinition as any[]) || []).map((definition: any, index) => + parseRawRules(definition, ruleType, [...currentRuleTrace, `[${index}]`], depth) + ) as RuleBuilderResult[]; + + const { subRules, maxDepth } = subRulesResults.reduce( + (acc, result) => { + return { + subRules: [...acc.subRules, result.rules!], + maxDepth: Math.max(acc.maxDepth, result.maxDepth), + }; + }, + { subRules: [] as Rule[], maxDepth: 0 } + ); + + if (ruleType === 'all') { + return { + rules: isRuleNegated ? new ExceptAllRule(subRules) : new AllRule(subRules), + maxDepth, + }; + } else { + return { + rules: isRuleNegated ? new ExceptAnyRule(subRules) : new AnyRule(subRules), + maxDepth, + }; + } + } + case 'except': { + assertIsObject(ruleDefinition, currentRuleTrace); + + if (parentRuleType !== 'all') { + throw new RuleBuilderError( + i18n.translate( + 'xpack.security.management.editRoleMapping.ruleBuilder.exceptOnlyInAllRule', + { + defaultMessage: `"except" rule can only exist within an "all" rule.`, + } + ), + currentRuleTrace + ); + } + // subtracting 1 from depth because we don't currently count the "except" level itself as part of the depth calculation + // for the purpose of determining if the rule set is "too complex" for the visual rule editor. + // The "except" rule MUST be nested within an "all" rule type (see validation above), so the depth itself will always be a non-negative number. + return parseRawRules(ruleDefinition || {}, ruleType, currentRuleTrace, depth - 1); + } + default: + throw new RuleBuilderError( + i18n.translate('xpack.security.management.editRoleMapping.ruleBuilder.unknownRuleType', { + defaultMessage: `Unknown rule type: {ruleType}.`, + values: { ruleType }, + }), + currentRuleTrace + ); + } +} + +function assertIsObject(ruleDefinition: any, ruleTrace: string[]) { + let fieldType: string = typeof ruleDefinition; + if (Array.isArray(ruleDefinition)) { + fieldType = 'array'; + } + + if (ruleDefinition && fieldType !== 'object') { + throw new RuleBuilderError( + i18n.translate('xpack.security.management.editRoleMapping.ruleBuilder.expectedObjectError', { + defaultMessage: `Expected an object, but found {type}.`, + values: { type: fieldType }, + }), + ruleTrace + ); + } +} diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/model/rule_builder_error.ts b/x-pack/legacy/plugins/security/public/views/management/role_mappings/model/rule_builder_error.ts new file mode 100644 index 0000000000000..87d73cde00dd6 --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/management/role_mappings/model/rule_builder_error.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/** + * Describes an error during rule building. + * In addition to a user-"friendly" message, this also includes a rule trace, + * which is the "JSON path" where the error occurred. + */ +export class RuleBuilderError extends Error { + constructor(message: string, public readonly ruleTrace: string[]) { + super(message); + + // Set the prototype explicitly, see: + // https://github.com/Microsoft/TypeScript/wiki/Breaking-Changes#extending-built-ins-like-error-array-and-map-may-no-longer-work + Object.setPrototypeOf(this, RuleBuilderError.prototype); + } +} diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/model/rule_group.ts b/x-pack/legacy/plugins/security/public/views/management/role_mappings/model/rule_group.ts new file mode 100644 index 0000000000000..3e1e7fad9b36f --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/management/role_mappings/model/rule_group.ts @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Rule } from './rule'; + +/** + * Represents a catagory of Role Mapping rules which are capable of containing other rules. + */ +export abstract class RuleGroup extends Rule { + /** + * Returns all immediate sub-rules within this group (non-recursive). + */ + abstract getRules(): Rule[]; + + /** + * Replaces the rule at the indicated location. + * @param ruleIndex the location of the rule to replace. + * @param rule the new rule. + */ + abstract replaceRule(ruleIndex: number, rule: Rule): void; + + /** + * Removes the rule at the indicated location. + * @param ruleIndex the location of the rule to remove. + */ + abstract removeRule(ruleIndex: number): void; + + /** + * Adds a rule to this group. + * @param rule the rule to add. + */ + abstract addRule(rule: Rule): void; + + /** + * Determines if the provided rules are allowed to be contained within this group. + * @param rules the rules to test. + */ + abstract canContainRules(rules: Rule[]): boolean; +} diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/role_mappings_grid/components/create_role_mapping_button/create_role_mapping_button.tsx b/x-pack/legacy/plugins/security/public/views/management/role_mappings/role_mappings_grid/components/create_role_mapping_button/create_role_mapping_button.tsx new file mode 100644 index 0000000000000..2342eeb97d03e --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/management/role_mappings/role_mappings_grid/components/create_role_mapping_button/create_role_mapping_button.tsx @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { EuiButton } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { getCreateRoleMappingHref } from '../../../../management_urls'; + +export const CreateRoleMappingButton = () => { + return ( + + + + ); +}; diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/role_mappings_grid/components/create_role_mapping_button/index.ts b/x-pack/legacy/plugins/security/public/views/management/role_mappings/role_mappings_grid/components/create_role_mapping_button/index.ts new file mode 100644 index 0000000000000..417bf50d754e6 --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/management/role_mappings/role_mappings_grid/components/create_role_mapping_button/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { CreateRoleMappingButton } from './create_role_mapping_button'; diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/role_mappings_grid/components/empty_prompt/empty_prompt.tsx b/x-pack/legacy/plugins/security/public/views/management/role_mappings/role_mappings_grid/components/empty_prompt/empty_prompt.tsx new file mode 100644 index 0000000000000..1919d3fbf4785 --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/management/role_mappings/role_mappings_grid/components/empty_prompt/empty_prompt.tsx @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { Fragment } from 'react'; +import { EuiEmptyPrompt } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { CreateRoleMappingButton } from '../create_role_mapping_button'; + +export const EmptyPrompt: React.FunctionComponent<{}> = () => ( + + + + } + body={ + +

+ +

+
+ } + actions={} + data-test-subj="roleMappingsEmptyPrompt" + /> +); diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/role_mappings_grid/components/empty_prompt/index.ts b/x-pack/legacy/plugins/security/public/views/management/role_mappings/role_mappings_grid/components/empty_prompt/index.ts new file mode 100644 index 0000000000000..982e34a0ceed5 --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/management/role_mappings/role_mappings_grid/components/empty_prompt/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { EmptyPrompt } from './empty_prompt'; diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/role_mappings_grid/components/index.ts b/x-pack/legacy/plugins/security/public/views/management/role_mappings/role_mappings_grid/components/index.ts new file mode 100644 index 0000000000000..4bd5df71da446 --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/management/role_mappings/role_mappings_grid/components/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { RoleMappingsGridPage } from './role_mappings_grid_page'; diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/role_mappings_grid/components/role_mappings_grid_page.test.tsx b/x-pack/legacy/plugins/security/public/views/management/role_mappings/role_mappings_grid/components/role_mappings_grid_page.test.tsx new file mode 100644 index 0000000000000..259cdc71e25a2 --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/management/role_mappings/role_mappings_grid/components/role_mappings_grid_page.test.tsx @@ -0,0 +1,182 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { mountWithIntl, nextTick } from 'test_utils/enzyme_helpers'; +import { RoleMappingsGridPage } from '.'; +import { SectionLoading, PermissionDenied, NoCompatibleRealms } from '../../components'; +import { EmptyPrompt } from './empty_prompt'; +import { findTestSubject } from 'test_utils/find_test_subject'; +import { EuiLink } from '@elastic/eui'; +import { RoleMappingsAPI } from '../../../../../lib/role_mappings_api'; +import { act } from '@testing-library/react'; + +describe('RoleMappingsGridPage', () => { + it('renders an empty prompt when no role mappings exist', async () => { + const roleMappingsAPI = ({ + getRoleMappings: jest.fn().mockResolvedValue([]), + checkRoleMappingFeatures: jest.fn().mockResolvedValue({ + canManageRoleMappings: true, + hasCompatibleRealms: true, + }), + } as unknown) as RoleMappingsAPI; + + const wrapper = mountWithIntl(); + expect(wrapper.find(SectionLoading)).toHaveLength(1); + expect(wrapper.find(EmptyPrompt)).toHaveLength(0); + + await nextTick(); + wrapper.update(); + + expect(wrapper.find(SectionLoading)).toHaveLength(0); + expect(wrapper.find(NoCompatibleRealms)).toHaveLength(0); + expect(wrapper.find(EmptyPrompt)).toHaveLength(1); + }); + + it('renders a permission denied message when unauthorized to manage role mappings', async () => { + const roleMappingsAPI = ({ + checkRoleMappingFeatures: jest.fn().mockResolvedValue({ + canManageRoleMappings: false, + hasCompatibleRealms: true, + }), + } as unknown) as RoleMappingsAPI; + + const wrapper = mountWithIntl(); + expect(wrapper.find(SectionLoading)).toHaveLength(1); + expect(wrapper.find(PermissionDenied)).toHaveLength(0); + + await nextTick(); + wrapper.update(); + + expect(wrapper.find(SectionLoading)).toHaveLength(0); + expect(wrapper.find(NoCompatibleRealms)).toHaveLength(0); + expect(wrapper.find(PermissionDenied)).toHaveLength(1); + }); + + it('renders a warning when there are no compatible realms enabled', async () => { + const roleMappingsAPI = ({ + getRoleMappings: jest.fn().mockResolvedValue([ + { + name: 'some realm', + enabled: true, + roles: [], + rules: { field: { username: '*' } }, + }, + ]), + checkRoleMappingFeatures: jest.fn().mockResolvedValue({ + canManageRoleMappings: true, + hasCompatibleRealms: false, + }), + } as unknown) as RoleMappingsAPI; + + const wrapper = mountWithIntl(); + expect(wrapper.find(SectionLoading)).toHaveLength(1); + expect(wrapper.find(NoCompatibleRealms)).toHaveLength(0); + + await nextTick(); + wrapper.update(); + + expect(wrapper.find(SectionLoading)).toHaveLength(0); + expect(wrapper.find(NoCompatibleRealms)).toHaveLength(1); + }); + + it('renders links to mapped roles', async () => { + const roleMappingsAPI = ({ + getRoleMappings: jest.fn().mockResolvedValue([ + { + name: 'some realm', + enabled: true, + roles: ['superuser'], + rules: { field: { username: '*' } }, + }, + ]), + checkRoleMappingFeatures: jest.fn().mockResolvedValue({ + canManageRoleMappings: true, + hasCompatibleRealms: true, + }), + } as unknown) as RoleMappingsAPI; + + const wrapper = mountWithIntl(); + await nextTick(); + wrapper.update(); + + const links = findTestSubject(wrapper, 'roleMappingRoles').find(EuiLink); + expect(links).toHaveLength(1); + expect(links.at(0).props()).toMatchObject({ + href: '#/management/security/roles/edit/superuser', + }); + }); + + it('describes the number of mapped role templates', async () => { + const roleMappingsAPI = ({ + getRoleMappings: jest.fn().mockResolvedValue([ + { + name: 'some realm', + enabled: true, + role_templates: [{}, {}], + rules: { field: { username: '*' } }, + }, + ]), + checkRoleMappingFeatures: jest.fn().mockResolvedValue({ + canManageRoleMappings: true, + hasCompatibleRealms: true, + }), + } as unknown) as RoleMappingsAPI; + + const wrapper = mountWithIntl(); + await nextTick(); + wrapper.update(); + + const templates = findTestSubject(wrapper, 'roleMappingRoles'); + expect(templates).toHaveLength(1); + expect(templates.text()).toEqual(`2 role templates defined`); + }); + + it('allows role mappings to be deleted, refreshing the grid after', async () => { + const roleMappingsAPI = ({ + getRoleMappings: jest.fn().mockResolvedValue([ + { + name: 'some-realm', + enabled: true, + roles: ['superuser'], + rules: { field: { username: '*' } }, + }, + ]), + checkRoleMappingFeatures: jest.fn().mockResolvedValue({ + canManageRoleMappings: true, + hasCompatibleRealms: true, + }), + deleteRoleMappings: jest.fn().mockReturnValue( + Promise.resolve([ + { + name: 'some-realm', + success: true, + }, + ]) + ), + } as unknown) as RoleMappingsAPI; + + const wrapper = mountWithIntl(); + await nextTick(); + wrapper.update(); + + expect(roleMappingsAPI.getRoleMappings).toHaveBeenCalledTimes(1); + expect(roleMappingsAPI.deleteRoleMappings).not.toHaveBeenCalled(); + + findTestSubject(wrapper, `deleteRoleMappingButton-some-realm`).simulate('click'); + expect(findTestSubject(wrapper, 'deleteRoleMappingConfirmationModal')).toHaveLength(1); + + await act(async () => { + findTestSubject(wrapper, 'confirmModalConfirmButton').simulate('click'); + await nextTick(); + wrapper.update(); + }); + + expect(roleMappingsAPI.deleteRoleMappings).toHaveBeenCalledWith(['some-realm']); + // Expect an additional API call to refresh the grid + expect(roleMappingsAPI.getRoleMappings).toHaveBeenCalledTimes(2); + }); +}); diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/role_mappings_grid/components/role_mappings_grid_page.tsx b/x-pack/legacy/plugins/security/public/views/management/role_mappings/role_mappings_grid/components/role_mappings_grid_page.tsx new file mode 100644 index 0000000000000..7b23f2288d1ba --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/management/role_mappings/role_mappings_grid/components/role_mappings_grid_page.tsx @@ -0,0 +1,474 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { Component, Fragment } from 'react'; +import { + EuiBadge, + EuiButton, + EuiButtonIcon, + EuiCallOut, + EuiFlexGroup, + EuiFlexItem, + EuiInMemoryTable, + EuiLink, + EuiPageContent, + EuiPageContentBody, + EuiPageContentHeader, + EuiPageContentHeaderSection, + EuiSpacer, + EuiText, + EuiTitle, + EuiToolTip, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { RoleMapping } from '../../../../../../common/model'; +import { RoleMappingsAPI } from '../../../../../lib/role_mappings_api'; +import { EmptyPrompt } from './empty_prompt'; +import { + NoCompatibleRealms, + DeleteProvider, + PermissionDenied, + SectionLoading, +} from '../../components'; +import { documentationLinks } from '../../services/documentation_links'; +import { + getCreateRoleMappingHref, + getEditRoleMappingHref, + getEditRoleHref, +} from '../../../management_urls'; + +interface Props { + roleMappingsAPI: RoleMappingsAPI; +} + +interface State { + loadState: 'loadingApp' | 'loadingTable' | 'permissionDenied' | 'finished'; + roleMappings: null | RoleMapping[]; + selectedItems: RoleMapping[]; + hasCompatibleRealms: boolean; + error: any; +} + +export class RoleMappingsGridPage extends Component { + constructor(props: any) { + super(props); + this.state = { + loadState: 'loadingApp', + roleMappings: null, + hasCompatibleRealms: true, + selectedItems: [], + error: undefined, + }; + } + + public componentDidMount() { + this.checkPrivileges(); + } + + public render() { + const { loadState, error, roleMappings } = this.state; + + if (loadState === 'permissionDenied') { + return ; + } + + if (loadState === 'loadingApp') { + return ( + + + + + + ); + } + + if (error) { + const { + body: { error: errorTitle, message, statusCode }, + } = error; + + return ( + + + } + color="danger" + iconType="alert" + > + {statusCode}: {errorTitle} - {message} + + + ); + } + + if (loadState === 'finished' && roleMappings && roleMappings.length === 0) { + return ( + + + + ); + } + + return ( + + + + +

+ +

+
+ +

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

+
+
+ + + + + +
+ + + {!this.state.hasCompatibleRealms && ( + <> + + + + )} + {this.renderTable()} + + +
+ ); + } + + private renderTable = () => { + const { roleMappings, selectedItems, loadState } = this.state; + + const message = + loadState === 'loadingTable' ? ( + + ) : ( + undefined + ); + + const sorting = { + sort: { + field: 'name', + direction: 'asc' as any, + }, + }; + + const pagination = { + initialPageSize: 20, + pageSizeOptions: [10, 20, 50], + }; + + const selection = { + onSelectionChange: (newSelectedItems: RoleMapping[]) => { + this.setState({ + selectedItems: newSelectedItems, + }); + }, + }; + + const search = { + toolsLeft: selectedItems.length ? ( + + {deleteRoleMappingsPrompt => { + return ( + deleteRoleMappingsPrompt(selectedItems, this.onRoleMappingsDeleted)} + color="danger" + data-test-subj="bulkDeleteActionButton" + > + + + ); + }} + + ) : ( + undefined + ), + toolsRight: ( + this.reloadRoleMappings()} + data-test-subj="reloadButton" + > + + + ), + box: { + incremental: true, + }, + filters: undefined, + }; + + return ( + { + return { + 'data-test-subj': 'roleMappingRow', + }; + }} + /> + ); + }; + + private getColumnConfig = () => { + const config = [ + { + field: 'name', + name: i18n.translate('xpack.security.management.roleMappings.nameColumnName', { + defaultMessage: 'Name', + }), + sortable: true, + render: (roleMappingName: string) => { + return ( + + {roleMappingName} + + ); + }, + }, + { + field: 'roles', + name: i18n.translate('xpack.security.management.roleMappings.rolesColumnName', { + defaultMessage: 'Roles', + }), + sortable: true, + render: (entry: any, record: RoleMapping) => { + const { roles = [], role_templates: roleTemplates = [] } = record; + if (roleTemplates.length > 0) { + return ( + + {i18n.translate('xpack.security.management.roleMappings.roleTemplates', { + defaultMessage: + '{templateCount, plural, one{# role template} other {# role templates}} defined', + values: { + templateCount: roleTemplates.length, + }, + })} + + ); + } + const roleLinks = roles.map((rolename, index) => { + return ( + + {rolename} + {index === roles.length - 1 ? null : ', '} + + ); + }); + return
{roleLinks}
; + }, + }, + { + field: 'enabled', + name: i18n.translate('xpack.security.management.roleMappings.enabledColumnName', { + defaultMessage: 'Enabled', + }), + sortable: true, + render: (enabled: boolean) => { + if (enabled) { + return ( + + + + ); + } + + return ( + + + + ); + }, + }, + { + name: i18n.translate('xpack.security.management.roleMappings.actionsColumnName', { + defaultMessage: 'Actions', + }), + actions: [ + { + render: (record: RoleMapping) => { + return ( + + + + ); + }, + }, + { + render: (record: RoleMapping) => { + return ( + + + + {deleteRoleMappingPrompt => { + return ( + + + deleteRoleMappingPrompt([record], this.onRoleMappingsDeleted) + } + /> + + ); + }} + + + + ); + }, + }, + ], + }, + ]; + return config; + }; + + private onRoleMappingsDeleted = (roleMappings: string[]): void => { + if (roleMappings.length) { + this.reloadRoleMappings(); + } + }; + + private async checkPrivileges() { + try { + const { + canManageRoleMappings, + hasCompatibleRealms, + } = await this.props.roleMappingsAPI.checkRoleMappingFeatures(); + + this.setState({ + loadState: canManageRoleMappings ? this.state.loadState : 'permissionDenied', + hasCompatibleRealms, + }); + + if (canManageRoleMappings) { + this.loadRoleMappings(); + } + } catch (e) { + this.setState({ error: e, loadState: 'finished' }); + } + } + + private reloadRoleMappings = () => { + this.setState({ roleMappings: [], loadState: 'loadingTable' }); + this.loadRoleMappings(); + }; + + private loadRoleMappings = async () => { + try { + const roleMappings = await this.props.roleMappingsAPI.getRoleMappings(); + this.setState({ roleMappings }); + } catch (e) { + this.setState({ error: e }); + } + + this.setState({ loadState: 'finished' }); + }; +} diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/role_mappings_grid/index.tsx b/x-pack/legacy/plugins/security/public/views/management/role_mappings/role_mappings_grid/index.tsx new file mode 100644 index 0000000000000..9e925d0fa6dc0 --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/management/role_mappings/role_mappings_grid/index.tsx @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React from 'react'; +import { render, unmountComponentAtNode } from 'react-dom'; +import routes from 'ui/routes'; +import { I18nContext } from 'ui/i18n'; +import { npSetup } from 'ui/new_platform'; +import { RoleMappingsAPI } from '../../../../lib/role_mappings_api'; +// @ts-ignore +import template from './role_mappings.html'; +import { ROLE_MAPPINGS_PATH } from '../../management_urls'; +import { getRoleMappingBreadcrumbs } from '../../breadcrumbs'; +import { RoleMappingsGridPage } from './components'; + +routes.when(ROLE_MAPPINGS_PATH, { + template, + k7Breadcrumbs: getRoleMappingBreadcrumbs, + controller($scope) { + $scope.$$postDigest(() => { + const domNode = document.getElementById('roleMappingsGridReactRoot'); + + render( + + + , + domNode + ); + + // unmount react on controller destroy + $scope.$on('$destroy', () => { + if (domNode) { + unmountComponentAtNode(domNode); + } + }); + }); + }, +}); diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/role_mappings_grid/role_mappings.html b/x-pack/legacy/plugins/security/public/views/management/role_mappings/role_mappings_grid/role_mappings.html new file mode 100644 index 0000000000000..cff3b821d132c --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/management/role_mappings/role_mappings_grid/role_mappings.html @@ -0,0 +1,3 @@ + +
+ diff --git a/x-pack/legacy/plugins/security/public/views/management/role_mappings/services/documentation_links.ts b/x-pack/legacy/plugins/security/public/views/management/role_mappings/services/documentation_links.ts new file mode 100644 index 0000000000000..36351f49890a1 --- /dev/null +++ b/x-pack/legacy/plugins/security/public/views/management/role_mappings/services/documentation_links.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ELASTIC_WEBSITE_URL, DOC_LINK_VERSION } from 'ui/documentation_links'; + +class DocumentationLinksService { + private esDocBasePath = `${ELASTIC_WEBSITE_URL}guide/en/elasticsearch/reference/${DOC_LINK_VERSION}`; + + public getRoleMappingDocUrl() { + return `${this.esDocBasePath}/mapping-roles.html`; + } + + public getRoleMappingAPIDocUrl() { + return `${this.esDocBasePath}/security-api-put-role-mapping.html`; + } + + public getRoleMappingTemplateDocUrl() { + return `${this.esDocBasePath}/security-api-put-role-mapping.html#_role_templates`; + } + + public getRoleMappingFieldRulesDocUrl() { + return `${this.esDocBasePath}/role-mapping-resources.html#mapping-roles-rule-field`; + } +} + +export const documentationLinks = new DocumentationLinksService(); diff --git a/x-pack/legacy/plugins/security/public/views/management/users_grid/users.js b/x-pack/legacy/plugins/security/public/views/management/users_grid/users.js index a7115f449ebfd..8d4e0526251d7 100644 --- a/x-pack/legacy/plugins/security/public/views/management/users_grid/users.js +++ b/x-pack/legacy/plugins/security/public/views/management/users_grid/users.js @@ -8,7 +8,6 @@ import React from 'react'; import { render, unmountComponentAtNode } from 'react-dom'; import routes from 'ui/routes'; import template from 'plugins/security/views/management/users_grid/users.html'; -import 'plugins/security/services/shield_user'; import { SECURITY_PATH, USERS_PATH } from '../management_urls'; import { UsersListPage } from './components'; import { UserAPIClient } from '../../../lib/api'; diff --git a/x-pack/legacy/plugins/security/public/views/overwritten_session/overwritten_session.tsx b/x-pack/legacy/plugins/security/public/views/overwritten_session/overwritten_session.tsx index 76088443212b2..fb39c517e1c2c 100644 --- a/x-pack/legacy/plugins/security/public/views/overwritten_session/overwritten_session.tsx +++ b/x-pack/legacy/plugins/security/public/views/overwritten_session/overwritten_session.tsx @@ -10,36 +10,40 @@ import React from 'react'; import { render } from 'react-dom'; import chrome from 'ui/chrome'; import { I18nContext } from 'ui/i18n'; +import { npSetup } from 'ui/new_platform'; +import { SecurityPluginSetup } from '../../../../../../plugins/security/public'; import { AuthenticatedUser } from '../../../common/model'; import { AuthenticationStatePage } from '../../components/authentication_state_page'; chrome .setVisible(false) .setRootTemplate('
') - .setRootController('overwritten_session', ($scope: any, ShieldUser: any) => { + .setRootController('overwritten_session', ($scope: any) => { $scope.$$postDigest(() => { - ShieldUser.getCurrent().$promise.then((user: AuthenticatedUser) => { - const overwrittenSessionPage = ( - - - } - > - - - - - - ); - render(overwrittenSessionPage, document.getElementById('reactOverwrittenSessionRoot')); - }); + ((npSetup.plugins as unknown) as { security: SecurityPluginSetup }).security.authc + .getCurrentUser() + .then((user: AuthenticatedUser) => { + const overwrittenSessionPage = ( + + + } + > + + + + + + ); + render(overwrittenSessionPage, document.getElementById('reactOverwrittenSessionRoot')); + }); }); }); diff --git a/x-pack/legacy/plugins/siem/common/constants.ts b/x-pack/legacy/plugins/siem/common/constants.ts index 4383329fea072..5116416b527a5 100644 --- a/x-pack/legacy/plugins/siem/common/constants.ts +++ b/x-pack/legacy/plugins/siem/common/constants.ts @@ -27,8 +27,6 @@ export const DEFAULT_SEARCH_AFTER_PAGE_SIZE = 100; export const DEFAULT_ANOMALY_SCORE = 'siem:defaultAnomalyScore'; export const DEFAULT_MAX_TABLE_QUERY_SIZE = 10000; export const DEFAULT_SCALE_DATE_FORMAT = 'dateFormat:scaled'; -export const DEFAULT_KBN_VERSION = 'kbnVersion'; -export const DEFAULT_TIMEZONE_BROWSER = 'timezoneBrowser'; export const DEFAULT_FROM = 'now-24h'; export const DEFAULT_TO = 'now'; export const DEFAULT_INTERVAL_PAUSE = true; diff --git a/x-pack/legacy/plugins/siem/cypress/README.md b/x-pack/legacy/plugins/siem/cypress/README.md index fb2b6cd2e3fd3..c9e0d4e18f78f 100644 --- a/x-pack/legacy/plugins/siem/cypress/README.md +++ b/x-pack/legacy/plugins/siem/cypress/README.md @@ -51,10 +51,23 @@ export const USERNAME = '[data-test-subj="loginUsername"]'; We prefer not to mock API responses in most of our smoke tests, but sometimes it's necessary because a test must assert that a specific value is rendered, and it's not possible to derive that value based on the data in the -envrionment where tests are running. +environment where tests are running. Mocked responses API from the server are located in `siem/cypress/fixtures`. +## Speeding up test execution time + +Loading the web page takes a big amount of time, in order to minimize that impact, the following points should be +taken into consideration until another solution is implemented: + +- Don't refresh the page for every test to clean the state of it. +- Instead, group the tests that are similar in different contexts. +- For every context login only once, clean the state between tests if needed without re-loading the page. +- All tests in a spec file must be order-independent. + - If you need to reload the page to make the tests order-independent, consider to create a new context. + +Remember that minimizing the number of times the web page is loaded, we minimize as well the execution time. + ## Authentication When running tests, there are two ways to specify the credentials used to diff --git a/x-pack/legacy/plugins/siem/cypress/integration/lib/events_viewer/selectors.ts b/x-pack/legacy/plugins/siem/cypress/integration/lib/events_viewer/selectors.ts index 0e3717feef7ad..6f7906d7fd791 100644 --- a/x-pack/legacy/plugins/siem/cypress/integration/lib/events_viewer/selectors.ts +++ b/x-pack/legacy/plugins/siem/cypress/integration/lib/events_viewer/selectors.ts @@ -19,6 +19,8 @@ export const HEADER_SUBTITLE = `${EVENTS_VIEWER_PANEL} [data-test-subj="header-p /** The inspect query modal */ export const INSPECT_MODAL = '[data-test-subj="modal-inspect-euiModal"]'; +export const CLOSE_MODAL = '[data-test-subj="modal-inspect-close"]'; + /** The inspect query button that launches the inspect query modal */ export const INSPECT_QUERY = `${EVENTS_VIEWER_PANEL} [data-test-subj="inspect-icon-button"]`; diff --git a/x-pack/legacy/plugins/siem/cypress/integration/lib/fields_browser/helpers.ts b/x-pack/legacy/plugins/siem/cypress/integration/lib/fields_browser/helpers.ts index e3495b6a78127..405c8eb34d6fc 100644 --- a/x-pack/legacy/plugins/siem/cypress/integration/lib/fields_browser/helpers.ts +++ b/x-pack/legacy/plugins/siem/cypress/integration/lib/fields_browser/helpers.ts @@ -42,3 +42,7 @@ export const clickOutsideFieldsBrowser = () => { export const filterFieldsBrowser = (fieldName: string) => { cy.get(FIELDS_BROWSER_FILTER_INPUT).type(fieldName); }; + +export const clearFieldsBrowser = () => { + cy.get(FIELDS_BROWSER_FILTER_INPUT).type('{selectall}{backspace}'); +}; diff --git a/x-pack/legacy/plugins/siem/cypress/integration/lib/logout/index.ts b/x-pack/legacy/plugins/siem/cypress/integration/lib/logout/index.ts deleted file mode 100644 index 7a6c7f71bc98c..0000000000000 --- a/x-pack/legacy/plugins/siem/cypress/integration/lib/logout/index.ts +++ /dev/null @@ -1,15 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export const logout = (): null => { - cy.request({ - method: 'GET', - url: `${Cypress.config().baseUrl}/logout`, - }).then(response => { - expect(response.status).to.eq(200); - }); - return null; -}; diff --git a/x-pack/legacy/plugins/siem/cypress/integration/lib/logout/selectors.ts b/x-pack/legacy/plugins/siem/cypress/integration/lib/logout/selectors.ts deleted file mode 100644 index 8cf015619f4c1..0000000000000 --- a/x-pack/legacy/plugins/siem/cypress/integration/lib/logout/selectors.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -/** The avatar / button that represents the logged-in Kibana user */ -export const USER_MENU = '[data-test-subj="userMenuButton"]'; - -/** Clicking this link logs out the currently logged-in Kibana user */ -export const LOGOUT_LINK = '[data-test-subj="logoutLink"]'; diff --git a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/events_viewer/events_viewer.spec.ts b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/events_viewer/events_viewer.spec.ts index 85878d8225609..1450ee8dc8abf 100644 --- a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/events_viewer/events_viewer.spec.ts +++ b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/events_viewer/events_viewer.spec.ts @@ -10,7 +10,6 @@ import { FIELDS_BROWSER_SELECTED_CATEGORY_TITLE, FIELDS_BROWSER_TITLE, } from '../../lib/fields_browser/selectors'; -import { logout } from '../../lib/logout'; import { HOSTS_PAGE } from '../../lib/urls'; import { loginAndWaitForPage, DEFAULT_TIMEOUT } from '../../lib/util/helpers'; import { @@ -19,6 +18,7 @@ import { filterSearchBar, } from '../../lib/events_viewer/helpers'; import { + CLOSE_MODAL, EVENTS_VIEWER_PANEL, HEADER_SUBTITLE, INSPECT_MODAL, @@ -40,166 +40,162 @@ const defaultHeadersInDefaultEcsCategory = [ ]; describe('Events Viewer', () => { - beforeEach(() => { - loginAndWaitForPage(HOSTS_PAGE); - - clickEventsTab(); - }); - - afterEach(() => { - return logout(); - }); - - it('renders the fields browser with the expected title when the Events Viewer Fields Browser button is clicked', () => { - openEventsViewerFieldsBrowser(); - - cy.get(FIELDS_BROWSER_TITLE) - .invoke('text') - .should('eq', 'Customize Columns'); - }); + context('Fields rendering', () => { + before(() => { + loginAndWaitForPage(HOSTS_PAGE); + clickEventsTab(); + }); - it('closes the fields browser when the user clicks outside of it', () => { - openEventsViewerFieldsBrowser(); + beforeEach(() => { + openEventsViewerFieldsBrowser(); + }); - clickOutsideFieldsBrowser(); + afterEach(() => { + clickOutsideFieldsBrowser(); + cy.get(FIELDS_BROWSER_CONTAINER).should('not.exist'); + }); - cy.get(FIELDS_BROWSER_CONTAINER).should('not.exist'); - }); + it('renders the fields browser with the expected title when the Events Viewer Fields Browser button is clicked', () => { + cy.get(FIELDS_BROWSER_TITLE) + .invoke('text') + .should('eq', 'Customize Columns'); + }); - it('displays the `default ECS` category (by default)', () => { - openEventsViewerFieldsBrowser(); + it('displays the `default ECS` category (by default)', () => { + cy.get(FIELDS_BROWSER_SELECTED_CATEGORY_TITLE) + .invoke('text') + .should('eq', 'default ECS'); + }); - cy.get(FIELDS_BROWSER_SELECTED_CATEGORY_TITLE) - .invoke('text') - .should('eq', 'default ECS'); + it('displays a checked checkbox for all of the default events viewer columns that are also in the default ECS category', () => { + defaultHeadersInDefaultEcsCategory.forEach(header => + cy.get(`[data-test-subj="field-${header.id}-checkbox"]`).should('be.checked') + ); + }); }); - it('displays a checked checkbox for all of the default events viewer columns that are also in the default ECS category', () => { - openEventsViewerFieldsBrowser(); - - defaultHeadersInDefaultEcsCategory.forEach(header => - cy.get(`[data-test-subj="field-${header.id}-checkbox"]`).should('be.checked') - ); - }); + context('Events viewer query modal', () => { + before(() => { + loginAndWaitForPage(HOSTS_PAGE); + clickEventsTab(); + }); - it('removes the message field from the timeline when the user un-checks the field', () => { - const toggleField = 'message'; + after(() => { + cy.get(CLOSE_MODAL).click(); + cy.get(INSPECT_MODAL, { timeout: DEFAULT_TIMEOUT }).should('not.exist'); + }); - cy.get(`${EVENTS_VIEWER_PANEL} [data-test-subj="header-text-${toggleField}"]`).should('exist'); + it('launches the inspect query modal when the inspect button is clicked', () => { + // wait for data to load + cy.get(SERVER_SIDE_EVENT_COUNT, { timeout: DEFAULT_TIMEOUT }) + .should('exist') + .invoke('text', { timeout: DEFAULT_TIMEOUT }) + .should('not.equal', '0'); - openEventsViewerFieldsBrowser(); + cy.get(INSPECT_QUERY, { timeout: DEFAULT_TIMEOUT }) + .should('exist') + .trigger('mousemove', { force: true }) + .click({ force: true }); - cy.get(`${EVENTS_VIEWER_PANEL} [data-test-subj="field-${toggleField}-checkbox"]`).uncheck({ - force: true, + cy.get(INSPECT_MODAL, { timeout: DEFAULT_TIMEOUT }).should('exist'); }); - - clickOutsideFieldsBrowser(); - - cy.get(`${EVENTS_VIEWER_PANEL} [data-test-subj="header-text-${toggleField}"]`).should( - 'not.exist' - ); }); - it('filters the events by applying filter criteria from the search bar at the top of the page', () => { - const filterInput = '4bf34c1c-eaa9-46de-8921-67a4ccc49829'; // this will never match real data + context('Events viewer fields behaviour', () => { + before(() => { + loginAndWaitForPage(HOSTS_PAGE); + clickEventsTab(); + }); - cy.get(HEADER_SUBTITLE) - .invoke('text') - .then(text1 => { - cy.get(HEADER_SUBTITLE) - .invoke('text', { timeout: DEFAULT_TIMEOUT }) - .should('not.equal', 'Showing: 0 events'); + beforeEach(() => { + openEventsViewerFieldsBrowser(); + }); - filterSearchBar(filterInput); + it('adds a field to the events viewer when the user clicks the checkbox', () => { + const filterInput = 'host.geo.c'; + const toggleField = 'host.geo.city_name'; - cy.get(HEADER_SUBTITLE) - .invoke('text') - .should(text2 => { - expect(text1).not.to.eq(text2); - }); - }); - }); + filterFieldsBrowser(filterInput); - it('adds a field to the events viewer when the user clicks the checkbox', () => { - const filterInput = 'host.geo.c'; - const toggleField = 'host.geo.city_name'; + cy.get(`${EVENTS_VIEWER_PANEL} [data-test-subj="header-text-${toggleField}"]`).should( + 'not.exist' + ); - openEventsViewerFieldsBrowser(); - - filterFieldsBrowser(filterInput); + cy.get(`${EVENTS_VIEWER_PANEL} [data-test-subj="field-${toggleField}-checkbox"]`).check({ + force: true, + }); - cy.get(`${EVENTS_VIEWER_PANEL} [data-test-subj="header-text-${toggleField}"]`).should( - 'not.exist' - ); + clickOutsideFieldsBrowser(); - cy.get(`${EVENTS_VIEWER_PANEL} [data-test-subj="field-${toggleField}-checkbox"]`).check({ - force: true, + cy.get(`${EVENTS_VIEWER_PANEL} [data-test-subj="header-text-${toggleField}"]`).should( + 'exist' + ); }); - clickOutsideFieldsBrowser(); + it('resets all fields in the events viewer when `Reset Fields` is clicked', () => { + const filterInput = 'host.geo.c'; + const toggleField = 'host.geo.country_name'; - cy.get(`${EVENTS_VIEWER_PANEL} [data-test-subj="header-text-${toggleField}"]`).should('exist'); - }); + filterFieldsBrowser(filterInput); - it('loads more events when the load more button is clicked', () => { - cy.get(LOCAL_EVENTS_COUNT, { timeout: DEFAULT_TIMEOUT }) - .invoke('text') - .then(text1 => { - cy.get(LOCAL_EVENTS_COUNT) - .invoke('text') - .should('equal', '25'); - - cy.get(LOAD_MORE).click({ force: true }); - - cy.get(LOCAL_EVENTS_COUNT) - .invoke('text') - .should(text2 => { - expect(text1).not.to.eq(text2); - }); - }); - }); + cy.get(`${EVENTS_VIEWER_PANEL} [data-test-subj="header-text-${toggleField}"]`).should( + 'not.exist' + ); - it('launches the inspect query modal when the inspect button is clicked', () => { - // wait for data to load - cy.get(SERVER_SIDE_EVENT_COUNT, { timeout: DEFAULT_TIMEOUT }) - .should('exist') - .invoke('text', { timeout: DEFAULT_TIMEOUT }) - .should('not.equal', '0'); + cy.get(`${EVENTS_VIEWER_PANEL} [data-test-subj="field-${toggleField}-checkbox"]`).check({ + force: true, + }); - cy.get(INSPECT_QUERY, { timeout: DEFAULT_TIMEOUT }) - .should('exist') - .trigger('mousemove', { force: true }) - .click({ force: true }); + cy.get(`${EVENTS_VIEWER_PANEL} [data-test-subj="reset-fields"]`).click({ force: true }); - cy.get(INSPECT_MODAL, { timeout: DEFAULT_TIMEOUT }).should('exist'); + cy.get(`${EVENTS_VIEWER_PANEL} [data-test-subj="header-text-${toggleField}"]`).should( + 'not.exist' + ); + }); }); - it('resets all fields in the events viewer when `Reset Fields` is clicked', () => { - const filterInput = 'host.geo.c'; - const toggleField = 'host.geo.city_name'; - - openEventsViewerFieldsBrowser(); - - filterFieldsBrowser(filterInput); - - cy.get(`${EVENTS_VIEWER_PANEL} [data-test-subj="header-text-${toggleField}"]`).should( - 'not.exist' - ); - - cy.get(`${EVENTS_VIEWER_PANEL} [data-test-subj="field-${toggleField}-checkbox"]`).check({ - force: true, + context('Events behaviour', () => { + before(() => { + loginAndWaitForPage(HOSTS_PAGE); + clickEventsTab(); }); - clickOutsideFieldsBrowser(); + it('filters the events by applying filter criteria from the search bar at the top of the page', () => { + const filterInput = '4bf34c1c-eaa9-46de-8921-67a4ccc49829'; // this will never match real data - cy.get(`${EVENTS_VIEWER_PANEL} [data-test-subj="header-text-${toggleField}"]`).should('exist'); + cy.get(HEADER_SUBTITLE) + .invoke('text') + .then(text1 => { + cy.get(HEADER_SUBTITLE) + .invoke('text', { timeout: DEFAULT_TIMEOUT }) + .should('not.equal', 'Showing: 0 events'); - openEventsViewerFieldsBrowser(); + filterSearchBar(filterInput); - cy.get(`${EVENTS_VIEWER_PANEL} [data-test-subj="reset-fields"]`).click({ force: true }); + cy.get(HEADER_SUBTITLE) + .invoke('text') + .should(text2 => { + expect(text1).not.to.eq(text2); + }); + }); + }); - cy.get(`${EVENTS_VIEWER_PANEL} [data-test-subj="header-text-${toggleField}"]`).should( - 'not.exist' - ); + it('loads more events when the load more button is clicked', () => { + cy.get(LOCAL_EVENTS_COUNT, { timeout: DEFAULT_TIMEOUT }) + .invoke('text') + .then(text1 => { + cy.get(LOCAL_EVENTS_COUNT) + .invoke('text') + .should('equal', '25'); + + cy.get(LOAD_MORE).click({ force: true }); + + cy.get(LOCAL_EVENTS_COUNT) + .invoke('text') + .should(text2 => { + expect(text1).not.to.eq(text2); + }); + }); + }); }); }); diff --git a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/fields_browser/fields_browser.spec.ts b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/fields_browser/fields_browser.spec.ts index dfc5e10893ebb..d1289732b6d7d 100644 --- a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/fields_browser/fields_browser.spec.ts +++ b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/fields_browser/fields_browser.spec.ts @@ -6,6 +6,7 @@ import { drag, drop } from '../../lib/drag_n_drop/helpers'; import { + clearFieldsBrowser, clickOutsideFieldsBrowser, openTimelineFieldsBrowser, populateTimeline, @@ -22,7 +23,6 @@ import { FIELDS_BROWSER_SYSTEM_CATEGORIES_COUNT, FIELDS_BROWSER_TITLE, } from '../../lib/fields_browser/selectors'; -import { logout } from '../../lib/logout'; import { HOSTS_PAGE } from '../../lib/urls'; import { loginAndWaitForPage, DEFAULT_TIMEOUT } from '../../lib/util/helpers'; @@ -38,237 +38,200 @@ const defaultHeaders = [ ]; describe('Fields Browser', () => { - beforeEach(() => { - loginAndWaitForPage(HOSTS_PAGE); - }); - - afterEach(() => { - return logout(); - }); - - it('renders the fields browser with the expected title when the Fields button is clicked', () => { - populateTimeline(); - - openTimelineFieldsBrowser(); - - cy.get(FIELDS_BROWSER_TITLE) - .invoke('text') - .should('eq', 'Customize Columns'); - }); - - it('closes the fields browser when the user clicks outside of it', () => { - populateTimeline(); - - openTimelineFieldsBrowser(); - - clickOutsideFieldsBrowser(); - - cy.get(FIELDS_BROWSER_CONTAINER).should('not.exist'); - }); - - it('displays the `default ECS` category (by default)', () => { - populateTimeline(); - - openTimelineFieldsBrowser(); - - cy.get(FIELDS_BROWSER_SELECTED_CATEGORY_TITLE) - .invoke('text') - .should('eq', 'default ECS'); - }); - - it('the `defaultECS` (selected) category count matches the default timeline header count', () => { - populateTimeline(); - - openTimelineFieldsBrowser(); - - cy.get(FIELDS_BROWSER_SELECTED_CATEGORY_COUNT) - .invoke('text') - .should('eq', `${defaultHeaders.length}`); - }); - - it('displays a checked checkbox for all of the default timeline columns', () => { - populateTimeline(); - - openTimelineFieldsBrowser(); - - defaultHeaders.forEach(header => - cy.get(`[data-test-subj="field-${header.id}-checkbox"]`).should('be.checked') - ); - }); + context('Fields Browser rendering', () => { + before(() => { + loginAndWaitForPage(HOSTS_PAGE); + populateTimeline(); + openTimelineFieldsBrowser(); + }); - it('removes the message field from the timeline when the user un-checks the field', () => { - const toggleField = 'message'; + afterEach(() => { + clearFieldsBrowser(); + }); - populateTimeline(); + it('renders the fields browser with the expected title when the Fields button is clicked', () => { + cy.get(FIELDS_BROWSER_TITLE) + .invoke('text') + .should('eq', 'Customize Columns'); + }); - cy.get(`[data-test-subj="timeline"] [data-test-subj="header-text-${toggleField}"]`).should( - 'exist' - ); + it('displays the `default ECS` category (by default)', () => { + cy.get(FIELDS_BROWSER_SELECTED_CATEGORY_TITLE) + .invoke('text') + .should('eq', 'default ECS'); + }); - openTimelineFieldsBrowser(); + it('the `defaultECS` (selected) category count matches the default timeline header count', () => { + cy.get(FIELDS_BROWSER_SELECTED_CATEGORY_COUNT) + .invoke('text') + .should('eq', `${defaultHeaders.length}`); + }); - cy.get(`[data-test-subj="timeline"] [data-test-subj="field-${toggleField}-checkbox"]`).uncheck({ - force: true, + it('displays a checked checkbox for all of the default timeline columns', () => { + defaultHeaders.forEach(header => + cy.get(`[data-test-subj="field-${header.id}-checkbox"]`).should('be.checked') + ); }); - clickOutsideFieldsBrowser(); + it('displays the expected count of categories that match the filter input', () => { + const filterInput = 'host.mac'; - cy.get(`[data-test-subj="timeline"] [data-test-subj="header-text-${toggleField}"]`).should( - 'not.exist' - ); - }); + filterFieldsBrowser(filterInput); - it('displays the expected count of categories that match the filter input', () => { - const filterInput = 'host.mac'; + cy.get(FIELDS_BROWSER_CATEGORIES_COUNT) + .invoke('text') + .should('eq', '2 categories'); + }); - populateTimeline(); + it('displays a search results label with the expected count of fields matching the filter input', () => { + const filterInput = 'host.mac'; + + filterFieldsBrowser(filterInput); + + cy.get(FIELDS_BROWSER_FILTER_INPUT, { timeout: DEFAULT_TIMEOUT }).should( + 'not.have.class', + 'euiFieldSearch-isLoading' + ); + + cy.get(FIELDS_BROWSER_HOST_CATEGORIES_COUNT) + .invoke('text') + .then(hostCategoriesCount => { + cy.get(FIELDS_BROWSER_SYSTEM_CATEGORIES_COUNT) + .invoke('text') + .then(systemCategoriesCount => { + cy.get(FIELDS_BROWSER_FIELDS_COUNT) + .invoke('text') + .should('eq', `${+hostCategoriesCount + +systemCategoriesCount} fields`); + }); + }); + }); - openTimelineFieldsBrowser(); + it('displays a count of only the fields in the selected category that match the filter input', () => { + const filterInput = 'host.geo.c'; - filterFieldsBrowser(filterInput); + filterFieldsBrowser(filterInput); - cy.get(FIELDS_BROWSER_CATEGORIES_COUNT) - .invoke('text') - .should('eq', '2 categories'); + cy.get(FIELDS_BROWSER_SELECTED_CATEGORY_COUNT) + .invoke('text') + .should('eq', '4'); + }); }); - it('displays a search results label with the expected count of fields matching the filter input', () => { - const filterInput = 'host.mac'; - - populateTimeline(); + context('Editing the timeline', () => { + before(() => { + loginAndWaitForPage(HOSTS_PAGE); + populateTimeline(); + openTimelineFieldsBrowser(); + }); - openTimelineFieldsBrowser(); + afterEach(() => { + openTimelineFieldsBrowser(); + clearFieldsBrowser(); + }); - filterFieldsBrowser(filterInput); + it('removes the message field from the timeline when the user un-checks the field', () => { + const toggleField = 'message'; - cy.get(FIELDS_BROWSER_FILTER_INPUT, { timeout: DEFAULT_TIMEOUT }).should( - 'not.have.class', - 'euiFieldSearch-isLoading' - ); + cy.get(`[data-test-subj="timeline"] [data-test-subj="header-text-${toggleField}"]`).should( + 'exist' + ); - cy.get(FIELDS_BROWSER_HOST_CATEGORIES_COUNT) - .invoke('text') - .then(hostCategoriesCount => { - cy.get(FIELDS_BROWSER_SYSTEM_CATEGORIES_COUNT) - .invoke('text') - .then(systemCategoriesCount => { - cy.get(FIELDS_BROWSER_FIELDS_COUNT) - .invoke('text') - .should('eq', `${+hostCategoriesCount + +systemCategoriesCount} fields`); - }); + cy.get( + `[data-test-subj="timeline"] [data-test-subj="field-${toggleField}-checkbox"]` + ).uncheck({ + force: true, }); - }); - - it('selects a search results label with the expected count of categories matching the filter input', () => { - const category = 'host'; - - populateTimeline(); - openTimelineFieldsBrowser(); - - filterFieldsBrowser(`${category}.`); - - cy.get(FIELDS_BROWSER_SELECTED_CATEGORY_TITLE) - .invoke('text') - .should('eq', category); - }); + clickOutsideFieldsBrowser(); - it('displays a count of only the fields in the selected category that match the filter input', () => { - const filterInput = 'host.geo.c'; + cy.get(FIELDS_BROWSER_CONTAINER).should('not.exist'); - populateTimeline(); + cy.get(`[data-test-subj="timeline"] [data-test-subj="header-text-${toggleField}"]`).should( + 'not.exist' + ); + }); - openTimelineFieldsBrowser(); + it('selects a search results label with the expected count of categories matching the filter input', () => { + const category = 'host'; - filterFieldsBrowser(filterInput); + filterFieldsBrowser(`${category}.`); - cy.get(FIELDS_BROWSER_SELECTED_CATEGORY_COUNT) - .invoke('text') - .should('eq', '4'); - }); + cy.get(FIELDS_BROWSER_SELECTED_CATEGORY_TITLE) + .invoke('text') + .should('eq', category); + }); - it('adds a field to the timeline when the user clicks the checkbox', () => { - const filterInput = 'host.geo.c'; - const toggleField = 'host.geo.city_name'; + it('adds a field to the timeline when the user clicks the checkbox', () => { + const filterInput = 'host.geo.c'; + const toggleField = 'host.geo.city_name'; - populateTimeline(); + filterFieldsBrowser(filterInput); - openTimelineFieldsBrowser(); + cy.get(`[data-test-subj="timeline"] [data-test-subj="header-text-${toggleField}"]`).should( + 'not.exist' + ); - filterFieldsBrowser(filterInput); + cy.get(`[data-test-subj="timeline"] [data-test-subj="field-${toggleField}-checkbox"]`).check({ + force: true, + }); - cy.get(`[data-test-subj="timeline"] [data-test-subj="header-text-${toggleField}"]`).should( - 'not.exist' - ); + clickOutsideFieldsBrowser(); - cy.get(`[data-test-subj="timeline"] [data-test-subj="field-${toggleField}-checkbox"]`).check({ - force: true, + cy.get(`[data-test-subj="timeline"] [data-test-subj="header-text-${toggleField}"]`, { + timeout: DEFAULT_TIMEOUT, + }).should('exist'); }); - clickOutsideFieldsBrowser(); + it('adds a field to the timeline when the user drags and drops a field', () => { + const filterInput = 'host.geo.c'; + const toggleField = 'host.geo.country_name'; - cy.get(`[data-test-subj="timeline"] [data-test-subj="header-text-${toggleField}"]`).should( - 'exist' - ); - }); - - it('adds a field to the timeline when the user drags and drops a field', () => { - const filterInput = 'host.geo.c'; - const toggleField = 'host.geo.city_name'; + filterFieldsBrowser(filterInput); - populateTimeline(); + cy.get(`[data-test-subj="timeline"] [data-test-subj="header-text-${toggleField}"]`).should( + 'not.exist' + ); - openTimelineFieldsBrowser(); + cy.get( + `[data-test-subj="timeline"] [data-test-subj="field-name-${toggleField}"]` + ).then(field => drag(field)); - filterFieldsBrowser(filterInput); + cy.get(`[data-test-subj="timeline"] [data-test-subj="headers-group"]`).then(headersDropArea => + drop(headersDropArea) + ); - cy.get(`[data-test-subj="timeline"] [data-test-subj="header-text-${toggleField}"]`).should( - 'not.exist' - ); + cy.get(`[data-test-subj="timeline"] [data-test-subj="header-text-${toggleField}"]`, { + timeout: DEFAULT_TIMEOUT, + }).should('exist'); + }); - cy.get(`[data-test-subj="timeline"] [data-test-subj="field-name-${toggleField}"]`).then(field => - drag(field) - ); + it('resets all fields in the timeline when `Reset Fields` is clicked', () => { + const filterInput = 'host.geo.c'; + const toggleField = 'host.geo.continent_name'; - cy.get(`[data-test-subj="timeline"] [data-test-subj="headers-group"]`).then(headersDropArea => - drop(headersDropArea) - ); + filterFieldsBrowser(filterInput); - cy.get(`[data-test-subj="timeline"] [data-test-subj="header-text-${toggleField}"]`, { - timeout: DEFAULT_TIMEOUT, - }).should('exist'); - }); + cy.get(`[data-test-subj="timeline"] [data-test-subj="header-text-${toggleField}"]`).should( + 'not.exist' + ); - it('resets all fields in the timeline when `Reset Fields` is clicked', () => { - const filterInput = 'host.geo.c'; - const toggleField = 'host.geo.city_name'; + cy.get(`[data-test-subj="timeline"] [data-test-subj="field-${toggleField}-checkbox"]`).check({ + force: true, + }); - populateTimeline(); + clickOutsideFieldsBrowser(); - openTimelineFieldsBrowser(); + cy.get(`[data-test-subj="timeline"] [data-test-subj="header-text-${toggleField}"]`).should( + 'exist' + ); - filterFieldsBrowser(filterInput); + openTimelineFieldsBrowser(); - cy.get(`[data-test-subj="timeline"] [data-test-subj="header-text-${toggleField}"]`).should( - 'not.exist' - ); + cy.get('[data-test-subj="timeline"] [data-test-subj="reset-fields"]').click({ force: true }); - cy.get(`[data-test-subj="timeline"] [data-test-subj="field-${toggleField}-checkbox"]`).check({ - force: true, + cy.get(`[data-test-subj="timeline"] [data-test-subj="header-text-${toggleField}"]`).should( + 'not.exist' + ); }); - - clickOutsideFieldsBrowser(); - - cy.get(`[data-test-subj="timeline"] [data-test-subj="header-text-${toggleField}"]`).should( - 'exist' - ); - - openTimelineFieldsBrowser(); - - cy.get('[data-test-subj="timeline"] [data-test-subj="reset-fields"]').click({ force: true }); - - cy.get(`[data-test-subj="timeline"] [data-test-subj="header-text-${toggleField}"]`).should( - 'not.exist' - ); }); }); diff --git a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/inspect/inspect.spec.ts b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/inspect/inspect.spec.ts index 54207966fd36f..ee25705a83989 100644 --- a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/inspect/inspect.spec.ts +++ b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/inspect/inspect.spec.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { logout } from '../../lib/logout'; import { HOSTS_PAGE } from '../../lib/urls'; import { INSPECT_BUTTON_ICON, @@ -18,9 +17,6 @@ import { executeKQL, hostExistsQuery, toggleTimelineVisibility } from '../../lib describe('Inspect', () => { describe('Hosts and network stats and tables', () => { - afterEach(() => { - return logout(); - }); INSPECT_BUTTONS_IN_SIEM.map(table => it(`inspects the ${table.title}`, () => { loginAndWaitForPage(table.url); @@ -36,10 +32,6 @@ describe('Inspect', () => { }); describe('Timeline', () => { - afterEach(() => { - return logout(); - }); - it('inspects the timeline', () => { loginAndWaitForPage(HOSTS_PAGE); toggleTimelineVisibility(); diff --git a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/ml_conditional_links/ml_conditional_links.spec.ts b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/ml_conditional_links/ml_conditional_links.spec.ts index 4c29c081b3e69..afeb8c3c13a4f 100644 --- a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/ml_conditional_links/ml_conditional_links.spec.ts +++ b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/ml_conditional_links/ml_conditional_links.spec.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { logout } from '../../lib/logout'; import { mlNetworkSingleIpNullKqlQuery, mlNetworkSingleIpKqlQuery, @@ -24,10 +23,6 @@ import { loginAndWaitForPage } from '../../lib/util/helpers'; import { KQL_INPUT } from '../../lib/url_state'; describe('ml conditional links', () => { - afterEach(() => { - return logout(); - }); - it('sets the KQL from a single IP with a value for the query', () => { loginAndWaitForPage(mlNetworkSingleIpKqlQuery); cy.get(KQL_INPUT, { timeout: 5000 }).should( diff --git a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/navigation/navigation.spec.ts b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/navigation/navigation.spec.ts index f4beba7cbb72d..a549b5eec2e7c 100644 --- a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/navigation/navigation.spec.ts +++ b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/navigation/navigation.spec.ts @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { logout } from '../../lib/logout'; -import { OVERVIEW_PAGE, TIMELINES_PAGE } from '../../lib/urls'; +import { TIMELINES_PAGE } from '../../lib/urls'; import { NAVIGATION_HOSTS, NAVIGATION_NETWORK, @@ -15,37 +14,27 @@ import { import { loginAndWaitForPage } from '../../lib/util/helpers'; describe('top-level navigation common to all pages in the SIEM app', () => { - afterEach(() => { - return logout(); + before(() => { + loginAndWaitForPage(TIMELINES_PAGE); }); - it('navigates to the Overview page', () => { - loginAndWaitForPage(TIMELINES_PAGE); - cy.get(NAVIGATION_OVERVIEW).click({ force: true }); - cy.url().should('include', '/siem#/overview'); }); it('navigates to the Hosts page', () => { - loginAndWaitForPage(TIMELINES_PAGE); - cy.get(NAVIGATION_HOSTS).click({ force: true }); cy.url().should('include', '/siem#/hosts'); }); it('navigates to the Network page', () => { - loginAndWaitForPage(TIMELINES_PAGE); - cy.get(NAVIGATION_NETWORK).click({ force: true }); cy.url().should('include', '/siem#/network'); }); it('navigates to the Timelines page', () => { - loginAndWaitForPage(OVERVIEW_PAGE); - cy.get(NAVIGATION_TIMELINES).click({ force: true }); cy.url().should('include', '/siem#/timelines'); diff --git a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/overview/overview.spec.ts b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/overview/overview.spec.ts index 2ea8b5e8bc5ce..4ef3eb67cafc9 100644 --- a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/overview/overview.spec.ts +++ b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/overview/overview.spec.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { logout } from '../../lib/logout'; import { OVERVIEW_PAGE } from '../../lib/urls'; import { clearFetch, stubApi } from '../../lib/fixtures/helpers'; import { HOST_STATS, NETWORK_STATS, STAT_AUDITD } from '../../lib/overview/selectors'; @@ -17,10 +16,6 @@ describe('Overview Page', () => { loginAndWaitForPage(OVERVIEW_PAGE); }); - afterEach(() => { - return logout(); - }); - it('Host and Network stats render with correct values', () => { cy.get(STAT_AUDITD.domId); diff --git a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/pagination/pagination.spec.ts b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/pagination/pagination.spec.ts index ebd0ad0125efb..3853e703a7c07 100644 --- a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/pagination/pagination.spec.ts +++ b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/pagination/pagination.spec.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { logout } from '../../lib/logout'; import { HOSTS_PAGE_TAB_URLS } from '../../lib/urls'; import { AUTHENTICATIONS_TABLE, @@ -19,13 +18,16 @@ import { import { DEFAULT_TIMEOUT, loginAndWaitForPage, waitForTableLoad } from '../../lib/util/helpers'; describe('Pagination', () => { + before(() => { + loginAndWaitForPage(HOSTS_PAGE_TAB_URLS.uncommonProcesses); + waitForTableLoad(UNCOMMON_PROCCESSES_TABLE); + }); + afterEach(() => { - return logout(); + cy.get(getPageButtonSelector(0)).click({ force: true }); }); it('pagination updates results and page number', () => { - loginAndWaitForPage(HOSTS_PAGE_TAB_URLS.uncommonProcesses); - waitForTableLoad(UNCOMMON_PROCCESSES_TABLE); cy.get(getPageButtonSelector(0)).should('have.class', 'euiPaginationButton-isActive'); cy.get(getDraggableField('process.name')) @@ -47,8 +49,6 @@ describe('Pagination', () => { }); it('pagination keeps track of page results when tabs change', () => { - loginAndWaitForPage(HOSTS_PAGE_TAB_URLS.uncommonProcesses); - waitForTableLoad(UNCOMMON_PROCCESSES_TABLE); cy.get(getPageButtonSelector(0)).should('have.class', 'euiPaginationButton-isActive'); let thirdPageResult: string; cy.get(getPageButtonSelector(2)).click({ force: true }); @@ -83,7 +83,6 @@ describe('Pagination', () => { * when we figure out a way to really mock the data, we should come back to it */ it('pagination resets results and page number to first page when refresh is clicked', () => { - loginAndWaitForPage(HOSTS_PAGE_TAB_URLS.uncommonProcesses); cy.get(NUMBERED_PAGINATION, { timeout: DEFAULT_TIMEOUT }); cy.get(getPageButtonSelector(0)).should('have.class', 'euiPaginationButton-isActive'); // let firstResult: string; diff --git a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/timeline/data_providers.spec.ts b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/timeline/data_providers.spec.ts index 236d5a53481b7..824e403185238 100644 --- a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/timeline/data_providers.spec.ts +++ b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/timeline/data_providers.spec.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { logout } from '../../lib/logout'; import { TIMELINE_DATA_PROVIDERS, TIMELINE_DROPPED_DATA_PROVIDERS, @@ -22,10 +21,6 @@ describe('timeline data providers', () => { loginAndWaitForPage(HOSTS_PAGE); }); - afterEach(() => { - return logout(); - }); - it('renders the data provider of a host dragged from the All Hosts widget on the hosts page', () => { waitForAllHostsWidget(); diff --git a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/timeline/flyout_button.spec.ts b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/timeline/flyout_button.spec.ts index c1c35e497d081..5b0ac03ae87dc 100644 --- a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/timeline/flyout_button.spec.ts +++ b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/timeline/flyout_button.spec.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { logout } from '../../lib/logout'; import { TIMELINE_FLYOUT_BODY, TIMELINE_NOT_READY_TO_DROP_BUTTON, @@ -21,10 +20,6 @@ describe('timeline flyout button', () => { loginAndWaitForPage(HOSTS_PAGE); }); - afterEach(() => { - return logout(); - }); - it('toggles open the timeline', () => { toggleTimelineVisibility(); diff --git a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/timeline/search_or_filter.spec.ts b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/timeline/search_or_filter.spec.ts index 0c9aed33d47ad..9f21b4e3d53a1 100644 --- a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/timeline/search_or_filter.spec.ts +++ b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/timeline/search_or_filter.spec.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { logout } from '../../lib/logout'; import { assertAtLeastOneEventMatchesSearch, executeKQL, @@ -19,10 +18,6 @@ describe('timeline search or filter KQL bar', () => { loginAndWaitForPage(HOSTS_PAGE); }); - afterEach(() => { - return logout(); - }); - it('executes a KQL query', () => { toggleTimelineVisibility(); diff --git a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/timeline/toggle_column.spec.ts b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/timeline/toggle_column.spec.ts index 8197f77db9a08..9a915b0e77d44 100644 --- a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/timeline/toggle_column.spec.ts +++ b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/timeline/toggle_column.spec.ts @@ -6,7 +6,6 @@ import { drag, drop } from '../../lib/drag_n_drop/helpers'; import { populateTimeline } from '../../lib/fields_browser/helpers'; -import { logout } from '../../lib/logout'; import { toggleFirstTimelineEventDetails } from '../../lib/timeline/helpers'; import { HOSTS_PAGE } from '../../lib/urls'; import { loginAndWaitForPage, DEFAULT_TIMEOUT } from '../../lib/util/helpers'; @@ -16,10 +15,6 @@ describe('toggle column in timeline', () => { loginAndWaitForPage(HOSTS_PAGE); }); - afterEach(() => { - return logout(); - }); - const timestampField = '@timestamp'; const idField = '_id'; diff --git a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/url_state/url_state.spec.ts b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/url_state/url_state.spec.ts index dba5099a93c5a..33ee2cb1cb302 100644 --- a/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/url_state/url_state.spec.ts +++ b/x-pack/legacy/plugins/siem/cypress/integration/smoke_tests/url_state/url_state.spec.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { logout } from '../../lib/logout'; import { ABSOLUTE_DATE_RANGE, DATE_PICKER_ABSOLUTE_INPUT, @@ -33,10 +32,6 @@ import { waitForAllHostsWidget } from '../../lib/hosts/helpers'; import { NAVIGATION_HOSTS_ALL_HOSTS, NAVIGATION_HOSTS_ANOMALIES } from '../../lib/hosts/selectors'; describe('url state', () => { - afterEach(() => { - return logout(); - }); - it('sets the global start and end dates from the url', () => { loginAndWaitForPage(ABSOLUTE_DATE_RANGE.url); cy.get(DATE_PICKER_START_DATE_POPOVER_BUTTON).should( diff --git a/x-pack/legacy/plugins/siem/cypress/support/index.js b/x-pack/legacy/plugins/siem/cypress/support/index.js index b20865149d02f..9b86c2ffa94c6 100644 --- a/x-pack/legacy/plugins/siem/cypress/support/index.js +++ b/x-pack/legacy/plugins/siem/cypress/support/index.js @@ -22,5 +22,9 @@ // Import commands.js using ES2015 syntax: import './commands'; +Cypress.Cookies.defaults({ + whitelist: 'sid', +}); + // Alternatively you can use CommonJS syntax: // require('./commands') diff --git a/x-pack/legacy/plugins/siem/dev_tools/circular_deps/run_check_circular_deps_cli.js b/x-pack/legacy/plugins/siem/dev_tools/circular_deps/run_check_circular_deps_cli.js index a4a9532f0e8e4..7d76b1dd921aa 100644 --- a/x-pack/legacy/plugins/siem/dev_tools/circular_deps/run_check_circular_deps_cli.js +++ b/x-pack/legacy/plugins/siem/dev_tools/circular_deps/run_check_circular_deps_cli.js @@ -24,9 +24,7 @@ run( // We can only care about SIEM code, we should not be penalyze for others if (circularFound.filter(cf => cf.includes('siem')).length !== 0) { throw createFailError( - 'SIEM circular dependencies of imports has been found:' + - '\n - ' + - circularFound.join('\n - ') + `SIEM circular dependencies of imports has been found:\n - ${circularFound.join('\n - ')}` ); } else { log.success('No circular deps 👍'); diff --git a/x-pack/legacy/plugins/siem/index.ts b/x-pack/legacy/plugins/siem/index.ts index cf9fffc6a1455..c5038626fdfc2 100644 --- a/x-pack/legacy/plugins/siem/index.ts +++ b/x-pack/legacy/plugins/siem/index.ts @@ -9,7 +9,7 @@ import { resolve } from 'path'; import { Server } from 'hapi'; import { Root } from 'joi'; -import { PluginInitializerContext } from 'src/core/server'; +import { PluginInitializerContext } from '../../../../src/core/server'; import { plugin } from './server'; import { savedObjectMappings } from './server/saved_objects'; @@ -43,7 +43,7 @@ export const siem = (kibana: any) => { description: i18n.translate('xpack.siem.securityDescription', { defaultMessage: 'Explore your SIEM App', }), - main: 'plugins/siem/app', + main: 'plugins/siem/legacy', euiIconType: 'securityAnalyticsApp', title: APP_NAME, listed: false, diff --git a/x-pack/legacy/plugins/siem/package.json b/x-pack/legacy/plugins/siem/package.json index bf5d6d3a3089c..558ac013e5963 100644 --- a/x-pack/legacy/plugins/siem/package.json +++ b/x-pack/legacy/plugins/siem/package.json @@ -13,7 +13,7 @@ "devDependencies": { "@types/lodash": "^4.14.110", "@types/js-yaml": "^3.12.1", - "@types/react-beautiful-dnd": "^11.0.3" + "@types/react-beautiful-dnd": "^11.0.4" }, "dependencies": { "lodash": "^4.17.15", diff --git a/x-pack/legacy/plugins/siem/public/app.ts b/x-pack/legacy/plugins/siem/public/app.ts deleted file mode 100644 index b068f8a9becda..0000000000000 --- a/x-pack/legacy/plugins/siem/public/app.ts +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import './apps/index'; diff --git a/x-pack/legacy/plugins/siem/public/app/app.tsx b/x-pack/legacy/plugins/siem/public/app/app.tsx new file mode 100644 index 0000000000000..5f9199735d8c0 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/app/app.tsx @@ -0,0 +1,110 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { createHashHistory, History } from 'history'; +import React, { memo, useMemo, FC } from 'react'; +import { ApolloProvider } from 'react-apollo'; +import { Store } from 'redux'; +import { Provider as ReduxStoreProvider } from 'react-redux'; +import { ThemeProvider } from 'styled-components'; + +import { EuiErrorBoundary } from '@elastic/eui'; +import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; +import euiLightVars from '@elastic/eui/dist/eui_theme_light.json'; +import { BehaviorSubject } from 'rxjs'; +import { pluck } from 'rxjs/operators'; +import { I18nContext } from 'ui/i18n'; + +import { KibanaContextProvider, useUiSetting$ } from '../lib/kibana'; +import { Storage } from '../../../../../../src/plugins/kibana_utils/public'; + +import { DEFAULT_DARK_MODE } from '../../common/constants'; +import { ErrorToastDispatcher } from '../components/error_toast_dispatcher'; +import { compose } from '../lib/compose/kibana_compose'; +import { AppFrontendLibs, AppApolloClient } from '../lib/lib'; +import { CoreStart, StartPlugins } from '../plugin'; +import { PageRouter } from '../routes'; +import { createStore } from '../store'; +import { GlobalToaster, ManageGlobalToaster } from '../components/toasters'; +import { MlCapabilitiesProvider } from '../components/ml/permissions/ml_capabilities_provider'; + +import { ApolloClientContext } from '../utils/apollo_context'; + +interface AppPluginRootComponentProps { + apolloClient: AppApolloClient; + history: History; + store: Store; + theme: any; // eslint-disable-line @typescript-eslint/no-explicit-any +} + +const AppPluginRootComponent: React.FC = ({ + theme, + store, + apolloClient, + history, +}) => ( + + + + + + + + + + + + + + + + + + + +); + +const AppPluginRoot = memo(AppPluginRootComponent); + +const StartAppComponent: FC = libs => { + const history = createHashHistory(); + const libs$ = new BehaviorSubject(libs); + const store = createStore(undefined, libs$.pipe(pluck('apolloClient'))); + const [darkMode] = useUiSetting$(DEFAULT_DARK_MODE); + const theme = useMemo( + () => ({ + eui: darkMode ? euiDarkVars : euiLightVars, + darkMode, + }), + [darkMode] + ); + + return ( + + ); +}; + +const StartApp = memo(StartAppComponent); + +interface SiemAppComponentProps { + core: CoreStart; + plugins: StartPlugins; +} + +const SiemAppComponent: React.FC = ({ core, plugins }) => ( + + + +); + +export const SiemApp = memo(SiemAppComponent); diff --git a/x-pack/legacy/plugins/siem/public/app/index.tsx b/x-pack/legacy/plugins/siem/public/app/index.tsx new file mode 100644 index 0000000000000..01175a98d1e44 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/app/index.tsx @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { render, unmountComponentAtNode } from 'react-dom'; + +import { CoreStart, StartPlugins, AppMountParameters } from '../plugin'; +import { SiemApp } from './app'; + +export const renderApp = ( + core: CoreStart, + plugins: StartPlugins, + { element }: AppMountParameters +) => { + render(, element); + return () => unmountComponentAtNode(element); +}; diff --git a/x-pack/legacy/plugins/siem/public/apps/index.ts b/x-pack/legacy/plugins/siem/public/apps/index.ts deleted file mode 100644 index 0cc5c5584e1b7..0000000000000 --- a/x-pack/legacy/plugins/siem/public/apps/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import chrome from 'ui/chrome'; -import { npStart } from 'ui/new_platform'; -import { Plugin } from './plugin'; - -const { data, embeddable, inspector, uiActions } = npStart.plugins; -const startPlugins = { data, embeddable, inspector, uiActions }; - -new Plugin( - // eslint-disable-next-line @typescript-eslint/no-explicit-any - { opaqueId: Symbol('siem'), env: {} as any, config: { get: () => ({} as any) } }, - chrome -).start(npStart.core, startPlugins); diff --git a/x-pack/legacy/plugins/siem/public/apps/plugin.tsx b/x-pack/legacy/plugins/siem/public/apps/plugin.tsx deleted file mode 100644 index aa42504e07635..0000000000000 --- a/x-pack/legacy/plugins/siem/public/apps/plugin.tsx +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import { render } from 'react-dom'; -import { LegacyCoreStart, PluginInitializerContext } from 'src/core/public'; -import { PluginsStart } from 'ui/new_platform/new_platform'; -import { Chrome } from 'ui/chrome'; - -import { DEFAULT_KBN_VERSION, DEFAULT_TIMEZONE_BROWSER } from '../../common/constants'; -import { SiemApp } from './start_app'; -import template from './template.html'; - -export const ROOT_ELEMENT_ID = 'react-siem-root'; - -export type StartCore = LegacyCoreStart; -export type StartPlugins = Required< - Pick ->; -export type StartServices = StartCore & StartPlugins; - -export class Plugin { - constructor( - // @ts-ignore this is added to satisfy the New Platform typing constraint, - // but we're not leveraging any of its functionality yet. - private readonly initializerContext: PluginInitializerContext, - private readonly chrome: Chrome - ) { - this.chrome = chrome; - } - - public start(core: StartCore, plugins: StartPlugins) { - // TODO(rylnd): These are unknown by uiSettings by default - core.uiSettings.set(DEFAULT_KBN_VERSION, '8.0.0'); - core.uiSettings.set(DEFAULT_TIMEZONE_BROWSER, 'UTC'); - - // @ts-ignore improper type description - this.chrome.setRootTemplate(template); - const checkForRoot = () => { - return new Promise(resolve => { - const ready = !!document.getElementById(ROOT_ELEMENT_ID); - if (ready) { - resolve(); - } else { - setTimeout(() => resolve(checkForRoot()), 10); - } - }); - }; - checkForRoot().then(() => { - const node = document.getElementById(ROOT_ELEMENT_ID); - if (node) { - render(, node); - } - }); - } -} diff --git a/x-pack/legacy/plugins/siem/public/apps/start_app.tsx b/x-pack/legacy/plugins/siem/public/apps/start_app.tsx deleted file mode 100644 index 54180b51fe039..0000000000000 --- a/x-pack/legacy/plugins/siem/public/apps/start_app.tsx +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { createHashHistory } from 'history'; -import React, { memo, FC } from 'react'; -import { ApolloProvider } from 'react-apollo'; -import { Provider as ReduxStoreProvider } from 'react-redux'; -import { ThemeProvider } from 'styled-components'; - -import { EuiErrorBoundary } from '@elastic/eui'; -import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; -import euiLightVars from '@elastic/eui/dist/eui_theme_light.json'; -import { BehaviorSubject } from 'rxjs'; -import { pluck } from 'rxjs/operators'; -import { I18nContext } from 'ui/i18n'; - -import { KibanaContextProvider, useUiSetting$ } from '../lib/kibana'; -import { Storage } from '../../../../../../src/plugins/kibana_utils/public'; - -import { DEFAULT_DARK_MODE } from '../../common/constants'; -import { ErrorToastDispatcher } from '../components/error_toast_dispatcher'; -import { compose } from '../lib/compose/kibana_compose'; -import { AppFrontendLibs } from '../lib/lib'; -import { StartCore, StartPlugins } from './plugin'; -import { PageRouter } from '../routes'; -import { createStore } from '../store'; -import { GlobalToaster, ManageGlobalToaster } from '../components/toasters'; -import { MlCapabilitiesProvider } from '../components/ml/permissions/ml_capabilities_provider'; - -import { ApolloClientContext } from '../utils/apollo_context'; - -const StartApp: FC = memo(libs => { - const history = createHashHistory(); - - const libs$ = new BehaviorSubject(libs); - - const store = createStore(undefined, libs$.pipe(pluck('apolloClient'))); - - const AppPluginRoot = memo(() => { - const [darkMode] = useUiSetting$(DEFAULT_DARK_MODE); - return ( - - - - - - - ({ - eui: darkMode ? euiDarkVars : euiLightVars, - darkMode, - })} - > - - - - - - - - - - - - - ); - }); - return ; -}); - -export const ROOT_ELEMENT_ID = 'react-siem-root'; - -export const SiemApp = memo<{ core: StartCore; plugins: StartPlugins }>(({ core, plugins }) => ( - - - -)); diff --git a/x-pack/legacy/plugins/siem/public/apps/template.html b/x-pack/legacy/plugins/siem/public/apps/template.html deleted file mode 100644 index 9f757b25ccecb..0000000000000 --- a/x-pack/legacy/plugins/siem/public/apps/template.html +++ /dev/null @@ -1 +0,0 @@ -
\ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/public/components/alerts_viewer/alerts_table.tsx b/x-pack/legacy/plugins/siem/public/components/alerts_viewer/alerts_table.tsx index 8fa4f3625c34f..179474ee6e9d4 100644 --- a/x-pack/legacy/plugins/siem/public/components/alerts_viewer/alerts_table.tsx +++ b/x-pack/legacy/plugins/siem/public/components/alerts_viewer/alerts_table.tsx @@ -51,33 +51,31 @@ const defaultAlertsFilters: esFilters.Filter[] = [ }, ]; -export const AlertsTable = React.memo( - ({ - endDate, - startDate, - pageFilters = [], - }: { - endDate: number; - startDate: number; - pageFilters?: esFilters.Filter[]; - }) => { - const alertsFilter = useMemo(() => [...defaultAlertsFilters, ...pageFilters], [pageFilters]); - return ( - ({ - documentType: i18n.ALERTS_DOCUMENT_TYPE, - footerText: i18n.TOTAL_COUNT_OF_ALERTS, - title: i18n.ALERTS_TABLE_TITLE, - }), - [] - )} - /> - ); - } -); +interface Props { + endDate: number; + startDate: number; + pageFilters?: esFilters.Filter[]; +} + +const AlertsTableComponent: React.FC = ({ endDate, startDate, pageFilters = [] }) => { + const alertsFilter = useMemo(() => [...defaultAlertsFilters, ...pageFilters], [pageFilters]); + return ( + ({ + documentType: i18n.ALERTS_DOCUMENT_TYPE, + footerText: i18n.TOTAL_COUNT_OF_ALERTS, + title: i18n.ALERTS_TABLE_TITLE, + }), + [] + )} + /> + ); +}; + +export const AlertsTable = React.memo(AlertsTableComponent); diff --git a/x-pack/legacy/plugins/siem/public/components/and_or_badge/index.tsx b/x-pack/legacy/plugins/siem/public/components/and_or_badge/index.tsx index be449e3d422d9..2fb270c284000 100644 --- a/x-pack/legacy/plugins/siem/public/components/and_or_badge/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/and_or_badge/index.tsx @@ -5,7 +5,7 @@ */ import { EuiBadge } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import * as i18n from './translations'; diff --git a/x-pack/legacy/plugins/siem/public/components/arrows/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/arrows/index.test.tsx index 10d3c899562e8..5404a1ac43844 100644 --- a/x-pack/legacy/plugins/siem/public/components/arrows/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/arrows/index.test.tsx @@ -5,8 +5,7 @@ */ import { mount } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { TestProviders } from '../../mock'; @@ -20,7 +19,7 @@ describe('arrows', () => { ); - expect(toJson(wrapper.find('ArrowBody'))).toMatchSnapshot(); + expect(wrapper.find('ArrowBody')).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/arrows/index.tsx b/x-pack/legacy/plugins/siem/public/components/arrows/index.tsx index dfc7645c564d2..97b5eb04ac7bb 100644 --- a/x-pack/legacy/plugins/siem/public/components/arrows/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/arrows/index.tsx @@ -5,7 +5,7 @@ */ import { EuiIcon } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; /** Renders the body (non-pointy part) of an arrow */ diff --git a/x-pack/legacy/plugins/siem/public/components/autocomplete_field/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/autocomplete_field/index.test.tsx index 77a7296e368cf..27e87d25e286f 100644 --- a/x-pack/legacy/plugins/siem/public/components/autocomplete_field/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/autocomplete_field/index.test.tsx @@ -7,9 +7,8 @@ import { EuiFieldSearch } from '@elastic/eui'; import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import { noop } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { ThemeProvider } from 'styled-components'; import { AutocompleteSuggestion } from '../../../../../../../src/plugins/data/public'; @@ -117,7 +116,7 @@ describe('Autocomplete', () => { value={''} /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it is rendering with placeholder', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/bytes/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/bytes/index.test.tsx index 6816bff24f1cd..d99a909efad10 100644 --- a/x-pack/legacy/plugins/siem/public/components/bytes/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/bytes/index.test.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import { TestProviders } from '../../mock'; import { PreferenceFormattedBytes } from '../formatted_bytes'; diff --git a/x-pack/legacy/plugins/siem/public/components/bytes/index.tsx b/x-pack/legacy/plugins/siem/public/components/bytes/index.tsx index fbe83623211b1..94c6ecba68be5 100644 --- a/x-pack/legacy/plugins/siem/public/components/bytes/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/bytes/index.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import { DefaultDraggable } from '../draggables'; import { PreferenceFormattedBytes } from '../formatted_bytes'; diff --git a/x-pack/legacy/plugins/siem/public/components/certificate_fingerprint/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/certificate_fingerprint/index.test.tsx index b0c165fedfffc..9cd0af062c54a 100644 --- a/x-pack/legacy/plugins/siem/public/components/certificate_fingerprint/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/certificate_fingerprint/index.test.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import { TestProviders } from '../../mock'; import { useMountAppended } from '../../utils/use_mount_appended'; diff --git a/x-pack/legacy/plugins/siem/public/components/certificate_fingerprint/index.tsx b/x-pack/legacy/plugins/siem/public/components/certificate_fingerprint/index.tsx index f8db7d754aab1..181d92dce06f9 100644 --- a/x-pack/legacy/plugins/siem/public/components/certificate_fingerprint/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/certificate_fingerprint/index.tsx @@ -5,7 +5,7 @@ */ import { EuiText } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { DraggableBadge } from '../draggables'; diff --git a/x-pack/legacy/plugins/siem/public/components/charts/areachart.test.tsx b/x-pack/legacy/plugins/siem/public/components/charts/areachart.test.tsx index 2b99efc05fd8c..ac283790671d3 100644 --- a/x-pack/legacy/plugins/siem/public/components/charts/areachart.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/charts/areachart.test.tsx @@ -5,7 +5,7 @@ */ import { ShallowWrapper, shallow } from 'enzyme'; -import * as React from 'react'; +import React from 'react'; import { AreaChartBaseComponent, AreaChartComponent } from './areachart'; import { ChartSeriesData } from './common'; diff --git a/x-pack/legacy/plugins/siem/public/components/charts/areachart.tsx b/x-pack/legacy/plugins/siem/public/components/charts/areachart.tsx index 297563c8e31cf..71f22efadc6ed 100644 --- a/x-pack/legacy/plugins/siem/public/components/charts/areachart.tsx +++ b/x-pack/legacy/plugins/siem/public/components/charts/areachart.tsx @@ -9,8 +9,6 @@ import { Axis, AreaSeries, Chart, - getAxisId, - getSpecId, Position, ScaleType, Settings, @@ -20,16 +18,15 @@ import { import { getOr, get, isNull, isNumber } from 'lodash/fp'; import { AutoSizer } from '../auto_sizer'; import { ChartPlaceHolder } from './chart_place_holder'; +import { useTimeZone } from '../../hooks'; import { chartDefaultSettings, ChartSeriesConfigs, ChartSeriesData, getChartHeight, getChartWidth, - getSeriesStyle, WrappedByAutoSizer, useTheme, - useBrowserTimeZone, } from './common'; // custom series styles: https://ela.st/areachart-styling @@ -74,11 +71,11 @@ export const AreaChartBaseComponent = ({ configs?: ChartSeriesConfigs | undefined; }) => { const theme = useTheme(); - const timeZone = useBrowserTimeZone(); + const timeZone = useTimeZone(); const xTickFormatter = get('configs.axis.xTickFormatter', chartConfigs); const yTickFormatter = get('configs.axis.yTickFormatter', chartConfigs); - const xAxisId = getAxisId(`group-${data[0].key}-x`); - const yAxisId = getAxisId(`group-${data[0].key}-y`); + const xAxisId = `group-${data[0].key}-x`; + const yAxisId = `group-${data[0].key}-y`; const settings = { ...chartDefaultSettings, theme, @@ -90,20 +87,19 @@ export const AreaChartBaseComponent = ({ {data.map(series => { const seriesKey = series.key; - const seriesSpecId = getSpecId(seriesKey); return checkIfAllTheDataInTheSeriesAreValid(series) ? ( ) : null; })} diff --git a/x-pack/legacy/plugins/siem/public/components/charts/barchart.test.tsx b/x-pack/legacy/plugins/siem/public/components/charts/barchart.test.tsx index 506b1ceb5ed83..ac9c4d591232a 100644 --- a/x-pack/legacy/plugins/siem/public/components/charts/barchart.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/charts/barchart.test.tsx @@ -5,7 +5,7 @@ */ import { shallow, ShallowWrapper } from 'enzyme'; -import * as React from 'react'; +import React from 'react'; import { BarChartBaseComponent, BarChartComponent } from './barchart'; import { ChartSeriesData } from './common'; diff --git a/x-pack/legacy/plugins/siem/public/components/charts/barchart.tsx b/x-pack/legacy/plugins/siem/public/components/charts/barchart.tsx index ee8b4eaf6b08c..415cbeb7c2440 100644 --- a/x-pack/legacy/plugins/siem/public/components/charts/barchart.tsx +++ b/x-pack/legacy/plugins/siem/public/components/charts/barchart.tsx @@ -5,17 +5,9 @@ */ import React from 'react'; -import { - Chart, - BarSeries, - Axis, - Position, - getAxisId, - getSpecId, - ScaleType, - Settings, -} from '@elastic/charts'; +import { Chart, BarSeries, Axis, Position, ScaleType, Settings } from '@elastic/charts'; import { getOr, get, isNumber } from 'lodash/fp'; +import { useTimeZone } from '../../hooks'; import { AutoSizer } from '../auto_sizer'; import { ChartPlaceHolder } from './chart_place_holder'; import { @@ -25,10 +17,7 @@ import { checkIfAllValuesAreZero, getChartHeight, getChartWidth, - getSeriesStyle, - SeriesType, WrappedByAutoSizer, - useBrowserTimeZone, useTheme, } from './common'; @@ -55,12 +44,12 @@ export const BarChartBaseComponent = ({ configs?: ChartSeriesConfigs | undefined; }) => { const theme = useTheme(); - const timeZone = useBrowserTimeZone(); + const timeZone = useTimeZone(); const xTickFormatter = get('configs.axis.xTickFormatter', chartConfigs); const yTickFormatter = get('configs.axis.yTickFormatter', chartConfigs); const tickSize = getOr(0, 'configs.axis.tickSize', chartConfigs); - const xAxisId = getAxisId(`stat-items-barchart-${data[0].key}-x`); - const yAxisId = getAxisId(`stat-items-barchart-${data[0].key}-y`); + const xAxisId = `stat-items-barchart-${data[0].key}-x`; + const yAxisId = `stat-items-barchart-${data[0].key}-y`; const settings = { ...chartDefaultSettings, theme, @@ -72,11 +61,9 @@ export const BarChartBaseComponent = ({ {data.map(series => { const barSeriesKey = series.key; - const barSeriesSpecId = getSpecId(barSeriesKey); - const seriesType = SeriesType.BAR; return checkIfAllTheDataInTheSeriesAreValid ? ( ) : null; })} diff --git a/x-pack/legacy/plugins/siem/public/components/charts/common.test.tsx b/x-pack/legacy/plugins/siem/public/components/charts/common.test.tsx index e9df0d3885a18..3748a2505c115 100644 --- a/x-pack/legacy/plugins/siem/public/components/charts/common.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/charts/common.test.tsx @@ -13,8 +13,6 @@ import { defaultChartHeight, getChartHeight, getChartWidth, - getSeriesStyle, - SeriesType, WrappedByAutoSizer, ChartSeriesData, useTheme, @@ -41,26 +39,6 @@ describe('WrappedByAutoSizer', () => { }); }); -describe('getSeriesStyle', () => { - it('should not create style mapping if color is not given', () => { - const mockSeriesKey = 'mockSeriesKey'; - const color = ''; - const customSeriesColors = getSeriesStyle(mockSeriesKey, color, SeriesType.BAR); - expect(customSeriesColors).toBeUndefined(); - }); - - it('should create correct style mapping for series of a chart', () => { - const mockSeriesKey = 'mockSeriesKey'; - const color = 'red'; - const customSeriesColors = getSeriesStyle(mockSeriesKey, color, SeriesType.BAR); - const expectedKey = { colorValues: [mockSeriesKey] }; - customSeriesColors!.forEach((value, key) => { - expect(JSON.stringify(key)).toEqual(JSON.stringify(expectedKey)); - expect(value).toEqual(color); - }); - }); -}); - describe('getChartHeight', () => { it('should render customHeight', () => { const height = getChartHeight(10, 100); diff --git a/x-pack/legacy/plugins/siem/public/components/charts/common.tsx b/x-pack/legacy/plugins/siem/public/components/charts/common.tsx index dfb201fc3d927..78cce72f0a0d3 100644 --- a/x-pack/legacy/plugins/siem/public/components/charts/common.tsx +++ b/x-pack/legacy/plugins/siem/public/components/charts/common.tsx @@ -5,23 +5,19 @@ */ import { - CustomSeriesColorsMap, DARK_THEME, - DataSeriesColorsValues, - getSpecId, LIGHT_THEME, mergeWithDefaultTheme, PartialTheme, Rendering, Rotation, ScaleType, - SettingSpecProps, + SettingsSpecProps, TickFormatter, } from '@elastic/charts'; -import moment from 'moment-timezone'; import styled from 'styled-components'; import { useUiSetting } from '../../lib/kibana'; -import { DEFAULT_DATE_FORMAT_TZ, DEFAULT_DARK_MODE } from '../../../common/constants'; +import { DEFAULT_DARK_MODE } from '../../../common/constants'; export const defaultChartHeight = '100%'; export const defaultChartWidth = '100%'; @@ -47,7 +43,7 @@ export interface ChartSeriesConfigs { xTickFormatter?: TickFormatter | undefined; yTickFormatter?: TickFormatter | undefined; }; - settings?: Partial; + settings?: Partial; } export interface ChartSeriesData { @@ -76,24 +72,6 @@ export enum SeriesType { LINE = 'line', } -// Customize colors: https://ela.st/custom-colors -export const getSeriesStyle = ( - seriesKey: string, - color: string | undefined, - seriesType?: SeriesType -) => { - if (!color) return undefined; - const customSeriesColors: CustomSeriesColorsMap = new Map(); - const dataSeriesColorValues: DataSeriesColorsValues = { - colorValues: seriesType === SeriesType.BAR ? [seriesKey] : [], - specId: getSpecId(seriesKey), - }; - - customSeriesColors.set(dataSeriesColorValues, color); - - return customSeriesColors; -}; - // Apply margins and paddings: https://ela.st/charts-spacing const theme: PartialTheme = { chartMargins: { @@ -129,11 +107,6 @@ export const chartDefaultSettings = { debug: false, }; -export const useBrowserTimeZone = () => { - const kibanaTimezone = useUiSetting(DEFAULT_DATE_FORMAT_TZ); - return kibanaTimezone === 'Browser' ? moment.tz.guess() : kibanaTimezone; -}; - export const getChartHeight = (customHeight?: number, autoSizerHeight?: number): string => { const height = customHeight || autoSizerHeight; return height ? `${height}px` : defaultChartHeight; diff --git a/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar.test.tsx b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar.test.tsx index a5eac381f9215..eae0fc4ff422b 100644 --- a/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar.test.tsx @@ -6,7 +6,6 @@ import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import React from 'react'; import { TestProviders } from '../../../mock'; @@ -44,7 +43,7 @@ describe('UtilityBar', () => { ); - expect(toJson(wrapper.find('UtilityBar'))).toMatchSnapshot(); + expect(wrapper.find('UtilityBar')).toMatchSnapshot(); }); test('it applies border styles when border is true', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_action.test.tsx b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_action.test.tsx index 2610fb44532f5..2a8a71955a986 100644 --- a/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_action.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_action.test.tsx @@ -5,7 +5,6 @@ */ import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import React from 'react'; import { TestProviders } from '../../../mock'; @@ -19,7 +18,7 @@ describe('UtilityBarAction', () => { ); - expect(toJson(wrapper.find('UtilityBarAction'))).toMatchSnapshot(); + expect(wrapper.find('UtilityBarAction')).toMatchSnapshot(); }); test('it renders a popover', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_group.test.tsx b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_group.test.tsx index 59ef7021d4049..e18e7d5e0b524 100644 --- a/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_group.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_group.test.tsx @@ -5,7 +5,6 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import React from 'react'; import { TestProviders } from '../../../mock'; @@ -21,6 +20,6 @@ describe('UtilityBarGroup', () => { ); - expect(toJson(wrapper.find('UtilityBarGroup'))).toMatchSnapshot(); + expect(wrapper.find('UtilityBarGroup')).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_section.test.tsx b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_section.test.tsx index baa4331ced8f8..f849fa4b4ee46 100644 --- a/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_section.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_section.test.tsx @@ -5,7 +5,6 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import React from 'react'; import { TestProviders } from '../../../mock'; @@ -23,6 +22,6 @@ describe('UtilityBarSection', () => { ); - expect(toJson(wrapper.find('UtilityBarSection'))).toMatchSnapshot(); + expect(wrapper.find('UtilityBarSection')).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_text.test.tsx b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_text.test.tsx index 794f207fd88e3..230dd80b1a86b 100644 --- a/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_text.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/detection_engine/utility_bar/utility_bar_text.test.tsx @@ -5,7 +5,6 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import React from 'react'; import { TestProviders } from '../../../mock'; @@ -19,6 +18,6 @@ describe('UtilityBarText', () => { ); - expect(toJson(wrapper.find('UtilityBarText'))).toMatchSnapshot(); + expect(wrapper.find('UtilityBarText')).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/direction/index.tsx b/x-pack/legacy/plugins/siem/public/components/direction/index.tsx index 9295e055f918d..ad1e63dbd7e6a 100644 --- a/x-pack/legacy/plugins/siem/public/components/direction/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/direction/index.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import { NetworkDirectionEcs } from '../../graphql/types'; import { DraggableBadge } from '../draggables'; diff --git a/x-pack/legacy/plugins/siem/public/components/drag_and_drop/drag_drop_context_wrapper.test.tsx b/x-pack/legacy/plugins/siem/public/components/drag_and_drop/drag_drop_context_wrapper.test.tsx index 1a8af9d99193a..9e8bde8d9ff92 100644 --- a/x-pack/legacy/plugins/siem/public/components/drag_and_drop/drag_drop_context_wrapper.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/drag_and_drop/drag_drop_context_wrapper.test.tsx @@ -5,8 +5,7 @@ */ import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { MockedProvider } from 'react-apollo/test-utils'; import { mockBrowserFields, mocksSource } from '../../containers/source/mock'; @@ -28,7 +27,7 @@ describe('DragDropContextWrapper', () => { ); - expect(toJson(wrapper.find('DragDropContextWrapper'))).toMatchSnapshot(); + expect(wrapper.find('DragDropContextWrapper')).toMatchSnapshot(); }); test('it renders the children', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/drag_and_drop/draggable_wrapper.test.tsx b/x-pack/legacy/plugins/siem/public/components/drag_and_drop/draggable_wrapper.test.tsx index 4b546bca1f72e..e846c923c5cbe 100644 --- a/x-pack/legacy/plugins/siem/public/components/drag_and_drop/draggable_wrapper.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/drag_and_drop/draggable_wrapper.test.tsx @@ -5,8 +5,7 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { MockedProvider } from 'react-apollo/test-utils'; import { mockBrowserFields, mocksSource } from '../../containers/source/mock'; @@ -33,7 +32,7 @@ describe('DraggableWrapper', () => { ); - expect(toJson(wrapper.find('DraggableWrapper'))).toMatchSnapshot(); + expect(wrapper.find('DraggableWrapper')).toMatchSnapshot(); }); test('it renders the children passed to the render prop', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/drag_and_drop/draggable_wrapper.tsx b/x-pack/legacy/plugins/siem/public/components/drag_and_drop/draggable_wrapper.tsx index 4faf02ead3fe1..9672097713a9b 100644 --- a/x-pack/legacy/plugins/siem/public/components/drag_and_drop/draggable_wrapper.tsx +++ b/x-pack/legacy/plugins/siem/public/components/drag_and_drop/draggable_wrapper.tsx @@ -13,15 +13,15 @@ import { Droppable, } from 'react-beautiful-dnd'; import { connect } from 'react-redux'; -import styled, { css } from 'styled-components'; +import styled from 'styled-components'; import { ActionCreator } from 'typescript-fsa'; import { EuiPortal } from '@elastic/eui'; import { dragAndDropActions } from '../../store/drag_and_drop'; import { DataProvider } from '../timeline/data_providers/data_provider'; -import { STATEFUL_EVENT_CSS_CLASS_NAME } from '../timeline/helpers'; import { TruncatableText } from '../truncatable_text'; import { getDraggableId, getDroppableId } from './helpers'; +import { ProviderContainer } from './provider_container'; // As right now, we do not know what we want there, we will keep it as a placeholder export const DragEffects = styled.div``; @@ -42,143 +42,12 @@ const Wrapper = styled.div` Wrapper.displayName = 'Wrapper'; -const ProviderContainer = styled.div<{ isDragging: boolean }>` - &, - &::before, - &::after { - transition: background ${({ theme }) => theme.eui.euiAnimSpeedFast} ease, - color ${({ theme }) => theme.eui.euiAnimSpeedFast} ease; +const ProviderContentWrapper = styled.span` + > span.euiToolTipAnchor { + display: block; /* allow EuiTooltip content to be truncatable */ } - - ${({ isDragging }) => - !isDragging && - css` - & { - border-radius: 2px; - padding: 0 4px 0 8px; - position: relative; - z-index: ${({ theme }) => theme.eui.euiZLevel0} !important; - - &::before { - background-image: linear-gradient( - 135deg, - ${({ theme }) => theme.eui.euiColorMediumShade} 25%, - transparent 25% - ), - linear-gradient( - -135deg, - ${({ theme }) => theme.eui.euiColorMediumShade} 25%, - transparent 25% - ), - linear-gradient( - 135deg, - transparent 75%, - ${({ theme }) => theme.eui.euiColorMediumShade} 75% - ), - linear-gradient( - -135deg, - transparent 75%, - ${({ theme }) => theme.eui.euiColorMediumShade} 75% - ); - background-position: 0 0, 1px 0, 1px -1px, 0px 1px; - background-size: 2px 2px; - bottom: 2px; - content: ''; - display: block; - left: 2px; - position: absolute; - top: 2px; - width: 4px; - } - } - - &:hover { - &, - & .euiBadge, - & .euiBadge__text { - cursor: move; /* Fallback for IE11 */ - cursor: grab; - } - } - - .${STATEFUL_EVENT_CSS_CLASS_NAME}:hover &, - tr:hover & { - background-color: ${({ theme }) => theme.eui.euiColorLightShade}; - - &::before { - background-image: linear-gradient( - 135deg, - ${({ theme }) => theme.eui.euiColorDarkShade} 25%, - transparent 25% - ), - linear-gradient( - -135deg, - ${({ theme }) => theme.eui.euiColorDarkShade} 25%, - transparent 25% - ), - linear-gradient( - 135deg, - transparent 75%, - ${({ theme }) => theme.eui.euiColorDarkShade} 75% - ), - linear-gradient( - -135deg, - transparent 75%, - ${({ theme }) => theme.eui.euiColorDarkShade} 75% - ); - } - } - - &:hover, - &:focus, - .${STATEFUL_EVENT_CSS_CLASS_NAME}:hover &:hover, - .${STATEFUL_EVENT_CSS_CLASS_NAME}:focus &:focus, - tr:hover &:hover, - tr:hover &:focus { - background-color: ${({ theme }) => theme.eui.euiColorPrimary}; - - &, - & a, - & a:hover { - color: ${({ theme }) => theme.eui.euiColorEmptyShade}; - } - - &::before { - background-image: linear-gradient( - 135deg, - ${({ theme }) => theme.eui.euiColorEmptyShade} 25%, - transparent 25% - ), - linear-gradient( - -135deg, - ${({ theme }) => theme.eui.euiColorEmptyShade} 25%, - transparent 25% - ), - linear-gradient( - 135deg, - transparent 75%, - ${({ theme }) => theme.eui.euiColorEmptyShade} 75% - ), - linear-gradient( - -135deg, - transparent 75%, - ${({ theme }) => theme.eui.euiColorEmptyShade} 75% - ); - } - } - `} - - ${({ isDragging }) => - isDragging && - css` - & { - z-index: 9999 !important; - } - `} `; -ProviderContainer.displayName = 'ProviderContainer'; - interface OwnProps { dataProvider: DataProvider; inline?: boolean; @@ -244,9 +113,11 @@ const DraggableWrapperComponent = React.memo( {render(dataProvider, provided, snapshot)} ) : ( - + {render(dataProvider, provided, snapshot)} - + )} diff --git a/x-pack/legacy/plugins/siem/public/components/drag_and_drop/droppable_wrapper.test.tsx b/x-pack/legacy/plugins/siem/public/components/drag_and_drop/droppable_wrapper.test.tsx index 056669673bb9e..bd2f01721290f 100644 --- a/x-pack/legacy/plugins/siem/public/components/drag_and_drop/droppable_wrapper.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/drag_and_drop/droppable_wrapper.test.tsx @@ -5,8 +5,7 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { MockedProvider } from 'react-apollo/test-utils'; import { mockBrowserFields, mocksSource } from '../../containers/source/mock'; @@ -33,7 +32,7 @@ describe('DroppableWrapper', () => { ); - expect(toJson(wrapper.find('DroppableWrapper'))).toMatchSnapshot(); + expect(wrapper.find('DroppableWrapper')).toMatchSnapshot(); }); test('it renders the children when a render prop is not provided', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/drag_and_drop/droppable_wrapper.tsx b/x-pack/legacy/plugins/siem/public/components/drag_and_drop/droppable_wrapper.tsx index c660ac6adaa71..821ef9be10e8d 100644 --- a/x-pack/legacy/plugins/siem/public/components/drag_and_drop/droppable_wrapper.tsx +++ b/x-pack/legacy/plugins/siem/public/components/drag_and_drop/droppable_wrapper.tsx @@ -5,7 +5,7 @@ */ import { rgba } from 'polished'; -import * as React from 'react'; +import React from 'react'; import { Droppable } from 'react-beautiful-dnd'; import styled from 'styled-components'; diff --git a/x-pack/legacy/plugins/siem/public/components/drag_and_drop/provider_container.tsx b/x-pack/legacy/plugins/siem/public/components/drag_and_drop/provider_container.tsx new file mode 100644 index 0000000000000..c1f029086aa35 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/drag_and_drop/provider_container.tsx @@ -0,0 +1,154 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import styled, { css } from 'styled-components'; +import { STATEFUL_EVENT_CSS_CLASS_NAME } from '../timeline/helpers'; + +interface ProviderContainerProps { + isDragging: boolean; +} + +const ProviderContainerComponent = styled.div` + &, + &::before, + &::after { + transition: background ${({ theme }) => theme.eui.euiAnimSpeedFast} ease, + color ${({ theme }) => theme.eui.euiAnimSpeedFast} ease; + } + + ${({ isDragging }) => + !isDragging && + css` + & { + border-radius: 2px; + padding: 0 4px 0 8px; + position: relative; + z-index: ${({ theme }) => theme.eui.euiZLevel0} !important; + + &::before { + background-image: linear-gradient( + 135deg, + ${({ theme }) => theme.eui.euiColorMediumShade} 25%, + transparent 25% + ), + linear-gradient( + -135deg, + ${({ theme }) => theme.eui.euiColorMediumShade} 25%, + transparent 25% + ), + linear-gradient( + 135deg, + transparent 75%, + ${({ theme }) => theme.eui.euiColorMediumShade} 75% + ), + linear-gradient( + -135deg, + transparent 75%, + ${({ theme }) => theme.eui.euiColorMediumShade} 75% + ); + background-position: 0 0, 1px 0, 1px -1px, 0px 1px; + background-size: 2px 2px; + bottom: 2px; + content: ''; + display: block; + left: 2px; + position: absolute; + top: 2px; + width: 4px; + } + } + + &:hover { + &, + & .euiBadge, + & .euiBadge__text { + cursor: move; /* Fallback for IE11 */ + cursor: grab; + } + } + + .${STATEFUL_EVENT_CSS_CLASS_NAME}:hover &, + tr:hover & { + background-color: ${({ theme }) => theme.eui.euiColorLightShade}; + + &::before { + background-image: linear-gradient( + 135deg, + ${({ theme }) => theme.eui.euiColorDarkShade} 25%, + transparent 25% + ), + linear-gradient( + -135deg, + ${({ theme }) => theme.eui.euiColorDarkShade} 25%, + transparent 25% + ), + linear-gradient( + 135deg, + transparent 75%, + ${({ theme }) => theme.eui.euiColorDarkShade} 75% + ), + linear-gradient( + -135deg, + transparent 75%, + ${({ theme }) => theme.eui.euiColorDarkShade} 75% + ); + } + } + + &:hover, + &:focus, + .${STATEFUL_EVENT_CSS_CLASS_NAME}:hover &:hover, + .${STATEFUL_EVENT_CSS_CLASS_NAME}:focus &:focus, + tr:hover &:hover, + tr:hover &:focus { + background-color: ${({ theme }) => theme.eui.euiColorPrimary}; + + &, + & a, + & a:hover { + color: ${({ theme }) => theme.eui.euiColorEmptyShade}; + } + + &::before { + background-image: linear-gradient( + 135deg, + ${({ theme }) => theme.eui.euiColorEmptyShade} 25%, + transparent 25% + ), + linear-gradient( + -135deg, + ${({ theme }) => theme.eui.euiColorEmptyShade} 25%, + transparent 25% + ), + linear-gradient( + 135deg, + transparent 75%, + ${({ theme }) => theme.eui.euiColorEmptyShade} 75% + ), + linear-gradient( + -135deg, + transparent 75%, + ${({ theme }) => theme.eui.euiColorEmptyShade} 75% + ); + } + } + `} + + ${({ isDragging }) => + isDragging && + css` + & { + z-index: 9999 !important; + } + `} +`; + +ProviderContainerComponent.displayName = 'ProviderContainerComponent'; + +export const ProviderContainer = React.memo(ProviderContainerComponent); + +ProviderContainer.displayName = 'ProviderContainer'; diff --git a/x-pack/legacy/plugins/siem/public/components/draggables/field_badge/index.tsx b/x-pack/legacy/plugins/siem/public/components/draggables/field_badge/index.tsx index 90d8ad463b476..ba0d53210bace 100644 --- a/x-pack/legacy/plugins/siem/public/components/draggables/field_badge/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/draggables/field_badge/index.tsx @@ -5,7 +5,7 @@ */ import { rgba } from 'polished'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; const Field = styled.div` diff --git a/x-pack/legacy/plugins/siem/public/components/draggables/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/draggables/index.test.tsx index f1ed533bef545..76335e3c72306 100644 --- a/x-pack/legacy/plugins/siem/public/components/draggables/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/draggables/index.test.tsx @@ -5,8 +5,7 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { TestProviders } from '../../mock'; import { getEmptyString } from '../empty_value'; @@ -34,7 +33,7 @@ describe('draggables', () => { {'A child of this'} ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it renders the default Badge', () => { @@ -50,7 +49,7 @@ describe('draggables', () => { {'A child of this'} ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/draggables/index.tsx b/x-pack/legacy/plugins/siem/public/components/draggables/index.tsx index 5b219dad9c841..57f047416ec1c 100644 --- a/x-pack/legacy/plugins/siem/public/components/draggables/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/draggables/index.tsx @@ -5,7 +5,7 @@ */ import { EuiBadge, EuiBadgeProps, EuiToolTip, IconType } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import { Omit } from '../../../common/utility_types'; import { DragEffects, DraggableWrapper } from '../drag_and_drop/draggable_wrapper'; @@ -105,12 +105,9 @@ export const DefaultDraggable = React.memo( ) : ( - + + {children} + ) } /> diff --git a/x-pack/legacy/plugins/siem/public/components/duration/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/duration/index.test.tsx index 140a625bc53fe..0dbc60ad9ae52 100644 --- a/x-pack/legacy/plugins/siem/public/components/duration/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/duration/index.test.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import { TestProviders } from '../../mock'; import { ONE_MILLISECOND_AS_NANOSECONDS } from '../formatted_duration/helpers'; diff --git a/x-pack/legacy/plugins/siem/public/components/duration/index.tsx b/x-pack/legacy/plugins/siem/public/components/duration/index.tsx index 15e6246f1f1ad..76712b789ffbe 100644 --- a/x-pack/legacy/plugins/siem/public/components/duration/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/duration/index.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import { DefaultDraggable } from '../draggables'; import { FormattedDuration } from '../formatted_duration'; diff --git a/x-pack/legacy/plugins/siem/public/components/edit_data_provider/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/edit_data_provider/index.test.tsx index 7c515862b0d92..1786905a4bb48 100644 --- a/x-pack/legacy/plugins/siem/public/components/edit_data_provider/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/edit_data_provider/index.test.tsx @@ -5,7 +5,7 @@ */ import { mount } from 'enzyme'; -import * as React from 'react'; +import React from 'react'; import { mockBrowserFields } from '../../containers/source/mock'; import { TestProviders } from '../../mock'; diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/embeddable.test.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/embeddable.test.tsx index 884d5bc348d6f..2dc3d8828675f 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/embeddable.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/embeddable.test.tsx @@ -5,7 +5,6 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import React from 'react'; import { Embeddable } from './embeddable'; @@ -18,6 +17,6 @@ describe('Embeddable', () => { ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/embeddable_header.test.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/embeddable_header.test.tsx index aa247b69eb4eb..3b8e137618ab0 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/embeddable_header.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/embeddable_header.test.tsx @@ -5,7 +5,6 @@ */ import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import React from 'react'; import { TestProviders } from '../../mock'; @@ -15,7 +14,7 @@ describe('EmbeddableHeader', () => { test('it renders', () => { const wrapper = shallow(); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it renders the title', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map.test.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map.test.tsx index 007916595fd6a..c752273777d2f 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map.test.tsx @@ -5,8 +5,7 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { useIndexPatterns } from '../../hooks/use_index_patterns'; import { EmbeddedMapComponent } from './embedded_map'; @@ -41,6 +40,6 @@ describe('EmbeddedMapComponent', () => { startDate={new Date('2019-08-28T05:50:47.877Z').getTime()} /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map.tsx index b39d43cc01b42..771e220a2a0b3 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map.tsx +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map.tsx @@ -15,10 +15,10 @@ import { DEFAULT_INDEX_KEY } from '../../../common/constants'; import { getIndexPatternTitleIdMapping } from '../../hooks/api/helpers'; import { useIndexPatterns } from '../../hooks/use_index_patterns'; import { Loader } from '../loader'; -import { useStateToaster } from '../toasters'; +import { displayErrorToast, useStateToaster } from '../toasters'; import { Embeddable } from './embeddable'; import { EmbeddableHeader } from './embeddable_header'; -import { createEmbeddable, displayErrorToast } from './embedded_map_helpers'; +import { createEmbeddable } from './embedded_map_helpers'; import { IndexPatternsMissingPrompt } from './index_patterns_missing_prompt'; import { MapToolTip } from './map_tool_tip/map_tool_tip'; import * as i18n from './translations'; @@ -134,7 +134,7 @@ export const EmbeddedMapComponent = ({ } } catch (e) { if (isSubscribed) { - displayErrorToast(i18n.ERROR_CREATING_EMBEDDABLE, e.message, dispatchToaster); + displayErrorToast(i18n.ERROR_CREATING_EMBEDDABLE, [e.message], dispatchToaster); setIsError(true); } } diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map_helpers.test.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map_helpers.test.tsx index 4e5fcee439827..a83e8377deeb6 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map_helpers.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map_helpers.test.tsx @@ -4,19 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { createEmbeddable, displayErrorToast } from './embedded_map_helpers'; +import { createEmbeddable } from './embedded_map_helpers'; import { createUiNewPlatformMock } from 'ui/new_platform/__mocks__/helpers'; import { createPortalNode } from 'react-reverse-portal'; jest.mock('ui/new_platform'); -jest.mock('uuid', () => { - return { - v1: jest.fn(() => '27261ae0-0bbb-11ea-b0ea-db767b07ea47'), - v4: jest.fn(() => '9e1f72a9-7c73-4b7f-a562-09940f7daf4a'), - }; -}); - const { npStart } = createUiNewPlatformMock(); npStart.plugins.embeddable.getEmbeddableFactory = jest.fn().mockImplementation(() => ({ createFromState: () => ({ @@ -25,24 +18,6 @@ npStart.plugins.embeddable.getEmbeddableFactory = jest.fn().mockImplementation(( })); describe('embedded_map_helpers', () => { - describe('displayErrorToast', () => { - test('dispatches toast with correct title and message', () => { - const mockToast = { - toast: { - color: 'danger', - errors: ['message'], - iconType: 'alert', - id: '9e1f72a9-7c73-4b7f-a562-09940f7daf4a', - title: 'Title', - }, - type: 'addToaster', - }; - const dispatchToasterMock = jest.fn(); - displayErrorToast('Title', 'message', dispatchToasterMock); - expect(dispatchToasterMock.mock.calls[0][0]).toEqual(mockToast); - }); - }); - describe('createEmbeddable', () => { test('attaches refresh action', async () => { const setQueryMock = jest.fn(); diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map_helpers.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map_helpers.tsx index b9a9df9824eee..838e74cc5624c 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map_helpers.tsx +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/embedded_map_helpers.tsx @@ -7,7 +7,6 @@ import uuid from 'uuid'; import React from 'react'; import { OutPortal, PortalNode } from 'react-reverse-portal'; -import { ActionToaster, AppToast } from '../toasters'; import { ViewMode } from '../../../../../../../src/legacy/core_plugins/embeddable_api/public/np_ready/public'; import { IndexPatternMapping, @@ -22,31 +21,6 @@ import { MAP_SAVED_OBJECT_TYPE } from '../../../../maps/common/constants'; import * as i18n from './translations'; import { Query, esFilters } from '../../../../../../../src/plugins/data/public'; -/** - * Displays an error toast for the provided title and message - * - * @param errorTitle Title of error to display in toaster and modal - * @param errorMessage Message to display in error modal when clicked - * @param dispatchToaster provided by useStateToaster() - */ -export const displayErrorToast = ( - errorTitle: string, - errorMessage: string, - dispatchToaster: React.Dispatch -) => { - const toast: AppToast = { - id: uuid.v4(), - title: errorTitle, - color: 'danger', - iconType: 'alert', - errors: [errorMessage], - }; - dispatchToaster({ - type: 'addToaster', - toast, - }); -}; - /** * Creates MapEmbeddable with provided initial configuration * diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/index_patterns_missing_prompt.test.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/index_patterns_missing_prompt.test.tsx index d04329edff475..4f617644a1fe1 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/index_patterns_missing_prompt.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/index_patterns_missing_prompt.test.tsx @@ -5,8 +5,7 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { IndexPatternsMissingPromptComponent } from './index_patterns_missing_prompt'; @@ -15,6 +14,6 @@ jest.mock('../../lib/kibana'); describe('IndexPatternsMissingPrompt', () => { test('renders correctly against snapshot', () => { const wrapper = shallow(); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/index_patterns_missing_prompt.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/index_patterns_missing_prompt.tsx index 798e3d2c10f97..a4f95d2e299ad 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/index_patterns_missing_prompt.tsx +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/index_patterns_missing_prompt.tsx @@ -6,7 +6,7 @@ import { EuiButton, EuiCode, EuiEmptyPrompt } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import * as React from 'react'; +import React from 'react'; import chrome from 'ui/chrome'; import { useKibana } from '../../lib/kibana'; diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/line_tool_tip_content.test.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/line_tool_tip_content.test.tsx index c43ab1ff4a036..824c717427763 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/line_tool_tip_content.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/line_tool_tip_content.test.tsx @@ -5,8 +5,7 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { LineToolTipContentComponent } from './line_tool_tip_content'; import { FeatureProperty } from '../types'; import { SUM_OF_DESTINATION_BYTES, SUM_OF_SOURCE_BYTES } from '../map_config'; @@ -27,6 +26,6 @@ describe('LineToolTipContent', () => { const wrapper = shallow( ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/map_tool_tip.test.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/map_tool_tip.test.tsx index 13eefb252fb04..2daaeb53e45f2 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/map_tool_tip.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/map_tool_tip.test.tsx @@ -5,8 +5,7 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { MapToolTipComponent } from './map_tool_tip'; import { MapFeature } from '../types'; @@ -19,7 +18,7 @@ jest.mock('../../search_bar', () => ({ describe('MapToolTip', () => { test('placeholder component renders correctly against snapshot', () => { const wrapper = shallow(); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('full component renders correctly against snapshot', () => { @@ -46,6 +45,6 @@ describe('MapToolTip', () => { loadFeatureGeometry={loadFeatureGeometry} /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/point_tool_tip_content.test.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/point_tool_tip_content.test.tsx index 929b4983b5fd7..8741cfaa26ca6 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/point_tool_tip_content.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/point_tool_tip_content.test.tsx @@ -5,8 +5,7 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { FeatureProperty } from '../types'; import { getRenderedFieldValue, PointToolTipContentComponent } from './point_tool_tip_content'; import { TestProviders } from '../../../mock'; @@ -49,7 +48,7 @@ describe('PointToolTipContent', () => { /> ); - expect(toJson(wrapper.find('PointToolTipContentComponent'))).toMatchSnapshot(); + expect(wrapper.find('PointToolTipContentComponent')).toMatchSnapshot(); }); test('renders array filter correctly', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/tooltip_footer.test.tsx b/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/tooltip_footer.test.tsx index 4c77570cfbc9f..7351ea0a183c3 100644 --- a/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/tooltip_footer.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/embeddables/map_tool_tip/tooltip_footer.test.tsx @@ -5,8 +5,7 @@ */ import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { ToolTipFooterComponent } from './tooltip_footer'; describe('ToolTipFilter', () => { @@ -27,7 +26,7 @@ describe('ToolTipFilter', () => { totalFeatures={100} /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); describe('Lower bounds', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/empty_page/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/empty_page/__snapshots__/index.test.tsx.snap index 9b6bfb1752a20..87409c5fdebe0 100644 --- a/x-pack/legacy/plugins/siem/public/components/empty_page/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/empty_page/__snapshots__/index.test.tsx.snap @@ -3,8 +3,12 @@ exports[`renders correctly 1`] = ` - + + { title="My Super Title" /> ); - expect(toJson(EmptyComponent)).toMatchSnapshot(); + expect(EmptyComponent).toMatchSnapshot(); }); diff --git a/x-pack/legacy/plugins/siem/public/components/empty_page/index.tsx b/x-pack/legacy/plugins/siem/public/components/empty_page/index.tsx index 8f0aa2d3479cc..f2b0ec1ab5e60 100644 --- a/x-pack/legacy/plugins/siem/public/components/empty_page/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/empty_page/index.tsx @@ -46,8 +46,8 @@ export const EmptyPage = React.memo( title={

{title}

} body={message &&

{message}

} actions={ - - + + ( {actionSecondaryLabel && actionSecondaryUrl && ( - + { test('it renders against snapshot', () => { const wrapper = shallow(

{getEmptyString()}

); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); describe('#getEmptyValue', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/error_toast_dispatcher/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/error_toast_dispatcher/index.test.tsx index 6233fcfe7c823..6b90d9ccd08c4 100644 --- a/x-pack/legacy/plugins/siem/public/components/error_toast_dispatcher/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/error_toast_dispatcher/index.test.tsx @@ -5,8 +5,7 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { Provider } from 'react-redux'; import { apolloClientObservable, mockGlobalState } from '../../mock'; @@ -30,7 +29,7 @@ describe('Error Toast Dispatcher', () => {
); - expect(toJson(wrapper.find('Connect(ErrorToastDispatcherComponent)'))).toMatchSnapshot(); + expect(wrapper.find('Connect(ErrorToastDispatcherComponent)')).toMatchSnapshot(); }); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/event_details/columns.tsx b/x-pack/legacy/plugins/siem/public/components/event_details/columns.tsx index d835d2c621931..1962850425baa 100644 --- a/x-pack/legacy/plugins/siem/public/components/event_details/columns.tsx +++ b/x-pack/legacy/plugins/siem/public/components/event_details/columns.tsx @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +/* eslint-disable react/display-name */ + import { EuiCheckbox, EuiFlexGroup, @@ -13,7 +15,7 @@ import { EuiText, EuiToolTip, } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import { Draggable } from 'react-beautiful-dnd'; import styled from 'styled-components'; @@ -23,16 +25,14 @@ import { WithCopyToClipboard } from '../../lib/clipboard/with_copy_to_clipboard' import { DragEffects } from '../drag_and_drop/draggable_wrapper'; import { DroppableWrapper } from '../drag_and_drop/droppable_wrapper'; import { getDroppableId, getDraggableFieldId, DRAG_TYPE_FIELD } from '../drag_and_drop/helpers'; -import { DefaultDraggable } from '../draggables'; import { DraggableFieldBadge } from '../draggables/field_badge'; -import { EVENT_DURATION_FIELD_NAME } from '../duration'; import { FieldName } from '../fields_browser/field_name'; import { SelectableText } from '../selectable_text'; import { OverflowField } from '../tables/helpers'; import { ColumnHeader } from '../timeline/body/column_headers/column_header'; import { defaultColumnHeaderType } from '../timeline/body/column_headers/default_headers'; import { DEFAULT_COLUMN_MIN_WIDTH } from '../timeline/body/helpers'; -import { DATE_FIELD_TYPE, MESSAGE_FIELD_NAME } from '../timeline/body/renderers/constants'; +import { MESSAGE_FIELD_NAME } from '../timeline/body/renderers/constants'; import { FormattedFieldValue } from '../timeline/body/renderers/formatted_field'; import { OnUpdateColumns } from '../timeline/events'; import { WithHoverActions } from '../with_hover_actions'; @@ -180,28 +180,14 @@ export const getColumns = ({ data.field === MESSAGE_FIELD_NAME ? ( ) : ( - - - - - + /> ) } /> diff --git a/x-pack/legacy/plugins/siem/public/components/event_details/event_details.test.tsx b/x-pack/legacy/plugins/siem/public/components/event_details/event_details.test.tsx index d97da7797bb45..162fc8fd8bb34 100644 --- a/x-pack/legacy/plugins/siem/public/components/event_details/event_details.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/event_details/event_details.test.tsx @@ -5,8 +5,7 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { mockDetailItemData, mockDetailItemDataId } from '../../mock/mock_detail_item'; import { TestProviders } from '../../mock/test_providers'; @@ -34,7 +33,7 @@ describe('EventDetails', () => { toggleColumn={jest.fn()} /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/event_details/event_fields_browser.test.tsx b/x-pack/legacy/plugins/siem/public/components/event_details/event_fields_browser.test.tsx index 25f95bfa1d383..5b18e2d9b1fbc 100644 --- a/x-pack/legacy/plugins/siem/public/components/event_details/event_fields_browser.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/event_details/event_fields_browser.test.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import { mockDetailItemData, mockDetailItemDataId } from '../../mock/mock_detail_item'; import { TestProviders } from '../../mock/test_providers'; @@ -14,8 +14,6 @@ import { mockBrowserFields } from '../../containers/source/mock'; import { defaultHeaders } from '../../mock/header'; import { useMountAppended } from '../../utils/use_mount_appended'; -jest.mock('../../lib/kibana'); - describe('EventFieldsBrowser', () => { const mount = useMountAppended(); diff --git a/x-pack/legacy/plugins/siem/public/components/event_details/event_fields_browser.tsx b/x-pack/legacy/plugins/siem/public/components/event_details/event_fields_browser.tsx index cec8ddff1a7c9..f4e9a2b789d71 100644 --- a/x-pack/legacy/plugins/siem/public/components/event_details/event_fields_browser.tsx +++ b/x-pack/legacy/plugins/siem/public/components/event_details/event_fields_browser.tsx @@ -6,7 +6,7 @@ import { sortBy } from 'lodash'; import { EuiInMemoryTable } from '@elastic/eui'; -import * as React from 'react'; +import React, { useMemo } from 'react'; import { ColumnHeader } from '../timeline/body/column_headers/column_header'; import { BrowserFields, getAllFieldsByName } from '../../containers/source'; @@ -29,26 +29,35 @@ interface Props { /** Renders a table view or JSON view of the `ECS` `data` */ export const EventFieldsBrowser = React.memo( ({ browserFields, columnHeaders, data, eventId, onUpdateColumns, timelineId, toggleColumn }) => { - const fieldsByName = getAllFieldsByName(browserFields); + const fieldsByName = useMemo(() => getAllFieldsByName(browserFields), [browserFields]); + const items = useMemo( + () => + sortBy(data, ['field']).map(item => ({ + ...item, + ...fieldsByName[item.field], + valuesConcatenated: item.values != null ? item.values.join() : '', + })), + [data, fieldsByName] + ); + const columns = useMemo( + () => + getColumns({ + browserFields, + columnHeaders, + eventId, + onUpdateColumns, + contextId: timelineId, + toggleColumn, + }), + [browserFields, columnHeaders, eventId, onUpdateColumns, timelineId, toggleColumn] + ); + return (
, column `render` callbacks expect complete BrowserField - items={sortBy(data, ['field']).map(item => { - return { - ...item, - ...fieldsByName[item.field], - valuesConcatenated: item.values != null ? item.values.join() : '', - }; - })} - columns={getColumns({ - browserFields, - columnHeaders, - eventId, - onUpdateColumns, - contextId: timelineId, - toggleColumn, - })} + items={items} + columns={columns} pagination={false} search={search} sorting={true} diff --git a/x-pack/legacy/plugins/siem/public/components/event_details/json_view.test.tsx b/x-pack/legacy/plugins/siem/public/components/event_details/json_view.test.tsx index 429fc94b2f2d3..0cf158c8ea90b 100644 --- a/x-pack/legacy/plugins/siem/public/components/event_details/json_view.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/event_details/json_view.test.tsx @@ -5,8 +5,7 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { mockDetailItemData } from '../../mock'; @@ -16,7 +15,7 @@ describe('JSON View', () => { describe('rendering', () => { test('should match snapshot', () => { const wrapper = shallow(); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/event_details/json_view.tsx b/x-pack/legacy/plugins/siem/public/components/event_details/json_view.tsx index 519f56adff2d2..9897e319e0487 100644 --- a/x-pack/legacy/plugins/siem/public/components/event_details/json_view.tsx +++ b/x-pack/legacy/plugins/siem/public/components/event_details/json_view.tsx @@ -6,7 +6,7 @@ import { EuiCodeEditor } from '@elastic/eui'; import { set } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { DetailItem } from '../../graphql/types'; diff --git a/x-pack/legacy/plugins/siem/public/components/events_viewer/events_viewer.test.tsx b/x-pack/legacy/plugins/siem/public/components/events_viewer/events_viewer.test.tsx index b44d83c27a60d..3cef3e98c2f0a 100644 --- a/x-pack/legacy/plugins/siem/public/components/events_viewer/events_viewer.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/events_viewer/events_viewer.test.tsx @@ -18,8 +18,6 @@ import { mockBrowserFields } from '../../containers/source/mock'; import { eventsDefaultModel } from './default_model'; import { useMountAppended } from '../../utils/use_mount_appended'; -jest.mock('../../lib/kibana'); - const mockUseFetchIndexPatterns: jest.Mock = useFetchIndexPatterns as jest.Mock; jest.mock('../../containers/detection_engine/rules/fetch_index_patterns'); mockUseFetchIndexPatterns.mockImplementation(() => [ diff --git a/x-pack/legacy/plugins/siem/public/components/events_viewer/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/events_viewer/index.test.tsx index 27c3abf7f6824..1e225dabb2541 100644 --- a/x-pack/legacy/plugins/siem/public/components/events_viewer/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/events_viewer/index.test.tsx @@ -17,8 +17,6 @@ import { useFetchIndexPatterns } from '../../containers/detection_engine/rules/f import { mockBrowserFields } from '../../containers/source/mock'; import { eventsDefaultModel } from './default_model'; -jest.mock('../../lib/kibana'); - const mockUseFetchIndexPatterns: jest.Mock = useFetchIndexPatterns as jest.Mock; jest.mock('../../containers/detection_engine/rules/fetch_index_patterns'); mockUseFetchIndexPatterns.mockImplementation(() => [ diff --git a/x-pack/legacy/plugins/siem/public/components/external_link_icon/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/external_link_icon/index.test.tsx index 01317e754ad35..24118ace6796f 100644 --- a/x-pack/legacy/plugins/siem/public/components/external_link_icon/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/external_link_icon/index.test.tsx @@ -5,7 +5,7 @@ */ import { mount } from 'enzyme'; -import * as React from 'react'; +import React from 'react'; import { TestProviders } from '../../mock'; diff --git a/x-pack/legacy/plugins/siem/public/components/external_link_icon/index.tsx b/x-pack/legacy/plugins/siem/public/components/external_link_icon/index.tsx index bba32e72abc37..147d2e2d541f5 100644 --- a/x-pack/legacy/plugins/siem/public/components/external_link_icon/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/external_link_icon/index.tsx @@ -5,7 +5,7 @@ */ import { EuiIcon } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; const LinkIcon = styled(EuiIcon)` diff --git a/x-pack/legacy/plugins/siem/public/components/field_renderers/field_renderers.test.tsx b/x-pack/legacy/plugins/siem/public/components/field_renderers/field_renderers.test.tsx index e45f5dacb36a2..88d03d8db6761 100644 --- a/x-pack/legacy/plugins/siem/public/components/field_renderers/field_renderers.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/field_renderers/field_renderers.test.tsx @@ -5,8 +5,7 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { FlowTarget, GetIpOverviewQuery, HostEcsFields } from '../../graphql/types'; import { TestProviders } from '../../mock'; @@ -37,7 +36,7 @@ describe('Field Renderers', () => { locationRenderer(['source.geo.city_name', 'source.geo.region_name'], mockData.complete) ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it renders emptyTagValue when no fields provided', () => { @@ -61,7 +60,7 @@ describe('Field Renderers', () => { test('it renders correctly against snapshot', () => { const wrapper = shallow(dateRenderer(mockData.complete.source!.firstSeen)); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it renders emptyTagValue when invalid field provided', () => { @@ -79,7 +78,7 @@ describe('Field Renderers', () => { autonomousSystemRenderer(mockData.complete.source!.autonomousSystem!, FlowTarget.source) ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it renders emptyTagValue when non-string field provided', () => { @@ -111,7 +110,7 @@ describe('Field Renderers', () => { test('it renders correctly against snapshot', () => { const wrapper = shallow(hostNameRenderer(mockData.complete.host, '10.10.10.10')); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it renders emptyTagValue when non-matching IP is provided', () => { @@ -154,7 +153,7 @@ describe('Field Renderers', () => { test('it renders correctly against snapshot', () => { const wrapper = shallow(hostNameRenderer(mockData.complete.host, '10.10.10.10')); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it renders emptyTagValue when non-matching IP is provided', () => { @@ -188,7 +187,7 @@ describe('Field Renderers', () => { test('it renders correctly against snapshot', () => { const wrapper = shallow(whoisRenderer('10.10.10.10')); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); }); @@ -196,7 +195,7 @@ describe('Field Renderers', () => { test('it renders correctly against snapshot', () => { const wrapper = shallow({reputationRenderer('10.10.10.10')}); - expect(toJson(wrapper.find('DragDropContext'))).toMatchSnapshot(); + expect(wrapper.find('DragDropContext')).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/fields_browser/categories_pane.test.tsx b/x-pack/legacy/plugins/siem/public/components/fields_browser/categories_pane.test.tsx index 2d3accbcb55c8..361a0789135e4 100644 --- a/x-pack/legacy/plugins/siem/public/components/fields_browser/categories_pane.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/fields_browser/categories_pane.test.tsx @@ -5,7 +5,7 @@ */ import { mount } from 'enzyme'; -import * as React from 'react'; +import React from 'react'; import { ThemeProvider } from 'styled-components'; import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; diff --git a/x-pack/legacy/plugins/siem/public/components/fields_browser/categories_pane.tsx b/x-pack/legacy/plugins/siem/public/components/fields_browser/categories_pane.tsx index 3165580f435a2..d6972625821cf 100644 --- a/x-pack/legacy/plugins/siem/public/components/fields_browser/categories_pane.tsx +++ b/x-pack/legacy/plugins/siem/public/components/fields_browser/categories_pane.tsx @@ -5,7 +5,7 @@ */ import { EuiInMemoryTable, EuiTitle } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { BrowserFields } from '../../containers/source'; diff --git a/x-pack/legacy/plugins/siem/public/components/fields_browser/category.test.tsx b/x-pack/legacy/plugins/siem/public/components/fields_browser/category.test.tsx index 4e16997ba92f6..38eaf43977fa2 100644 --- a/x-pack/legacy/plugins/siem/public/components/fields_browser/category.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/fields_browser/category.test.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import { mockBrowserFields } from '../../containers/source/mock'; diff --git a/x-pack/legacy/plugins/siem/public/components/fields_browser/category.tsx b/x-pack/legacy/plugins/siem/public/components/fields_browser/category.tsx index 7b8451db2212f..9d2a7da9b2d00 100644 --- a/x-pack/legacy/plugins/siem/public/components/fields_browser/category.tsx +++ b/x-pack/legacy/plugins/siem/public/components/fields_browser/category.tsx @@ -5,7 +5,7 @@ */ import { EuiInMemoryTable } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { BrowserFields } from '../../containers/source'; diff --git a/x-pack/legacy/plugins/siem/public/components/fields_browser/category_columns.test.tsx b/x-pack/legacy/plugins/siem/public/components/fields_browser/category_columns.test.tsx index ce66a2d8d7919..cbead878f525d 100644 --- a/x-pack/legacy/plugins/siem/public/components/fields_browser/category_columns.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/fields_browser/category_columns.test.tsx @@ -5,7 +5,7 @@ */ import { mount } from 'enzyme'; -import * as React from 'react'; +import React from 'react'; import { mockBrowserFields } from '../../containers/source/mock'; diff --git a/x-pack/legacy/plugins/siem/public/components/fields_browser/category_columns.tsx b/x-pack/legacy/plugins/siem/public/components/fields_browser/category_columns.tsx index a4a53a5a51435..0c7dd7e908ce3 100644 --- a/x-pack/legacy/plugins/siem/public/components/fields_browser/category_columns.tsx +++ b/x-pack/legacy/plugins/siem/public/components/fields_browser/category_columns.tsx @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +/* eslint-disable react/display-name */ + import { EuiIcon, EuiFlexGroup, diff --git a/x-pack/legacy/plugins/siem/public/components/fields_browser/category_title.test.tsx b/x-pack/legacy/plugins/siem/public/components/fields_browser/category_title.test.tsx index e0628a410921e..792e0342a6d59 100644 --- a/x-pack/legacy/plugins/siem/public/components/fields_browser/category_title.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/fields_browser/category_title.test.tsx @@ -5,7 +5,7 @@ */ import { mount } from 'enzyme'; -import * as React from 'react'; +import React from 'react'; import { mockBrowserFields } from '../../containers/source/mock'; diff --git a/x-pack/legacy/plugins/siem/public/components/fields_browser/category_title.tsx b/x-pack/legacy/plugins/siem/public/components/fields_browser/category_title.tsx index 49255abc83dd5..cd14cef328a7e 100644 --- a/x-pack/legacy/plugins/siem/public/components/fields_browser/category_title.tsx +++ b/x-pack/legacy/plugins/siem/public/components/fields_browser/category_title.tsx @@ -5,7 +5,7 @@ */ import { EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { BrowserFields } from '../../containers/source'; diff --git a/x-pack/legacy/plugins/siem/public/components/fields_browser/field_browser.test.tsx b/x-pack/legacy/plugins/siem/public/components/fields_browser/field_browser.test.tsx index c43d5833fe1da..9214fd5f2540c 100644 --- a/x-pack/legacy/plugins/siem/public/components/fields_browser/field_browser.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/fields_browser/field_browser.test.tsx @@ -5,7 +5,7 @@ */ import { mount } from 'enzyme'; -import * as React from 'react'; +import React from 'react'; import { mockBrowserFields } from '../../containers/source/mock'; import { TestProviders } from '../../mock'; diff --git a/x-pack/legacy/plugins/siem/public/components/fields_browser/field_browser.tsx b/x-pack/legacy/plugins/siem/public/components/fields_browser/field_browser.tsx index c23908c396386..c8a0eb9da688b 100644 --- a/x-pack/legacy/plugins/siem/public/components/fields_browser/field_browser.tsx +++ b/x-pack/legacy/plugins/siem/public/components/fields_browser/field_browser.tsx @@ -102,138 +102,138 @@ type Props = Pick< * This component has no internal state, but it uses lifecycle methods to * set focus to the search input, scroll to the selected category, etc */ -export const FieldsBrowser = React.memo( - ({ - browserFields, - columnHeaders, - filteredBrowserFields, - isEventViewer, - isSearching, - onCategorySelected, - onFieldSelected, - onHideFieldBrowser, - onSearchInputChange, - onOutsideClick, - onUpdateColumns, - searchInput, - selectedCategoryId, - timelineId, - toggleColumn, - width, - }) => { - /** Focuses the input that filters the field browser */ - const focusInput = () => { - const elements = document.getElementsByClassName( - getFieldBrowserSearchInputClassName(timelineId) - ); +const FieldsBrowserComponent: React.FC = ({ + browserFields, + columnHeaders, + filteredBrowserFields, + isEventViewer, + isSearching, + onCategorySelected, + onFieldSelected, + onHideFieldBrowser, + onSearchInputChange, + onOutsideClick, + onUpdateColumns, + searchInput, + selectedCategoryId, + timelineId, + toggleColumn, + width, +}) => { + /** Focuses the input that filters the field browser */ + const focusInput = () => { + const elements = document.getElementsByClassName( + getFieldBrowserSearchInputClassName(timelineId) + ); - if (elements.length > 0) { - (elements[0] as HTMLElement).focus(); // this cast is required because focus() does not exist on every `Element` returned by `getElementsByClassName` + if (elements.length > 0) { + (elements[0] as HTMLElement).focus(); // this cast is required because focus() does not exist on every `Element` returned by `getElementsByClassName` + } + }; + + /** Invoked when the user types in the input to filter the field browser */ + const onInputChange = useCallback( + (event: React.ChangeEvent) => { + onSearchInputChange(event.target.value); + }, + [onSearchInputChange] + ); + + const selectFieldAndHide = useCallback( + (fieldId: string) => { + if (onFieldSelected != null) { + onFieldSelected(fieldId); } - }; - - /** Invoked when the user types in the input to filter the field browser */ - const onInputChange = useCallback( - (event: React.ChangeEvent) => { - onSearchInputChange(event.target.value); - }, - [onSearchInputChange] - ); - const selectFieldAndHide = useCallback( - (fieldId: string) => { - if (onFieldSelected != null) { - onFieldSelected(fieldId); - } + onHideFieldBrowser(); + }, + [onFieldSelected, onHideFieldBrowser] + ); + + const scrollViews = () => { + if (selectedCategoryId !== '') { + const categoryPaneTitles = document.getElementsByClassName( + getCategoryPaneCategoryClassName({ + categoryId: selectedCategoryId, + timelineId, + }) + ); - onHideFieldBrowser(); - }, - [onFieldSelected, onHideFieldBrowser] - ); + if (categoryPaneTitles.length > 0) { + categoryPaneTitles[0].scrollIntoView(); + } + + const fieldPaneTitles = document.getElementsByClassName( + getFieldBrowserCategoryTitleClassName({ + categoryId: selectedCategoryId, + timelineId, + }) + ); - const scrollViews = () => { - if (selectedCategoryId !== '') { - const categoryPaneTitles = document.getElementsByClassName( - getCategoryPaneCategoryClassName({ - categoryId: selectedCategoryId, - timelineId, - }) - ); - - if (categoryPaneTitles.length > 0) { - categoryPaneTitles[0].scrollIntoView(); - } - - const fieldPaneTitles = document.getElementsByClassName( - getFieldBrowserCategoryTitleClassName({ - categoryId: selectedCategoryId, - timelineId, - }) - ); - - if (fieldPaneTitles.length > 0) { - fieldPaneTitles[0].scrollIntoView(); - } + if (fieldPaneTitles.length > 0) { + fieldPaneTitles[0].scrollIntoView(); } + } + + focusInput(); // always re-focus the input to enable additional filtering + }; + + useEffect(() => { + scrollViews(); + }, [selectedCategoryId, timelineId]); + + return ( + + +
+ + + + + + + + + + + + + ); +}; - focusInput(); // always re-focus the input to enable additional filtering - }; - - useEffect(() => { - scrollViews(); - }, [selectedCategoryId, timelineId]); - - return ( - - -
- - - - - - - - - - - - - ); - } -); +export const FieldsBrowser = React.memo(FieldsBrowserComponent); diff --git a/x-pack/legacy/plugins/siem/public/components/fields_browser/field_items.test.tsx b/x-pack/legacy/plugins/siem/public/components/fields_browser/field_items.test.tsx index 6034f5a476443..4d0c707c46910 100644 --- a/x-pack/legacy/plugins/siem/public/components/fields_browser/field_items.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/fields_browser/field_items.test.tsx @@ -5,7 +5,7 @@ */ import { omit } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { mockBrowserFields } from '../../containers/source/mock'; import { TestProviders } from '../../mock'; diff --git a/x-pack/legacy/plugins/siem/public/components/fields_browser/field_items.tsx b/x-pack/legacy/plugins/siem/public/components/fields_browser/field_items.tsx index dc902b792d601..778e9d3d3c744 100644 --- a/x-pack/legacy/plugins/siem/public/components/fields_browser/field_items.tsx +++ b/x-pack/legacy/plugins/siem/public/components/fields_browser/field_items.tsx @@ -4,9 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ +/* eslint-disable react/display-name */ + import { EuiCheckbox, EuiIcon, EuiToolTip, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { uniqBy } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { Draggable } from 'react-beautiful-dnd'; import styled from 'styled-components'; diff --git a/x-pack/legacy/plugins/siem/public/components/fields_browser/field_name.test.tsx b/x-pack/legacy/plugins/siem/public/components/fields_browser/field_name.test.tsx index 59ee2efa1f006..1437af7a30adb 100644 --- a/x-pack/legacy/plugins/siem/public/components/fields_browser/field_name.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/fields_browser/field_name.test.tsx @@ -5,7 +5,7 @@ */ import { mount } from 'enzyme'; -import * as React from 'react'; +import React from 'react'; import { mockBrowserFields } from '../../containers/source/mock'; import { TestProviders } from '../../mock'; diff --git a/x-pack/legacy/plugins/siem/public/components/fields_browser/fields_pane.test.tsx b/x-pack/legacy/plugins/siem/public/components/fields_browser/fields_pane.test.tsx index 68ba2e2774314..f3ec87a96d46b 100644 --- a/x-pack/legacy/plugins/siem/public/components/fields_browser/fields_pane.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/fields_browser/fields_pane.test.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import { mockBrowserFields } from '../../containers/source/mock'; import { TestProviders } from '../../mock'; diff --git a/x-pack/legacy/plugins/siem/public/components/fields_browser/fields_pane.tsx b/x-pack/legacy/plugins/siem/public/components/fields_browser/fields_pane.tsx index 170cf324ca6d8..fba6e22e4b21f 100644 --- a/x-pack/legacy/plugins/siem/public/components/fields_browser/fields_pane.tsx +++ b/x-pack/legacy/plugins/siem/public/components/fields_browser/fields_pane.tsx @@ -5,7 +5,7 @@ */ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { BrowserFields } from '../../containers/source'; diff --git a/x-pack/legacy/plugins/siem/public/components/fields_browser/header.test.tsx b/x-pack/legacy/plugins/siem/public/components/fields_browser/header.test.tsx index 7e36a028961c4..42689065354d0 100644 --- a/x-pack/legacy/plugins/siem/public/components/fields_browser/header.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/fields_browser/header.test.tsx @@ -5,7 +5,7 @@ */ import { mount } from 'enzyme'; -import * as React from 'react'; +import React from 'react'; import { mockBrowserFields } from '../../containers/source/mock'; import { TestProviders } from '../../mock'; diff --git a/x-pack/legacy/plugins/siem/public/components/fields_browser/header.tsx b/x-pack/legacy/plugins/siem/public/components/fields_browser/header.tsx index 8acb19970c268..45b331f133e85 100644 --- a/x-pack/legacy/plugins/siem/public/components/fields_browser/header.tsx +++ b/x-pack/legacy/plugins/siem/public/components/fields_browser/header.tsx @@ -12,7 +12,7 @@ import { EuiText, EuiTitle, } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { BrowserFields } from '../../containers/source'; diff --git a/x-pack/legacy/plugins/siem/public/components/fields_browser/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/fields_browser/index.test.tsx index 8a01a01b1daae..24e4cd77b20d3 100644 --- a/x-pack/legacy/plugins/siem/public/components/fields_browser/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/fields_browser/index.test.tsx @@ -5,7 +5,7 @@ */ import { mount } from 'enzyme'; -import * as React from 'react'; +import React from 'react'; import { mockBrowserFields } from '../../containers/source/mock'; import { TestProviders } from '../../mock'; diff --git a/x-pack/legacy/plugins/siem/public/components/fields_browser/index.tsx b/x-pack/legacy/plugins/siem/public/components/fields_browser/index.tsx index 3958cd463d56e..c8cde5fa02a51 100644 --- a/x-pack/legacy/plugins/siem/public/components/fields_browser/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/fields_browser/index.tsx @@ -212,9 +212,7 @@ export const StatefulFieldsBrowserComponent = React.memo { ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/filters_global/filters_global.tsx b/x-pack/legacy/plugins/siem/public/components/filters_global/filters_global.tsx index edf6f7f01ab2e..b4d8c790002b2 100644 --- a/x-pack/legacy/plugins/siem/public/components/filters_global/filters_global.tsx +++ b/x-pack/legacy/plugins/siem/public/components/filters_global/filters_global.tsx @@ -13,7 +13,7 @@ import { gutterTimeline } from '../../lib/helpers'; const offsetChrome = 49; -const disableSticky = 'screen and (max-width: ' + euiLightVars.euiBreakpoints.s + ')'; +const disableSticky = `screen and (max-width: ${euiLightVars.euiBreakpoints.s})`; const disableStickyMq = window.matchMedia(disableSticky); const Wrapper = styled.aside<{ isSticky?: boolean }>` diff --git a/x-pack/legacy/plugins/siem/public/components/flow_controls/flow_direction_select.test.tsx b/x-pack/legacy/plugins/siem/public/components/flow_controls/flow_direction_select.test.tsx index 47f0aef4b4806..f984b534c188d 100644 --- a/x-pack/legacy/plugins/siem/public/components/flow_controls/flow_direction_select.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/flow_controls/flow_direction_select.test.tsx @@ -5,8 +5,7 @@ */ import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { FlowDirection } from '../../graphql/types'; @@ -25,7 +24,7 @@ describe('Select Flow Direction', () => { /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/flow_controls/flow_target_select.test.tsx b/x-pack/legacy/plugins/siem/public/components/flow_controls/flow_target_select.test.tsx index b1f757841e54b..373cc3cdc4a92 100644 --- a/x-pack/legacy/plugins/siem/public/components/flow_controls/flow_target_select.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/flow_controls/flow_target_select.test.tsx @@ -5,9 +5,8 @@ */ import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import { clone } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { ActionCreator } from 'typescript-fsa'; import { FlowDirection, FlowTarget } from '../../graphql/types'; @@ -31,7 +30,7 @@ describe('FlowTargetSelect Component', () => { test('it renders the FlowTargetSelect', () => { const wrapper = shallow(); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/flyout/button/index.tsx b/x-pack/legacy/plugins/siem/public/components/flyout/button/index.tsx index fee4f25f9e255..6ec5912872467 100644 --- a/x-pack/legacy/plugins/siem/public/components/flyout/button/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/flyout/button/index.tsx @@ -6,7 +6,7 @@ import { EuiNotificationBadge, EuiIcon, EuiButton } from '@elastic/eui'; import { rgba } from 'polished'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { DroppableWrapper } from '../../drag_and_drop/droppable_wrapper'; diff --git a/x-pack/legacy/plugins/siem/public/components/flyout/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/flyout/index.test.tsx index be7e8fac70bf5..83b842956e10e 100644 --- a/x-pack/legacy/plugins/siem/public/components/flyout/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/flyout/index.test.tsx @@ -5,9 +5,8 @@ */ import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import { set } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { ActionCreator } from 'typescript-fsa'; import { apolloClientObservable, mockGlobalState, TestProviders } from '../../mock'; @@ -35,7 +34,7 @@ describe('Flyout', () => { /> ); - expect(toJson(wrapper.find('Flyout'))).toMatchSnapshot(); + expect(wrapper.find('Flyout')).toMatchSnapshot(); }); test('it renders the default flyout state as a button', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/flyout/index.tsx b/x-pack/legacy/plugins/siem/public/components/flyout/index.tsx index 2d347830d5b1b..528f02f0a845b 100644 --- a/x-pack/legacy/plugins/siem/public/components/flyout/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/flyout/index.tsx @@ -6,7 +6,7 @@ import { EuiBadge } from '@elastic/eui'; import { defaultTo, getOr } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { connect } from 'react-redux'; import styled from 'styled-components'; import { ActionCreator } from 'typescript-fsa'; diff --git a/x-pack/legacy/plugins/siem/public/components/flyout/pane/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/flyout/pane/index.test.tsx index 246261035508b..365f99c6667b8 100644 --- a/x-pack/legacy/plugins/siem/public/components/flyout/pane/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/flyout/pane/index.test.tsx @@ -5,8 +5,7 @@ */ import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { TestProviders } from '../../../mock'; import { flyoutHeaderHeight } from '..'; @@ -16,8 +15,6 @@ const testFlyoutHeight = 980; const testWidth = 640; const usersViewing = ['elastic']; -jest.mock('../../../lib/kibana'); - describe('Pane', () => { test('renders correctly against snapshot', () => { const EmptyComponent = shallow( @@ -34,7 +31,7 @@ describe('Pane', () => { ); - expect(toJson(EmptyComponent.find('Pane'))).toMatchSnapshot(); + expect(EmptyComponent.find('Pane')).toMatchSnapshot(); }); test('it should NOT let the flyout expand to take up the full width of the element that contains it', () => { @@ -53,7 +50,7 @@ describe('Pane', () => { ); - expect(wrapper.find('[data-test-subj="eui-flyout"]').get(0).props.maxWidth).toEqual('95%'); + expect(wrapper.find('Resizable').get(0).props.maxWidth).toEqual('95vw'); }); test('it applies timeline styles to the EuiFlyout', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/flyout/pane/index.tsx b/x-pack/legacy/plugins/siem/public/components/flyout/pane/index.tsx index f2f0cf4f980f3..00ac15092a6ec 100644 --- a/x-pack/legacy/plugins/siem/public/components/flyout/pane/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/flyout/pane/index.tsx @@ -5,13 +5,14 @@ */ import { EuiButtonIcon, EuiFlyout, EuiFlyoutBody, EuiFlyoutHeader, EuiToolTip } from '@elastic/eui'; -import React, { useCallback } from 'react'; +import React, { useCallback, useState } from 'react'; import { connect } from 'react-redux'; import styled from 'styled-components'; import { ActionCreator } from 'typescript-fsa'; +import { Resizable, ResizeCallback } from 're-resizable'; +import { throttle } from 'lodash/fp'; -import { OnResize, Resizeable } from '../../resize_handle'; -import { TimelineResizeHandle } from '../../resize_handle/styled_handles'; +import { TimelineResizeHandle } from './timeline_resize_handle'; import { FlyoutHeader } from '../header'; import * as i18n from './translations'; @@ -41,10 +42,10 @@ interface DispatchProps { type Props = OwnProps & DispatchProps; -const EuiFlyoutContainer = styled.div<{ headerHeight: number; width: number }>` +const EuiFlyoutContainer = styled.div<{ headerHeight: number }>` .timeline-flyout { min-width: 150px; - width: ${({ width }) => `${width}px`}; + width: auto; } .timeline-flyout-header { align-items: center; @@ -65,8 +66,6 @@ const EuiFlyoutContainer = styled.div<{ headerHeight: number; width: number }>` } `; -EuiFlyoutContainer.displayName = 'EuiFlyoutContainer'; - const FlyoutHeaderContainer = styled.div` align-items: center; display: flex; @@ -75,88 +74,95 @@ const FlyoutHeaderContainer = styled.div` width: 100%; `; -FlyoutHeaderContainer.displayName = 'FlyoutHeaderContainer'; - // manually wrap the close button because EuiButtonIcon can't be a wrapped `styled` const WrappedCloseButton = styled.div` margin-right: 5px; `; -WrappedCloseButton.displayName = 'WrappedCloseButton'; - -const FlyoutHeaderWithCloseButton = React.memo<{ +const FlyoutHeaderWithCloseButtonComponent: React.FC<{ onClose: () => void; timelineId: string; usersViewing: string[]; -}>( - ({ onClose, timelineId, usersViewing }) => ( - - - - - - - - - ), +}> = ({ onClose, timelineId, usersViewing }) => ( + + + + + + + + +); + +const FlyoutHeaderWithCloseButton = React.memo( + FlyoutHeaderWithCloseButtonComponent, (prevProps, nextProps) => prevProps.timelineId === nextProps.timelineId && prevProps.usersViewing === nextProps.usersViewing ); -FlyoutHeaderWithCloseButton.displayName = 'FlyoutHeaderWithCloseButton'; - -const FlyoutPaneComponent = React.memo( - ({ - applyDeltaToWidth, - children, - flyoutHeight, - headerHeight, - onClose, - timelineId, - usersViewing, - width, - }) => { - const renderFlyout = useCallback(() => <>, []); - - const onResize: OnResize = useCallback( - ({ delta, id }) => { - const bodyClientWidthPixels = document.body.clientWidth; - +const FlyoutPaneComponent: React.FC = ({ + applyDeltaToWidth, + children, + flyoutHeight, + headerHeight, + onClose, + timelineId, + usersViewing, + width, +}) => { + const [lastDelta, setLastDelta] = useState(0); + const onResizeStop: ResizeCallback = useCallback( + (e, direction, ref, delta) => { + const bodyClientWidthPixels = document.body.clientWidth; + + if (delta.width) { applyDeltaToWidth({ bodyClientWidthPixels, - delta, - id, + delta: -(delta.width - lastDelta), + id: timelineId, maxWidthPercent, minWidthPixels, }); - }, - [applyDeltaToWidth, maxWidthPercent, minWidthPixels] - ); - return ( - - - setLastDelta(0), [setLastDelta]); + const throttledResize = throttle(100, onResizeStop); + + return ( + + + - } - id={timelineId} - onResize={onResize} - render={renderFlyout} - /> + ), + }} + onResizeStart={resetLastDelta} + onResize={throttledResize} + > ( {children} - - - ); - } -); - -FlyoutPaneComponent.displayName = 'FlyoutPaneComponent'; + + + + ); +}; export const Pane = connect(null, { applyDeltaToWidth: timelineActions.applyDeltaToWidth, -})(FlyoutPaneComponent); +})(React.memo(FlyoutPaneComponent)); Pane.displayName = 'Pane'; diff --git a/x-pack/legacy/plugins/siem/public/components/flyout/pane/timeline_resize_handle.tsx b/x-pack/legacy/plugins/siem/public/components/flyout/pane/timeline_resize_handle.tsx new file mode 100644 index 0000000000000..3ee29c2eaaa16 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/flyout/pane/timeline_resize_handle.tsx @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import styled from 'styled-components'; + +export const TIMELINE_RESIZE_HANDLE_WIDTH = 2; // px + +export const TimelineResizeHandle = styled.div<{ height: number }>` + cursor: col-resize; + height: 100%; + min-height: 20px; + width: 0; + border: ${TIMELINE_RESIZE_HANDLE_WIDTH}px solid ${props => props.theme.eui.euiColorLightShade}; + z-index: 2; + height: ${({ height }) => `${height}px`}; + position: absolute; +`; diff --git a/x-pack/legacy/plugins/siem/public/components/formatted_bytes/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/formatted_bytes/__snapshots__/index.test.tsx.snap index d23b1c61f7aee..ae30325f2a93b 100644 --- a/x-pack/legacy/plugins/siem/public/components/formatted_bytes/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/formatted_bytes/__snapshots__/index.test.tsx.snap @@ -1,6 +1,6 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`formatted_bytes PreferenceFormattedBytes rendering renders correctly against snapshot 1`] = ` +exports[`PreferenceFormattedBytes renders correctly against snapshot 1`] = ` 2.7MB diff --git a/x-pack/legacy/plugins/siem/public/components/formatted_bytes/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/formatted_bytes/index.test.tsx index 8c27a55d3a6b0..914d233bccc1f 100644 --- a/x-pack/legacy/plugins/siem/public/components/formatted_bytes/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/formatted_bytes/index.test.tsx @@ -5,10 +5,8 @@ */ import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; -import { mockFrameworks, getMockKibanaUiSetting } from '../../mock'; import { useUiSetting$ } from '../../lib/kibana'; import { PreferenceFormattedBytesComponent } from '.'; @@ -16,50 +14,42 @@ import { PreferenceFormattedBytesComponent } from '.'; jest.mock('../../lib/kibana'); const mockUseUiSetting$ = useUiSetting$ as jest.Mock; -describe('formatted_bytes', () => { - describe('PreferenceFormattedBytes', () => { - describe('rendering', () => { - beforeEach(() => { - mockUseUiSetting$.mockClear(); - }); +const DEFAULT_BYTES_FORMAT_VALUE = '0,0.[0]b'; // kibana's default for this setting +const bytes = '2806422'; - const bytes = '2806422'; +describe('PreferenceFormattedBytes', () => { + test('renders correctly against snapshot', () => { + mockUseUiSetting$.mockImplementation(() => [DEFAULT_BYTES_FORMAT_VALUE]); + const wrapper = shallow(); - test('renders correctly against snapshot', () => { - mockUseUiSetting$.mockImplementation( - getMockKibanaUiSetting(mockFrameworks.default_browser) - ); - const wrapper = shallow(); - expect(toJson(wrapper)).toMatchSnapshot(); - }); + expect(wrapper).toMatchSnapshot(); + }); + + test('it renders bytes to Numeral formatting when no format setting exists', () => { + mockUseUiSetting$.mockImplementation(() => [null]); + const wrapper = mount(); + + expect(wrapper.text()).toEqual('2,806,422'); + }); - test('it renders bytes to hardcoded format when no configuration exists', () => { - mockUseUiSetting$.mockImplementation(() => [null]); - const wrapper = mount(); - expect(wrapper.text()).toEqual('2.7MB'); - }); + test('it renders bytes according to the default format', () => { + mockUseUiSetting$.mockImplementation(() => [DEFAULT_BYTES_FORMAT_VALUE]); + const wrapper = mount(); - test('it renders bytes according to the default format', () => { - mockUseUiSetting$.mockImplementation( - getMockKibanaUiSetting(mockFrameworks.default_browser) - ); - const wrapper = mount(); - expect(wrapper.text()).toEqual('2.7MB'); - }); + expect(wrapper.text()).toEqual('2.7MB'); + }); + + test('it renders bytes supplied as a number according to the default format', () => { + mockUseUiSetting$.mockImplementation(() => [DEFAULT_BYTES_FORMAT_VALUE]); + const wrapper = mount(); + + expect(wrapper.text()).toEqual('2.7MB'); + }); - test('it renders bytes supplied as a number according to the default format', () => { - mockUseUiSetting$.mockImplementation( - getMockKibanaUiSetting(mockFrameworks.default_browser) - ); - const wrapper = mount(); - expect(wrapper.text()).toEqual('2.7MB'); - }); + test('it renders bytes according to new format', () => { + mockUseUiSetting$.mockImplementation(() => ['0b']); + const wrapper = mount(); - test('it renders bytes according to new format', () => { - mockUseUiSetting$.mockImplementation(getMockKibanaUiSetting(mockFrameworks.bytes_short)); - const wrapper = mount(); - expect(wrapper.text()).toEqual('3MB'); - }); - }); + expect(wrapper.text()).toEqual('3MB'); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/formatted_bytes/index.tsx b/x-pack/legacy/plugins/siem/public/components/formatted_bytes/index.tsx index 003ce0879b7b5..98a1acf471629 100644 --- a/x-pack/legacy/plugins/siem/public/components/formatted_bytes/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/formatted_bytes/index.tsx @@ -4,19 +4,28 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import numeral from '@elastic/numeral'; import { DEFAULT_BYTES_FORMAT } from '../../../common/constants'; import { useUiSetting$ } from '../../lib/kibana'; -export const PreferenceFormattedBytesComponent = ({ value }: { value: string | number }) => { +type Bytes = string | number; + +export const formatBytes = (value: Bytes, format: string) => { + return numeral(value).format(format); +}; + +export const useFormatBytes = () => { const [bytesFormat] = useUiSetting$(DEFAULT_BYTES_FORMAT); - return ( - <>{bytesFormat ? numeral(value).format(bytesFormat) : numeral(value).format('0,0.[0]b')} - ); + + return (value: Bytes) => formatBytes(value, bytesFormat); }; +export const PreferenceFormattedBytesComponent = ({ value }: { value: Bytes }) => ( + <>{useFormatBytes()(value)} +); + PreferenceFormattedBytesComponent.displayName = 'PreferenceFormattedBytesComponent'; export const PreferenceFormattedBytes = React.memo(PreferenceFormattedBytesComponent); diff --git a/x-pack/legacy/plugins/siem/public/components/formatted_date/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/formatted_date/__snapshots__/index.test.tsx.snap index d196a23bff5bf..9e851ddcd7d0f 100644 --- a/x-pack/legacy/plugins/siem/public/components/formatted_date/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/formatted_date/__snapshots__/index.test.tsx.snap @@ -1,9 +1,9 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP -exports[`formatted_date PreferenceFormattedDate rendering renders correctly against snapshot 1`] = ` +exports[`formatted_date PreferenceFormattedDate renders correctly against snapshot 1`] = ` - 2019-02-25T22:27:05.000Z + 2019-02-25T22:27:05Z `; diff --git a/x-pack/legacy/plugins/siem/public/components/formatted_date/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/formatted_date/index.test.tsx index dad1d5feb5c6e..0d8222ce85e1b 100644 --- a/x-pack/legacy/plugins/siem/public/components/formatted_date/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/formatted_date/index.test.tsx @@ -5,175 +5,165 @@ */ import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import moment from 'moment-timezone'; -import * as React from 'react'; +import React from 'react'; -import { useUiSetting$ } from '../../lib/kibana'; +import { useDateFormat, useTimeZone } from '../../hooks'; -import { mockFrameworks, TestProviders, MockFrameworks, getMockKibanaUiSetting } from '../../mock'; +import { TestProviders } from '../../mock'; import { getEmptyString, getEmptyValue } from '../empty_value'; import { PreferenceFormattedDate, FormattedDate, FormattedRelativePreferenceDate } from '.'; -jest.mock('../../lib/kibana'); -const mockUseUiSetting$ = useUiSetting$ as jest.Mock; +jest.mock('../../hooks'); +const mockUseDateFormat = useDateFormat as jest.Mock; +const mockUseTimeZone = useTimeZone as jest.Mock; + +const isoDateString = '2019-02-25T22:27:05.000Z'; describe('formatted_date', () => { + let isoDate: Date; + + beforeEach(() => { + isoDate = new Date(isoDateString); + mockUseDateFormat.mockImplementation(() => 'MMM D, YYYY @ HH:mm:ss.SSS'); + mockUseTimeZone.mockImplementation(() => 'UTC'); + }); + describe('PreferenceFormattedDate', () => { - describe('rendering', () => { - const isoDateString = '2019-02-25T22:27:05.000Z'; - const isoDate = new Date(isoDateString); - const configFormattedDateString = (dateString: string, config: MockFrameworks): string => - moment - .tz( - dateString, - config.dateFormatTz! === 'Browser' ? config.timezone! : config.dateFormatTz! - ) - .format(config.dateFormat); - - test('renders correctly against snapshot', () => { - mockUseUiSetting$.mockImplementation(() => [null]); - const wrapper = mount(); - expect(toJson(wrapper)).toMatchSnapshot(); - }); - - test('it renders the UTC ISO8601 date string supplied when no configuration exists', () => { - mockUseUiSetting$.mockImplementation(() => [null]); - const wrapper = mount(); - expect(wrapper.text()).toEqual(isoDateString); - }); - - test('it renders the UTC ISO8601 date supplied when the default configuration exists', () => { - mockUseUiSetting$.mockImplementation(getMockKibanaUiSetting(mockFrameworks.default_UTC)); - - const wrapper = mount(); - expect(wrapper.text()).toEqual( - configFormattedDateString(isoDateString, mockFrameworks.default_UTC) - ); - }); - - test('it renders the correct tz when the default browser configuration exists', () => { - mockUseUiSetting$.mockImplementation( - getMockKibanaUiSetting(mockFrameworks.default_browser) - ); - const wrapper = mount(); - expect(wrapper.text()).toEqual( - configFormattedDateString(isoDateString, mockFrameworks.default_browser) - ); - }); - - test('it renders the correct tz when a non-UTC configuration exists', () => { - mockUseUiSetting$.mockImplementation(getMockKibanaUiSetting(mockFrameworks.default_MT)); - const wrapper = mount(); - expect(wrapper.text()).toEqual( - configFormattedDateString(isoDateString, mockFrameworks.default_MT) - ); - }); + test('renders correctly against snapshot', () => { + mockUseDateFormat.mockImplementation(() => ''); + const wrapper = mount(); + + expect(wrapper).toMatchSnapshot(); + }); + + test('it renders the date with the default configuration', () => { + const wrapper = mount(); + + expect(wrapper.text()).toEqual('Feb 25, 2019 @ 22:27:05.000'); + }); + + test('it renders a UTC ISO8601 date string supplied when no date format configuration exists', () => { + mockUseDateFormat.mockImplementation(() => ''); + const wrapper = mount(); + + expect(wrapper.text()).toEqual('2019-02-25T22:27:05Z'); + }); + + test('it renders the correct timezone when a non-UTC configuration exists', () => { + mockUseTimeZone.mockImplementation(() => 'America/Denver'); + const wrapper = mount(); + + expect(wrapper.text()).toEqual('Feb 25, 2019 @ 15:27:05.000'); + }); + + test('it renders the date with a user-defined format', () => { + mockUseDateFormat.mockImplementation(() => 'MMM-DD-YYYY'); + const wrapper = mount(); + + expect(wrapper.text()).toEqual('Feb-25-2019'); }); }); describe('FormattedDate', () => { - describe('rendering', () => { - test('it renders against a numeric epoch', () => { - mockUseUiSetting$.mockImplementation(getMockKibanaUiSetting(mockFrameworks.default_UTC)); - const wrapper = mount(); - expect(wrapper.text()).toEqual('May 28, 2019 @ 21:35:39.000'); - }); - - test('it renders against a string epoch', () => { - mockUseUiSetting$.mockImplementation(getMockKibanaUiSetting(mockFrameworks.default_UTC)); - const wrapper = mount(); - expect(wrapper.text()).toEqual('May 28, 2019 @ 21:35:39.000'); - }); - - test('it renders against a ISO string', () => { - mockUseUiSetting$.mockImplementation(getMockKibanaUiSetting(mockFrameworks.default_UTC)); - const wrapper = mount( - - ); - expect(wrapper.text()).toEqual('May 28, 2019 @ 22:04:49.957'); - }); - - test('it renders against an empty string as an empty string placeholder', () => { - mockUseUiSetting$.mockImplementation(getMockKibanaUiSetting(mockFrameworks.default_UTC)); - const wrapper = mount( - - - - ); - expect(wrapper.text()).toEqual('(Empty String)'); - }); - - test('it renders against an null as a EMPTY_VALUE', () => { - mockUseUiSetting$.mockImplementation(getMockKibanaUiSetting(mockFrameworks.default_UTC)); - const wrapper = mount( - - - - ); - expect(wrapper.text()).toEqual(getEmptyValue()); - }); - - test('it renders against an undefined as a EMPTY_VALUE', () => { - mockUseUiSetting$.mockImplementation(getMockKibanaUiSetting(mockFrameworks.default_UTC)); - const wrapper = mount( - - - - ); - expect(wrapper.text()).toEqual(getEmptyValue()); - }); - - test('it renders against an invalid date time as just the string its self', () => { - mockUseUiSetting$.mockImplementation(getMockKibanaUiSetting(mockFrameworks.default_UTC)); - const wrapper = mount( - - - - ); - expect(wrapper.text()).toEqual('Rebecca Evan Braden'); - }); + test('it renders against a numeric epoch', () => { + const wrapper = mount(); + expect(wrapper.text()).toEqual('May 28, 2019 @ 21:35:39.000'); + }); + + test('it renders against a string epoch', () => { + const wrapper = mount(); + expect(wrapper.text()).toEqual('May 28, 2019 @ 21:35:39.000'); + }); + + test('it renders against a ISO string', () => { + const wrapper = mount( + + ); + expect(wrapper.text()).toEqual('May 28, 2019 @ 22:04:49.957'); + }); + + test('it renders against an empty string as an empty string placeholder', () => { + const wrapper = mount( + + + + ); + + expect(wrapper.text()).toEqual(getEmptyString()); + }); + + test('it renders against an null as a EMPTY_VALUE', () => { + const wrapper = mount( + + + + ); + + expect(wrapper.text()).toEqual(getEmptyValue()); + }); + + test('it renders against an undefined as a EMPTY_VALUE', () => { + const wrapper = mount( + + + + ); + + expect(wrapper.text()).toEqual(getEmptyValue()); + }); + + test('it renders against an invalid date time as just the string its self', () => { + const wrapper = mount( + + + + ); + + expect(wrapper.text()).toEqual('Rebecca Evan Braden'); }); }); describe('FormattedRelativePreferenceDate', () => { - describe('rendering', () => { - test('renders time over an hour correctly against snapshot', () => { - const isoDateString = '2019-02-25T22:27:05.000Z'; - const wrapper = shallow(); - expect(wrapper.find('[data-test-subj="preference-time"]').exists()).toBe(true); - }); - test('renders time under an hour correctly against snapshot', () => { - const timeTwelveMinutesAgo = new Date(new Date().getTime() - 12 * 60 * 1000).toISOString(); - const wrapper = shallow(); - expect(wrapper.find('[data-test-subj="relative-time"]').exists()).toBe(true); - }); - test('renders empty string value correctly', () => { - const wrapper = mount( - - - - ); - expect(wrapper.text()).toBe(getEmptyString()); - }); - - test('renders undefined value correctly', () => { - const wrapper = mount( - - - - ); - expect(wrapper.text()).toBe(getEmptyValue()); - }); - - test('renders null value correctly', () => { - const wrapper = mount( - - - - ); - expect(wrapper.text()).toBe(getEmptyValue()); - }); + test('renders time over an hour correctly against snapshot', () => { + const wrapper = shallow(); + expect(wrapper.find('[data-test-subj="preference-time"]').exists()).toBe(true); + }); + + test('renders time under an hour correctly against snapshot', () => { + const timeTwelveMinutesAgo = new Date(new Date().getTime() - 12 * 60 * 1000).toISOString(); + const wrapper = shallow(); + + expect(wrapper.find('[data-test-subj="relative-time"]').exists()).toBe(true); + }); + + test('renders empty string value correctly', () => { + const wrapper = mount( + + + + ); + + expect(wrapper.text()).toBe(getEmptyString()); + }); + + test('renders undefined value correctly', () => { + const wrapper = mount( + + + + ); + + expect(wrapper.text()).toBe(getEmptyValue()); + }); + + test('renders null value correctly', () => { + const wrapper = mount( + + + + ); + + expect(wrapper.text()).toBe(getEmptyValue()); }); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/formatted_date/index.tsx b/x-pack/legacy/plugins/siem/public/components/formatted_date/index.tsx index 19e8ec3f95d26..4e5903c02abf7 100644 --- a/x-pack/legacy/plugins/siem/public/components/formatted_date/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/formatted_date/index.tsx @@ -5,32 +5,19 @@ */ import moment from 'moment-timezone'; -import * as React from 'react'; +import React from 'react'; import { FormattedRelative } from '@kbn/i18n/react'; -import { useUiSetting$ } from '../../lib/kibana'; - -import { - DEFAULT_DATE_FORMAT, - DEFAULT_DATE_FORMAT_TZ, - DEFAULT_TIMEZONE_BROWSER, -} from '../../../common/constants'; +import { useDateFormat, useTimeZone } from '../../hooks'; import { getOrEmptyTagFromValue } from '../empty_value'; import { LocalizedDateTooltip } from '../localized_date_tooltip'; import { getMaybeDate } from './maybe_date'; export const PreferenceFormattedDate = React.memo<{ value: Date }>(({ value }) => { - const [dateFormat] = useUiSetting$(DEFAULT_DATE_FORMAT); - const [dateFormatTz] = useUiSetting$(DEFAULT_DATE_FORMAT_TZ); - const [timezone] = useUiSetting$(DEFAULT_TIMEZONE_BROWSER); + const dateFormat = useDateFormat(); + const timeZone = useTimeZone(); - return ( - <> - {dateFormat && dateFormatTz && timezone - ? moment.tz(value, dateFormatTz === 'Browser' ? timezone : dateFormatTz).format(dateFormat) - : moment.utc(value).toISOString()} - - ); + return <>{moment.tz(value, timeZone).format(dateFormat)}; }); PreferenceFormattedDate.displayName = 'PreferenceFormattedDate'; diff --git a/x-pack/legacy/plugins/siem/public/components/formatted_duration/index.tsx b/x-pack/legacy/plugins/siem/public/components/formatted_duration/index.tsx index 8afbafe57af4a..fa8d87cf4e82d 100644 --- a/x-pack/legacy/plugins/siem/public/components/formatted_duration/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/formatted_duration/index.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import { getFormattedDurationString } from './helpers'; import { FormattedDurationTooltip } from './tooltip'; diff --git a/x-pack/legacy/plugins/siem/public/components/formatted_duration/tooltip/index.tsx b/x-pack/legacy/plugins/siem/public/components/formatted_duration/tooltip/index.tsx index 1372b3ef10920..6c11a7aad7ad2 100644 --- a/x-pack/legacy/plugins/siem/public/components/formatted_duration/tooltip/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/formatted_duration/tooltip/index.tsx @@ -5,7 +5,7 @@ */ import { EuiToolTip } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import styled from 'styled-components'; diff --git a/x-pack/legacy/plugins/siem/public/components/formatted_ip/index.tsx b/x-pack/legacy/plugins/siem/public/components/formatted_ip/index.tsx index 8dcb558122d01..48d34451404be 100644 --- a/x-pack/legacy/plugins/siem/public/components/formatted_ip/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/formatted_ip/index.tsx @@ -5,7 +5,7 @@ */ import { isArray, isEmpty, isString, uniq } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { DragEffects, DraggableWrapper } from '../drag_and_drop/draggable_wrapper'; import { escapeDataProviderId } from '../drag_and_drop/helpers'; diff --git a/x-pack/legacy/plugins/siem/public/components/header_global/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/header_global/index.test.tsx index a45bed87829bf..098de39bbfef5 100644 --- a/x-pack/legacy/plugins/siem/public/components/header_global/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/header_global/index.test.tsx @@ -5,7 +5,6 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import React from 'react'; import '../../mock/match_media'; @@ -23,6 +22,6 @@ describe('HeaderGlobal', () => { test('it renders', () => { const wrapper = shallow(); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/header_page/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/header_page/index.test.tsx index 633ff90524de6..83a70fd90d82b 100644 --- a/x-pack/legacy/plugins/siem/public/components/header_page/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/header_page/index.test.tsx @@ -6,7 +6,6 @@ import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import React from 'react'; import { TestProviders } from '../../mock'; @@ -29,7 +28,7 @@ describe('HeaderPage', () => { ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it renders the title', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/header_section/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/header_section/index.test.tsx index fbd8642c01fac..2bc80be20e42d 100644 --- a/x-pack/legacy/plugins/siem/public/components/header_section/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/header_section/index.test.tsx @@ -6,7 +6,6 @@ import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import React from 'react'; import { TestProviders } from '../../mock'; @@ -16,7 +15,7 @@ describe('HeaderSection', () => { test('it renders', () => { const wrapper = shallow(); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it renders the title', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/inspect/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/inspect/index.test.tsx index 3a2e516fffb7e..26c5f499717e9 100644 --- a/x-pack/legacy/plugins/siem/public/components/inspect/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/inspect/index.test.tsx @@ -6,7 +6,7 @@ import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; import { mount } from 'enzyme'; -import * as React from 'react'; +import React from 'react'; import { ThemeProvider } from 'styled-components'; import { diff --git a/x-pack/legacy/plugins/siem/public/components/inspect/index.tsx b/x-pack/legacy/plugins/siem/public/components/inspect/index.tsx index 04d6d94d6624d..a2a0ffdde34a5 100644 --- a/x-pack/legacy/plugins/siem/public/components/inspect/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/inspect/index.tsx @@ -20,7 +20,7 @@ import * as i18n from './translations'; const InspectContainer = styled.div<{ showInspect: boolean }>` .euiButtonIcon { - ${props => (props.showInspect ? 'opacity: 1;' : 'opacity: 0')} + ${props => (props.showInspect ? 'opacity: 1;' : 'opacity: 0;')} transition: opacity ${props => getOr(250, 'theme.eui.euiAnimSpeedNormal', props)} ease; } `; diff --git a/x-pack/legacy/plugins/siem/public/components/ip/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/ip/index.test.tsx index 2dda3eca563fe..cac372a27180e 100644 --- a/x-pack/legacy/plugins/siem/public/components/ip/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ip/index.test.tsx @@ -5,8 +5,7 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { TestProviders } from '../../mock/test_providers'; import { useMountAppended } from '../../utils/use_mount_appended'; @@ -20,7 +19,7 @@ describe('Port', () => { const wrapper = shallow( ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it renders the the ip address', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/ip/index.tsx b/x-pack/legacy/plugins/siem/public/components/ip/index.tsx index 8c327989963b4..49237c3bb1bb9 100644 --- a/x-pack/legacy/plugins/siem/public/components/ip/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ip/index.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import { FormattedFieldValue } from '../timeline/body/renderers/formatted_field'; diff --git a/x-pack/legacy/plugins/siem/public/components/ja3_fingerprint/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/ja3_fingerprint/index.test.tsx index 3842b7be67876..c4ea6ff63a0a7 100644 --- a/x-pack/legacy/plugins/siem/public/components/ja3_fingerprint/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ja3_fingerprint/index.test.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import { TestProviders } from '../../mock'; import { useMountAppended } from '../../utils/use_mount_appended'; diff --git a/x-pack/legacy/plugins/siem/public/components/ja3_fingerprint/index.tsx b/x-pack/legacy/plugins/siem/public/components/ja3_fingerprint/index.tsx index 950ab252ad0bd..955a57576dc8e 100644 --- a/x-pack/legacy/plugins/siem/public/components/ja3_fingerprint/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ja3_fingerprint/index.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { DraggableBadge } from '../draggables'; diff --git a/x-pack/legacy/plugins/siem/public/components/last_event_time/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/last_event_time/index.test.tsx index dcecc636d9f0f..69a795d0c8db7 100644 --- a/x-pack/legacy/plugins/siem/public/components/last_event_time/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/last_event_time/index.test.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import { getEmptyValue } from '../empty_value'; import { LastEventIndexKey } from '../../graphql/types'; diff --git a/x-pack/legacy/plugins/siem/public/components/link_icon/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/link_icon/index.test.tsx index 87761a51a431f..59f2acba4121b 100644 --- a/x-pack/legacy/plugins/siem/public/components/link_icon/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/link_icon/index.test.tsx @@ -5,7 +5,6 @@ */ import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import React from 'react'; import { TestProviders } from '../../mock'; @@ -19,7 +18,7 @@ describe('LinkIcon', () => { ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it renders an action button when onClick is provided', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/links/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/links/index.test.tsx index 21580a0ac8664..dd5c3bf8bb5d5 100644 --- a/x-pack/legacy/plugins/siem/public/components/links/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/links/index.test.tsx @@ -5,7 +5,7 @@ */ import { mount } from 'enzyme'; -import * as React from 'react'; +import React from 'react'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { encodeIpv6 } from '../../lib/helpers'; diff --git a/x-pack/legacy/plugins/siem/public/components/links/index.tsx b/x-pack/legacy/plugins/siem/public/components/links/index.tsx index 9d8d01d2bb49a..f63d13fcda7f0 100644 --- a/x-pack/legacy/plugins/siem/public/components/links/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/links/index.tsx @@ -5,7 +5,7 @@ */ import { EuiLink } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import { encodeIpv6 } from '../../lib/helpers'; import { getHostDetailsUrl, getIPDetailsUrl } from '../link_to'; diff --git a/x-pack/legacy/plugins/siem/public/components/loader/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/loader/index.test.tsx index 1c97e766345aa..48dd91922583f 100644 --- a/x-pack/legacy/plugins/siem/public/components/loader/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/loader/index.test.tsx @@ -5,7 +5,6 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import React from 'react'; import { Loader } from './index'; @@ -17,6 +16,6 @@ describe('rendering', () => { {'Loading'} ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/loading/index.tsx b/x-pack/legacy/plugins/siem/public/components/loading/index.tsx index cd437911ab589..8c39a3d6ffcbe 100644 --- a/x-pack/legacy/plugins/siem/public/components/loading/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/loading/index.tsx @@ -5,7 +5,7 @@ */ import { EuiFlexGroup, EuiFlexItem, EuiLoadingSpinner, EuiPanel, EuiText } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; const SpinnerFlexItem = styled(EuiFlexItem)` diff --git a/x-pack/legacy/plugins/siem/public/components/localized_date_tooltip/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/localized_date_tooltip/index.test.tsx index 5feb70edffb9a..c25f0d71833c0 100644 --- a/x-pack/legacy/plugins/siem/public/components/localized_date_tooltip/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/localized_date_tooltip/index.test.tsx @@ -5,7 +5,7 @@ */ import { mount } from 'enzyme'; import moment from 'moment-timezone'; -import * as React from 'react'; +import React from 'react'; import { LocalizedDateTooltip } from '.'; diff --git a/x-pack/legacy/plugins/siem/public/components/localized_date_tooltip/index.tsx b/x-pack/legacy/plugins/siem/public/components/localized_date_tooltip/index.tsx index 82520ce2745c0..ed0b1b1689218 100644 --- a/x-pack/legacy/plugins/siem/public/components/localized_date_tooltip/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/localized_date_tooltip/index.tsx @@ -7,7 +7,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiToolTip } from '@elastic/eui'; import { FormattedRelative } from '@kbn/i18n/react'; import moment from 'moment'; -import * as React from 'react'; +import React from 'react'; export const LocalizedDateTooltip = React.memo<{ children: React.ReactNode; diff --git a/x-pack/legacy/plugins/siem/public/components/markdown/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/markdown/index.test.tsx index 56c215218ad5e..de662c162fc0a 100644 --- a/x-pack/legacy/plugins/siem/public/components/markdown/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/markdown/index.test.tsx @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { Markdown } from '.'; @@ -98,7 +97,7 @@ describe('Markdown', () => { test('it renders the expected table content', () => { const wrapper = shallow(); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); }); @@ -152,7 +151,7 @@ describe('Markdown', () => { test('it renders the expected content containing a link', () => { const wrapper = shallow(); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/markdown/index.tsx b/x-pack/legacy/plugins/siem/public/components/markdown/index.tsx index 16e065ae721c9..30695c9d0c7e2 100644 --- a/x-pack/legacy/plugins/siem/public/components/markdown/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/markdown/index.tsx @@ -4,8 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ +/* eslint-disable react/display-name */ + import { EuiLink, EuiTableRow, EuiTableRowCell, EuiText, EuiToolTip } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import ReactMarkdown from 'react-markdown'; import styled from 'styled-components'; diff --git a/x-pack/legacy/plugins/siem/public/components/markdown/markdown_hint.test.tsx b/x-pack/legacy/plugins/siem/public/components/markdown/markdown_hint.test.tsx index 80ccd07c30249..59aae5abce5c4 100644 --- a/x-pack/legacy/plugins/siem/public/components/markdown/markdown_hint.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/markdown/markdown_hint.test.tsx @@ -5,8 +5,7 @@ */ import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { MarkdownHintComponent } from './markdown_hint'; @@ -89,7 +88,7 @@ describe('MarkdownHintComponent ', () => { test('it renders the expected hints', () => { const wrapper = shallow(); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/markdown/markdown_hint.tsx b/x-pack/legacy/plugins/siem/public/components/markdown/markdown_hint.tsx index 5ecd1d4c9d2ad..199059670e4bd 100644 --- a/x-pack/legacy/plugins/siem/public/components/markdown/markdown_hint.tsx +++ b/x-pack/legacy/plugins/siem/public/components/markdown/markdown_hint.tsx @@ -5,7 +5,7 @@ */ import { EuiText } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import * as i18n from './translations'; diff --git a/x-pack/legacy/plugins/siem/public/components/matrix_histogram/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/matrix_histogram/index.test.tsx index 87d4e072e4299..78a4c967ee0bb 100644 --- a/x-pack/legacy/plugins/siem/public/components/matrix_histogram/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/matrix_histogram/index.test.tsx @@ -4,8 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ +/* eslint-disable react/display-name */ + import { shallow } from 'enzyme'; -import * as React from 'react'; +import React from 'react'; import { MatrixHistogram } from '.'; diff --git a/x-pack/legacy/plugins/siem/public/components/matrix_histogram/utils.ts b/x-pack/legacy/plugins/siem/public/components/matrix_histogram/utils.ts index 1eb5e96b86857..a7ef71f7a6a0d 100644 --- a/x-pack/legacy/plugins/siem/public/components/matrix_histogram/utils.ts +++ b/x-pack/legacy/plugins/siem/public/components/matrix_histogram/utils.ts @@ -6,7 +6,7 @@ import { ScaleType, niceTimeFormatter, Position } from '@elastic/charts'; import { get, groupBy, map, toPairs } from 'lodash/fp'; -import numeral from '@elastic/numeral'; + import { UpdateDateRange, ChartSeriesData } from '../charts/common'; import { MatrixHistogramDataTypes, MatrixHistogramMappingTypes } from './types'; @@ -87,7 +87,3 @@ export const getCustomChartData = ( }, formattedChartData); else return formattedChartData; }; - -export const bytesFormatter = (value: number) => { - return numeral(value).format('0,0.[0]b'); -}; diff --git a/x-pack/legacy/plugins/siem/public/components/ml/anomaly/anomaly_table_provider.tsx b/x-pack/legacy/plugins/siem/public/components/ml/anomaly/anomaly_table_provider.tsx index 3966448f84df0..6ccc41546e558 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml/anomaly/anomaly_table_provider.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml/anomaly/anomaly_table_provider.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import { InfluencerInput, Anomalies, CriteriaFields } from '../types'; import { useAnomaliesTableData } from './use_anomalies_table_data'; diff --git a/x-pack/legacy/plugins/siem/public/components/ml/anomaly/use_anomalies_table_data.ts b/x-pack/legacy/plugins/siem/public/components/ml/anomaly/use_anomalies_table_data.ts index bce99c943c7a5..48277b0b6fa52 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml/anomaly/use_anomalies_table_data.ts +++ b/x-pack/legacy/plugins/siem/public/components/ml/anomaly/use_anomalies_table_data.ts @@ -15,11 +15,8 @@ import { errorToToaster } from '../api/error_to_toaster'; import * as i18n from './translations'; import { useUiSetting$ } from '../../../lib/kibana'; -import { - DEFAULT_ANOMALY_SCORE, - DEFAULT_TIMEZONE_BROWSER, - DEFAULT_KBN_VERSION, -} from '../../../../common/constants'; +import { DEFAULT_ANOMALY_SCORE } from '../../../../common/constants'; +import { useTimeZone } from '../../../hooks'; interface Args { influencers?: InfluencerInput[]; @@ -67,9 +64,8 @@ export const useAnomaliesTableData = ({ const capabilities = useContext(MlCapabilitiesContext); const userPermissions = hasMlUserPermissions(capabilities); const [, dispatchToaster] = useStateToaster(); - const [timezone] = useUiSetting$(DEFAULT_TIMEZONE_BROWSER); + const timeZone = useTimeZone(); const [anomalyScore] = useUiSetting$(DEFAULT_ANOMALY_SCORE); - const [kbnVersion] = useUiSetting$(DEFAULT_KBN_VERSION); const siemJobIds = siemJobs.filter(job => job.isInstalled).map(job => job.id); @@ -95,11 +91,10 @@ export const useAnomaliesTableData = ({ earliestMs, latestMs, influencers: influencersInput, - dateFormatTz: timezone, + dateFormatTz: timeZone, maxRecords: 500, maxExamples: 10, }, - kbnVersion, abortCtrl.signal ); if (isSubscribed) { diff --git a/x-pack/legacy/plugins/siem/public/components/ml/api/anomalies_table_data.ts b/x-pack/legacy/plugins/siem/public/components/ml/api/anomalies_table_data.ts index e66d984a15294..10b2538d1e785 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml/api/anomalies_table_data.ts +++ b/x-pack/legacy/plugins/siem/public/components/ml/api/anomalies_table_data.ts @@ -21,20 +21,15 @@ export interface Body { maxExamples: number; } -export const anomaliesTableData = async ( - body: Body, - kbnVersion: string, - signal: AbortSignal -): Promise => { +export const anomaliesTableData = async (body: Body, signal: AbortSignal): Promise => { const response = await fetch(`${chrome.getBasePath()}/api/ml/results/anomalies_table_data`, { method: 'POST', credentials: 'same-origin', body: JSON.stringify(body), headers: { - 'kbn-system-api': 'true', 'content-Type': 'application/json', - 'kbn-xsrf': kbnVersion, - 'kbn-version': kbnVersion, + 'kbn-system-api': 'true', + 'kbn-xsrf': 'true', }, signal, }); diff --git a/x-pack/legacy/plugins/siem/public/components/ml/api/get_ml_capabilities.ts b/x-pack/legacy/plugins/siem/public/components/ml/api/get_ml_capabilities.ts index c1654a1648f2b..1333951028494 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml/api/get_ml_capabilities.ts +++ b/x-pack/legacy/plugins/siem/public/components/ml/api/get_ml_capabilities.ts @@ -22,18 +22,14 @@ export interface Body { maxExamples: number; } -export const getMlCapabilities = async ( - kbnVersion: string, - signal: AbortSignal -): Promise => { +export const getMlCapabilities = async (signal: AbortSignal): Promise => { const response = await fetch(`${chrome.getBasePath()}/api/ml/ml_capabilities`, { method: 'GET', credentials: 'same-origin', headers: { - 'kbn-system-api': 'true', 'content-Type': 'application/json', - 'kbn-xsrf': kbnVersion, - 'kbn-version': kbnVersion, + 'kbn-system-api': 'true', + 'kbn-xsrf': 'true', }, signal, }); diff --git a/x-pack/legacy/plugins/siem/public/components/ml/api/throw_if_not_ok.ts b/x-pack/legacy/plugins/siem/public/components/ml/api/throw_if_not_ok.ts index 5d28f4b40ab60..6ca843207a15e 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml/api/throw_if_not_ok.ts +++ b/x-pack/legacy/plugins/siem/public/components/ml/api/throw_if_not_ok.ts @@ -10,11 +10,7 @@ import * as i18n from './translations'; import { MlError } from '../types'; import { SetupMlResponse } from '../../ml_popover/types'; -export interface MessageBody { - error?: string; - message?: string; - statusCode?: number; -} +export { MessageBody, parseJsonFromBody } from '../../../utils/api'; export interface MlStartJobError { error: MlError; @@ -35,15 +31,6 @@ export class ToasterErrors extends Error implements ToasterErrorsType { } } -export const parseJsonFromBody = async (response: Response): Promise => { - try { - const text = await response.text(); - return JSON.parse(text); - } catch (error) { - return null; - } -}; - export const tryParseResponse = (response: string): string => { try { return JSON.stringify(JSON.parse(response), null, 2); diff --git a/x-pack/legacy/plugins/siem/public/components/ml/entity_draggable.test.tsx b/x-pack/legacy/plugins/siem/public/components/ml/entity_draggable.test.tsx index 562e3c15675a7..c48a5590b49cf 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml/entity_draggable.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml/entity_draggable.test.tsx @@ -5,7 +5,6 @@ */ import React from 'react'; -import toJson from 'enzyme-to-json'; import { shallow } from 'enzyme'; import { EntityDraggableComponent } from './entity_draggable'; import { TestProviders } from '../../mock/test_providers'; @@ -22,7 +21,7 @@ describe('entity_draggable', () => { entityValue="entity-value" /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('renders with entity name with entity value as text', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/ml/influencers/create_influencers.test.tsx b/x-pack/legacy/plugins/siem/public/components/ml/influencers/create_influencers.test.tsx index 615e83d208dd6..d49c3008b696c 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml/influencers/create_influencers.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml/influencers/create_influencers.test.tsx @@ -5,7 +5,6 @@ */ import React from 'react'; -import toJson from 'enzyme-to-json'; import { mockAnomalies } from '../mock'; import { cloneDeep } from 'lodash/fp'; import { shallow, mount } from 'enzyme'; @@ -20,7 +19,7 @@ describe('create_influencers', () => { test('renders correctly against snapshot', () => { const wrapper = shallow({createInfluencers(anomalies.anomalies[0].influencers)}); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it returns an empty string when influencers is undefined', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/ml/permissions/ml_capabilities_provider.tsx b/x-pack/legacy/plugins/siem/public/components/ml/permissions/ml_capabilities_provider.tsx index b8d6908df464e..cae05e26b115b 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml/permissions/ml_capabilities_provider.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml/permissions/ml_capabilities_provider.tsx @@ -11,8 +11,6 @@ import { getMlCapabilities } from '../api/get_ml_capabilities'; import { emptyMlCapabilities } from '../empty_ml_capabilities'; import { errorToToaster } from '../api/error_to_toaster'; import { useStateToaster } from '../../toasters'; -import { useUiSetting$ } from '../../../lib/kibana'; -import { DEFAULT_KBN_VERSION } from '../../../../common/constants'; import * as i18n from './translations'; @@ -36,7 +34,6 @@ export const MlCapabilitiesProvider = React.memo<{ children: JSX.Element }>(({ c emptyMlCapabilitiesProvider ); const [, dispatchToaster] = useStateToaster(); - const [kbnVersion] = useUiSetting$(DEFAULT_KBN_VERSION); useEffect(() => { let isSubscribed = true; @@ -44,7 +41,7 @@ export const MlCapabilitiesProvider = React.memo<{ children: JSX.Element }>(({ c async function fetchMlCapabilities() { try { - const mlCapabilities = await getMlCapabilities(kbnVersion, abortCtrl.signal); + const mlCapabilities = await getMlCapabilities(abortCtrl.signal); if (isSubscribed) { setCapabilities({ ...mlCapabilities, capabilitiesFetched: true }); } diff --git a/x-pack/legacy/plugins/siem/public/components/ml/score/anomaly_score.test.tsx b/x-pack/legacy/plugins/siem/public/components/ml/score/anomaly_score.test.tsx index cf24d6c02a138..9ff0081f4359f 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml/score/anomaly_score.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml/score/anomaly_score.test.tsx @@ -5,9 +5,8 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import { cloneDeep } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { AnomalyScoreComponent } from './anomaly_score'; import { mockAnomalies } from '../mock'; import { TestProviders } from '../../../mock/test_providers'; @@ -36,7 +35,7 @@ describe('anomaly_scores', () => { narrowDateRange={narrowDateRange} /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('should not show a popover on initial render', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/ml/score/anomaly_scores.test.tsx b/x-pack/legacy/plugins/siem/public/components/ml/score/anomaly_scores.test.tsx index 759e84e36f4ac..3041134f669ee 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml/score/anomaly_scores.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml/score/anomaly_scores.test.tsx @@ -5,9 +5,8 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import { cloneDeep } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { AnomalyScoresComponent, createJobKey } from './anomaly_scores'; import { mockAnomalies } from '../mock'; import { TestProviders } from '../../../mock/test_providers'; @@ -36,7 +35,7 @@ describe('anomaly_scores', () => { narrowDateRange={narrowDateRange} /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('renders spinner when isLoading is true is passed', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/ml/score/create_descriptions_list.test.tsx b/x-pack/legacy/plugins/siem/public/components/ml/score/create_descriptions_list.test.tsx index f00fb62d74ac3..7c8900bf77d95 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml/score/create_descriptions_list.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml/score/create_descriptions_list.test.tsx @@ -5,8 +5,7 @@ */ import { shallow, mount } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { mockAnomalies } from '../mock'; import { createDescriptionList } from './create_description_list'; import { EuiDescriptionList } from '@elastic/eui'; @@ -35,7 +34,7 @@ describe('create_description_list', () => { )} /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it calls the narrow date range function on click', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/ml/score/draggable_score.test.tsx b/x-pack/legacy/plugins/siem/public/components/ml/score/draggable_score.test.tsx index 0d389ae14a825..f7759bb74c3ab 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml/score/draggable_score.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml/score/draggable_score.test.tsx @@ -5,7 +5,6 @@ */ import React from 'react'; -import toJson from 'enzyme-to-json'; import { mockAnomalies } from '../mock'; import { cloneDeep } from 'lodash/fp'; import { shallow } from 'enzyme'; @@ -22,13 +21,13 @@ describe('draggable_score', () => { const wrapper = shallow( ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('renders correctly against snapshot when the index is not included', () => { const wrapper = shallow( ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/ml/tables/get_anomalies_host_table_columns.tsx b/x-pack/legacy/plugins/siem/public/components/ml/tables/get_anomalies_host_table_columns.tsx index daac4835adb28..4e6484c23613f 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml/tables/get_anomalies_host_table_columns.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml/tables/get_anomalies_host_table_columns.tsx @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +/* eslint-disable react/display-name */ + import React from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiLink } from '@elastic/eui'; import { Columns } from '../../paginated_table'; diff --git a/x-pack/legacy/plugins/siem/public/components/ml/tables/get_anomalies_network_table_columns.tsx b/x-pack/legacy/plugins/siem/public/components/ml/tables/get_anomalies_network_table_columns.tsx index 2113d3b82f52e..0c823ef75cf26 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml/tables/get_anomalies_network_table_columns.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml/tables/get_anomalies_network_table_columns.tsx @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +/* eslint-disable react/display-name */ + import React from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiLink } from '@elastic/eui'; diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/api.tsx b/x-pack/legacy/plugins/siem/public/components/ml_popover/api.tsx index 8e7bedd8f872a..a04b8f4b99653 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml_popover/api.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/api.tsx @@ -25,12 +25,10 @@ import { throwIfNotOk } from '../../hooks/api/api'; * Checks the ML Recognizer API to see if a given indexPattern has any compatible modules * * @param indexPatternName ES index pattern to check for compatible modules - * @param headers optional headers to add * @param signal to cancel request */ export const checkRecognizer = async ({ indexPatternName, - kbnVersion, signal, }: CheckRecognizerProps): Promise => { const response = await fetch( @@ -39,10 +37,9 @@ export const checkRecognizer = async ({ method: 'GET', credentials: 'same-origin', headers: { - 'kbn-system-api': 'true', 'content-type': 'application/json', - 'kbn-version': kbnVersion, - 'kbn-xsrf': kbnVersion, + 'kbn-system-api': 'true', + 'kbn-xsrf': 'true', }, signal, } @@ -55,22 +52,16 @@ export const checkRecognizer = async ({ * Returns ML Module for given moduleId. Returns all modules if no moduleId specified * * @param moduleId id of the module to retrieve - * @param headers optional headers to add optional headers to add * @param signal to cancel request */ -export const getModules = async ({ - moduleId = '', - kbnVersion, - signal, -}: GetModulesProps): Promise => { +export const getModules = async ({ moduleId = '', signal }: GetModulesProps): Promise => { const response = await fetch(`${chrome.getBasePath()}/api/ml/modules/get_module/${moduleId}`, { method: 'GET', credentials: 'same-origin', headers: { - 'kbn-system-api': 'true', 'content-type': 'application/json', - 'kbn-version': kbnVersion, - 'kbn-xsrf': kbnVersion, + 'kbn-system-api': 'true', + 'kbn-xsrf': 'true', }, signal, }); @@ -93,7 +84,6 @@ export const setupMlJob = async ({ indexPatternName = 'auditbeat-*', jobIdErrorFilter = [], groups = ['siem'], - kbnVersion, prefix = '', }: MlSetupArgs): Promise => { const response = await fetch(`${chrome.getBasePath()}/api/ml/modules/setup/${configTemplate}`, { @@ -107,10 +97,9 @@ export const setupMlJob = async ({ useDedicatedIndex: true, }), headers: { - 'kbn-system-api': 'true', 'content-type': 'application/json', - 'kbn-version': kbnVersion, - 'kbn-xsrf': kbnVersion, + 'kbn-system-api': 'true', + 'kbn-xsrf': 'true', }, }); await throwIfNotOk(response); @@ -124,16 +113,13 @@ export const setupMlJob = async ({ * * @param datafeedIds * @param start - * @param headers optional headers to add */ export const startDatafeeds = async ({ datafeedIds, - kbnVersion, start = 0, }: { datafeedIds: string[]; start: number; - kbnVersion: string; }): Promise => { const response = await fetch(`${chrome.getBasePath()}/api/ml/jobs/force_start_datafeeds`, { method: 'POST', @@ -143,10 +129,9 @@ export const startDatafeeds = async ({ ...(start !== 0 && { start }), }), headers: { - 'kbn-system-api': 'true', 'content-type': 'application/json', - 'kbn-version': kbnVersion, - 'kbn-xsrf': kbnVersion, + 'kbn-system-api': 'true', + 'kbn-xsrf': 'true', }, }); await throwIfNotOk(response); @@ -163,10 +148,8 @@ export const startDatafeeds = async ({ */ export const stopDatafeeds = async ({ datafeedIds, - kbnVersion, }: { datafeedIds: string[]; - kbnVersion: string; }): Promise<[StopDatafeedResponse | ErrorResponse, CloseJobsResponse]> => { const stopDatafeedsResponse = await fetch(`${chrome.getBasePath()}/api/ml/jobs/stop_datafeeds`, { method: 'POST', @@ -175,9 +158,9 @@ export const stopDatafeeds = async ({ datafeedIds, }), headers: { - 'kbn-system-api': 'true', 'content-type': 'application/json', - 'kbn-xsrf': kbnVersion, + 'kbn-system-api': 'true', + 'kbn-xsrf': 'true', }, }); @@ -198,7 +181,7 @@ export const stopDatafeeds = async ({ headers: { 'content-type': 'application/json', 'kbn-system-api': 'true', - 'kbn-xsrf': kbnVersion, + 'kbn-xsrf': 'true', }, }); @@ -214,10 +197,7 @@ export const stopDatafeeds = async ({ * * @param signal to cancel request */ -export const getJobsSummary = async ( - signal: AbortSignal, - kbnVersion: string -): Promise => { +export const getJobsSummary = async (signal: AbortSignal): Promise => { const response = await fetch(`${chrome.getBasePath()}/api/ml/jobs/jobs_summary`, { method: 'POST', credentials: 'same-origin', @@ -225,8 +205,7 @@ export const getJobsSummary = async ( headers: { 'content-type': 'application/json', 'kbn-system-api': 'true', - 'kbn-version': kbnVersion, - 'kbn-xsrf': kbnVersion, + 'kbn-xsrf': 'true', }, signal, }); diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/hooks/use_siem_jobs.tsx b/x-pack/legacy/plugins/siem/public/components/ml_popover/hooks/use_siem_jobs.tsx index f9d110d711d07..9df93d087e166 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml_popover/hooks/use_siem_jobs.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/hooks/use_siem_jobs.tsx @@ -13,7 +13,7 @@ import { MlCapabilitiesContext } from '../../ml/permissions/ml_capabilities_prov import { useStateToaster } from '../../toasters'; import { errorToToaster } from '../../ml/api/error_to_toaster'; import { useUiSetting$ } from '../../../lib/kibana'; -import { DEFAULT_INDEX_KEY, DEFAULT_KBN_VERSION } from '../../../../common/constants'; +import { DEFAULT_INDEX_KEY } from '../../../../common/constants'; import * as i18n from './translations'; import { createSiemJobs } from './use_siem_jobs_helpers'; @@ -34,7 +34,6 @@ export const useSiemJobs = (refetchData: boolean): Return => { const capabilities = useContext(MlCapabilitiesContext); const userPermissions = hasMlUserPermissions(capabilities); const [siemDefaultIndex] = useUiSetting$(DEFAULT_INDEX_KEY); - const [kbnVersion] = useUiSetting$(DEFAULT_KBN_VERSION); const [, dispatchToaster] = useStateToaster(); useEffect(() => { @@ -47,11 +46,10 @@ export const useSiemJobs = (refetchData: boolean): Return => { try { // Batch fetch all installed jobs, ML modules, and check which modules are compatible with siemDefaultIndex const [jobSummaryData, modulesData, compatibleModules] = await Promise.all([ - getJobsSummary(abortCtrl.signal, kbnVersion), - getModules({ signal: abortCtrl.signal, kbnVersion }), + getJobsSummary(abortCtrl.signal), + getModules({ signal: abortCtrl.signal }), checkRecognizer({ indexPatternName: siemDefaultIndex, - kbnVersion, signal: abortCtrl.signal, }), ]); diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/filters/groups_filter_popover.test.tsx b/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/filters/groups_filter_popover.test.tsx index 542529c628b72..a14ff789f1078 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/filters/groups_filter_popover.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/filters/groups_filter_popover.test.tsx @@ -5,8 +5,7 @@ */ import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { GroupsFilterPopoverComponent } from './groups_filter_popover'; import { mockSiemJobs } from '../../__mocks__/api'; import { SiemJob } from '../../types'; @@ -23,7 +22,7 @@ describe('GroupsFilterPopover', () => { const wrapper = shallow( ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('when a filter is clicked, it becomes checked ', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/filters/jobs_table_filters.test.tsx b/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/filters/jobs_table_filters.test.tsx index 0711cc1c87966..cbee724802156 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/filters/jobs_table_filters.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/filters/jobs_table_filters.test.tsx @@ -5,8 +5,7 @@ */ import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { JobsTableFiltersComponent } from './jobs_table_filters'; import { SiemJob } from '../../types'; import { cloneDeep } from 'lodash/fp'; @@ -23,7 +22,7 @@ describe('JobsTableFilters', () => { const wrapper = shallow( ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('when you click Elastic Jobs filter, state is updated and it is selected', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/job_switch.test.tsx b/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/job_switch.test.tsx index 91e5510f4938d..1186573e3e209 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/job_switch.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/job_switch.test.tsx @@ -5,8 +5,7 @@ */ import { shallow, mount } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { isChecked, isFailure, isJobLoading, JobSwitchComponent } from './job_switch'; import { cloneDeep } from 'lodash/fp'; @@ -29,7 +28,7 @@ describe('JobSwitch', () => { onJobStateChange={onJobStateChangeMock} /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('should call onJobStateChange when the switch is clicked to be true/open', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/jobs_table.test.tsx b/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/jobs_table.test.tsx index 691d43a8b18b3..fa524d8ff3dbc 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/jobs_table.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/jobs_table.test.tsx @@ -5,8 +5,7 @@ */ import { shallow, mount } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { JobsTableComponent } from './jobs_table'; import { mockSiemJobs } from '../__mocks__/api'; import { cloneDeep } from 'lodash/fp'; @@ -28,7 +27,7 @@ describe('JobsTableComponent', () => { onJobStateChange={onJobStateChangeMock} /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('should render the hyperlink which points specifically to the job id', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/jobs_table.tsx b/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/jobs_table.tsx index 73451d574d841..0d243a84aa192 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/jobs_table.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/jobs_table.tsx @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +/* eslint-disable react/display-name */ + import chrome from 'ui/chrome'; import React, { useEffect, useState } from 'react'; diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/showing_count.test.tsx b/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/showing_count.test.tsx index 2e2445fe933bb..bf1802f42084e 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/showing_count.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/jobs_table/showing_count.test.tsx @@ -5,13 +5,12 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { ShowingCountComponent } from './showing_count'; describe('ShowingCount', () => { test('renders correctly against snapshot', () => { const wrapper = shallow(); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/ml_popover.test.tsx b/x-pack/legacy/plugins/siem/public/components/ml_popover/ml_popover.test.tsx index 987c63be3f7be..bd7d696757ca6 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml_popover/ml_popover.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/ml_popover.test.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { MlPopover } from './ml_popover'; diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/ml_popover.tsx b/x-pack/legacy/plugins/siem/public/components/ml_popover/ml_popover.tsx index c34ed51d22994..307be06424ee3 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml_popover/ml_popover.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/ml_popover.tsx @@ -10,8 +10,7 @@ import moment from 'moment'; import React, { useContext, useReducer, useState } from 'react'; import styled from 'styled-components'; -import { DEFAULT_KBN_VERSION } from '../../../common/constants'; -import { useKibana, useUiSetting$ } from '../../lib/kibana'; +import { useKibana } from '../../lib/kibana'; import { METRIC_TYPE, TELEMETRY_EVENT, trackUiAction as track } from '../../lib/track_usage'; import { errorToToaster } from '../ml/api/error_to_toaster'; import { hasMlAdminPermissions } from '../ml/permissions/has_ml_admin_permissions'; @@ -97,7 +96,6 @@ export const MlPopover = React.memo(() => { const [isPopoverOpen, setIsPopoverOpen] = useState(false); const [filterProperties, setFilterProperties] = useState(defaultFilterProps); - const [kbnVersion] = useUiSetting$(DEFAULT_KBN_VERSION); const [isLoadingSiemJobs, siemJobs] = useSiemJobs(refreshToggle); const [, dispatchToaster] = useStateToaster(); const capabilities = useContext(MlCapabilitiesContext); @@ -114,7 +112,6 @@ export const MlPopover = React.memo(() => { indexPatternName: job.defaultIndexPattern, jobIdErrorFilter: [job.id], groups: job.groups, - kbnVersion, }); } catch (error) { errorToToaster({ title: i18n.CREATE_JOB_FAILURE, error, dispatchToaster }); @@ -132,14 +129,14 @@ export const MlPopover = React.memo(() => { if (enable) { const startTime = Math.max(latestTimestampMs, maxStartTime); try { - await startDatafeeds({ datafeedIds: [`datafeed-${job.id}`], kbnVersion, start: startTime }); + await startDatafeeds({ datafeedIds: [`datafeed-${job.id}`], start: startTime }); } catch (error) { track(METRIC_TYPE.COUNT, TELEMETRY_EVENT.JOB_ENABLE_FAILURE); errorToToaster({ title: i18n.START_JOB_FAILURE, error, dispatchToaster }); } } else { try { - await stopDatafeeds({ datafeedIds: [`datafeed-${job.id}`], kbnVersion }); + await stopDatafeeds({ datafeedIds: [`datafeed-${job.id}`] }); } catch (error) { track(METRIC_TYPE.COUNT, TELEMETRY_EVENT.JOB_DISABLE_FAILURE); errorToToaster({ title: i18n.STOP_JOB_FAILURE, error, dispatchToaster }); diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/popover_description.test.tsx b/x-pack/legacy/plugins/siem/public/components/ml_popover/popover_description.test.tsx index d409f5de200a4..e611a5234da5e 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml_popover/popover_description.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/popover_description.test.tsx @@ -5,13 +5,12 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { PopoverDescriptionComponent } from './popover_description'; describe('JobsTableFilters', () => { test('renders correctly against snapshot', () => { const wrapper = shallow(); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/types.ts b/x-pack/legacy/plugins/siem/public/components/ml_popover/types.ts index f8794c1963961..964ae8c8242d4 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml_popover/types.ts +++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/types.ts @@ -14,7 +14,6 @@ export interface Group { export interface CheckRecognizerProps { indexPatternName: string[]; - kbnVersion: string; signal: AbortSignal; } @@ -30,7 +29,6 @@ export interface RecognizerModule { export interface GetModulesProps { moduleId?: string; - kbnVersion: string; signal: AbortSignal; } @@ -97,7 +95,6 @@ export interface MlSetupArgs { jobIdErrorFilter: string[]; groups: string[]; prefix?: string; - kbnVersion: string; } /** diff --git a/x-pack/legacy/plugins/siem/public/components/ml_popover/upgrade_contents.test.tsx b/x-pack/legacy/plugins/siem/public/components/ml_popover/upgrade_contents.test.tsx index c522b7750c414..2ba08073b25b9 100644 --- a/x-pack/legacy/plugins/siem/public/components/ml_popover/upgrade_contents.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/ml_popover/upgrade_contents.test.tsx @@ -5,13 +5,12 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { UpgradeContentsComponent } from './upgrade_contents'; describe('JobsTableFilters', () => { test('renders correctly against snapshot', () => { const wrapper = shallow(); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/navigation/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/navigation/index.test.tsx index d7061ba4efd9c..cae209a76fc1c 100644 --- a/x-pack/legacy/plugins/siem/public/components/navigation/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/navigation/index.test.tsx @@ -5,7 +5,7 @@ */ import { mount } from 'enzyme'; -import * as React from 'react'; +import React from 'react'; import { CONSTANTS } from '../url_state/constants'; import { SiemNavigationComponent } from './'; diff --git a/x-pack/legacy/plugins/siem/public/components/navigation/tab_navigation/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/navigation/tab_navigation/index.test.tsx index 840e2bf3f42dc..b9563b60f301b 100644 --- a/x-pack/legacy/plugins/siem/public/components/navigation/tab_navigation/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/navigation/tab_navigation/index.test.tsx @@ -5,7 +5,7 @@ */ import { mount } from 'enzyme'; -import * as React from 'react'; +import React from 'react'; import { navTabs } from '../../../pages/home/home_navigations'; import { SiemPageName } from '../../../pages/home/types'; diff --git a/x-pack/legacy/plugins/siem/public/components/netflow/fingerprints/index.tsx b/x-pack/legacy/plugins/siem/public/components/netflow/fingerprints/index.tsx index 6b29068f3cd2d..f0dc67a5ff4c3 100644 --- a/x-pack/legacy/plugins/siem/public/components/netflow/fingerprints/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/netflow/fingerprints/index.tsx @@ -6,7 +6,7 @@ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { uniq } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { CertificateFingerprint, diff --git a/x-pack/legacy/plugins/siem/public/components/netflow/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/netflow/index.test.tsx index 22531983b2399..ecf162ebf2739 100644 --- a/x-pack/legacy/plugins/siem/public/components/netflow/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/netflow/index.test.tsx @@ -4,9 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import toJson from 'enzyme-to-json'; import { get } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { shallow } from 'enzyme'; import { asArrayIfExists } from '../../lib/helpers'; @@ -123,7 +122,7 @@ describe('Netflow', () => { test('renders correctly against snapshot', () => { const wrapper = shallow(getNetflowInstance()); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it renders a destination label', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/netflow/index.tsx b/x-pack/legacy/plugins/siem/public/components/netflow/index.tsx index 26e6986c386be..3d6c2a7b767cb 100644 --- a/x-pack/legacy/plugins/siem/public/components/netflow/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/netflow/index.tsx @@ -5,7 +5,7 @@ */ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import { Fingerprints } from './fingerprints'; import { NetflowColumns } from './netflow_columns'; diff --git a/x-pack/legacy/plugins/siem/public/components/netflow/netflow_columns/duration_event_start_end.tsx b/x-pack/legacy/plugins/siem/public/components/netflow/netflow_columns/duration_event_start_end.tsx index f006ec0f003c1..09fa5d9fe1596 100644 --- a/x-pack/legacy/plugins/siem/public/components/netflow/netflow_columns/duration_event_start_end.tsx +++ b/x-pack/legacy/plugins/siem/public/components/netflow/netflow_columns/duration_event_start_end.tsx @@ -6,7 +6,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiIcon, EuiText } from '@elastic/eui'; import { uniq } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { DefaultDraggable } from '../../draggables'; diff --git a/x-pack/legacy/plugins/siem/public/components/netflow/netflow_columns/index.tsx b/x-pack/legacy/plugins/siem/public/components/netflow/netflow_columns/index.tsx index 0dc4a2271e207..f8a0256ff4d43 100644 --- a/x-pack/legacy/plugins/siem/public/components/netflow/netflow_columns/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/netflow/netflow_columns/index.tsx @@ -5,7 +5,7 @@ */ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { SourceDestination } from '../../source_destination'; diff --git a/x-pack/legacy/plugins/siem/public/components/netflow/netflow_columns/user_process.tsx b/x-pack/legacy/plugins/siem/public/components/netflow/netflow_columns/user_process.tsx index adac7dede1f85..ab71dc301156f 100644 --- a/x-pack/legacy/plugins/siem/public/components/netflow/netflow_columns/user_process.tsx +++ b/x-pack/legacy/plugins/siem/public/components/netflow/netflow_columns/user_process.tsx @@ -6,7 +6,7 @@ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { uniq } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { DraggableBadge } from '../../draggables'; diff --git a/x-pack/legacy/plugins/siem/public/components/notes/add_note/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/notes/add_note/index.test.tsx index fc76780ef80c7..ca6abc90d317b 100644 --- a/x-pack/legacy/plugins/siem/public/components/notes/add_note/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/notes/add_note/index.test.tsx @@ -5,8 +5,7 @@ */ import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { AddNote } from '.'; @@ -24,7 +23,7 @@ describe('AddNote', () => { updateNote={jest.fn()} /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it renders the Cancel button when onCancelAddNote is provided', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/notes/add_note/new_note.test.tsx b/x-pack/legacy/plugins/siem/public/components/notes/add_note/new_note.test.tsx index 3ab556a4e5dc4..24db5c5ec8125 100644 --- a/x-pack/legacy/plugins/siem/public/components/notes/add_note/new_note.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/notes/add_note/new_note.test.tsx @@ -5,8 +5,7 @@ */ import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import * as i18n from '../translations'; @@ -19,7 +18,7 @@ describe('NewNote', () => { const wrapper = shallow( ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it renders a tab labeled "Note"', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/notes/add_note/new_note.tsx b/x-pack/legacy/plugins/siem/public/components/notes/add_note/new_note.tsx index 64cbdf1c678c5..5a3439d53dd89 100644 --- a/x-pack/legacy/plugins/siem/public/components/notes/add_note/new_note.tsx +++ b/x-pack/legacy/plugins/siem/public/components/notes/add_note/new_note.tsx @@ -5,7 +5,7 @@ */ import { EuiPanel, EuiTabbedContent, EuiTextArea } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { Markdown } from '../../markdown'; diff --git a/x-pack/legacy/plugins/siem/public/components/notes/columns.tsx b/x-pack/legacy/plugins/siem/public/components/notes/columns.tsx index 0c7a18d6076aa..32e10ac3eb77d 100644 --- a/x-pack/legacy/plugins/siem/public/components/notes/columns.tsx +++ b/x-pack/legacy/plugins/siem/public/components/notes/columns.tsx @@ -4,7 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +/* eslint-disable react/display-name */ + +import React from 'react'; import { EuiTableDataType } from '@elastic/eui'; import { NoteCard } from './note_card'; diff --git a/x-pack/legacy/plugins/siem/public/components/notes/helpers.tsx b/x-pack/legacy/plugins/siem/public/components/notes/helpers.tsx index 65e46f8d84c9f..c933055186e07 100644 --- a/x-pack/legacy/plugins/siem/public/components/notes/helpers.tsx +++ b/x-pack/legacy/plugins/siem/public/components/notes/helpers.tsx @@ -6,7 +6,7 @@ import { EuiIcon, EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui'; import moment from 'moment'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { Note } from '../../lib/note'; diff --git a/x-pack/legacy/plugins/siem/public/components/notes/note_card/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/notes/note_card/index.test.tsx index 2c16f85c78076..a927627353f69 100644 --- a/x-pack/legacy/plugins/siem/public/components/notes/note_card/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/notes/note_card/index.test.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { ThemeProvider } from 'styled-components'; import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; diff --git a/x-pack/legacy/plugins/siem/public/components/notes/note_card/index.tsx b/x-pack/legacy/plugins/siem/public/components/notes/note_card/index.tsx index 88e59ddd1419b..e02ebc2a25fd0 100644 --- a/x-pack/legacy/plugins/siem/public/components/notes/note_card/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/notes/note_card/index.tsx @@ -5,7 +5,7 @@ */ import { EuiPanel } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { NoteCardBody } from './note_card_body'; diff --git a/x-pack/legacy/plugins/siem/public/components/notes/note_card/note_card_body.test.tsx b/x-pack/legacy/plugins/siem/public/components/notes/note_card/note_card_body.test.tsx index b6cd23496b190..46e1bab37495a 100644 --- a/x-pack/legacy/plugins/siem/public/components/notes/note_card/note_card_body.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/notes/note_card/note_card_body.test.tsx @@ -5,8 +5,7 @@ */ import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { ThemeProvider } from 'styled-components'; import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; @@ -24,7 +23,7 @@ describe('NoteCardBody', () => { ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it renders the text of the note in an h1', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/notes/note_card/note_card_body.tsx b/x-pack/legacy/plugins/siem/public/components/notes/note_card/note_card_body.tsx index 94ea0a6ee3129..11761c8fd39b0 100644 --- a/x-pack/legacy/plugins/siem/public/components/notes/note_card/note_card_body.tsx +++ b/x-pack/legacy/plugins/siem/public/components/notes/note_card/note_card_body.tsx @@ -5,7 +5,7 @@ */ import { EuiPanel, EuiToolTip } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { WithCopyToClipboard } from '../../../lib/clipboard/with_copy_to_clipboard'; diff --git a/x-pack/legacy/plugins/siem/public/components/notes/note_card/note_card_header.test.tsx b/x-pack/legacy/plugins/siem/public/components/notes/note_card/note_card_header.test.tsx index 0511ad264647a..3525a88fc2ddd 100644 --- a/x-pack/legacy/plugins/siem/public/components/notes/note_card/note_card_header.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/notes/note_card/note_card_header.test.tsx @@ -5,7 +5,7 @@ */ import moment from 'moment-timezone'; -import * as React from 'react'; +import React from 'react'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; import * as i18n from '../translations'; diff --git a/x-pack/legacy/plugins/siem/public/components/notes/note_card/note_card_header.tsx b/x-pack/legacy/plugins/siem/public/components/notes/note_card/note_card_header.tsx index a227b5af38e85..e6aa0542df4b3 100644 --- a/x-pack/legacy/plugins/siem/public/components/notes/note_card/note_card_header.tsx +++ b/x-pack/legacy/plugins/siem/public/components/notes/note_card/note_card_header.tsx @@ -5,7 +5,7 @@ */ import { EuiAvatar, EuiPanel } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import * as i18n from '../translations'; diff --git a/x-pack/legacy/plugins/siem/public/components/notes/note_card/note_created.test.tsx b/x-pack/legacy/plugins/siem/public/components/notes/note_card/note_created.test.tsx index 9c41d9d52a755..5d99375c38217 100644 --- a/x-pack/legacy/plugins/siem/public/components/notes/note_card/note_created.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/notes/note_card/note_created.test.tsx @@ -5,7 +5,7 @@ */ import moment from 'moment-timezone'; -import * as React from 'react'; +import React from 'react'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { NoteCreated } from './note_created'; diff --git a/x-pack/legacy/plugins/siem/public/components/notes/note_card/note_created.tsx b/x-pack/legacy/plugins/siem/public/components/notes/note_card/note_created.tsx index c94f4021e1a4a..cdd0406c71450 100644 --- a/x-pack/legacy/plugins/siem/public/components/notes/note_card/note_created.tsx +++ b/x-pack/legacy/plugins/siem/public/components/notes/note_card/note_created.tsx @@ -5,7 +5,7 @@ */ import { FormattedRelative } from '@kbn/i18n/react'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { LocalizedDateTooltip } from '../../localized_date_tooltip'; diff --git a/x-pack/legacy/plugins/siem/public/components/notes/note_cards/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/notes/note_cards/index.test.tsx index dc52822eaff77..f70e841d1eefd 100644 --- a/x-pack/legacy/plugins/siem/public/components/notes/note_cards/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/notes/note_cards/index.test.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { ThemeProvider } from 'styled-components'; import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/delete_timeline_modal/delete_timeline_modal.test.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/delete_timeline_modal/delete_timeline_modal.test.tsx index 917ec3f1bf0b8..e061141bf43e7 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/delete_timeline_modal/delete_timeline_modal.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/delete_timeline_modal/delete_timeline_modal.test.tsx @@ -5,7 +5,7 @@ */ import { mountWithIntl } from 'test_utils/enzyme_helpers'; -import * as React from 'react'; +import React from 'react'; import { DeleteTimelineModal } from './delete_timeline_modal'; diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/delete_timeline_modal/delete_timeline_modal.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/delete_timeline_modal/delete_timeline_modal.tsx index 1163fbba1572a..82fe0d1d162a4 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/delete_timeline_modal/delete_timeline_modal.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/delete_timeline_modal/delete_timeline_modal.tsx @@ -6,7 +6,7 @@ import { EuiConfirmModal, EUI_MODAL_CONFIRM_BUTTON } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import * as React from 'react'; +import React from 'react'; import * as i18n from '../translations'; diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/delete_timeline_modal/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/delete_timeline_modal/index.test.tsx index 561eac000bbf7..a3c5371435e52 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/delete_timeline_modal/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/delete_timeline_modal/index.test.tsx @@ -6,7 +6,7 @@ import { EuiButtonIconProps } from '@elastic/eui'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; -import * as React from 'react'; +import React from 'react'; import { DeleteTimelineModalButton } from '.'; diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/helpers.ts b/x-pack/legacy/plugins/siem/public/components/open_timeline/helpers.ts index 91480f20d8b00..41e13408c1e01 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/helpers.ts +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/helpers.ts @@ -181,6 +181,7 @@ export interface QueryTimelineById { apolloClient: ApolloClient | ApolloClient<{}> | undefined; duplicate: boolean; timelineId: string; + onOpenTimeline?: (timeline: TimelineModel) => void; openTimeline?: boolean; updateIsLoading: ActionCreator<{ id: string; isLoading: boolean }>; updateTimeline: DispatchUpdateTimeline; @@ -190,6 +191,7 @@ export const queryTimelineById = ({ apolloClient, duplicate = false, timelineId, + onOpenTimeline, openTimeline = true, updateIsLoading, updateTimeline, @@ -209,7 +211,9 @@ export const queryTimelineById = ({ ); const { timeline, notes } = formatTimelineResultToModel(timelineToOpen, duplicate); - if (updateTimeline) { + if (onOpenTimeline != null) { + onOpenTimeline(timeline); + } else if (updateTimeline) { updateTimeline({ duplicate, from: getOr(getDefaultFromValue(), 'dateRange.start', timeline), diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/index.test.tsx index c9f52d9d204ae..520e2094fb336 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/index.test.tsx @@ -7,7 +7,7 @@ import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; import { mount } from 'enzyme'; import { MockedProvider } from 'react-apollo/test-utils'; -import * as React from 'react'; +import React from 'react'; import { ThemeProvider } from 'styled-components'; import { wait } from '../../lib/helpers'; diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/index.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/index.tsx index c22c5fdbcfbc5..a97cfefaf0393 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/index.tsx @@ -12,18 +12,20 @@ import { Dispatch } from 'redux'; import { defaultHeaders } from '../../components/timeline/body/column_headers/default_headers'; import { deleteTimelineMutation } from '../../containers/timeline/delete/persist.gql_query'; import { AllTimelinesVariables, AllTimelinesQuery } from '../../containers/timeline/all'; - import { allTimelinesQuery } from '../../containers/timeline/all/index.gql_query'; import { DeleteTimelineMutation, SortFieldTimeline, Direction } from '../../graphql/types'; import { State, timelineSelectors } from '../../store'; +import { timelineDefaults, TimelineModel } from '../../store/timeline/model'; import { createTimeline as dispatchCreateNewTimeline, updateIsLoading as dispatchUpdateIsLoading, } from '../../store/timeline/actions'; +import { ColumnHeader } from '../timeline/body/column_headers/column_header'; import { OpenTimeline } from './open_timeline'; import { OPEN_TIMELINE_CLASS_NAME, queryTimelineById, dispatchUpdateTimeline } from './helpers'; import { OpenTimelineModalBody } from './open_timeline_modal/open_timeline_modal_body'; import { + ActionTimelineToShow, DeleteTimelines, EuiSearchBarQuery, OnDeleteSelected, @@ -41,14 +43,14 @@ import { OpenTimelineReduxProps, } from './types'; import { DEFAULT_SORT_FIELD, DEFAULT_SORT_DIRECTION } from './constants'; -import { ColumnHeader } from '../timeline/body/column_headers/column_header'; -import { timelineDefaults } from '../../store/timeline/model'; interface OwnProps { apolloClient: ApolloClient; /** Displays open timeline in modal */ isModal: boolean; closeModalTimeline?: () => void; + hideActions?: ActionTimelineToShow[]; + onOpenTimeline?: (timeline: TimelineModel) => void; } export type OpenTimelineOwnProps = OwnProps & @@ -69,15 +71,17 @@ export const getSelectedTimelineIds = (selectedItems: OpenTimelineResult[]): str /** Manages the state (e.g table selection) of the (pure) `OpenTimeline` component */ export const StatefulOpenTimelineComponent = React.memo( ({ + apolloClient, + closeModalTimeline, + createNewTimeline, defaultPageSize, + hideActions = [], isModal = false, + onOpenTimeline, + timeline, title, - apolloClient, - closeModalTimeline, updateTimeline, updateIsLoading, - timeline, - createNewTimeline, }) => { /** Required by EuiTable for expandable rows: a map of `TimelineResult.savedObjectId` to rendered notes */ const [itemIdToExpandedNotesRowMap, setItemIdToExpandedNotesRowMap] = useState< @@ -212,6 +216,7 @@ export const StatefulOpenTimelineComponent = React.memo( queryTimelineById({ apolloClient, duplicate, + onOpenTimeline, timelineId, updateIsLoading, updateTimeline, @@ -286,6 +291,7 @@ export const StatefulOpenTimelineComponent = React.memo( data-test-subj={'open-timeline-modal'} deleteTimelines={onDeleteOneTimeline} defaultPageSize={defaultPageSize} + hideActions={hideActions} isLoading={loading} itemIdToExpandedNotesRowMap={itemIdToExpandedNotesRowMap} onAddTimelinesToFavorites={undefined} diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/note_previews/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/note_previews/index.test.tsx index 70bf7d2cbeb52..463111bd9735f 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/note_previews/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/note_previews/index.test.tsx @@ -8,7 +8,7 @@ import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; import { cloneDeep } from 'lodash/fp'; import moment from 'moment'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; -import * as React from 'react'; +import React from 'react'; import { ThemeProvider } from 'styled-components'; import { mockTimelineResults } from '../../../mock/timeline_results'; diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/note_previews/index.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/note_previews/index.tsx index 2c35d5d8254cd..e0507c2370831 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/note_previews/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/note_previews/index.tsx @@ -5,7 +5,7 @@ */ import { uniqBy } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { NotePreview } from './note_preview'; diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/note_previews/note_preview.test.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/note_previews/note_preview.test.tsx index 8092172a29cef..7cefaf08d76cb 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/note_previews/note_preview.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/note_previews/note_preview.test.tsx @@ -6,7 +6,7 @@ import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; -import * as React from 'react'; +import React from 'react'; import { ThemeProvider } from 'styled-components'; import { getEmptyValue } from '../../empty_value'; diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/note_previews/note_preview.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/note_previews/note_preview.tsx index 126a14f1f8e32..bb4a032734b5b 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/note_previews/note_preview.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/note_previews/note_preview.tsx @@ -6,7 +6,7 @@ import { EuiAvatar, EuiFlexGroup, EuiFlexItem, EuiText, EuiTitle, EuiToolTip } from '@elastic/eui'; import { FormattedRelative } from '@kbn/i18n/react'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { getEmptyValue, defaultToEmptyTag } from '../../empty_value'; diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline.test.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline.test.tsx index dbc7199aac725..a1ca7812bba34 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline.test.tsx @@ -7,7 +7,7 @@ import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; import { cloneDeep } from 'lodash/fp'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; -import * as React from 'react'; +import React from 'react'; import { ThemeProvider } from 'styled-components'; import { DEFAULT_SEARCH_RESULTS_PER_PAGE } from '../../pages/timelines/timelines_page'; @@ -143,7 +143,7 @@ describe('OpenTimeline', () => { ).toBe(true); }); - test('it shows extended columns and actions when onDeleteSelected and deleteTimelines are specified', () => { + test('it shows the delete action columns when onDeleteSelected and deleteTimelines are specified', () => { const wrapper = mountWithIntl( { .first() .props() as TimelinesTableProps; - expect(props.showExtendedColumnsAndActions).toBe(true); + expect(props.actionTimelineToShow).toContain('delete'); }); - test('it does NOT show extended columns and actions when is onDeleteSelected undefined and deleteTimelines is specified', () => { + test('it does NOT show the delete action columns when is onDeleteSelected undefined and deleteTimelines is specified', () => { const wrapper = mountWithIntl( { .first() .props() as TimelinesTableProps; - expect(props.showExtendedColumnsAndActions).toBe(false); + expect(props.actionTimelineToShow).not.toContain('delete'); }); - test('it does NOT show extended columns and actions when is onDeleteSelected provided and deleteTimelines is undefined', () => { + test('it does NOT show the delete action columns when is onDeleteSelected provided and deleteTimelines is undefined', () => { const wrapper = mountWithIntl( { .first() .props() as TimelinesTableProps; - expect(props.showExtendedColumnsAndActions).toBe(false); + expect(props.actionTimelineToShow).not.toContain('delete'); }); - test('it does NOT show extended columns and actions when both onDeleteSelected and deleteTimelines are undefined', () => { + test('it does NOT show the delete action when both onDeleteSelected and deleteTimelines are undefined', () => { const wrapper = mountWithIntl( { .first() .props() as TimelinesTableProps; - expect(props.showExtendedColumnsAndActions).toBe(false); + expect(props.actionTimelineToShow).not.toContain('delete'); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline.tsx index 3b4057c69a696..8aab02b495392 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline.tsx @@ -5,7 +5,7 @@ */ import { EuiPanel } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import { OPEN_TIMELINE_CLASS_NAME } from './helpers'; import { OpenTimelineProps } from './types'; @@ -57,6 +57,11 @@ export const OpenTimeline = React.memo( /> ( pageIndex={pageIndex} pageSize={pageSize} searchResults={searchResults} - showExtendedColumnsAndActions={onDeleteSelected != null && deleteTimelines != null} + showExtendedColumns={true} sortDirection={sortDirection} sortField={sortField} totalSearchResultsCount={totalSearchResultsCount} diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/index.test.tsx index e3dc6d974b5e0..ca8fa50c572fe 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/index.test.tsx @@ -6,7 +6,7 @@ import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; import { mount } from 'enzyme'; -import * as React from 'react'; +import React from 'react'; import { MockedProvider } from 'react-apollo/test-utils'; import { ThemeProvider } from 'styled-components'; diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/index.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/index.tsx index cd89eb8aad6f4..c530929a3c96e 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/index.tsx @@ -7,39 +7,49 @@ import { EuiModal, EuiOverlayMask } from '@elastic/eui'; import React from 'react'; +import { TimelineModel } from '../../../store/timeline/model'; import { useApolloClient } from '../../../utils/apollo_context'; + import * as i18n from '../translations'; +import { ActionTimelineToShow } from '../types'; import { StatefulOpenTimeline } from '..'; export interface OpenTimelineModalProps { onClose: () => void; + hideActions?: ActionTimelineToShow[]; + modalTitle?: string; + onOpen?: (timeline: TimelineModel) => void; } const DEFAULT_SEARCH_RESULTS_PER_PAGE = 10; const OPEN_TIMELINE_MODAL_WIDTH = 1000; // px -export const OpenTimelineModal = React.memo(({ onClose }) => { - const apolloClient = useApolloClient(); - - if (!apolloClient) return null; - - return ( - - - - - - ); -}); +export const OpenTimelineModal = React.memo( + ({ hideActions = [], modalTitle, onClose, onOpen }) => { + const apolloClient = useApolloClient(); + + if (!apolloClient) return null; + + return ( + + + + + + ); + } +); OpenTimelineModal.displayName = 'OpenTimelineModal'; diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/open_timeline_modal_body.test.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/open_timeline_modal_body.test.tsx index a5abb42c2e3b6..2c3adb138b7ac 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/open_timeline_modal_body.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/open_timeline_modal_body.test.tsx @@ -7,7 +7,7 @@ import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; import { cloneDeep } from 'lodash/fp'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; -import * as React from 'react'; +import React from 'react'; import { ThemeProvider } from 'styled-components'; import { DEFAULT_SEARCH_RESULTS_PER_PAGE } from '../../../pages/timelines/timelines_page'; @@ -143,7 +143,7 @@ describe('OpenTimelineModal', () => { ).toBe(true); }); - test('it shows extended columns and actions when onDeleteSelected and deleteTimelines are specified', () => { + test('it shows the delete action when onDeleteSelected and deleteTimelines are specified', () => { const wrapper = mountWithIntl( { .first() .props() as TimelinesTableProps; - expect(props.showExtendedColumnsAndActions).toBe(true); + expect(props.actionTimelineToShow).toContain('delete'); }); - test('it does NOT show extended columns and actions when is onDeleteSelected undefined and deleteTimelines is specified', () => { + test('it does NOT show the delete when is onDeleteSelected undefined and deleteTimelines is specified', () => { const wrapper = mountWithIntl( { .first() .props() as TimelinesTableProps; - expect(props.showExtendedColumnsAndActions).toBe(false); + expect(props.actionTimelineToShow).not.toContain('delete'); }); - test('it does NOT show extended columns and actions when is onDeleteSelected provided and deleteTimelines is undefined', () => { + test('it does NOT show the delete action when is onDeleteSelected provided and deleteTimelines is undefined', () => { const wrapper = mountWithIntl( { .first() .props() as TimelinesTableProps; - expect(props.showExtendedColumnsAndActions).toBe(false); + expect(props.actionTimelineToShow).not.toContain('delete'); }); - test('it does NOT show extended columns and actions when both onDeleteSelected and deleteTimelines are undefined', () => { + test('it does NOT show extended columns when both onDeleteSelected and deleteTimelines are undefined', () => { const wrapper = mountWithIntl( { .first() .props() as TimelinesTableProps; - expect(props.showExtendedColumnsAndActions).toBe(false); + expect(props.actionTimelineToShow).not.toContain('delete'); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/open_timeline_modal_body.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/open_timeline_modal_body.tsx index b097d2e0d01eb..dcd0b37770583 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/open_timeline_modal_body.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/open_timeline_modal_body.tsx @@ -5,10 +5,10 @@ */ import { EuiModalBody, EuiModalHeader } from '@elastic/eui'; -import * as React from 'react'; +import React, { memo, useMemo } from 'react'; import styled from 'styled-components'; -import { OpenTimelineProps } from '../types'; +import { OpenTimelineProps, ActionTimelineToShow } from '../types'; import { SearchRow } from '../search_row'; import { TimelinesTable } from '../timelines_table'; import { TitleRow } from '../title_row'; @@ -19,10 +19,11 @@ export const HeaderContainer = styled.div` HeaderContainer.displayName = 'HeaderContainer'; -export const OpenTimelineModalBody = React.memo( +export const OpenTimelineModalBody = memo( ({ deleteTimelines, defaultPageSize, + hideActions = [], isLoading, itemIdToExpandedNotesRowMap, onAddTimelinesToFavorites, @@ -43,51 +44,61 @@ export const OpenTimelineModalBody = React.memo( sortField, title, totalSearchResultsCount, - }) => ( - <> - - - + }) => { + const actionsToShow = useMemo(() => { + const actions: ActionTimelineToShow[] = + onDeleteSelected != null && deleteTimelines != null + ? ['delete', 'duplicate'] + : ['duplicate']; + return actions.filter(action => !hideActions.includes(action)); + }, [onDeleteSelected, deleteTimelines, hideActions]); + return ( + <> + + + + + + + - + - - - - - - - - ) + + + ); + } ); OpenTimelineModalBody.displayName = 'OpenTimelineModalBody'; diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/open_timeline_modal_button.test.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/open_timeline_modal_button.test.tsx index 9a70fd476e89e..66947a313f5e5 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/open_timeline_modal_button.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/open_timeline_modal/open_timeline_modal_button.test.tsx @@ -6,7 +6,7 @@ import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; import { mount } from 'enzyme'; -import * as React from 'react'; +import React from 'react'; import { MockedProvider } from 'react-apollo/test-utils'; import { ThemeProvider } from 'styled-components'; diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/search_row/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/search_row/index.test.tsx index c25bba7b6b041..1a4708ed5af08 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/search_row/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/search_row/index.test.tsx @@ -7,7 +7,7 @@ import { EuiFilterButtonProps } from '@elastic/eui'; import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; -import * as React from 'react'; +import React from 'react'; import { ThemeProvider } from 'styled-components'; import { SearchRow } from '.'; diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/search_row/index.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/search_row/index.tsx index fa16c8cfb7035..5765d31078bcf 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/search_row/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/search_row/index.tsx @@ -14,7 +14,7 @@ import { EuiText, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import * as i18n from '../translations'; diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/actions_columns.test.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/actions_columns.test.tsx index 749ba8672abea..eec11f571328f 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/actions_columns.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/actions_columns.test.tsx @@ -8,7 +8,7 @@ import { EuiButtonIconProps } from '@elastic/eui'; import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; import { cloneDeep, omit } from 'lodash/fp'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; -import * as React from 'react'; +import React from 'react'; import { ThemeProvider } from 'styled-components'; import { DEFAULT_SEARCH_RESULTS_PER_PAGE } from '../../../pages/timelines/timelines_page'; @@ -27,10 +27,11 @@ describe('#getActionsColumns', () => { mockResults = cloneDeep(mockTimelineResults); }); - test('it renders the delete timeline (trash icon) when showDeleteAction is true (because showExtendedColumnsAndActions is true)', () => { + test('it renders the delete timeline (trash icon) when actionTimelineToShow is including the action delete', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={mockResults} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={mockResults.length} @@ -53,10 +54,11 @@ describe('#getActionsColumns', () => { expect(wrapper.find('[data-test-subj="delete-timeline"]').exists()).toBe(true); }); - test('it does NOT render the delete timeline (trash icon) when showDeleteAction is false (because showExtendedColumnsAndActions is false)', () => { + test('it does NOT render the delete timeline (trash icon) when actionTimelineToShow is NOT including the action delete', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={mockResults} - showExtendedColumnsAndActions={false} + showExtendedColumns={false} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={mockResults.length} @@ -79,10 +81,65 @@ describe('#getActionsColumns', () => { expect(wrapper.find('[data-test-subj="delete-timeline"]').exists()).toBe(false); }); + test('it renders the duplicate icon timeline when actionTimelineToShow is including the action duplicate', () => { + const wrapper = mountWithIntl( + + + + ); + + expect(wrapper.find('[data-test-subj="open-duplicate"]').exists()).toBe(true); + }); + + test('it does NOT render the duplicate timeline when actionTimelineToShow is NOT including the action duplicate)', () => { + const wrapper = mountWithIntl( + + + + ); + + expect(wrapper.find('[data-test-subj="open-duplicate"]').exists()).toBe(false); + }); + test('it does NOT render the delete timeline (trash icon) when deleteTimelines is not provided', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={mockResults} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={mockResults.length} @@ -111,6 +168,7 @@ describe('#getActionsColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={missingSavedObjectId} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={missingSavedObjectId.length} @@ -141,6 +199,7 @@ describe('#getActionsColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={mockResults} - showExtendedColumnsAndActions={false} + showExtendedColumns={false} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={mockResults.length} @@ -174,6 +233,7 @@ describe('#getActionsColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={mockResults} - showExtendedColumnsAndActions={false} + showExtendedColumns={false} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={mockResults.length} diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/actions_columns.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/actions_columns.tsx index 3a8bc83fdc98a..2b8bd3339cca2 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/actions_columns.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/actions_columns.tsx @@ -4,25 +4,32 @@ * you may not use this file except in compliance with the Elastic License. */ +/* eslint-disable react/display-name */ + import { EuiButtonIcon, EuiToolTip } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import { ACTION_COLUMN_WIDTH } from './common_styles'; import { DeleteTimelineModalButton } from '../delete_timeline_modal'; import * as i18n from '../translations'; -import { DeleteTimelines, OnOpenTimeline, OpenTimelineResult } from '../types'; +import { + ActionTimelineToShow, + DeleteTimelines, + OnOpenTimeline, + OpenTimelineResult, +} from '../types'; /** * Returns the action columns (e.g. delete, open duplicate timeline) */ export const getActionsColumns = ({ + actionTimelineToShow, onOpenTimeline, deleteTimelines, - showDeleteAction, }: { + actionTimelineToShow: ActionTimelineToShow[]; deleteTimelines?: DeleteTimelines; onOpenTimeline: OnOpenTimeline; - showDeleteAction: boolean; }) => { const openAsDuplicateColumn = { align: 'center', @@ -65,7 +72,10 @@ export const getActionsColumns = ({ width: ACTION_COLUMN_WIDTH, }; - return showDeleteAction && deleteTimelines != null - ? [openAsDuplicateColumn, deleteTimelineColumn] - : [openAsDuplicateColumn]; + return [ + actionTimelineToShow.includes('duplicate') ? openAsDuplicateColumn : null, + actionTimelineToShow.includes('delete') && deleteTimelines != null + ? deleteTimelineColumn + : null, + ].filter(action => action != null); }; diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/common_columns.test.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/common_columns.test.tsx index fa08df1df4785..0f2cda9d79f0b 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/common_columns.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/common_columns.test.tsx @@ -7,7 +7,7 @@ import { EuiButtonIconProps } from '@elastic/eui'; import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; import { cloneDeep, omit } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { ThemeProvider } from 'styled-components'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; @@ -37,6 +37,7 @@ describe('#getCommonColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={hasNotes} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={hasNotes.length} @@ -63,6 +64,7 @@ describe('#getCommonColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={missingNotes} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={missingNotes.length} @@ -89,6 +91,7 @@ describe('#getCommonColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={nullNotes} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={nullNotes.length} @@ -115,6 +118,7 @@ describe('#getCommonColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={emptylNotes} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={emptylNotes.length} @@ -143,6 +147,7 @@ describe('#getCommonColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={missingSavedObjectId} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={missingSavedObjectId.length} @@ -169,6 +174,7 @@ describe('#getCommonColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={nullSavedObjectId} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={nullSavedObjectId.length} @@ -195,6 +201,7 @@ describe('#getCommonColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={hasNotes} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={hasNotes.length} @@ -231,6 +238,7 @@ describe('#getCommonColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={hasNotes} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={hasNotes.length} @@ -269,6 +277,7 @@ describe('#getCommonColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={hasNotes} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={hasNotes.length} @@ -311,6 +320,7 @@ describe('#getCommonColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={hasNotes} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={hasNotes.length} @@ -346,6 +356,7 @@ describe('#getCommonColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={mockResults} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={mockResults.length} @@ -377,6 +388,7 @@ describe('#getCommonColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={mockResults} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={mockResults.length} @@ -411,6 +423,7 @@ describe('#getCommonColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={missingSavedObjectId} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={missingSavedObjectId.length} @@ -442,6 +455,7 @@ describe('#getCommonColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={missingTitle} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={missingTitle.length} @@ -475,6 +489,7 @@ describe('#getCommonColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={withMissingSavedObjectIdAndTitle} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={withMissingSavedObjectIdAndTitle.length} @@ -508,6 +523,7 @@ describe('#getCommonColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={withJustWhitespaceTitle} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={withJustWhitespaceTitle.length} @@ -541,6 +557,7 @@ describe('#getCommonColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={withMissingSavedObjectId} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={withMissingSavedObjectId.length} @@ -571,6 +588,7 @@ describe('#getCommonColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={mockResults} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={mockResults.length} @@ -605,6 +623,7 @@ describe('#getCommonColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={missingSavedObjectId} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={missingSavedObjectId.length} @@ -637,6 +656,7 @@ describe('#getCommonColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={mockResults} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={mockResults.length} @@ -673,6 +693,7 @@ describe('#getCommonColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={mockResults} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={mockResults.length} @@ -704,6 +725,7 @@ describe('#getCommonColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={mockResults} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={mockResults.length} @@ -737,6 +759,7 @@ describe('#getCommonColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={missingDescription} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={missingDescription.length} @@ -771,6 +794,7 @@ describe('#getCommonColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={justWhitespaceDescription} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={justWhitespaceDescription.length} @@ -803,6 +827,7 @@ describe('#getCommonColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={mockResults} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={mockResults.length} @@ -834,6 +859,7 @@ describe('#getCommonColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={mockResults} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={mockResults.length} @@ -868,6 +894,7 @@ describe('#getCommonColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={missingUpdated} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={missingUpdated.length} diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/common_columns.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/common_columns.tsx index e4ab42bb37c55..0d3a73a389050 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/common_columns.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/common_columns.tsx @@ -4,9 +4,11 @@ * you may not use this file except in compliance with the Elastic License. */ +/* eslint-disable react/display-name */ + import { EuiButtonIcon, EuiLink } from '@elastic/eui'; import { omit } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { ACTION_COLUMN_WIDTH } from './common_styles'; import { isUntitled } from '../helpers'; @@ -25,11 +27,9 @@ export const getCommonColumns = ({ itemIdToExpandedNotesRowMap, onOpenTimeline, onToggleShowNotes, - showExtendedColumnsAndActions, }: { onOpenTimeline: OnOpenTimeline; onToggleShowNotes: OnToggleShowNotes; - showExtendedColumnsAndActions: boolean; itemIdToExpandedNotesRowMap: Record; }) => [ { diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/extended_columns.test.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/extended_columns.test.tsx index 13362e0f43a28..4cbe1e45c473b 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/extended_columns.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/extended_columns.test.tsx @@ -7,7 +7,7 @@ import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; import { cloneDeep, omit } from 'lodash/fp'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; -import * as React from 'react'; +import React from 'react'; import { ThemeProvider } from 'styled-components'; import { DEFAULT_SEARCH_RESULTS_PER_PAGE } from '../../../pages/timelines/timelines_page'; @@ -35,6 +35,7 @@ describe('#getExtendedColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={mockResults} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={mockResults.length} @@ -66,6 +67,7 @@ describe('#getExtendedColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={mockResults} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={mockResults.length} @@ -99,6 +101,7 @@ describe('#getExtendedColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={missingUpdatedBy} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={missingUpdatedBy.length} diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/extended_columns.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/extended_columns.tsx index 63f6b8b674a3d..b6d874fa0c4d1 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/extended_columns.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/extended_columns.tsx @@ -4,7 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +/* eslint-disable react/display-name */ + +import React from 'react'; import { defaultToEmptyTag } from '../../empty_value'; diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/icon_header_columns.test.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/icon_header_columns.test.tsx index b6048b85eea75..31377d176acac 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/icon_header_columns.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/icon_header_columns.test.tsx @@ -7,7 +7,7 @@ import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; import { cloneDeep, omit } from 'lodash/fp'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; -import * as React from 'react'; +import React from 'react'; import { ThemeProvider } from 'styled-components'; import { DEFAULT_SEARCH_RESULTS_PER_PAGE } from '../../../pages/timelines/timelines_page'; @@ -30,6 +30,7 @@ describe('#getActionsColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={mockResults} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={mockResults.length} @@ -57,6 +58,7 @@ describe('#getActionsColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={with6Events} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={with6Events.length} @@ -82,6 +84,7 @@ describe('#getActionsColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={mockResults} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={mockResults.length} @@ -109,6 +112,7 @@ describe('#getActionsColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={with4Notes} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={with4Notes.length} @@ -134,6 +138,7 @@ describe('#getActionsColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={mockResults} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={mockResults.length} @@ -161,6 +166,7 @@ describe('#getActionsColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={undefinedFavorite} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={undefinedFavorite.length} @@ -187,6 +193,7 @@ describe('#getActionsColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={nullFavorite} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={nullFavorite.length} @@ -213,6 +220,7 @@ describe('#getActionsColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={emptyFavorite} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={emptyFavorite.length} @@ -249,6 +257,7 @@ describe('#getActionsColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={emptyFavorite} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={emptyFavorite.length} @@ -289,6 +298,7 @@ describe('#getActionsColumns', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={emptyFavorite} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={emptyFavorite.length} diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/icon_header_columns.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/icon_header_columns.tsx index 6d8a792e91b02..5b0f3ded7d71b 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/icon_header_columns.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/icon_header_columns.tsx @@ -4,8 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ +/* eslint-disable react/display-name */ + import { EuiIcon, EuiToolTip } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import { ACTION_COLUMN_WIDTH } from './common_styles'; import { getNotesCount, getPinnedEventCount } from '../helpers'; diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/index.test.tsx index d75863d1ccb8b..26d9607a91fcd 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/index.test.tsx @@ -7,7 +7,7 @@ import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; import { cloneDeep } from 'lodash/fp'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; -import * as React from 'react'; +import React from 'react'; import { ThemeProvider } from 'styled-components'; import { DEFAULT_SEARCH_RESULTS_PER_PAGE } from '../../../pages/timelines/timelines_page'; @@ -28,10 +28,11 @@ describe('TimelinesTable', () => { mockResults = cloneDeep(mockTimelineResults); }); - test('it renders the select all timelines header checkbox when showExtendedColumnsAndActions is true', () => { + test('it renders the select all timelines header checkbox when actionTimelineToShow has the action selectable', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={mockResults} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={mockResults.length} @@ -59,10 +60,11 @@ describe('TimelinesTable', () => { ).toBe(true); }); - test('it does NOT render the select all timelines header checkbox when showExtendedColumnsAndActions is false', () => { + test('it does NOT render the select all timelines header checkbox when actionTimelineToShow has not the action selectable', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={mockResults} - showExtendedColumnsAndActions={false} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={mockResults.length} @@ -90,10 +92,11 @@ describe('TimelinesTable', () => { ).toBe(false); }); - test('it renders the Modified By column when showExtendedColumnsAndActions is true ', () => { + test('it renders the Modified By column when showExtendedColumns is true ', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={mockResults} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={mockResults.length} @@ -121,10 +124,11 @@ describe('TimelinesTable', () => { ).toContain(i18n.MODIFIED_BY); }); - test('it renders the notes column in the position of the Modified By column when showExtendedColumnsAndActions is false', () => { + test('it renders the notes column in the position of the Modified By column when showExtendedColumns is false', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={mockResults} - showExtendedColumnsAndActions={false} + showExtendedColumns={false} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={mockResults.length} @@ -148,16 +152,17 @@ describe('TimelinesTable', () => { wrapper .find('thead tr th') .at(5) - .find('[data-test-subj="notes-count-header-icon"]') + .find('svg[data-test-subj="notes-count-header-icon"]') .first() .exists() ).toBe(true); }); - test('it renders the delete timeline (trash icon) when showExtendedColumnsAndActions is true', () => { + test('it renders the delete timeline (trash icon) when actionTimelineToShow has the delete action', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={mockResults} - showExtendedColumnsAndActions={true} + showExtendedColumns={false} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={mockResults.length} @@ -185,10 +190,11 @@ describe('TimelinesTable', () => { ).toBe(true); }); - test('it does NOT render the delete timeline (trash icon) when showExtendedColumnsAndActions is false', () => { + test('it does NOT render the delete timeline (trash icon) when actionTimelineToShow has NOT the delete action', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={mockResults} - showExtendedColumnsAndActions={false} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={mockResults.length} @@ -216,10 +222,11 @@ describe('TimelinesTable', () => { ).toBe(false); }); - test('it renders the rows per page selector when showExtendedColumnsAndActions is true', () => { + test('it renders the rows per page selector when showExtendedColumns is true', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={mockResults} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={mockResults.length} @@ -247,10 +254,11 @@ describe('TimelinesTable', () => { ).toBe(true); }); - test('it does NOT render the rows per page selector when showExtendedColumnsAndActions is false', () => { + test('it does NOT render the rows per page selector when showExtendedColumns is false', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={mockResults} - showExtendedColumnsAndActions={false} + showExtendedColumns={false} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={mockResults.length} @@ -284,6 +292,7 @@ describe('TimelinesTable', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={defaultPageSize} searchResults={mockResults} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={mockResults.length} @@ -311,10 +320,11 @@ describe('TimelinesTable', () => { ).toEqual('Rows per page: 123'); }); - test('it sorts the Last Modified column in descending order when showExtendedColumnsAndActions is true ', () => { + test('it sorts the Last Modified column in descending order when showExtendedColumns is true ', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={mockResults} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={mockResults.length} @@ -342,10 +352,11 @@ describe('TimelinesTable', () => { ).toContain(i18n.LAST_MODIFIED); }); - test('it sorts the Last Modified column in descending order when showExtendedColumnsAndActions is false ', () => { + test('it sorts the Last Modified column in descending order when showExtendedColumns is false ', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={mockResults} - showExtendedColumnsAndActions={false} + showExtendedColumns={false} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={mockResults.length} @@ -376,6 +387,7 @@ describe('TimelinesTable', () => { test('it displays the expected message when no search results are found', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={[]} - showExtendedColumnsAndActions={false} + showExtendedColumns={false} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={0} @@ -408,6 +420,7 @@ describe('TimelinesTable', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={mockResults} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={mockResults.length} @@ -446,6 +459,7 @@ describe('TimelinesTable', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={mockResults} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={mockResults.length} @@ -479,6 +493,7 @@ describe('TimelinesTable', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={mockResults} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={mockResults.length} @@ -510,6 +525,7 @@ describe('TimelinesTable', () => { const wrapper = mountWithIntl( { pageIndex={0} pageSize={DEFAULT_SEARCH_RESULTS_PER_PAGE} searchResults={mockResults} - showExtendedColumnsAndActions={true} + showExtendedColumns={true} sortDirection={DEFAULT_SORT_DIRECTION} sortField={DEFAULT_SORT_FIELD} totalSearchResultsCount={mockResults.length} diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/index.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/index.tsx index 8f25b5345988a..f09a9f6af048b 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/timelines_table/index.tsx @@ -5,11 +5,12 @@ */ import { EuiBasicTable as _EuiBasicTable } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import * as i18n from '../translations'; import { + ActionTimelineToShow, DeleteTimelines, OnOpenTimeline, OnSelectionChange, @@ -36,8 +37,8 @@ const BasicTable = styled(EuiBasicTable)` `; BasicTable.displayName = 'BasicTable'; -const getExtendedColumnsIfEnabled = (showExtendedColumnsAndActions: boolean) => - showExtendedColumnsAndActions ? [...getExtendedColumns()] : []; +const getExtendedColumnsIfEnabled = (showExtendedColumns: boolean) => + showExtendedColumns ? [...getExtendedColumns()] : []; /** * Returns the column definitions (passed as the `columns` prop to @@ -46,34 +47,36 @@ const getExtendedColumnsIfEnabled = (showExtendedColumnsAndActions: boolean) => * `Timelines` page */ const getTimelinesTableColumns = ({ + actionTimelineToShow, deleteTimelines, itemIdToExpandedNotesRowMap, onOpenTimeline, onToggleShowNotes, - showExtendedColumnsAndActions, + showExtendedColumns, }: { + actionTimelineToShow: ActionTimelineToShow[]; deleteTimelines?: DeleteTimelines; itemIdToExpandedNotesRowMap: Record; onOpenTimeline: OnOpenTimeline; onToggleShowNotes: OnToggleShowNotes; - showExtendedColumnsAndActions: boolean; + showExtendedColumns: boolean; }) => [ ...getCommonColumns({ itemIdToExpandedNotesRowMap, onOpenTimeline, onToggleShowNotes, - showExtendedColumnsAndActions, }), - ...getExtendedColumnsIfEnabled(showExtendedColumnsAndActions), + ...getExtendedColumnsIfEnabled(showExtendedColumns), ...getIconHeaderColumns(), ...getActionsColumns({ deleteTimelines, onOpenTimeline, - showDeleteAction: showExtendedColumnsAndActions, + actionTimelineToShow, }), ]; export interface TimelinesTableProps { + actionTimelineToShow: ActionTimelineToShow[]; deleteTimelines?: DeleteTimelines; defaultPageSize: number; loading: boolean; @@ -85,7 +88,7 @@ export interface TimelinesTableProps { pageIndex: number; pageSize: number; searchResults: OpenTimelineResult[]; - showExtendedColumnsAndActions: boolean; + showExtendedColumns: boolean; sortDirection: 'asc' | 'desc'; sortField: string; totalSearchResultsCount: number; @@ -97,6 +100,7 @@ export interface TimelinesTableProps { */ export const TimelinesTable = React.memo( ({ + actionTimelineToShow, deleteTimelines, defaultPageSize, loading: isLoading, @@ -108,13 +112,13 @@ export const TimelinesTable = React.memo( pageIndex, pageSize, searchResults, - showExtendedColumnsAndActions, + showExtendedColumns, sortField, sortDirection, totalSearchResultsCount, }) => { const pagination = { - hidePerPageOptions: !showExtendedColumnsAndActions, + hidePerPageOptions: !showExtendedColumns, pageIndex, pageSize, pageSizeOptions: [ @@ -142,16 +146,17 @@ export const TimelinesTable = React.memo( return ( ( noItemsMessage={i18n.ZERO_TIMELINES_MATCH} onChange={onTableChange} pagination={pagination} - selection={showExtendedColumnsAndActions ? selection : undefined} + selection={actionTimelineToShow.includes('selectable') ? selection : undefined} sorting={sorting} /> ); diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/title_row/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/title_row/index.test.tsx index 9303c09c994aa..88dfab470ac96 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/title_row/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/title_row/index.test.tsx @@ -7,7 +7,7 @@ import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; import { EuiButtonProps } from '@elastic/eui'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; -import * as React from 'react'; +import React from 'react'; import { ThemeProvider } from 'styled-components'; import { TitleRow } from '.'; diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/title_row/index.tsx b/x-pack/legacy/plugins/siem/public/components/open_timeline/title_row/index.tsx index 2e82c4979f41d..c7de367e04364 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/title_row/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/title_row/index.tsx @@ -5,7 +5,7 @@ */ import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import * as i18n from '../translations'; import { OpenTimelineProps } from '../types'; diff --git a/x-pack/legacy/plugins/siem/public/components/open_timeline/types.ts b/x-pack/legacy/plugins/siem/public/components/open_timeline/types.ts index 7bbefb9efa99e..e5e85ccf0954a 100644 --- a/x-pack/legacy/plugins/siem/public/components/open_timeline/types.ts +++ b/x-pack/legacy/plugins/siem/public/components/open_timeline/types.ts @@ -95,6 +95,8 @@ export interface OnTableChangeParams { /** Invoked by the EUI table implementation when the user interacts with the table */ export type OnTableChange = (tableChange: OnTableChangeParams) => void; +export type ActionTimelineToShow = 'duplicate' | 'delete' | 'selectable'; + export interface OpenTimelineProps { /** Invoked when the user clicks the delete (trash) icon on an individual timeline */ deleteTimelines?: DeleteTimelines; @@ -140,6 +142,8 @@ export interface OpenTimelineProps { title: string; /** The total (server-side) count of the search results */ totalSearchResultsCount: number; + /** Hide action on timeline if needed it */ + hideActions?: ActionTimelineToShow[]; } export interface UpdateTimeline { diff --git a/x-pack/legacy/plugins/siem/public/components/page/add_filter_to_global_search_bar/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/add_filter_to_global_search_bar/index.test.tsx index 70e75cb54671a..345701c97901f 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/add_filter_to_global_search_bar/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/add_filter_to_global_search_bar/index.test.tsx @@ -5,8 +5,7 @@ */ import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { apolloClientObservable, mockGlobalState, TestProviders } from '../../../mock'; import { createStore, State } from '../../../store'; @@ -63,7 +62,7 @@ describe('AddFilterToGlobalSearchBar Component', () => { ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('Rendering tooltip', async () => { diff --git a/x-pack/legacy/plugins/siem/public/components/page/detection_engine/histogram_signals/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/page/detection_engine/histogram_signals/__snapshots__/index.test.tsx.snap deleted file mode 100644 index d2579f427debe..0000000000000 --- a/x-pack/legacy/plugins/siem/public/components/page/detection_engine/histogram_signals/__snapshots__/index.test.tsx.snap +++ /dev/null @@ -1,3 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`HistogramSignals it renders 1`] = ``; diff --git a/x-pack/legacy/plugins/siem/public/components/page/detection_engine/histogram_signals/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/detection_engine/histogram_signals/index.test.tsx deleted file mode 100644 index ad1d80a761854..0000000000000 --- a/x-pack/legacy/plugins/siem/public/components/page/detection_engine/histogram_signals/index.test.tsx +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import React from 'react'; - -import { TestProviders } from '../../../../mock'; -import { HistogramSignals } from './index'; - -describe('HistogramSignals', () => { - test('it renders', () => { - const wrapper = shallow( - - - - ); - - expect(toJson(wrapper.find('HistogramSignals'))).toMatchSnapshot(); - }); -}); diff --git a/x-pack/legacy/plugins/siem/public/components/page/detection_engine/histogram_signals/index.tsx b/x-pack/legacy/plugins/siem/public/components/page/detection_engine/histogram_signals/index.tsx deleted file mode 100644 index fa26664930fe5..0000000000000 --- a/x-pack/legacy/plugins/siem/public/components/page/detection_engine/histogram_signals/index.tsx +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { - Axis, - Chart, - HistogramBarSeries, - Settings, - getAxisId, - getSpecId, - niceTimeFormatByDay, - timeFormatter, -} from '@elastic/charts'; -import React from 'react'; -import { npStart } from 'ui/new_platform'; - -export const HistogramSignals = React.memo(() => { - const sampleChartData = [ - { x: 1571090784000, y: 2, a: 'a' }, - { x: 1571090784000, y: 2, b: 'b' }, - { x: 1571093484000, y: 7, a: 'a' }, - { x: 1571096184000, y: 3, a: 'a' }, - { x: 1571098884000, y: 2, a: 'a' }, - { x: 1571101584000, y: 7, a: 'a' }, - { x: 1571104284000, y: 3, a: 'a' }, - { x: 1571106984000, y: 2, a: 'a' }, - { x: 1571109684000, y: 7, a: 'a' }, - { x: 1571112384000, y: 3, a: 'a' }, - { x: 1571115084000, y: 2, a: 'a' }, - { x: 1571117784000, y: 7, a: 'a' }, - { x: 1571120484000, y: 3, a: 'a' }, - { x: 1571123184000, y: 2, a: 'a' }, - { x: 1571125884000, y: 7, a: 'a' }, - { x: 1571128584000, y: 3, a: 'a' }, - { x: 1571131284000, y: 2, a: 'a' }, - { x: 1571133984000, y: 7, a: 'a' }, - { x: 1571136684000, y: 3, a: 'a' }, - { x: 1571139384000, y: 2, a: 'a' }, - { x: 1571142084000, y: 7, a: 'a' }, - { x: 1571144784000, y: 3, a: 'a' }, - { x: 1571147484000, y: 2, a: 'a' }, - { x: 1571150184000, y: 7, a: 'a' }, - { x: 1571152884000, y: 3, a: 'a' }, - { x: 1571155584000, y: 2, a: 'a' }, - { x: 1571158284000, y: 7, a: 'a' }, - { x: 1571160984000, y: 3, a: 'a' }, - { x: 1571163684000, y: 2, a: 'a' }, - { x: 1571166384000, y: 7, a: 'a' }, - { x: 1571169084000, y: 3, a: 'a' }, - { x: 1571171784000, y: 2, a: 'a' }, - { x: 1571174484000, y: 7, a: 'a' }, - ]; - - return ( - - - - - - - - - - ); -}); -HistogramSignals.displayName = 'HistogramSignals'; diff --git a/x-pack/legacy/plugins/siem/public/components/page/hosts/authentications_table/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/hosts/authentications_table/index.test.tsx index 71e61e2425373..d7c25e97b3838 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/hosts/authentications_table/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/hosts/authentications_table/index.test.tsx @@ -5,9 +5,8 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import { getOr } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { Provider as ReduxStoreProvider } from 'react-redux'; import { apolloClientObservable, mockGlobalState } from '../../../../mock'; @@ -49,7 +48,7 @@ describe('Authentication Table Component', () => { ); - expect(toJson(wrapper.find('Connect(AuthenticationTableComponent)'))).toMatchSnapshot(); + expect(wrapper.find('Connect(AuthenticationTableComponent)')).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/page/hosts/authentications_table/index.tsx b/x-pack/legacy/plugins/siem/public/components/page/hosts/authentications_table/index.tsx index ad5755068e662..f5485922647ca 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/hosts/authentications_table/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/hosts/authentications_table/index.tsx @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +/* eslint-disable react/display-name */ + import { has } from 'lodash/fp'; import React, { useCallback } from 'react'; import { connect } from 'react-redux'; diff --git a/x-pack/legacy/plugins/siem/public/components/page/hosts/first_last_seen_host/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/hosts/first_last_seen_host/index.test.tsx index 35c1eded18f15..4a836333f3311 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/hosts/first_last_seen_host/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/hosts/first_last_seen_host/index.test.tsx @@ -5,7 +5,7 @@ */ import { cloneDeep } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { MockedProvider } from 'react-apollo/test-utils'; import { render, act } from '@testing-library/react'; @@ -15,8 +15,6 @@ import { TestProviders } from '../../../../mock'; import { FirstLastSeenHost, FirstLastSeenHostType } from '.'; -jest.mock('../../../../lib/kibana'); - describe('FirstLastSeen Component', () => { const firstSeen = 'Apr 8, 2019 @ 16:09:40.692'; const lastSeen = 'Apr 8, 2019 @ 18:35:45.064'; diff --git a/x-pack/legacy/plugins/siem/public/components/page/hosts/host_overview/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/hosts/host_overview/index.test.tsx index 830665f827301..90cfe696610d9 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/hosts/host_overview/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/hosts/host_overview/index.test.tsx @@ -5,8 +5,7 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { TestProviders } from '../../../../mock'; import { HostOverview } from './index'; @@ -31,7 +30,7 @@ describe('Host Summary Component', () => { ); - expect(toJson(wrapper.find('HostOverview'))).toMatchSnapshot(); + expect(wrapper.find('HostOverview')).toMatchSnapshot(); }); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/page/hosts/hosts_table/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/hosts/hosts_table/index.test.tsx index 4728925eb741a..e561594013dea 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/hosts/hosts_table/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/hosts/hosts_table/index.test.tsx @@ -5,9 +5,8 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import { getOr } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { MockedProvider } from 'react-apollo/test-utils'; import { @@ -61,7 +60,7 @@ describe('Hosts Table', () => { ); - expect(toJson(wrapper.find('HostsTable'))).toMatchSnapshot(); + expect(wrapper.find('HostsTable')).toMatchSnapshot(); }); describe('Sorting on Table', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/page/hosts/kpi_hosts/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/hosts/kpi_hosts/index.test.tsx index 577ec5ff51470..dc2340d42ebd9 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/hosts/kpi_hosts/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/hosts/kpi_hosts/index.test.tsx @@ -7,7 +7,6 @@ import { mockKpiHostsData, mockKpiHostDetailsData } from './mock'; import React from 'react'; import { shallow, ShallowWrapper } from 'enzyme'; -import toJson from 'enzyme-to-json'; import { KpiHostsComponentBase } from '.'; import * as statItems from '../../../stat_items'; import { kpiHostsMapping } from './kpi_hosts_mapping'; @@ -30,7 +29,7 @@ describe('kpiHostsComponent', () => { narrowDateRange={narrowDateRange} /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it should render KpiHostsData', () => { @@ -44,7 +43,7 @@ describe('kpiHostsComponent', () => { narrowDateRange={narrowDateRange} /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it should render KpiHostDetailsData', () => { @@ -58,7 +57,7 @@ describe('kpiHostsComponent', () => { narrowDateRange={narrowDateRange} /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/page/hosts/uncommon_process_table/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/hosts/uncommon_process_table/index.test.tsx index 28ddb1df12c3a..76fc2a0c389c3 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/hosts/uncommon_process_table/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/hosts/uncommon_process_table/index.test.tsx @@ -5,9 +5,8 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import { getOr } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { TestProviders } from '../../../../mock'; import { hostsModel } from '../../../../store'; @@ -45,7 +44,7 @@ describe('Uncommon Process Table Component', () => { ); - expect(toJson(wrapper.find('UncommonProcessTable'))).toMatchSnapshot(); + expect(wrapper.find('UncommonProcessTable')).toMatchSnapshot(); }); test('it has a double dash (empty value) without any hosts at all', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/page/hosts/uncommon_process_table/index.tsx b/x-pack/legacy/plugins/siem/public/components/page/hosts/uncommon_process_table/index.tsx index a8b9b2a1f61ce..b4c01053f0e9c 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/hosts/uncommon_process_table/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/hosts/uncommon_process_table/index.tsx @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +/* eslint-disable react/display-name */ + import React, { useCallback } from 'react'; import { connect } from 'react-redux'; import { ActionCreator } from 'typescript-fsa'; diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/dns_histogram/index.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/dns_histogram/index.tsx index 490efd08f0aa7..e7b0d8e7d00d5 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/dns_histogram/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/network/dns_histogram/index.tsx @@ -9,22 +9,23 @@ import React from 'react'; import { ScaleType } from '@elastic/charts'; import * as i18n from './translation'; import { MatrixHistogram } from '../../../matrix_histogram'; -import { bytesFormatter } from '../../../matrix_histogram/utils'; import { MatrixOverOrdinalHistogramData } from '../../../../graphql/types'; import { MatrixHistogramBasicProps } from '../../../matrix_histogram/types'; +import { useFormatBytes } from '../../../formatted_bytes'; export const NetworkDnsHistogram = ( props: MatrixHistogramBasicProps ) => { const dataKey = 'histogram'; const { ...matrixOverTimeProps } = props; + const formatBytes = useFormatBytes(); return ( diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/flow_target_select_connected/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/flow_target_select_connected/index.test.tsx index c9d18d5f996f3..8c744c6573ee8 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/flow_target_select_connected/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/network/flow_target_select_connected/index.test.tsx @@ -5,7 +5,7 @@ */ import { mount } from 'enzyme'; -import * as React from 'react'; +import React from 'react'; import { apolloClientObservable, mockGlobalState, TestProviders } from '../../../../mock'; import { createStore, State } from '../../../../store'; diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/ip_overview/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/ip_overview/index.test.tsx index 6c2edd764855c..3038d7f41c632 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/ip_overview/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/network/ip_overview/index.test.tsx @@ -5,8 +5,7 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { ActionCreator } from 'typescript-fsa'; import { FlowTarget } from '../../../../graphql/types'; @@ -52,7 +51,7 @@ describe('IP Overview Component', () => { ); - expect(toJson(wrapper.find('IpOverview'))).toMatchSnapshot(); + expect(wrapper.find('IpOverview')).toMatchSnapshot(); }); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/kpi_network/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/kpi_network/index.test.tsx index 964617c4c85b1..48d3b25f59e4a 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/kpi_network/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/network/kpi_network/index.test.tsx @@ -5,8 +5,7 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { Provider as ReduxStoreProvider } from 'react-redux'; import { apolloClientObservable, mockGlobalState } from '../../../../mock'; @@ -42,7 +41,7 @@ describe('KpiNetwork Component', () => { ); - expect(toJson(wrapper.find('KpiNetworkComponent'))).toMatchSnapshot(); + expect(wrapper.find('KpiNetworkComponent')).toMatchSnapshot(); }); test('it renders the default widget', () => { @@ -59,7 +58,7 @@ describe('KpiNetwork Component', () => { ); - expect(toJson(wrapper.find('KpiNetworkComponent'))).toMatchSnapshot(); + expect(wrapper.find('KpiNetworkComponent')).toMatchSnapshot(); }); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/kpi_network/index.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/kpi_network/index.tsx index d59d4ccd60c60..73602fe3400a9 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/kpi_network/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/network/kpi_network/index.tsx @@ -146,6 +146,8 @@ export const KpiNetworkBaseComponent = React.memo<{ ); }); +KpiNetworkBaseComponent.displayName = 'KpiNetworkBaseComponent'; + export const KpiNetworkComponent = React.memo( ({ data, from, id, loading, to, narrowDateRange }) => { return loading ? ( diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/network_dns_table/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/network_dns_table/index.test.tsx index b88653bcadde8..e425057dd0f75 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/network_dns_table/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/network/network_dns_table/index.test.tsx @@ -5,9 +5,8 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import { getOr } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { MockedProvider } from 'react-apollo/test-utils'; import { Provider as ReduxStoreProvider } from 'react-redux'; @@ -50,7 +49,7 @@ describe('NetworkTopNFlow Table Component', () => { ); - expect(toJson(wrapper.find('Connect(NetworkDnsTableComponent)'))).toMatchSnapshot(); + expect(wrapper.find('Connect(NetworkDnsTableComponent)')).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/network_dns_table/is_ptr_included.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/network_dns_table/is_ptr_included.test.tsx index e39723f57f0b0..31a1b1667087a 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/network_dns_table/is_ptr_included.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/network/network_dns_table/is_ptr_included.test.tsx @@ -5,8 +5,7 @@ */ import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { FlowDirection } from '../../../../graphql/types'; @@ -19,7 +18,7 @@ describe('NetworkTopNFlow Select direction', () => { test('it renders the basic switch to include PTR in table', () => { const wrapper = shallow(); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/network_http_table/columns.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/network_http_table/columns.tsx index 6a47a58c85f31..bffc7235b6804 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/network_http_table/columns.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/network/network_http_table/columns.tsx @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +/* eslint-disable react/display-name */ + import React from 'react'; import numeral from '@elastic/numeral'; import { NetworkHttpEdges, NetworkHttpFields, NetworkHttpItem } from '../../../../graphql/types'; diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/network_http_table/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/network_http_table/index.test.tsx index 81e0c7fad7b39..6ce87728ebff7 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/network_http_table/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/network/network_http_table/index.test.tsx @@ -5,9 +5,8 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import { getOr } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { MockedProvider } from 'react-apollo/test-utils'; import { Provider as ReduxStoreProvider } from 'react-redux'; @@ -51,7 +50,7 @@ describe('NetworkHttp Table Component', () => { ); - expect(toJson(wrapper.find('Connect(NetworkHttpTableComponent)'))).toMatchSnapshot(); + expect(wrapper.find('Connect(NetworkHttpTableComponent)')).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/network_top_countries_table/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/network_top_countries_table/index.test.tsx index 8fd245b077243..764e440a5a4be 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/network_top_countries_table/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/network/network_top_countries_table/index.test.tsx @@ -5,9 +5,8 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import { getOr } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { MockedProvider } from 'react-apollo/test-utils'; import { Provider as ReduxStoreProvider } from 'react-redux'; @@ -59,7 +58,7 @@ describe('NetworkTopCountries Table Component', () => { ); - expect(toJson(wrapper.find('Connect(NetworkTopCountriesTableComponent)'))).toMatchSnapshot(); + expect(wrapper.find('Connect(NetworkTopCountriesTableComponent)')).toMatchSnapshot(); }); test('it renders the IP Details NetworkTopCountries table', () => { const wrapper = shallow( @@ -84,7 +83,7 @@ describe('NetworkTopCountries Table Component', () => { ); - expect(toJson(wrapper.find('Connect(NetworkTopCountriesTableComponent)'))).toMatchSnapshot(); + expect(wrapper.find('Connect(NetworkTopCountriesTableComponent)')).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/network_top_n_flow_table/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/network_top_n_flow_table/index.test.tsx index 5c4aa862283f2..24f68ef03d891 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/network_top_n_flow_table/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/network/network_top_n_flow_table/index.test.tsx @@ -5,9 +5,8 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import { getOr } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { MockedProvider } from 'react-apollo/test-utils'; import { Provider as ReduxStoreProvider } from 'react-redux'; @@ -59,7 +58,7 @@ describe('NetworkTopNFlow Table Component', () => { ); - expect(toJson(wrapper.find('Connect(NetworkTopNFlowTableComponent)'))).toMatchSnapshot(); + expect(wrapper.find('Connect(NetworkTopNFlowTableComponent)')).toMatchSnapshot(); }); test('it renders the default NetworkTopNFlow table on the IP Details page', () => { @@ -85,7 +84,7 @@ describe('NetworkTopNFlow Table Component', () => { ); - expect(toJson(wrapper.find('Connect(NetworkTopNFlowTableComponent)'))).toMatchSnapshot(); + expect(wrapper.find('Connect(NetworkTopNFlowTableComponent)')).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/tls_table/columns.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/tls_table/columns.tsx index aea8ee9e6b9e1..44a538871d951 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/tls_table/columns.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/network/tls_table/columns.tsx @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +/* eslint-disable react/display-name */ + import React from 'react'; import moment from 'moment'; import { TlsNode } from '../../../../graphql/types'; diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/tls_table/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/tls_table/index.test.tsx index 920d1cd8210e5..81a472f3175e5 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/tls_table/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/network/tls_table/index.test.tsx @@ -5,9 +5,8 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import { getOr } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { MockedProvider } from 'react-apollo/test-utils'; import { Provider as ReduxStoreProvider } from 'react-redux'; @@ -47,7 +46,7 @@ describe('Tls Table Component', () => { ); - expect(toJson(wrapper.find('Connect(TlsTableComponent)'))).toMatchSnapshot(); + expect(wrapper.find('Connect(TlsTableComponent)')).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/page/network/users_table/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/network/users_table/index.test.tsx index d01923f01543f..b23c7bd504fb7 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/network/users_table/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/network/users_table/index.test.tsx @@ -5,9 +5,8 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import { getOr } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { MockedProvider } from 'react-apollo/test-utils'; import { Provider as ReduxStoreProvider } from 'react-redux'; @@ -55,7 +54,7 @@ describe('Users Table Component', () => { ); - expect(toJson(wrapper.find('Connect(UsersTableComponent)'))).toMatchSnapshot(); + expect(wrapper.find('Connect(UsersTableComponent)')).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/page/overview/overview_host_stats/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/overview/overview_host_stats/index.test.tsx index d324df2ecb0c1..f99b2687d7072 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/overview/overview_host_stats/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/overview/overview_host_stats/index.test.tsx @@ -5,8 +5,7 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { OverviewHostStats } from '.'; import { mockData } from './mock'; @@ -15,7 +14,7 @@ describe('Overview Host Stat Data', () => { describe('rendering', () => { test('it renders the default OverviewHostStats', () => { const wrapper = shallow(); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); }); describe('loading', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/page/overview/overview_network_stats/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/page/overview/overview_network_stats/index.test.tsx index 1b28c032221ab..08093c5d38c15 100644 --- a/x-pack/legacy/plugins/siem/public/components/page/overview/overview_network_stats/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/page/overview/overview_network_stats/index.test.tsx @@ -5,8 +5,7 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { OverviewNetworkStats } from '.'; import { mockData } from './mock'; @@ -17,7 +16,7 @@ describe('Overview Network Stat Data', () => { const wrapper = shallow( ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); }); describe('loading', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/paginated_table/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/paginated_table/index.test.tsx index 0444360d2b965..947bdee6a5cd2 100644 --- a/x-pack/legacy/plugins/siem/public/components/paginated_table/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/paginated_table/index.test.tsx @@ -5,8 +5,7 @@ */ import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { Direction } from '../../graphql/types'; @@ -58,7 +57,7 @@ describe('Paginated Table Component', () => { ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it renders the loading panel at the beginning ', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/pin/index.tsx b/x-pack/legacy/plugins/siem/public/components/pin/index.tsx index 8aec3a60cbc37..9f898f9acaf2e 100644 --- a/x-pack/legacy/plugins/siem/public/components/pin/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/pin/index.tsx @@ -6,7 +6,7 @@ import { EuiButtonIcon, IconSize } from '@elastic/eui'; import { noop } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import * as i18n from '../../components/timeline/body/translations'; diff --git a/x-pack/legacy/plugins/siem/public/components/port/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/port/index.test.tsx index 330385e39ca79..6ab587f266a8a 100644 --- a/x-pack/legacy/plugins/siem/public/components/port/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/port/index.test.tsx @@ -5,8 +5,7 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { TestProviders } from '../../mock/test_providers'; import { useMountAppended } from '../../utils/use_mount_appended'; @@ -20,7 +19,7 @@ describe('Port', () => { const wrapper = shallow( ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it renders the port', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/port/index.tsx b/x-pack/legacy/plugins/siem/public/components/port/index.tsx index a8961a92f24cf..bd6289547d0dc 100644 --- a/x-pack/legacy/plugins/siem/public/components/port/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/port/index.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import { DefaultDraggable } from '../draggables'; import { getEmptyValue } from '../empty_value'; diff --git a/x-pack/legacy/plugins/siem/public/components/progress_inline/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/progress_inline/index.test.tsx index 8ecc50402cef1..e5c39b365d979 100644 --- a/x-pack/legacy/plugins/siem/public/components/progress_inline/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/progress_inline/index.test.tsx @@ -5,7 +5,6 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import React from 'react'; import { ProgressInline } from './index'; @@ -18,6 +17,6 @@ describe('ProgressInline', () => { ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/query_bar/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/query_bar/index.test.tsx index e403963cbbe20..870d0b40d8cd4 100644 --- a/x-pack/legacy/plugins/siem/public/components/query_bar/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/query_bar/index.test.tsx @@ -14,8 +14,6 @@ import { FilterManager, SearchBar } from '../../../../../../../src/plugins/data/ import { QueryBar, QueryBarComponentProps } from '.'; import { createKibanaContextProviderMock } from '../../mock/kibana_react'; -jest.mock('../../lib/kibana'); - const mockUiSettingsForFilterManager = createKibanaCoreStartMock().uiSettings; describe('QueryBar ', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/resize_handle/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/resize_handle/__snapshots__/index.test.tsx.snap deleted file mode 100644 index 38027f80e6684..0000000000000 --- a/x-pack/legacy/plugins/siem/public/components/resize_handle/__snapshots__/index.test.tsx.snap +++ /dev/null @@ -1,14 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`Resizeable it renders 1`] = ` - - - - - -`; diff --git a/x-pack/legacy/plugins/siem/public/components/resize_handle/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/resize_handle/index.test.tsx deleted file mode 100644 index f1580b59362bf..0000000000000 --- a/x-pack/legacy/plugins/siem/public/components/resize_handle/index.test.tsx +++ /dev/null @@ -1,185 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; - -import { TestProviders } from '../../mock/test_providers'; - -import { - addGlobalResizeCursorStyleToBody, - globalResizeCursorClassName, - removeGlobalResizeCursorStyleFromBody, - Resizeable, - calculateDeltaX, -} from '.'; -import { CommonResizeHandle } from './styled_handles'; - -describe('Resizeable', () => { - afterEach(() => { - document.body.classList.remove(globalResizeCursorClassName); - }); - - test('it applies the provided height to the ResizeHandleContainer when a height is specified', () => { - const wrapper = mount( - - } - height="100%" - id="test" - onResize={jest.fn()} - render={() => <>} - /> - - ); - - expect(wrapper.find('[data-test-subj="resize-handle-container"]').first()).toHaveStyleRule( - 'height', - '100%' - ); - }); - - test('it applies positioning styles to the ResizeHandleContainer when positionAbsolute is true and bottom/left/right/top is specified', () => { - const wrapper = mount( - - } - id="test" - left={0} - onResize={jest.fn()} - positionAbsolute - render={() => <>} - right={0} - top={0} - /> - - ); - const resizeHandleContainer = wrapper - .find('[data-test-subj="resize-handle-container"]') - .first(); - - expect(resizeHandleContainer).toHaveStyleRule('bottom', '0'); - expect(resizeHandleContainer).toHaveStyleRule('left', '0'); - expect(resizeHandleContainer).toHaveStyleRule('position', 'absolute'); - expect(resizeHandleContainer).toHaveStyleRule('right', '0'); - expect(resizeHandleContainer).toHaveStyleRule('top', '0'); - }); - - test('it DOES NOT apply positioning styles to the ResizeHandleContainer when positionAbsolute is false, regardless if bottom/left/right/top is specified', () => { - const wrapper = mount( - - } - id="test" - left={0} - onResize={jest.fn()} - render={() => <>} - right={0} - top={0} - /> - - ); - const resizeHandleContainer = wrapper - .find('[data-test-subj="resize-handle-container"]') - .first(); - - expect(resizeHandleContainer).not.toHaveStyleRule('bottom', '0'); - expect(resizeHandleContainer).not.toHaveStyleRule('left', '0'); - expect(resizeHandleContainer).not.toHaveStyleRule('position', 'absolute'); - expect(resizeHandleContainer).not.toHaveStyleRule('right', '0'); - expect(resizeHandleContainer).not.toHaveStyleRule('top', '0'); - }); - - test('it renders', () => { - const wrapper = shallow( - } - height="100%" - id="test" - onResize={jest.fn()} - render={() => <>} - /> - ); - - expect(toJson(wrapper)).toMatchSnapshot(); - }); - - describe('resize cursor styling', () => { - test('it does NOT apply the global-resize-cursor style to the body by default', () => { - mount( - - } - height="100%" - id="test" - onResize={jest.fn()} - render={() => <>} - /> - - ); - - expect(document.body.className).not.toContain(globalResizeCursorClassName); - }); - - describe('#addGlobalResizeCursorStyleToBody', () => { - test('it adds the global-resize-cursor style to the body', () => { - mount( - - } - height="100%" - id="test" - onResize={jest.fn()} - render={() => <>} - /> - - ); - - addGlobalResizeCursorStyleToBody(); - - expect(document.body.className).toContain(globalResizeCursorClassName); - }); - }); - - describe('#removeGlobalResizeCursorStyleFromBody', () => { - test('it removes the global-resize-cursor style from body', () => { - mount( - - } - height="100%" - id="test" - onResize={jest.fn()} - render={() => <>} - /> - - ); - - addGlobalResizeCursorStyleToBody(); - removeGlobalResizeCursorStyleFromBody(); - - expect(document.body.className).not.toContain(globalResizeCursorClassName); - }); - }); - - describe('#calculateDeltaX', () => { - test('it returns 0 when prevX isEqual 0', () => { - expect(calculateDeltaX({ prevX: 0, screenX: 189 })).toEqual(0); - }); - - test('it returns positive difference when screenX > prevX', () => { - expect(calculateDeltaX({ prevX: 10, screenX: 189 })).toEqual(179); - }); - - test('it returns negative difference when prevX > screenX ', () => { - expect(calculateDeltaX({ prevX: 199, screenX: 189 })).toEqual(-10); - }); - }); - }); -}); diff --git a/x-pack/legacy/plugins/siem/public/components/resize_handle/index.tsx b/x-pack/legacy/plugins/siem/public/components/resize_handle/index.tsx deleted file mode 100644 index eb3326c2f2cd0..0000000000000 --- a/x-pack/legacy/plugins/siem/public/components/resize_handle/index.tsx +++ /dev/null @@ -1,145 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React, { useEffect, useRef } from 'react'; -import { fromEvent, Observable, Subscription } from 'rxjs'; -import { concatMap, takeUntil } from 'rxjs/operators'; -import styled from 'styled-components'; - -export type OnResize = ({ delta, id }: { delta: number; id: string }) => void; - -export const resizeCursorStyle = 'col-resize'; -export const globalResizeCursorClassName = 'global-resize-cursor'; - -/** This polyfill is for Safari and IE-11 only. `movementX` is more accurate and "feels" better, so only use this function on Safari and IE-11 */ -export const calculateDeltaX = ({ prevX, screenX }: { prevX: number; screenX: number }) => - prevX !== 0 ? screenX - prevX : 0; - -const isSafari = /^((?!chrome|android|crios|fxios|Firefox).)*safari/i.test(navigator.userAgent); - -interface ResizeHandleContainerProps { - bottom?: string | number; - /** optionally provide a height style ResizeHandleContainer */ - height?: string; - left?: string | number; - positionAbsolute?: boolean; - right?: string | number; - top?: string | number; -} - -interface Props extends ResizeHandleContainerProps { - /** a (styled) resize handle */ - handle: React.ReactNode; - /** the `onResize` callback will be invoked with this id */ - id: string; - /** invoked when the handle is resized */ - onResize: OnResize; - /** The resizeable content to render */ - render: (isResizing: boolean) => React.ReactNode; -} - -const ResizeHandleContainer = styled.div` - bottom: ${({ positionAbsolute, bottom }) => positionAbsolute && bottom}; - cursor: ${resizeCursorStyle}; - height: ${({ height }) => height}; - left: ${({ positionAbsolute, left }) => positionAbsolute && left}; - position: ${({ positionAbsolute }) => positionAbsolute && 'absolute'}; - right: ${({ positionAbsolute, right }) => positionAbsolute && right}; - top: ${({ positionAbsolute, top }) => positionAbsolute && top}; - z-index: ${({ positionAbsolute, theme }) => positionAbsolute && theme.eui.euiZLevel1}; -`; -ResizeHandleContainer.displayName = 'ResizeHandleContainer'; - -export const addGlobalResizeCursorStyleToBody = () => { - document.body.classList.add(globalResizeCursorClassName); -}; - -export const removeGlobalResizeCursorStyleFromBody = () => { - document.body.classList.remove(globalResizeCursorClassName); -}; - -export const Resizeable = React.memo( - ({ bottom, handle, height, id, left, onResize, positionAbsolute, render, right, top }) => { - const drag$ = useRef | null>(null); - const dragEventTargets = useRef>([]); - const dragSubscription = useRef(null); - const prevX = useRef(0); - const ref = useRef(null); - const upSubscription = useRef(null); - const isResizingRef = useRef(false); - - const calculateDelta = (e: MouseEvent) => { - const deltaX = calculateDeltaX({ prevX: prevX.current, screenX: e.screenX }); - prevX.current = e.screenX; - return deltaX; - }; - useEffect(() => { - const move$ = fromEvent(document, 'mousemove'); - const down$ = fromEvent(ref.current!, 'mousedown'); - const up$ = fromEvent(document, 'mouseup'); - - drag$.current = down$.pipe(concatMap(() => move$.pipe(takeUntil(up$)))); - dragSubscription.current = - drag$.current && - drag$.current.subscribe(event => { - // We do a feature detection of event.movementX here and if it is missing - // we calculate the delta manually. Browsers IE-11 and Safari will call calculateDelta - const delta = - event.movementX == null || isSafari ? calculateDelta(event) : event.movementX; - if (!isResizingRef.current) { - isResizingRef.current = true; - } - onResize({ id, delta }); - if (event.target != null && event.target instanceof HTMLElement) { - const htmlElement: HTMLElement = event.target; - dragEventTargets.current = [ - ...dragEventTargets.current, - { htmlElement, prevCursor: htmlElement.style.cursor }, - ]; - htmlElement.style.cursor = resizeCursorStyle; - } - }); - - upSubscription.current = up$.subscribe(() => { - if (isResizingRef.current) { - dragEventTargets.current.reverse().forEach(eventTarget => { - eventTarget.htmlElement.style.cursor = eventTarget.prevCursor; - }); - dragEventTargets.current = []; - isResizingRef.current = false; - } - }); - return () => { - if (dragSubscription.current != null) { - dragSubscription.current.unsubscribe(); - } - if (upSubscription.current != null) { - upSubscription.current.unsubscribe(); - } - }; - }, []); - - return ( - <> - {render(isResizingRef.current)} - - {handle} - - - ); - } -); - -Resizeable.displayName = 'Resizeable'; diff --git a/x-pack/legacy/plugins/siem/public/components/resize_handle/is_resizing.tsx b/x-pack/legacy/plugins/siem/public/components/resize_handle/is_resizing.tsx deleted file mode 100644 index 5eb2d397b4c98..0000000000000 --- a/x-pack/legacy/plugins/siem/public/components/resize_handle/is_resizing.tsx +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { useState } from 'react'; - -export const useIsContainerResizing = () => { - const [isResizing, setIsResizing] = useState(false); - - return { - isResizing, - setIsResizing, - }; -}; diff --git a/x-pack/legacy/plugins/siem/public/components/resize_handle/styled_handles.tsx b/x-pack/legacy/plugins/siem/public/components/resize_handle/styled_handles.tsx deleted file mode 100644 index 4f641c5d2042e..0000000000000 --- a/x-pack/legacy/plugins/siem/public/components/resize_handle/styled_handles.tsx +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import styled from 'styled-components'; - -export const TIMELINE_RESIZE_HANDLE_WIDTH = 2; // px - -export const CommonResizeHandle = styled.div` - cursor: col-resize; - height: 100%; - min-height: 20px; - width: 0; -`; -CommonResizeHandle.displayName = 'CommonResizeHandle'; - -export const TimelineResizeHandle = styled(CommonResizeHandle)<{ height: number }>` - border: ${TIMELINE_RESIZE_HANDLE_WIDTH}px solid ${props => props.theme.eui.euiColorLightShade}; - z-index: 2; - height: ${({ height }) => `${height}px`}; - position: absolute; -`; -TimelineResizeHandle.displayName = 'TimelineResizeHandle'; diff --git a/x-pack/legacy/plugins/siem/public/components/scroll_to_top/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/scroll_to_top/index.test.tsx index 988bb13841fa5..1804093732861 100644 --- a/x-pack/legacy/plugins/siem/public/components/scroll_to_top/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/scroll_to_top/index.test.tsx @@ -5,7 +5,7 @@ */ import { mount } from 'enzyme'; -import * as React from 'react'; +import React from 'react'; import { globalNode, HookWrapper } from '../../mock'; import { useScrollToTop } from '.'; diff --git a/x-pack/legacy/plugins/siem/public/components/selectable_text/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/selectable_text/index.test.tsx index 95c68d0233c69..430cffa520c00 100644 --- a/x-pack/legacy/plugins/siem/public/components/selectable_text/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/selectable_text/index.test.tsx @@ -5,15 +5,14 @@ */ import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { SelectableText } from '.'; describe('SelectableText', () => { test('renders correctly against snapshot', () => { const wrapper = shallow({'You may select this text'}); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it applies the user-select: text style', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/skeleton_row/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/skeleton_row/index.test.tsx index de463f8e29f91..1fdcd8eee941f 100644 --- a/x-pack/legacy/plugins/siem/public/components/skeleton_row/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/skeleton_row/index.test.tsx @@ -5,7 +5,6 @@ */ import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import React from 'react'; import { TestProviders } from '../../mock'; @@ -14,7 +13,7 @@ import { SkeletonRow } from './index'; describe('SkeletonRow', () => { test('it renders', () => { const wrapper = shallow(); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it renders the correct number of cells if cellCount is specified', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/skeleton_row/index.tsx b/x-pack/legacy/plugins/siem/public/components/skeleton_row/index.tsx index 20aea3251d838..dce360877130e 100644 --- a/x-pack/legacy/plugins/siem/public/components/skeleton_row/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/skeleton_row/index.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React from 'react'; +import React, { useMemo } from 'react'; import styled from 'styled-components'; interface RowProps { @@ -12,7 +12,7 @@ interface RowProps { rowPadding?: string; } -const Row = styled.div.attrs(({ rowHeight, rowPadding, theme }) => ({ +const RowComponent = styled.div.attrs(({ rowHeight, rowPadding, theme }) => ({ className: 'siemSkeletonRow', rowHeight: rowHeight || theme.eui.euiSizeXL, rowPadding: rowPadding || `${theme.eui.paddingSizes.s} ${theme.eui.paddingSizes.xs}`, @@ -22,6 +22,10 @@ const Row = styled.div.attrs(({ rowHeight, rowPadding, theme }) => ({ height: ${({ rowHeight }) => rowHeight}; padding: ${({ rowPadding }) => rowPadding}; `; +RowComponent.displayName = 'RowComponent'; + +const Row = React.memo(RowComponent); + Row.displayName = 'Row'; interface CellProps { @@ -29,7 +33,7 @@ interface CellProps { cellMargin?: string; } -const Cell = styled.div.attrs(({ cellColor, cellMargin, theme }) => ({ +const CellComponent = styled.div.attrs(({ cellColor, cellMargin, theme }) => ({ className: 'siemSkeletonRow__cell', cellColor: cellColor || theme.eui.euiColorLightestShade, cellMargin: cellMargin || theme.eui.gutterTypes.gutterSmall, @@ -42,6 +46,10 @@ const Cell = styled.div.attrs(({ cellColor, cellMargin, theme }) => ( margin-left: ${({ cellMargin }) => cellMargin}; } `; +CellComponent.displayName = 'CellComponent'; + +const Cell = React.memo(CellComponent); + Cell.displayName = 'Cell'; export interface SkeletonRowProps extends CellProps, RowProps { @@ -51,9 +59,14 @@ export interface SkeletonRowProps extends CellProps, RowProps { export const SkeletonRow = React.memo( ({ cellColor, cellCount = 4, cellMargin, rowHeight, rowPadding, style }) => { - const cells = [...Array(cellCount)].map((_, i) => ( - - )); + const cells = useMemo( + () => + [...Array(cellCount)].map( + (_, i) => , + [cellCount] + ), + [cellCount, cellColor, cellMargin] + ); return ( diff --git a/x-pack/legacy/plugins/siem/public/components/source_destination/geo_fields.tsx b/x-pack/legacy/plugins/siem/public/components/source_destination/geo_fields.tsx index edf3dc08c7282..baeca10ee0fae 100644 --- a/x-pack/legacy/plugins/siem/public/components/source_destination/geo_fields.tsx +++ b/x-pack/legacy/plugins/siem/public/components/source_destination/geo_fields.tsx @@ -6,7 +6,7 @@ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { get, uniq } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { DefaultDraggable } from '../draggables'; diff --git a/x-pack/legacy/plugins/siem/public/components/source_destination/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/source_destination/index.test.tsx index c437994145d63..3dee668d66a70 100644 --- a/x-pack/legacy/plugins/siem/public/components/source_destination/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/source_destination/index.test.tsx @@ -6,9 +6,8 @@ import numeral from '@elastic/numeral'; import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import { get } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { asArrayIfExists } from '../../lib/helpers'; import { getMockNetflowData } from '../../mock'; @@ -101,7 +100,7 @@ describe('SourceDestination', () => { test('renders correctly against snapshot', () => { const wrapper = shallow(
{getSourceDestinationInstance()}
); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it renders a destination label', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/source_destination/index.tsx b/x-pack/legacy/plugins/siem/public/components/source_destination/index.tsx index 0333181c3521c..c994c4b2cd519 100644 --- a/x-pack/legacy/plugins/siem/public/components/source_destination/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/source_destination/index.tsx @@ -5,7 +5,7 @@ */ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { Network } from './network'; diff --git a/x-pack/legacy/plugins/siem/public/components/source_destination/ip_with_port.tsx b/x-pack/legacy/plugins/siem/public/components/source_destination/ip_with_port.tsx index 4ec317737e72d..ea6ce4caa7270 100644 --- a/x-pack/legacy/plugins/siem/public/components/source_destination/ip_with_port.tsx +++ b/x-pack/legacy/plugins/siem/public/components/source_destination/ip_with_port.tsx @@ -5,7 +5,7 @@ */ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { Ip } from '../ip'; diff --git a/x-pack/legacy/plugins/siem/public/components/source_destination/network.tsx b/x-pack/legacy/plugins/siem/public/components/source_destination/network.tsx index cfacbb077856c..a0b86b3e9a133 100644 --- a/x-pack/legacy/plugins/siem/public/components/source_destination/network.tsx +++ b/x-pack/legacy/plugins/siem/public/components/source_destination/network.tsx @@ -6,7 +6,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; import { uniq } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { DirectionBadge } from '../direction'; diff --git a/x-pack/legacy/plugins/siem/public/components/source_destination/source_destination_arrows.tsx b/x-pack/legacy/plugins/siem/public/components/source_destination/source_destination_arrows.tsx index 0675212591a66..005ebc14dcdcc 100644 --- a/x-pack/legacy/plugins/siem/public/components/source_destination/source_destination_arrows.tsx +++ b/x-pack/legacy/plugins/siem/public/components/source_destination/source_destination_arrows.tsx @@ -6,7 +6,7 @@ import { EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; import numeral from '@elastic/numeral'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { ArrowBody, ArrowHead } from '../arrows'; diff --git a/x-pack/legacy/plugins/siem/public/components/source_destination/source_destination_ip.test.tsx b/x-pack/legacy/plugins/siem/public/components/source_destination/source_destination_ip.test.tsx index 463373b5894f1..60ab59c3796ff 100644 --- a/x-pack/legacy/plugins/siem/public/components/source_destination/source_destination_ip.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/source_destination/source_destination_ip.test.tsx @@ -5,7 +5,7 @@ */ import { get } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { asArrayIfExists } from '../../lib/helpers'; import { getMockNetflowData } from '../../mock'; diff --git a/x-pack/legacy/plugins/siem/public/components/source_destination/source_destination_ip.tsx b/x-pack/legacy/plugins/siem/public/components/source_destination/source_destination_ip.tsx index ebc5beaa4e354..33159387214e4 100644 --- a/x-pack/legacy/plugins/siem/public/components/source_destination/source_destination_ip.tsx +++ b/x-pack/legacy/plugins/siem/public/components/source_destination/source_destination_ip.tsx @@ -6,7 +6,7 @@ import { EuiBadge, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { isEmpty, isEqual, uniqWith } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { DESTINATION_IP_FIELD_NAME, SOURCE_IP_FIELD_NAME } from '../ip'; import { DESTINATION_PORT_FIELD_NAME, SOURCE_PORT_FIELD_NAME, Port } from '../port'; diff --git a/x-pack/legacy/plugins/siem/public/components/source_destination/source_destination_with_arrows.tsx b/x-pack/legacy/plugins/siem/public/components/source_destination/source_destination_with_arrows.tsx index 53dade22351bf..d6a3ce4158734 100644 --- a/x-pack/legacy/plugins/siem/public/components/source_destination/source_destination_with_arrows.tsx +++ b/x-pack/legacy/plugins/siem/public/components/source_destination/source_destination_with_arrows.tsx @@ -5,7 +5,7 @@ */ import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import { SourceDestinationArrows } from './source_destination_arrows'; import { SourceDestinationIp } from './source_destination_ip'; diff --git a/x-pack/legacy/plugins/siem/public/components/stat_items/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/stat_items/__snapshots__/index.test.tsx.snap index 098f54640e4b2..5ed750b519cbf 100644 --- a/x-pack/legacy/plugins/siem/public/components/stat_items/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/stat_items/__snapshots__/index.test.tsx.snap @@ -105,7 +105,7 @@ exports[`Stat Items Component disable charts it renders the default widget 1`] = showInspect={false} >
{ ], ])('disable charts', wrapper => { test('it renders the default widget', () => { - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('should render titles', () => { @@ -180,7 +181,7 @@ describe('Stat Items Component', () => { ); }); test('it renders the default widget', () => { - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('should handle multiple titles', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/subtitle/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/subtitle/index.test.tsx index 3424c05f32d63..155b219c04b92 100644 --- a/x-pack/legacy/plugins/siem/public/components/subtitle/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/subtitle/index.test.tsx @@ -5,7 +5,6 @@ */ import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import React from 'react'; import { TestProviders } from '../../mock'; @@ -15,7 +14,7 @@ describe('Subtitle', () => { test('it renders', () => { const wrapper = shallow(); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it renders one subtitle string item', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/super_date_picker/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/super_date_picker/index.test.tsx index 013104da7c612..c5838fa283e17 100644 --- a/x-pack/legacy/plugins/siem/public/components/super_date_picker/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/super_date_picker/index.test.tsx @@ -5,7 +5,7 @@ */ import { mount } from 'enzyme'; -import * as React from 'react'; +import React from 'react'; import { Provider as ReduxStoreProvider } from 'react-redux'; import { useUiSetting$ } from '../../lib/kibana'; diff --git a/x-pack/legacy/plugins/siem/public/components/tables/helpers.test.tsx b/x-pack/legacy/plugins/siem/public/components/tables/helpers.test.tsx index d864d4306b8ef..17c7c0bac8fa2 100644 --- a/x-pack/legacy/plugins/siem/public/components/tables/helpers.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/tables/helpers.test.tsx @@ -10,9 +10,8 @@ import { getRowItemDraggable, OverflowFieldComponent, } from './helpers'; -import * as React from 'react'; +import React from 'react'; import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import { TestProviders } from '../../mock'; import { getEmptyValue } from '../empty_value'; import { useMountAppended } from '../../utils/use_mount_appended'; @@ -29,7 +28,7 @@ describe('Table Helpers', () => { idPrefix: 'idPrefix', }); const wrapper = shallow({rowItem}); - expect(toJson(wrapper.find('DraggableWrapper'))).toMatchSnapshot(); + expect(wrapper.find('DraggableWrapper')).toMatchSnapshot(); }); test('it returns empty value when rowItem is undefined', () => { @@ -97,7 +96,7 @@ describe('Table Helpers', () => { idPrefix: 'idPrefix', }); const wrapper = shallow({rowItems}); - expect(toJson(wrapper.find('DragDropContext'))).toMatchSnapshot(); + expect(wrapper.find('DragDropContext')).toMatchSnapshot(); }); test('it returns empty value when rowItems is undefined', () => { @@ -193,7 +192,7 @@ describe('Table Helpers', () => { test('it returns correctly against snapshot', () => { const rowItemOverflow = getRowItemOverflow(items, 'attrName', 1, 1); const wrapper = shallow(
{rowItemOverflow}
); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it does not show "more not shown" when maxOverflowItems are not exceeded', () => { @@ -215,7 +214,7 @@ describe('Table Helpers', () => { const wrapper = shallow( ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it does not truncates as per custom overflowLength value', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/auto_save_warning/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/auto_save_warning/index.tsx index 6c793126efa72..c2dfda6a81ce4 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/auto_save_warning/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/auto_save_warning/index.tsx @@ -11,7 +11,7 @@ import { EuiGlobalToastListToast as Toast, } from '@elastic/eui'; import { getOr } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { connect } from 'react-redux'; import { ActionCreator } from 'typescript-fsa'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/actions/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/actions/index.test.tsx index a9628ebbd183f..9351fddd90dd5 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/actions/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/actions/index.test.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import { mount } from 'enzyme'; -import * as React from 'react'; +import React from 'react'; import { TestProviders } from '../../../../mock'; import { DEFAULT_ACTIONS_COLUMN_WIDTH } from '../helpers'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/actions/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/actions/index.tsx index 54b1fb0893c83..6cf14cd972d3e 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/actions/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/actions/index.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ import { EuiButtonIcon, EuiCheckbox, EuiLoadingSpinner, EuiToolTip } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import { Note } from '../../../../lib/note'; import { AssociateNote, UpdateNote } from '../../../notes/helpers'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/__snapshots__/index.test.tsx.snap index 1b66a130c3550..dfea99ffd7091 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/__snapshots__/index.test.tsx.snap @@ -1,20 +1,20 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`ColumnHeaders rendering renders correctly against snapshot 1`] = ` - - - + - - + - - - - + + + - - + + `; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/actions/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/actions/index.tsx index b57b343d614a8..64e8aa3c7e7b7 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/actions/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/actions/index.tsx @@ -5,7 +5,7 @@ */ import { EuiButtonIcon } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import { OnColumnRemoved } from '../../../events'; import { EventsHeadingExtra, EventsLoading } from '../../../styles'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/column_header.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/column_header.tsx index 15911f522032a..ccaeeff972a81 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/column_header.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/column_header.tsx @@ -4,6 +4,18 @@ * you may not use this file except in compliance with the Elastic License. */ +import React from 'react'; +import { Draggable } from 'react-beautiful-dnd'; +import { Resizable, ResizeCallback } from 're-resizable'; +import { DragEffects } from '../../../drag_and_drop/draggable_wrapper'; +import { getDraggableFieldId, DRAG_TYPE_FIELD } from '../../../drag_and_drop/helpers'; +import { DraggableFieldBadge } from '../../../draggables/field_badge'; +import { OnColumnRemoved, OnColumnSorted, OnFilterChange, OnColumnResized } from '../../events'; +import { EventsTh, EventsThContent, EventsHeadingHandle } from '../../styles'; +import { Sort } from '../sort'; +import { DraggingContainer } from './common/dragging_container'; + +import { Header } from './header'; import { ColumnId } from '../column_id'; export type ColumnHeaderType = 'not-filtered' | 'text-filter'; @@ -22,3 +34,90 @@ export interface ColumnHeader { type?: string; width: number; } + +interface ColumneHeaderProps { + draggableIndex: number; + header: ColumnHeader; + onColumnRemoved: OnColumnRemoved; + onColumnSorted: OnColumnSorted; + onColumnResized: OnColumnResized; + onFilterChange?: OnFilterChange; + sort: Sort; + timelineId: string; +} + +const ColumnHeaderComponent: React.FC = ({ + draggableIndex, + header, + timelineId, + onColumnRemoved, + onColumnResized, + onColumnSorted, + onFilterChange, + sort, +}) => { + const [isDragging, setIsDragging] = React.useState(false); + const handleResizeStop: ResizeCallback = (e, direction, ref, delta) => { + onColumnResized({ columnId: header.id, delta: delta.width }); + }; + + return ( + , + }} + onResizeStop={handleResizeStop} + > + + {(dragProvided, dragSnapshot) => ( + + {!dragSnapshot.isDragging ? ( + +
+ + ) : ( + + + + + + )} + + )} + + + ); +}; + +export const ColumnHeader = React.memo(ColumnHeaderComponent); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/common/dragging_container.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/common/dragging_container.tsx new file mode 100644 index 0000000000000..21aa17aa1c52c --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/common/dragging_container.tsx @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { FC, memo, useEffect } from 'react'; + +interface DraggingContainerProps { + children: JSX.Element; + onDragging: Function; +} + +const DraggingContainerComponent: FC = ({ children, onDragging }) => { + useEffect(() => { + onDragging(true); + + return () => onDragging(false); + }); + + return children; +}; + +export const DraggingContainer = memo(DraggingContainerComponent); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/events_select/helpers.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/events_select/helpers.tsx index 057f751f451ac..853c1ec24b703 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/events_select/helpers.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/events_select/helpers.tsx @@ -5,7 +5,7 @@ */ import { EuiText } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { Pin } from '../../../../pin'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/events_select/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/events_select/index.tsx index 634db2dc52676..87a1035fc0739 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/events_select/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/events_select/index.tsx @@ -6,7 +6,7 @@ import { EuiCheckbox, EuiSuperSelect } from '@elastic/eui'; import { noop } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import styled, { createGlobalStyle } from 'styled-components'; import { getEventsSelectOptions } from './helpers'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/filter/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/filter/index.test.tsx index d93983d7d4054..b9cfee395bafb 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/filter/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/filter/index.test.tsx @@ -5,8 +5,7 @@ */ import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { ColumnHeaderType } from '../column_header'; import { defaultHeaders } from '../default_headers'; @@ -24,7 +23,7 @@ describe('Filter', () => { }; const wrapper = shallow(); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); describe('rendering', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/filter/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/filter/index.tsx index c56322cc69e0c..0b5247e7da678 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/filter/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/filter/index.tsx @@ -5,7 +5,7 @@ */ import { noop } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { OnFilterChange } from '../../../events'; import { ColumnHeader } from '../column_header'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/header/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/header/__snapshots__/index.test.tsx.snap index 64c2b6ed10692..d30054ae1a3fe 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/header/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/header/__snapshots__/index.test.tsx.snap @@ -1,14 +1,50 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Header renders correctly against snapshot 1`] = ` -} - id="@timestamp" - onResize={[Function]} - positionAbsolute={true} - render={[Function]} - right="-1px" - top={0} -/> + + + + + + `; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/header/header_content.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/header/header_content.tsx new file mode 100644 index 0000000000000..c38ae26050c93 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/header/header_content.tsx @@ -0,0 +1,76 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiToolTip } from '@elastic/eui'; +import { noop } from 'lodash/fp'; +import React from 'react'; + +import { TruncatableText } from '../../../../truncatable_text'; +import { EventsHeading, EventsHeadingTitleButton, EventsHeadingTitleSpan } from '../../../styles'; +import { useTimelineContext } from '../../../timeline_context'; +import { Sort } from '../../sort'; +import { SortIndicator } from '../../sort/sort_indicator'; +import { ColumnHeader } from '../column_header'; +import { HeaderToolTipContent } from '../header_tooltip_content'; +import { getSortDirection } from './helpers'; + +interface HeaderContentProps { + children: React.ReactNode; + header: ColumnHeader; + isResizing: boolean; + onClick: () => void; + sort: Sort; +} + +const HeaderContentComponent: React.FC = ({ + children, + header, + isResizing, + onClick, + sort, +}) => { + const isLoading = useTimelineContext(); + + return ( + + {header.aggregatable ? ( + + + } + > + <>{header.label ?? header.id} + + + + + + ) : ( + + + } + > + <>{header.label ?? header.id} + + + + )} + + {children} + + ); +}; + +export const HeaderContent = React.memo(HeaderContentComponent); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/header/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/header/index.test.tsx index bfcf3cd639799..fab2e7ee872bf 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/header/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/header/index.test.tsx @@ -5,8 +5,7 @@ */ import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { Direction } from '../../../../../graphql/types'; import { TestProviders } from '../../../../../mock'; @@ -33,14 +32,12 @@ describe('Header', () => { ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); describe('rendering', () => { @@ -50,9 +47,7 @@ describe('Header', () => { @@ -75,9 +70,7 @@ describe('Header', () => { @@ -99,9 +92,7 @@ describe('Header', () => { @@ -127,9 +118,7 @@ describe('Header', () => { @@ -154,9 +143,7 @@ describe('Header', () => { @@ -182,9 +169,7 @@ describe('Header', () => { @@ -202,9 +187,7 @@ describe('Header', () => { @@ -222,9 +205,7 @@ describe('Header', () => { @@ -335,9 +316,7 @@ describe('Header', () => { @@ -358,9 +337,7 @@ describe('Header', () => { @@ -370,25 +347,4 @@ describe('Header', () => { expect(wrapper.find('[data-test-subj="header-tooltip"]').exists()).toEqual(true); }); }); - - describe('setIsResizing', () => { - test('setIsResizing have been call when it renders actions', () => { - const mockSetIsResizing = jest.fn(); - mount( - - - - ); - - expect(mockSetIsResizing).toHaveBeenCalled(); - }); - }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/header/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/header/index.tsx index 311b4bfda60fe..c45b9ce425deb 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/header/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/header/index.tsx @@ -4,103 +4,34 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiToolTip } from '@elastic/eui'; import { noop } from 'lodash/fp'; -import React from 'react'; +import React, { useCallback } from 'react'; -import { OnResize, Resizeable } from '../../../../resize_handle'; -import { TruncatableText } from '../../../../truncatable_text'; -import { OnColumnRemoved, OnColumnResized, OnColumnSorted, OnFilterChange } from '../../../events'; -import { - EventsHeading, - EventsHeadingHandle, - EventsHeadingTitleButton, - EventsHeadingTitleSpan, -} from '../../../styles'; -import { useTimelineContext } from '../../../timeline_context'; +import { OnColumnRemoved, OnColumnSorted, OnFilterChange } from '../../../events'; import { Sort } from '../../sort'; -import { SortIndicator } from '../../sort/sort_indicator'; import { Actions } from '../actions'; import { ColumnHeader } from '../column_header'; import { Filter } from '../filter'; -import { HeaderToolTipContent } from '../header_tooltip_content'; -import { getNewSortDirectionOnClick, getSortDirection } from './helpers'; - -interface HeaderCompProps { - children: React.ReactNode; - header: ColumnHeader; - isResizing: boolean; - onClick: () => void; - sort: Sort; -} - -const HeaderComp = React.memo( - ({ children, header, isResizing, onClick, sort }) => { - const isLoading = useTimelineContext(); - - return ( - - {header.aggregatable ? ( - - - } - > - <>{header.label ?? header.id} - - - - - - ) : ( - - - } - > - <>{header.label ?? header.id} - - - - )} - - {children} - - ); - } -); -HeaderComp.displayName = 'HeaderComp'; +import { getNewSortDirectionOnClick } from './helpers'; +import { HeaderContent } from './header_content'; interface Props { header: ColumnHeader; onColumnRemoved: OnColumnRemoved; - onColumnResized: OnColumnResized; onColumnSorted: OnColumnSorted; onFilterChange?: OnFilterChange; - setIsResizing: (isResizing: boolean) => void; sort: Sort; timelineId: string; } -/** Renders a header */ -export const HeaderComponent = ({ +export const HeaderComponent: React.FC = ({ header, onColumnRemoved, - onColumnResized, onColumnSorted, onFilterChange = noop, - setIsResizing, sort, -}: Props) => { - const onClick = () => { +}) => { + const onClick = useCallback(() => { onColumnSorted!({ columnId: header.id, sortDirection: getNewSortDirectionOnClick({ @@ -108,41 +39,17 @@ export const HeaderComponent = ({ currentSort: sort, }), }); - }; - - const onResize: OnResize = ({ delta, id }) => { - onColumnResized({ columnId: id, delta }); - }; - - const renderActions = (isResizing: boolean) => { - setIsResizing(isResizing); - return ( - <> - - - - - - - ); - }; + }, [onColumnSorted, header, sort]); return ( - } - id={header.id} - onResize={onResize} - positionAbsolute - render={renderActions} - right="-1px" - top={0} - /> + <> + + + + + + ); }; -HeaderComponent.displayName = 'HeaderComponent'; - export const Header = React.memo(HeaderComponent); - -Header.displayName = 'Header'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/header_tooltip_content/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/header_tooltip_content/index.test.tsx index da348590bd044..20c139ae1d050 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/header_tooltip_content/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/header_tooltip_content/index.test.tsx @@ -5,9 +5,8 @@ */ import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import { cloneDeep } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { defaultHeaders } from '../../../../../mock'; import { ColumnHeader } from '../column_header'; @@ -89,6 +88,6 @@ describe('HeaderToolTipContent', () => { test('it renders the expected table content', () => { const wrapper = shallow(); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/header_tooltip_content/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/header_tooltip_content/index.tsx index a63ec2bf840a6..5deb2c3e66376 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/header_tooltip_content/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/header_tooltip_content/index.tsx @@ -6,7 +6,7 @@ import { EuiIcon } from '@elastic/eui'; import { isEmpty } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { getIconFromType } from '../../../../event_details/helpers'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/index.test.tsx index 7765b0360d35b..4b97dd7573a45 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/index.test.tsx @@ -5,8 +5,7 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { DEFAULT_ACTIONS_COLUMN_WIDTH } from '../helpers'; import { defaultHeaders } from './default_headers'; @@ -18,14 +17,6 @@ import { useMountAppended } from '../../../../utils/use_mount_appended'; import { ColumnHeadersComponent } from '.'; -jest.mock('../../../resize_handle/is_resizing', () => ({ - ...jest.requireActual('../../../resize_handle/is_resizing'), - useIsContainerResizing: () => ({ - isResizing: true, - setIsResizing: jest.fn(), - }), -})); - describe('ColumnHeaders', () => { const mount = useMountAppended(); @@ -54,7 +45,7 @@ describe('ColumnHeaders', () => { toggleColumn={jest.fn()} /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it renders the field browser', () => { @@ -118,37 +109,5 @@ describe('ColumnHeaders', () => { ).toContain(h.id); }); }); - - test('it disables dragging during a column resize', () => { - const wrapper = mount( - - - - ); - - defaultHeaders.forEach(h => { - expect( - wrapper - .find('[data-test-subj="draggable"]') - .first() - .prop('isDragDisabled') - ).toBe(true); - }); - }); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/index.tsx index 95a7ae52b0f23..953ffb4d4932b 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/index.tsx @@ -6,20 +6,13 @@ import { EuiCheckbox } from '@elastic/eui'; import { noop } from 'lodash/fp'; -import * as React from 'react'; -import { Draggable, Droppable } from 'react-beautiful-dnd'; +import React from 'react'; +import { Droppable } from 'react-beautiful-dnd'; import { BrowserFields } from '../../../../containers/source'; -import { DragEffects } from '../../../drag_and_drop/draggable_wrapper'; -import { - DRAG_TYPE_FIELD, - droppableTimelineColumnsPrefix, - getDraggableFieldId, -} from '../../../drag_and_drop/helpers'; -import { DraggableFieldBadge } from '../../../draggables/field_badge'; +import { DRAG_TYPE_FIELD, droppableTimelineColumnsPrefix } from '../../../drag_and_drop/helpers'; import { StatefulFieldsBrowser } from '../../../fields_browser'; import { FIELD_BROWSER_HEIGHT, FIELD_BROWSER_WIDTH } from '../../../fields_browser/helpers'; -import { useIsContainerResizing } from '../../../resize_handle/is_resizing'; import { OnColumnRemoved, OnColumnResized, @@ -39,7 +32,6 @@ import { import { Sort } from '../sort'; import { ColumnHeader } from './column_header'; import { EventsSelect } from './events_select'; -import { Header } from './header'; interface Props { actionsColumnWidth: number; @@ -78,132 +70,86 @@ export const ColumnHeadersComponent = ({ sort, timelineId, toggleColumn, -}: Props) => { - const { isResizing, setIsResizing } = useIsContainerResizing(); - - return ( - - - - {showEventsSelect && ( - - - - - - )} - - {showSelectAllCheckbox && ( - - - ) => { - onSelectAll({ isSelected: event.currentTarget.checked }); - }} - /> - - - )} - +}: Props) => ( + + + + {showEventsSelect && ( - - + + + + )} + {showSelectAllCheckbox && ( + + + ) => { + onSelectAll({ isSelected: event.currentTarget.checked }); + }} /> - + )} + + + + + + - - {dropProvided => ( + + {(dropProvided, snapshot) => ( + <> - {columnHeaders.map((header, i) => ( - ( + - {(dragProvided, dragSnapshot) => ( - - {!dragSnapshot.isDragging ? ( - -
- - ) : ( - - - - )} - - )} - + draggableIndex={draggableIndex} + timelineId={timelineId} + header={header} + onColumnRemoved={onColumnRemoved} + onColumnSorted={onColumnSorted} + onFilterChange={onFilterChange} + onColumnResized={onColumnResized} + sort={sort} + /> ))} - )} - - - - ); -}; - -ColumnHeadersComponent.displayName = 'ColumnHeadersComponent'; + {dropProvided.placeholder} + + )} + + + +); export const ColumnHeaders = React.memo(ColumnHeadersComponent); - -ColumnHeaders.displayName = 'ColumnHeaders'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/range_picker/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/range_picker/index.test.tsx index d919bdb0788a6..12ce3bb709242 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/range_picker/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/range_picker/index.test.tsx @@ -5,7 +5,7 @@ */ import { mount } from 'enzyme'; -import * as React from 'react'; +import React from 'react'; import { RangePicker } from '.'; import { Ranges } from './ranges'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/range_picker/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/range_picker/index.tsx index c221829f92d04..de21fdac6434a 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/range_picker/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/range_picker/index.tsx @@ -5,7 +5,7 @@ */ import { EuiSelect } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { OnRangeSelected } from '../../../events'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/text_filter/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/text_filter/index.test.tsx index c6b1ee056ebcc..4378a96b2919a 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/text_filter/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/text_filter/index.test.tsx @@ -5,8 +5,7 @@ */ import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { DEFAULT_PLACEHOLDER, TextFilter } from '.'; @@ -14,7 +13,7 @@ describe('TextFilter', () => { describe('rendering', () => { test('renders correctly against snapshot', () => { const wrapper = shallow(); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); describe('placeholder', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/text_filter/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/text_filter/index.tsx index 09672eb9a38fe..fcc23314a1813 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/text_filter/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/column_headers/text_filter/index.tsx @@ -6,7 +6,7 @@ import { EuiFieldText } from '@elastic/eui'; import { noop } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { OnFilterChange } from '../../../events'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/data_driven_columns/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/timeline/body/data_driven_columns/__snapshots__/index.test.tsx.snap index d370a24cc1d4d..75c05dd1455af 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/data_driven_columns/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/data_driven_columns/__snapshots__/index.test.tsx.snap @@ -1,10 +1,10 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`Columns it renders the expected columns 1`] = ` - - - - - - + + - - - - - + + - - - - - + + - - - - - + + - - - - - + + - - - - - + + - - - - - + + + `; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/data_driven_columns/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/data_driven_columns/index.test.tsx index f5b33296561c7..36427015260a7 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/data_driven_columns/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/data_driven_columns/index.test.tsx @@ -4,9 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { mockTimelineData } from '../../../../mock'; import { defaultHeaders } from '../column_headers/default_headers'; @@ -29,6 +28,6 @@ describe('Columns', () => { /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/data_driven_columns/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/data_driven_columns/index.tsx index 2b2401519eb32..37b6e30215056 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/data_driven_columns/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/data_driven_columns/index.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import { TimelineNonEcsData } from '../../../../graphql/types'; import { OnColumnResized } from '../../events'; @@ -30,7 +30,7 @@ export const DataDrivenColumns = React.memo( return ( {columnHeaders.map((header, index) => ( - + {getColumnRenderer(header.id, columnRenderers, data).renderColumn({ columnName: header.id, diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/events/stateful_event.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/events/stateful_event.tsx index baa5c35880d68..b93b0531c740f 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/events/stateful_event.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/events/stateful_event.tsx @@ -9,7 +9,7 @@ import uuid from 'uuid'; import VisibilitySensor from 'react-visibility-sensor'; import { BrowserFields } from '../../../../containers/source'; -import { TimelineDetailsComponentQuery } from '../../../../containers/timeline/details'; +import { TimelineDetailsQuery } from '../../../../containers/timeline/details'; import { TimelineItem, DetailItem, TimelineNonEcsData } from '../../../../graphql/types'; import { requestIdleCallbackViaScheduler } from '../../../../lib/helpers/scheduler'; import { Note } from '../../../../lib/note'; @@ -91,7 +91,7 @@ interface AttributesProps { children: React.ReactNode; } -const Attributes = React.memo(({ children }) => { +const AttributesComponent: React.FC = ({ children }) => { const width = useTimelineWidthContext(); // Passing the styles directly to the component because the width is @@ -106,187 +106,187 @@ const Attributes = React.memo(({ children }) => { {children} ); -}); +}; -export const StatefulEvent = React.memo( - ({ - actionsColumnWidth, - addNoteToEvent, - browserFields, - columnHeaders, - columnRenderers, - event, - eventIdToNoteIds, - getNotesByIds, - isEventViewer = false, - isEventPinned = false, - loadingEventIds, - maxDelay = 0, - onColumnResized, - onPinEvent, - onRowSelected, - onUnPinEvent, - onUpdateColumns, - rowRenderers, - selectedEventIds, - showCheckboxes, - timelineId, - toggleColumn, - updateNote, - }) => { - const [expanded, setExpanded] = useState<{ [eventId: string]: boolean }>({}); - const [initialRender, setInitialRender] = useState(false); - const [showNotes, setShowNotes] = useState<{ [eventId: string]: boolean }>({}); +const Attributes = React.memo(AttributesComponent); - const divElement = useRef(null); +const StatefulEventComponent: React.FC = ({ + actionsColumnWidth, + addNoteToEvent, + browserFields, + columnHeaders, + columnRenderers, + event, + eventIdToNoteIds, + getNotesByIds, + isEventViewer = false, + isEventPinned = false, + loadingEventIds, + maxDelay = 0, + onColumnResized, + onPinEvent, + onRowSelected, + onUnPinEvent, + onUpdateColumns, + rowRenderers, + selectedEventIds, + showCheckboxes, + timelineId, + toggleColumn, + updateNote, +}) => { + const [expanded, setExpanded] = useState<{ [eventId: string]: boolean }>({}); + const [initialRender, setInitialRender] = useState(false); + const [showNotes, setShowNotes] = useState<{ [eventId: string]: boolean }>({}); - const onToggleShowNotes = useCallback(() => { - const eventId = event._id; - setShowNotes({ ...showNotes, [eventId]: !showNotes[eventId] }); - }, [event, showNotes]); + const divElement = useRef(null); - const onToggleExpanded = useCallback(() => { - const eventId = event._id; - setExpanded({ - ...expanded, - [eventId]: !expanded[eventId], - }); - }, [event, expanded]); + const onToggleShowNotes = useCallback(() => { + const eventId = event._id; + setShowNotes({ ...showNotes, [eventId]: !showNotes[eventId] }); + }, [event, showNotes]); - const associateNote = useCallback( - (noteId: string) => { - addNoteToEvent({ eventId: event._id, noteId }); - if (!isEventPinned) { - onPinEvent(event._id); // pin the event, because it has notes - } - }, - [addNoteToEvent, event, isEventPinned, onPinEvent] - ); + const onToggleExpanded = useCallback(() => { + const eventId = event._id; + setExpanded({ + ...expanded, + [eventId]: !expanded[eventId], + }); + }, [event, expanded]); - /** - * Incrementally loads the events when it mounts by trying to - * see if it resides within a window frame and if it is it will - * indicate to React that it should render its self by setting - * its initialRender to true. - */ - useEffect(() => { - let _isMounted = true; + const associateNote = useCallback( + (noteId: string) => { + addNoteToEvent({ eventId: event._id, noteId }); + if (!isEventPinned) { + onPinEvent(event._id); // pin the event, because it has notes + } + }, + [addNoteToEvent, event, isEventPinned, onPinEvent] + ); - requestIdleCallbackViaScheduler( - () => { - if (!initialRender && _isMounted) { - setInitialRender(true); - } - }, - { timeout: maxDelay } - ); - return () => { - _isMounted = false; - }; - }, []); + /** + * Incrementally loads the events when it mounts by trying to + * see if it resides within a window frame and if it is it will + * indicate to React that it should render its self by setting + * its initialRender to true. + */ + useEffect(() => { + let _isMounted = true; - // Number of current columns plus one for actions. - const columnCount = columnHeaders.length + 1; + requestIdleCallbackViaScheduler( + () => { + if (!initialRender && _isMounted) { + setInitialRender(true); + } + }, + { timeout: maxDelay } + ); + return () => { + _isMounted = false; + }; + }, []); - // If we are not ready to render yet, just return null - // see useEffect() for when it schedules the first - // time this stateful component should be rendered. - if (!initialRender) { - return ; - } + // Number of current columns plus one for actions. + const columnCount = columnHeaders.length + 1; - return ( - - {({ isVisible }) => { - if (isVisible) { - return ( - - {({ detailsData, loading }) => ( - { - if (c != null) { - divElement.current = c; - } - }} - > - {getRowRenderer(event.ecs, rowRenderers).renderRow({ - browserFields, - data: event.ecs, - children: ( - - ), - timelineId, - })} + // If we are not ready to render yet, just return null + // see useEffect() for when it schedules the first + // time this stateful component should be rendered. + if (!initialRender) { + return ; + } - - + {({ isVisible }) => { + if (isVisible) { + return ( + + {({ detailsData, loading }) => ( + { + if (c != null) { + divElement.current = c; + } + }} + > + {getRowRenderer(event.ecs, rowRenderers).renderRow({ + browserFields, + data: event.ecs, + children: ( + - - - )} - - ); - } else { - // Height place holder for visibility detection as well as re-rendering sections. - const height = - divElement.current != null - ? `${divElement.current.clientHeight}px` - : DEFAULT_ROW_HEIGHT; + ), + timelineId, + })} - // height is being inlined directly in here because of performance with StyledComponents - // involving quick and constant changes to the DOM. - // https://github.com/styled-components/styled-components/issues/134#issuecomment-312415291 - return ; - } - }} - - ); - } -); + + + + + )} + + ); + } else { + // Height place holder for visibility detection as well as re-rendering sections. + const height = + divElement.current != null + ? `${divElement.current.clientHeight}px` + : DEFAULT_ROW_HEIGHT; + + // height is being inlined directly in here because of performance with StyledComponents + // involving quick and constant changes to the DOM. + // https://github.com/styled-components/styled-components/issues/134#issuecomment-312415291 + return ; + } + }} + + ); +}; -StatefulEvent.displayName = 'StatefulEvent'; +export const StatefulEvent = React.memo(StatefulEventComponent); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/events/stateful_event_child.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/events/stateful_event_child.tsx index 9ea1bbb1e8430..a39c254c61126 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/events/stateful_event_child.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/events/stateful_event_child.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import uuid from 'uuid'; import { TimelineNonEcsData } from '../../../../graphql/types'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/index.test.tsx index 5c6a0872ce340..239d8a9d77916 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/index.test.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import { mockBrowserFields } from '../../../containers/source/mock'; import { Direction } from '../../../graphql/types'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/index.tsx index 23406f1b5f35f..c4ad532f76fc4 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/index.tsx @@ -116,7 +116,7 @@ export const Body = React.memo( `; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/__snapshots__/get_column_renderer.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/__snapshots__/get_column_renderer.test.tsx.snap index b867e0a788449..a79a7ed23e3d1 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/__snapshots__/get_column_renderer.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/__snapshots__/get_column_renderer.test.tsx.snap @@ -2,24 +2,14 @@ exports[`get_column_renderer renders correctly against snapshot 1`] = ` - `; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/__snapshots__/plain_column_renderer.test.tsx.snap b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/__snapshots__/plain_column_renderer.test.tsx.snap index e2eaf1d872b26..563e356a689c8 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/__snapshots__/plain_column_renderer.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/__snapshots__/plain_column_renderer.test.tsx.snap @@ -2,24 +2,14 @@ exports[`plain_column_renderer rendering renders correctly against snapshot 1`] = ` - `; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/args.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/args.test.tsx index ad904554e33ad..53a2054412440 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/args.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/args.test.tsx @@ -5,8 +5,7 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { useMountAppended } from '../../../../utils/use_mount_appended'; import { TestProviders } from '../../../../mock'; @@ -25,7 +24,7 @@ describe('Args', () => { processTitle="process-title-1" /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it returns an empty string when both args and process title are undefined', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/args.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/args.tsx index 8282931aa0579..22367ec879851 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/args.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/args.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import { DraggableBadge } from '../../../draggables'; import { isNillEmptyOrNotFinite, TokensFlexItem } from './helpers'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_details.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_details.test.tsx index 8d3a713369031..21cccc88f4fbc 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_details.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_details.test.tsx @@ -5,8 +5,7 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { BrowserFields } from '../../../../../containers/source'; import { mockBrowserFields } from '../../../../../containers/source/mock'; @@ -30,7 +29,7 @@ describe('GenericDetails', () => { timelineId="test" /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it returns auditd if the data does contain auditd data', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_details.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_details.tsx index b60d89c857421..c25c656b75e41 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_details.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_details.tsx @@ -6,7 +6,7 @@ import { EuiFlexGroup, EuiSpacer } from '@elastic/eui'; import { get } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { BrowserFields } from '../../../../../containers/source'; import { Ecs } from '../../../../../graphql/types'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_file_details.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_file_details.test.tsx index af4971faae295..fce0e1d645e16 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_file_details.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_file_details.test.tsx @@ -5,8 +5,7 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { BrowserFields } from '../../../../../containers/source'; import { mockBrowserFields } from '../../../../../containers/source/mock'; @@ -31,7 +30,7 @@ describe('GenericFileDetails', () => { timelineId="test" /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it returns auditd if the data does contain auditd data', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_file_details.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_file_details.tsx index be84696033d69..797361878e6c5 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_file_details.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_file_details.tsx @@ -6,7 +6,7 @@ import { EuiFlexGroup, EuiSpacer, IconType } from '@elastic/eui'; import { get } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { BrowserFields } from '../../../../../containers/source'; import { Ecs } from '../../../../../graphql/types'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_row_renderer.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_row_renderer.test.tsx index f2cce8b16b5d6..b78d9261849cb 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_row_renderer.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_row_renderer.test.tsx @@ -5,9 +5,8 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import { cloneDeep } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { BrowserFields } from '../../../../../containers/source'; import { mockBrowserFields } from '../../../../../containers/source/mock'; @@ -46,7 +45,7 @@ describe('GenericRowRenderer', () => { }); const wrapper = shallow({children}); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('should return false if not a auditd datum', () => { @@ -125,7 +124,7 @@ describe('GenericRowRenderer', () => { }); const wrapper = shallow({children}); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('should return false if not a auditd datum', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_row_renderer.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_row_renderer.tsx index 71d56d641037d..bcf464ab6da15 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_row_renderer.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/generic_row_renderer.tsx @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +/* eslint-disable react/display-name */ + import { IconType } from '@elastic/eui'; import { get } from 'lodash/fp'; import React from 'react'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/primary_secondary_user_info.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/primary_secondary_user_info.test.tsx index a5b861be08e56..598769e854b42 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/primary_secondary_user_info.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/primary_secondary_user_info.test.tsx @@ -5,8 +5,7 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { TestProviders } from '../../../../../mock'; import { PrimarySecondaryUserInfo, nilOrUnSet } from './primary_secondary_user_info'; @@ -26,7 +25,7 @@ describe('UserPrimarySecondary', () => { secondary="secondary-1" /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('should render user name only if that is all that is present', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/primary_secondary_user_info.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/primary_secondary_user_info.tsx index bd350a599bb47..a54042d3de9d8 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/primary_secondary_user_info.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/primary_secondary_user_info.tsx @@ -5,7 +5,7 @@ */ import { EuiFlexGroup } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import { DraggableBadge } from '../../../../draggables'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/session_user_host_working_dir.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/session_user_host_working_dir.test.tsx index 2240d83169e0d..a0a9977f5765e 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/session_user_host_working_dir.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/session_user_host_working_dir.test.tsx @@ -6,8 +6,7 @@ import { EuiFlexItem } from '@elastic/eui'; import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { TestProviders } from '../../../../../mock'; import { SessionUserHostWorkingDir } from './session_user_host_working_dir'; @@ -32,7 +31,7 @@ describe('SessionUserHostWorkingDir', () => { /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it renders with just eventId and contextId', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/session_user_host_working_dir.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/session_user_host_working_dir.tsx index 3c825e6c931be..6a6b55bb817c8 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/session_user_host_working_dir.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/auditd/session_user_host_working_dir.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import { DraggableBadge } from '../../../../draggables'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/dns/dns_request_event_details.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/dns/dns_request_event_details.test.tsx index 07207c822ad29..a7c9d10e82a2f 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/dns/dns_request_event_details.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/dns/dns_request_event_details.test.tsx @@ -9,7 +9,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import { TestProviders } from '../../../../../mock'; import { mockBrowserFields } from '../../../../../../public/containers/source/mock'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/dns/dns_request_event_details.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/dns/dns_request_event_details.tsx index 752163901de2e..824e8c00de307 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/dns/dns_request_event_details.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/dns/dns_request_event_details.tsx @@ -6,7 +6,7 @@ import { EuiSpacer } from '@elastic/eui'; import { get } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { BrowserFields } from '../../../../../containers/source'; import { Details } from '../helpers'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/dns/dns_request_event_details_line.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/dns/dns_request_event_details_line.test.tsx index d6c50460194a7..e12eacd73559d 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/dns/dns_request_event_details_line.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/dns/dns_request_event_details_line.test.tsx @@ -9,7 +9,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import { TestProviders } from '../../../../../mock'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/dns/dns_request_event_details_line.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/dns/dns_request_event_details_line.tsx index fd49395379e24..c7a08620bebbb 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/dns/dns_request_event_details_line.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/dns/dns_request_event_details_line.tsx @@ -5,7 +5,7 @@ */ import { EuiFlexGroup } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import { DraggableBadge } from '../../../../draggables'; import { isNillEmptyOrNotFinite, TokensFlexItem } from '../helpers'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/empty_column_renderer.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/empty_column_renderer.test.tsx index 04f7cd9b560a6..b31d01b8e94a0 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/empty_column_renderer.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/empty_column_renderer.test.tsx @@ -5,7 +5,6 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import { cloneDeep } from 'lodash/fp'; import React from 'react'; @@ -36,7 +35,7 @@ describe('empty_column_renderer', () => { timelineId: 'test', }); const wrapper = shallow({emptyColumn}); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('should return isInstance true if source is empty', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/empty_column_renderer.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/empty_column_renderer.tsx index 67554cc764486..7e2346ced8785 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/empty_column_renderer.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/empty_column_renderer.tsx @@ -4,7 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +/* eslint-disable react/display-name */ + +import React from 'react'; import { TimelineNonEcsData } from '../../../../graphql/types'; import { DraggableWrapper, DragEffects } from '../../../drag_and_drop/draggable_wrapper'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/endgame/endgame_security_event_details.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/endgame/endgame_security_event_details.test.tsx index 77569f07a23c2..72b879d4ade78 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/endgame/endgame_security_event_details.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/endgame/endgame_security_event_details.test.tsx @@ -9,7 +9,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import { TestProviders } from '../../../../../mock'; import { mockBrowserFields } from '../../../../../../public/containers/source/mock'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/endgame/endgame_security_event_details.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/endgame/endgame_security_event_details.tsx index 10f9c4ad9e545..35a88f52f05a3 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/endgame/endgame_security_event_details.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/endgame/endgame_security_event_details.tsx @@ -6,7 +6,7 @@ import { EuiSpacer } from '@elastic/eui'; import { get } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { BrowserFields } from '../../../../../containers/source'; import { Ecs } from '../../../../../graphql/types'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/endgame/endgame_security_event_details_line.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/endgame/endgame_security_event_details_line.test.tsx index 45a824e034b15..4e522f6ed5c94 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/endgame/endgame_security_event_details_line.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/endgame/endgame_security_event_details_line.test.tsx @@ -9,7 +9,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import { TestProviders } from '../../../../../mock'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/endgame/endgame_security_event_details_line.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/endgame/endgame_security_event_details_line.tsx index 185e9c9a8287a..c2c42ba0e4ddc 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/endgame/endgame_security_event_details_line.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/endgame/endgame_security_event_details_line.tsx @@ -5,7 +5,7 @@ */ import { EuiFlexGroup } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import { DraggableBadge } from '../../../../draggables'; import { isNillEmptyOrNotFinite, TokensFlexItem } from '../helpers'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/exit_code_draggable.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/exit_code_draggable.test.tsx index 21fbafb64d57f..4da236bfa34c3 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/exit_code_draggable.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/exit_code_draggable.test.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import { TestProviders } from '../../../../mock'; import { useMountAppended } from '../../../../utils/use_mount_appended'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/exit_code_draggable.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/exit_code_draggable.tsx index 9ea5f2cdd99fa..7671e3f0509a5 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/exit_code_draggable.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/exit_code_draggable.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import { DraggableBadge } from '../../../draggables'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/file_draggable.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/file_draggable.test.tsx index ff63d02acc37c..d800821f8d8a5 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/file_draggable.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/file_draggable.test.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import { TestProviders } from '../../../../mock'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/file_draggable.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/file_draggable.tsx index a1c43f3ecb163..e4871c6479c6b 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/file_draggable.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/file_draggable.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import { DraggableBadge } from '../../../draggables'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/formatted_field.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/formatted_field.test.tsx index 5c54e5be3374c..73f7b004ca3f7 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/formatted_field.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/formatted_field.test.tsx @@ -4,12 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import { get } from 'lodash/fp'; -import * as React from 'react'; -import { ThemeProvider } from 'styled-components'; +import React from 'react'; import { mockTimelineData, TestProviders } from '../../../../mock'; import { getEmptyValue } from '../../../empty_value'; @@ -21,31 +18,34 @@ import { HOST_NAME_FIELD_NAME } from './constants'; jest.mock('../../../../lib/kibana'); describe('Events', () => { - const theme = () => ({ eui: euiDarkVars, darkMode: true }); const mount = useMountAppended(); test('renders correctly against snapshot', () => { const wrapper = shallow( - + + + ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper.find('FormattedFieldValue')).toMatchSnapshot(); }); test('it renders a localized date tooltip for a field type of date that has a valid timestamp', () => { const wrapper = mount( - + + + ); expect(wrapper.find('[data-test-subj="localized-date-tool-tip"]').exists()).toEqual(true); @@ -53,13 +53,15 @@ describe('Events', () => { test('it does NOT render a localized date tooltip when field type is NOT date, even if it contains valid timestamp', () => { const wrapper = mount( - + + + ); expect(wrapper.find('[data-test-subj="localized-date-tool-tip"]').exists()).toEqual(false); @@ -72,13 +74,15 @@ describe('Events', () => { }; const wrapper = mount( - + + + ); expect(wrapper.find('[data-test-subj="localized-date-tool-tip"]').exists()).toEqual(false); @@ -86,13 +90,15 @@ describe('Events', () => { test('it renders the value for a non-date field when the field is populated', () => { const wrapper = mount( - + + + ); expect(wrapper.text()).toEqual('nginx'); @@ -100,7 +106,7 @@ describe('Events', () => { test('it renders placeholder text for a non-date field when the field is NOT populated', () => { const wrapper = mount( - + { fieldType="text" value={get('fake.field', mockTimelineData[0].ecs)} /> - + ); expect(wrapper.text()).toEqual(getEmptyValue()); @@ -116,14 +122,16 @@ describe('Events', () => { test('it renders tooltip for truncatable message when it exists', () => { const wrapper = mount( - + + + ); expect(wrapper.find('[data-test-subj="message-tool-tip"]').exists()).toEqual(true); @@ -179,53 +187,61 @@ describe('Events', () => { test('it renders a message text string', () => { const wrapper = mount( - + + + ); expect(wrapper.text()).toEqual('some message'); }); test('it renders truncatable message text when fieldName is message with truncate prop', () => { const wrapper = mount( - + + + ); expect(wrapper.find('[data-test-subj="truncatable-message"]').exists()).toEqual(true); }); test('it does NOT render the truncatable message style when fieldName is NOT message', () => { const wrapper = mount( - + + + ); expect(wrapper.find('[data-test-subj="truncatable-message"]').exists()).toEqual(false); }); test('it renders a hyperlink to the hosts details page when fieldName is host.name, and a hostname is provided', () => { const wrapper = mount( - + + + ); expect(wrapper.find('[data-test-subj="host-details-link"]').exists()).toEqual(true); }); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/formatted_field.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/formatted_field.tsx index 7529c0623ef47..280de3683a11f 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/formatted_field.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/formatted_field.tsx @@ -5,9 +5,10 @@ */ import { EuiFlexGroup, EuiFlexItem, EuiToolTip } from '@elastic/eui'; -import { isNumber, isString } from 'lodash/fp'; -import * as React from 'react'; +import { isNumber, isString, isEmpty } from 'lodash/fp'; +import React from 'react'; +import { DefaultDraggable } from '../../../draggables'; import { Bytes, BYTES_FORMAT } from '../../../bytes'; import { Duration, EVENT_DURATION_FIELD_NAME } from '../../../duration'; import { getOrEmptyTagFromValue, getEmptyTagValue } from '../../../empty_value'; @@ -23,6 +24,9 @@ import { MESSAGE_FIELD_NAME, } from './constants'; +// simple black-list to prevent dragging and dropping fields such as message name +const columnNamesNotDraggable = [MESSAGE_FIELD_NAME]; + export const FormattedFieldValue = React.memo<{ contextId: string; eventId: string; @@ -42,7 +46,16 @@ export const FormattedFieldValue = React.memo<{ /> ); } else if (fieldType === DATE_FIELD_TYPE) { - return ; + return ( + + + + ); } else if (PORT_NAMES.some(portName => fieldName === portName)) { return ( @@ -55,11 +68,16 @@ export const FormattedFieldValue = React.memo<{ const hostname = `${value}`; return isString(value) && hostname.length > 0 ? ( - + - {value} + {value} - + ) : ( getEmptyTagValue() ); @@ -67,34 +85,46 @@ export const FormattedFieldValue = React.memo<{ return ( ); - } else if (fieldName === MESSAGE_FIELD_NAME && value != null && value !== '') { - return ( - <> - {truncate ? ( - - - - {fieldName} - - - {value} - - - } - > - <>{value} - - - ) : ( + } else if (columnNamesNotDraggable.includes(fieldName)) { + return truncate && !isEmpty(value) ? ( + + + + {fieldName} + + + {value} + + + } + > <>{value} - )} - + + + ) : ( + <>{value} ); } else { - return getOrEmptyTagFromValue(value); + const contentValue = getOrEmptyTagFromValue(value); + const content = truncate ? {contentValue} : contentValue; + + return ( + + {content} + + ); } }); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/get_column_renderer.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/get_column_renderer.test.tsx index d445ec2859e2c..25d5c71caf48a 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/get_column_renderer.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/get_column_renderer.test.tsx @@ -5,9 +5,8 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import { cloneDeep } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { TimelineNonEcsData } from '../../../../graphql/types'; import { mockTimelineData } from '../../../../mock'; @@ -41,7 +40,7 @@ describe('get_column_renderer', () => { }); const wrapper = shallow({column}); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('should render event severity when dealing with data that is not suricata', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/get_row_renderer.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/get_row_renderer.test.tsx index bea525116021d..f367769b78f40 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/get_row_renderer.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/get_row_renderer.test.tsx @@ -5,9 +5,8 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import { cloneDeep } from 'lodash'; -import * as React from 'react'; +import React from 'react'; import { mockBrowserFields } from '../../../../containers/source/mock'; import { Ecs } from '../../../../graphql/types'; @@ -44,7 +43,7 @@ describe('get_column_renderer', () => { }); const wrapper = shallow({row}); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('should render plain row data when it is a non suricata row', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/host_working_dir.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/host_working_dir.test.tsx index 6c58b1ec6f35c..d84dfcc561882 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/host_working_dir.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/host_working_dir.test.tsx @@ -5,7 +5,6 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import React from 'react'; import { mockTimelineData, TestProviders } from '../../../../mock'; @@ -24,7 +23,7 @@ describe('HostWorkingDir', () => { workingDirectory="[working-directory-123]" /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it renders a hostname without a workingDirectory', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/host_working_dir.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/host_working_dir.tsx index 0bdecfecd6c59..db49df30be473 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/host_working_dir.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/host_working_dir.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import { DraggableBadge } from '../../../draggables'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/netflow.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/netflow.tsx index 0904c836c2f30..0990301b6e2b9 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/netflow.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/netflow.tsx @@ -5,7 +5,7 @@ */ import { get } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { Ecs } from '../../../../graphql/types'; import { asArrayIfExists } from '../../../../lib/helpers'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/netflow/netflow_row_renderer.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/netflow/netflow_row_renderer.test.tsx index 6ba8f3f28dae8..68629a9a70058 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/netflow/netflow_row_renderer.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/netflow/netflow_row_renderer.test.tsx @@ -5,8 +5,7 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { BrowserFields } from '../../../../../containers/source'; import { mockBrowserFields } from '../../../../../containers/source/mock'; @@ -38,7 +37,7 @@ describe('netflowRowRenderer', () => { }); const wrapper = shallow({children}); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); describe('#isInstance', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/netflow/netflow_row_renderer.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/netflow/netflow_row_renderer.tsx index 3ae154a14aaf5..754d6ad99b7fe 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/netflow/netflow_row_renderer.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/netflow/netflow_row_renderer.tsx @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +/* eslint-disable react/display-name */ + import { get } from 'lodash/fp'; import React from 'react'; import styled from 'styled-components'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/parent_process_draggable.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/parent_process_draggable.test.tsx index 80ae10a48415c..684def7386da0 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/parent_process_draggable.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/parent_process_draggable.test.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import { TestProviders } from '../../../../mock'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/parent_process_draggable.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/parent_process_draggable.tsx index 7cb6c3704a238..1402743ef8a51 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/parent_process_draggable.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/parent_process_draggable.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import { DraggableBadge } from '../../../draggables'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/plain_column_renderer.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/plain_column_renderer.test.tsx index 008885b5264c8..8a22307767a40 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/plain_column_renderer.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/plain_column_renderer.test.tsx @@ -5,23 +5,17 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import { cloneDeep } from 'lodash/fp'; -import moment from 'moment-timezone'; -import * as React from 'react'; +import React from 'react'; import { TimelineNonEcsData } from '../../../../graphql/types'; -import { defaultHeaders, mockFrameworks, mockTimelineData, TestProviders } from '../../../../mock'; +import { defaultHeaders, mockTimelineData, TestProviders } from '../../../../mock'; import { getEmptyValue } from '../../../empty_value'; import { useMountAppended } from '../../../../utils/use_mount_appended'; import { plainColumnRenderer } from './plain_column_renderer'; import { getValues, deleteItemIdx, findItem } from './helpers'; -jest.mock('../../../../lib/kibana'); - -const mockFramework = mockFrameworks.default_UTC; - describe('plain_column_renderer', () => { const mount = useMountAppended(); @@ -41,7 +35,7 @@ describe('plain_column_renderer', () => { timelineId: 'test', }); const wrapper = shallow({column}); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('should return isInstance false if source is empty', () => { @@ -134,11 +128,7 @@ describe('plain_column_renderer', () => { {column} ); - expect(wrapper.text()).toEqual( - moment - .tz(getValues('@timestamp', mockDatum)![0], mockFramework.dateFormatTz!) - .format(mockFramework.dateFormat) - ); + expect(wrapper.text()).toEqual('Nov 5, 2018 @ 19:03:25.937'); }); test('should return an empty value if destination ip is empty', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/plain_column_renderer.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/plain_column_renderer.tsx index 25a70502e3e45..70485c41f3b88 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/plain_column_renderer.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/plain_column_renderer.tsx @@ -4,29 +4,18 @@ * you may not use this file except in compliance with the Elastic License. */ -import { isNumber } from 'lodash/fp'; import React from 'react'; import { TimelineNonEcsData } from '../../../../graphql/types'; -import { DragEffects, DraggableWrapper } from '../../../drag_and_drop/draggable_wrapper'; -import { escapeDataProviderId } from '../../../drag_and_drop/helpers'; import { getEmptyTagValue } from '../../../empty_value'; -import { FormattedIp } from '../../../formatted_ip'; -import { IS_OPERATOR, DataProvider } from '../../data_providers/data_provider'; -import { Provider } from '../../data_providers/provider'; import { ColumnHeader } from '../column_headers/column_header'; import { ColumnRenderer } from './column_renderer'; -import { IP_FIELD_TYPE, MESSAGE_FIELD_NAME } from './constants'; import { FormattedFieldValue } from './formatted_field'; -import { parseQueryValue } from './parse_query_value'; import { parseValue } from './parse_value'; export const dataExistsAtColumn = (columnName: string, data: TimelineNonEcsData[]): boolean => data.findIndex(item => item.field === columnName) !== -1; -// simple black-list to prevent dragging and dropping fields such as message name -const columnNamesNotDraggable = [MESSAGE_FIELD_NAME]; - export const plainColumnRenderer: ColumnRenderer = { isInstance: (columnName: string, data: TimelineNonEcsData[]) => dataExistsAtColumn(columnName, data), @@ -47,91 +36,17 @@ export const plainColumnRenderer: ColumnRenderer = { values: string[] | undefined | null; }) => values != null - ? values.map(value => { - const itemDataProvider: DataProvider = { - enabled: true, - id: escapeDataProviderId( - `plain-column-renderer-data-provider-${timelineId}-${columnName}-${eventId}-${field.id}-${value}` - ), - name: `${columnName}: ${parseQueryValue(value)}`, - queryMatch: { - field: field.id, - value: parseQueryValue(value), - operator: IS_OPERATOR, - }, - excluded: false, - kqlQuery: '', - and: [], - }; - if (field.type === IP_FIELD_TYPE) { - // since ip fields may contain multiple IP addresses, return a FormattedIp here to avoid a "draggable of draggables" - return ( - - ); - } - - if (columnNamesNotDraggable.includes(columnName)) { - if (truncate) { - return ( - - ); - } else { - return ( - - ); - } - } - // note: we use a raw DraggableWrapper here instead of a DefaultDraggable, - // because we pass a width to enable text truncation, and we will show empty values - return ( - - snapshot.isDragging ? ( - - - - ) : ( - - ) - } - truncate={truncate} - /> - ); - }) + ? values.map(value => ( + + )) : getEmptyTagValue(), }; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/plain_row_renderer.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/plain_row_renderer.test.tsx index 355f3fb248238..50ea7ca05b921 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/plain_row_renderer.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/plain_row_renderer.test.tsx @@ -6,7 +6,6 @@ import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import { cloneDeep } from 'lodash'; import React from 'react'; import { ThemeProvider } from 'styled-components'; @@ -30,7 +29,7 @@ describe('plain_row_renderer', () => { timelineId: 'test', }); const wrapper = shallow({children}); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('should always return isInstance true', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/plain_row_renderer.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/plain_row_renderer.tsx index 1eed05e6d2a7e..6725830c97d0a 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/plain_row_renderer.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/plain_row_renderer.tsx @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +/* eslint-disable react/display-name */ + import React from 'react'; import { RowRenderer } from './row_renderer'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/process_draggable.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/process_draggable.test.tsx index ff1cb60db0d93..8cc7323ed358f 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/process_draggable.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/process_draggable.test.tsx @@ -5,8 +5,7 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { TestProviders } from '../../../../mock'; import { ProcessDraggable, ProcessDraggableWithNonExistentProcess } from './process_draggable'; @@ -28,7 +27,7 @@ describe('ProcessDraggable', () => { processPid={123} /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it returns null if everything is null', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/process_draggable.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/process_draggable.tsx index 29d6c0e7d59c2..35512c60629dd 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/process_draggable.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/process_draggable.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import { DraggableBadge } from '../../../draggables'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/process_hash.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/process_hash.tsx index cb0b40bdd8fca..b6696d38dc1c5 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/process_hash.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/process_hash.tsx @@ -5,7 +5,7 @@ */ import { EuiFlexGroup } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { DraggableBadge } from '../../../draggables'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_details.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_details.test.tsx index 3f77726474c56..027aa0df8bcdd 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_details.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_details.test.tsx @@ -5,8 +5,7 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { mockBrowserFields } from '../../../../../containers/source/mock'; import { mockTimelineData } from '../../../../../mock'; @@ -26,7 +25,7 @@ describe('SuricataDetails', () => { timelineId="test" /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it returns text if the data does contain suricata data', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_details.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_details.tsx index 35733e5e0b31b..17f5f236265ed 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_details.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_details.tsx @@ -6,7 +6,7 @@ import { EuiSpacer } from '@elastic/eui'; import { get } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { BrowserFields } from '../../../../../containers/source'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_refs.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_refs.tsx index 6fe6523180e58..dd773bb88ef68 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_refs.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_refs.tsx @@ -5,7 +5,7 @@ */ import { EuiFlexGroup, EuiFlexItem, EuiLink } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { ExternalLinkIcon } from '../../../../external_link_icon'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_row_renderer.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_row_renderer.test.tsx index 66c9613c02995..170d17e8e279e 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_row_renderer.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_row_renderer.test.tsx @@ -5,9 +5,8 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import { cloneDeep } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { mockBrowserFields } from '../../../../../containers/source/mock'; import { Ecs } from '../../../../../graphql/types'; @@ -35,7 +34,7 @@ describe('suricata_row_renderer', () => { }); const wrapper = shallow({children}); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('should return false if not a suricata datum', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_row_renderer.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_row_renderer.tsx index 0bafe54b715fd..b227326551e01 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_row_renderer.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_row_renderer.tsx @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +/* eslint-disable react/display-name */ + import { get } from 'lodash/fp'; import React from 'react'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_signature.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_signature.test.tsx index 4eefb4b0bc8b9..beae16af558ed 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_signature.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_signature.test.tsx @@ -5,8 +5,7 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { TestProviders } from '../../../../../mock'; import { useMountAppended } from '../../../../../utils/use_mount_appended'; @@ -30,7 +29,7 @@ describe('SuricataSignature', () => { signature="ET SCAN ATTACK Hello" /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_signature.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_signature.tsx index 632e8ff35950e..2b9adfe21b120 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_signature.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/suricata/suricata_signature.tsx @@ -5,7 +5,7 @@ */ import { EuiBadge, EuiBadgeProps, EuiFlexGroup, EuiFlexItem, EuiToolTip } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { DragEffects, DraggableWrapper } from '../../../../drag_and_drop/draggable_wrapper'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/auth_ssh.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/auth_ssh.test.tsx index e42a91b7d7972..4e4e1a0b7bf6f 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/auth_ssh.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/auth_ssh.test.tsx @@ -5,8 +5,7 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { AuthSsh } from './auth_ssh'; @@ -21,7 +20,7 @@ describe('AuthSsh', () => { sshMethod="[ssh-method]" /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it returns null if sshSignature and sshMethod are both null', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/auth_ssh.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/auth_ssh.tsx index 60eab8381e98d..0ff2eec35314d 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/auth_ssh.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/auth_ssh.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import { DraggableBadge } from '../../../../draggables'; import { TokensFlexItem } from '../helpers'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_details.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_details.test.tsx index 54f5b2f165287..19113d93f7cb0 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_details.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_details.test.tsx @@ -5,8 +5,7 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { BrowserFields } from '../../../../../containers/source'; import { mockBrowserFields } from '../../../../../containers/source/mock'; @@ -30,7 +29,7 @@ describe('SystemGenericDetails', () => { timelineId="test" /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it returns system rendering if the data does contain system data', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_details.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_details.tsx index e3627d0ec918a..e1524c8e5aecb 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_details.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_details.tsx @@ -6,7 +6,7 @@ import { EuiFlexGroup, EuiSpacer } from '@elastic/eui'; import { get } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { BrowserFields } from '../../../../../containers/source'; import { Ecs } from '../../../../../graphql/types'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_file_details.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_file_details.test.tsx index d4260d9bd183a..cab7191c13aef 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_file_details.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_file_details.test.tsx @@ -5,8 +5,7 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { BrowserFields } from '../../../../../containers/source'; import { mockBrowserFields } from '../../../../../containers/source/mock'; @@ -30,7 +29,7 @@ describe('SystemGenericFileDetails', () => { timelineId="test" /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it returns system rendering if the data does contain system data', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_file_details.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_file_details.tsx index 401c1c522ca60..c47d9603cbea2 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_file_details.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_file_details.tsx @@ -6,7 +6,7 @@ import { EuiFlexGroup, EuiSpacer } from '@elastic/eui'; import { get } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { BrowserFields } from '../../../../../containers/source'; import { Ecs } from '../../../../../graphql/types'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_row_renderer.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_row_renderer.test.tsx index b2dbdb6b0e45c..5f809d595f1b0 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_row_renderer.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_row_renderer.test.tsx @@ -5,9 +5,8 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import { cloneDeep } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { BrowserFields } from '../../../../../containers/source'; import { mockBrowserFields } from '../../../../../containers/source/mock'; @@ -49,8 +48,6 @@ import { } from './generic_row_renderer'; import * as i18n from './translations'; -jest.mock('../../../../../lib/kibana'); - describe('GenericRowRenderer', () => { const mount = useMountAppended(); @@ -77,7 +74,7 @@ describe('GenericRowRenderer', () => { }); const wrapper = shallow({children}); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('should return false if not a system datum', () => { @@ -141,7 +138,7 @@ describe('GenericRowRenderer', () => { }); const wrapper = shallow({children}); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('should return false if not a auditd datum', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_row_renderer.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_row_renderer.tsx index f8930fdca7ba2..3e64248d39876 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_row_renderer.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/generic_row_renderer.tsx @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +/* eslint-disable react/display-name */ + import { get } from 'lodash/fp'; import React from 'react'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/package.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/package.test.tsx index 167abe2185bcc..100c8fbe5a988 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/package.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/package.test.tsx @@ -5,8 +5,7 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { TestProviders } from '../../../../../mock'; import { useMountAppended } from '../../../../../utils/use_mount_appended'; @@ -26,7 +25,7 @@ describe('Package', () => { packageVersion="package-version-123" /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it returns null if all of the package information is null ', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/package.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/package.tsx index d87639d2b8d6e..a28e850e2af96 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/package.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/system/package.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import { DraggableBadge } from '../../../../draggables'; import { TokensFlexItem } from '../helpers'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/unknown_column_renderer.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/unknown_column_renderer.test.tsx index 41a71f55cae19..73d1d5cb441ef 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/unknown_column_renderer.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/unknown_column_renderer.test.tsx @@ -6,7 +6,6 @@ import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import { cloneDeep } from 'lodash'; import React from 'react'; import { ThemeProvider } from 'styled-components'; @@ -34,7 +33,7 @@ describe('unknown_column_renderer', () => { timelineId: 'test', }); const wrapper = shallow({emptyColumn}); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('should return isInstance true with a made up column name', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/user_host_working_dir.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/user_host_working_dir.test.tsx index 662392078f38a..45b670acb569a 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/user_host_working_dir.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/user_host_working_dir.test.tsx @@ -5,8 +5,7 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { TestProviders } from '../../../../mock'; import { UserHostWorkingDir } from './user_host_working_dir'; @@ -27,7 +26,7 @@ describe('UserHostWorkingDir', () => { workingDirectory="[working-directory-123]" /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it returns null if userDomain, userName, hostName, and workingDirectory are all null', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/user_host_working_dir.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/user_host_working_dir.tsx index 281cfd39bd9d2..d370afee2585f 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/user_host_working_dir.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/user_host_working_dir.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import { DraggableBadge } from '../../../draggables'; import { TokensFlexItem } from './helpers'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_details.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_details.test.tsx index 73b90410bc803..7617a01acf1d9 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_details.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_details.test.tsx @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { mockBrowserFields } from '../../../../../containers/source/mock'; import { mockTimelineData, TestProviders } from '../../../../../mock'; @@ -26,7 +25,7 @@ describe('ZeekDetails', () => { /> ); - expect(toJson(wrapper.find('ZeekDetails'))).toMatchSnapshot(); + expect(wrapper.find('ZeekDetails')).toMatchSnapshot(); }); test('it returns zeek.connection if the data does contain zeek.connection data', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_details.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_details.tsx index 1a31e560d7810..d8561186b4546 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_details.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_details.tsx @@ -5,7 +5,7 @@ */ import { EuiSpacer } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { BrowserFields } from '../../../../../containers/source'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_row_renderer.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_row_renderer.test.tsx index b4fd9a978b7d8..4242308a55a64 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_row_renderer.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_row_renderer.test.tsx @@ -5,9 +5,8 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import { cloneDeep } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { mockBrowserFields } from '../../../../../containers/source/mock'; import { Ecs } from '../../../../../graphql/types'; @@ -34,7 +33,7 @@ describe('zeek_row_renderer', () => { }); const wrapper = shallow({children}); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('should return false if not a zeek datum', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_row_renderer.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_row_renderer.tsx index 83d1c4347f57c..fc528e33b5ab6 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_row_renderer.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_row_renderer.tsx @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +/* eslint-disable react/display-name */ + import { get } from 'lodash/fp'; import React from 'react'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_signature.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_signature.test.tsx index 4ef2bb89e05ca..c09bd6b7a356d 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_signature.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_signature.test.tsx @@ -5,9 +5,8 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import { cloneDeep } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { Ecs } from '../../../../../graphql/types'; import { mockTimelineData, TestProviders } from '../../../../../mock'; @@ -37,7 +36,7 @@ describe('ZeekSignature', () => { describe('rendering', () => { test('it renders the default Zeek', () => { const wrapper = shallow(); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_signature.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_signature.tsx index 6a6ae4e4e7da5..72f58df5677e4 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_signature.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/renderers/zeek/zeek_signature.tsx @@ -6,7 +6,7 @@ import { EuiBadge, EuiBadgeProps, EuiFlexGroup, EuiFlexItem, EuiToolTip } from '@elastic/eui'; import { get } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { Ecs } from '../../../../../graphql/types'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/sort/sort_indicator.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/sort/sort_indicator.test.tsx index cda1fe0844e00..db3e96a4e2650 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/sort/sort_indicator.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/sort/sort_indicator.test.tsx @@ -5,8 +5,7 @@ */ import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { Direction } from '../../../../graphql/types'; @@ -16,7 +15,7 @@ describe('SortIndicator', () => { describe('rendering', () => { test('renders correctly against snapshot', () => { const wrapper = shallow(); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it renders the sort indicator', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/body/sort/sort_indicator.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/body/sort/sort_indicator.tsx index fc77bbd725704..74fb1e5e4034c 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/body/sort/sort_indicator.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/body/sort/sort_indicator.tsx @@ -5,7 +5,7 @@ */ import { EuiIcon } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import { Direction } from '../../../../graphql/types'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/data_providers.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/data_providers.test.tsx index d67c6c9648a15..a88062d9093d7 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/data_providers.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/data_providers.test.tsx @@ -5,8 +5,7 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { TestProviders } from '../../../mock/test_providers'; import { useMountAppended } from '../../../utils/use_mount_appended'; @@ -36,7 +35,7 @@ describe('DataProviders', () => { show={true} /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it should render a placeholder when there are zero data providers', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/empty.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/empty.test.tsx index c249e263d1205..10586657b52a3 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/empty.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/empty.test.tsx @@ -5,8 +5,7 @@ */ import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { Empty } from './empty'; import { TestProviders } from '../../../mock/test_providers'; @@ -15,7 +14,7 @@ describe('Empty', () => { describe('rendering', () => { test('renders correctly against snapshot', () => { const wrapper = shallow(); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); const dropMessage = ['Drop', 'anything', 'highlighted', 'here']; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/empty.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/empty.tsx index 87d45f6d3db17..a47fb932ed26c 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/empty.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/empty.tsx @@ -5,7 +5,7 @@ */ import { EuiBadge, EuiBadgeProps, EuiText } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { AndOrBadge } from '../../and_or_badge'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/index.tsx index cce6dfc140375..525cc8e301d11 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/index.tsx @@ -5,7 +5,7 @@ */ import { rgba } from 'polished'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { BrowserFields } from '../../../containers/source'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/provider.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/provider.test.tsx index aa3f07cb1b17a..f0d7ca83fb391 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/provider.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/provider.test.tsx @@ -5,8 +5,7 @@ */ import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { TestProviders } from '../../../mock/test_providers'; @@ -17,7 +16,7 @@ describe('Provider', () => { describe('rendering', () => { test('renders correctly against snapshot', () => { const wrapper = shallow(); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it renders the data provider', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/provider_item_and.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/provider_item_and.tsx index 05dc7b7b84587..badc92d00c174 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/provider_item_and.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/provider_item_and.tsx @@ -5,7 +5,7 @@ */ import { EuiFlexItem } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import { AndOrBadge } from '../../and_or_badge'; import { BrowserFields } from '../../../containers/source'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/provider_item_and_drag_drop.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/provider_item_and_drag_drop.tsx index 17457b900f3a9..1a1e8292b7e02 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/provider_item_and_drag_drop.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/provider_item_and_drag_drop.tsx @@ -6,7 +6,7 @@ import { EuiBadge, EuiBadgeProps, EuiFlexGroup, EuiFlexItem, EuiText } from '@elastic/eui'; import { rgba } from 'polished'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { AndOrBadge } from '../../and_or_badge'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/providers.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/providers.test.tsx index c9454846c5548..d8076ac90e6b2 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/providers.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/providers.test.tsx @@ -5,8 +5,7 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { TestProviders } from '../../../mock/test_providers'; import { DroppableWrapper } from '../../drag_and_drop/droppable_wrapper'; @@ -36,7 +35,7 @@ describe('Providers', () => { onToggleDataProviderExcluded={jest.fn()} /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it renders the data providers', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/providers.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/providers.tsx index 4d095485ef69d..bfe99f6920e66 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/providers.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/data_providers/providers.tsx @@ -5,7 +5,7 @@ */ import { EuiFlexGroup, EuiFlexItem, EuiFormHelpText } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import { Draggable } from 'react-beautiful-dnd'; import styled from 'styled-components'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/expandable_event/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/expandable_event/index.tsx index 8f5d91d8ce11f..1c5df9d220a62 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/expandable_event/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/expandable_event/index.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { BrowserFields } from '../../../containers/source'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/footer/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/footer/index.test.tsx index 07b7741e5c152..b6ca4fe125c69 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/footer/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/footer/index.test.tsx @@ -5,9 +5,8 @@ */ import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import { getOr } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { TestProviders } from '../../../mock/test_providers'; @@ -41,7 +40,7 @@ describe('Footer Timeline Component', () => { /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it renders the loading panel at the beginning ', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/header/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/header/index.test.tsx index 4527e39128f89..5af7aff4f8795 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/header/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/header/index.test.tsx @@ -5,8 +5,7 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { Direction } from '../../../graphql/types'; import { mockIndexPattern } from '../../../mock'; @@ -44,7 +43,7 @@ describe('Header', () => { }} /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it renders the data providers', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/header/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/header/index.tsx index 814d25d9c718d..7e570d613ca5a 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/header/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/header/index.tsx @@ -5,7 +5,7 @@ */ import { EuiCallOut } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { IIndexPattern } from 'src/plugins/data/public'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/properties/helpers.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/properties/helpers.tsx index 0e222f470f0d7..ae139c24d0176 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/properties/helpers.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/properties/helpers.tsx @@ -18,7 +18,7 @@ import { EuiOverlayMask, EuiToolTip, } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import uuid from 'uuid'; import { Note } from '../../../lib/note'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/properties/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/properties/index.test.tsx index bc05204cc47fe..495b94f8c02e7 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/properties/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/properties/index.test.tsx @@ -5,7 +5,7 @@ */ import { mount } from 'enzyme'; -import * as React from 'react'; +import React from 'react'; import { Provider as ReduxStoreProvider } from 'react-redux'; import { mockGlobalState, apolloClientObservable } from '../../../mock'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/refetch_timeline.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/refetch_timeline.tsx index 8a78d04c88311..c804ccf658296 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/refetch_timeline.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/refetch_timeline.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { memo, useEffect } from 'react'; +import React, { useEffect } from 'react'; import { connect } from 'react-redux'; import { compose } from 'redux'; import { ActionCreator } from 'typescript-fsa'; @@ -33,18 +33,23 @@ export interface TimelineRefetchProps { type OwnProps = TimelineRefetchProps & TimelineRefetchDispatch; -const TimelineRefetchComponent = memo( - ({ id, inputId, inspect, loading, refetch, setTimelineQuery }) => { - useEffect(() => { - setTimelineQuery({ id, inputId, inspect, loading, refetch }); - }, [id, inputId, loading, refetch, inspect]); - - return null; - } -); +const TimelineRefetchComponent: React.FC = ({ + id, + inputId, + inspect, + loading, + refetch, + setTimelineQuery, +}) => { + useEffect(() => { + setTimelineQuery({ id, inputId, inspect, loading, refetch }); + }, [id, inputId, loading, refetch, inspect]); + + return null; +}; export const TimelineRefetch = compose>( connect(null, { setTimelineQuery: inputsActions.setQuery, }) -)(TimelineRefetchComponent); +)(React.memo(TimelineRefetchComponent)); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/search_or_filter/helpers.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/search_or_filter/helpers.tsx index c9b1a3ced6e93..5db453988cbb8 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/search_or_filter/helpers.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/search_or_filter/helpers.tsx @@ -5,7 +5,7 @@ */ import { EuiSpacer, EuiText } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { AndOrBadge } from '../../and_or_badge'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/search_or_filter/search_or_filter.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/search_or_filter/search_or_filter.tsx index b645202ab4c54..45eb7f85c809f 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/search_or_filter/search_or_filter.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/search_or_filter/search_or_filter.tsx @@ -5,7 +5,7 @@ */ import { EuiFlexGroup, EuiFlexItem, EuiSuperSelect, EuiToolTip } from '@elastic/eui'; -import * as React from 'react'; +import React from 'react'; import styled, { createGlobalStyle } from 'styled-components'; import { esFilters, IIndexPattern } from '../../../../../../../../src/plugins/data/public'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/search_super_select/index.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/search_super_select/index.tsx new file mode 100644 index 0000000000000..c4361bbc8990d --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/timeline/search_super_select/index.tsx @@ -0,0 +1,322 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + EuiHighlight, + EuiInputPopover, + EuiSuperSelect, + EuiSelectable, + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiTextColor, + EuiFilterButton, + EuiFilterGroup, + EuiPortal, +} from '@elastic/eui'; +import { Option } from '@elastic/eui/src/components/selectable/types'; +import { isEmpty } from 'lodash/fp'; +import React, { memo, useCallback, useMemo, useState } from 'react'; +import { ListProps } from 'react-virtualized'; +import styled, { createGlobalStyle } from 'styled-components'; + +import { AllTimelinesQuery } from '../../../containers/timeline/all'; +import { getEmptyTagValue } from '../../empty_value'; +import { isUntitled } from '../../../components/open_timeline/helpers'; +import * as i18nTimeline from '../../../components/open_timeline/translations'; +import { SortFieldTimeline, Direction } from '../../../graphql/types'; +import * as i18n from './translations'; + +const SearchTimelineSuperSelectGlobalStyle = createGlobalStyle` + .euiPopover__panel.euiPopover__panel-isOpen.timeline-search-super-select-popover__popoverPanel { + visibility: hidden; + z-index: 0; + } +`; + +const MyEuiFlexItem = styled(EuiFlexItem)` + display: inline-block; + max-width: 296px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +`; + +const EuiSelectableContainer = styled.div` + .euiSelectable { + .euiFormControlLayout__childrenWrapper { + display: flex; + } + } +`; + +const MyEuiFlexGroup = styled(EuiFlexGroup)` + padding 0px 4px; +`; + +interface SearchTimelineSuperSelectProps { + isDisabled: boolean; + timelineId: string | null; + timelineTitle: string | null; + onTimelineChange: (timelineTitle: string, timelineId: string | null) => void; +} + +const basicSuperSelectOptions = [ + { + value: '-1', + inputDisplay: i18n.DEFAULT_TIMELINE_TITLE, + }, +]; + +const getBasicSelectableOptions = (timelineId: string) => [ + { + description: i18n.DEFAULT_TIMELINE_DESCRIPTION, + label: i18n.DEFAULT_TIMELINE_TITLE, + id: null, + title: i18n.DEFAULT_TIMELINE_TITLE, + checked: timelineId === '-1' ? 'on' : undefined, + } as Option, +]; + +const ORIGINAL_PAGE_SIZE = 50; +const POPOVER_HEIGHT = 260; +const TIMELINE_ITEM_HEIGHT = 50; +const SearchTimelineSuperSelectComponent: React.FC = ({ + isDisabled, + timelineId, + timelineTitle, + onTimelineChange, +}) => { + const [pageSize, setPageSize] = useState(ORIGINAL_PAGE_SIZE); + const [heightTrigger, setHeightTrigger] = useState(0); + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + const [searchTimelineValue, setSearchTimelineValue] = useState(''); + const [onlyFavorites, setOnlyFavorites] = useState(false); + const [searchRef, setSearchRef] = useState(null); + + const onSearchTimeline = useCallback(val => { + setSearchTimelineValue(val); + }, []); + + const handleClosePopover = useCallback(() => { + setIsPopoverOpen(false); + }, []); + + const handleOpenPopover = useCallback(() => { + setIsPopoverOpen(true); + }, []); + + const handleOnToggleOnlyFavorites = useCallback(() => { + setOnlyFavorites(!onlyFavorites); + }, [onlyFavorites]); + + const renderTimelineOption = useCallback((option, searchValue) => { + return ( + + + + + + + + + {isUntitled(option) ? i18nTimeline.UNTITLED_TIMELINE : option.title} + + + + + + {option.description != null && option.description.trim().length > 0 + ? option.description + : getEmptyTagValue()} + + + + + + + + + + ); + }, []); + + const handleTimelineChange = useCallback( + options => { + const selectedTimeline = options.filter( + (option: { checked: string }) => option.checked === 'on' + ); + if (selectedTimeline != null && selectedTimeline.length > 0) { + onTimelineChange( + isEmpty(selectedTimeline[0].title) + ? i18nTimeline.UNTITLED_TIMELINE + : selectedTimeline[0].title, + selectedTimeline[0].id === '-1' ? null : selectedTimeline[0].id + ); + } + setIsPopoverOpen(false); + }, + [onTimelineChange] + ); + + const handleOnScroll = useCallback( + ( + totalTimelines: number, + totalCount: number, + { + clientHeight, + scrollHeight, + scrollTop, + }: { + clientHeight: number; + scrollHeight: number; + scrollTop: number; + } + ) => { + if (totalTimelines < totalCount) { + const clientHeightTrigger = clientHeight * 1.2; + if ( + scrollTop > 10 && + scrollHeight - scrollTop < clientHeightTrigger && + scrollHeight > heightTrigger + ) { + setHeightTrigger(scrollHeight); + setPageSize(pageSize + ORIGINAL_PAGE_SIZE); + } + } + }, + [heightTrigger, pageSize] + ); + + const superSelect = useMemo( + () => ( + + ), + [handleOpenPopover, isDisabled, timelineId, timelineTitle] + ); + + const favoritePortal = useMemo( + () => + searchRef != null ? ( + + + + + + {i18nTimeline.ONLY_FAVORITES} + + + + + + ) : null, + [searchRef, onlyFavorites, handleOnToggleOnlyFavorites] + ); + + return ( + + + {({ timelines, loading, totalCount }) => ( + + { + setSearchRef(ref); + }, + }} + singleSelection={true} + options={[ + ...(!onlyFavorites && searchTimelineValue === '' + ? getBasicSelectableOptions(timelineId == null ? '-1' : timelineId) + : []), + ...timelines.map( + (t, index) => + ({ + description: t.description, + favorite: !isEmpty(t.favorite), + label: t.title, + id: t.savedObjectId, + key: `${t.title}-${index}`, + title: t.title, + checked: t.savedObjectId === timelineId ? 'on' : undefined, + } as Option) + ), + ]} + > + {(list, search) => ( + <> + {search} + {favoritePortal} + {list} + + )} + + + )} + + + + ); +}; + +export const SearchTimelineSuperSelect = memo(SearchTimelineSuperSelectComponent); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/search_super_select/translations.ts b/x-pack/legacy/plugins/siem/public/components/timeline/search_super_select/translations.ts new file mode 100644 index 0000000000000..bffee407bc999 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/components/timeline/search_super_select/translations.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const DEFAULT_TIMELINE_TITLE = i18n.translate('xpack.siem.timeline.defaultTimelineTitle', { + defaultMessage: 'Default blank timeline', +}); + +export const DEFAULT_TIMELINE_DESCRIPTION = i18n.translate( + 'xpack.siem.timeline.defaultTimelineDescription', + { + defaultMessage: 'Timeline offered by default when creating new timeline.', + } +); + +export const SEARCH_BOX_TIMELINE_PLACEHOLDER = i18n.translate( + 'xpack.siem.timeline.searchBoxPlaceholder', + { + defaultMessage: 'e.g. timeline name or description', + } +); diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/styles.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/styles.tsx index db5d27626fc6d..b6fdc1b2973aa 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/styles.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/styles.tsx @@ -27,10 +27,10 @@ export const TimelineBodyGlobalStyle = createGlobalStyle` } `; -export const TimelineBody = styled.div.attrs(({ className }) => ({ +export const TimelineBody = styled.div.attrs(({ className = '' }) => ({ className: `siemTimeline__body ${className}`, }))<{ bodyHeight: number }>` - height: ${({ bodyHeight }) => bodyHeight + 'px'}; + height: ${({ bodyHeight }) => `${bodyHeight}px`}; overflow: auto; scrollbar-width: thin; @@ -56,15 +56,14 @@ TimelineBody.displayName = 'TimelineBody'; * EVENTS TABLE */ -export const EventsTable = styled.div.attrs(({ className }) => ({ +export const EventsTable = styled.div.attrs(({ className = '' }) => ({ className: `siemEventsTable ${className}`, role: 'table', }))``; -EventsTable.displayName = 'EventsTable'; /* EVENTS HEAD */ -export const EventsThead = styled.div.attrs(({ className }) => ({ +export const EventsThead = styled.div.attrs(({ className = '' }) => ({ className: `siemEventsTable__thead ${className}`, role: 'rowgroup', }))` @@ -75,7 +74,6 @@ export const EventsThead = styled.div.attrs(({ className }) => ({ top: 0; z-index: ${({ theme }) => theme.eui.euiZLevel1}; `; -EventsThead.displayName = 'EventsThead'; export const EventsTrHeader = styled.div.attrs(({ className }) => ({ className: `siemEventsTable__trHeader ${className}`, @@ -83,34 +81,36 @@ export const EventsTrHeader = styled.div.attrs(({ className }) => ({ }))` display: flex; `; -EventsTrHeader.displayName = 'EventsTrHeader'; -export const EventsThGroupActions = styled.div.attrs(({ className }) => ({ +export const EventsThGroupActions = styled.div.attrs(({ className = '' }) => ({ className: `siemEventsTable__thGroupActions ${className}`, }))<{ actionsColumnWidth: number; justifyContent: string }>` display: flex; - flex: 0 0 ${({ actionsColumnWidth }) => actionsColumnWidth + 'px'}; + flex: 0 0 ${({ actionsColumnWidth }) => `${actionsColumnWidth}px`}; justify-content: ${({ justifyContent }) => justifyContent}; min-width: 0; `; -EventsThGroupActions.displayName = 'EventsThGroupActions'; -export const EventsThGroupData = styled.div.attrs(({ className }) => ({ +export const EventsThGroupData = styled.div.attrs(({ className = '' }) => ({ className: `siemEventsTable__thGroupData ${className}`, -}))` +}))<{ isDragging?: boolean }>` display: flex; + + > div:hover .siemEventsHeading__handle { + display: ${({ isDragging }) => (isDragging ? 'none' : 'block')}; + opacity: 1; + visibility: visible; + } `; -EventsThGroupData.displayName = 'EventsThGroupData'; -export const EventsTh = styled.div.attrs(({ className }) => ({ +export const EventsTh = styled.div.attrs(({ className = '' }) => ({ className: `siemEventsTable__th ${className}`, role: 'columnheader', -}))<{ isDragging?: boolean; position?: string }>` +}))` align-items: center; display: flex; flex-shrink: 0; min-width: 0; - position: ${({ position }) => position}; .siemEventsTable__thGroupActions &:first-child:last-child { flex: 1; @@ -121,10 +121,18 @@ export const EventsTh = styled.div.attrs(({ className }) => ({ cursor: move; /* Fallback for IE11 */ cursor: grab; } + + > div:focus { + outline: 0; /* disable focus on Resizable element */ + } + + /* don't display Draggable placeholder */ + [data-rbd-placeholder-context-id] { + display: none !important; + } `; -EventsTh.displayName = 'EventsTh'; -export const EventsThContent = styled.div.attrs(({ className }) => ({ +export const EventsThContent = styled.div.attrs(({ className = '' }) => ({ className: `siemEventsTable__thContent ${className}`, }))<{ textAlign?: string }>` font-size: ${({ theme }) => theme.eui.euiFontSizeXS}; @@ -135,19 +143,17 @@ export const EventsThContent = styled.div.attrs(({ className }) => ({ text-align: ${({ textAlign }) => textAlign}; width: 100%; /* Using width: 100% instead of flex: 1 and max-width: 100% for IE11 */ `; -EventsThContent.displayName = 'EventsThContent'; /* EVENTS BODY */ -export const EventsTbody = styled.div.attrs(({ className }) => ({ +export const EventsTbody = styled.div.attrs(({ className = '' }) => ({ className: `siemEventsTable__tbody ${className}`, role: 'rowgroup', }))` overflow-x: hidden; `; -EventsTbody.displayName = 'EventsTbody'; -export const EventsTrGroup = styled.div.attrs(({ className }) => ({ +export const EventsTrGroup = styled.div.attrs(({ className = '' }) => ({ className: `siemEventsTable__trGroup ${className}`, }))<{ className?: string }>` border-bottom: ${({ theme }) => theme.eui.euiBorderWidthThin} solid @@ -157,17 +163,15 @@ export const EventsTrGroup = styled.div.attrs(({ className }) => ({ background-color: ${({ theme }) => theme.eui.euiTableHoverColor}; } `; -EventsTrGroup.displayName = 'EventsTrGroup'; -export const EventsTrData = styled.div.attrs(({ className }) => ({ +export const EventsTrData = styled.div.attrs(({ className = '' }) => ({ className: `siemEventsTable__trData ${className}`, role: 'row', }))` display: flex; `; -EventsTrData.displayName = 'EventsTrData'; -export const EventsTrSupplement = styled.div.attrs(({ className }) => ({ +export const EventsTrSupplement = styled.div.attrs(({ className = '' }) => ({ className: `siemEventsTable__trSupplement ${className}`, }))<{ className: string }>` font-size: ${({ theme }) => theme.eui.euiFontSizeXS}; @@ -175,26 +179,23 @@ export const EventsTrSupplement = styled.div.attrs(({ className }) => ({ padding: 0 ${({ theme }) => theme.eui.paddingSizes.xs} 0 ${({ theme }) => theme.eui.paddingSizes.xl}; `; -EventsTrSupplement.displayName = 'EventsTrSupplement'; -export const EventsTdGroupActions = styled.div.attrs(({ className }) => ({ +export const EventsTdGroupActions = styled.div.attrs(({ className = '' }) => ({ className: `siemEventsTable__tdGroupActions ${className}`, }))<{ actionsColumnWidth: number }>` display: flex; justify-content: space-between; - flex: 0 0 ${({ actionsColumnWidth }) => actionsColumnWidth + 'px'}; + flex: 0 0 ${({ actionsColumnWidth }) => `${actionsColumnWidth}px`}; min-width: 0; `; -EventsTdGroupActions.displayName = 'EventsTdGroupActions'; -export const EventsTdGroupData = styled.div.attrs(({ className }) => ({ +export const EventsTdGroupData = styled.div.attrs(({ className = '' }) => ({ className: `siemEventsTable__tdGroupData ${className}`, }))` display: flex; `; -EventsTdGroupData.displayName = 'EventsTdGroupData'; -export const EventsTd = styled.div.attrs(({ className }) => ({ +export const EventsTd = styled.div.attrs(({ className = '' }) => ({ className: `siemEventsTable__td ${className}`, role: 'cell', }))` @@ -207,7 +208,6 @@ export const EventsTd = styled.div.attrs(({ className }) => ({ flex: 1; } `; -EventsTd.displayName = 'EventsTd'; export const EventsTdContent = styled.div.attrs(({ className }) => ({ className: `siemEventsTable__tdContent ${className}`, @@ -219,13 +219,12 @@ export const EventsTdContent = styled.div.attrs(({ className }) => ({ text-align: ${({ textAlign }) => textAlign}; width: 100%; /* Using width: 100% instead of flex: 1 and max-width: 100% for IE11 */ `; -EventsTdContent.displayName = 'EventsTdContent'; /** * EVENTS HEADING */ -export const EventsHeading = styled.div.attrs(({ className }) => ({ +export const EventsHeading = styled.div.attrs(({ className = '' }) => ({ className: `siemEventsHeading ${className}`, }))<{ isLoading: boolean }>` align-items: center; @@ -235,9 +234,8 @@ export const EventsHeading = styled.div.attrs(({ className }) => ({ cursor: ${({ isLoading }) => (isLoading ? 'wait' : 'grab')}; } `; -EventsHeading.displayName = 'EventsHeading'; -export const EventsHeadingTitleButton = styled.button.attrs(({ className }) => ({ +export const EventsHeadingTitleButton = styled.button.attrs(({ className = '' }) => ({ className: `siemEventsHeading__title siemEventsHeading__title--aggregatable ${className}`, type: 'button', }))` @@ -260,16 +258,14 @@ export const EventsHeadingTitleButton = styled.button.attrs(({ className }) => ( margin-left: ${({ theme }) => theme.eui.euiSizeXS}; } `; -EventsHeadingTitleButton.displayName = 'EventsHeadingTitleButton'; export const EventsHeadingTitleSpan = styled.span.attrs(({ className }) => ({ className: `siemEventsHeading__title siemEventsHeading__title--notAggregatable ${className}`, }))` min-width: 0; `; -EventsHeadingTitleSpan.displayName = 'EventsHeadingTitleSpan'; -export const EventsHeadingExtra = styled.div.attrs(({ className }) => ({ +export const EventsHeadingExtra = styled.div.attrs(({ className = '' }) => ({ className: `siemEventsHeading__extra ${className}`, }))` margin-left: auto; @@ -285,9 +281,8 @@ export const EventsHeadingExtra = styled.div.attrs(({ className }) => ({ } } `; -EventsHeadingExtra.displayName = 'EventsHeadingExtra'; -export const EventsHeadingHandle = styled.div.attrs(({ className }) => ({ +export const EventsHeadingHandle = styled.div.attrs(({ className = '' }) => ({ className: `siemEventsHeading__handle ${className}`, }))` background-color: ${({ theme }) => theme.eui.euiBorderColor}; @@ -297,17 +292,11 @@ export const EventsHeadingHandle = styled.div.attrs(({ className }) => ({ visibility: hidden; width: ${({ theme }) => theme.eui.euiBorderWidthThick}; - .siemEventsTable__thead:hover & { - opacity: 1; - visibility: visible; - } - &:hover { background-color: ${({ theme }) => theme.eui.euiColorPrimary}; cursor: col-resize; } `; -EventsHeadingHandle.displayName = 'EventsHeadingHandle'; /** * EVENTS LOADING diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/timeline.test.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/timeline.test.tsx index bb500de239da7..2971053bc5252 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/timeline.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/timeline.test.tsx @@ -5,8 +5,7 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { MockedProvider } from 'react-apollo/test-utils'; import { timelineQuery } from '../../containers/timeline/index.gql_query'; @@ -78,7 +77,7 @@ describe('Timeline', () => { toggleColumn={jest.fn()} /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it renders the timeline header', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/timeline.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/timeline.tsx index 5646b26428bf8..e15c58d32425a 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/timeline.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/timeline.tsx @@ -6,7 +6,7 @@ import { EuiFlexGroup } from '@elastic/eui'; import { getOr, isEmpty } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { BrowserFields } from '../../containers/source'; diff --git a/x-pack/legacy/plugins/siem/public/components/timeline/timeline_context.tsx b/x-pack/legacy/plugins/siem/public/components/timeline/timeline_context.tsx index d3251e1d331e3..611452cc7ccd1 100644 --- a/x-pack/legacy/plugins/siem/public/components/timeline/timeline_context.tsx +++ b/x-pack/legacy/plugins/siem/public/components/timeline/timeline_context.tsx @@ -45,30 +45,35 @@ interface ManageTimelineContextProps { // todo we need to refactor this as more complex context/reducer with useReducer // to avoid so many Context, at least the separation of code is there now -export const ManageTimelineContext = memo( - ({ children, loading, width, type = initTimelineType }) => { - const [myLoading, setLoading] = useState(initTimelineContext); - const [myWidth, setWidth] = useState(initTimelineWidth); - const [myType, setType] = useState(initTimelineType); +const ManageTimelineContextComponent: React.FC = ({ + children, + loading, + width, + type = initTimelineType, +}) => { + const [myLoading, setLoading] = useState(initTimelineContext); + const [myWidth, setWidth] = useState(initTimelineWidth); + const [myType, setType] = useState(initTimelineType); - useEffect(() => { - setLoading(loading); - }, [loading]); + useEffect(() => { + setLoading(loading); + }, [loading]); - useEffect(() => { - setType(type); - }, [type]); + useEffect(() => { + setType(type); + }, [type]); - useEffect(() => { - setWidth(width); - }, [width]); + useEffect(() => { + setWidth(width); + }, [width]); - return ( - - - {children} - - - ); - } -); + return ( + + + {children} + + + ); +}; + +export const ManageTimelineContext = memo(ManageTimelineContextComponent); diff --git a/x-pack/legacy/plugins/siem/public/components/toasters/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/toasters/index.test.tsx index 5ef5a5ab31d4b..9338eb9f0fabd 100644 --- a/x-pack/legacy/plugins/siem/public/components/toasters/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/toasters/index.test.tsx @@ -8,7 +8,20 @@ import { cloneDeep, set } from 'lodash/fp'; import { mount } from 'enzyme'; import React, { useEffect } from 'react'; -import { AppToast, useStateToaster, ManageGlobalToaster, GlobalToaster } from '.'; +import { + AppToast, + useStateToaster, + ManageGlobalToaster, + GlobalToaster, + displayErrorToast, +} from '.'; + +jest.mock('uuid', () => { + return { + v1: jest.fn(() => '27261ae0-0bbb-11ea-b0ea-db767b07ea47'), + v4: jest.fn(() => '9e1f72a9-7c73-4b7f-a562-09940f7daf4a'), + }; +}); const mockToast: AppToast = { color: 'danger', @@ -270,4 +283,22 @@ describe('Toaster', () => { expect(wrapper.find('.euiToastHeader__title').text()).toBe('Test & Test'); }); }); + + describe('displayErrorToast', () => { + test('dispatches toast with correct title and message', () => { + const mockErrorToast = { + toast: { + color: 'danger', + errors: ['message'], + iconType: 'alert', + id: '9e1f72a9-7c73-4b7f-a562-09940f7daf4a', + title: 'Title', + }, + type: 'addToaster', + }; + const dispatchToasterMock = jest.fn(); + displayErrorToast('Title', ['message'], dispatchToasterMock); + expect(dispatchToasterMock.mock.calls[0][0]).toEqual(mockErrorToast); + }); + }); }); diff --git a/x-pack/legacy/plugins/siem/public/components/toasters/index.tsx b/x-pack/legacy/plugins/siem/public/components/toasters/index.tsx index 27d59d429913c..7098e618aeb55 100644 --- a/x-pack/legacy/plugins/siem/public/components/toasters/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/toasters/index.tsx @@ -8,6 +8,7 @@ import { EuiGlobalToastList, EuiGlobalToastListToast as Toast, EuiButton } from import { noop } from 'lodash/fp'; import React, { createContext, Dispatch, useReducer, useContext, useState } from 'react'; import styled from 'styled-components'; +import uuid from 'uuid'; import { ModalAllErrors } from './modal_all_errors'; import * as i18n from './translations'; @@ -122,3 +123,28 @@ const ErrorToastContainer = styled.div` `; ErrorToastContainer.displayName = 'ErrorToastContainer'; + +/** + * Displays an error toast for the provided title and message + * + * @param errorTitle Title of error to display in toaster and modal + * @param errorMessages Message to display in error modal when clicked + * @param dispatchToaster provided by useStateToaster() + */ +export const displayErrorToast = ( + errorTitle: string, + errorMessages: string[], + dispatchToaster: React.Dispatch +) => { + const toast: AppToast = { + id: uuid.v4(), + title: errorTitle, + color: 'danger', + iconType: 'alert', + errors: errorMessages, + }; + dispatchToaster({ + type: 'addToaster', + toast, + }); +}; diff --git a/x-pack/legacy/plugins/siem/public/components/toasters/modal_all_errors.test.tsx b/x-pack/legacy/plugins/siem/public/components/toasters/modal_all_errors.test.tsx index 3a7774298f6af..bfca035e891e8 100644 --- a/x-pack/legacy/plugins/siem/public/components/toasters/modal_all_errors.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/toasters/modal_all_errors.test.tsx @@ -5,9 +5,8 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { ModalAllErrors } from './modal_all_errors'; import { AppToast } from '.'; @@ -30,7 +29,7 @@ describe('Modal all errors', () => { const wrapper = shallow( ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it renders null when isShowing is negative', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/truncatable_text/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/truncatable_text/index.test.tsx index 8c5a08fdf5e21..92868b23a3ccd 100644 --- a/x-pack/legacy/plugins/siem/public/components/truncatable_text/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/truncatable_text/index.test.tsx @@ -5,15 +5,14 @@ */ import { mount, shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { TruncatableText } from '.'; describe('TruncatableText', () => { test('renders correctly against snapshot', () => { const wrapper = shallow({'Hiding in plain sight'}); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it adds the hidden overflow style', () => { diff --git a/x-pack/legacy/plugins/siem/public/components/url_state/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/url_state/index.test.tsx index 63412302fedfb..67823bea9e170 100644 --- a/x-pack/legacy/plugins/siem/public/components/url_state/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/url_state/index.test.tsx @@ -5,7 +5,7 @@ */ import { mount } from 'enzyme'; -import * as React from 'react'; +import React from 'react'; import { HookWrapper } from '../../mock'; import { SiemPageName } from '../../pages/home/types'; diff --git a/x-pack/legacy/plugins/siem/public/components/url_state/index.tsx b/x-pack/legacy/plugins/siem/public/components/url_state/index.tsx index a7e7729de2e27..e656ec3496d8d 100644 --- a/x-pack/legacy/plugins/siem/public/components/url_state/index.tsx +++ b/x-pack/legacy/plugins/siem/public/components/url_state/index.tsx @@ -19,16 +19,12 @@ import { dispatchUpdateTimeline } from '../open_timeline/helpers'; import { dispatchSetInitialStateFromUrl } from './initialize_redux_by_url'; import { makeMapStateToProps } from './helpers'; -export const UrlStateContainer = React.memo( - (props: UrlStateContainerPropTypes) => { - useUrlStateHooks(props); - return null; - }, - (prevProps, nextProps) => - prevProps.pathName === nextProps.pathName && isEqual(prevProps.urlState, nextProps.urlState) -); - -UrlStateContainer.displayName = 'UrlStateContainer'; +export const UrlStateContainer: React.FC = ( + props: UrlStateContainerPropTypes +) => { + useUrlStateHooks(props); + return null; +}; const mapDispatchToProps = (dispatch: Dispatch) => ({ setInitialStateFromUrl: dispatchSetInitialStateFromUrl(dispatch), @@ -39,13 +35,21 @@ const mapDispatchToProps = (dispatch: Dispatch) => ({ export const UrlStateRedux = compose>( connect(makeMapStateToProps, mapDispatchToProps) -)(UrlStateContainer); +)( + React.memo( + UrlStateContainer, + (prevProps, nextProps) => + prevProps.pathName === nextProps.pathName && isEqual(prevProps.urlState, nextProps.urlState) + ) +); -export const UseUrlState = React.memo(props => { +const UseUrlStateComponent: React.FC = props => { const [routeProps] = useRouteSpy(); const urlStateReduxProps: RouteSpyState & UrlStateProps = { ...routeProps, ...props, }; return ; -}); +}; + +export const UseUrlState = React.memo(UseUrlStateComponent); diff --git a/x-pack/legacy/plugins/siem/public/components/url_state/index_mocked.test.tsx b/x-pack/legacy/plugins/siem/public/components/url_state/index_mocked.test.tsx index 705b2106be315..f673b77ea13c5 100644 --- a/x-pack/legacy/plugins/siem/public/components/url_state/index_mocked.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/url_state/index_mocked.test.tsx @@ -5,7 +5,7 @@ */ import { mount } from 'enzyme'; -import * as React from 'react'; +import React from 'react'; import { HookWrapper } from '../../mock/hook_wrapper'; import { SiemPageName } from '../../pages/home/types'; diff --git a/x-pack/legacy/plugins/siem/public/components/wrapper_page/index.test.tsx b/x-pack/legacy/plugins/siem/public/components/wrapper_page/index.test.tsx index 5d73e9bcf8e71..788ea14f4bd22 100644 --- a/x-pack/legacy/plugins/siem/public/components/wrapper_page/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/components/wrapper_page/index.test.tsx @@ -5,7 +5,6 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import React from 'react'; import { TestProviders } from '../../mock'; @@ -21,7 +20,7 @@ describe('WrapperPage', () => { ); - expect(toJson(wrapper.find('WrapperPage'))).toMatchSnapshot(); + expect(wrapper.find('WrapperPage')).toMatchSnapshot(); }); describe('restrict width', () => { @@ -34,7 +33,7 @@ describe('WrapperPage', () => { ); - expect(toJson(wrapper.find('WrapperPage'))).toMatchSnapshot(); + expect(wrapper.find('WrapperPage')).toMatchSnapshot(); }); test('custom max width when restrictWidth is number', () => { @@ -46,7 +45,7 @@ describe('WrapperPage', () => { ); - expect(toJson(wrapper.find('WrapperPage'))).toMatchSnapshot(); + expect(wrapper.find('WrapperPage')).toMatchSnapshot(); }); test('custom max width when restrictWidth is string', () => { @@ -58,7 +57,7 @@ describe('WrapperPage', () => { ); - expect(toJson(wrapper.find('WrapperPage'))).toMatchSnapshot(); + expect(wrapper.find('WrapperPage')).toMatchSnapshot(); }); }); }); diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/api.ts b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/api.ts index a3ee878e305b4..8f8b66ae35a3b 100644 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/api.ts +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/api.ts @@ -15,25 +15,28 @@ import { NewRule, Rule, FetchRuleProps, + BasicFetchProps, + RuleError, } from './types'; import { throwIfNotOk } from '../../../hooks/api/api'; -import { DETECTION_ENGINE_RULES_URL } from '../../../../common/constants'; +import { + DETECTION_ENGINE_RULES_URL, + DETECTION_ENGINE_PREPACKAGED_URL, +} from '../../../../common/constants'; /** * Add provided Rule * * @param rule to add - * @param kbnVersion current Kibana Version to use for headers * @param signal to cancel request */ -export const addRule = async ({ rule, kbnVersion, signal }: AddRulesProps): Promise => { +export const addRule = async ({ rule, signal }: AddRulesProps): Promise => { const response = await fetch(`${chrome.getBasePath()}${DETECTION_ENGINE_RULES_URL}`, { method: rule.id != null ? 'PUT' : 'POST', credentials: 'same-origin', headers: { 'content-type': 'application/json', - 'kbn-version': kbnVersion, - 'kbn-xsrf': kbnVersion, + 'kbn-xsrf': 'true', }, body: JSON.stringify(rule), signal, @@ -49,7 +52,6 @@ export const addRule = async ({ rule, kbnVersion, signal }: AddRulesProps): Prom * @param filterOptions desired filters (e.g. filter/sortField/sortOrder) * @param pagination desired pagination options (e.g. page/perPage) * @param id if specified, will return specific rule if exists - * @param kbnVersion current Kibana Version to use for headers * @param signal to cancel request */ export const fetchRules = async ({ @@ -64,7 +66,6 @@ export const fetchRules = async ({ total: 0, }, id, - kbnVersion, signal, }: FetchRulesProps): Promise => { const queryParams = [ @@ -101,16 +102,14 @@ export const fetchRules = async ({ * Fetch a Rule by providing a Rule ID * * @param id Rule ID's (not rule_id) - * @param kbnVersion current Kibana Version to use for headers */ -export const fetchRuleById = async ({ id, kbnVersion, signal }: FetchRuleProps): Promise => { +export const fetchRuleById = async ({ id, signal }: FetchRuleProps): Promise => { const response = await fetch(`${chrome.getBasePath()}${DETECTION_ENGINE_RULES_URL}?id=${id}`, { method: 'GET', credentials: 'same-origin', headers: { 'content-type': 'application/json', - 'kbn-version': kbnVersion, - 'kbn-xsrf': kbnVersion, + 'kbn-xsrf': 'true', }, signal, }); @@ -124,78 +123,65 @@ export const fetchRuleById = async ({ id, kbnVersion, signal }: FetchRuleProps): * * @param ids array of Rule ID's (not rule_id) to enable/disable * @param enabled to enable or disable - * @param kbnVersion current Kibana Version to use for headers + * + * @throws An error if response is not OK */ -export const enableRules = async ({ - ids, - enabled, - kbnVersion, -}: EnableRulesProps): Promise => { - const requests = ids.map(id => - fetch(`${chrome.getBasePath()}${DETECTION_ENGINE_RULES_URL}`, { +export const enableRules = async ({ ids, enabled }: EnableRulesProps): Promise => { + const response = await fetch( + `${chrome.getBasePath()}${DETECTION_ENGINE_RULES_URL}/_bulk_update`, + { method: 'PUT', credentials: 'same-origin', headers: { 'content-type': 'application/json', - 'kbn-version': kbnVersion, - 'kbn-xsrf': kbnVersion, + 'kbn-xsrf': 'true', }, - body: JSON.stringify({ id, enabled }), - }) + body: JSON.stringify(ids.map(id => ({ id, enabled }))), + } ); - const responses = await Promise.all(requests); - await responses.map(response => throwIfNotOk(response)); - return Promise.all( - responses.map>(response => response.json()) - ); + await throwIfNotOk(response); + return response.json(); }; /** * Deletes provided Rule ID's * * @param ids array of Rule ID's (not rule_id) to delete - * @param kbnVersion current Kibana Version to use for headers + * + * @throws An error if response is not OK */ -export const deleteRules = async ({ ids, kbnVersion }: DeleteRulesProps): Promise => { - // TODO: Don't delete if immutable! - const requests = ids.map(id => - fetch(`${chrome.getBasePath()}${DETECTION_ENGINE_RULES_URL}?id=${id}`, { +export const deleteRules = async ({ ids }: DeleteRulesProps): Promise> => { + const response = await fetch( + `${chrome.getBasePath()}${DETECTION_ENGINE_RULES_URL}/_bulk_delete`, + { method: 'DELETE', credentials: 'same-origin', headers: { 'content-type': 'application/json', - 'kbn-version': kbnVersion, - 'kbn-xsrf': kbnVersion, + 'kbn-xsrf': 'true', }, - }) + body: JSON.stringify(ids.map(id => ({ id }))), + } ); - const responses = await Promise.all(requests); - await responses.map(response => throwIfNotOk(response)); - return Promise.all( - responses.map>(response => response.json()) - ); + await throwIfNotOk(response); + return response.json(); }; /** * Duplicates provided Rules * * @param rule to duplicate - * @param kbnVersion current Kibana Version to use for headers */ -export const duplicateRules = async ({ - rules, - kbnVersion, -}: DuplicateRulesProps): Promise => { +export const duplicateRules = async ({ rules }: DuplicateRulesProps): Promise => { const requests = rules.map(rule => fetch(`${chrome.getBasePath()}${DETECTION_ENGINE_RULES_URL}`, { method: 'POST', credentials: 'same-origin', headers: { 'content-type': 'application/json', - 'kbn-version': kbnVersion, - 'kbn-xsrf': kbnVersion, + 'kbn-xsrf': 'true', }, body: JSON.stringify({ ...rule, @@ -218,3 +204,22 @@ export const duplicateRules = async ({ responses.map>(response => response.json()) ); }; + +/** + * Create Prepackaged Rules + * + * @param signal AbortSignal for cancelling request + */ +export const createPrepackagedRules = async ({ signal }: BasicFetchProps): Promise => { + const response = await fetch(`${chrome.getBasePath()}${DETECTION_ENGINE_PREPACKAGED_URL}`, { + method: 'PUT', + credentials: 'same-origin', + headers: { + 'content-type': 'application/json', + 'kbn-xsrf': 'true', + }, + signal, + }); + await throwIfNotOk(response); + return true; +}; diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/persist_rule.tsx b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/persist_rule.tsx index 82490991236de..ea03c34ec31ba 100644 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/persist_rule.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/persist_rule.tsx @@ -6,8 +6,6 @@ import { useEffect, useState, Dispatch } from 'react'; -import { useUiSetting$ } from '../../../lib/kibana'; -import { DEFAULT_KBN_VERSION } from '../../../../common/constants'; import { useStateToaster } from '../../../components/toasters'; import { errorToToaster } from '../../../components/ml/api/error_to_toaster'; @@ -26,7 +24,6 @@ export const usePersistRule = (): Return => { const [rule, setRule] = useState(null); const [isSaved, setIsSaved] = useState(false); const [isLoading, setIsLoading] = useState(false); - const [kbnVersion] = useUiSetting$(DEFAULT_KBN_VERSION); const [, dispatchToaster] = useStateToaster(); useEffect(() => { @@ -37,7 +34,7 @@ export const usePersistRule = (): Return => { if (rule != null) { try { setIsLoading(true); - await persistRule({ rule, kbnVersion, signal: abortCtrl.signal }); + await persistRule({ rule, signal: abortCtrl.signal }); if (isSubscribed) { setIsSaved(true); diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/types.ts b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/types.ts index 0885e541cead5..147b04567f6c7 100644 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/types.ts +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/types.ts @@ -41,7 +41,6 @@ export type NewRule = t.TypeOf; export interface AddRulesProps { rule: NewRule; - kbnVersion: string; signal: AbortSignal; } @@ -71,15 +70,19 @@ export const RuleSchema = t.intersection([ risk_score: t.number, rule_id: t.string, severity: t.string, - type: t.string, tags: t.array(t.string), + type: t.string, to: t.string, threats: t.array(t.unknown), updated_at: t.string, updated_by: t.string, }), t.partial({ + output_index: t.string, saved_id: t.string, + timeline_id: t.string, + timeline_title: t.string, + version: t.number, }), ]); @@ -88,6 +91,16 @@ export const RulesSchema = t.array(RuleSchema); export type Rule = t.TypeOf; export type Rules = t.TypeOf; +export interface RuleError { + rule_id: string; + error: { status_code: number; message: string }; +} + +export interface RuleResponseBuckets { + rules: Rule[]; + errors: RuleError[]; +} + export interface PaginationOptions { page: number; perPage: number; @@ -98,7 +111,6 @@ export interface FetchRulesProps { pagination?: PaginationOptions; filterOptions?: FilterOptions; id?: string; - kbnVersion: string; signal: AbortSignal; } @@ -117,22 +129,22 @@ export interface FetchRulesResponse { export interface FetchRuleProps { id: string; - kbnVersion: string; signal: AbortSignal; } export interface EnableRulesProps { ids: string[]; enabled: boolean; - kbnVersion: string; } export interface DeleteRulesProps { ids: string[]; - kbnVersion: string; } export interface DuplicateRulesProps { rules: Rules; - kbnVersion: string; +} + +export interface BasicFetchProps { + signal: AbortSignal; } diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_rule.tsx b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_rule.tsx index ad0b87385ee79..22ba86cd09f74 100644 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_rule.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_rule.tsx @@ -6,8 +6,6 @@ import { useEffect, useState } from 'react'; -import { useUiSetting$ } from '../../../lib/kibana'; -import { DEFAULT_KBN_VERSION } from '../../../../common/constants'; import { useStateToaster } from '../../../components/toasters'; import { errorToToaster } from '../../../components/ml/api/error_to_toaster'; import { fetchRuleById } from './api'; @@ -25,7 +23,6 @@ type Return = [boolean, Rule | null]; export const useRule = (id: string | undefined): Return => { const [rule, setRule] = useState(null); const [loading, setLoading] = useState(true); - const [kbnVersion] = useUiSetting$(DEFAULT_KBN_VERSION); const [, dispatchToaster] = useStateToaster(); useEffect(() => { @@ -37,7 +34,6 @@ export const useRule = (id: string | undefined): Return => { setLoading(true); const ruleResponse = await fetchRuleById({ id: idToFetch, - kbnVersion, signal: abortCtrl.signal, }); diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_rules.tsx b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_rules.tsx index 66285c804aa28..b49dd8d51d4f7 100644 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_rules.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/rules/use_rules.tsx @@ -6,8 +6,6 @@ import { useEffect, useState } from 'react'; -import { useUiSetting$ } from '../../../lib/kibana'; -import { DEFAULT_KBN_VERSION } from '../../../../common/constants'; import { FetchRulesResponse, FilterOptions, PaginationOptions } from './types'; import { useStateToaster } from '../../../components/toasters'; import { fetchRules } from './api'; @@ -35,7 +33,6 @@ export const useRules = ( data: [], }); const [loading, setLoading] = useState(true); - const [kbnVersion] = useUiSetting$(DEFAULT_KBN_VERSION); const [, dispatchToaster] = useStateToaster(); useEffect(() => { @@ -48,7 +45,6 @@ export const useRules = ( const fetchRulesResult = await fetchRules({ filterOptions, pagination, - kbnVersion, signal: abortCtrl.signal, }); diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/api.ts b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/api.ts index f6274db4a9dae..8754d73637e7c 100644 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/api.ts +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/api.ts @@ -10,18 +10,29 @@ import { throwIfNotOk } from '../../../hooks/api/api'; import { DETECTION_ENGINE_QUERY_SIGNALS_URL, DETECTION_ENGINE_SIGNALS_STATUS_URL, + DETECTION_ENGINE_INDEX_URL, + DETECTION_ENGINE_PRIVILEGES_URL, } from '../../../../common/constants'; -import { QuerySignals, SignalSearchResponse, UpdateSignalStatusProps } from './types'; +import { + QuerySignals, + SignalSearchResponse, + UpdateSignalStatusProps, + SignalsIndex, + SignalIndexError, + Privilege, + PostSignalError, + BasicSignals, +} from './types'; +import { parseJsonFromBody } from '../../../utils/api'; /** * Fetch Signals by providing a query * * @param query String to match a dsl - * @param kbnVersion current Kibana Version to use for headers + * @param signal AbortSignal for cancelling request */ export const fetchQuerySignals = async ({ query, - kbnVersion, signal, }: QuerySignals): Promise> => { const response = await fetch(`${chrome.getBasePath()}${DETECTION_ENGINE_QUERY_SIGNALS_URL}`, { @@ -29,10 +40,9 @@ export const fetchQuerySignals = async ({ credentials: 'same-origin', headers: { 'content-type': 'application/json', - 'kbn-version': kbnVersion, - 'kbn-xsrf': kbnVersion, + 'kbn-xsrf': 'true', }, - body: query, + body: JSON.stringify(query), signal, }); await throwIfNotOk(response); @@ -45,13 +55,11 @@ export const fetchQuerySignals = async ({ * * @param query of signals to update * @param status to update to('open' / 'closed') - * @param kbnVersion current Kibana Version to use for headers - * @param signal to cancel request + * @param signal AbortSignal for cancelling request */ export const updateSignalStatus = async ({ query, status, - kbnVersion, signal, }: UpdateSignalStatusProps): Promise => { const response = await fetch(`${chrome.getBasePath()}${DETECTION_ENGINE_SIGNALS_STATUS_URL}`, { @@ -59,8 +67,7 @@ export const updateSignalStatus = async ({ credentials: 'same-origin', headers: { 'content-type': 'application/json', - 'kbn-version': kbnVersion, - 'kbn-xsrf': kbnVersion, + 'kbn-xsrf': 'true', }, body: JSON.stringify({ status, ...query }), signal, @@ -69,3 +76,75 @@ export const updateSignalStatus = async ({ await throwIfNotOk(response); return response.json(); }; + +/** + * Fetch Signal Index + * + * @param signal AbortSignal for cancelling request + */ +export const getSignalIndex = async ({ signal }: BasicSignals): Promise => { + const response = await fetch(`${chrome.getBasePath()}${DETECTION_ENGINE_INDEX_URL}`, { + method: 'GET', + credentials: 'same-origin', + headers: { + 'content-type': 'application/json', + 'kbn-xsrf': 'true', + }, + signal, + }); + if (response.ok) { + const signalIndex = await response.json(); + return signalIndex; + } + const error = await parseJsonFromBody(response); + if (error != null) { + throw new SignalIndexError(error); + } + return null; +}; + +/** + * Get User Privileges + * + * @param signal AbortSignal for cancelling request + */ +export const getUserPrivilege = async ({ signal }: BasicSignals): Promise => { + const response = await fetch(`${chrome.getBasePath()}${DETECTION_ENGINE_PRIVILEGES_URL}`, { + method: 'GET', + credentials: 'same-origin', + headers: { + 'content-type': 'application/json', + 'kbn-xsrf': 'true', + }, + signal, + }); + + await throwIfNotOk(response); + return response.json(); +}; + +/** + * Create Signal Index if needed it + * + * @param signal AbortSignal for cancelling request + */ +export const createSignalIndex = async ({ signal }: BasicSignals): Promise => { + const response = await fetch(`${chrome.getBasePath()}${DETECTION_ENGINE_INDEX_URL}`, { + method: 'POST', + credentials: 'same-origin', + headers: { + 'content-type': 'application/json', + 'kbn-xsrf': 'true', + }, + signal, + }); + if (response.ok) { + const signalIndex = await response.json(); + return signalIndex; + } + const error = await parseJsonFromBody(response); + if (error != null) { + throw new PostSignalError(error); + } + return null; +}; diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/errors_types/get_index_error.ts b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/errors_types/get_index_error.ts new file mode 100644 index 0000000000000..e4f2a658b7362 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/errors_types/get_index_error.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { MessageBody } from '../../../../components/ml/api/throw_if_not_ok'; + +export class SignalIndexError extends Error { + message: string = ''; + statusCode: number = -1; + error: string = ''; + + constructor(errObj: MessageBody) { + super(errObj.message); + this.message = errObj.message ?? ''; + this.statusCode = errObj.statusCode ?? -1; + this.error = errObj.error ?? ''; + this.name = 'SignalIndexError'; + + // Set the prototype explicitly. + Object.setPrototypeOf(this, SignalIndexError.prototype); + } +} diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/errors_types/index.ts b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/errors_types/index.ts new file mode 100644 index 0000000000000..4ce8e6ba89183 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/errors_types/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export * from './get_index_error'; +export * from './post_index_error'; +export * from './privilege_user_error'; diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/errors_types/post_index_error.ts b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/errors_types/post_index_error.ts new file mode 100644 index 0000000000000..d6d8cccfb4540 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/errors_types/post_index_error.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { MessageBody } from '../../../../components/ml/api/throw_if_not_ok'; + +export class PostSignalError extends Error { + message: string = ''; + statusCode: number = -1; + error: string = ''; + + constructor(errObj: MessageBody) { + super(errObj.message); + this.message = errObj.message ?? ''; + this.statusCode = errObj.statusCode ?? -1; + this.error = errObj.error ?? ''; + this.name = 'PostSignalError'; + + // Set the prototype explicitly. + Object.setPrototypeOf(this, PostSignalError.prototype); + } +} diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/errors_types/privilege_user_error.ts b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/errors_types/privilege_user_error.ts new file mode 100644 index 0000000000000..5cd458a7fe9aa --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/errors_types/privilege_user_error.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { MessageBody } from '../../../../components/ml/api/throw_if_not_ok'; + +export class PrivilegeUserError extends Error { + message: string = ''; + statusCode: number = -1; + error: string = ''; + + constructor(errObj: MessageBody) { + super(errObj.message); + this.message = errObj.message ?? ''; + this.statusCode = errObj.statusCode ?? -1; + this.error = errObj.error ?? ''; + this.name = 'PrivilegeUserError'; + + // Set the prototype explicitly. + Object.setPrototypeOf(this, PrivilegeUserError.prototype); + } +} diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/translations.ts b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/translations.ts index 5b5dc9e9699fe..2b8f54e5438df 100644 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/translations.ts +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/translations.ts @@ -12,3 +12,24 @@ export const SIGNAL_FETCH_FAILURE = i18n.translate( defaultMessage: 'Failed to query signals', } ); + +export const PRIVILEGE_FETCH_FAILURE = i18n.translate( + 'xpack.siem.containers.detectionEngine.signals.errorFetchingSignalsDescription', + { + defaultMessage: 'Failed to query signals', + } +); + +export const SIGNAL_GET_NAME_FAILURE = i18n.translate( + 'xpack.siem.containers.detectionEngine.signals.errorGetSignalDescription', + { + defaultMessage: 'Failed to get signal index name', + } +); + +export const SIGNAL_POST_FAILURE = i18n.translate( + 'xpack.siem.containers.detectionEngine.signals.errorPostSignalDescription', + { + defaultMessage: 'Failed to create signal index', + } +); diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/types.ts b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/types.ts index 5846f3275c0fd..34cb7684a0399 100644 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/types.ts +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/types.ts @@ -4,18 +4,22 @@ * you may not use this file except in compliance with the Elastic License. */ -export interface QuerySignals { - query: string; - kbnVersion: string; +export * from './errors_types'; + +export interface BasicSignals { signal: AbortSignal; } +export interface QuerySignals extends BasicSignals { + query: object; +} export interface SignalsResponse { took: number; timeout: boolean; } -export interface SignalSearchResponse extends SignalsResponse { +export interface SignalSearchResponse + extends SignalsResponse { _shards: { total: number; successful: number; @@ -35,6 +39,62 @@ export interface SignalSearchResponse extend export interface UpdateSignalStatusProps { query: object; status: 'open' | 'closed'; - kbnVersion: string; signal?: AbortSignal; // TODO: implement cancelling } + +export interface SignalsIndex { + name: string; +} + +export interface Privilege { + username: string; + has_all_requested: boolean; + cluster: { + monitor_ml: boolean; + manage_ccr: boolean; + manage_index_templates: boolean; + monitor_watcher: boolean; + monitor_transform: boolean; + read_ilm: boolean; + manage_api_key: boolean; + manage_security: boolean; + manage_own_api_key: boolean; + manage_saml: boolean; + all: boolean; + manage_ilm: boolean; + manage_ingest_pipelines: boolean; + read_ccr: boolean; + manage_rollup: boolean; + monitor: boolean; + manage_watcher: boolean; + manage: boolean; + manage_transform: boolean; + manage_token: boolean; + manage_ml: boolean; + manage_pipeline: boolean; + monitor_rollup: boolean; + transport_client: boolean; + create_snapshot: boolean; + }; + index: { + [indexName: string]: { + all: boolean; + manage_ilm: boolean; + read: boolean; + create_index: boolean; + read_cross_cluster: boolean; + index: boolean; + monitor: boolean; + delete: boolean; + manage: boolean; + delete_index: boolean; + create_doc: boolean; + view_index_metadata: boolean; + create: boolean; + manage_follow_index: boolean; + manage_leader_index: boolean; + write: boolean; + }; + }; + isAuthenticated: boolean; +} diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/use_privilege_user.tsx b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/use_privilege_user.tsx new file mode 100644 index 0000000000000..792ff29ad2488 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/use_privilege_user.tsx @@ -0,0 +1,78 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { useEffect, useState } from 'react'; + +import { errorToToaster } from '../../../components/ml/api/error_to_toaster'; +import { useStateToaster } from '../../../components/toasters'; +import { getUserPrivilege } from './api'; +import * as i18n from './translations'; + +interface Return { + loading: boolean; + isAuthenticated: boolean | null; + hasIndexManage: boolean | null; + hasManageApiKey: boolean | null; + hasIndexWrite: boolean | null; +} +/** + * Hook to get user privilege from + * + */ +export const usePrivilegeUser = (): Return => { + const [loading, setLoading] = useState(true); + const [isAuthenticated, setAuthenticated] = useState(null); + const [hasIndexManage, setHasIndexManage] = useState(null); + const [hasIndexWrite, setHasIndexWrite] = useState(null); + const [hasManageApiKey, setHasManageApiKey] = useState(null); + const [, dispatchToaster] = useStateToaster(); + + useEffect(() => { + let isSubscribed = true; + const abortCtrl = new AbortController(); + setLoading(true); + + async function fetchData() { + try { + const privilege = await getUserPrivilege({ + signal: abortCtrl.signal, + }); + + if (isSubscribed && privilege != null) { + setAuthenticated(privilege.isAuthenticated); + if (privilege.index != null && Object.keys(privilege.index).length > 0) { + const indexName = Object.keys(privilege.index)[0]; + setHasIndexManage(privilege.index[indexName].manage); + setHasIndexWrite(privilege.index[indexName].write); + setHasManageApiKey( + privilege.cluster.manage_security || + privilege.cluster.manage_api_key || + privilege.cluster.manage_own_api_key + ); + } + } + } catch (error) { + if (isSubscribed) { + setAuthenticated(false); + setHasIndexManage(false); + setHasIndexWrite(false); + errorToToaster({ title: i18n.PRIVILEGE_FETCH_FAILURE, error, dispatchToaster }); + } + } + if (isSubscribed) { + setLoading(false); + } + } + + fetchData(); + return () => { + isSubscribed = false; + abortCtrl.abort(); + }; + }, []); + + return { loading, isAuthenticated, hasIndexManage, hasManageApiKey, hasIndexWrite }; +}; diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/use_query.tsx b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/use_query.tsx index d865625288550..fa88a84fb1187 100644 --- a/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/use_query.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/use_query.tsx @@ -4,30 +4,27 @@ * you may not use this file except in compliance with the Elastic License. */ -import { useEffect, useState } from 'react'; - -import { useUiSetting$ } from '../../../lib/kibana'; -import { DEFAULT_KBN_VERSION } from '../../../../common/constants'; -import { errorToToaster } from '../../../components/ml/api/error_to_toaster'; -import { useStateToaster } from '../../../components/toasters'; +import React, { SetStateAction, useEffect, useState } from 'react'; import { fetchQuerySignals } from './api'; -import * as i18n from './translations'; import { SignalSearchResponse } from './types'; -type Return = [boolean, SignalSearchResponse | null]; +type Return = [ + boolean, + SignalSearchResponse | null, + React.Dispatch> +]; /** * Hook for using to get a Signals from the Detection Engine API * - * @param query convert a dsl into string + * @param initialQuery query dsl object * */ -export const useQuerySignals = (query: string): Return => { +export const useQuerySignals = (initialQuery: object): Return => { + const [query, setQuery] = useState(initialQuery); const [signals, setSignals] = useState | null>(null); const [loading, setLoading] = useState(true); - const [kbnVersion] = useUiSetting$(DEFAULT_KBN_VERSION); - const [, dispatchToaster] = useStateToaster(); useEffect(() => { let isSubscribed = true; @@ -38,7 +35,6 @@ export const useQuerySignals = (query: string): Return => try { const signalResponse = await fetchQuerySignals({ query, - kbnVersion, signal: abortCtrl.signal, }); @@ -48,7 +44,6 @@ export const useQuerySignals = (query: string): Return => } catch (error) { if (isSubscribed) { setSignals(null); - errorToToaster({ title: i18n.SIGNAL_FETCH_FAILURE, error, dispatchToaster }); } } if (isSubscribed) { @@ -63,5 +58,5 @@ export const useQuerySignals = (query: string): Return => }; }, [query]); - return [loading, signals]; + return [loading, signals, setQuery]; }; diff --git a/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/use_signal_index.tsx b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/use_signal_index.tsx new file mode 100644 index 0000000000000..189d8a1bf3f75 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/containers/detection_engine/signals/use_signal_index.tsx @@ -0,0 +1,95 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { useEffect, useState, useRef } from 'react'; + +import { errorToToaster } from '../../../components/ml/api/error_to_toaster'; +import { useStateToaster } from '../../../components/toasters'; +import { createPrepackagedRules } from '../rules'; +import { createSignalIndex, getSignalIndex } from './api'; +import * as i18n from './translations'; +import { PostSignalError, SignalIndexError } from './types'; + +type Func = () => void; + +type Return = [boolean, boolean | null, string | null, Func | null]; + +/** + * Hook for managing signal index + * + * + */ +export const useSignalIndex = (): Return => { + const [loading, setLoading] = useState(true); + const [signalIndexName, setSignalIndexName] = useState(null); + const [signalIndexExists, setSignalIndexExists] = useState(null); + const createDeSignalIndex = useRef(null); + const [, dispatchToaster] = useStateToaster(); + + useEffect(() => { + let isSubscribed = true; + const abortCtrl = new AbortController(); + + const fetchData = async () => { + try { + setLoading(true); + const signal = await getSignalIndex({ signal: abortCtrl.signal }); + + if (isSubscribed && signal != null) { + setSignalIndexName(signal.name); + setSignalIndexExists(true); + createPrepackagedRules({ signal: abortCtrl.signal }); + } + } catch (error) { + if (isSubscribed) { + setSignalIndexName(null); + setSignalIndexExists(false); + if (error instanceof SignalIndexError && error.statusCode !== 404) { + errorToToaster({ title: i18n.SIGNAL_GET_NAME_FAILURE, error, dispatchToaster }); + } + } + } + if (isSubscribed) { + setLoading(false); + } + }; + + const createIndex = async () => { + let isFetchingData = false; + try { + setLoading(true); + await createSignalIndex({ signal: abortCtrl.signal }); + + if (isSubscribed) { + isFetchingData = true; + fetchData(); + } + } catch (error) { + if (isSubscribed) { + if (error instanceof PostSignalError && error.statusCode === 409) { + fetchData(); + } else { + setSignalIndexName(null); + setSignalIndexExists(false); + errorToToaster({ title: i18n.SIGNAL_POST_FAILURE, error, dispatchToaster }); + } + } + } + if (isSubscribed && !isFetchingData) { + setLoading(false); + } + }; + + fetchData(); + createDeSignalIndex.current = createIndex; + return () => { + isSubscribed = false; + abortCtrl.abort(); + }; + }, []); + + return [loading, signalIndexExists, signalIndexName, createDeSignalIndex.current]; +}; diff --git a/x-pack/legacy/plugins/siem/public/containers/global_time/index.tsx b/x-pack/legacy/plugins/siem/public/containers/global_time/index.tsx index 665148b7ad650..d77e0215f8353 100644 --- a/x-pack/legacy/plugins/siem/public/containers/global_time/index.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/global_time/index.tsx @@ -47,33 +47,38 @@ interface OwnProps { type GlobalTimeProps = OwnProps & GlobalTimeReduxState & GlobalTimeDispatch; -export const GlobalTimeComponent = React.memo( - ({ children, deleteAllQuery, deleteOneQuery, from, to, setGlobalQuery }: GlobalTimeProps) => { - const [isInitializing, setIsInitializing] = useState(true); +export const GlobalTimeComponent: React.FC = ({ + children, + deleteAllQuery, + deleteOneQuery, + from, + to, + setGlobalQuery, +}) => { + const [isInitializing, setIsInitializing] = useState(true); - useEffect(() => { - if (isInitializing) { - setIsInitializing(false); - } - return () => { - deleteAllQuery({ id: 'global' }); - }; - }, []); + useEffect(() => { + if (isInitializing) { + setIsInitializing(false); + } + return () => { + deleteAllQuery({ id: 'global' }); + }; + }, []); - return ( - <> - {children({ - isInitializing, - from, - to, - setQuery: ({ id, inspect, loading, refetch }: SetQuery) => - setGlobalQuery({ inputId: 'global', id, inspect, loading, refetch }), - deleteQuery: ({ id }: { id: string }) => deleteOneQuery({ inputId: 'global', id }), - })} - - ); - } -); + return ( + <> + {children({ + isInitializing, + from, + to, + setQuery: ({ id, inspect, loading, refetch }: SetQuery) => + setGlobalQuery({ inputId: 'global', id, inspect, loading, refetch }), + deleteQuery: ({ id }: { id: string }) => deleteOneQuery({ inputId: 'global', id }), + })} + + ); +}; const mapStateToProps = (state: State) => { const timerange: inputsModel.TimeRange = inputsSelectors.globalTimeRangeSelector(state); @@ -87,4 +92,4 @@ export const GlobalTime = connect(mapStateToProps, { deleteAllQuery: inputsActions.deleteAllQuery, deleteOneQuery: inputsActions.deleteOneQuery, setGlobalQuery: inputsActions.setQuery, -})(GlobalTimeComponent); +})(React.memo(GlobalTimeComponent)); diff --git a/x-pack/legacy/plugins/siem/public/containers/timeline/all/index.tsx b/x-pack/legacy/plugins/siem/public/containers/timeline/all/index.tsx index 5ff28480f1b3f..22c7b03f34dd5 100644 --- a/x-pack/legacy/plugins/siem/public/containers/timeline/all/index.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/timeline/all/index.tsx @@ -71,32 +71,38 @@ const getAllTimeline = memoizeOne( })) ); -export const AllTimelinesQuery = React.memo( - ({ children, onlyUserFavorite, pageInfo, search, sort }) => { - const variables: GetAllTimeline.Variables = { - onlyUserFavorite, - pageInfo, - search, - sort, - }; - return ( - - query={allTimelinesQuery} - fetchPolicy="network-only" - notifyOnNetworkStatusChange - variables={variables} - > - {({ data, loading }) => { - return children!({ - loading, - totalCount: getOr(0, 'getAllTimeline.totalCount', data), - timelines: getAllTimeline( - JSON.stringify(variables), - getOr([], 'getAllTimeline.timeline', data) - ), - }); - }} - - ); - } -); +const AllTimelinesQueryComponent: React.FC = ({ + children, + onlyUserFavorite, + pageInfo, + search, + sort, +}) => { + const variables: GetAllTimeline.Variables = { + onlyUserFavorite, + pageInfo, + search, + sort, + }; + return ( + + query={allTimelinesQuery} + fetchPolicy="network-only" + notifyOnNetworkStatusChange + variables={variables} + > + {({ data, loading }) => + children!({ + loading, + totalCount: getOr(0, 'getAllTimeline.totalCount', data), + timelines: getAllTimeline( + JSON.stringify(variables), + getOr([], 'getAllTimeline.timeline', data) + ), + }) + } + + ); +}; + +export const AllTimelinesQuery = React.memo(AllTimelinesQueryComponent); diff --git a/x-pack/legacy/plugins/siem/public/containers/timeline/details/index.tsx b/x-pack/legacy/plugins/siem/public/containers/timeline/details/index.tsx index 721cfefe01780..cf1b8954307e7 100644 --- a/x-pack/legacy/plugins/siem/public/containers/timeline/details/index.tsx +++ b/x-pack/legacy/plugins/siem/public/containers/timeline/details/index.tsx @@ -32,33 +32,39 @@ const getDetailsEvent = memoizeOne( (variables: string, detail: DetailItem[]): DetailItem[] => detail ); -export const TimelineDetailsComponentQuery = React.memo( - ({ children, indexName, eventId, executeQuery, sourceId }) => { - const variables: GetTimelineDetailsQuery.Variables = { - sourceId, - indexName, - eventId, - defaultIndex: useUiSetting(DEFAULT_INDEX_KEY), - }; - return executeQuery ? ( - - query={timelineDetailsQuery} - fetchPolicy="network-only" - notifyOnNetworkStatusChange - variables={variables} - > - {({ data, loading, refetch }) => { - return children!({ - loading, - detailsData: getDetailsEvent( - JSON.stringify(variables), - getOr([], 'source.TimelineDetails.data', data) - ), - }); - }} - - ) : ( - children!({ loading: false, detailsData: null }) - ); - } -); +const TimelineDetailsQueryComponent: React.FC = ({ + children, + indexName, + eventId, + executeQuery, + sourceId, +}) => { + const variables: GetTimelineDetailsQuery.Variables = { + sourceId, + indexName, + eventId, + defaultIndex: useUiSetting(DEFAULT_INDEX_KEY), + }; + return executeQuery ? ( + + query={timelineDetailsQuery} + fetchPolicy="network-only" + notifyOnNetworkStatusChange + variables={variables} + > + {({ data, loading, refetch }) => + children!({ + loading, + detailsData: getDetailsEvent( + JSON.stringify(variables), + getOr([], 'source.TimelineDetails.data', data) + ), + }) + } + + ) : ( + children!({ loading: false, detailsData: null }) + ); +}; + +export const TimelineDetailsQuery = React.memo(TimelineDetailsQueryComponent); diff --git a/x-pack/legacy/plugins/siem/public/hooks/api/api.tsx b/x-pack/legacy/plugins/siem/public/hooks/api/api.tsx index 12282241247cb..8d319ffe23902 100644 --- a/x-pack/legacy/plugins/siem/public/hooks/api/api.tsx +++ b/x-pack/legacy/plugins/siem/public/hooks/api/api.tsx @@ -17,13 +17,9 @@ const emptyIndexPattern: IndexPatternSavedObject[] = []; * * TODO: Refactor to context provider: https://github.com/elastic/siem-team/issues/448 * - * @param headers * @param signal */ -export const getIndexPatterns = async ( - signal: AbortSignal, - kbnVersion: string -): Promise => { +export const getIndexPatterns = async (signal: AbortSignal): Promise => { const response = await fetch( `${chrome.getBasePath()}/api/saved_objects/_find?type=index-pattern&fields=title&fields=type&per_page=10000`, { @@ -31,9 +27,8 @@ export const getIndexPatterns = async ( credentials: 'same-origin', headers: { 'content-type': 'application/json', - 'kbn-xsrf': kbnVersion, - 'kbn-version': kbnVersion, 'kbn-system-api': 'true', + 'kbn-xsrf': 'true', }, signal, } diff --git a/x-pack/legacy/plugins/siem/public/hooks/index.ts b/x-pack/legacy/plugins/siem/public/hooks/index.ts new file mode 100644 index 0000000000000..5049e4587d383 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/hooks/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { useDateFormat, useTimeZone } from './use_ui_settings'; diff --git a/x-pack/legacy/plugins/siem/public/hooks/use_index_patterns.tsx b/x-pack/legacy/plugins/siem/public/hooks/use_index_patterns.tsx index f5b595b0d01c6..7abe88402096c 100644 --- a/x-pack/legacy/plugins/siem/public/hooks/use_index_patterns.tsx +++ b/x-pack/legacy/plugins/siem/public/hooks/use_index_patterns.tsx @@ -6,11 +6,9 @@ import { useEffect, useState } from 'react'; -import { DEFAULT_KBN_VERSION } from '../../common/constants'; import { useStateToaster } from '../components/toasters'; import { errorToToaster } from '../components/ml/api/error_to_toaster'; import { IndexPatternSavedObject } from '../components/ml_popover/types'; -import { useUiSetting$ } from '../lib/kibana'; import { getIndexPatterns } from './api/api'; import * as i18n from './translations'; @@ -21,7 +19,6 @@ export const useIndexPatterns = (refreshToggle = false): Return => { const [indexPatterns, setIndexPatterns] = useState([]); const [isLoading, setIsLoading] = useState(true); const [, dispatchToaster] = useStateToaster(); - const [kbnVersion] = useUiSetting$(DEFAULT_KBN_VERSION); useEffect(() => { let isSubscribed = true; @@ -30,7 +27,7 @@ export const useIndexPatterns = (refreshToggle = false): Return => { async function fetchIndexPatterns() { try { - const data = await getIndexPatterns(abortCtrl.signal, kbnVersion); + const data = await getIndexPatterns(abortCtrl.signal); if (isSubscribed) { setIndexPatterns(data); diff --git a/x-pack/legacy/plugins/siem/public/hooks/use_ui_settings.ts b/x-pack/legacy/plugins/siem/public/hooks/use_ui_settings.ts new file mode 100644 index 0000000000000..7eb0242e8e116 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/hooks/use_ui_settings.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import moment from 'moment-timezone'; + +import { DEFAULT_DATE_FORMAT, DEFAULT_DATE_FORMAT_TZ } from '../../common/constants'; +import { useUiSetting } from '../lib/kibana'; + +export const useDateFormat = (): string => useUiSetting(DEFAULT_DATE_FORMAT); + +export const useTimeZone = (): string => { + const timeZone = useUiSetting(DEFAULT_DATE_FORMAT_TZ); + return timeZone === 'Browser' ? moment.tz.guess() : timeZone; +}; diff --git a/x-pack/legacy/plugins/siem/public/index.ts b/x-pack/legacy/plugins/siem/public/index.ts new file mode 100644 index 0000000000000..3a396a0637ea1 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Plugin, PluginInitializerContext } from './plugin'; + +export const plugin = (context: PluginInitializerContext): Plugin => new Plugin(context); diff --git a/x-pack/legacy/plugins/siem/public/legacy.ts b/x-pack/legacy/plugins/siem/public/legacy.ts new file mode 100644 index 0000000000000..49a03c93120d4 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/legacy.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { npSetup, npStart } from 'ui/new_platform'; + +import { PluginInitializerContext } from '../../../../../src/core/public'; +import { plugin } from './'; + +const pluginInstance = plugin({} as PluginInitializerContext); + +pluginInstance.setup(npSetup.core, npSetup.plugins); +pluginInstance.start(npStart.core, npStart.plugins); diff --git a/x-pack/legacy/plugins/siem/public/lib/clipboard/clipboard.tsx b/x-pack/legacy/plugins/siem/public/lib/clipboard/clipboard.tsx index 44cf7502f3c5e..fdb6ed130a525 100644 --- a/x-pack/legacy/plugins/siem/public/lib/clipboard/clipboard.tsx +++ b/x-pack/legacy/plugins/siem/public/lib/clipboard/clipboard.tsx @@ -6,7 +6,7 @@ import { EuiGlobalToastListToast as Toast, EuiButtonIcon } from '@elastic/eui'; import copy from 'copy-to-clipboard'; -import * as React from 'react'; +import React from 'react'; import uuid from 'uuid'; import * as i18n from './translations'; diff --git a/x-pack/legacy/plugins/siem/public/lib/clipboard/with_copy_to_clipboard.tsx b/x-pack/legacy/plugins/siem/public/lib/clipboard/with_copy_to_clipboard.tsx index c392c2511551f..ee94c2daa3fc4 100644 --- a/x-pack/legacy/plugins/siem/public/lib/clipboard/with_copy_to_clipboard.tsx +++ b/x-pack/legacy/plugins/siem/public/lib/clipboard/with_copy_to_clipboard.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; import styled from 'styled-components'; import { Clipboard } from './clipboard'; diff --git a/x-pack/legacy/plugins/siem/public/lib/compose/helpers.ts b/x-pack/legacy/plugins/siem/public/lib/compose/helpers.ts index 80a42b91dbd32..9cdd4148134ad 100644 --- a/x-pack/legacy/plugins/siem/public/lib/compose/helpers.ts +++ b/x-pack/legacy/plugins/siem/public/lib/compose/helpers.ts @@ -20,9 +20,7 @@ export const getLinks = (cache: InMemoryCache) => [ }), new HttpLink({ credentials: 'same-origin', - headers: { - 'kbn-xsrf': chrome.getXsrfToken(), - }, + headers: { 'kbn-xsrf': 'true' }, uri: `${chrome.getBasePath()}/api/siem/graphql`, }), ]; diff --git a/x-pack/legacy/plugins/siem/public/lib/kibana/index.ts b/x-pack/legacy/plugins/siem/public/lib/kibana/index.ts index 96d9c8330d265..012a1cfef5da2 100644 --- a/x-pack/legacy/plugins/siem/public/lib/kibana/index.ts +++ b/x-pack/legacy/plugins/siem/public/lib/kibana/index.ts @@ -12,7 +12,7 @@ import { useUiSetting$, withKibana, } from '../../../../../../../src/plugins/kibana_react/public'; -import { StartServices } from '../../apps/plugin'; +import { StartServices } from '../../plugin'; export type KibanaContext = KibanaReactContextValue; export interface WithKibanaProps { diff --git a/x-pack/legacy/plugins/siem/public/lib/lib.ts b/x-pack/legacy/plugins/siem/public/lib/lib.ts index 7a6cd32aa8864..e7b39d2ea50f9 100644 --- a/x-pack/legacy/plugins/siem/public/lib/lib.ts +++ b/x-pack/legacy/plugins/siem/public/lib/lib.ts @@ -24,7 +24,6 @@ export interface AppFrameworkAdapter { darkMode?: boolean; indexPattern?: string; anomalyScore?: number; - kbnVersion?: string; scaledDateFormat?: string; timezone?: string; diff --git a/x-pack/legacy/plugins/siem/public/mock/hook_wrapper.tsx b/x-pack/legacy/plugins/siem/public/mock/hook_wrapper.tsx index 4f1c2049c0027..292ddc036dcaf 100644 --- a/x-pack/legacy/plugins/siem/public/mock/hook_wrapper.tsx +++ b/x-pack/legacy/plugins/siem/public/mock/hook_wrapper.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import * as React from 'react'; +import React from 'react'; interface HookWrapperProps { // eslint-disable-next-line @typescript-eslint/no-explicit-any diff --git a/x-pack/legacy/plugins/siem/public/mock/index.ts b/x-pack/legacy/plugins/siem/public/mock/index.ts index 620e266618c5c..dbf5f2e55e713 100644 --- a/x-pack/legacy/plugins/siem/public/mock/index.ts +++ b/x-pack/legacy/plugins/siem/public/mock/index.ts @@ -8,7 +8,6 @@ export * from './global_state'; export * from './header'; export * from './hook_wrapper'; export * from './index_pattern'; -export * from './kibana_config'; export * from './mock_timeline_data'; export * from './mock_detail_item'; export * from './netflow'; diff --git a/x-pack/legacy/plugins/siem/public/mock/kibana_config.ts b/x-pack/legacy/plugins/siem/public/mock/kibana_config.ts deleted file mode 100644 index 23f1f0e86dd6a..0000000000000 --- a/x-pack/legacy/plugins/siem/public/mock/kibana_config.ts +++ /dev/null @@ -1,123 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import { - DEFAULT_DATE_FORMAT, - DEFAULT_DATE_FORMAT_TZ, - DEFAULT_BYTES_FORMAT, - DEFAULT_KBN_VERSION, - DEFAULT_TIMEZONE_BROWSER, - DEFAULT_TIMEPICKER_QUICK_RANGES, -} from '../../common/constants'; - -export interface MockFrameworks { - bytesFormat: string; - dateFormat: string; - dateFormatTz: string; - timezone: string; -} - -export const getMockKibanaUiSetting = (config: MockFrameworks) => (key: string) => { - if (key === DEFAULT_DATE_FORMAT) { - return [config.dateFormat]; - } else if (key === DEFAULT_DATE_FORMAT_TZ) { - return [config.dateFormatTz]; - } else if (key === DEFAULT_BYTES_FORMAT) { - return [config.bytesFormat]; - } else if (key === DEFAULT_KBN_VERSION) { - return ['8.0.0']; - } else if (key === DEFAULT_TIMEZONE_BROWSER) { - return config && config.timezone ? [config.timezone] : ['America/New_York']; - } else if (key === DEFAULT_TIMEPICKER_QUICK_RANGES) { - return [ - [ - { - from: 'now/d', - to: 'now/d', - display: 'Today', - }, - { - from: 'now/w', - to: 'now/w', - display: 'This week', - }, - { - from: 'now-15m', - to: 'now', - display: 'Last 15 minutes', - }, - { - from: 'now-30m', - to: 'now', - display: 'Last 30 minutes', - }, - { - from: 'now-1h', - to: 'now', - display: 'Last 1 hour', - }, - { - from: 'now-24h', - to: 'now', - display: 'Last 24 hours', - }, - { - from: 'now-7d', - to: 'now', - display: 'Last 7 days', - }, - { - from: 'now-30d', - to: 'now', - display: 'Last 30 days', - }, - { - from: 'now-90d', - to: 'now', - display: 'Last 90 days', - }, - { - from: 'now-1y', - to: 'now', - display: 'Last 1 year', - }, - ], - ]; - } - return [null]; -}; - -export const mockFrameworks: Readonly> = { - bytes_short: { - bytesFormat: '0b', - dateFormat: 'MMM D, YYYY @ HH:mm:ss.SSS', - dateFormatTz: 'Browser', - timezone: 'America/Denver', - }, - default_browser: { - bytesFormat: '0,0.[0]b', - dateFormat: 'MMM D, YYYY @ HH:mm:ss.SSS', - dateFormatTz: 'Browser', - timezone: 'America/Denver', - }, - default_ET: { - bytesFormat: '0,0.[0]b', - dateFormat: 'MMM D, YYYY @ HH:mm:ss.SSS', - dateFormatTz: 'America/New_York', - timezone: 'America/New_York', - }, - default_MT: { - bytesFormat: '0,0.[0]b', - dateFormat: 'MMM D, YYYY @ HH:mm:ss.SSS', - dateFormatTz: 'America/Denver', - timezone: 'America/Denver', - }, - default_UTC: { - bytesFormat: '0,0.[0]b', - dateFormat: 'MMM D, YYYY @ HH:mm:ss.SSS', - dateFormatTz: 'UTC', - timezone: 'UTC', - }, -}; diff --git a/x-pack/legacy/plugins/siem/public/mock/kibana_react.ts b/x-pack/legacy/plugins/siem/public/mock/kibana_react.ts index 15944df1822b3..7d843977d1f32 100644 --- a/x-pack/legacy/plugins/siem/public/mock/kibana_react.ts +++ b/x-pack/legacy/plugins/siem/public/mock/kibana_react.ts @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +/* eslint-disable react/display-name */ + import React from 'react'; import { KibanaContextProvider } from '../../../../../../src/plugins/kibana_react/public'; @@ -20,7 +22,7 @@ import { DEFAULT_TO, DEFAULT_INTERVAL_PAUSE, DEFAULT_INTERVAL_VALUE, - DEFAULT_TIMEZONE_BROWSER, + DEFAULT_BYTES_FORMAT, } from '../../common/constants'; import { defaultIndexPattern } from '../../default_index_pattern'; import { createKibanaCoreStartMock, createKibanaPluginsStartMock } from './kibana_core'; @@ -38,8 +40,8 @@ export const mockUiSettings: Record = { value: DEFAULT_INTERVAL_VALUE, }, [DEFAULT_INDEX_KEY]: defaultIndexPattern, + [DEFAULT_BYTES_FORMAT]: '0,0.[0]b', [DEFAULT_DATE_FORMAT_TZ]: 'UTC', - [DEFAULT_TIMEZONE_BROWSER]: 'America/New_York', [DEFAULT_DATE_FORMAT]: 'MMM D, YYYY @ HH:mm:ss.SSS', [DEFAULT_DARK_MODE]: false, }; @@ -85,11 +87,15 @@ export const createWithKibanaMock = () => { export const createKibanaContextProviderMock = () => { const kibana = createUseKibanaMock()(); + const uiSettings = { + ...kibana.services.uiSettings, + get: createUseUiSettingMock(), + }; // eslint-disable-next-line @typescript-eslint/no-explicit-any return ({ services, ...rest }: any) => React.createElement(KibanaContextProvider, { ...rest, - services: { ...kibana.services, ...services }, + services: { ...kibana.services, uiSettings, ...services }, }); }; diff --git a/x-pack/legacy/plugins/siem/public/mock/test_providers.tsx b/x-pack/legacy/plugins/siem/public/mock/test_providers.tsx index 6c0a85e3ef778..c7692755c1330 100644 --- a/x-pack/legacy/plugins/siem/public/mock/test_providers.tsx +++ b/x-pack/legacy/plugins/siem/public/mock/test_providers.tsx @@ -9,7 +9,7 @@ import { I18nProvider } from '@kbn/i18n/react'; import { InMemoryCache as Cache } from 'apollo-cache-inmemory'; import ApolloClient from 'apollo-client'; import { ApolloLink } from 'apollo-link'; -import * as React from 'react'; +import React from 'react'; import { ApolloProvider } from 'react-apollo'; import { DragDropContext, DropResult, ResponderProvided } from 'react-beautiful-dnd'; import { Provider as ReduxStoreProvider } from 'react-redux'; @@ -61,26 +61,33 @@ Object.defineProperty(window, 'localStorage', { const MockKibanaContextProvider = createKibanaContextProviderMock(); /** A utility for wrapping children in the providers required to run most tests */ -export const TestProviders = React.memo( - ({ children, store = createStore(state, apolloClientObservable), onDragEnd = jest.fn() }) => ( - - - - - ({ eui: euiDarkVars, darkMode: true })}> - {children} - - - - - - ) +const TestProvidersComponent: React.FC = ({ + children, + store = createStore(state, apolloClientObservable), + onDragEnd = jest.fn(), +}) => ( + + + + + ({ eui: euiDarkVars, darkMode: true })}> + {children} + + + + + ); -export const TestProviderWithoutDragAndDrop = React.memo( - ({ children, store = createStore(state, apolloClientObservable) }) => ( - - {children} - - ) +export const TestProviders = React.memo(TestProvidersComponent); + +const TestProviderWithoutDragAndDropComponent: React.FC = ({ + children, + store = createStore(state, apolloClientObservable), +}) => ( + + {children} + ); + +export const TestProviderWithoutDragAndDrop = React.memo(TestProviderWithoutDragAndDropComponent); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/activity_monitor/columns.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/activity_monitor/columns.tsx index 41dd50120f345..2ad37960d0423 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/activity_monitor/columns.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/activity_monitor/columns.tsx @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +/* eslint-disable react/display-name */ + import { EuiIconTip, EuiLink, diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/no_write_signals_callout/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/no_write_signals_callout/index.tsx new file mode 100644 index 0000000000000..1950531998450 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/no_write_signals_callout/index.tsx @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiCallOut, EuiButton } from '@elastic/eui'; +import React, { memo, useCallback, useState } from 'react'; + +import * as i18n from './translations'; + +const NoWriteSignalsCallOutComponent = () => { + const [showCallOut, setShowCallOut] = useState(true); + const handleCallOut = useCallback(() => setShowCallOut(false), [setShowCallOut]); + + return showCallOut ? ( + +

{i18n.NO_WRITE_SIGNALS_CALLOUT_MSG}

+ + {i18n.DISMISS_CALLOUT} + +
+ ) : null; +}; + +export const NoWriteSignalsCallOut = memo(NoWriteSignalsCallOutComponent); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/no_write_signals_callout/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/no_write_signals_callout/translations.ts new file mode 100644 index 0000000000000..065d775e1dc6a --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/no_write_signals_callout/translations.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const NO_WRITE_SIGNALS_CALLOUT_TITLE = i18n.translate( + 'xpack.siem.detectionEngine.noWriteSignalsCallOutTitle', + { + defaultMessage: 'Signals index permissions required', + } +); + +export const NO_WRITE_SIGNALS_CALLOUT_MSG = i18n.translate( + 'xpack.siem.detectionEngine.noWriteSignalsCallOutMsg', + { + defaultMessage: + 'You are currently missing the required permissions to update signals. Please contact your administrator for further assistance.', + } +); + +export const DISMISS_CALLOUT = i18n.translate( + 'xpack.siem.detectionEngine.dismissNoWriteSignalButton', + { + defaultMessage: 'Dismiss', + } +); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/actions.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/actions.tsx index c0ed58daeca7f..d08e282a4c399 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/actions.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/actions.tsx @@ -42,18 +42,13 @@ export const updateSignalStatusAction = async ({ status, setEventsLoading, setEventsDeleted, - kbnVersion, }: UpdateSignalStatusActionProps) => { try { setEventsLoading({ eventIds: signalIds, isLoading: true }); const queryObject = query ? { query: JSON.parse(query) } : getUpdateSignalsQuery(signalIds); - await updateSignalStatus({ - query: queryObject, - status, - kbnVersion, - }); + await updateSignalStatus({ query: queryObject, status }); // TODO: Only delete those that were successfully updated from updatedRules setEventsDeleted({ eventIds: signalIds, isDeleted: true }); } catch (e) { diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/default_config.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/default_config.tsx index ea9a3ccef05b4..83b6ba690ec5b 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/default_config.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/default_config.tsx @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +/* eslint-disable react/display-name */ + import { EuiButtonIcon, EuiToolTip } from '@elastic/eui'; import React from 'react'; @@ -166,58 +168,66 @@ export const requiredFieldsForActions = [ ]; export const getSignalsActions = ({ + canUserCRUD, + hasIndexWrite, setEventsLoading, setEventsDeleted, createTimeline, status, - kbnVersion, }: { + canUserCRUD: boolean; + hasIndexWrite: boolean; setEventsLoading: ({ eventIds, isLoading }: SetEventsLoadingProps) => void; setEventsDeleted: ({ eventIds, isDeleted }: SetEventsDeletedProps) => void; createTimeline: CreateTimeline; status: 'open' | 'closed'; - kbnVersion: string; -}): TimelineAction[] => [ - { - getAction: ({ eventId, data }: TimelineActionProps): JSX.Element => ( - - sendSignalsToTimelineAction({ createTimeline, data: [data] })} - iconType="tableDensityNormal" - aria-label="Next" - /> - - ), - id: 'sendSignalToTimeline', - width: 26, - }, - { - getAction: ({ eventId, data }: TimelineActionProps): JSX.Element => ( - - - updateSignalStatusAction({ - signalIds: [eventId], - status, - setEventsLoading, - setEventsDeleted, - kbnVersion, - }) - } - iconType={status === FILTER_OPEN ? 'indexOpen' : 'indexClose'} - aria-label="Next" - /> - - ), - id: 'updateSignalStatus', - width: 26, - }, -]; +}): TimelineAction[] => { + const actions = [ + { + getAction: ({ eventId, data }: TimelineActionProps): JSX.Element => ( + + sendSignalsToTimelineAction({ createTimeline, data: [data] })} + iconType="tableDensityNormal" + aria-label="Next" + /> + + ), + id: 'sendSignalToTimeline', + width: 26, + }, + ]; + return canUserCRUD && hasIndexWrite + ? [ + ...actions, + { + getAction: ({ eventId, data }: TimelineActionProps): JSX.Element => ( + + + updateSignalStatusAction({ + signalIds: [eventId], + status, + setEventsLoading, + setEventsDeleted, + }) + } + iconType={status === FILTER_OPEN ? 'indexOpen' : 'indexClose'} + aria-label="Next" + /> + + ), + id: 'updateSignalStatus', + width: 26, + }, + ] + : actions; +}; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/index.tsx index aeb5e677374fc..d149eb700ad03 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/index.tsx @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { EuiPanel, EuiLoadingContent } from '@elastic/eui'; import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { connect } from 'react-redux'; import { ActionCreator } from 'typescript-fsa'; @@ -25,8 +26,7 @@ import { SignalFilterOption, SignalsTableFilterGroup, } from './signals_filter_group'; -import { useKibana, useUiSetting$ } from '../../../../lib/kibana'; -import { DEFAULT_KBN_VERSION, DEFAULT_SIGNALS_INDEX } from '../../../../../common/constants'; +import { useKibana } from '../../../../lib/kibana'; import { defaultHeaders } from '../../../../components/timeline/body/column_headers/default_headers'; import { ColumnHeader } from '../../../../components/timeline/body/column_headers/column_header'; import { esFilters, esQuery } from '../../../../../../../../../src/plugins/data/common/es_query'; @@ -47,6 +47,8 @@ import { useFetchIndexPatterns } from '../../../../containers/detection_engine/r import { InputsRange } from '../../../../store/inputs/model'; import { Query } from '../../../../../../../../../src/plugins/data/common/query'; +import { HeaderSection } from '../../../../components/header_section'; + const SIGNALS_PAGE_TIMELINE_ID = 'signals-page'; interface ReduxProps { @@ -89,8 +91,12 @@ interface DispatchProps { } interface OwnProps { + canUserCRUD: boolean; defaultFilters?: esFilters.Filter[]; + hasIndexWrite: boolean; from: number; + loading: boolean; + signalsIndex: string; to: number; } @@ -98,6 +104,7 @@ type SignalsTableComponentProps = OwnProps & ReduxProps & DispatchProps; export const SignalsTableComponent = React.memo( ({ + canUserCRUD, createTimeline, clearEventsDeleted, clearEventsLoading, @@ -106,22 +113,22 @@ export const SignalsTableComponent = React.memo( from, globalFilters, globalQuery, + hasIndexWrite, isSelectAllChecked, + loading, loadingEventIds, removeTimelineLinkTo, selectedEventIds, setEventsDeleted, setEventsLoading, + signalsIndex, to, }) => { const [selectAll, setSelectAll] = useState(false); const [showClearSelectionAction, setShowClearSelectionAction] = useState(false); const [filterGroup, setFilterGroup] = useState(FILTER_OPEN); - const [{ browserFields, indexPatterns }] = useFetchIndexPatterns([ - `${DEFAULT_SIGNALS_INDEX}-default`, - ]); // TODO Get from new FrankInspired XavierHook - const [kbnVersion] = useUiSetting$(DEFAULT_KBN_VERSION); + const [{ browserFields, indexPatterns }] = useFetchIndexPatterns([signalsIndex]); const kibana = useKibana(); const getGlobalQuery = useCallback(() => { @@ -208,7 +215,6 @@ export const SignalsTableComponent = React.memo( status, setEventsDeleted: setEventsDeletedCallback, setEventsLoading: setEventsLoadingCallback, - kbnVersion, }); }, [ @@ -231,8 +237,10 @@ export const SignalsTableComponent = React.memo( (totalCount: number) => { return ( 0} clearSelection={clearSelectionCallback} + hasIndexWrite={hasIndexWrite} isFilteredToOpen={filterGroup === FILTER_OPEN} selectAll={selectAllCallback} selectedEventIds={selectedEventIds} @@ -244,6 +252,8 @@ export const SignalsTableComponent = React.memo( ); }, [ + canUserCRUD, + hasIndexWrite, clearSelectionCallback, filterGroup, loadingEventIds.length, @@ -257,18 +267,17 @@ export const SignalsTableComponent = React.memo( const additionalActions = useMemo( () => getSignalsActions({ + canUserCRUD, + hasIndexWrite, createTimeline: createTimelineCallback, setEventsLoading: setEventsLoadingCallback, setEventsDeleted: setEventsDeletedCallback, status: filterGroup === FILTER_OPEN ? FILTER_CLOSED : FILTER_OPEN, - kbnVersion, }), - [createTimelineCallback, filterGroup, kbnVersion] + [canUserCRUD, createTimelineCallback, filterGroup] ); - const defaultIndices = useMemo(() => [`${DEFAULT_SIGNALS_INDEX}-default`], [ - `${DEFAULT_SIGNALS_INDEX}-default`, - ]); + const defaultIndices = useMemo(() => [signalsIndex], [signalsIndex]); const defaultFiltersMemo = useMemo( () => [ ...defaultFilters, @@ -285,14 +294,23 @@ export const SignalsTableComponent = React.memo( queryFields: requiredFieldsForActions, timelineActions: additionalActions, title: i18n.SIGNALS_TABLE_TITLE, - selectAll, + selectAll: canUserCRUD ? selectAll : false, }), - [additionalActions, selectAll] + [additionalActions, canUserCRUD, selectAll] ); + if (loading) { + return ( + + + + + ); + } + return ( void; - }) => { - const [filterGroup, setFilterGroup] = useState(FILTER_OPEN); - - const onClickOpenFilterCallback = useCallback(() => { - setFilterGroup(FILTER_OPEN); - onFilterGroupChanged(FILTER_OPEN); - }, [setFilterGroup, onFilterGroupChanged]); - - const onClickCloseFilterCallback = useCallback(() => { - setFilterGroup(FILTER_CLOSED); - onFilterGroupChanged(FILTER_CLOSED); - }, [setFilterGroup, onFilterGroupChanged]); - - return ( - - - {i18n.OPEN_SIGNALS} - - - - {i18n.CLOSED_SIGNALS} - - - ); - } -); +interface Props { + onFilterGroupChanged: (filterGroup: SignalFilterOption) => void; +} + +const SignalsTableFilterGroupComponent: React.FC = ({ onFilterGroupChanged }) => { + const [filterGroup, setFilterGroup] = useState(FILTER_OPEN); + + const onClickOpenFilterCallback = useCallback(() => { + setFilterGroup(FILTER_OPEN); + onFilterGroupChanged(FILTER_OPEN); + }, [setFilterGroup, onFilterGroupChanged]); + + const onClickCloseFilterCallback = useCallback(() => { + setFilterGroup(FILTER_CLOSED); + onFilterGroupChanged(FILTER_CLOSED); + }, [setFilterGroup, onFilterGroupChanged]); + + return ( + + + {i18n.OPEN_SIGNALS} + + + + {i18n.CLOSED_SIGNALS} + + + ); +}; + +export const SignalsTableFilterGroup = React.memo(SignalsTableFilterGroupComponent); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/signals_utility_bar/batch_actions.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/signals_utility_bar/batch_actions.tsx index bbbc7728e36a5..b756b2eb75a7a 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/signals_utility_bar/batch_actions.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/signals_utility_bar/batch_actions.tsx @@ -11,6 +11,15 @@ import { TimelineNonEcsData } from '../../../../../graphql/types'; import { SendSignalsToTimeline, UpdateSignalsStatus } from '../types'; import { FILTER_CLOSED, FILTER_OPEN } from '../signals_filter_group'; +interface GetBatchItems { + areEventsLoading: boolean; + allEventsSelected: boolean; + selectedEventIds: Readonly>; + updateSignalsStatus: UpdateSignalsStatus; + sendSignalsToTimeline: SendSignalsToTimeline; + closePopover: () => void; + isFilteredToOpen: boolean; +} /** * Returns ViewInTimeline / UpdateSignalStatus actions to be display within an EuiContextMenuPanel * @@ -22,15 +31,15 @@ import { FILTER_CLOSED, FILTER_OPEN } from '../signals_filter_group'; * @param closePopover * @param isFilteredToOpen currently selected filter options */ -export const getBatchItems = ( - areEventsLoading: boolean, - allEventsSelected: boolean, - selectedEventIds: Readonly>, - updateSignalsStatus: UpdateSignalsStatus, - sendSignalsToTimeline: SendSignalsToTimeline, - closePopover: () => void, - isFilteredToOpen: boolean -) => { +export const getBatchItems = ({ + areEventsLoading, + allEventsSelected, + selectedEventIds, + updateSignalsStatus, + sendSignalsToTimeline, + closePopover, + isFilteredToOpen, +}: GetBatchItems) => { const allDisabled = areEventsLoading || Object.keys(selectedEventIds).length === 0; const sendToTimelineDisabled = allEventsSelected || uniqueRuleCount(selectedEventIds) > 1; const filterString = isFilteredToOpen diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/signals_utility_bar/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/signals_utility_bar/index.tsx index 72b250470d19b..e28fb3e06870e 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/signals_utility_bar/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/signals_utility_bar/index.tsx @@ -22,6 +22,8 @@ import { TimelineNonEcsData } from '../../../../../graphql/types'; import { SendSignalsToTimeline, UpdateSignalsStatus } from '../types'; interface SignalsUtilityBarProps { + canUserCRUD: boolean; + hasIndexWrite: boolean; areEventsLoading: boolean; clearSelection: () => void; isFilteredToOpen: boolean; @@ -33,103 +35,103 @@ interface SignalsUtilityBarProps { updateSignalsStatus: UpdateSignalsStatus; } -export const SignalsUtilityBar = React.memo( - ({ - areEventsLoading, - clearSelection, - totalCount, - selectedEventIds, - isFilteredToOpen, - selectAll, - showClearSelection, - updateSignalsStatus, - sendSignalsToTimeline, - }) => { - const [defaultNumberFormat] = useUiSetting$(DEFAULT_NUMBER_FORMAT); +const SignalsUtilityBarComponent: React.FC = ({ + canUserCRUD, + hasIndexWrite, + areEventsLoading, + clearSelection, + totalCount, + selectedEventIds, + isFilteredToOpen, + selectAll, + showClearSelection, + updateSignalsStatus, + sendSignalsToTimeline, +}) => { + const [defaultNumberFormat] = useUiSetting$(DEFAULT_NUMBER_FORMAT); - const getBatchItemsPopoverContent = useCallback( - (closePopover: () => void) => ( - - ), - [ - areEventsLoading, - selectedEventIds, - updateSignalsStatus, - sendSignalsToTimeline, - isFilteredToOpen, - ] - ); + const getBatchItemsPopoverContent = useCallback( + (closePopover: () => void) => ( + + ), + [ + areEventsLoading, + selectedEventIds, + updateSignalsStatus, + sendSignalsToTimeline, + isFilteredToOpen, + hasIndexWrite, + ] + ); - const formattedTotalCount = numeral(totalCount).format(defaultNumberFormat); - const formattedSelectedEventsCount = numeral(Object.keys(selectedEventIds).length).format( - defaultNumberFormat - ); + const formattedTotalCount = numeral(totalCount).format(defaultNumberFormat); + const formattedSelectedEventsCount = numeral(Object.keys(selectedEventIds).length).format( + defaultNumberFormat + ); - return ( - <> - - - - - {i18n.SHOWING_SIGNALS(formattedTotalCount, totalCount)} - - + return ( + <> + + + + {i18n.SHOWING_SIGNALS(formattedTotalCount, totalCount)} + - - {totalCount > 0 && ( - <> - - {i18n.SELECTED_SIGNALS( - showClearSelection ? formattedTotalCount : formattedSelectedEventsCount, - showClearSelection ? totalCount : Object.keys(selectedEventIds).length - )} - + + {canUserCRUD && hasIndexWrite && ( + <> + + {i18n.SELECTED_SIGNALS( + showClearSelection ? formattedTotalCount : formattedSelectedEventsCount, + showClearSelection ? totalCount : Object.keys(selectedEventIds).length + )} + - - {i18n.BATCH_ACTIONS} - + + {i18n.BATCH_ACTIONS} + - { - if (!showClearSelection) { - selectAll(); - } else { - clearSelection(); - } - }} - > - {showClearSelection - ? i18n.CLEAR_SELECTION - : i18n.SELECT_ALL_SIGNALS(formattedTotalCount, totalCount)} - - - )} - - - - - ); - }, - (prevProps, nextProps) => { - return ( - prevProps.selectedEventIds === nextProps.selectedEventIds && - prevProps.totalCount === nextProps.totalCount && - prevProps.showClearSelection === nextProps.showClearSelection - ); - } + { + if (!showClearSelection) { + selectAll(); + } else { + clearSelection(); + } + }} + > + {showClearSelection + ? i18n.CLEAR_SELECTION + : i18n.SELECT_ALL_SIGNALS(formattedTotalCount, totalCount)} + + + )} + + + + + ); +}; + +export const SignalsUtilityBar = React.memo( + SignalsUtilityBarComponent, + (prevProps, nextProps) => + prevProps.selectedEventIds === nextProps.selectedEventIds && + prevProps.totalCount === nextProps.totalCount && + prevProps.showClearSelection === nextProps.showClearSelection ); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/types.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/types.ts index b02b8eb0ef976..51bea27ec6a4b 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/types.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals/types.ts @@ -31,7 +31,6 @@ export interface UpdateSignalStatusActionProps { status: 'open' | 'closed'; setEventsLoading: ({ eventIds, isLoading }: SetEventsLoadingProps) => void; setEventsDeleted: ({ eventIds, isDeleted }: SetEventsDeletedProps) => void; - kbnVersion: string; } export type SendSignalsToTimeline = () => void; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_chart/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_chart/index.tsx deleted file mode 100644 index 094f17922af1a..0000000000000 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_chart/index.tsx +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import { EuiPanel, EuiSelect } from '@elastic/eui'; -import { noop } from 'lodash/fp'; -import React, { memo } from 'react'; - -import { HeaderSection } from '../../../../components/header_section'; -import { HistogramSignals } from '../../../../components/page/detection_engine/histogram_signals'; - -export const sampleChartOptions = [ - { text: 'Risk scores', value: 'risk_scores' }, - { text: 'Severities', value: 'severities' }, - { text: 'Top destination IPs', value: 'destination_ips' }, - { text: 'Top event actions', value: 'event_actions' }, - { text: 'Top event categories', value: 'event_categories' }, - { text: 'Top host names', value: 'host_names' }, - { text: 'Top rule types', value: 'rule_types' }, - { text: 'Top rules', value: 'rules' }, - { text: 'Top source IPs', value: 'source_ips' }, - { text: 'Top users', value: 'users' }, -]; - -export const SignalsCharts = memo(() => ( - - - noop} - prepend="Stack by" - value={sampleChartOptions[0].value} - /> - - - - -)); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/config.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/config.ts new file mode 100644 index 0000000000000..f329780b075e3 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/config.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as i18n from './translations'; +import { SignalsHistogramOption } from './types'; + +export const signalsHistogramOptions: SignalsHistogramOption[] = [ + { text: i18n.STACK_BY_RISK_SCORES, value: 'signal.rule.risk_score' }, + { text: i18n.STACK_BY_SEVERITIES, value: 'signal.rule.severity' }, + { text: i18n.STACK_BY_DESTINATION_IPS, value: 'destination.ip' }, + { text: i18n.STACK_BY_ACTIONS, value: 'event.action' }, + { text: i18n.STACK_BY_CATEGORIES, value: 'event.category' }, + { text: i18n.STACK_BY_HOST_NAMES, value: 'host.name' }, + { text: i18n.STACK_BY_RULE_TYPES, value: 'signal.rule.type' }, + { text: i18n.STACK_BY_RULE_NAMES, value: 'signal.rule.name' }, + { text: i18n.STACK_BY_SOURCE_IPS, value: 'source.ip' }, + { text: i18n.STACK_BY_USERS, value: 'user.name' }, +]; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/index.tsx new file mode 100644 index 0000000000000..fda40f5f9fa5d --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/index.tsx @@ -0,0 +1,112 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { Position } from '@elastic/charts'; +import { EuiButton, EuiPanel, EuiSelect } from '@elastic/eui'; +import numeral from '@elastic/numeral'; +import React, { memo, useCallback, useMemo, useState } from 'react'; + +import { HeaderSection } from '../../../../components/header_section'; +import { SignalsHistogram } from './signals_histogram'; + +import * as i18n from './translations'; +import { Query } from '../../../../../../../../../src/plugins/data/common/query'; +import { esFilters } from '../../../../../../../../../src/plugins/data/common/es_query'; +import { SignalsHistogramOption, SignalsTotal } from './types'; +import { signalsHistogramOptions } from './config'; +import { getDetectionEngineUrl } from '../../../../components/link_to'; +import { DEFAULT_NUMBER_FORMAT } from '../../../../../common/constants'; +import { useUiSetting$ } from '../../../../lib/kibana'; + +const defaultTotalSignalsObj: SignalsTotal = { + value: 0, + relation: 'eq', +}; + +interface SignalsHistogramPanelProps { + defaultStackByOption?: SignalsHistogramOption; + filters?: esFilters.Filter[]; + from: number; + query?: Query; + legendPosition?: Position; + loadingInitial?: boolean; + showLinkToSignals?: boolean; + showTotalSignalsCount?: boolean; + stackByOptions?: SignalsHistogramOption[]; + title?: string; + to: number; + updateDateRange: (min: number, max: number) => void; +} + +export const SignalsHistogramPanel = memo( + ({ + defaultStackByOption = signalsHistogramOptions[0], + filters, + query, + from, + legendPosition = 'bottom', + loadingInitial = false, + showLinkToSignals = false, + showTotalSignalsCount = false, + stackByOptions, + to, + title = i18n.HISTOGRAM_HEADER, + updateDateRange, + }) => { + const [defaultNumberFormat] = useUiSetting$(DEFAULT_NUMBER_FORMAT); + const [totalSignalsObj, setTotalSignalsObj] = useState(defaultTotalSignalsObj); + const [selectedStackByOption, setSelectedStackByOption] = useState( + defaultStackByOption + ); + + const totalSignals = useMemo( + () => + i18n.SHOWING_SIGNALS( + numeral(totalSignalsObj.value).format(defaultNumberFormat), + totalSignalsObj.value, + totalSignalsObj.relation === 'gte' ? '>' : totalSignalsObj.relation === 'lte' ? '<' : '' + ), + [totalSignalsObj] + ); + + const setSelectedOptionCallback = useCallback((event: React.ChangeEvent) => { + setSelectedStackByOption( + stackByOptions?.find(co => co.value === event.target.value) ?? defaultStackByOption + ); + }, []); + + return ( + + + {stackByOptions && ( + + )} + {showLinkToSignals && ( + {i18n.VIEW_SIGNALS} + )} + + + + + ); + } +); + +SignalsHistogramPanel.displayName = 'SignalsHistogramPanel'; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/signals_histogram/helpers.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/signals_histogram/helpers.tsx new file mode 100644 index 0000000000000..ed503e9872f0a --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/signals_histogram/helpers.tsx @@ -0,0 +1,73 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { HistogramData, SignalsAggregation, SignalsBucket, SignalsGroupBucket } from '../types'; +import { SignalSearchResponse } from '../../../../../containers/detection_engine/signals/types'; +import * as i18n from '../translations'; + +export const formatSignalsData = ( + signalsData: SignalSearchResponse<{}, SignalsAggregation> | null +) => { + const groupBuckets: SignalsGroupBucket[] = + signalsData?.aggregations?.signalsByGrouping?.buckets ?? []; + return groupBuckets.reduce((acc, { key: group, signals }) => { + const signalsBucket: SignalsBucket[] = signals.buckets ?? []; + + return [ + ...acc, + ...signalsBucket.map(({ key, doc_count }: SignalsBucket) => ({ + x: key, + y: doc_count, + g: group, + })), + ]; + }, []); +}; + +export const getSignalsHistogramQuery = ( + stackByField: string, + from: number, + to: number, + additionalFilters: Array<{ + bool: { filter: unknown[]; should: unknown[]; must_not: unknown[]; must: unknown[] }; + }> +) => ({ + aggs: { + signalsByGrouping: { + terms: { + field: stackByField, + missing: stackByField.endsWith('.ip') ? '0.0.0.0' : i18n.ALL_OTHERS, + order: { + _count: 'desc', + }, + size: 10, + }, + aggs: { + signals: { + auto_date_histogram: { + field: '@timestamp', + buckets: 36, + }, + }, + }, + }, + }, + query: { + bool: { + filter: [ + ...additionalFilters, + { + range: { + '@timestamp': { + gte: from, + lte: to, + }, + }, + }, + ], + }, + }, +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/signals_histogram/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/signals_histogram/index.tsx new file mode 100644 index 0000000000000..218fcc3a70f79 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/signals_histogram/index.tsx @@ -0,0 +1,122 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + Axis, + Chart, + getAxisId, + getSpecId, + HistogramBarSeries, + niceTimeFormatByDay, + Position, + Settings, + timeFormatter, +} from '@elastic/charts'; +import React, { useEffect, useMemo } from 'react'; +import { EuiLoadingContent } from '@elastic/eui'; +import { isEmpty } from 'lodash/fp'; +import { useQuerySignals } from '../../../../../containers/detection_engine/signals/use_query'; +import { Query } from '../../../../../../../../../../src/plugins/data/common/query'; +import { esFilters, esQuery } from '../../../../../../../../../../src/plugins/data/common/es_query'; +import { SignalsAggregation, SignalsTotal } from '../types'; +import { formatSignalsData, getSignalsHistogramQuery } from './helpers'; +import { useTheme } from '../../../../../components/charts/common'; +import { useKibana } from '../../../../../lib/kibana'; + +interface HistogramSignalsProps { + filters?: esFilters.Filter[]; + from: number; + legendPosition?: Position; + loadingInitial: boolean; + query?: Query; + setTotalSignalsCount: React.Dispatch; + stackByField: string; + to: number; + updateDateRange: (min: number, max: number) => void; +} + +export const SignalsHistogram = React.memo( + ({ + to, + from, + query, + filters, + legendPosition = 'bottom', + loadingInitial, + setTotalSignalsCount, + stackByField, + updateDateRange, + }) => { + const [isLoadingSignals, signalsData, setQuery] = useQuerySignals<{}, SignalsAggregation>( + getSignalsHistogramQuery(stackByField, from, to, []) + ); + const theme = useTheme(); + const kibana = useKibana(); + + const formattedSignalsData = useMemo(() => formatSignalsData(signalsData), [signalsData]); + + useEffect(() => { + setTotalSignalsCount( + signalsData?.hits.total ?? { + value: 0, + relation: 'eq', + } + ); + }, [signalsData]); + + useEffect(() => { + const converted = esQuery.buildEsQuery( + undefined, + query != null ? [query] : [], + filters?.filter(f => f.meta.disabled === false) ?? [], + { + ...esQuery.getEsQueryConfig(kibana.services.uiSettings), + dateFormatTZ: undefined, + } + ); + + setQuery( + getSignalsHistogramQuery(stackByField, from, to, !isEmpty(converted) ? [converted] : []) + ); + }, [stackByField, from, to, query, filters]); + + return ( + <> + {loadingInitial || isLoadingSignals ? ( + + ) : ( + + + + + + + + + + )} + + ); + } +); +SignalsHistogram.displayName = 'SignalsHistogram'; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/translations.ts new file mode 100644 index 0000000000000..0245b9968cc36 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/translations.ts @@ -0,0 +1,116 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const STACK_BY_LABEL = i18n.translate( + 'xpack.siem.detectionEngine.signals.histogram.stackByOptions.stackByLabel', + { + defaultMessage: 'Stack by', + } +); + +export const STACK_BY_RISK_SCORES = i18n.translate( + 'xpack.siem.detectionEngine.signals.histogram.stackByOptions.riskScoresDropDown', + { + defaultMessage: 'Risk scores', + } +); + +export const STACK_BY_SEVERITIES = i18n.translate( + 'xpack.siem.detectionEngine.signals.histogram.stackByOptions.severitiesDropDown', + { + defaultMessage: 'Severities', + } +); + +export const STACK_BY_DESTINATION_IPS = i18n.translate( + 'xpack.siem.detectionEngine.signals.histogram.stackByOptions.destinationIpsDropDown', + { + defaultMessage: 'Top destination IPs', + } +); + +export const STACK_BY_SOURCE_IPS = i18n.translate( + 'xpack.siem.detectionEngine.signals.histogram.stackByOptions.sourceIpsDropDown', + { + defaultMessage: 'Top source IPs', + } +); + +export const STACK_BY_ACTIONS = i18n.translate( + 'xpack.siem.detectionEngine.signals.histogram.stackByOptions.eventActionsDropDown', + { + defaultMessage: 'Top event actions', + } +); + +export const STACK_BY_CATEGORIES = i18n.translate( + 'xpack.siem.detectionEngine.signals.histogram.stackByOptions.eventCategoriesDropDown', + { + defaultMessage: 'Top event categories', + } +); + +export const STACK_BY_HOST_NAMES = i18n.translate( + 'xpack.siem.detectionEngine.signals.histogram.stackByOptions.hostNamesDropDown', + { + defaultMessage: 'Top host names', + } +); + +export const STACK_BY_RULE_TYPES = i18n.translate( + 'xpack.siem.detectionEngine.signals.histogram.stackByOptions.ruleTypesDropDown', + { + defaultMessage: 'Top rule types', + } +); + +export const STACK_BY_RULE_NAMES = i18n.translate( + 'xpack.siem.detectionEngine.signals.histogram.stackByOptions.rulesDropDown', + { + defaultMessage: 'Top rules', + } +); + +export const STACK_BY_USERS = i18n.translate( + 'xpack.siem.detectionEngine.signals.histogram.stackByOptions.usersDropDown', + { + defaultMessage: 'Top users', + } +); + +export const HISTOGRAM_HEADER = i18n.translate( + 'xpack.siem.detectionEngine.signals.histogram.headerTitle', + { + defaultMessage: 'Signal detection frequency', + } +); + +export const ALL_OTHERS = i18n.translate( + 'xpack.siem.detectionEngine.signals.histogram.allOthersGroupingLabel', + { + defaultMessage: 'All others', + } +); + +export const VIEW_SIGNALS = i18n.translate( + 'xpack.siem.detectionEngine.signals.histogram.viewSignalsButtonLabel', + { + defaultMessage: 'View signals', + } +); + +export const SHOWING_SIGNALS = ( + totalSignalsFormatted: string, + totalSignals: number, + modifier: string +) => + i18n.translate('xpack.siem.detectionEngine.signals.histogram.showingSignalsTitle', { + values: { totalSignalsFormatted, totalSignals, modifier }, + defaultMessage: + 'Showing: {modifier}{totalSignalsFormatted} {totalSignals, plural, =1 {signal} other {signals}}', + }); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/types.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/types.ts new file mode 100644 index 0000000000000..4eb10852450ad --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_histogram_panel/types.ts @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export interface SignalsHistogramOption { + text: string; + value: string; +} + +export interface HistogramData { + x: number; + y: number; + g: string; +} + +export interface SignalsAggregation { + signalsByGrouping: { + buckets: SignalsGroupBucket[]; + }; +} + +export interface SignalsBucket { + key_as_string: string; + key: number; + doc_count: number; +} + +export interface SignalsGroupBucket { + key: string; + signals: { + buckets: SignalsBucket[]; + }; +} + +export interface SignalsTotal { + value: number; + relation: string; +} diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_info/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_info/index.tsx index 7f285a4a708c9..fc1110e382847 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_info/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_info/index.tsx @@ -9,7 +9,7 @@ import { FormattedRelative } from '@kbn/i18n/react'; import React, { useState, useEffect } from 'react'; import { useQuerySignals } from '../../../../containers/detection_engine/signals/use_query'; -import { buildlastSignalsQuery } from './query.dsl'; +import { buildLastSignalsQuery } from './query.dsl'; import { Aggs } from './types'; interface SignalInfo { @@ -22,18 +22,11 @@ export const useSignalInfo = ({ ruleId = null }: SignalInfo): Return => { const [lastSignals, setLastSignals] = useState( ); - const [totalSignals, setTotalSignals] = useState( + const [totalSignals, setTotalSignals] = useState( ); - let query = ''; - try { - query = JSON.stringify(buildlastSignalsQuery(ruleId)); - } catch { - query = ''; - } - - const [, signals] = useQuerySignals(query); + const [loading, signals] = useQuerySignals(buildLastSignalsQuery(ruleId)); useEffect(() => { if (signals != null) { @@ -46,8 +39,11 @@ export const useSignalInfo = ({ ruleId = null }: SignalInfo): Return => { ) : null ); setTotalSignals(<>{mySignals.hits.total.value}); + } else { + setLastSignals(null); + setTotalSignals(null); } - }, [signals]); + }, [loading, signals]); return [lastSignals, totalSignals]; }; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_info/query.dsl.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_info/query.dsl.ts index 0b14aa17a9450..8cb07a4f8e6b5 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_info/query.dsl.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/signals_info/query.dsl.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -export const buildlastSignalsQuery = (ruleId: string | undefined | null) => { +export const buildLastSignalsQuery = (ruleId: string | undefined | null) => { const queryFilter = [ { bool: { should: [{ match: { 'signal.status': 'open' } }], minimum_should_match: 1 }, diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/user_info/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/user_info/index.tsx new file mode 100644 index 0000000000000..bbaccb7882484 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/components/user_info/index.tsx @@ -0,0 +1,238 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { noop } from 'lodash/fp'; +import React, { useEffect, useReducer, Dispatch, createContext, useContext } from 'react'; + +import { usePrivilegeUser } from '../../../../containers/detection_engine/signals/use_privilege_user'; +import { useSignalIndex } from '../../../../containers/detection_engine/signals/use_signal_index'; +import { useKibana } from '../../../../lib/kibana'; + +export interface State { + canUserCRUD: boolean | null; + hasIndexManage: boolean | null; + hasIndexWrite: boolean | null; + hasManageApiKey: boolean | null; + isSignalIndexExists: boolean | null; + isAuthenticated: boolean | null; + loading: boolean; + signalIndexName: string | null; +} + +const initialState: State = { + canUserCRUD: null, + hasIndexManage: null, + hasIndexWrite: null, + hasManageApiKey: null, + isSignalIndexExists: null, + isAuthenticated: null, + loading: true, + signalIndexName: null, +}; + +export type Action = + | { type: 'updateLoading'; loading: boolean } + | { + type: 'updateHasManageApiKey'; + hasManageApiKey: boolean | null; + } + | { + type: 'updateHasIndexManage'; + hasIndexManage: boolean | null; + } + | { + type: 'updateHasIndexWrite'; + hasIndexWrite: boolean | null; + } + | { + type: 'updateIsSignalIndexExists'; + isSignalIndexExists: boolean | null; + } + | { + type: 'updateIsAuthenticated'; + isAuthenticated: boolean | null; + } + | { + type: 'updateCanUserCRUD'; + canUserCRUD: boolean | null; + } + | { + type: 'updateSignalIndexName'; + signalIndexName: string | null; + }; + +export const userInfoReducer = (state: State, action: Action): State => { + switch (action.type) { + case 'updateLoading': { + return { + ...state, + loading: action.loading, + }; + } + case 'updateHasIndexManage': { + return { + ...state, + hasIndexManage: action.hasIndexManage, + }; + } + case 'updateHasIndexWrite': { + return { + ...state, + hasIndexWrite: action.hasIndexWrite, + }; + } + case 'updateHasManageApiKey': { + return { + ...state, + hasManageApiKey: action.hasManageApiKey, + }; + } + case 'updateIsSignalIndexExists': { + return { + ...state, + isSignalIndexExists: action.isSignalIndexExists, + }; + } + case 'updateIsAuthenticated': { + return { + ...state, + isAuthenticated: action.isAuthenticated, + }; + } + case 'updateCanUserCRUD': { + return { + ...state, + canUserCRUD: action.canUserCRUD, + }; + } + case 'updateSignalIndexName': { + return { + ...state, + signalIndexName: action.signalIndexName, + }; + } + default: + return state; + } +}; + +const StateUserInfoContext = createContext<[State, Dispatch]>([initialState, () => noop]); + +const useUserData = () => useContext(StateUserInfoContext); + +interface ManageUserInfoProps { + children: React.ReactNode; +} + +export const ManageUserInfo = ({ children }: ManageUserInfoProps) => ( + + {children} + +); + +export const useUserInfo = (): State => { + const [ + { + canUserCRUD, + hasIndexManage, + hasIndexWrite, + hasManageApiKey, + isSignalIndexExists, + isAuthenticated, + loading, + signalIndexName, + }, + dispatch, + ] = useUserData(); + const { + loading: privilegeLoading, + isAuthenticated: isApiAuthenticated, + hasIndexManage: hasApiIndexManage, + hasIndexWrite: hasApiIndexWrite, + hasManageApiKey: hasApiManageApiKey, + } = usePrivilegeUser(); + const [ + indexNameLoading, + isApiSignalIndexExists, + apiSignalIndexName, + createSignalIndex, + ] = useSignalIndex(); + + const uiCapabilities = useKibana().services.application.capabilities; + const capabilitiesCanUserCRUD: boolean = + typeof uiCapabilities.siem.crud === 'boolean' ? uiCapabilities.siem.crud : false; + + useEffect(() => { + if (loading !== privilegeLoading || indexNameLoading) { + dispatch({ type: 'updateLoading', loading: privilegeLoading || indexNameLoading }); + } + }, [loading, privilegeLoading, indexNameLoading]); + + useEffect(() => { + if (hasIndexManage !== hasApiIndexManage && hasApiIndexManage != null) { + dispatch({ type: 'updateHasIndexManage', hasIndexManage: hasApiIndexManage }); + } + }, [hasIndexManage, hasApiIndexManage]); + + useEffect(() => { + if (hasIndexWrite !== hasApiIndexWrite && hasApiIndexWrite != null) { + dispatch({ type: 'updateHasIndexWrite', hasIndexWrite: hasApiIndexWrite }); + } + }, [hasIndexWrite, hasApiIndexWrite]); + + useEffect(() => { + if (hasManageApiKey !== hasApiManageApiKey && hasApiManageApiKey != null) { + dispatch({ type: 'updateHasManageApiKey', hasManageApiKey: hasApiManageApiKey }); + } + }, [hasManageApiKey, hasApiManageApiKey]); + + useEffect(() => { + if (isSignalIndexExists !== isApiSignalIndexExists && isApiSignalIndexExists != null) { + dispatch({ type: 'updateIsSignalIndexExists', isSignalIndexExists: isApiSignalIndexExists }); + } + }, [isSignalIndexExists, isApiSignalIndexExists]); + + useEffect(() => { + if (isAuthenticated !== isApiAuthenticated && isApiAuthenticated != null) { + dispatch({ type: 'updateIsAuthenticated', isAuthenticated: isApiAuthenticated }); + } + }, [isAuthenticated, isApiAuthenticated]); + + useEffect(() => { + if (canUserCRUD !== capabilitiesCanUserCRUD && capabilitiesCanUserCRUD != null) { + dispatch({ type: 'updateCanUserCRUD', canUserCRUD: capabilitiesCanUserCRUD }); + } + }, [canUserCRUD, capabilitiesCanUserCRUD]); + + useEffect(() => { + if (signalIndexName !== apiSignalIndexName && apiSignalIndexName != null) { + dispatch({ type: 'updateSignalIndexName', signalIndexName: apiSignalIndexName }); + } + }, [signalIndexName, apiSignalIndexName]); + + useEffect(() => { + if ( + isAuthenticated && + hasIndexManage && + isSignalIndexExists != null && + !isSignalIndexExists && + createSignalIndex != null + ) { + createSignalIndex(); + } + }, [createSignalIndex, isAuthenticated, isSignalIndexExists, hasIndexManage]); + + return { + loading, + isSignalIndexExists, + isAuthenticated, + canUserCRUD, + hasIndexManage, + hasIndexWrite, + hasManageApiKey, + signalIndexName, + }; +}; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine.tsx index 6e6b71729b07e..e638cf89e77bb 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine.tsx @@ -5,9 +5,11 @@ */ import { EuiButton, EuiSpacer } from '@elastic/eui'; -import React from 'react'; +import React, { useCallback } from 'react'; import { StickyContainer } from 'react-sticky'; +import { connect } from 'react-redux'; +import { ActionCreator } from 'typescript-fsa'; import { FiltersGlobal } from '../../components/filters_global'; import { HeaderPage } from '../../components/header_page'; import { SiemSearchBar } from '../../components/search_bar'; @@ -16,60 +18,162 @@ import { GlobalTime } from '../../containers/global_time'; import { indicesExistOrDataTemporarilyUnavailable, WithSource } from '../../containers/source'; import { SpyRoute } from '../../utils/route/spy_routes'; -import { SignalsTable } from './components/signals'; -import { SignalsCharts } from './components/signals_chart'; +import { Query } from '../../../../../../../src/plugins/data/common/query'; +import { esFilters } from '../../../../../../../src/plugins/data/common/es_query'; +import { State } from '../../store'; +import { inputsSelectors } from '../../store/inputs'; +import { setAbsoluteRangeDatePicker as dispatchSetAbsoluteRangeDatePicker } from '../../store/inputs/actions'; +import { InputsModelId } from '../../store/inputs/constants'; +import { InputsRange } from '../../store/inputs/model'; import { useSignalInfo } from './components/signals_info'; +import { SignalsTable } from './components/signals'; +import { NoWriteSignalsCallOut } from './components/no_write_signals_callout'; +import { SignalsHistogramPanel } from './components/signals_histogram_panel'; +import { signalsHistogramOptions } from './components/signals_histogram_panel/config'; +import { useUserInfo } from './components/user_info'; import { DetectionEngineEmptyPage } from './detection_engine_empty_page'; +import { DetectionEngineNoIndex } from './detection_engine_no_signal_index'; +import { DetectionEngineUserUnauthenticated } from './detection_engine_user_unauthenticated'; import * as i18n from './translations'; -export const DetectionEngineComponent = React.memo(() => { - const [lastSignals] = useSignalInfo({}); - return ( - <> - - {({ indicesExist, indexPattern }) => { - return indicesExistOrDataTemporarilyUnavailable(indicesExist) ? ( - - - - +interface ReduxProps { + filters: esFilters.Filter[]; + query: Query; +} - - ; +} + +type DetectionEngineComponentProps = ReduxProps & DispatchProps; + +const DetectionEngineComponent = React.memo( + ({ filters, query, setAbsoluteRangeDatePicker }) => { + const { + loading, + isSignalIndexExists, + isAuthenticated: isUserAuthenticated, + canUserCRUD, + signalIndexName, + hasIndexWrite, + } = useUserInfo(); + + const [lastSignals] = useSignalInfo({}); + + const updateDateRangeCallback = useCallback( + (min: number, max: number) => { + setAbsoluteRangeDatePicker({ id: 'global', from: min, to: max }); + }, + [setAbsoluteRangeDatePicker] + ); + + if (isUserAuthenticated != null && !isUserAuthenticated && !loading) { + return ( + + + + + ); + } + if (isSignalIndexExists != null && !isSignalIndexExists && !loading) { + return ( + + + + + ); + } + return ( + <> + {hasIndexWrite != null && !hasIndexWrite && } + + {({ indicesExist, indexPattern }) => { + return indicesExistOrDataTemporarilyUnavailable(indicesExist) ? ( + + + + + + + {i18n.LAST_SIGNAL} + {': '} + {lastSignals} + + ) + } + title={i18n.PAGE_TITLE} + > + + {i18n.BUTTON_MANAGE_RULES} + + + + + {({ to, from }) => ( <> - {i18n.LAST_SIGNAL} - {': '} - {lastSignals} + + + + + - ) - } - title={i18n.PAGE_TITLE} - > - - {i18n.BUTTON_MANAGE_RULES} - - - - - - - {({ to, from }) => } + )} + + + + ) : ( + + + - - ) : ( - - - - - ); - }} - - - - - ); -}); + ); + }} + + + + ); + } +); DetectionEngineComponent.displayName = 'DetectionEngineComponent'; + +const makeMapStateToProps = () => { + const getGlobalInputs = inputsSelectors.globalSelector(); + return (state: State) => { + const globalInputs: InputsRange = getGlobalInputs(state); + const { query, filters } = globalInputs; + + return { + query, + filters, + }; + }; +}; + +export const DetectionEngine = connect(makeMapStateToProps, { + setAbsoluteRangeDatePicker: dispatchSetAbsoluteRangeDatePicker, +})(DetectionEngineComponent); + +DetectionEngine.displayName = 'DetectionEngine'; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine_no_signal_index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine_no_signal_index.tsx new file mode 100644 index 0000000000000..713bd6239d80e --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine_no_signal_index.tsx @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { documentationLinks } from 'ui/documentation_links'; + +import { EmptyPage } from '../../components/empty_page'; +import * as i18n from './translations'; + +export const DetectionEngineNoIndex = React.memo(() => ( + +)); + +DetectionEngineNoIndex.displayName = 'DetectionEngineNoIndex'; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine_user_unauthenticated.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine_user_unauthenticated.tsx new file mode 100644 index 0000000000000..bd3876b810a73 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/detection_engine_user_unauthenticated.tsx @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { documentationLinks } from 'ui/documentation_links'; + +import { EmptyPage } from '../../components/empty_page'; +import * as i18n from './translations'; + +export const DetectionEngineUserUnauthenticated = React.memo(() => ( + +)); + +DetectionEngineUserUnauthenticated.displayName = 'DetectionEngineUserUnauthenticated'; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/index.tsx index 21ebac2b4d337..c4e83429aebdb 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/index.tsx @@ -8,38 +8,41 @@ import React from 'react'; import { Redirect, Route, Switch, RouteComponentProps } from 'react-router-dom'; import { CreateRuleComponent } from './rules/create'; -import { DetectionEngineComponent } from './detection_engine'; +import { DetectionEngine } from './detection_engine'; import { EditRuleComponent } from './rules/edit'; -import { RuleDetailsComponent } from './rules/details'; +import { RuleDetails } from './rules/details'; import { RulesComponent } from './rules'; +import { ManageUserInfo } from './components/user_info'; const detectionEnginePath = `/:pageName(detection-engine)`; type Props = Partial> & { url: string }; export const DetectionEngineContainer = React.memo(() => ( - - - - - - - - - - - - - - - - - ( - - )} - /> - + + + + + + + + + + + + + + + + + + ( + + )} + /> + + )); DetectionEngineContainer.displayName = 'DetectionEngineContainer'; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/__mocks__/mock.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/__mocks__/mock.ts new file mode 100644 index 0000000000000..3762cb0a4ba07 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/__mocks__/mock.ts @@ -0,0 +1,154 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Rule, RuleError } from '../../../../../containers/detection_engine/rules'; +import { TableData } from '../../types'; + +export const mockRule = (id: string): Rule => ({ + created_at: '2020-01-10T21:11:45.839Z', + updated_at: '2020-01-10T21:11:45.839Z', + created_by: 'elastic', + description: '24/7', + enabled: true, + false_positives: [], + filters: [], + from: 'now-300s', + id, + immutable: false, + index: ['auditbeat-*'], + interval: '5m', + rule_id: 'b5ba41ab-aaf3-4f43-971b-bdf9434ce0ea', + language: 'kuery', + output_index: '.siem-signals-default', + max_signals: 100, + risk_score: 21, + name: 'Home Grown!', + query: '', + references: [], + saved_id: "Garrett's IP", + timeline_id: '86aa74d0-2136-11ea-9864-ebc8cc1cb8c2', + timeline_title: 'Untitled timeline', + meta: { from: '0m' }, + severity: 'low', + updated_by: 'elastic', + tags: [], + to: 'now', + type: 'saved_query', + threats: [], + version: 1, +}); + +export const mockRuleError = (id: string): RuleError => ({ + rule_id: id, + error: { status_code: 404, message: `id: "${id}" not found` }, +}); + +export const mockRules: Rule[] = [ + mockRule('abe6c564-050d-45a5-aaf0-386c37dd1f61'), + mockRule('63f06f34-c181-4b2d-af35-f2ace572a1ee'), +]; +export const mockTableData: TableData[] = [ + { + activate: true, + id: 'abe6c564-050d-45a5-aaf0-386c37dd1f61', + immutable: false, + isLoading: false, + lastCompletedRun: undefined, + lastResponse: { type: '—' }, + method: 'saved_query', + rule: { + href: '#/detection-engine/rules/id/abe6c564-050d-45a5-aaf0-386c37dd1f61', + name: 'Home Grown!', + status: 'Status Placeholder', + }, + rule_id: 'b5ba41ab-aaf3-4f43-971b-bdf9434ce0ea', + severity: 'low', + sourceRule: { + created_at: '2020-01-10T21:11:45.839Z', + created_by: 'elastic', + description: '24/7', + enabled: true, + false_positives: [], + filters: [], + from: 'now-300s', + id: 'abe6c564-050d-45a5-aaf0-386c37dd1f61', + immutable: false, + index: ['auditbeat-*'], + interval: '5m', + language: 'kuery', + max_signals: 100, + meta: { from: '0m' }, + name: 'Home Grown!', + output_index: '.siem-signals-default', + query: '', + references: [], + risk_score: 21, + rule_id: 'b5ba41ab-aaf3-4f43-971b-bdf9434ce0ea', + saved_id: "Garrett's IP", + severity: 'low', + tags: [], + threats: [], + timeline_id: '86aa74d0-2136-11ea-9864-ebc8cc1cb8c2', + timeline_title: 'Untitled timeline', + to: 'now', + type: 'saved_query', + updated_at: '2020-01-10T21:11:45.839Z', + updated_by: 'elastic', + version: 1, + }, + tags: [], + }, + { + activate: true, + id: '63f06f34-c181-4b2d-af35-f2ace572a1ee', + immutable: false, + isLoading: false, + lastCompletedRun: undefined, + lastResponse: { type: '—' }, + method: 'saved_query', + rule: { + href: '#/detection-engine/rules/id/63f06f34-c181-4b2d-af35-f2ace572a1ee', + name: 'Home Grown!', + status: 'Status Placeholder', + }, + rule_id: 'b5ba41ab-aaf3-4f43-971b-bdf9434ce0ea', + severity: 'low', + sourceRule: { + created_at: '2020-01-10T21:11:45.839Z', + created_by: 'elastic', + description: '24/7', + enabled: true, + false_positives: [], + filters: [], + from: 'now-300s', + id: '63f06f34-c181-4b2d-af35-f2ace572a1ee', + immutable: false, + index: ['auditbeat-*'], + interval: '5m', + language: 'kuery', + max_signals: 100, + meta: { from: '0m' }, + name: 'Home Grown!', + output_index: '.siem-signals-default', + query: '', + references: [], + risk_score: 21, + rule_id: 'b5ba41ab-aaf3-4f43-971b-bdf9434ce0ea', + saved_id: "Garrett's IP", + severity: 'low', + tags: [], + threats: [], + timeline_id: '86aa74d0-2136-11ea-9864-ebc8cc1cb8c2', + timeline_title: 'Untitled timeline', + to: 'now', + type: 'saved_query', + updated_at: '2020-01-10T21:11:45.839Z', + updated_by: 'elastic', + version: 1, + }, + tags: [], + }, +]; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/actions.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/actions.tsx index 4de6136e9d3de..24e3cfde1e448 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/actions.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/actions.tsx @@ -5,7 +5,7 @@ */ import * as H from 'history'; -import React from 'react'; +import React, { Dispatch } from 'react'; import { DETECTION_ENGINE_PAGE_NAME } from '../../../../components/link_to/redirect_to_detection_engine'; import { @@ -16,8 +16,13 @@ import { } from '../../../../containers/detection_engine/rules'; import { Action } from './reducer'; +import { ActionToaster, displayErrorToast } from '../../../../components/toasters'; + +import * as i18n from '../translations'; +import { bucketRulesResponse } from './helpers'; + export const editRuleAction = (rule: Rule, history: H.History) => { - history.push(`/${DETECTION_ENGINE_PAGE_NAME}/rules/${rule.id}/edit`); + history.push(`/${DETECTION_ENGINE_PAGE_NAME}/rules/id/${rule.id}/edit`); }; export const runRuleAction = () => {}; @@ -25,12 +30,16 @@ export const runRuleAction = () => {}; export const duplicateRuleAction = async ( rule: Rule, dispatch: React.Dispatch, - kbnVersion: string + dispatchToaster: Dispatch ) => { - dispatch({ type: 'updateLoading', ids: [rule.id], isLoading: true }); - const duplicatedRule = await duplicateRules({ rules: [rule], kbnVersion }); - dispatch({ type: 'updateLoading', ids: [rule.id], isLoading: false }); - dispatch({ type: 'updateRules', rules: duplicatedRule, appendRuleId: rule.id }); + try { + dispatch({ type: 'updateLoading', ids: [rule.id], isLoading: true }); + const duplicatedRule = await duplicateRules({ rules: [rule] }); + dispatch({ type: 'updateLoading', ids: [rule.id], isLoading: false }); + dispatch({ type: 'updateRules', rules: duplicatedRule, appendRuleId: rule.id }); + } catch (e) { + displayErrorToast(i18n.DUPLICATE_RULE_ERROR, [e.message], dispatchToaster); + } }; export const exportRulesAction = async (rules: Rule[], dispatch: React.Dispatch) => { @@ -40,25 +49,59 @@ export const exportRulesAction = async (rules: Rule[], dispatch: React.Dispatch< export const deleteRulesAction = async ( ids: string[], dispatch: React.Dispatch, - kbnVersion: string + dispatchToaster: Dispatch ) => { - dispatch({ type: 'updateLoading', ids, isLoading: true }); - const deletedRules = await deleteRules({ ids, kbnVersion }); - dispatch({ type: 'deleteRules', rules: deletedRules }); + try { + dispatch({ type: 'updateLoading', ids, isLoading: true }); + + const response = await deleteRules({ ids }); + const { rules, errors } = bucketRulesResponse(response); + + dispatch({ type: 'deleteRules', rules }); + + if (errors.length > 0) { + displayErrorToast( + i18n.BATCH_ACTION_DELETE_SELECTED_ERROR(ids.length), + errors.map(e => e.error.message), + dispatchToaster + ); + } + } catch (e) { + displayErrorToast( + i18n.BATCH_ACTION_DELETE_SELECTED_ERROR(ids.length), + [e.message], + dispatchToaster + ); + } }; export const enableRulesAction = async ( ids: string[], enabled: boolean, dispatch: React.Dispatch, - kbnVersion: string + dispatchToaster: Dispatch ) => { + const errorTitle = enabled + ? i18n.BATCH_ACTION_ACTIVATE_SELECTED_ERROR(ids.length) + : i18n.BATCH_ACTION_DEACTIVATE_SELECTED_ERROR(ids.length); + try { dispatch({ type: 'updateLoading', ids, isLoading: true }); - const updatedRules = await enableRules({ ids, enabled, kbnVersion }); - dispatch({ type: 'updateRules', rules: updatedRules }); - } catch { - // TODO Add error toast support to actions (and @throw jsdoc to api calls) + + const response = await enableRules({ ids, enabled }); + const { rules, errors } = bucketRulesResponse(response); + + dispatch({ type: 'updateRules', rules }); + + if (errors.length > 0) { + displayErrorToast( + errorTitle, + errors.map(e => e.error.message), + dispatchToaster + ); + } + } catch (e) { + displayErrorToast(errorTitle, [e.message], dispatchToaster); dispatch({ type: 'updateLoading', ids, isLoading: false }); } }; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/batch_actions.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/batch_actions.tsx index c8fb9d98fde6a..3356ef101677d 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/batch_actions.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/batch_actions.tsx @@ -5,21 +5,23 @@ */ import { EuiContextMenuItem } from '@elastic/eui'; -import React from 'react'; +import React, { Dispatch } from 'react'; import * as i18n from '../translations'; import { TableData } from '../types'; import { Action } from './reducer'; import { deleteRulesAction, enableRulesAction, exportRulesAction } from './actions'; +import { ActionToaster } from '../../../../components/toasters'; export const getBatchItems = ( selectedState: TableData[], - dispatch: React.Dispatch, - closePopover: () => void, - kbnVersion: string + dispatch: Dispatch, + dispatchToaster: Dispatch, + closePopover: () => void ) => { const containsEnabled = selectedState.some(v => v.activate); const containsDisabled = selectedState.some(v => !v.activate); const containsLoading = selectedState.some(v => v.isLoading); + const containsImmutable = selectedState.some(v => v.immutable); return [ { closePopover(); const deactivatedIds = selectedState.filter(s => !s.activate).map(s => s.id); - await enableRulesAction(deactivatedIds, true, dispatch, kbnVersion); + await enableRulesAction(deactivatedIds, true, dispatch, dispatchToaster); }} > {i18n.BATCH_ACTION_ACTIVATE_SELECTED} @@ -41,7 +43,7 @@ export const getBatchItems = ( onClick={async () => { closePopover(); const activatedIds = selectedState.filter(s => s.activate).map(s => s.id); - await enableRulesAction(activatedIds, false, dispatch, kbnVersion); + await enableRulesAction(activatedIds, false, dispatch, dispatchToaster); }} > {i18n.BATCH_ACTION_DEACTIVATE_SELECTED} @@ -73,13 +75,14 @@ export const getBatchItems = ( { closePopover(); await deleteRulesAction( selectedState.map(({ sourceRule: { id } }) => id), dispatch, - kbnVersion + dispatchToaster ); }} > diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/columns.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/columns.tsx index ad5d210efa42d..0c1804f26ecdd 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/columns.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/columns.tsx @@ -4,9 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ +/* eslint-disable react/display-name */ + import { EuiBadge, - EuiHealth, EuiIconTip, EuiLink, EuiTextColor, @@ -14,8 +15,7 @@ import { EuiTableActionsColumnType, } from '@elastic/eui'; import * as H from 'history'; -import React from 'react'; -import euiLightVars from '@elastic/eui/dist/eui_theme_light.json'; +import React, { Dispatch } from 'react'; import { getEmptyTagValue } from '../../../../components/empty_value'; import { deleteRulesAction, @@ -30,8 +30,14 @@ import { TableData } from '../types'; import * as i18n from '../translations'; import { PreferenceFormattedDate } from '../../../../components/formatted_date'; import { RuleSwitch } from '../components/rule_switch'; +import { SeverityBadge } from '../components/severity_badge'; +import { ActionToaster } from '../../../../components/toasters'; -const getActions = (dispatch: React.Dispatch, kbnVersion: string, history: H.History) => [ +const getActions = ( + dispatch: React.Dispatch, + dispatchToaster: Dispatch, + history: H.History +) => [ { description: i18n.EDIT_RULE_SETTINGS, icon: 'visControls', @@ -50,7 +56,8 @@ const getActions = (dispatch: React.Dispatch, kbnVersion: string, histor description: i18n.DUPLICATE_RULE, icon: 'copy', name: i18n.DUPLICATE_RULE, - onClick: (rowItem: TableData) => duplicateRuleAction(rowItem.sourceRule, dispatch, kbnVersion), + onClick: (rowItem: TableData) => + duplicateRuleAction(rowItem.sourceRule, dispatch, dispatchToaster), }, { description: i18n.EXPORT_RULE, @@ -62,116 +69,113 @@ const getActions = (dispatch: React.Dispatch, kbnVersion: string, histor description: i18n.DELETE_RULE, icon: 'trash', name: i18n.DELETE_RULE, - onClick: (rowItem: TableData) => deleteRulesAction([rowItem.id], dispatch, kbnVersion), + onClick: (rowItem: TableData) => deleteRulesAction([rowItem.id], dispatch, dispatchToaster), + enabled: (rowItem: TableData) => !rowItem.immutable, }, ]; +type RulesColumns = EuiBasicTableColumn | EuiTableActionsColumnType; + // Michael: Are we able to do custom, in-table-header filters, as shown in my wireframes? export const getColumns = ( dispatch: React.Dispatch, - kbnVersion: string, - history: H.History -): Array | EuiTableActionsColumnType> => [ - { - field: 'rule', - name: i18n.COLUMN_RULE, - render: (value: TableData['rule']) => {value.name}, - truncateText: true, - width: '24%', - }, - { - field: 'method', - name: i18n.COLUMN_METHOD, - truncateText: true, - }, - { - field: 'severity', - name: i18n.COLUMN_SEVERITY, - render: (value: TableData['severity']) => ( - - {value} - - ), - truncateText: true, - }, - { - field: 'lastCompletedRun', - name: i18n.COLUMN_LAST_COMPLETE_RUN, - render: (value: TableData['lastCompletedRun']) => { - return value == null ? ( - getEmptyTagValue() - ) : ( - - ); + dispatchToaster: Dispatch, + history: H.History, + hasNoPermissions: boolean +): RulesColumns[] => { + const cols: RulesColumns[] = [ + { + field: 'rule', + name: i18n.COLUMN_RULE, + render: (value: TableData['rule']) => {value.name}, + truncateText: true, + width: '24%', }, - sortable: true, - truncateText: true, - width: '16%', - }, - { - field: 'lastResponse', - name: i18n.COLUMN_LAST_RESPONSE, - render: (value: TableData['lastResponse']) => { - return value == null ? ( - getEmptyTagValue() - ) : ( - <> - {value.type === 'Fail' ? ( - - {value.type} - - ) : ( - {value.type} - )} - - ); + { + field: 'method', + name: i18n.COLUMN_METHOD, + truncateText: true, }, - truncateText: true, - }, - { - field: 'tags', - name: i18n.COLUMN_TAGS, - render: (value: TableData['tags']) => ( -
- <> - {value.map((tag, i) => ( - - {tag} - - ))} - -
- ), - truncateText: true, - width: '20%', - }, - { - align: 'center', - field: 'activate', - name: i18n.COLUMN_ACTIVATE, - render: (value: TableData['activate'], item: TableData) => ( - - ), - sortable: true, - width: '85px', - }, - { - actions: getActions(dispatch, kbnVersion, history), - width: '40px', - } as EuiTableActionsColumnType, -]; + { + field: 'severity', + name: i18n.COLUMN_SEVERITY, + render: (value: TableData['severity']) => , + truncateText: true, + }, + { + field: 'lastCompletedRun', + name: i18n.COLUMN_LAST_COMPLETE_RUN, + render: (value: TableData['lastCompletedRun']) => { + return value == null ? ( + getEmptyTagValue() + ) : ( + + ); + }, + sortable: true, + truncateText: true, + width: '16%', + }, + { + field: 'lastResponse', + name: i18n.COLUMN_LAST_RESPONSE, + render: (value: TableData['lastResponse']) => { + return value == null ? ( + getEmptyTagValue() + ) : ( + <> + {value.type === 'Fail' ? ( + + {value.type} + + ) : ( + {value.type} + )} + + ); + }, + truncateText: true, + }, + { + field: 'tags', + name: i18n.COLUMN_TAGS, + render: (value: TableData['tags']) => ( +
+ <> + {value.map((tag, i) => ( + + {tag} + + ))} + +
+ ), + truncateText: true, + width: '20%', + }, + { + align: 'center', + field: 'activate', + name: i18n.COLUMN_ACTIVATE, + render: (value: TableData['activate'], item: TableData) => ( + + ), + sortable: true, + width: '85px', + }, + ]; + const actions: RulesColumns[] = [ + { + actions: getActions(dispatch, dispatchToaster, history), + width: '40px', + } as EuiTableActionsColumnType, + ]; + + return hasNoPermissions ? cols : [...cols, ...actions]; +}; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/helpers.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/helpers.test.tsx new file mode 100644 index 0000000000000..e925161444e42 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/helpers.test.tsx @@ -0,0 +1,61 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { bucketRulesResponse, formatRules } from './helpers'; +import { mockRule, mockRuleError, mockRules, mockTableData } from './__mocks__/mock'; +import uuid from 'uuid'; +import { Rule, RuleError } from '../../../../containers/detection_engine/rules'; + +describe('AllRulesTable Helpers', () => { + const mockRule1: Readonly = mockRule(uuid.v4()); + const mockRule2: Readonly = mockRule(uuid.v4()); + const mockRuleError1: Readonly = mockRuleError(uuid.v4()); + const mockRuleError2: Readonly = mockRuleError(uuid.v4()); + + describe('formatRules', () => { + test('formats rules with no selection', () => { + const formattedRules = formatRules(mockRules); + expect(formattedRules).toEqual(mockTableData); + }); + + test('formats rules with selection', () => { + const mockTableDataWithSelected = [...mockTableData]; + mockTableDataWithSelected[0].isLoading = true; + const formattedRules = formatRules(mockRules, [mockRules[0].id]); + expect(formattedRules).toEqual(mockTableDataWithSelected); + }); + }); + + describe('bucketRulesResponse', () => { + test('buckets empty response', () => { + const bucketedResponse = bucketRulesResponse([]); + expect(bucketedResponse).toEqual({ rules: [], errors: [] }); + }); + + test('buckets all error response', () => { + const bucketedResponse = bucketRulesResponse([mockRuleError1, mockRuleError2]); + expect(bucketedResponse).toEqual({ rules: [], errors: [mockRuleError1, mockRuleError2] }); + }); + + test('buckets all success response', () => { + const bucketedResponse = bucketRulesResponse([mockRule1, mockRule2]); + expect(bucketedResponse).toEqual({ rules: [mockRule1, mockRule2], errors: [] }); + }); + + test('buckets mixed success/error response', () => { + const bucketedResponse = bucketRulesResponse([ + mockRule1, + mockRuleError1, + mockRule2, + mockRuleError2, + ]); + expect(bucketedResponse).toEqual({ + rules: [mockRule1, mockRule2], + errors: [mockRuleError1, mockRuleError2], + }); + }); + }); +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/helpers.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/helpers.ts index 1909b75a85835..b18938920082d 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/helpers.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/helpers.ts @@ -4,16 +4,27 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Rule } from '../../../../containers/detection_engine/rules'; +import { + Rule, + RuleError, + RuleResponseBuckets, +} from '../../../../containers/detection_engine/rules'; import { TableData } from '../types'; import { getEmptyValue } from '../../../../components/empty_value'; +/** + * Formats rules into the correct format for the AllRulesTable + * + * @param rules as returned from the Rules API + * @param selectedIds ids of the currently selected rules + */ export const formatRules = (rules: Rule[], selectedIds?: string[]): TableData[] => rules.map(rule => ({ id: rule.id, + immutable: rule.immutable, rule_id: rule.rule_id, rule: { - href: `#/detection-engine/rules/${encodeURIComponent(rule.id)}`, + href: `#/detection-engine/rules/id/${encodeURIComponent(rule.id)}`, name: rule.name, status: 'Status Placeholder', }, @@ -28,3 +39,18 @@ export const formatRules = (rules: Rule[], selectedIds?: string[]): TableData[] sourceRule: rule, isLoading: selectedIds?.includes(rule.id) ?? false, })); + +/** + * Separates rules/errors from bulk rules API response (create/update/delete) + * + * @param response Array from bulk rules API + */ +export const bucketRulesResponse = (response: Array) => + response.reduce( + (acc, cv): RuleResponseBuckets => { + return 'error' in cv + ? { rules: [...acc.rules], errors: [...acc.errors, cv] } + : { rules: [...acc.rules, cv], errors: [...acc.errors] }; + }, + { rules: [], errors: [] } + ); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/index.tsx index 442360bbf1484..202be75f09e69 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/all/index.tsx @@ -11,7 +11,7 @@ import { EuiLoadingContent, EuiSpacer, } from '@elastic/eui'; -import React, { useCallback, useEffect, useReducer, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useReducer, useState } from 'react'; import { useHistory } from 'react-router-dom'; import uuid from 'uuid'; @@ -31,8 +31,6 @@ import { getBatchItems } from './batch_actions'; import { EuiBasicTableOnChange, TableData } from '../types'; import { allRulesReducer, State } from './reducer'; import * as i18n from '../translations'; -import { useUiSetting$ } from '../../../../lib/kibana'; -import { DEFAULT_KBN_VERSION } from '../../../../../common/constants'; import { JSONDownloader } from '../components/json_downloader'; import { useStateToaster } from '../../../../components/toasters'; @@ -62,7 +60,11 @@ const initialState: State = { * * Delete * * Import/Export */ -export const AllRules = React.memo<{ importCompleteToggle: boolean }>(importCompleteToggle => { +export const AllRules = React.memo<{ + hasNoPermissions: boolean; + importCompleteToggle: boolean; + loading: boolean; +}>(({ hasNoPermissions, importCompleteToggle, loading }) => { const [ { exportPayload, @@ -78,18 +80,39 @@ export const AllRules = React.memo<{ importCompleteToggle: boolean }>(importComp const history = useHistory(); const [isInitialLoad, setIsInitialLoad] = useState(true); const [isLoadingRules, rulesData] = useRules(pagination, filterOptions, refreshToggle); - const [kbnVersion] = useUiSetting$(DEFAULT_KBN_VERSION); const [, dispatchToaster] = useStateToaster(); const getBatchItemsPopoverContent = useCallback( (closePopover: () => void) => ( ), - [selectedItems, dispatch, kbnVersion] + [selectedItems, dispatch, dispatchToaster] ); + const tableOnChangeCallback = useCallback( + ({ page, sort }: EuiBasicTableOnChange) => { + dispatch({ + type: 'updatePagination', + pagination: { ...pagination, page: page.index + 1, perPage: page.size }, + }); + dispatch({ + type: 'updateFilterOptions', + filterOptions: { + ...filterOptions, + sortField: 'enabled', // Only enabled is supported for sorting currently + sortOrder: sort?.direction ?? 'desc', + }, + }); + }, + [dispatch, filterOptions, pagination] + ); + + const columns = useMemo(() => { + return getColumns(dispatch, dispatchToaster, history, hasNoPermissions); + }, [dispatch, dispatchToaster, history]); + useEffect(() => { dispatch({ type: 'loading', isLoading: isLoadingRules }); @@ -116,6 +139,15 @@ export const AllRules = React.memo<{ importCompleteToggle: boolean }>(importComp }); }, [rulesData]); + const euiBasicTableSelectionProps = useMemo( + () => ({ + selectable: (item: TableData) => !item.isLoading, + onSelectionChange: (selected: TableData[]) => + dispatch({ type: 'setSelected', selectedItems: selected }), + }), + [] + ); + return ( <> (importComp {i18n.SELECTED_RULES(selectedItems.length)} - - {i18n.BATCH_ACTIONS} - + {!hasNoPermissions && ( + + {i18n.BATCH_ACTIONS} + + )} (importComp { - dispatch({ - type: 'updatePagination', - pagination: { ...pagination, page: page.index + 1, perPage: page.size }, - }); - dispatch({ - type: 'updateFilterOptions', - filterOptions: { - ...filterOptions, - sortField: 'enabled', // Only enabled is supported for sorting currently - sortOrder: sort!.direction, - }, - }); - }} + onChange={tableOnChangeCallback} pagination={{ pageIndex: pagination.page - 1, pageSize: pagination.perPage, totalItemCount: pagination.total, - pageSizeOptions: [5, 10, 20], - }} - selection={{ - selectable: (item: TableData) => !item.isLoading, - onSelectionChange: (selected: TableData[]) => - dispatch({ type: 'setSelected', selectedItems: selected }), + pageSizeOptions: [5, 10, 20, 50, 100, 200, 300], }} sorting={{ sort: { field: 'activate', direction: filterOptions.sortOrder } }} + selection={hasNoPermissions ? undefined : euiBasicTableSelectionProps} /> - {isLoading && } + {(isLoading || loading) && ( + + )} )} diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/accordion_title/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/accordion_title/index.tsx index 66353a9613650..77a30b70705ff 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/accordion_title/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/accordion_title/index.tsx @@ -5,7 +5,7 @@ */ import { EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui'; -import React, { memo } from 'react'; +import React from 'react'; import { RuleStatusIcon, RuleStatusIconProps } from '../status_icon'; @@ -13,7 +13,7 @@ interface AccordionTitleProps extends RuleStatusIconProps { title: string; } -export const AccordionTitle = memo(({ name, title, type }) => ( +const AccordionTitleComponent: React.FC = ({ name, title, type }) => ( @@ -24,4 +24,6 @@ export const AccordionTitle = memo(({ name, title, type }) -)); +); + +export const AccordionTitle = React.memo(AccordionTitleComponent); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/add_item_form/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/add_item_form/index.tsx index f090f6d97eaf9..0c75da7d8a632 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/add_item_form/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/add_item_form/index.tsx @@ -4,9 +4,18 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiButtonEmpty, EuiButtonIcon, EuiFormRow, EuiFieldText, EuiSpacer } from '@elastic/eui'; +import { + EuiButtonEmpty, + EuiButtonIcon, + EuiFlexGroup, + EuiFlexItem, + EuiFormRow, + EuiFieldText, + EuiSpacer, +} from '@elastic/eui'; import { isEmpty } from 'lodash/fp'; import React, { ChangeEvent, useCallback, useEffect, useState, useRef } from 'react'; +import styled from 'styled-components'; import * as RuleI18n from '../../translations'; import { FieldHook, getFieldValidityAndErrorMessage } from '../shared_imports'; @@ -17,11 +26,46 @@ interface AddItemProps { dataTestSubj: string; idAria: string; isDisabled: boolean; + validate?: (args: unknown) => boolean; } -export const AddItem = ({ addText, dataTestSubj, field, idAria, isDisabled }: AddItemProps) => { +const MyEuiFormRow = styled(EuiFormRow)` + .euiFormRow__labelWrapper { + .euiText { + padding-right: 32px; + } + } +`; + +export const MyAddItemButton = styled(EuiButtonEmpty)` + margin-top: 4px; + + &.euiButtonEmpty--xSmall { + font-size: 12px; + } + + .euiIcon { + width: 12px; + height: 12px; + } +`; + +MyAddItemButton.defaultProps = { + flush: 'left', + iconType: 'plusInCircle', + size: 'xs', +}; + +export const AddItem = ({ + addText, + dataTestSubj, + field, + idAria, + isDisabled, + validate, +}: AddItemProps) => { + const [showValidation, setShowValidation] = useState(false); const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage(field); - // const [items, setItems] = useState(['']); const [haveBeenKeyboardDeleted, setHaveBeenKeyboardDeleted] = useState(-1); const inputsRef = useRef([]); @@ -29,7 +73,8 @@ export const AddItem = ({ addText, dataTestSubj, field, idAria, isDisabled }: Ad const removeItem = useCallback( (index: number) => { const values = field.value as string[]; - field.setValue([...values.slice(0, index), ...values.slice(index + 1)]); + const newValues = [...values.slice(0, index), ...values.slice(index + 1)]; + field.setValue(newValues.length === 0 ? [''] : newValues); inputsRef.current = [ ...inputsRef.current.slice(0, index), ...inputsRef.current.slice(index + 1), @@ -46,11 +91,7 @@ export const AddItem = ({ addText, dataTestSubj, field, idAria, isDisabled }: Ad const addItem = useCallback(() => { const values = field.value as string[]; - if (!isEmpty(values) && values[values.length - 1]) { - field.setValue([...values, '']); - } else if (isEmpty(values)) { - field.setValue(['']); - } + field.setValue([...values, '']); }, [field]); const updateItem = useCallback( @@ -58,22 +99,7 @@ export const AddItem = ({ addText, dataTestSubj, field, idAria, isDisabled }: Ad event.persist(); const values = field.value as string[]; const value = event.target.value; - if (isEmpty(value)) { - field.setValue([...values.slice(0, index), ...values.slice(index + 1)]); - inputsRef.current = [ - ...inputsRef.current.slice(0, index), - ...inputsRef.current.slice(index + 1), - ]; - setHaveBeenKeyboardDeleted(inputsRef.current.length - 1); - inputsRef.current = inputsRef.current.map((ref, i) => { - if (i >= index && inputsRef.current[index] != null) { - ref.value = 're-render'; - } - return ref; - }); - } else { - field.setValue([...values.slice(0, index), value, ...values.slice(index + 1)]); - } + field.setValue([...values.slice(0, index), value, ...values.slice(index + 1)]); }, [field] ); @@ -104,11 +130,11 @@ export const AddItem = ({ addText, dataTestSubj, field, idAria, isDisabled }: Ad const values = field.value as string[]; return ( - - + + setShowValidation(true)} + onChange={e => updateItem(e, index)} + fullWidth + {...euiFieldProps} + /> + + removeItem(index)} aria-label={RuleI18n.DELETE} /> - } - onChange={e => updateItem(e, index)} - compressed - fullWidth - {...euiFieldProps} - /> + + + {values.length - 1 !== index && }
); })} - + {addText} - + - + ); }; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/assets/list_tree_icon.svg b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/assets/list_tree_icon.svg new file mode 100644 index 0000000000000..527d8d445bc03 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/assets/list_tree_icon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/filter_label.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/filter_label.tsx index 15844f5012291..bc2cd39da44be 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/filter_label.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/filter_label.tsx @@ -16,7 +16,7 @@ interface Props { valueLabel?: string; } -export const FilterLabel = memo(({ filter, valueLabel }) => { +const FilterLabelComponent: React.FC = ({ filter, valueLabel }) => { const prefixText = filter.meta.negate ? ` ${i18n.translate('xpack.siem.detectionEngine.createRule.filterLabel.negatedFilterPrefix', { defaultMessage: 'NOT ', @@ -90,4 +90,6 @@ export const FilterLabel = memo(({ filter, valueLabel }) => { ); } -}); +}; + +export const FilterLabel = memo(FilterLabelComponent); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/helpers.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/helpers.tsx new file mode 100644 index 0000000000000..e8b6919165c8b --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/helpers.tsx @@ -0,0 +1,245 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + EuiBadge, + EuiLoadingSpinner, + EuiFlexGroup, + EuiFlexItem, + EuiLink, + EuiButtonEmpty, + EuiSpacer, +} from '@elastic/eui'; + +import { isEmpty } from 'lodash/fp'; +import React from 'react'; +import styled from 'styled-components'; + +import { esFilters } from '../../../../../../../../../../src/plugins/data/public'; + +import { tacticsOptions, techniquesOptions } from '../../../mitre/mitre_tactics_techniques'; + +import { FilterLabel } from './filter_label'; +import * as i18n from './translations'; +import { BuildQueryBarDescription, BuildThreatsDescription, ListItems } from './types'; +import { SeverityBadge } from '../severity_badge'; +import ListTreeIcon from './assets/list_tree_icon.svg'; + +const isNotEmptyArray = (values: string[]) => + !isEmpty(values) && values.filter(val => !isEmpty(val)).length > 0; + +const EuiBadgeWrap = styled(EuiBadge)` + .euiBadge__text { + white-space: pre-wrap !important; + } +`; + +export const buildQueryBarDescription = ({ + field, + filters, + filterManager, + query, + savedId, + indexPatterns, +}: BuildQueryBarDescription): ListItems[] => { + let items: ListItems[] = []; + if (!isEmpty(filters)) { + filterManager.setFilters(filters); + items = [ + ...items, + { + title: <>{i18n.FILTERS_LABEL} , + description: ( + + {filterManager.getFilters().map((filter, index) => ( + + + {indexPatterns != null ? ( + + ) : ( + + )} + + + ))} + + ), + }, + ]; + } + if (!isEmpty(query.query)) { + items = [ + ...items, + { + title: <>{i18n.QUERY_LABEL} , + description: <>{query.query} , + }, + ]; + } + if (!isEmpty(savedId)) { + items = [ + ...items, + { + title: <>{i18n.SAVED_ID_LABEL} , + description: <>{savedId} , + }, + ]; + } + return items; +}; + +const ThreatsEuiFlexGroup = styled(EuiFlexGroup)` + .euiFlexItem { + margin-bottom: 0px; + } +`; + +const TechniqueLinkItem = styled(EuiButtonEmpty)` + .euiIcon { + width: 8px; + height: 8px; + } +`; + +const ReferenceLinkItem = styled(EuiButtonEmpty)` + .euiIcon { + width: 12px; + height: 12px; + } +`; + +export const buildThreatsDescription = ({ + label, + threats, +}: BuildThreatsDescription): ListItems[] => { + if (threats.length > 0) { + return [ + { + title: label, + description: ( + + {threats.map((threat, index) => { + const tactic = tacticsOptions.find(t => t.name === threat.tactic.name); + return ( + + + {tactic != null ? tactic.text : ''} + + + {threat.techniques.map(technique => { + const myTechnique = techniquesOptions.find(t => t.name === technique.name); + return ( + + + {myTechnique != null ? myTechnique.label : ''} + + + ); + })} + + + ); + })} + + + ), + }, + ]; + } + return []; +}; + +export const buildUnorderedListArrayDescription = ( + label: string, + field: string, + values: string[] +): ListItems[] => { + if (isNotEmptyArray(values)) { + return [ + { + title: label, + description: ( +
    + {values.map((val: string) => + isEmpty(val) ? null :
  • {val}
  • + )} +
+ ), + }, + ]; + } + return []; +}; + +export const buildStringArrayDescription = ( + label: string, + field: string, + values: string[] +): ListItems[] => { + if (isNotEmptyArray(values)) { + return [ + { + title: label, + description: ( + + {values.map((val: string) => + isEmpty(val) ? null : ( + + {val} + + ) + )} + + ), + }, + ]; + } + return []; +}; + +export const buildSeverityDescription = (label: string, value: string): ListItems[] => [ + { + title: label, + description: , + }, +]; + +export const buildUrlsDescription = (label: string, values: string[]): ListItems[] => { + if (isNotEmptyArray(values)) { + return [ + { + title: label, + description: ( + + {values.map((val: string) => ( + + + {val} + + + ))} + + ), + }, + ]; + } + return []; +}; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/index.tsx index 39c660a0079a6..8cf1601e2c4b6 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/index.tsx @@ -4,20 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - EuiBadge, - EuiDescriptionList, - EuiLoadingSpinner, - EuiFlexGroup, - EuiFlexItem, - EuiTextArea, - EuiLink, - EuiText, - EuiListGroup, -} from '@elastic/eui'; +import { EuiDescriptionList, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { isEmpty, chunk, get, pick } from 'lodash/fp'; -import React, { memo, ReactNode, useState } from 'react'; -import styled from 'styled-components'; +import React, { memo, useState } from 'react'; import { IIndexPattern, @@ -25,13 +14,20 @@ import { FilterManager, Query, } from '../../../../../../../../../../src/plugins/data/public'; +import { DEFAULT_TIMELINE_TITLE } from '../../../../../components/timeline/search_super_select/translations'; import { useKibana } from '../../../../../lib/kibana'; -import { FilterLabel } from './filter_label'; -import { FormSchema } from '../shared_imports'; -import * as I18n from './translations'; - import { IMitreEnterpriseAttack } from '../../types'; -import { tacticsOptions, techniquesOptions } from '../../../mitre/mitre_tactics_techniques'; +import { FieldValueTimeline } from '../pick_timeline'; +import { FormSchema } from '../shared_imports'; +import { ListItems } from './types'; +import { + buildQueryBarDescription, + buildSeverityDescription, + buildStringArrayDescription, + buildThreatsDescription, + buildUnorderedListArrayDescription, + buildUrlsDescription, +} from './helpers'; interface StepRuleDescriptionProps { direction?: 'row' | 'column'; @@ -40,67 +36,46 @@ interface StepRuleDescriptionProps { schema: FormSchema; } -const EuiBadgeWrap = styled(EuiBadge)` - .euiBadge__text { - white-space: pre-wrap !important; - } -`; - -const EuiFlexItemWidth = styled(EuiFlexItem)<{ direction: string }>` - ${props => (props.direction === 'row' ? 'width : 50%;' : 'width: 100%;')}; -`; +const StepRuleDescriptionComponent: React.FC = ({ + data, + direction = 'row', + indexPatterns, + schema, +}) => { + const kibana = useKibana(); + const [filterManager] = useState(new FilterManager(kibana.services.uiSettings)); -const MyEuiListGroup = styled(EuiListGroup)` - padding: 0px; - .euiListGroupItem__button { - padding: 0px; - } -`; - -const ThreatsEuiFlexGroup = styled(EuiFlexGroup)` - .euiFlexItem { - margin-bottom: 0px; - } -`; - -const MyEuiTextArea = styled(EuiTextArea)` - max-width: 100%; - height: 80px; -`; - -export const StepRuleDescription = memo( - ({ data, direction = 'row', indexPatterns, schema }) => { - const kibana = useKibana(); - const [filterManager] = useState(new FilterManager(kibana.services.uiSettings)); + const keys = Object.keys(schema); + const listItems = keys.reduce( + (acc: ListItems[], key: string) => [ + ...acc, + ...buildListItems(data, pick(key, schema), filterManager, indexPatterns), + ], + [] + ); - const keys = Object.keys(schema); - const listItems = keys.reduce( - (acc: ListItems[], key: string) => [ - ...acc, - ...buildListItems(data, pick(key, schema), filterManager, indexPatterns), - ], - [] - ); + if (direction === 'row') { return ( - - {chunk(Math.ceil(listItems.length / 2), listItems).map((chunckListItems, index) => ( - - - + + {chunk(Math.ceil(listItems.length / 2), listItems).map((chunkListItems, index) => ( + + + ))} ); } -); -interface ListItems { - title: NonNullable; - description: NonNullable; -} + return ( + + + + + + ); +}; + +export const StepRuleDescription = memo(StepRuleDescriptionComponent); const buildListItems = ( data: unknown, @@ -129,131 +104,65 @@ const getDescriptionItem = ( filterManager: FilterManager, indexPatterns?: IIndexPattern ): ListItems[] => { - if (field === 'useIndicesConfig') { - return []; - } else if (field === 'queryBar') { + if (field === 'queryBar') { const filters = get('queryBar.filters', value) as esFilters.Filter[]; const query = get('queryBar.query', value) as Query; const savedId = get('queryBar.saved_id', value); - let items: ListItems[] = []; - if (!isEmpty(filters)) { - filterManager.setFilters(filters); - items = [ - ...items, - { - title: <>{I18n.FILTERS_LABEL}, - description: ( - - {filterManager.getFilters().map((filter, index) => ( - - - {indexPatterns != null ? ( - - ) : ( - - )} - - - ))} - - ), - }, - ]; - } - if (!isEmpty(query.query)) { - items = [ - ...items, - { - title: <>{I18n.QUERY_LABEL}, - description: <>{query.query}, - }, - ]; - } - if (!isEmpty(savedId)) { - items = [ - ...items, - { - title: <>{I18n.SAVED_ID_LABEL}, - description: <>{savedId}, - }, - ]; - } - return items; + return buildQueryBarDescription({ + field, + filters, + filterManager, + query, + savedId, + indexPatterns, + }); } else if (field === 'threats') { const threats: IMitreEnterpriseAttack[] = get(field, value).filter( (threat: IMitreEnterpriseAttack) => threat.tactic.name !== 'none' ); - if (threats.length > 0) { - return [ - { - title: label, - description: ( - - {threats.map((threat, index) => { - const tactic = tacticsOptions.find(t => t.name === threat.tactic.name); - return ( - - -
- - {tactic != null ? tactic.text : ''} - -
- { - const myTechnique = techniquesOptions.find( - t => t.name === technique.name - ); - return { - label: myTechnique != null ? myTechnique.label : '', - href: technique.reference, - target: '_blank', - }; - })} - /> -
-
- ); - })} -
- ), - }, - ]; - } - return []; + return buildThreatsDescription({ label, threats }); } else if (field === 'description') { return [ { title: label, - description: , + description: get(field, value), }, ]; + } else if (field === 'references') { + const urls: string[] = get(field, value); + return buildUrlsDescription(label, urls); + } else if (field === 'falsePositives') { + const values: string[] = get(field, value); + return buildUnorderedListArrayDescription(label, field, values); } else if (Array.isArray(get(field, value))) { const values: string[] = get(field, value); - if (!isEmpty(values) && values.filter(val => !isEmpty(val)).length > 0) { - return [ - { - title: label, - description: ( - - {values.map((val: string) => - isEmpty(val) ? null : ( - - {val} - - ) - )} - - ), - }, - ]; - } - return []; + return buildStringArrayDescription(label, field, values); + } else if (field === 'severity') { + const val: string = get(field, value); + return buildSeverityDescription(label, val); + } else if (field === 'riskScore') { + return [ + { + title: label, + description: get(field, value), + }, + ]; + } else if (field === 'timeline') { + const timeline = get(field, value) as FieldValueTimeline; + return [ + { + title: label, + description: timeline.title ?? DEFAULT_TIMELINE_TITLE, + }, + ]; + } else if (field === 'riskScore') { + const description: string = get(field, value); + return [ + { + title: label, + description, + }, + ]; } const description: string = get(field, value); if (!isEmpty(description)) { diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/translations.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/translations.tsx index 0995e0e916652..9695fd21067ee 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/translations.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/translations.tsx @@ -11,7 +11,7 @@ export const FILTERS_LABEL = i18n.translate('xpack.siem.detectionEngine.createRu }); export const QUERY_LABEL = i18n.translate('xpack.siem.detectionEngine.createRule.QueryLabel', { - defaultMessage: 'Query', + defaultMessage: 'Custom query', }); export const SAVED_ID_LABEL = i18n.translate('xpack.siem.detectionEngine.createRule.savedIdLabel', { diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/types.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/types.ts new file mode 100644 index 0000000000000..d32fbcd725d12 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/description_step/types.ts @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { ReactNode } from 'react'; + +import { + IIndexPattern, + esFilters, + FilterManager, + Query, +} from '../../../../../../../../../../src/plugins/data/public'; +import { IMitreEnterpriseAttack } from '../../types'; + +export interface ListItems { + title: NonNullable; + description: NonNullable; +} + +export interface BuildQueryBarDescription { + field: string; + filters: esFilters.Filter[]; + filterManager: FilterManager; + query: Query; + savedId: string; + indexPatterns?: IIndexPattern; +} + +export interface BuildThreatsDescription { + label: string; + threats: IMitreEnterpriseAttack[]; +} diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/import_rule_modal/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/import_rule_modal/index.test.tsx index 381a3138bf617..e10194853e7f9 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/import_rule_modal/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/import_rule_modal/index.test.tsx @@ -5,8 +5,7 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { ImportRuleModalComponent } from './index'; jest.mock('../../../../../lib/kibana'); @@ -20,6 +19,6 @@ describe('ImportRuleModal', () => { importComplete={jest.fn()} /> ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/import_rule_modal/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/import_rule_modal/index.tsx index 6d50fb768068f..75be92f2fe846 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/import_rule_modal/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/import_rule_modal/index.tsx @@ -28,8 +28,6 @@ import { fold } from 'fp-ts/lib/Either'; import uuid from 'uuid'; import { duplicateRules, RulesSchema } from '../../../../../containers/detection_engine/rules'; -import { useUiSetting$ } from '../../../../../lib/kibana'; -import { DEFAULT_KBN_VERSION } from '../../../../../../common/constants'; import { useStateToaster } from '../../../../../components/toasters'; import { ndjsonToJSON } from '../json_downloader'; import * as i18n from './translations'; @@ -54,7 +52,6 @@ export const ImportRuleModalComponent = ({ }: ImportRuleModalProps) => { const [selectedFiles, setSelectedFiles] = useState(null); const [isImporting, setIsImporting] = useState(false); - const [kbnVersion] = useUiSetting$(DEFAULT_KBN_VERSION); const [, dispatchToaster] = useStateToaster(); const cleanupAndCloseModal = () => { @@ -89,7 +86,7 @@ export const ImportRuleModalComponent = ({ }, identity) ); - const duplicatedRules = await duplicateRules({ rules: decodedRules, kbnVersion }); + const duplicatedRules = await duplicateRules({ rules: decodedRules }); importComplete(); cleanupAndCloseModal(); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/json_downloader/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/json_downloader/index.test.tsx index d7a508e2c53e3..859918cdc8e60 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/json_downloader/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/json_downloader/index.test.tsx @@ -5,8 +5,7 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; -import * as React from 'react'; +import React from 'react'; import { JSONDownloaderComponent, jsonToNDJSON, ndjsonToJSON } from './index'; const jsonArray = [ @@ -37,7 +36,7 @@ describe('JSONDownloader', () => { const wrapper = shallow( ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); describe('jsonToNDJSON', () => { diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/mitre/helpers.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/mitre/helpers.ts new file mode 100644 index 0000000000000..1202fe54ad194 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/mitre/helpers.ts @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { isEmpty } from 'lodash/fp'; + +import { IMitreAttack } from '../../types'; + +export const isMitreAttackInvalid = ( + tacticName: string | null | undefined, + techniques: IMitreAttack[] | null | undefined +) => { + if (isEmpty(tacticName) || (tacticName !== 'none' && isEmpty(techniques))) { + return true; + } + return false; +}; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/mitre/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/mitre/index.tsx index a777506ee12ae..f9a22c37cfdf0 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/mitre/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/mitre/index.tsx @@ -5,30 +5,33 @@ */ import { - EuiButtonEmpty, EuiButtonIcon, EuiFormRow, - EuiSelect, + EuiSuperSelect, EuiSpacer, EuiFlexGroup, EuiFlexItem, EuiComboBox, - EuiFormControlLayout, + EuiText, } from '@elastic/eui'; import { isEmpty, kebabCase, camelCase } from 'lodash/fp'; -import React, { ChangeEvent, useCallback } from 'react'; +import React, { useCallback, useState } from 'react'; import styled from 'styled-components'; import { tacticsOptions, techniquesOptions } from '../../../mitre/mitre_tactics_techniques'; -import * as RuleI18n from '../../translations'; +import * as Rulei18n from '../../translations'; import { FieldHook, getFieldValidityAndErrorMessage } from '../shared_imports'; -import * as I18n from './translations'; +import { threatsDefault } from '../step_about_rule/default_value'; import { IMitreEnterpriseAttack } from '../../types'; +import { MyAddItemButton } from '../add_item_form'; +import { isMitreAttackInvalid } from './helpers'; +import * as i18n from './translations'; -const MyEuiFormControlLayout = styled(EuiFormControlLayout)` - &.euiFormControlLayout--compressed { - height: fit-content !important; - } +const MitreContainer = styled.div` + margin-top: 16px; +`; +const MyEuiSuperSelect = styled(EuiSuperSelect)` + width: 280px; `; interface AddItemProps { field: FieldHook; @@ -38,12 +41,18 @@ interface AddItemProps { } export const AddMitreThreat = ({ dataTestSubj, field, idAria, isDisabled }: AddItemProps) => { + const [showValidation, setShowValidation] = useState(false); const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage(field); const removeItem = useCallback( (index: number) => { const values = field.value as string[]; - field.setValue([...values.slice(0, index), ...values.slice(index + 1)]); + const newValues = [...values.slice(0, index), ...values.slice(index + 1)]; + if (isEmpty(newValues)) { + field.setValue(threatsDefault); + } else { + field.setValue(newValues); + } }, [field] ); @@ -61,9 +70,9 @@ export const AddMitreThreat = ({ dataTestSubj, field, idAria, isDisabled }: AddI }, [field]); const updateTactic = useCallback( - (index: number, event: ChangeEvent) => { + (index: number, value: string) => { const values = field.value as IMitreEnterpriseAttack[]; - const { id, reference, name } = tacticsOptions.find(t => t.value === event.target.value) || { + const { id, reference, name } = tacticsOptions.find(t => t.value === value) || { id: '', name: '', reference: '', @@ -97,75 +106,111 @@ export const AddMitreThreat = ({ dataTestSubj, field, idAria, isDisabled }: AddI const values = field.value as IMitreEnterpriseAttack[]; + const getSelectTactic = (tacticName: string, index: number, disabled: boolean) => ( + {i18n.TACTIC_PLACEHOLDER}, + value: 'none', + disabled, + }, + ] + : []), + ...tacticsOptions.map(t => ({ + inputDisplay: <>{t.text}, + value: t.value, + disabled, + })), + ]} + aria-label="" + onChange={updateTactic.bind(null, index)} + fullWidth={false} + valueOfSelected={camelCase(tacticName)} + /> + ); + + const getSelectTechniques = (item: IMitreEnterpriseAttack, index: number, disabled: boolean) => { + const invalid = isMitreAttackInvalid(item.tactic.name, item.techniques); + const options = techniquesOptions.filter(t => t.tactics.includes(kebabCase(item.tactic.name))); + const selectedOptions = item.techniques.map(technic => ({ + ...technic, + label: `${technic.name} (${technic.id})`, // API doesn't allow for label field + })); + + return ( + + + setShowValidation(true)} + /> + {showValidation && invalid && ( + +

{errorMessage}

+
+ )} +
+ + removeItem(index)} + aria-label={Rulei18n.DELETE} + /> + +
+ ); + }; + return ( - - <> - {values.map((item, index) => { - const euiSelectFieldProps = { - disabled: isDisabled, - }; - return ( -
- - - ({ text: t.text, value: t.value })), - ]} - aria-label="" - onChange={updateTactic.bind(null, index)} - prepend={I18n.TACTIC} - compressed - fullWidth={false} - value={camelCase(item.tactic.name)} - {...euiSelectFieldProps} - /> - - - - - t.tactics.includes(kebabCase(item.tactic.name)) - )} - selectedOptions={item.techniques} - onChange={updateTechniques.bind(null, index)} - isDisabled={isDisabled} - fullWidth={true} - /> - - - - removeItem(index)} - aria-label={RuleI18n.DELETE} - /> - - - {values.length - 1 !== index && } -
- ); - })} - - {I18n.ADD_MITRE_ATTACK} - - -
+ + {values.map((item, index) => ( +
+ + + {index === 0 ? ( + + <>{getSelectTactic(item.tactic.name, index, isDisabled)} + + ) : ( + getSelectTactic(item.tactic.name, index, isDisabled) + )} + + + {index === 0 ? ( + + <>{getSelectTechniques(item, index, isDisabled)} + + ) : ( + getSelectTechniques(item, index, isDisabled) + )} + + + {values.length - 1 !== index && } +
+ ))} + + {i18n.ADD_MITRE_ATTACK} + +
); }; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/mitre/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/mitre/translations.ts index 22ee6cc3ef911..557e91691b6c7 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/mitre/translations.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/mitre/translations.ts @@ -7,18 +7,18 @@ import { i18n } from '@kbn/i18n'; export const TACTIC = i18n.translate('xpack.siem.detectionEngine.mitreAttack.tacticsDescription', { - defaultMessage: 'Tactic', + defaultMessage: 'tactic', }); -export const TECHNIQUES = i18n.translate( +export const TECHNIQUE = i18n.translate( 'xpack.siem.detectionEngine.mitreAttack.techniquesDescription', { - defaultMessage: 'Techniques', + defaultMessage: 'technique', } ); export const ADD_MITRE_ATTACK = i18n.translate('xpack.siem.detectionEngine.mitreAttack.addTitle', { - defaultMessage: 'Add MITRE ATT&CK threat', + defaultMessage: 'Add MITRE ATT&CK\\u2122 threat', }); export const TECHNIQUES_PLACEHOLDER = i18n.translate( diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/optional_field_label/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/optional_field_label/index.tsx new file mode 100644 index 0000000000000..0dab87b0a3b74 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/optional_field_label/index.tsx @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiText } from '@elastic/eui'; +import React from 'react'; + +import * as RuleI18n from '../../translations'; + +export const OptionalFieldLabel = ( + + {RuleI18n.OPTIONAL_FIELD} + +); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pick_timeline/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pick_timeline/index.tsx new file mode 100644 index 0000000000000..873e0c2184c61 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/pick_timeline/index.tsx @@ -0,0 +1,74 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiFormRow } from '@elastic/eui'; +import React, { useCallback, useEffect, useState } from 'react'; + +import { SearchTimelineSuperSelect } from '../../../../../components/timeline/search_super_select'; +import { FieldHook, getFieldValidityAndErrorMessage } from '../shared_imports'; + +export interface FieldValueTimeline { + id: string | null; + title: string | null; +} + +interface QueryBarDefineRuleProps { + dataTestSubj: string; + field: FieldHook; + idAria: string; + isDisabled: boolean; +} + +export const PickTimeline = ({ + dataTestSubj, + field, + idAria, + isDisabled = false, +}: QueryBarDefineRuleProps) => { + const [timelineId, setTimelineId] = useState(null); + const [timelineTitle, setTimelineTitle] = useState(null); + + const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage(field); + + useEffect(() => { + const { id, title } = field.value as FieldValueTimeline; + if (timelineTitle !== title && timelineId !== id) { + setTimelineId(id); + setTimelineTitle(title); + } + }, [field.value]); + + const handleOnTimelineChange = useCallback( + (title: string, id: string | null) => { + if (id === null) { + field.setValue({ id, title: null }); + } else if (timelineTitle !== title && timelineId !== id) { + field.setValue({ id, title }); + } + }, + [field] + ); + + return ( + + + + ); +}; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/query_bar/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/query_bar/index.tsx index c294ec24c4cb7..46a7a13ec03f1 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/query_bar/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/query_bar/index.tsx @@ -6,7 +6,7 @@ import { EuiFormRow, EuiMutationObserver } from '@elastic/eui'; import { isEqual } from 'lodash/fp'; -import React, { useCallback, useEffect, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import { Subscription } from 'rxjs'; import styled from 'styled-components'; @@ -19,11 +19,18 @@ import { SavedQueryTimeFilter, } from '../../../../../../../../../../src/plugins/data/public'; +import { BrowserFields } from '../../../../../containers/source'; +import { OpenTimelineModal } from '../../../../../components/open_timeline/open_timeline_modal'; +import { ActionTimelineToShow } from '../../../../../components/open_timeline/types'; import { QueryBar } from '../../../../../components/query_bar'; +import { buildGlobalQuery } from '../../../../../components/timeline/helpers'; +import { getDataProviderFilter } from '../../../../../components/timeline/query_bar'; +import { convertKueryToElasticSearchQuery } from '../../../../../lib/keury'; import { useKibana } from '../../../../../lib/kibana'; +import { TimelineModel } from '../../../../../store/timeline/model'; import { useSavedQueryServices } from '../../../../../utils/saved_query_services'; - import { FieldHook, getFieldValidityAndErrorMessage } from '../shared_imports'; +import * as i18n from './translations'; export interface FieldValueQueryBar { filters: esFilters.Filter[]; @@ -31,17 +38,20 @@ export interface FieldValueQueryBar { saved_id: string | null; } interface QueryBarDefineRuleProps { + browserFields: BrowserFields; dataTestSubj: string; field: FieldHook; idAria: string; isLoading: boolean; indexPattern: IIndexPattern; + onCloseTimelineSearch: () => void; + openTimelineSearch: boolean; resizeParentContainer?: (height: number) => void; } const StyledEuiFormRow = styled(EuiFormRow)` .kbnTypeahead__items { - max-height: 14vh !important; + max-height: 45vh !important; } .globalQueryBar { padding: 4px 0px 0px 0px; @@ -56,14 +66,18 @@ const StyledEuiFormRow = styled(EuiFormRow)` // TODO need to add disabled in the SearchBar export const QueryBarDefineRule = ({ + browserFields, dataTestSubj, field, idAria, indexPattern, isLoading = false, + onCloseTimelineSearch, + openTimelineSearch = false, resizeParentContainer, }: QueryBarDefineRuleProps) => { const [originalHeight, setOriginalHeight] = useState(-1); + const [loadingTimeline, setLoadingTimeline] = useState(false); const [savedQuery, setSavedQuery] = useState(null); const [queryDraft, setQueryDraft] = useState({ query: '', language: 'kuery' }); const { isInvalid, errorMessage } = getFieldValidityAndErrorMessage(field); @@ -168,6 +182,38 @@ export const QueryBarDefineRule = ({ [field.value] ); + const onCloseTimelineModal = useCallback(() => { + setLoadingTimeline(true); + onCloseTimelineSearch(); + }, [onCloseTimelineSearch]); + + const onOpenTimeline = useCallback( + (timeline: TimelineModel) => { + setLoadingTimeline(false); + const newQuery = { + query: timeline.kqlQuery.filterQuery?.kuery?.expression ?? '', + language: timeline.kqlQuery.filterQuery?.kuery?.kind ?? 'kuery', + }; + const dataProvidersDsl = + timeline.dataProviders != null && timeline.dataProviders.length > 0 + ? convertKueryToElasticSearchQuery( + buildGlobalQuery(timeline.dataProviders, browserFields), + indexPattern + ) + : ''; + const newFilters = timeline.filters ?? []; + field.setValue({ + filters: + dataProvidersDsl !== '' + ? [...newFilters, getDataProviderFilter(dataProvidersDsl)] + : newFilters, + query: newQuery, + saved_id: '', + }); + }, + [browserFields, field, indexPattern] + ); + const onMutation = (event: unknown, observer: unknown) => { if (resizeParentContainer != null) { const suggestionContainer = document.getElementById('kbnTypeahead__items'); @@ -189,39 +235,51 @@ export const QueryBarDefineRule = ({ } }; + const actionTimelineToHide = useMemo(() => ['duplicate'], []); + return ( - - + - {mutationRef => ( -
- -
- )} -
-
+ + {mutationRef => ( +
+ +
+ )} +
+ + {openTimelineSearch ? ( + + ) : null} + ); }; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/query_bar/translations.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/query_bar/translations.tsx new file mode 100644 index 0000000000000..9b14e4f8599da --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/query_bar/translations.tsx @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const IMPORT_TIMELINE_MODAL = i18n.translate( + 'xpack.siem.detectionEngine.createRule.stepDefineRule.importTimelineModalTitle', + { + defaultMessage: 'Import query from saved timeline', + } +); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/read_only_callout/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/read_only_callout/index.tsx new file mode 100644 index 0000000000000..6ec76bacc2323 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/read_only_callout/index.tsx @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiCallOut, EuiButton } from '@elastic/eui'; +import React, { memo, useCallback, useState } from 'react'; + +import * as i18n from './translations'; + +const ReadOnlyCallOutComponent = () => { + const [showCallOut, setShowCallOut] = useState(true); + const handleCallOut = useCallback(() => setShowCallOut(false), [setShowCallOut]); + + return showCallOut ? ( + +

{i18n.READ_ONLY_CALLOUT_MSG}

+ + {i18n.DISMISS_CALLOUT} + +
+ ) : null; +}; + +export const ReadOnlyCallOut = memo(ReadOnlyCallOutComponent); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/read_only_callout/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/read_only_callout/translations.ts new file mode 100644 index 0000000000000..c3429f4365031 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/read_only_callout/translations.ts @@ -0,0 +1,26 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +export const READ_ONLY_CALLOUT_TITLE = i18n.translate( + 'xpack.siem.detectionEngine.readOnlyCallOutTitle', + { + defaultMessage: 'Rule permissions required', + } +); + +export const READ_ONLY_CALLOUT_MSG = i18n.translate( + 'xpack.siem.detectionEngine.readOnlyCallOutMsg', + { + defaultMessage: + 'You are currently missing the required permissions to create/edit detection engine rule. Please contact your administrator for further assistance.', + } +); + +export const DISMISS_CALLOUT = i18n.translate('xpack.siem.detectionEngine.dismissButton', { + defaultMessage: 'Dismiss', +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_switch/__snapshots__/index.test.tsx.snap b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_switch/__snapshots__/index.test.tsx.snap index f264dde07c594..604f86866d565 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_switch/__snapshots__/index.test.tsx.snap +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_switch/__snapshots__/index.test.tsx.snap @@ -11,7 +11,6 @@ exports[`RuleSwitch renders correctly against snapshot 1`] = ` { test('renders correctly against snapshot', () => { const wrapper = shallow( ); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_switch/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_switch/index.tsx index 54f0fc453830e..9cb0323ed8987 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_switch/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/rule_switch/index.tsx @@ -15,11 +15,10 @@ import { isEmpty } from 'lodash/fp'; import styled from 'styled-components'; import React, { useCallback, useState, useEffect } from 'react'; -import { DEFAULT_KBN_VERSION } from '../../../../../../common/constants'; import { enableRules } from '../../../../../containers/detection_engine/rules'; -import { useUiSetting$ } from '../../../../../lib/kibana'; import { enableRulesAction } from '../../all/actions'; import { Action } from '../../all/reducer'; +import { useStateToaster } from '../../../../../components/toasters'; const StaticSwitch = styled(EuiSwitch)` .euiSwitch__thumb, @@ -34,6 +33,7 @@ export interface RuleSwitchProps { dispatch?: React.Dispatch; id: string; enabled: boolean; + isDisabled?: boolean; isLoading?: boolean; optionLabel?: string; } @@ -44,25 +44,25 @@ export interface RuleSwitchProps { export const RuleSwitchComponent = ({ dispatch, id, + isDisabled, isLoading, enabled, optionLabel, }: RuleSwitchProps) => { const [myIsLoading, setMyIsLoading] = useState(false); const [myEnabled, setMyEnabled] = useState(enabled ?? false); - const [kbnVersion] = useUiSetting$(DEFAULT_KBN_VERSION); + const [, dispatchToaster] = useStateToaster(); const onRuleStateChange = useCallback( async (event: EuiSwitchEvent) => { setMyIsLoading(true); if (dispatch != null) { - await enableRulesAction([id], event.target.checked!, dispatch, kbnVersion); + await enableRulesAction([id], event.target.checked!, dispatch, dispatchToaster); } else { try { const updatedRules = await enableRules({ ids: [id], enabled: event.target.checked!, - kbnVersion, }); setMyEnabled(updatedRules[0].enabled); } catch { @@ -71,7 +71,7 @@ export const RuleSwitchComponent = ({ } setMyIsLoading(false); }, - [dispatch, id, kbnVersion] + [dispatch, id] ); useEffect(() => { @@ -96,7 +96,7 @@ export const RuleSwitchComponent = ({ data-test-subj="rule-switch" label={optionLabel ?? ''} showLabel={!isEmpty(optionLabel)} - disabled={false} + disabled={isDisabled} checked={myEnabled} onChange={onRuleStateChange} /> diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/schedule_item_form/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/schedule_item_form/index.tsx index 2e57ff8ba2c4f..fa4bea319f859 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/schedule_item_form/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/schedule_item_form/index.tsx @@ -4,9 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiFieldNumber, EuiFormRow, EuiSelect } from '@elastic/eui'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiFieldNumber, + EuiFormRow, + EuiSelect, + EuiFormControlLayout, +} from '@elastic/eui'; import { isEmpty } from 'lodash/fp'; -import React, { useCallback, useEffect, useState } from 'react'; +import React, { useCallback, useEffect, useMemo, useState } from 'react'; import styled from 'styled-components'; import { FieldHook, getFieldValidityAndErrorMessage } from '../shared_imports'; @@ -26,10 +33,32 @@ const timeTypeOptions = [ { value: 'h', text: I18n.HOURS }, ]; +// move optional label to the end of input +const StyledLabelAppend = styled(EuiFlexItem)` + &.euiFlexItem.euiFlexItem--flexGrowZero { + margin-left: 31px; + } +`; + const StyledEuiFormRow = styled(EuiFormRow)` + max-width: none; + .euiFormControlLayout { max-width: 200px !important; } + + .euiFormControlLayout__childrenWrapper > *:first-child { + box-shadow: none; + height: 38px; + } + + .euiFormControlLayout:not(:first-child) { + border-left: 1px solid ${({ theme }) => theme.eui.euiColorLightShade}; + } +`; + +const MyEuiSelect = styled(EuiSelect)` + width: auto; `; export const ScheduleItem = ({ dataTestSubj, field, idAria, isDisabled }: ScheduleItemProps) => { @@ -79,22 +108,33 @@ export const ScheduleItem = ({ dataTestSubj, field, idAria, isDisabled }: Schedu // EUI missing some props const rest = { disabled: isDisabled }; + const label = useMemo( + () => ( + + + {field.label} + + + {field.labelAppend} + + + ), + [field.label, field.labelAppend] + ); return ( - } - compressed - fullWidth - min={0} - onChange={onChangeTimeVal} - value={timeVal} - {...rest} - /> + > + + ); }; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/severity_badge/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/severity_badge/index.tsx new file mode 100644 index 0000000000000..09c02dfca56f9 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/severity_badge/index.tsx @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { upperFirst } from 'lodash/fp'; +import React from 'react'; +import { EuiHealth } from '@elastic/eui'; +import euiLightVars from '@elastic/eui/dist/eui_theme_light.json'; + +interface Props { + value: string; +} + +const SeverityBadgeComponent: React.FC = ({ value }) => ( + + {upperFirst(value)} + +); + +export const SeverityBadge = React.memo(SeverityBadgeComponent); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/status_icon/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/status_icon/index.tsx index 22b116557ae6e..3ec5bf1a12eb0 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/status_icon/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/status_icon/index.tsx @@ -25,13 +25,15 @@ const RuleStatusIconStyled = styled.div` } `; -export const RuleStatusIcon = memo(({ name, type }) => { +const RuleStatusIconComponent: React.FC = ({ name, type }) => { const theme = useEuiTheme(); - const color = type === 'passive' ? theme.euiColorLightestShade : theme.euiColorDarkestShade; + const color = type === 'passive' ? theme.euiColorLightestShade : theme.euiColorPrimary; return ( {type === 'valid' ? : null} ); -}); +}; + +export const RuleStatusIcon = memo(RuleStatusIconComponent); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/data.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/data.ts deleted file mode 100644 index 7d6e434bcc8c6..0000000000000 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/data.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import * as I18n from './translations'; - -export type SeverityValue = 'low' | 'medium' | 'high' | 'critical'; - -interface SeverityOptionItem { - value: SeverityValue; - text: string; -} - -export const severityOptions: SeverityOptionItem[] = [ - { value: 'low', text: I18n.LOW }, - { value: 'medium', text: I18n.MEDIUM }, - { value: 'high', text: I18n.HIGH }, - { value: 'critical', text: I18n.CRITICAL }, -]; - -export const defaultRiskScoreBySeverity: Record = { - low: 21, - medium: 47, - high: 73, - critical: 99, -}; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/data.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/data.tsx new file mode 100644 index 0000000000000..269d2d4509508 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/data.tsx @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import styled from 'styled-components'; +import { EuiHealth } from '@elastic/eui'; +import euiLightVars from '@elastic/eui/dist/eui_theme_light.json'; +import React from 'react'; +import * as I18n from './translations'; + +export type SeverityValue = 'low' | 'medium' | 'high' | 'critical'; + +interface SeverityOptionItem { + value: SeverityValue; + inputDisplay: React.ReactElement; +} + +const StyledEuiHealth = styled(EuiHealth)` + line-height: inherit; +`; + +export const severityOptions: SeverityOptionItem[] = [ + { + value: 'low', + inputDisplay: {I18n.LOW}, + }, + { + value: 'medium', + inputDisplay: ( + {I18n.MEDIUM} + ), + }, + { + value: 'high', + inputDisplay: {I18n.HIGH}, + }, + { + value: 'critical', + inputDisplay: ( + {I18n.CRITICAL} + ), + }, +]; + +export const defaultRiskScoreBySeverity: Record = { + low: 21, + medium: 47, + high: 73, + critical: 99, +}; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/default_value.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/default_value.ts index c0c5ae77a1960..328c4a0f96066 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/default_value.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/default_value.ts @@ -6,6 +6,14 @@ import { AboutStepRule } from '../../types'; +export const threatsDefault = [ + { + framework: 'MITRE ATT&CK', + tactic: { id: 'none', name: 'none', reference: 'none' }, + techniques: [], + }, +]; + export const stepAboutDefaultValue: AboutStepRule = { name: '', description: '', @@ -15,11 +23,9 @@ export const stepAboutDefaultValue: AboutStepRule = { references: [''], falsePositives: [''], tags: [], - threats: [ - { - framework: 'MITRE ATT&CK', - tactic: { id: 'none', name: 'none', reference: 'none' }, - techniques: [], - }, - ], + timeline: { + id: null, + title: null, + }, + threats: threatsDefault, }; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/helpers.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/helpers.ts new file mode 100644 index 0000000000000..99b01c8b22974 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/helpers.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { isEmpty } from 'lodash/fp'; + +const urlExpression = /(https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|www\.[a-zA-Z0-9][a-zA-Z0-9-]+[a-zA-Z0-9]\.[^\s]{2,}|https?:\/\/(?:www\.|(?!www))[a-zA-Z0-9]+\.[^\s]{2,}|www\.[a-zA-Z0-9]+\.[^\s]{2,})/gi; + +export const isUrlInvalid = (url: string | null | undefined) => { + if (!isEmpty(url) && url != null && url.match(urlExpression) == null) { + return true; + } + return false; +}; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/index.tsx index e266c0b9ab47d..0e03a11776fb7 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/index.tsx @@ -6,18 +6,23 @@ import { EuiButton, EuiHorizontalRule, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; import { isEqual, get } from 'lodash/fp'; -import React, { memo, useCallback, useEffect, useState } from 'react'; +import React, { FC, memo, useCallback, useEffect, useState } from 'react'; +import styled from 'styled-components'; import { RuleStepProps, RuleStep, AboutStepRule } from '../../types'; import * as RuleI18n from '../../translations'; -import { Field, Form, FormDataProvider, getUseField, UseField, useForm } from '../shared_imports'; import { AddItem } from '../add_item_form'; +import { StepRuleDescription } from '../description_step'; +import { AddMitreThreat } from '../mitre'; +import { Field, Form, FormDataProvider, getUseField, UseField, useForm } from '../shared_imports'; + import { defaultRiskScoreBySeverity, severityOptions, SeverityValue } from './data'; import { stepAboutDefaultValue } from './default_value'; +import { isUrlInvalid } from './helpers'; import { schema } from './schema'; import * as I18n from './translations'; -import { StepRuleDescription } from '../description_step'; -import { AddMitreThreat } from '../mitre'; +import { PickTimeline } from '../pick_timeline'; +import { StepContentWrapper } from '../step_content_wrapper'; const CommonUseField = getUseField({ component: Field }); @@ -25,64 +30,71 @@ interface StepAboutRuleProps extends RuleStepProps { defaultValues?: AboutStepRule | null; } -export const StepAboutRule = memo( - ({ - defaultValues, - descriptionDirection = 'row', - isReadOnlyView, - isUpdateView = false, - isLoading, - setForm, - setStepData, - }) => { - const [myStepData, setMyStepData] = useState(stepAboutDefaultValue); +const TagContainer = styled.div` + margin-top: 16px; +`; - const { form } = useForm({ - defaultValue: myStepData, - options: { stripEmptyFields: false }, - schema, - }); +const StepAboutRuleComponent: FC = ({ + addPadding = false, + defaultValues, + descriptionDirection = 'row', + isReadOnlyView, + isUpdateView = false, + isLoading, + setForm, + setStepData, +}) => { + const [myStepData, setMyStepData] = useState(stepAboutDefaultValue); - const onSubmit = useCallback(async () => { - if (setStepData) { - setStepData(RuleStep.aboutRule, null, false); - const { isValid, data } = await form.submit(); - if (isValid) { - setStepData(RuleStep.aboutRule, data, isValid); - setMyStepData({ ...data, isNew: false } as AboutStepRule); - } - } - }, [form]); + const { form } = useForm({ + defaultValue: myStepData, + options: { stripEmptyFields: false }, + schema, + }); - useEffect(() => { - const { isNew, ...initDefaultValue } = myStepData; - if (defaultValues != null && !isEqual(initDefaultValue, defaultValues)) { - const myDefaultValues = { - ...defaultValues, - isNew: false, - }; - setMyStepData(myDefaultValues); - if (!isReadOnlyView) { - Object.keys(schema).forEach(key => { - const val = get(key, myDefaultValues); - if (val != null) { - form.setFieldValue(key, val); - } - }); - } + const onSubmit = useCallback(async () => { + if (setStepData) { + setStepData(RuleStep.aboutRule, null, false); + const { isValid, data } = await form.submit(); + if (isValid) { + setStepData(RuleStep.aboutRule, data, isValid); + setMyStepData({ ...data, isNew: false } as AboutStepRule); } - }, [defaultValues]); + } + }, [form]); - useEffect(() => { - if (setForm != null) { - setForm(RuleStep.aboutRule, form); + useEffect(() => { + const { isNew, ...initDefaultValue } = myStepData; + if (defaultValues != null && !isEqual(initDefaultValue, defaultValues)) { + const myDefaultValues = { + ...defaultValues, + isNew: false, + }; + setMyStepData(myDefaultValues); + if (!isReadOnlyView) { + Object.keys(schema).forEach(key => { + const val = get(key, myDefaultValues); + if (val != null) { + form.setFieldValue(key, val); + } + }); } - }, [form]); + } + }, [defaultValues]); + + useEffect(() => { + if (setForm != null) { + setForm(RuleStep.aboutRule, form); + } + }, [form]); - return isReadOnlyView && myStepData != null ? ( + return isReadOnlyView && myStepData != null ? ( + - ) : ( - <> + + ) : ( + <> +
( idAria: 'detectionEngineStepAboutRuleName', 'data-test-subj': 'detectionEngineStepAboutRuleName', euiFieldProps: { - compressed: true, fullWidth: false, disabled: isLoading, }, @@ -99,11 +110,9 @@ export const StepAboutRule = memo( ( idAria: 'detectionEngineStepAboutRuleSeverity', 'data-test-subj': 'detectionEngineStepAboutRuleSeverity', euiFieldProps: { - compressed: true, fullWidth: false, disabled: isLoading, options: severityOptions, @@ -129,29 +137,38 @@ export const StepAboutRule = memo( euiFieldProps: { max: 100, min: 0, - compressed: true, fullWidth: false, disabled: isLoading, options: severityOptions, + showTicks: true, + tickInterval: 25, }, }} /> + ( path="threats" component={AddMitreThreat} componentProps={{ - compressed: true, idAria: 'detectionEngineStepAboutRuleMitreThreats', isDisabled: isLoading, dataTestSubj: 'detectionEngineStepAboutRuleMitreThreats', }} /> - + + + {({ severity }) => { const newRiskScore = defaultRiskScoreBySeverity[severity as SeverityValue]; @@ -191,24 +209,26 @@ export const StepAboutRule = memo( }} - {!isUpdateView && ( - <> - - - - - {myStepData.isNew ? RuleI18n.CONTINUE : RuleI18n.UPDATE} - - - - - )} - - ); - } -); +
+ {!isUpdateView && ( + <> + + + + + {RuleI18n.CONTINUE} + + + + + )} + + ); +}; + +export const StepAboutRule = memo(StepAboutRuleComponent); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/schema.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/schema.tsx index c72312bb90836..3de0e7605f3d9 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/schema.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/schema.tsx @@ -4,12 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiText } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; -import { isEmpty } from 'lodash/fp'; -import React from 'react'; -import * as RuleI18n from '../../translations'; import { IMitreEnterpriseAttack } from '../../types'; import { FIELD_TYPES, @@ -18,6 +14,9 @@ import { ValidationFunc, ERROR_CODE, } from '../shared_imports'; +import { isMitreAttackInvalid } from '../mitre/helpers'; +import { OptionalFieldLabel } from '../optional_field_label'; +import { isUrlInvalid } from './helpers'; import * as I18n from './translations'; const { emptyField } = fieldValidators; @@ -63,7 +62,7 @@ export const schema: FormSchema = { ], }, severity: { - type: FIELD_TYPES.SELECT, + type: FIELD_TYPES.SUPER_SELECT, label: i18n.translate( 'xpack.siem.detectionEngine.createRule.stepAboutRule.fieldSeverityLabel', { @@ -92,6 +91,14 @@ export const schema: FormSchema = { } ), }, + timeline: { + label: i18n.translate( + 'xpack.siem.detectionEngine.createRule.stepAboutRule.fieldTimelineTemplateLabel', + { + defaultMessage: 'Timeline template', + } + ), + }, references: { label: i18n.translate( 'xpack.siem.detectionEngine.createRule.stepAboutRule.fieldReferenceUrlsLabel', @@ -99,25 +106,47 @@ export const schema: FormSchema = { defaultMessage: 'Reference URLs', } ), - labelAppend: {RuleI18n.OPTIONAL_FIELD}, + labelAppend: OptionalFieldLabel, + validations: [ + { + validator: ( + ...args: Parameters + ): ReturnType> | undefined => { + const [{ value, path }] = args; + let hasError = false; + (value as string[]).forEach(url => { + if (isUrlInvalid(url)) { + hasError = true; + } + }); + return hasError + ? { + code: 'ERR_FIELD_FORMAT', + path, + message: I18n.URL_FORMAT_INVALID, + } + : undefined; + }, + }, + ], }, falsePositives: { label: i18n.translate( 'xpack.siem.detectionEngine.createRule.stepAboutRule.fieldFalsePositiveLabel', { - defaultMessage: 'False positives', + defaultMessage: 'False positive examples', } ), - labelAppend: {RuleI18n.OPTIONAL_FIELD}, + labelAppend: OptionalFieldLabel, }, threats: { label: i18n.translate( 'xpack.siem.detectionEngine.createRule.stepAboutRule.fieldMitreThreatLabel', { - defaultMessage: 'MITRE ATT&CK', + defaultMessage: 'MITRE ATT&CK\\u2122', } ), - labelAppend: {RuleI18n.OPTIONAL_FIELD}, + labelAppend: OptionalFieldLabel, validations: [ { validator: ( @@ -126,7 +155,7 @@ export const schema: FormSchema = { const [{ value, path }] = args; let hasError = false; (value as IMitreEnterpriseAttack[]).forEach(v => { - if (isEmpty(v.tactic.name) || (v.tactic.name !== 'none' && isEmpty(v.techniques))) { + if (isMitreAttackInvalid(v.tactic.name, v.techniques)) { hasError = true; } }); @@ -146,6 +175,13 @@ export const schema: FormSchema = { label: i18n.translate('xpack.siem.detectionEngine.createRule.stepAboutRule.fieldTagsLabel', { defaultMessage: 'Tags', }), - labelAppend: {RuleI18n.OPTIONAL_FIELD}, + helpText: i18n.translate( + 'xpack.siem.detectionEngine.createRule.stepAboutRule.fieldTagsHelpText', + { + defaultMessage: + 'Type one or more custom identifying tags for this rule. Press enter after each tag to begin a new one.', + } + ), + labelAppend: OptionalFieldLabel, }, }; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/translations.ts index 017d4fe6fdf49..9323769765739 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/translations.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_about_rule/translations.ts @@ -9,14 +9,14 @@ import { i18n } from '@kbn/i18n'; export const ADD_REFERENCE = i18n.translate( 'xpack.siem.detectionEngine.createRule.stepAboutRuleForm.addReferenceDescription', { - defaultMessage: 'Add reference', + defaultMessage: 'Add reference URL', } ); export const ADD_FALSE_POSITIVE = i18n.translate( 'xpack.siem.detectionEngine.createRule.stepAboutRuleForm.addFalsePositiveDescription', { - defaultMessage: 'Add false positive', + defaultMessage: 'Add false positive example', } ); @@ -54,3 +54,10 @@ export const CUSTOM_MITRE_ATTACK_TECHNIQUES_REQUIRED = i18n.translate( defaultMessage: 'At least one Technique is required with a Tactic.', } ); + +export const URL_FORMAT_INVALID = i18n.translate( + 'xpack.siem.detectionEngine.createRule.stepDefineRule.referencesUrlInvalidError', + { + defaultMessage: 'Url is invalid format', + } +); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_content_wrapper/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_content_wrapper/index.tsx new file mode 100644 index 0000000000000..b04a321dab05b --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_content_wrapper/index.tsx @@ -0,0 +1,18 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import styled from 'styled-components'; + +const StyledDiv = styled.div<{ addPadding: boolean }>` + padding-left: ${({ addPadding }) => addPadding && '53px'}; /* to align with the step title */ +`; + +StyledDiv.defaultProps = { + addPadding: false, +}; + +export const StepContentWrapper = React.memo(StyledDiv); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/index.tsx index cc4e959cc9c78..6bdef4a69af1e 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/index.tsx @@ -4,9 +4,16 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiHorizontalRule, EuiFlexGroup, EuiFlexItem, EuiButton } from '@elastic/eui'; -import { isEqual, get } from 'lodash/fp'; -import React, { memo, useCallback, useState, useEffect } from 'react'; +import { + EuiButtonEmpty, + EuiHorizontalRule, + EuiFlexGroup, + EuiFlexItem, + EuiButton, +} from '@elastic/eui'; +import { isEmpty, isEqual, get } from 'lodash/fp'; +import React, { FC, memo, useCallback, useState, useEffect } from 'react'; +import styled from 'styled-components'; import { IIndexPattern } from '../../../../../../../../../../src/plugins/data/public'; import { useFetchIndexPatterns } from '../../../../../containers/detection_engine/rules'; @@ -16,9 +23,10 @@ import * as RuleI18n from '../../translations'; import { DefineStepRule, RuleStep, RuleStepProps } from '../../types'; import { StepRuleDescription } from '../description_step'; import { QueryBarDefineRule } from '../query_bar'; +import { StepContentWrapper } from '../step_content_wrapper'; import { Field, Form, FormDataProvider, getUseField, UseField, useForm } from '../shared_imports'; import { schema } from './schema'; -import * as I18n from './translations'; +import * as i18n from './translations'; const CommonUseField = getUseField({ component: Field }); @@ -34,7 +42,20 @@ const stepDefineDefaultValue = { filters: [], saved_id: null, }, - useIndicesConfig: 'true', +}; + +const MyLabelButton = styled(EuiButtonEmpty)` + height: 18px; + font-size: 12px; + + .euiIcon { + width: 14px; + height: 14px; + } +`; + +MyLabelButton.defaultProps = { + flush: 'right', }; const getStepDefaultValue = ( @@ -45,7 +66,6 @@ const getStepDefaultValue = ( return { ...defaultValues, isNew: false, - useIndicesConfig: `${isEqual(defaultValues.index, indicesConfig)}`, }; } else { return { @@ -55,166 +75,176 @@ const getStepDefaultValue = ( } }; -export const StepDefineRule = memo( - ({ - defaultValues, - descriptionDirection = 'row', - isReadOnlyView, - isLoading, - isUpdateView = false, - resizeParentContainer, - setForm, - setStepData, - }) => { - const [localUseIndicesConfig, setLocalUseIndicesConfig] = useState(''); - const [indicesConfig] = useUiSetting$(DEFAULT_INDEX_KEY); - const [ - { indexPatterns: indexPatternQueryBar, isLoading: indexPatternLoadingQueryBar }, - setIndices, - ] = useFetchIndexPatterns(defaultValues != null ? defaultValues.index : indicesConfig ?? []); - const [myStepData, setMyStepData] = useState(stepDefineDefaultValue); - - const { form } = useForm({ - defaultValue: myStepData, - options: { stripEmptyFields: false }, - schema, - }); - - const onSubmit = useCallback(async () => { - if (setStepData) { - setStepData(RuleStep.defineRule, null, false); - const { isValid, data } = await form.submit(); - if (isValid && setStepData) { - setStepData(RuleStep.defineRule, data, isValid); - setMyStepData({ ...data, isNew: false } as DefineStepRule); - } +const StepDefineRuleComponent: FC = ({ + addPadding = false, + defaultValues, + descriptionDirection = 'row', + isReadOnlyView, + isLoading, + isUpdateView = false, + setForm, + setStepData, +}) => { + const [openTimelineSearch, setOpenTimelineSearch] = useState(false); + const [localUseIndicesConfig, setLocalUseIndicesConfig] = useState(false); + const [indicesConfig] = useUiSetting$(DEFAULT_INDEX_KEY); + const [mylocalIndicesConfig, setMyLocalIndicesConfig] = useState( + defaultValues != null ? defaultValues.index : indicesConfig ?? [] + ); + const [ + { browserFields, indexPatterns: indexPatternQueryBar, isLoading: indexPatternLoadingQueryBar }, + ] = useFetchIndexPatterns(mylocalIndicesConfig); + const [myStepData, setMyStepData] = useState( + getStepDefaultValue(indicesConfig, null) + ); + + const { form } = useForm({ + defaultValue: myStepData, + options: { stripEmptyFields: false }, + schema, + }); + + const onSubmit = useCallback(async () => { + if (setStepData) { + setStepData(RuleStep.defineRule, null, false); + const { isValid, data } = await form.submit(); + if (isValid && setStepData) { + setStepData(RuleStep.defineRule, data, isValid); + setMyStepData({ ...data, isNew: false } as DefineStepRule); } - }, [form]); - - useEffect(() => { - if (indicesConfig != null && defaultValues != null) { - const myDefaultValues = getStepDefaultValue(indicesConfig, defaultValues); - if (!isEqual(myDefaultValues, myStepData)) { - setMyStepData(myDefaultValues); - setLocalUseIndicesConfig(myDefaultValues.useIndicesConfig); - if (!isReadOnlyView) { - Object.keys(schema).forEach(key => { - const val = get(key, myDefaultValues); - if (val != null) { - form.setFieldValue(key, val); - } - }); - } + } + }, [form]); + + useEffect(() => { + if (indicesConfig != null && defaultValues != null) { + const myDefaultValues = getStepDefaultValue(indicesConfig, defaultValues); + if (!isEqual(myDefaultValues, myStepData)) { + setMyStepData(myDefaultValues); + setLocalUseIndicesConfig(isEqual(myDefaultValues.index, indicesConfig)); + if (!isReadOnlyView) { + Object.keys(schema).forEach(key => { + const val = get(key, myDefaultValues); + if (val != null) { + form.setFieldValue(key, val); + } + }); } } - }, [defaultValues, indicesConfig]); + } + }, [defaultValues, indicesConfig]); - useEffect(() => { - if (setForm != null) { - setForm(RuleStep.defineRule, form); - } - }, [form]); + useEffect(() => { + if (setForm != null) { + setForm(RuleStep.defineRule, form); + } + }, [form]); + + const handleResetIndices = useCallback(() => { + const indexField = form.getFields().index; + indexField.setValue(indicesConfig); + }, [form, indicesConfig]); + + const handleOpenTimelineSearch = useCallback(() => { + setOpenTimelineSearch(true); + }, []); + + const handleCloseTimelineSearch = useCallback(() => { + setOpenTimelineSearch(false); + }, []); - return isReadOnlyView && myStepData != null ? ( + return isReadOnlyView && myStepData != null ? ( + - ) : ( - <> + + ) : ( + <> +
- + {i18n.RESET_DEFAULT_INDEX} + + ) : null, + }} componentProps={{ idAria: 'detectionEngineStepDefineRuleIndices', 'data-test-subj': 'detectionEngineStepDefineRuleIndices', euiFieldProps: { - compressed: true, fullWidth: true, isDisabled: isLoading, + placeholder: '', }, }} /> + {i18n.IMPORT_TIMELINE_QUERY} + + ), + }} component={QueryBarDefineRule} componentProps={{ - compressed: true, + browserFields, loading: indexPatternLoadingQueryBar, idAria: 'detectionEngineStepDefineRuleQueryBar', indexPattern: indexPatternQueryBar, isDisabled: isLoading, isLoading: indexPatternLoadingQueryBar, dataTestSubj: 'detectionEngineStepDefineRuleQueryBar', - resizeParentContainer, + openTimelineSearch, + onCloseTimelineSearch: handleCloseTimelineSearch, }} /> - - {({ useIndicesConfig }) => { - if (localUseIndicesConfig !== useIndicesConfig) { - const indexField = form.getFields().index; - if ( - indexField != null && - useIndicesConfig === 'true' && - !isEqual(indexField.value, indicesConfig) - ) { - indexField.setValue(indicesConfig); - setIndices(indicesConfig); - } else if ( - indexField != null && - useIndicesConfig === 'false' && - isEqual(indexField.value, indicesConfig) - ) { - indexField.setValue([]); - setIndices([]); + + {({ index }) => { + if (index != null) { + if (isEqual(index, indicesConfig) && !localUseIndicesConfig) { + setLocalUseIndicesConfig(true); + } + if (!isEqual(index, indicesConfig) && localUseIndicesConfig) { + setLocalUseIndicesConfig(false); + } + if (index != null && !isEmpty(index) && !isEqual(index, mylocalIndicesConfig)) { + setMyLocalIndicesConfig(index); } - setLocalUseIndicesConfig(useIndicesConfig); } - return null; }} - {!isUpdateView && ( - <> - - - - - {myStepData.isNew ? RuleI18n.CONTINUE : RuleI18n.UPDATE} - - - - - )} - - ); - } -); +
+ {!isUpdateView && ( + <> + + + + + {RuleI18n.CONTINUE} + + + + + )} + + ); +}; + +export const StepDefineRule = memo(StepDefineRuleComponent); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/schema.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/schema.tsx index 9b54ada8227c6..079ec0dab4c5a 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/schema.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/schema.tsx @@ -10,7 +10,6 @@ import { isEmpty } from 'lodash/fp'; import React from 'react'; import { esKuery } from '../../../../../../../../../../src/plugins/data/public'; -import * as RuleI18n from '../../translations'; import { FieldValueQueryBar } from '../query_bar'; import { ERROR_CODE, @@ -19,33 +18,27 @@ import { FormSchema, ValidationFunc, } from '../shared_imports'; -import { CUSTOM_QUERY_REQUIRED, INVALID_CUSTOM_QUERY } from './translations'; +import { CUSTOM_QUERY_REQUIRED, INVALID_CUSTOM_QUERY, INDEX_HELPER_TEXT } from './translations'; const { emptyField } = fieldValidators; export const schema: FormSchema = { - useIndicesConfig: { - type: FIELD_TYPES.RADIO_GROUP, + index: { + type: FIELD_TYPES.COMBO_BOX, label: i18n.translate( - 'xpack.siem.detectionEngine.createRule.stepDefineRule.fieldIndicesTypeLabel', + 'xpack.siem.detectionEngine.createRule.stepAboutRule.fiedIndexPatternsLabel', { - defaultMessage: 'Indices type', + defaultMessage: 'Index patterns', } ), - }, - index: { - type: FIELD_TYPES.COMBO_BOX, - label: i18n.translate('xpack.siem.detectionEngine.createRule.stepAboutRule.fiedIndicesLabel', { - defaultMessage: 'Indices', - }), - labelAppend: {RuleI18n.OPTIONAL_FIELD}, + helpText: {INDEX_HELPER_TEXT}, validations: [ { validator: emptyField( i18n.translate( 'xpack.siem.detectionEngine.createRule.stepDefineRule.outputIndiceNameFieldRequiredError', { - defaultMessage: 'An output indice name for signals is required.', + defaultMessage: 'A minimum of one index pattern is required.', } ) ), diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/translations.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/translations.tsx index 0050c59a4a2c8..8394f090e346c 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/translations.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_define_rule/translations.tsx @@ -33,3 +33,25 @@ export const CUSTOM_INDICES = i18n.translate( defaultMessage: 'Provide custom list of indices', } ); + +export const INDEX_HELPER_TEXT = i18n.translate( + 'xpack.siem.detectionEngine.createRule.stepDefineRule.indicesHelperDescription', + { + defaultMessage: + 'Enter the pattern of Elasticsearch indices where you would like this rule to run. By default, these will include index patterns defined in SIEM advanced settings.', + } +); + +export const RESET_DEFAULT_INDEX = i18n.translate( + 'xpack.siem.detectionEngine.createRule.stepDefineRule.resetDefaultIndicesButton', + { + defaultMessage: 'Reset to default index patterns', + } +); + +export const IMPORT_TIMELINE_QUERY = i18n.translate( + 'xpack.siem.detectionEngine.createRule.stepDefineRule.importTimelineQueryButton', + { + defaultMessage: 'Import query from saved timeline', + } +); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_panel/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_panel/index.tsx index 21b38a83dad9d..88cecadb8b137 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_panel/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_panel/index.tsx @@ -17,15 +17,15 @@ interface StepPanelProps { } const MyPanel = styled(EuiPanel)` - poistion: relative; + position: relative; `; -export const StepPanel = memo(({ children, loading, title }) => { - return ( - - {loading && } - - {children} - - ); -}); +const StepPanelComponent: React.FC = ({ children, loading, title }) => ( + + {loading && } + + {children} + +); + +export const StepPanel = memo(StepPanelComponent); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/index.tsx index 6f7e49bc8ab9a..b99201abe8777 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/index.tsx @@ -6,12 +6,13 @@ import { EuiHorizontalRule, EuiFlexGroup, EuiFlexItem, EuiButton } from '@elastic/eui'; import { isEqual, get } from 'lodash/fp'; -import React, { memo, useCallback, useEffect, useState } from 'react'; +import React, { FC, memo, useCallback, useEffect, useState } from 'react'; import { RuleStep, RuleStepProps, ScheduleStepRule } from '../../types'; import { StepRuleDescription } from '../description_step'; import { ScheduleItem } from '../schedule_item_form'; import { Form, UseField, useForm } from '../shared_imports'; +import { StepContentWrapper } from '../step_content_wrapper'; import { schema } from './schema'; import * as I18n from './translations'; @@ -26,73 +27,75 @@ const stepScheduleDefaultValue = { from: '0m', }; -export const StepScheduleRule = memo( - ({ - defaultValues, - descriptionDirection = 'row', - isReadOnlyView, - isLoading, - isUpdateView = false, - setStepData, - setForm, - }) => { - const [myStepData, setMyStepData] = useState(stepScheduleDefaultValue); +const StepScheduleRuleComponent: FC = ({ + addPadding = false, + defaultValues, + descriptionDirection = 'row', + isReadOnlyView, + isLoading, + isUpdateView = false, + setStepData, + setForm, +}) => { + const [myStepData, setMyStepData] = useState(stepScheduleDefaultValue); - const { form } = useForm({ - defaultValue: myStepData, - options: { stripEmptyFields: false }, - schema, - }); + const { form } = useForm({ + defaultValue: myStepData, + options: { stripEmptyFields: false }, + schema, + }); - const onSubmit = useCallback( - async (enabled: boolean) => { - if (setStepData) { - setStepData(RuleStep.scheduleRule, null, false); - const { isValid: newIsValid, data } = await form.submit(); - if (newIsValid) { - setStepData(RuleStep.scheduleRule, { ...data, enabled }, newIsValid); - setMyStepData({ ...data, isNew: false } as ScheduleStepRule); - } - } - }, - [form] - ); - - useEffect(() => { - const { isNew, ...initDefaultValue } = myStepData; - if (defaultValues != null && !isEqual(initDefaultValue, defaultValues)) { - const myDefaultValues = { - ...defaultValues, - isNew: false, - }; - setMyStepData(myDefaultValues); - if (!isReadOnlyView) { - Object.keys(schema).forEach(key => { - const val = get(key, myDefaultValues); - if (val != null) { - form.setFieldValue(key, val); - } - }); + const onSubmit = useCallback( + async (enabled: boolean) => { + if (setStepData) { + setStepData(RuleStep.scheduleRule, null, false); + const { isValid: newIsValid, data } = await form.submit(); + if (newIsValid) { + setStepData(RuleStep.scheduleRule, { ...data, enabled }, newIsValid); + setMyStepData({ ...data, isNew: false } as ScheduleStepRule); } } - }, [defaultValues]); + }, + [form] + ); - useEffect(() => { - if (setForm != null) { - setForm(RuleStep.scheduleRule, form); + useEffect(() => { + const { isNew, ...initDefaultValue } = myStepData; + if (defaultValues != null && !isEqual(initDefaultValue, defaultValues)) { + const myDefaultValues = { + ...defaultValues, + isNew: false, + }; + setMyStepData(myDefaultValues); + if (!isReadOnlyView) { + Object.keys(schema).forEach(key => { + const val = get(key, myDefaultValues); + if (val != null) { + form.setFieldValue(key, val); + } + }); } - }, [form]); + } + }, [defaultValues]); - return isReadOnlyView && myStepData != null ? ( + useEffect(() => { + if (setForm != null) { + setForm(RuleStep.scheduleRule, form); + } + }, [form]); + + return isReadOnlyView && myStepData != null ? ( + - ) : ( - <> + + ) : ( + <> +
( path="from" component={ScheduleItem} componentProps={{ - compressed: true, idAria: 'detectionEngineStepScheduleRuleFrom', isDisabled: isLoading, dataTestSubj: 'detectionEngineStepScheduleRuleFrom', }} /> +
+ + {!isUpdateView && ( + <> + + + + + {I18n.COMPLETE_WITHOUT_ACTIVATING} + + + + + {I18n.COMPLETE_WITH_ACTIVATING} + + + + + )} + + ); +}; - {!isUpdateView && ( - <> - - - - - {I18n.COMPLETE_WITHOUT_ACTIVATING} - - - - - {I18n.COMPLETE_WITH_ACTIVATING} - - - - - )} - - ); - } -); +export const StepScheduleRule = memo(StepScheduleRuleComponent); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/schema.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/schema.tsx index 31e56265dec42..4da17b88b9ad0 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/schema.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/components/step_schedule_rule/schema.tsx @@ -4,11 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ -import { EuiText } from '@elastic/eui'; -import React from 'react'; import { i18n } from '@kbn/i18n'; -import * as RuleI18n from '../../translations'; +import { OptionalFieldLabel } from '../optional_field_label'; import { FormSchema } from '../shared_imports'; export const schema: FormSchema = { @@ -33,7 +31,7 @@ export const schema: FormSchema = { defaultMessage: 'Additional look-back', } ), - labelAppend: {RuleI18n.OPTIONAL_FIELD}, + labelAppend: OptionalFieldLabel, helpText: i18n.translate( 'xpack.siem.detectionEngine.createRule.stepScheduleRule.fieldAdditionalLookBackHelpText', { diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/helpers.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/helpers.ts index a25ccce569dd4..ce91e15cdcf0d 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/helpers.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/helpers.ts @@ -40,14 +40,14 @@ const getTimeTypeValue = (time: string): { unit: string; value: number } => { }; const formatDefineStepData = (defineStepData: DefineStepRule): DefineStepRuleJson => { - const { queryBar, useIndicesConfig, isNew, ...rest } = defineStepData; + const { queryBar, isNew, ...rest } = defineStepData; const { filters, query, saved_id: savedId } = queryBar; return { ...rest, language: query.language, filters, query: query.query as string, - ...(savedId != null ? { saved_id: savedId } : {}), + ...(savedId != null && savedId !== '' ? { saved_id: savedId } : {}), }; }; @@ -72,11 +72,25 @@ const formatScheduleStepData = (scheduleData: ScheduleStepRule): ScheduleStepRul }; const formatAboutStepData = (aboutStepData: AboutStepRule): AboutStepRuleJson => { - const { falsePositives, references, riskScore, threats, isNew, ...rest } = aboutStepData; + const { + falsePositives, + references, + riskScore, + threats, + timeline, + isNew, + ...rest + } = aboutStepData; return { false_positives: falsePositives.filter(item => !isEmpty(item)), references: references.filter(item => !isEmpty(item)), risk_score: riskScore, + ...(timeline.id != null && timeline.title != null + ? { + timeline_id: timeline.id, + timeline_title: timeline.title, + } + : {}), threats: threats .filter(threat => threat.tactic.name !== 'none') .map(threat => ({ @@ -97,7 +111,7 @@ export const formatRule = ( scheduleData: ScheduleStepRule, ruleId?: string ): NewRule => { - const type: FormatRuleType = defineStepData.queryBar.saved_id != null ? 'saved_query' : 'query'; + const type: FormatRuleType = !isEmpty(defineStepData.queryBar.saved_id) ? 'saved_query' : 'query'; const persistData = { type, ...formatDefineStepData(defineStepData), diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/index.tsx index 3e8dbeba89546..e5656f5b081fb 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/index.tsx @@ -12,12 +12,14 @@ import styled from 'styled-components'; import { HeaderPage } from '../../../../components/header_page'; import { DETECTION_ENGINE_PAGE_NAME } from '../../../../components/link_to/redirect_to_detection_engine'; import { WrapperPage } from '../../../../components/wrapper_page'; +import { usePersistRule } from '../../../../containers/detection_engine/rules'; +import { SpyRoute } from '../../../../utils/route/spy_routes'; +import { useUserInfo } from '../../components/user_info'; import { AccordionTitle } from '../components/accordion_title'; +import { FormData, FormHook } from '../components/shared_imports'; import { StepAboutRule } from '../components/step_about_rule'; import { StepDefineRule } from '../components/step_define_rule'; import { StepScheduleRule } from '../components/step_schedule_rule'; -import { usePersistRule } from '../../../../containers/detection_engine/rules'; -import { SpyRoute } from '../../../../utils/route/spy_routes'; import * as RuleI18n from '../translations'; import { AboutStepRule, DefineStepRule, RuleStep, RuleStepData, ScheduleStepRule } from '../types'; import { formatRule } from './helpers'; @@ -25,20 +27,43 @@ import * as i18n from './translations'; const stepsRuleOrder = [RuleStep.defineRule, RuleStep.aboutRule, RuleStep.scheduleRule]; -const ResizeEuiPanel = styled(EuiPanel)<{ - height?: number; +const MyEuiPanel = styled(EuiPanel)<{ + zIndex?: number; }>` + position: relative; + z-index: ${props => props.zIndex}; /* ugly fix to allow searchBar to overflow the EuiPanel */ + + .euiAccordion__iconWrapper { + display: none; + } .euiAccordion__childWrapper { - height: ${props => (props.height !== -1 ? `${props.height}px !important` : 'auto')}; + overflow: visible; + } + .euiAccordion__button { + cursor: default !important; + &:hover { + text-decoration: none !important; + } } `; export const CreateRuleComponent = React.memo(() => { - const [heightAccordion, setHeightAccordion] = useState(-1); - const [openAccordionId, setOpenAccordionId] = useState(RuleStep.defineRule); + const { + loading, + isSignalIndexExists, + isAuthenticated, + canUserCRUD, + hasManageApiKey, + } = useUserInfo(); + const [openAccordionId, setOpenAccordionId] = useState(RuleStep.defineRule); const defineRuleRef = useRef(null); const aboutRuleRef = useRef(null); const scheduleRuleRef = useRef(null); + const stepsForm = useRef | null>>({ + [RuleStep.defineRule]: null, + [RuleStep.aboutRule]: null, + [RuleStep.scheduleRule]: null, + }); const stepsData = useRef>({ [RuleStep.defineRule]: { isValid: false, data: {} }, [RuleStep.aboutRule]: { isValid: false, data: {} }, @@ -50,6 +75,18 @@ export const CreateRuleComponent = React.memo(() => { [RuleStep.scheduleRule]: false, }); const [{ isLoading, isSaved }, setRule] = usePersistRule(); + const userHasNoPermissions = + canUserCRUD != null && hasManageApiKey != null ? !canUserCRUD || !hasManageApiKey : false; + + if ( + isSignalIndexExists != null && + isAuthenticated != null && + (!isSignalIndexExists || !isAuthenticated) + ) { + return ; + } else if (userHasNoPermissions) { + return ; + } const setStepData = useCallback( (step: RuleStep, data: unknown, isValid: boolean) => { @@ -57,11 +94,17 @@ export const CreateRuleComponent = React.memo(() => { if (isValid) { const stepRuleIdx = stepsRuleOrder.findIndex(item => step === item); if ([0, 1].includes(stepRuleIdx)) { - setIsStepRuleInEditView({ - ...isStepRuleInReadOnlyView, - [step]: true, - }); - if (openAccordionId !== stepsRuleOrder[stepRuleIdx + 1]) { + if (isStepRuleInReadOnlyView[stepsRuleOrder[stepRuleIdx + 1]]) { + setIsStepRuleInEditView({ + ...isStepRuleInReadOnlyView, + [step]: true, + [stepsRuleOrder[stepRuleIdx + 1]]: false, + }); + } else if (openAccordionId !== stepsRuleOrder[stepRuleIdx + 1]) { + setIsStepRuleInEditView({ + ...isStepRuleInReadOnlyView, + [step]: true, + }); openCloseAccordion(stepsRuleOrder[stepRuleIdx + 1]); setOpenAccordionId(stepsRuleOrder[stepRuleIdx + 1]); } @@ -80,9 +123,13 @@ export const CreateRuleComponent = React.memo(() => { } } }, - [openAccordionId, stepsData.current, setRule] + [isStepRuleInReadOnlyView, openAccordionId, stepsData.current, setRule] ); + const setStepsForm = useCallback((step: RuleStep, form: FormHook) => { + stepsForm.current[step] = form; + }, []); + const getAccordionType = useCallback( (accordionId: RuleStep) => { if (accordionId === openAccordionId) { @@ -135,42 +182,38 @@ export const CreateRuleComponent = React.memo(() => { (id: RuleStep, isOpen: boolean) => { const activeRuleIdx = stepsRuleOrder.findIndex(step => step === openAccordionId); const stepRuleIdx = stepsRuleOrder.findIndex(step => step === id); - const isLatestStepsRuleValid = - stepRuleIdx === 0 - ? true - : stepsRuleOrder - .filter((stepRule, index) => index < stepRuleIdx) - .every(stepRule => stepsData.current[stepRule].isValid); - if (stepRuleIdx < activeRuleIdx && !isOpen) { + if ((id === openAccordionId || stepRuleIdx < activeRuleIdx) && !isOpen) { openCloseAccordion(id); } else if (stepRuleIdx >= activeRuleIdx) { if ( - openAccordionId != null && openAccordionId !== id && !stepsData.current[openAccordionId].isValid && !isStepRuleInReadOnlyView[id] && isOpen ) { openCloseAccordion(id); - } else if (!isLatestStepsRuleValid && isOpen) { - openCloseAccordion(id); - } else if (id !== openAccordionId && isOpen) { - setOpenAccordionId(id); } } }, - [isStepRuleInReadOnlyView, openAccordionId] + [isStepRuleInReadOnlyView, openAccordionId, stepsData] ); const manageIsEditable = useCallback( - (id: RuleStep) => { - setIsStepRuleInEditView({ - ...isStepRuleInReadOnlyView, - [id]: false, - }); + async (id: RuleStep) => { + const activeForm = await stepsForm.current[openAccordionId]?.submit(); + if (activeForm != null && activeForm?.isValid) { + setOpenAccordionId(id); + openCloseAccordion(openAccordionId); + + setIsStepRuleInEditView({ + ...isStepRuleInReadOnlyView, + [openAccordionId]: openAccordionId === RuleStep.scheduleRule ? false : true, + [id]: false, + }); + } }, - [isStepRuleInReadOnlyView] + [isStepRuleInReadOnlyView, openAccordionId] ); if (isSaved) { @@ -183,10 +226,10 @@ export const CreateRuleComponent = React.memo(() => { - + { size="xs" onClick={manageIsEditable.bind(null, RuleStep.defineRule)} > - {`Edit`} + {i18n.EDIT_RULE} ) } > - + setHeightAccordion(height)} + descriptionDirection="row" /> - - - + + + { size="xs" onClick={manageIsEditable.bind(null, RuleStep.aboutRule)} > - {`Edit`} + {i18n.EDIT_RULE} ) } > - + - - - + + + { size="xs" onClick={manageIsEditable.bind(null, RuleStep.scheduleRule)} > - {`Edit`} + {i18n.EDIT_RULE} ) } > - + - + diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/translations.ts index 884f3f3741228..329bcc286fb70 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/translations.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/create/translations.ts @@ -9,3 +9,7 @@ import { i18n } from '@kbn/i18n'; export const PAGE_TITLE = i18n.translate('xpack.siem.detectionEngine.createRule.pageTitle', { defaultMessage: 'Create new rule', }); + +export const EDIT_RULE = i18n.translate('xpack.siem.detectionEngine.createRule.editRuleButton', { + defaultMessage: 'Edit', +}); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx index b0cf183949dd9..cdb08a0bbeffe 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/details/index.tsx @@ -6,10 +6,12 @@ import { EuiButton, EuiLoadingSpinner, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import React, { memo, useMemo } from 'react'; -import { useParams } from 'react-router-dom'; +import React, { memo, useCallback, useMemo } from 'react'; +import { Redirect, useParams } from 'react-router-dom'; import { StickyContainer } from 'react-sticky'; +import { ActionCreator } from 'typescript-fsa'; +import { connect } from 'react-redux'; import { FiltersGlobal } from '../../../../components/filters_global'; import { FormattedDate } from '../../../../components/formatted_date'; import { HeaderPage } from '../../../../components/header_page'; @@ -24,200 +26,300 @@ import { } from '../../../../containers/source'; import { SpyRoute } from '../../../../utils/route/spy_routes'; -import { SignalsCharts } from '../../components/signals_chart'; +import { SignalsHistogramPanel } from '../../components/signals_histogram_panel'; import { SignalsTable } from '../../components/signals'; +import { useUserInfo } from '../../components/user_info'; import { DetectionEngineEmptyPage } from '../../detection_engine_empty_page'; import { useSignalInfo } from '../../components/signals_info'; import { StepAboutRule } from '../components/step_about_rule'; import { StepDefineRule } from '../components/step_define_rule'; import { StepScheduleRule } from '../components/step_schedule_rule'; import { buildSignalsRuleIdFilter } from '../../components/signals/default_config'; +import { NoWriteSignalsCallOut } from '../../components/no_write_signals_callout'; import * as detectionI18n from '../../translations'; +import { ReadOnlyCallOut } from '../components/read_only_callout'; import { RuleSwitch } from '../components/rule_switch'; import { StepPanel } from '../components/step_panel'; import { getStepsData } from '../helpers'; import * as ruleI18n from '../translations'; import * as i18n from './translations'; import { GlobalTime } from '../../../../containers/global_time'; +import { signalsHistogramOptions } from '../../components/signals_histogram_panel/config'; +import { InputsModelId } from '../../../../store/inputs/constants'; +import { esFilters } from '../../../../../../../../../src/plugins/data/common/es_query'; +import { Query } from '../../../../../../../../../src/plugins/data/common/query'; +import { inputsSelectors } from '../../../../store/inputs'; +import { State } from '../../../../store'; +import { InputsRange } from '../../../../store/inputs/model'; +import { setAbsoluteRangeDatePicker as dispatchSetAbsoluteRangeDatePicker } from '../../../../store/inputs/actions'; -export const RuleDetailsComponent = memo(() => { - const { ruleId } = useParams(); - const [loading, rule] = useRule(ruleId); - const { aboutRuleData, defineRuleData, scheduleRuleData } = getStepsData({ - rule, - detailsView: true, - }); - const [lastSignals] = useSignalInfo({ ruleId }); - - const title = loading === true || rule === null ? : rule.name; - const subTitle = useMemo( - () => - loading === true || rule === null ? ( - - ) : ( - [ - - ), - }} - />, - rule?.updated_by != null ? ( +interface ReduxProps { + filters: esFilters.Filter[]; + query: Query; +} + +export interface DispatchProps { + setAbsoluteRangeDatePicker: ActionCreator<{ + id: InputsModelId; + from: number; + to: number; + }>; +} + +type RuleDetailsComponentProps = ReduxProps & DispatchProps; + +const RuleDetailsComponent = memo( + ({ filters, query, setAbsoluteRangeDatePicker }) => { + const { + loading, + isSignalIndexExists, + isAuthenticated, + canUserCRUD, + hasManageApiKey, + hasIndexWrite, + signalIndexName, + } = useUserInfo(); + const { ruleId } = useParams(); + const [isLoading, rule] = useRule(ruleId); + const { aboutRuleData, defineRuleData, scheduleRuleData } = getStepsData({ + rule, + detailsView: true, + }); + const [lastSignals] = useSignalInfo({ ruleId }); + const userHasNoPermissions = + canUserCRUD != null && hasManageApiKey != null ? !canUserCRUD || !hasManageApiKey : false; + + if ( + isSignalIndexExists != null && + isAuthenticated != null && + (!isSignalIndexExists || !isAuthenticated) + ) { + return ; + } + + const title = isLoading === true || rule === null ? : rule.name; + const subTitle = useMemo( + () => + isLoading === true || rule === null ? ( + + ) : ( + [ ), }} - /> - ) : ( - '' - ), - ] - ), - [loading, rule] - ); - - const signalDefaultFilters = useMemo( - () => (ruleId != null ? buildSignalsRuleIdFilter(ruleId) : []), - [ruleId] - ); - return ( - <> - - {({ indicesExist, indexPattern }) => { - return indicesExistOrDataTemporarilyUnavailable(indicesExist) ? ( - - {({ to, from }) => ( - - - - - - - - {detectionI18n.LAST_SIGNAL} - {': '} - {lastSignals} - - ) : null, - 'Status: Comming Soon', - ]} - title={title} - > - - - + />, + rule?.updated_by != null ? ( + + ), + }} + /> + ) : ( + '' + ), + ] + ), + [isLoading, rule] + ); + + const signalDefaultFilters = useMemo( + () => (ruleId != null ? buildSignalsRuleIdFilter(ruleId) : []), + [ruleId] + ); + + const signalMergedFilters = useMemo(() => [...signalDefaultFilters, ...filters], [ + signalDefaultFilters, + filters, + ]); + + const updateDateRangeCallback = useCallback( + (min: number, max: number) => { + setAbsoluteRangeDatePicker({ id: 'global', from: min, to: max }); + }, + [setAbsoluteRangeDatePicker] + ); + + return ( + <> + {hasIndexWrite != null && !hasIndexWrite && } + {userHasNoPermissions && } + + {({ indicesExist, indexPattern }) => { + return indicesExistOrDataTemporarilyUnavailable(indicesExist) ? ( + + {({ to, from }) => ( + + + + + + + + {detectionI18n.LAST_SIGNAL} + {': '} + {lastSignals} + + ) : null, + 'Status: Comming Soon', + ]} + title={title} + > + + + + + + + + + + {ruleI18n.EDIT_RULE_SETTINGS} + + + + + + + + + + + + + {defineRuleData != null && ( + + )} + - - - - - {ruleI18n.EDIT_RULE_SETTINGS} - - - + + + {aboutRuleData != null && ( + + )} + + + + + + {scheduleRuleData != null && ( + + )} + - - - - - - - - {defineRuleData != null && ( - - )} - - - - - - {aboutRuleData != null && ( - - )} - - - - - - {scheduleRuleData != null && ( - - )} - - - - - - - - - - - {ruleId != null && ( - - )} - - - )} - - ) : ( - - - - - - ); - }} - - - - - ); -}); + + + + + + + {ruleId != null && ( + + )} + + + )} + + ) : ( + + + + + + ); + }} + + + + + ); + } +); + RuleDetailsComponent.displayName = 'RuleDetailsComponent'; + +const makeMapStateToProps = () => { + const getGlobalInputs = inputsSelectors.globalSelector(); + return (state: State) => { + const globalInputs: InputsRange = getGlobalInputs(state); + const { query, filters } = globalInputs; + + return { + query, + filters, + }; + }; +}; + +export const RuleDetails = connect(makeMapStateToProps, { + setAbsoluteRangeDatePicker: dispatchSetAbsoluteRangeDatePicker, +})(RuleDetailsComponent); + +RuleDetails.displayName = 'RuleDetails'; diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/index.tsx index 8e32f82dff0b1..e583461f52439 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/edit/index.tsx @@ -22,6 +22,7 @@ import { WrapperPage } from '../../../../components/wrapper_page'; import { SpyRoute } from '../../../../utils/route/spy_routes'; import { DETECTION_ENGINE_PAGE_NAME } from '../../../../components/link_to/redirect_to_detection_engine'; import { useRule, usePersistRule } from '../../../../containers/detection_engine/rules'; +import { useUserInfo } from '../../components/user_info'; import { FormHook, FormData } from '../components/shared_imports'; import { StepPanel } from '../components/step_panel'; import { StepAboutRule } from '../components/step_about_rule'; @@ -47,8 +48,28 @@ interface ScheduleStepRuleForm extends StepRuleForm { } export const EditRuleComponent = memo(() => { + const { + loading: initLoading, + isSignalIndexExists, + isAuthenticated, + canUserCRUD, + hasManageApiKey, + } = useUserInfo(); const { ruleId } = useParams(); const [loading, rule] = useRule(ruleId); + + const userHasNoPermissions = + canUserCRUD != null && hasManageApiKey != null ? !canUserCRUD || !hasManageApiKey : false; + if ( + isSignalIndexExists != null && + isAuthenticated != null && + (!isSignalIndexExists || !isAuthenticated) + ) { + return ; + } else if (userHasNoPermissions) { + return ; + } + const [initForm, setInitForm] = useState(false); const [myAboutRuleForm, setMyAboutRuleForm] = useState({ data: null, @@ -88,7 +109,7 @@ export const EditRuleComponent = memo(() => { content: ( <> - + {myDefineRuleForm.data != null && ( { content: ( <> - + {myAboutRuleForm.data != null && ( { content: ( <> - + {myScheduleRuleForm.data != null && ( { ], [ loading, + initLoading, isLoading, myAboutRuleForm, myDefineRuleForm, @@ -249,7 +271,7 @@ export const EditRuleComponent = memo(() => { }, []); if (isSaved || (rule != null && rule.immutable)) { - return ; + return ; } return ( @@ -257,7 +279,7 @@ export const EditRuleComponent = memo(() => { { responsive={false} > - + {i18n.CANCEL} - + {i18n.SAVE_CHANGES} diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/helpers.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/helpers.tsx index 46301ae808919..cc0882dd7e426 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/helpers.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/helpers.tsx @@ -5,6 +5,7 @@ */ import { pick } from 'lodash/fp'; +import { useLocation } from 'react-router-dom'; import { esFilters } from '../../../../../../../../src/plugins/data/public'; import { Rule } from '../../../containers/detection_engine/rules'; @@ -33,7 +34,6 @@ export const getStepsData = ({ filters: rule.filters as esFilters.Filter[], saved_id: rule.saved_id ?? null, }, - useIndicesConfig: 'true', } : null; const aboutRuleData: AboutStepRule | null = @@ -45,6 +45,10 @@ export const getStepsData = ({ threats: rule.threats as IMitreEnterpriseAttack[], falsePositives: rule.false_positives, riskScore: rule.risk_score, + timeline: { + id: rule.timeline_id ?? null, + title: rule.timeline_title ?? null, + }, } : null; const scheduleRuleData: ScheduleStepRule | null = @@ -61,3 +65,5 @@ export const getStepsData = ({ return { aboutRuleData, defineRuleData, scheduleRuleData }; }; + +export const useQuery = () => new URLSearchParams(useLocation().search); diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/index.tsx b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/index.tsx index 8b4cc2a213589..dd46b33ca7257 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/index.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/index.tsx @@ -5,9 +5,11 @@ */ import { EuiButton, EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; -import React, { useState } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; +import React, { useState } from 'react'; +import { Redirect } from 'react-router-dom'; +import { DETECTION_ENGINE_PAGE_NAME } from '../../../components/link_to/redirect_to_detection_engine'; import { FormattedRelativePreferenceDate } from '../../../components/formatted_date'; import { getEmptyTagValue } from '../../../components/empty_value'; import { HeaderPage } from '../../../components/header_page'; @@ -16,15 +18,34 @@ import { SpyRoute } from '../../../utils/route/spy_routes'; import { AllRules } from './all'; import { ImportRuleModal } from './components/import_rule_modal'; +import { ReadOnlyCallOut } from './components/read_only_callout'; +import { useUserInfo } from '../components/user_info'; import * as i18n from './translations'; export const RulesComponent = React.memo(() => { const [showImportModal, setShowImportModal] = useState(false); const [importCompleteToggle, setImportCompleteToggle] = useState(false); + const { + loading, + isSignalIndexExists, + isAuthenticated, + canUserCRUD, + hasManageApiKey, + } = useUserInfo(); + if ( + isSignalIndexExists != null && + isAuthenticated != null && + (!isSignalIndexExists || !isAuthenticated) + ) { + return ; + } + const userHasNoPermissions = + canUserCRUD != null && hasManageApiKey != null ? !canUserCRUD || !hasManageApiKey : false; const lastCompletedRun = undefined; return ( <> + {userHasNoPermissions && } setShowImportModal(false)} @@ -32,7 +53,10 @@ export const RulesComponent = React.memo(() => { /> { { setShowImportModal(true); }} @@ -59,16 +84,23 @@ export const RulesComponent = React.memo(() => { {i18n.IMPORT_RULE} - - + {i18n.ADD_NEW_RULE}
- - + diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/translations.ts index ecd6bef942bfb..d55e08e9ecd73 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/translations.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/translations.ts @@ -50,6 +50,15 @@ export const BATCH_ACTION_ACTIVATE_SELECTED = i18n.translate( } ); +export const BATCH_ACTION_ACTIVATE_SELECTED_ERROR = (totalRules: number) => + i18n.translate( + 'xpack.siem.detectionEngine.rules.allRules.batchActions.activateSelectedErrorTitle', + { + values: { totalRules }, + defaultMessage: 'Error activating {totalRules, plural, =1 {rule} other {rules}}…', + } + ); + export const BATCH_ACTION_DEACTIVATE_SELECTED = i18n.translate( 'xpack.siem.detectionEngine.rules.allRules.batchActions.deactivateSelectedTitle', { @@ -57,6 +66,15 @@ export const BATCH_ACTION_DEACTIVATE_SELECTED = i18n.translate( } ); +export const BATCH_ACTION_DEACTIVATE_SELECTED_ERROR = (totalRules: number) => + i18n.translate( + 'xpack.siem.detectionEngine.rules.allRules.batchActions.deactivateSelectedErrorTitle', + { + values: { totalRules }, + defaultMessage: 'Error deactivating {totalRules, plural, =1 {rule} other {rules}}…', + } + ); + export const BATCH_ACTION_EXPORT_SELECTED = i18n.translate( 'xpack.siem.detectionEngine.rules.allRules.batchActions.exportSelectedTitle', { @@ -78,6 +96,22 @@ export const BATCH_ACTION_DELETE_SELECTED = i18n.translate( } ); +export const BATCH_ACTION_DELETE_SELECTED_IMMUTABLE = i18n.translate( + 'xpack.siem.detectionEngine.rules.allRules.batchActions.deleteSelectedImmutableTitle', + { + defaultMessage: 'Selection contains immutable rules which cannot be deleted', + } +); + +export const BATCH_ACTION_DELETE_SELECTED_ERROR = (totalRules: number) => + i18n.translate( + 'xpack.siem.detectionEngine.rules.allRules.batchActions.deleteSelectedErrorTitle', + { + values: { totalRules }, + defaultMessage: 'Error deleting {totalRules, plural, =1 {rule} other {rules}}…', + } + ); + export const EXPORT_FILENAME = i18n.translate( 'xpack.siem.detectionEngine.rules.allRules.exportFilenameTitle', { @@ -143,6 +177,13 @@ export const DUPLICATE_RULE = i18n.translate( } ); +export const DUPLICATE_RULE_ERROR = i18n.translate( + 'xpack.siem.detectionEngine.rules.allRules.actions.duplicateRuleErrorDescription', + { + defaultMessage: 'Error duplicating rule…', + } +); + export const EXPORT_RULE = i18n.translate( 'xpack.siem.detectionEngine.rules.allRules.actions.exportRuleDescription', { @@ -207,15 +248,15 @@ export const COLUMN_ACTIVATE = i18n.translate( ); export const DEFINE_RULE = i18n.translate('xpack.siem.detectionEngine.rules.defineRuleTitle', { - defaultMessage: 'Define Rule', + defaultMessage: 'Define rule', }); export const ABOUT_RULE = i18n.translate('xpack.siem.detectionEngine.rules.aboutRuleTitle', { - defaultMessage: 'About Rule', + defaultMessage: 'About rule', }); export const SCHEDULE_RULE = i18n.translate('xpack.siem.detectionEngine.rules.scheduleRuleTitle', { - defaultMessage: 'Schedule Rule', + defaultMessage: 'Schedule rule', }); export const DEFINITION = i18n.translate('xpack.siem.detectionEngine.rules.stepDefinitionTitle', { diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/types.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/types.ts index 9b535034810bd..3da294fc9b845 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/types.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/rules/types.ts @@ -8,6 +8,7 @@ import { esFilters } from '../../../../../../../../src/plugins/data/common'; import { Rule } from '../../../containers/detection_engine/rules'; import { FieldValueQueryBar } from './components/query_bar'; import { FormData, FormHook } from './components/shared_imports'; +import { FieldValueTimeline } from './components/pick_timeline'; export interface EuiBasicTableSortTypes { field: string; @@ -24,6 +25,7 @@ export interface EuiBasicTableOnChange { export interface TableData { id: string; + immutable: boolean; rule_id: string; rule: { href: string; @@ -56,6 +58,7 @@ export interface RuleStepData { } export interface RuleStepProps { + addPadding?: boolean; descriptionDirection?: 'row' | 'column'; setStepData?: (step: RuleStep, data: unknown, isValid: boolean) => void; isReadOnlyView: boolean; @@ -76,11 +79,11 @@ export interface AboutStepRule extends StepRuleData { references: string[]; falsePositives: string[]; tags: string[]; + timeline: FieldValueTimeline; threats: IMitreEnterpriseAttack[]; } export interface DefineStepRule extends StepRuleData { - useIndicesConfig: string; index: string[]; queryBar: FieldValueQueryBar; } @@ -108,6 +111,8 @@ export interface AboutStepRuleJson { references: string[]; false_positives: string[]; tags: string[]; + timeline_id?: string; + timeline_title?: string; threats: IMitreEnterpriseAttack[]; } diff --git a/x-pack/legacy/plugins/siem/public/pages/detection_engine/translations.ts b/x-pack/legacy/plugins/siem/public/pages/detection_engine/translations.ts index c7a71bf48d7dd..e5f830d3a49b0 100644 --- a/x-pack/legacy/plugins/siem/public/pages/detection_engine/translations.ts +++ b/x-pack/legacy/plugins/siem/public/pages/detection_engine/translations.ts @@ -51,3 +51,34 @@ export const EMPTY_ACTION_SECONDARY = i18n.translate( defaultMessage: 'Go to documentation', } ); + +export const NO_INDEX_TITLE = i18n.translate('xpack.siem.detectionEngine.noIndexTitle', { + defaultMessage: 'Let’s set up your detection engine', +}); + +export const NO_INDEX_MSG_BODY = i18n.translate('xpack.siem.detectionEngine.noIndexMsgBody', { + defaultMessage: + 'To use the detection engine, a user with the required cluster and index privileges must first access this page. For more help, contact your administrator.', +}); + +export const GO_TO_DOCUMENTATION = i18n.translate( + 'xpack.siem.detectionEngine.goToDocumentationButton', + { + defaultMessage: 'View documentation', + } +); + +export const USER_UNAUTHENTICATED_TITLE = i18n.translate( + 'xpack.siem.detectionEngine.userUnauthenticatedTitle', + { + defaultMessage: 'Detection engine permissions required', + } +); + +export const USER_UNAUTHENTICATED_MSG_BODY = i18n.translate( + 'xpack.siem.detectionEngine.userUnauthenticatedMsgBody', + { + defaultMessage: + 'You do not have the required permissions for viewing the detection engine. For more help, contact your administrator.', + } +); diff --git a/x-pack/legacy/plugins/siem/public/pages/hosts/details/details_tabs.test.tsx b/x-pack/legacy/plugins/siem/public/pages/hosts/details/details_tabs.test.tsx index 092c2463419d1..aafeea6465fb3 100644 --- a/x-pack/legacy/plugins/siem/public/pages/hosts/details/details_tabs.test.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/hosts/details/details_tabs.test.tsx @@ -16,8 +16,6 @@ import { hostDetailsPagePath } from '../types'; import { type } from './utils'; import { useMountAppended } from '../../../utils/use_mount_appended'; -jest.mock('../../../lib/kibana'); - jest.mock('../../../containers/source', () => ({ indicesExistOrDataTemporarilyUnavailable: () => true, WithSource: ({ diff --git a/x-pack/legacy/plugins/siem/public/pages/hosts/hosts.test.tsx b/x-pack/legacy/plugins/siem/public/pages/hosts/hosts.test.tsx index 00dcb5908a98b..065d91b3fc2fa 100644 --- a/x-pack/legacy/plugins/siem/public/pages/hosts/hosts.test.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/hosts/hosts.test.tsx @@ -6,21 +6,23 @@ import { mount } from 'enzyme'; import { cloneDeep } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { Router } from 'react-router-dom'; import { MockedProvider } from 'react-apollo/test-utils'; import { ActionCreator } from 'typescript-fsa'; +import { esFilters } from '../../../../../../../src/plugins/data/common/es_query'; import '../../mock/match_media'; import { mocksSource } from '../../containers/source/mock'; import { wait } from '../../lib/helpers'; -import { TestProviders } from '../../mock'; +import { apolloClientObservable, TestProviders, mockGlobalState } from '../../mock'; import { InputsModelId } from '../../store/inputs/constants'; import { SiemNavigation } from '../../components/navigation'; +import { inputsActions } from '../../store/inputs'; +import { State, createStore } from '../../store'; import { HostsComponentProps } from './types'; import { Hosts } from './hosts'; - -jest.mock('../../lib/kibana'); +import { HostsTabs } from './hosts_tabs'; // Test will fail because we will to need to mock some core services to make the test work // For now let's forget about SiemSearchBar and QueryBar @@ -138,4 +140,58 @@ describe('Hosts - rendering', () => { wrapper.update(); expect(wrapper.find(SiemNavigation).exists()).toBe(true); }); + + test('it should add the new filters after init', async () => { + const newFilters: esFilters.Filter[] = [ + { + query: { + bool: { + filter: [ + { + bool: { + should: [ + { + match_phrase: { + 'host.name': 'ItRocks', + }, + }, + ], + minimum_should_match: 1, + }, + }, + ], + }, + }, + meta: { + alias: '', + disabled: false, + key: 'bool', + negate: false, + type: 'custom', + value: + '{"query": {"bool": {"filter": [{"bool": {"should": [{"match_phrase": {"host.name": "ItRocks"}}],"minimum_should_match": 1}}]}}}', + }, + }, + ]; + localSource[0].result.data.source.status.indicesExist = true; + const myState: State = mockGlobalState; + const myStore = createStore(myState, apolloClientObservable); + const wrapper = mount( + + + + + + + + ); + await wait(); + wrapper.update(); + + myStore.dispatch(inputsActions.setSearchBarFilter({ id: 'global', filters: newFilters })); + wrapper.update(); + expect(wrapper.find(HostsTabs).props().filterQuery).toEqual( + '{"bool":{"must":[],"filter":[{"match_all":{}},{"bool":{"filter":[{"bool":{"should":[{"match_phrase":{"host.name":"ItRocks"}}],"minimum_should_match":1}}]}}],"should":[],"must_not":[]}}' + ); + }); }); diff --git a/x-pack/legacy/plugins/siem/public/pages/hosts/hosts.tsx b/x-pack/legacy/plugins/siem/public/pages/hosts/hosts.tsx index 6b69f06b97b83..2c475e4ba6ac5 100644 --- a/x-pack/legacy/plugins/siem/public/pages/hosts/hosts.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/hosts/hosts.tsx @@ -41,7 +41,7 @@ import { HostsTableType } from '../../store/hosts/model'; const KpiHostsComponentManage = manageQuery(KpiHostsComponent); -const HostsComponent = React.memo( +export const HostsComponent = React.memo( ({ deleteQuery, isInitializing, @@ -56,13 +56,12 @@ const HostsComponent = React.memo( const capabilities = React.useContext(MlCapabilitiesContext); const kibana = useKibana(); const { tabName } = useParams(); - const hostsFilters = React.useMemo(() => { if (tabName === HostsTableType.alerts) { return filters.length > 0 ? [...filters, ...filterAlertsHosts] : filterAlertsHosts; } return filters; - }, [tabName]); + }, [tabName, filters]); const narrowDateRange = useCallback( (min: number, max: number) => { setAbsoluteRangeDatePicker({ id: 'global', from: min, to: max }); diff --git a/x-pack/legacy/plugins/siem/public/pages/network/ip_details/index.test.tsx b/x-pack/legacy/plugins/siem/public/pages/network/ip_details/index.test.tsx index d624631c1feae..9a9d1cf085eb9 100644 --- a/x-pack/legacy/plugins/siem/public/pages/network/ip_details/index.test.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/network/ip_details/index.test.tsx @@ -5,9 +5,8 @@ */ import { shallow } from 'enzyme'; -import toJson from 'enzyme-to-json'; import { cloneDeep } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { Router } from 'react-router-dom'; import { MockedProvider } from 'react-apollo/test-utils'; import { ActionCreator } from 'typescript-fsa'; @@ -28,8 +27,6 @@ const pop: Action = 'POP'; type GlobalWithFetch = NodeJS.Global & { fetch: jest.Mock }; -jest.mock('../../../lib/kibana'); - // Test will fail because we will to need to mock some core services to make the test work // For now let's forget about SiemSearchBar and QueryBar jest.mock('../../../components/search_bar', () => ({ @@ -131,7 +128,7 @@ describe('Ip Details', () => { test('it matches the snapshot', () => { const wrapper = shallow(); - expect(toJson(wrapper)).toMatchSnapshot(); + expect(wrapper).toMatchSnapshot(); }); test('it renders ipv6 headline', async () => { diff --git a/x-pack/legacy/plugins/siem/public/pages/network/network.test.tsx b/x-pack/legacy/plugins/siem/public/pages/network/network.test.tsx index 335bb62c5c852..3a22e800d893f 100644 --- a/x-pack/legacy/plugins/siem/public/pages/network/network.test.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/network/network.test.tsx @@ -6,17 +6,18 @@ import { mount } from 'enzyme'; import { cloneDeep } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { Router } from 'react-router-dom'; import { MockedProvider } from 'react-apollo/test-utils'; import '../../mock/match_media'; - +import { esFilters } from '../../../../../../../src/plugins/data/common/es_query'; import { mocksSource } from '../../containers/source/mock'; -import { TestProviders } from '../../mock'; +import { TestProviders, mockGlobalState, apolloClientObservable } from '../../mock'; +import { State, createStore } from '../../store'; +import { inputsActions } from '../../store/inputs'; import { Network } from './network'; - -jest.mock('../../lib/kibana'); +import { NetworkRoutes } from './navigation'; // Test will fail because we will to need to mock some core services to make the test work // For now let's forget about SiemSearchBar and QueryBar @@ -113,4 +114,58 @@ describe('rendering - rendering', () => { wrapper.update(); expect(wrapper.find('[data-test-subj="empty-page"]').exists()).toBe(false); }); + + test('it should add the new filters after init', async () => { + const newFilters: esFilters.Filter[] = [ + { + query: { + bool: { + filter: [ + { + bool: { + should: [ + { + match_phrase: { + 'host.name': 'ItRocks', + }, + }, + ], + minimum_should_match: 1, + }, + }, + ], + }, + }, + meta: { + alias: '', + disabled: false, + key: 'bool', + negate: false, + type: 'custom', + value: + '{"query": {"bool": {"filter": [{"bool": {"should": [{"match_phrase": {"host.name": "ItRocks"}}],"minimum_should_match": 1}}]}}}', + }, + }, + ]; + localSource[0].result.data.source.status.indicesExist = true; + const myState: State = mockGlobalState; + const myStore = createStore(myState, apolloClientObservable); + const wrapper = mount( + + + + + + + + ); + await new Promise(resolve => setTimeout(resolve)); + wrapper.update(); + + myStore.dispatch(inputsActions.setSearchBarFilter({ id: 'global', filters: newFilters })); + wrapper.update(); + expect(wrapper.find(NetworkRoutes).props().filterQuery).toEqual( + '{"bool":{"must":[],"filter":[{"match_all":{}},{"bool":{"filter":[{"bool":{"should":[{"match_phrase":{"host.name":"ItRocks"}}],"minimum_should_match":1}}]}}],"should":[],"must_not":[]}}' + ); + }); }); diff --git a/x-pack/legacy/plugins/siem/public/pages/network/network.tsx b/x-pack/legacy/plugins/siem/public/pages/network/network.tsx index c39935742a2e0..ad3513a6f529e 100644 --- a/x-pack/legacy/plugins/siem/public/pages/network/network.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/network/network.tsx @@ -59,7 +59,8 @@ const NetworkComponent = React.memo( return filters.length > 0 ? [...filters, ...filterAlertsNetwork] : filterAlertsNetwork; } return filters; - }, [tabName]); + }, [tabName, filters]); + const narrowDateRange = useCallback( (min: number, max: number) => { setAbsoluteRangeDatePicker({ id: 'global', from: min, to: max }); diff --git a/x-pack/legacy/plugins/siem/public/pages/overview/overview.test.tsx b/x-pack/legacy/plugins/siem/public/pages/overview/overview.test.tsx index 300df4a742adf..eff61bf6a9710 100644 --- a/x-pack/legacy/plugins/siem/public/pages/overview/overview.test.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/overview/overview.test.tsx @@ -6,7 +6,7 @@ import { mount } from 'enzyme'; import { cloneDeep } from 'lodash/fp'; -import * as React from 'react'; +import React from 'react'; import { MockedProvider } from 'react-apollo/test-utils'; import { MemoryRouter } from 'react-router-dom'; @@ -14,8 +14,6 @@ import { TestProviders } from '../../mock'; import { mocksSource } from '../../containers/source/mock'; import { Overview } from './index'; -jest.mock('../../lib/kibana'); - let localSource: Array<{ request: {}; result: { diff --git a/x-pack/legacy/plugins/siem/public/pages/timelines/timelines_page.tsx b/x-pack/legacy/plugins/siem/public/pages/timelines/timelines_page.tsx index 4dec9278ed6b0..86f702a8ad8a4 100644 --- a/x-pack/legacy/plugins/siem/public/pages/timelines/timelines_page.tsx +++ b/x-pack/legacy/plugins/siem/public/pages/timelines/timelines_page.tsx @@ -17,7 +17,6 @@ import * as i18n from './translations'; const TimelinesContainer = styled.div` width: 100%; `; -TimelinesContainer.displayName = 'TimelinesContainer'; interface TimelinesProps { apolloClient: ApolloClient; @@ -27,7 +26,7 @@ type OwnProps = TimelinesProps; export const DEFAULT_SEARCH_RESULTS_PER_PAGE = 10; -export const TimelinesPage = React.memo(({ apolloClient }) => ( +const TimelinesPageComponent: React.FC = ({ apolloClient }) => ( <> @@ -44,4 +43,6 @@ export const TimelinesPage = React.memo(({ apolloClient }) => ( -)); +); + +export const TimelinesPage = React.memo(TimelinesPageComponent); diff --git a/x-pack/legacy/plugins/siem/public/plugin.tsx b/x-pack/legacy/plugins/siem/public/plugin.tsx new file mode 100644 index 0000000000000..44624f497a91b --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/plugin.tsx @@ -0,0 +1,67 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + AppMountParameters, + CoreSetup, + CoreStart, + PluginInitializerContext, + Plugin as IPlugin, +} from '../../../../../src/core/public'; +import { HomePublicPluginSetup } from '../../../../../src/plugins/home/public'; +import { DataPublicPluginStart } from '../../../../../src/plugins/data/public'; +import { IEmbeddableStart } from '../../../../../src/plugins/embeddable/public'; +import { Start as InspectorStart } from '../../../../../src/plugins/inspector/public'; +import { IUiActionsStart } from '../../../../../src/plugins/ui_actions/public'; + +export { AppMountParameters, CoreSetup, CoreStart, PluginInitializerContext }; + +export interface SetupPlugins { + home: HomePublicPluginSetup; +} +export interface StartPlugins { + data: DataPublicPluginStart; + embeddable: IEmbeddableStart; + inspector: InspectorStart; + uiActions: IUiActionsStart; +} +export type StartServices = CoreStart & StartPlugins; + +export type Setup = ReturnType; +export type Start = ReturnType; + +export class Plugin implements IPlugin { + public id = 'siem'; + public name = 'SIEM'; + constructor( + // @ts-ignore this is added to satisfy the New Platform typing constraint, + // but we're not leveraging any of its functionality yet. + private readonly initializerContext: PluginInitializerContext + ) {} + + public setup(core: CoreSetup, plugins: SetupPlugins) { + core.application.register({ + id: this.id, + title: this.name, + async mount(context, params) { + const [coreStart, pluginsStart] = await core.getStartServices(); + const { renderApp } = await import('./app'); + + return renderApp(coreStart, pluginsStart as StartPlugins, params); + }, + }); + + return {}; + } + + public start(core: CoreStart, plugins: StartPlugins) { + return {}; + } + + public stop() { + return {}; + } +} diff --git a/x-pack/legacy/plugins/siem/public/register_feature.ts b/x-pack/legacy/plugins/siem/public/register_feature.ts index 36c4e28dbd874..ca7a22408b6ff 100644 --- a/x-pack/legacy/plugins/siem/public/register_feature.ts +++ b/x-pack/legacy/plugins/siem/public/register_feature.ts @@ -4,19 +4,17 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - FeatureCatalogueCategory, - FeatureCatalogueRegistryProvider, -} from 'ui/registry/feature_catalogue'; +import { npSetup } from 'ui/new_platform'; +import { FeatureCatalogueCategory } from '../../../../../src/plugins/home/public'; +import { APP_ID } from '../common/constants'; -const APP_ID = 'siem'; - -FeatureCatalogueRegistryProvider.register(() => ({ - id: 'siem', +// TODO(rylnd): move this into Plugin.setup once we're on NP +npSetup.plugins.home.featureCatalogue.register({ + id: APP_ID, title: 'SIEM', description: 'Explore security metrics and logs for events and alerts', icon: 'securityAnalyticsApp', path: `/app/${APP_ID}`, showOnHomePage: true, category: FeatureCatalogueCategory.DATA, -})); +}); diff --git a/x-pack/legacy/plugins/siem/public/routes.tsx b/x-pack/legacy/plugins/siem/public/routes.tsx index 0e9bcf5dc5bfa..cbb58a473e8ea 100644 --- a/x-pack/legacy/plugins/siem/public/routes.tsx +++ b/x-pack/legacy/plugins/siem/public/routes.tsx @@ -16,7 +16,7 @@ interface RouterProps { history: History; } -export const PageRouter: FC = memo(({ history }) => ( +const PageRouterComponent: FC = ({ history }) => ( @@ -25,4 +25,6 @@ export const PageRouter: FC = memo(({ history }) => ( -)); +); + +export const PageRouter = memo(PageRouterComponent); diff --git a/x-pack/legacy/plugins/siem/public/utils/api/index.ts b/x-pack/legacy/plugins/siem/public/utils/api/index.ts new file mode 100644 index 0000000000000..1dc14413b04d2 --- /dev/null +++ b/x-pack/legacy/plugins/siem/public/utils/api/index.ts @@ -0,0 +1,20 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export interface MessageBody { + error?: string; + message?: string; + statusCode?: number; +} + +export const parseJsonFromBody = async (response: Response): Promise => { + try { + const text = await response.text(); + return JSON.parse(text); + } catch (error) { + return null; + } +}; diff --git a/x-pack/legacy/plugins/siem/public/utils/route/manage_spy_routes.tsx b/x-pack/legacy/plugins/siem/public/utils/route/manage_spy_routes.tsx index 87b40c565c758..d1e76f75fca81 100644 --- a/x-pack/legacy/plugins/siem/public/utils/route/manage_spy_routes.tsx +++ b/x-pack/legacy/plugins/siem/public/utils/route/manage_spy_routes.tsx @@ -4,12 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { memo, useReducer } from 'react'; +import React, { FC, memo, useReducer } from 'react'; import { ManageRoutesSpyProps, RouteSpyState, RouteSpyAction } from './types'; import { RouterSpyStateContext, initRouteSpy } from './helpers'; -export const ManageRoutesSpy = memo(({ children }: ManageRoutesSpyProps) => { +const ManageRoutesSpyComponent: FC = ({ children }) => { const reducerSpyRoute = (state: RouteSpyState, action: RouteSpyAction) => { switch (action.type) { case 'updateRoute': @@ -28,4 +28,6 @@ export const ManageRoutesSpy = memo(({ children }: ManageRoutesSpyProps) => { {children} ); -}); +}; + +export const ManageRoutesSpy = memo(ManageRoutesSpyComponent); diff --git a/x-pack/legacy/plugins/siem/scripts/convert_saved_search_to_rules.js b/x-pack/legacy/plugins/siem/scripts/convert_saved_search_to_rules.js index 9e24e93b0c391..0da44eec3aaa3 100644 --- a/x-pack/legacy/plugins/siem/scripts/convert_saved_search_to_rules.js +++ b/x-pack/legacy/plugins/siem/scripts/convert_saved_search_to_rules.js @@ -61,7 +61,7 @@ const allRulesNdJson = 'index.ts'; const walk = dir => { const list = fs.readdirSync(dir); return list.reduce((accum, file) => { - const fileWithDir = dir + '/' + file; + const fileWithDir = `${dir}/${file}`; const stat = fs.statSync(fileWithDir); if (stat && stat.isDirectory()) { return [...accum, ...walk(fileWithDir)]; diff --git a/x-pack/legacy/plugins/siem/server/kibana.index.ts b/x-pack/legacy/plugins/siem/server/kibana.index.ts index 59df643246835..7060a3f662914 100644 --- a/x-pack/legacy/plugins/siem/server/kibana.index.ts +++ b/x-pack/legacy/plugins/siem/server/kibana.index.ts @@ -25,6 +25,8 @@ import { addPrepackedRulesRoute } from './lib/detection_engine/routes/rules/add_ import { createRulesBulkRoute } from './lib/detection_engine/routes/rules/create_rules_bulk_route'; import { updateRulesBulkRoute } from './lib/detection_engine/routes/rules/update_rules_bulk_route'; import { deleteRulesBulkRoute } from './lib/detection_engine/routes/rules/delete_rules_bulk_route'; +import { importRulesRoute } from './lib/detection_engine/routes/rules/import_rules_route'; +import { exportRulesRoute } from './lib/detection_engine/routes/rules/export_rules_route'; const APP_ID = 'siem'; @@ -50,6 +52,8 @@ export const initServerWithKibana = (context: PluginInitializerContext, __legacy createRulesBulkRoute(__legacy); updateRulesBulkRoute(__legacy); deleteRulesBulkRoute(__legacy); + importRulesRoute(__legacy); + exportRulesRoute(__legacy); // Detection Engine Signals routes that have the REST endpoints of /api/detection_engine/signals // POST /api/detection_engine/signals/status diff --git a/x-pack/legacy/plugins/siem/server/lib/case/saved_object_mappings_temp.ts b/x-pack/legacy/plugins/siem/server/lib/case/saved_object_mappings_temp.ts new file mode 100644 index 0000000000000..bd73805600a33 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/case/saved_object_mappings_temp.ts @@ -0,0 +1,91 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +/* eslint-disable @typescript-eslint/no-empty-interface */ +/* eslint-disable @typescript-eslint/camelcase */ +import { + NewCaseFormatted, + NewCommentFormatted, +} from '../../../../../../../x-pack/plugins/case/server'; +import { ElasticsearchMappingOf } from '../../utils/typed_elasticsearch_mappings'; + +// Temporary file to write mappings for case +// while Saved Object Mappings API is programmed for the NP +// See: https://github.com/elastic/kibana/issues/50309 + +export const caseSavedObjectType = 'case-workflow'; +export const caseCommentSavedObjectType = 'case-workflow-comment'; + +export const caseSavedObjectMappings: { + [caseSavedObjectType]: ElasticsearchMappingOf; +} = { + [caseSavedObjectType]: { + properties: { + assignees: { + properties: { + username: { + type: 'keyword', + }, + full_name: { + type: 'keyword', + }, + }, + }, + created_at: { + type: 'date', + }, + description: { + type: 'text', + }, + title: { + type: 'keyword', + }, + created_by: { + properties: { + username: { + type: 'keyword', + }, + full_name: { + type: 'keyword', + }, + }, + }, + state: { + type: 'keyword', + }, + tags: { + type: 'keyword', + }, + case_type: { + type: 'keyword', + }, + }, + }, +}; + +export const caseCommentSavedObjectMappings: { + [caseCommentSavedObjectType]: ElasticsearchMappingOf; +} = { + [caseCommentSavedObjectType]: { + properties: { + comment: { + type: 'text', + }, + created_at: { + type: 'date', + }, + created_by: { + properties: { + full_name: { + type: 'keyword', + }, + username: { + type: 'keyword', + }, + }, + }, + }, + }, +}; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/get_index_exists.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/get_index_exists.test.ts new file mode 100644 index 0000000000000..cb358c15e5fad --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/get_index_exists.test.ts @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { getIndexExists } from './get_index_exists'; + +class StatusCode extends Error { + status: number = -1; + constructor(status: number, message: string) { + super(message); + this.status = status; + } +} + +describe('get_index_exists', () => { + test('it should return a true if you have _shards', async () => { + const callWithRequest = jest.fn().mockResolvedValue({ _shards: { total: 1 } }); + const indexExists = await getIndexExists(callWithRequest, 'some-index'); + expect(indexExists).toEqual(true); + }); + + test('it should return a false if you do NOT have _shards', async () => { + const callWithRequest = jest.fn().mockResolvedValue({ _shards: { total: 0 } }); + const indexExists = await getIndexExists(callWithRequest, 'some-index'); + expect(indexExists).toEqual(false); + }); + + test('it should return a false if it encounters a 404', async () => { + const callWithRequest = jest.fn().mockImplementation(() => { + throw new StatusCode(404, 'I am a 404 error'); + }); + const indexExists = await getIndexExists(callWithRequest, 'some-index'); + expect(indexExists).toEqual(false); + }); + + test('it should reject if it encounters a non 404', async () => { + const callWithRequest = jest.fn().mockImplementation(() => { + throw new StatusCode(500, 'I am a 500 error'); + }); + await expect(getIndexExists(callWithRequest, 'some-index')).rejects.toThrow('I am a 500 error'); + }); +}); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/get_index_exists.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/get_index_exists.ts index ff65caa59a866..705f542b50124 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/get_index_exists.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/index/get_index_exists.ts @@ -4,15 +4,29 @@ * you may not use this file except in compliance with the Elastic License. */ -import { IndicesExistsParams } from 'elasticsearch'; -import { CallClusterOptions } from 'src/legacy/core_plugins/elasticsearch'; import { CallWithRequest } from '../types'; export const getIndexExists = async ( - callWithRequest: CallWithRequest, + callWithRequest: CallWithRequest< + { index: string; size: number; terminate_after: number; allow_no_indices: boolean }, + {}, + { _shards: { total: number } } + >, index: string ): Promise => { - return callWithRequest('indices.exists', { - index, - }); + try { + const response = await callWithRequest('search', { + index, + size: 0, + terminate_after: 1, + allow_no_indices: true, + }); + return response._shards.total > 0; + } catch (err) { + if (err.status === 404) { + return false; + } else { + throw err; + } + } }; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts index a78879924acd0..8ed5333bd2a25 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/__mocks__/request_responses.ts @@ -52,6 +52,7 @@ export const fullRuleAlertParamsRest = (): RuleAlertParamsRest => ({ created_at: '2019-12-13T16:40:33.400Z', updated_at: '2019-12-13T16:40:33.400Z', timeline_id: 'timeline-id', + timeline_title: 'timeline-title', }); export const typicalPayload = (): Partial => ({ @@ -158,7 +159,7 @@ export const getPrivilegeRequest = (): ServerInjectOptions => ({ url: `${DETECTION_ENGINE_PRIVILEGES_URL}`, }); -interface FindHit { +export interface FindHit { page: number; perPage: number; total: number; @@ -175,7 +176,7 @@ export const getFindResult = (): FindHit => ({ export const getFindResultWithSingleHit = (): FindHit => ({ page: 1, perPage: 1, - total: 0, + total: 1, data: [getResult()], }); @@ -271,6 +272,7 @@ export const getResult = (): RuleAlertType => ({ outputIndex: '.siem-signals', savedId: 'some-id', timelineId: 'some-timeline-id', + timelineTitle: 'some-timeline-title', meta: { someMeta: 'someField' }, filters: [ { @@ -283,9 +285,7 @@ export const getResult = (): RuleAlertType => ({ ], riskScore: 50, maxSignals: 100, - size: 1, severity: 'high', - tags: [], to: 'now', type: 'query', threats: [ @@ -308,6 +308,8 @@ export const getResult = (): RuleAlertType => ({ references: ['http://www.example.com', 'https://ww.example.com'], version: 1, }, + createdAt: new Date('2019-12-13T16:40:33.400Z'), + updatedAt: new Date('2019-12-13T16:40:33.400Z'), schedule: { interval: '5m' }, enabled: true, actions: [], @@ -379,4 +381,5 @@ export const getMockPrivileges = () => ({ }, }, application: {}, + isAuthenticated: false, }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/signals_mapping.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/signals_mapping.json index afe9bac9d87fe..79fb136afd52a 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/signals_mapping.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/index/signals_mapping.json @@ -36,6 +36,9 @@ "timeline_id": { "type": "keyword" }, + "timeline_title": { + "type": "keyword" + }, "max_signals": { "type": "keyword" }, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/privileges/read_privileges_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/privileges/read_privileges_route.ts index 457de05674f66..240200af8b585 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/privileges/read_privileges_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/privileges/read_privileges_route.ts @@ -5,6 +5,7 @@ */ import Hapi from 'hapi'; +import { merge } from 'lodash/fp'; import { DETECTION_ENGINE_PRIVILEGES_URL } from '../../../../../common/constants'; import { RulesRequest } from '../../rules/types'; import { ServerFacade } from '../../../../types'; @@ -28,7 +29,9 @@ export const createReadPrivilegesRulesRoute = (server: ServerFacade): Hapi.Serve const callWithRequest = callWithRequestFactory(request, server); const index = getIndex(request, server); const permissions = await readPrivileges(callWithRequest, index); - return permissions; + return merge(permissions, { + isAuthenticated: request?.auth?.isAuthenticated ?? false, + }); } catch (err) { return transformError(err); } diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts index 256b341fca656..3d9719a7b248b 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_bulk_route.ts @@ -74,6 +74,7 @@ export const createCreateRulesBulkRoute = (server: ServerFacade): Hapi.ServerRou updated_at: updatedAt, references, timeline_id: timelineId, + timeline_title: timelineTitle, version, } = payloadRule; const ruleIdOrUuid = ruleId ?? uuid.v4(); @@ -112,6 +113,7 @@ export const createCreateRulesBulkRoute = (server: ServerFacade): Hapi.ServerRou outputIndex: finalIndex, savedId, timelineId, + timelineTitle, meta, filters, ruleId: ruleIdOrUuid, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_route.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_route.test.ts index 094449a5f61ac..10dc14f7ed610 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_route.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_route.test.ts @@ -28,7 +28,9 @@ describe('create_rules', () => { jest.resetAllMocks(); ({ server, alertsClient, actionsClient, elasticsearch } = createMockServer()); elasticsearch.getCluster = jest.fn().mockImplementation(() => ({ - callWithRequest: jest.fn().mockImplementation(() => true), + callWithRequest: jest + .fn() + .mockImplementation((endpoint, params) => ({ _shards: { total: 1 } })), })); createRulesRoute(server); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_route.ts index 476d5b8a49ba2..cf8fb2a28288f 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/create_rules_route.ts @@ -44,6 +44,7 @@ export const createCreateRulesRoute = (server: ServerFacade): Hapi.ServerRoute = output_index: outputIndex, saved_id: savedId, timeline_id: timelineId, + timeline_title: timelineTitle, meta, filters, rule_id: ruleId, @@ -101,6 +102,7 @@ export const createCreateRulesRoute = (server: ServerFacade): Hapi.ServerRoute = outputIndex: finalIndex, savedId, timelineId, + timelineTitle, meta, filters, ruleId: ruleId != null ? ruleId : uuid.v4(), diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/delete_rules_bulk_route.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/delete_rules_bulk_route.test.ts index 11a076951fd8c..bab05b065f6f7 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/delete_rules_bulk_route.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/delete_rules_bulk_route.test.ts @@ -24,6 +24,7 @@ import { import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; import { deleteRulesBulkRoute } from './delete_rules_bulk_route'; +import { BulkError } from '../utils'; describe('delete_rules', () => { let { server, alertsClient } = createMockServer(); @@ -83,10 +84,14 @@ describe('delete_rules', () => { alertsClient.get.mockResolvedValue(getResult()); alertsClient.delete.mockResolvedValue({}); const { payload } = await server.inject(getDeleteBulkRequest()); - const parsed = JSON.parse(payload); - expect(parsed).toEqual([ - { error: { message: 'rule_id: "rule-1" not found', statusCode: 404 }, id: 'rule-1' }, - ]); + const parsed: BulkError[] = JSON.parse(payload); + const expected: BulkError[] = [ + { + error: { message: 'rule_id: "rule-1" not found', status_code: 404 }, + rule_id: 'rule-1', + }, + ]; + expect(parsed).toEqual(expected); }); test('returns 404 if actionClient is not available on the route', async () => { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/export_rules_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/export_rules_route.ts new file mode 100644 index 0000000000000..aa17946849027 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/export_rules_route.ts @@ -0,0 +1,70 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import Boom from 'boom'; +import Hapi from 'hapi'; +import { isFunction } from 'lodash/fp'; +import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; +import { ExportRulesRequest } from '../../rules/types'; +import { ServerFacade } from '../../../../types'; +import { getNonPackagedRulesCount } from '../../rules/get_existing_prepackaged_rules'; +import { exportRulesSchema, exportRulesQuerySchema } from '../schemas/export_rules_schema'; +import { getExportByObjectIds } from '../../rules/get_export_by_object_ids'; +import { getExportAll } from '../../rules/get_export_all'; + +export const createExportRulesRoute = (server: ServerFacade): Hapi.ServerRoute => { + return { + method: 'POST', + path: `${DETECTION_ENGINE_RULES_URL}/_export`, + options: { + tags: ['access:siem'], + validate: { + options: { + abortEarly: false, + }, + payload: exportRulesSchema, + query: exportRulesQuerySchema, + }, + }, + async handler(request: ExportRulesRequest, headers) { + const alertsClient = isFunction(request.getAlertsClient) ? request.getAlertsClient() : null; + const actionsClient = isFunction(request.getActionsClient) + ? request.getActionsClient() + : null; + + if (!alertsClient || !actionsClient) { + return headers.response().code(404); + } + + const exportSizeLimit = server.config().get('savedObjects.maxImportExportSize'); + if (request.payload?.objects != null && request.payload.objects.length > exportSizeLimit) { + return Boom.badRequest(`Can't export more than ${exportSizeLimit} rules`); + } else { + const nonPackagedRulesCount = await getNonPackagedRulesCount({ alertsClient }); + if (nonPackagedRulesCount > exportSizeLimit) { + return Boom.badRequest(`Can't export more than ${exportSizeLimit} rules`); + } + } + + const exported = + request.payload?.objects != null + ? await getExportByObjectIds(alertsClient, request.payload.objects) + : await getExportAll(alertsClient); + + const response = request.query.exclude_export_details + ? headers.response(exported.rulesNdjson) + : headers.response(`${exported.rulesNdjson}${exported.exportDetails}`); + + return response + .header('Content-Disposition', `attachment; filename="${request.query.file_name}"`) + .header('Content-Type', 'application/ndjson'); + }, + }; +}; + +export const exportRulesRoute = (server: ServerFacade): void => { + server.route(createExportRulesRoute(server)); +}; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/import_rules_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/import_rules_route.ts new file mode 100644 index 0000000000000..e312b5fc6bb10 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/import_rules_route.ts @@ -0,0 +1,213 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import Boom from 'boom'; +import Hapi from 'hapi'; +import { extname } from 'path'; +import { isFunction } from 'lodash/fp'; +import { createPromiseFromStreams } from '../../../../../../../../../src/legacy/utils/streams'; +import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; +import { createRules } from '../../rules/create_rules'; +import { ImportRulesRequest } from '../../rules/types'; +import { ServerFacade } from '../../../../types'; +import { readRules } from '../../rules/read_rules'; +import { getIndexExists } from '../../index/get_index_exists'; +import { + callWithRequestFactory, + getIndex, + createImportErrorObject, + transformImportError, + ImportSuccessError, +} from '../utils'; +import { createRulesStreamFromNdJson } from '../../rules/create_rules_stream_from_ndjson'; +import { ImportRuleAlertRest } from '../../types'; +import { transformOrImportError } from './utils'; +import { updateRules } from '../../rules/update_rules'; +import { importRulesQuerySchema, importRulesPayloadSchema } from '../schemas/import_rules_schema'; + +export const createImportRulesRoute = (server: ServerFacade): Hapi.ServerRoute => { + return { + method: 'POST', + path: `${DETECTION_ENGINE_RULES_URL}/_import`, + options: { + tags: ['access:siem'], + payload: { + maxBytes: server.config().get('savedObjects.maxImportPayloadBytes'), + output: 'stream', + allow: 'multipart/form-data', + }, + validate: { + options: { + abortEarly: false, + }, + query: importRulesQuerySchema, + payload: importRulesPayloadSchema, + }, + }, + async handler(request: ImportRulesRequest, headers) { + const alertsClient = isFunction(request.getAlertsClient) ? request.getAlertsClient() : null; + const actionsClient = isFunction(request.getActionsClient) + ? request.getActionsClient() + : null; + + if (!alertsClient || !actionsClient) { + return headers.response().code(404); + } + const { filename } = request.payload.file.hapi; + const fileExtension = extname(filename).toLowerCase(); + if (fileExtension !== '.ndjson') { + return Boom.badRequest(`Invalid file extension ${fileExtension}`); + } + + const objectLimit = server.config().get('savedObjects.maxImportExportSize'); + const readStream = createRulesStreamFromNdJson(request.payload.file, objectLimit); + const parsedObjects = await createPromiseFromStreams<[ImportRuleAlertRest | Error]>([ + readStream, + ]); + + const reduced = await parsedObjects.reduce>( + async (accum, parsedRule) => { + const existingImportSuccessError = await accum; + if (parsedRule instanceof Error) { + // If the JSON object had a validation or parse error then we return + // early with the error and an (unknown) for the ruleId + return createImportErrorObject({ + ruleId: '(unknown)', // TODO: Better handling where we know which ruleId is having issues with imports + statusCode: 400, + message: parsedRule.message, + existingImportSuccessError, + }); + } + + const { + description, + enabled, + false_positives: falsePositives, + from, + immutable, + query, + language, + output_index: outputIndex, + saved_id: savedId, + meta, + filters, + rule_id: ruleId, + index, + interval, + max_signals: maxSignals, + risk_score: riskScore, + name, + severity, + tags, + threats, + to, + type, + references, + timeline_id: timelineId, + timeline_title: timelineTitle, + version, + } = parsedRule; + try { + const finalIndex = outputIndex != null ? outputIndex : getIndex(request, server); + const callWithRequest = callWithRequestFactory(request, server); + const indexExists = await getIndexExists(callWithRequest, finalIndex); + if (!indexExists) { + return createImportErrorObject({ + ruleId, + statusCode: 409, + message: `To create a rule, the index must exist first. Index ${finalIndex} does not exist`, + existingImportSuccessError, + }); + } + const rule = await readRules({ alertsClient, ruleId }); + if (rule == null) { + const createdRule = await createRules({ + alertsClient, + actionsClient, + createdAt: new Date().toISOString(), + description, + enabled, + falsePositives, + from, + immutable, + query, + language, + outputIndex: finalIndex, + savedId, + timelineId, + timelineTitle, + meta, + filters, + ruleId, + index, + interval, + maxSignals, + riskScore, + name, + severity, + tags, + to, + type, + threats, + updatedAt: new Date().toISOString(), + references, + version, + }); + return transformOrImportError(ruleId, createdRule, existingImportSuccessError); + } else if (rule != null && request.query.overwrite) { + const updatedRule = await updateRules({ + alertsClient, + actionsClient, + description, + enabled, + falsePositives, + from, + immutable, + query, + language, + outputIndex, + savedId, + timelineId, + timelineTitle, + meta, + filters, + id: undefined, + ruleId, + index, + interval, + maxSignals, + riskScore, + name, + severity, + tags, + to, + type, + threats, + references, + version, + }); + return transformOrImportError(ruleId, updatedRule, existingImportSuccessError); + } else { + return existingImportSuccessError; + } + } catch (err) { + return transformImportError(ruleId, err, existingImportSuccessError); + } + }, + Promise.resolve({ + success: true, + success_count: 0, + errors: [], + }) + ); + return reduced; + }, + }; +}; + +export const importRulesRoute = (server: ServerFacade): void => { + server.route(createImportRulesRoute(server)); +}; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_bulk.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_bulk.test.ts index 9ae2941e6e5f2..2410dcee203f6 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_bulk.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_bulk.test.ts @@ -23,6 +23,7 @@ import { } from '../__mocks__/request_responses'; import { DETECTION_ENGINE_RULES_URL } from '../../../../../common/constants'; import { updateRulesBulkRoute } from './update_rules_bulk_route'; +import { BulkError } from '../utils'; describe('update_rules_bulk', () => { let { server, alertsClient, actionsClient } = createMockServer(); @@ -58,10 +59,14 @@ describe('update_rules_bulk', () => { actionsClient.update.mockResolvedValue(updateActionResult()); alertsClient.update.mockResolvedValue(getResult()); const { payload } = await server.inject(getUpdateBulkRequest()); - const parsed = JSON.parse(payload); - expect(parsed).toEqual([ - { error: { message: 'rule_id: "rule-1" not found', statusCode: 404 }, id: 'rule-1' }, - ]); + const parsed: BulkError[] = JSON.parse(payload); + const expected: BulkError[] = [ + { + error: { message: 'rule_id: "rule-1" not found', status_code: 404 }, + rule_id: 'rule-1', + }, + ]; + expect(parsed).toEqual(expected); }); test('returns 404 if actionClient is not available on the route', async () => { @@ -125,10 +130,14 @@ describe('update_rules_bulk', () => { payload: [typicalPayload()], }; const { payload } = await server.inject(request); - const parsed = JSON.parse(payload); - expect(parsed).toEqual([ - { error: { message: 'rule_id: "rule-1" not found', statusCode: 404 }, id: 'rule-1' }, - ]); + const parsed: BulkError[] = JSON.parse(payload); + const expected: BulkError[] = [ + { + error: { message: 'rule_id: "rule-1" not found', status_code: 404 }, + rule_id: 'rule-1', + }, + ]; + expect(parsed).toEqual(expected); }); test('returns 200 if type is query', async () => { diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts index b30b6c791522b..180a75bdaaeea 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_bulk_route.ts @@ -50,6 +50,7 @@ export const createUpdateRulesBulkRoute = (server: ServerFacade): Hapi.ServerRou output_index: outputIndex, saved_id: savedId, timeline_id: timelineId, + timeline_title: timelineTitle, meta, filters, rule_id: ruleId, @@ -82,6 +83,7 @@ export const createUpdateRulesBulkRoute = (server: ServerFacade): Hapi.ServerRou outputIndex, savedId, timelineId, + timelineTitle, meta, filters, id, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_route.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_route.ts index ec3d9514fa5db..6db8a8902915a 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_route.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/update_rules_route.ts @@ -38,6 +38,7 @@ export const createUpdateRulesRoute: Hapi.ServerRoute = { output_index: outputIndex, saved_id: savedId, timeline_id: timelineId, + timeline_title: timelineTitle, meta, filters, rule_id: ruleId, @@ -77,6 +78,7 @@ export const createUpdateRulesRoute: Hapi.ServerRoute = { outputIndex, savedId, timelineId, + timelineTitle, meta, filters, id, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/utils.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/utils.test.ts index b1f61d11458fe..c1b4c7de73f68 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/utils.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/utils.test.ts @@ -14,11 +14,15 @@ import { transformTags, getIdBulkError, transformOrBulkError, + transformRulesToNdjson, + transformAlertsToRules, + transformOrImportError, } from './utils'; import { getResult } from '../__mocks__/request_responses'; import { INTERNAL_IDENTIFIER } from '../../../../../common/constants'; import { OutputRuleAlertRest } from '../../types'; -import { BulkError } from '../utils'; +import { BulkError, ImportSuccessError } from '../utils'; +import { sampleRule } from '../../signals/__mocks__/es_results'; describe('utils', () => { describe('transformAlertToRule', () => { @@ -79,6 +83,7 @@ describe('utils', () => { }, saved_id: 'some-id', timeline_id: 'some-timeline-id', + timeline_title: 'some-timeline-title', to: 'now', type: 'query', version: 1, @@ -141,6 +146,7 @@ describe('utils', () => { }, saved_id: 'some-id', timeline_id: 'some-timeline-id', + timeline_title: 'some-timeline-title', to: 'now', type: 'query', version: 1, @@ -205,6 +211,7 @@ describe('utils', () => { }, saved_id: 'some-id', timeline_id: 'some-timeline-id', + timeline_title: 'some-timeline-title', to: 'now', type: 'query', version: 1, @@ -269,6 +276,7 @@ describe('utils', () => { }, saved_id: 'some-id', timeline_id: 'some-timeline-id', + timeline_title: 'some-timeline-title', to: 'now', type: 'query', version: 1, @@ -331,6 +339,7 @@ describe('utils', () => { }, saved_id: 'some-id', timeline_id: 'some-timeline-id', + timeline_title: 'some-timeline-title', to: 'now', type: 'query', version: 1, @@ -396,6 +405,7 @@ describe('utils', () => { }, saved_id: 'some-id', timeline_id: 'some-timeline-id', + timeline_title: 'some-timeline-title', to: 'now', type: 'query', version: 1, @@ -461,6 +471,7 @@ describe('utils', () => { }, saved_id: 'some-id', timeline_id: 'some-timeline-id', + timeline_title: 'some-timeline-title', to: 'now', type: 'query', version: 1, @@ -526,6 +537,7 @@ describe('utils', () => { }, saved_id: 'some-id', timeline_id: 'some-timeline-id', + timeline_title: 'some-timeline-title', to: 'now', type: 'query', version: 1, @@ -642,6 +654,7 @@ describe('utils', () => { }, saved_id: 'some-id', timeline_id: 'some-timeline-id', + timeline_title: 'some-timeline-title', version: 1, }; expect(output).toEqual({ @@ -714,6 +727,7 @@ describe('utils', () => { }, saved_id: 'some-id', timeline_id: 'some-timeline-id', + timeline_title: 'some-timeline-title', version: 1, }; expect(output).toEqual(expected); @@ -746,8 +760,8 @@ describe('utils', () => { test('outputs message about id not being found if only id is defined and ruleId is undefined', () => { const error = getIdBulkError({ id: '123', ruleId: undefined }); const expected: BulkError = { - id: '123', - error: { message: 'id: "123" not found', statusCode: 404 }, + rule_id: '123', + error: { message: 'id: "123" not found', status_code: 404 }, }; expect(error).toEqual(expected); }); @@ -755,8 +769,8 @@ describe('utils', () => { test('outputs message about id not being found if only id is defined and ruleId is null', () => { const error = getIdBulkError({ id: '123', ruleId: null }); const expected: BulkError = { - id: '123', - error: { message: 'id: "123" not found', statusCode: 404 }, + rule_id: '123', + error: { message: 'id: "123" not found', status_code: 404 }, }; expect(error).toEqual(expected); }); @@ -764,8 +778,8 @@ describe('utils', () => { test('outputs message about ruleId not being found if only ruleId is defined and id is undefined', () => { const error = getIdBulkError({ id: undefined, ruleId: 'rule-id-123' }); const expected: BulkError = { - id: 'rule-id-123', - error: { message: 'rule_id: "rule-id-123" not found', statusCode: 404 }, + rule_id: 'rule-id-123', + error: { message: 'rule_id: "rule-id-123" not found', status_code: 404 }, }; expect(error).toEqual(expected); }); @@ -773,8 +787,8 @@ describe('utils', () => { test('outputs message about ruleId not being found if only ruleId is defined and id is null', () => { const error = getIdBulkError({ id: null, ruleId: 'rule-id-123' }); const expected: BulkError = { - id: 'rule-id-123', - error: { message: 'rule_id: "rule-id-123" not found', statusCode: 404 }, + rule_id: 'rule-id-123', + error: { message: 'rule_id: "rule-id-123" not found', status_code: 404 }, }; expect(error).toEqual(expected); }); @@ -782,8 +796,8 @@ describe('utils', () => { test('outputs message about both being not defined when both are undefined', () => { const error = getIdBulkError({ id: undefined, ruleId: undefined }); const expected: BulkError = { - id: '(unknown id)', - error: { message: 'id or rule_id should have been defined', statusCode: 404 }, + rule_id: '(unknown id)', + error: { message: 'id or rule_id should have been defined', status_code: 404 }, }; expect(error).toEqual(expected); }); @@ -791,8 +805,8 @@ describe('utils', () => { test('outputs message about both being not defined when both are null', () => { const error = getIdBulkError({ id: null, ruleId: null }); const expected: BulkError = { - id: '(unknown id)', - error: { message: 'id or rule_id should have been defined', statusCode: 404 }, + rule_id: '(unknown id)', + error: { message: 'id or rule_id should have been defined', status_code: 404 }, }; expect(error).toEqual(expected); }); @@ -800,8 +814,8 @@ describe('utils', () => { test('outputs message about both being not defined when id is null and ruleId is undefined', () => { const error = getIdBulkError({ id: null, ruleId: undefined }); const expected: BulkError = { - id: '(unknown id)', - error: { message: 'id or rule_id should have been defined', statusCode: 404 }, + rule_id: '(unknown id)', + error: { message: 'id or rule_id should have been defined', status_code: 404 }, }; expect(error).toEqual(expected); }); @@ -809,8 +823,8 @@ describe('utils', () => { test('outputs message about both being not defined when id is undefined and ruleId is null', () => { const error = getIdBulkError({ id: undefined, ruleId: null }); const expected: BulkError = { - id: '(unknown id)', - error: { message: 'id or rule_id should have been defined', statusCode: 404 }, + rule_id: '(unknown id)', + error: { message: 'id or rule_id should have been defined', status_code: 404 }, }; expect(error).toEqual(expected); }); @@ -875,6 +889,7 @@ describe('utils', () => { }, saved_id: 'some-id', timeline_id: 'some-timeline-id', + timeline_title: 'some-timeline-title', version: 1, }; expect(output).toEqual(expected); @@ -882,10 +897,279 @@ describe('utils', () => { test('returns 500 if the data is not of type siem alert', () => { const output = transformOrBulkError('rule-1', { data: [{ random: 1 }] }); - expect(output).toEqual({ - id: 'rule-1', - error: { message: 'Internal error transforming', statusCode: 500 }, + const expected: BulkError = { + rule_id: 'rule-1', + error: { message: 'Internal error transforming', status_code: 500 }, + }; + expect(output).toEqual(expected); + }); + }); + + describe('transformRulesToNdjson', () => { + test('if rules are empty it returns an empty string', () => { + const ruleNdjson = transformRulesToNdjson([]); + expect(ruleNdjson).toEqual(''); + }); + + test('single rule will transform with new line ending character for ndjson', () => { + const rule = sampleRule(); + const ruleNdjson = transformRulesToNdjson([rule]); + expect(ruleNdjson.endsWith('\n')).toBe(true); + }); + + test('multiple rules will transform with two new line ending characters for ndjson', () => { + const result1 = sampleRule(); + const result2 = sampleRule(); + result2.id = 'some other id'; + result2.rule_id = 'some other id'; + result2.name = 'Some other rule'; + + const ruleNdjson = transformRulesToNdjson([result1, result2]); + // this is how we count characters in JavaScript :-) + const count = ruleNdjson.split('\n').length - 1; + expect(count).toBe(2); + }); + + test('you can parse two rules back out without errors', () => { + const result1 = sampleRule(); + const result2 = sampleRule(); + result2.id = 'some other id'; + result2.rule_id = 'some other id'; + result2.name = 'Some other rule'; + + const ruleNdjson = transformRulesToNdjson([result1, result2]); + const ruleStrings = ruleNdjson.split('\n'); + const reParsed1 = JSON.parse(ruleStrings[0]); + const reParsed2 = JSON.parse(ruleStrings[1]); + expect(reParsed1).toEqual(result1); + expect(reParsed2).toEqual(result2); + }); + }); + + describe('transformAlertsToRules', () => { + test('given an empty array returns an empty array', () => { + expect(transformAlertsToRules([])).toEqual([]); + }); + + test('given single alert will return the alert transformed', () => { + const result1 = getResult(); + const transformed = transformAlertsToRules([result1]); + expect(transformed).toEqual([ + { + created_at: '2019-12-13T16:40:33.400Z', + created_by: 'elastic', + description: 'Detecting root and admin users', + enabled: true, + false_positives: [], + filters: [{ query: { match_phrase: { 'host.name': 'some-host' } } }], + from: 'now-6m', + id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + immutable: false, + index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], + interval: '5m', + language: 'kuery', + max_signals: 100, + meta: { someMeta: 'someField' }, + name: 'Detect Root/Admin Users', + output_index: '.siem-signals', + query: 'user.name: root or user.name: admin', + references: ['http://www.example.com', 'https://ww.example.com'], + risk_score: 50, + rule_id: 'rule-1', + saved_id: 'some-id', + severity: 'high', + tags: [], + threats: [ + { + framework: 'MITRE ATT&CK', + tactic: { + id: 'TA0040', + name: 'impact', + reference: 'https://attack.mitre.org/tactics/TA0040/', + }, + techniques: [ + { + id: 'T1499', + name: 'endpoint denial of service', + reference: 'https://attack.mitre.org/techniques/T1499/', + }, + ], + }, + ], + timeline_id: 'some-timeline-id', + timeline_title: 'some-timeline-title', + to: 'now', + type: 'query', + updated_at: '2019-12-13T16:40:33.400Z', + updated_by: 'elastic', + version: 1, + }, + ]); + }); + + test('given two alerts will return the two alerts transformed', () => { + const result1 = getResult(); + const result2 = getResult(); + result2.id = 'some other id'; + result2.params.ruleId = 'some other id'; + + const transformed = transformAlertsToRules([result1, result2]); + expect(transformed).toEqual([ + { + created_at: '2019-12-13T16:40:33.400Z', + created_by: 'elastic', + description: 'Detecting root and admin users', + enabled: true, + false_positives: [], + filters: [{ query: { match_phrase: { 'host.name': 'some-host' } } }], + from: 'now-6m', + id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + immutable: false, + index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], + interval: '5m', + language: 'kuery', + max_signals: 100, + meta: { someMeta: 'someField' }, + name: 'Detect Root/Admin Users', + output_index: '.siem-signals', + query: 'user.name: root or user.name: admin', + references: ['http://www.example.com', 'https://ww.example.com'], + risk_score: 50, + rule_id: 'rule-1', + saved_id: 'some-id', + severity: 'high', + tags: [], + threats: [ + { + framework: 'MITRE ATT&CK', + tactic: { + id: 'TA0040', + name: 'impact', + reference: 'https://attack.mitre.org/tactics/TA0040/', + }, + techniques: [ + { + id: 'T1499', + name: 'endpoint denial of service', + reference: 'https://attack.mitre.org/techniques/T1499/', + }, + ], + }, + ], + timeline_id: 'some-timeline-id', + timeline_title: 'some-timeline-title', + to: 'now', + type: 'query', + updated_at: '2019-12-13T16:40:33.400Z', + updated_by: 'elastic', + version: 1, + }, + { + created_at: '2019-12-13T16:40:33.400Z', + created_by: 'elastic', + description: 'Detecting root and admin users', + enabled: true, + false_positives: [], + filters: [{ query: { match_phrase: { 'host.name': 'some-host' } } }], + from: 'now-6m', + id: 'some other id', + immutable: false, + index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], + interval: '5m', + language: 'kuery', + max_signals: 100, + meta: { someMeta: 'someField' }, + name: 'Detect Root/Admin Users', + output_index: '.siem-signals', + query: 'user.name: root or user.name: admin', + references: ['http://www.example.com', 'https://ww.example.com'], + risk_score: 50, + rule_id: 'some other id', + saved_id: 'some-id', + severity: 'high', + tags: [], + threats: [ + { + framework: 'MITRE ATT&CK', + tactic: { + id: 'TA0040', + name: 'impact', + reference: 'https://attack.mitre.org/tactics/TA0040/', + }, + techniques: [ + { + id: 'T1499', + name: 'endpoint denial of service', + reference: 'https://attack.mitre.org/techniques/T1499/', + }, + ], + }, + ], + timeline_id: 'some-timeline-id', + timeline_title: 'some-timeline-title', + to: 'now', + type: 'query', + updated_at: '2019-12-13T16:40:33.400Z', + updated_by: 'elastic', + version: 1, + }, + ]); + }); + }); + + describe('transformOrImportError', () => { + test('returns 1 given success if the alert is an alert type and the existing success count is 0', () => { + const output = transformOrImportError('rule-1', getResult(), { + success: true, + success_count: 0, + errors: [], + }); + const expected: ImportSuccessError = { + success: true, + errors: [], + success_count: 1, + }; + expect(output).toEqual(expected); + }); + + test('returns 2 given successes if the alert is an alert type and the existing success count is 1', () => { + const output = transformOrImportError('rule-1', getResult(), { + success: true, + success_count: 1, + errors: [], }); + const expected: ImportSuccessError = { + success: true, + errors: [], + success_count: 2, + }; + expect(output).toEqual(expected); + }); + + test('returns 1 error and success of false if the data is not of type siem alert', () => { + const output = transformOrImportError( + 'rule-1', + { data: [{ random: 1 }] }, + { + success: true, + success_count: 1, + errors: [], + } + ); + const expected: ImportSuccessError = { + success: false, + errors: [ + { + rule_id: 'rule-1', + error: { + message: 'Internal error transforming', + status_code: 500, + }, + }, + ], + success_count: 1, + }; + expect(output).toEqual(expected); }); }); }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/utils.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/utils.ts index b9bf3f8a942fc..21972905a5063 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/utils.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/rules/utils.ts @@ -9,7 +9,13 @@ import { pickBy } from 'lodash/fp'; import { INTERNAL_IDENTIFIER } from '../../../../../common/constants'; import { RuleAlertType, isAlertType, isAlertTypes } from '../../rules/types'; import { OutputRuleAlertRest } from '../../types'; -import { createBulkErrorObject, BulkError } from '../utils'; +import { + createBulkErrorObject, + BulkError, + createSuccessObject, + ImportSuccessError, + createImportErrorObject, +} from '../utils'; export const getIdError = ({ id, @@ -85,6 +91,7 @@ export const transformAlertToRule = (alert: RuleAlertType): Partial>): string => { + if (rules.length !== 0) { + const rulesString = rules.map(rule => JSON.stringify(rule)).join('\n'); + return `${rulesString}\n`; + } else { + return ''; + } +}; + +export const transformAlertsToRules = ( + alerts: RuleAlertType[] +): Array> => { + return alerts.map(alert => transformAlertToRule(alert)); +}; + export const transformFindAlertsOrError = (findResults: { data: unknown[] }): unknown | Boom => { if (isAlertTypes(findResults.data)) { findResults.data = findResults.data.map(alert => transformAlertToRule(alert)); @@ -127,3 +149,20 @@ export const transformOrBulkError = ( }); } }; + +export const transformOrImportError = ( + ruleId: string, + alert: unknown, + existingImportSuccessError: ImportSuccessError +): ImportSuccessError => { + if (isAlertType(alert)) { + return createSuccessObject(existingImportSuccessError); + } else { + return createImportErrorObject({ + ruleId, + statusCode: 500, + message: 'Internal error transforming', + existingImportSuccessError, + }); + } +}; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/add_prepackaged_rules_schema.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/add_prepackaged_rules_schema.test.ts index 04a72135d3f00..74eb4d6c8e918 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/add_prepackaged_rules_schema.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/add_prepackaged_rules_schema.test.ts @@ -1076,9 +1076,7 @@ describe('add prepackaged rules schema', () => { test('You can omit the query string when filters are present', () => { expect( - addPrepackagedRulesSchema.validate< - Partial & { meta: string }> - >({ + addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', risk_score: 50, description: 'some description', @@ -1099,7 +1097,7 @@ describe('add prepackaged rules schema', () => { ).toBeFalsy(); }); - test('validates with timeline_id', () => { + test('validates with timeline_id and timeline_title', () => { expect( addPrepackagedRulesSchema.validate>({ rule_id: 'rule-1', @@ -1117,7 +1115,131 @@ describe('add prepackaged rules schema', () => { language: 'kuery', version: 1, timeline_id: 'timeline-id', + timeline_title: 'timeline-title', }).error ).toBeFalsy(); }); + + test('You cannot omit timeline_title when timeline_id is present', () => { + expect( + addPrepackagedRulesSchema.validate>({ + rule_id: 'rule-1', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + immutable: true, + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + language: 'kuery', + filters: [], + max_signals: 1, + version: 1, + timeline_id: 'timeline-id', + }).error + ).toBeTruthy(); + }); + + test('You cannot have a null value for timeline_title when timeline_id is present', () => { + expect( + addPrepackagedRulesSchema.validate>({ + rule_id: 'rule-1', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + immutable: true, + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + language: 'kuery', + filters: [], + max_signals: 1, + version: 1, + timeline_id: 'timeline-id', + timeline_title: null, + }).error + ).toBeTruthy(); + }); + + test('You cannot have empty string for timeline_title when timeline_id is present', () => { + expect( + addPrepackagedRulesSchema.validate>({ + rule_id: 'rule-1', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + immutable: true, + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + language: 'kuery', + filters: [], + max_signals: 1, + version: 1, + timeline_id: 'timeline-id', + timeline_title: '', + }).error + ).toBeTruthy(); + }); + + test('You cannot have timeline_title with an empty timeline_id', () => { + expect( + addPrepackagedRulesSchema.validate>({ + rule_id: 'rule-1', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + immutable: true, + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + language: 'kuery', + filters: [], + max_signals: 1, + version: 1, + timeline_id: '', + timeline_title: 'some-title', + }).error + ).toBeTruthy(); + }); + + test('You cannot have timeline_title without timeline_id', () => { + expect( + addPrepackagedRulesSchema.validate>({ + rule_id: 'rule-1', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + immutable: true, + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + language: 'kuery', + filters: [], + max_signals: 1, + version: 1, + timeline_title: 'some-title', + }).error + ).toBeTruthy(); + }); }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/add_prepackaged_rules_schema.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/add_prepackaged_rules_schema.ts index c993b05cb5f29..49907b4a975e6 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/add_prepackaged_rules_schema.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/add_prepackaged_rules_schema.ts @@ -21,6 +21,7 @@ import { language, saved_id, timeline_id, + timeline_title, meta, risk_score, max_signals, @@ -63,6 +64,7 @@ export const addPrepackagedRulesSchema = Joi.object({ otherwise: Joi.forbidden(), }), timeline_id, + timeline_title, meta, risk_score: risk_score.required(), max_signals: max_signals.default(DEFAULT_MAX_SIGNALS), diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_schema.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_schema.test.ts index 8dc00b66e97a3..87916bea60649 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_schema.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_schema.test.ts @@ -1024,7 +1024,7 @@ describe('create rules schema', () => { test('You can omit the query string when filters are present', () => { expect( - createRulesSchema.validate & { meta: string }>>({ + createRulesSchema.validate>({ rule_id: 'rule-1', output_index: '.siem-signals', risk_score: 50, @@ -1045,7 +1045,7 @@ describe('create rules schema', () => { ).toBeFalsy(); }); - test('timeline_id validates', () => { + test('validates with timeline_id and timeline_title', () => { expect( createRulesSchema.validate>({ rule_id: 'rule-1', @@ -1062,8 +1062,122 @@ describe('create rules schema', () => { references: ['index-1'], query: 'some query', language: 'kuery', - timeline_id: 'some_id', + timeline_id: 'timeline-id', + timeline_title: 'timeline-title', }).error ).toBeFalsy(); }); + + test('You cannot omit timeline_title when timeline_id is present', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + timeline_id: 'some_id', + }).error + ).toBeTruthy(); + }); + + test('You cannot have a null value for timeline_title when timeline_id is present', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + timeline_id: 'some_id', + timeline_title: null, + }).error + ).toBeTruthy(); + }); + + test('You cannot have empty string for timeline_title when timeline_id is present', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + timeline_id: 'some_id', + timeline_title: '', + }).error + ).toBeTruthy(); + }); + + test('You cannot have timeline_title with an empty timeline_id', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + timeline_id: '', + timeline_title: 'some-title', + }).error + ).toBeTruthy(); + }); + + test('You cannot have timeline_title without timeline_id', () => { + expect( + createRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + timeline_title: 'some-title', + }).error + ).toBeTruthy(); + }); }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_schema.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_schema.ts index 614451312d04d..df5c1694d6c78 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_schema.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/create_rules_schema.ts @@ -22,6 +22,7 @@ import { output_index, saved_id, timeline_id, + timeline_title, meta, risk_score, max_signals, @@ -57,6 +58,7 @@ export const createRulesSchema = Joi.object({ otherwise: Joi.forbidden(), }), timeline_id, + timeline_title, meta, risk_score: risk_score.required(), max_signals: max_signals.default(DEFAULT_MAX_SIGNALS), diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/export_rules_schema.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/export_rules_schema.test.ts new file mode 100644 index 0000000000000..7850e3a733f09 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/export_rules_schema.test.ts @@ -0,0 +1,99 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { exportRulesSchema, exportRulesQuerySchema } from './export_rules_schema'; +import { ExportRulesRequest } from '../../rules/types'; + +describe('create rules schema', () => { + describe('exportRulesSchema', () => { + test('null value or absent values validate', () => { + expect(exportRulesSchema.validate(null).error).toBeFalsy(); + }); + + test('empty object does not validate', () => { + expect( + exportRulesSchema.validate>({}).error + ).toBeTruthy(); + }); + + test('empty object array does validate', () => { + expect( + exportRulesSchema.validate>({ objects: [] }).error + ).toBeTruthy(); + }); + + test('array with rule_id validates', () => { + expect( + exportRulesSchema.validate>({ + objects: [{ rule_id: 'test-1' }], + }).error + ).toBeFalsy(); + }); + + test('array with id does not validate as we do not allow that on purpose since we export rule_id', () => { + expect( + exportRulesSchema.validate>({ + objects: [{ id: 'test-1' }], + }).error + ).toBeTruthy(); + }); + }); + + describe('exportRulesQuerySchema', () => { + test('default value for file_name is export.ndjson', () => { + expect( + exportRulesQuerySchema.validate>({}).value.file_name + ).toEqual('export.ndjson'); + }); + + test('default value for exclude_export_details is false', () => { + expect( + exportRulesQuerySchema.validate>({}).value + .exclude_export_details + ).toEqual(false); + }); + + test('file_name validates', () => { + expect( + exportRulesQuerySchema.validate>({ + file_name: 'test.ndjson', + }).error + ).toBeFalsy(); + }); + + test('file_name does not validate with a number', () => { + expect( + exportRulesQuerySchema.validate< + Partial & { file_name: number }> + >({ + file_name: 5, + }).error + ).toBeTruthy(); + }); + + test('exclude_export_details validates with a boolean true', () => { + expect( + exportRulesQuerySchema.validate>({ + exclude_export_details: true, + }).error + ).toBeFalsy(); + }); + + test('exclude_export_details does not validate with a weird string', () => { + expect( + exportRulesQuerySchema.validate< + Partial< + Omit & { + exclude_export_details: string; + } + > + >({ + exclude_export_details: 'blah', + }).error + ).toBeTruthy(); + }); + }); +}); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/export_rules_schema.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/export_rules_schema.ts new file mode 100644 index 0000000000000..a14d81604d9f8 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/export_rules_schema.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import Joi from 'joi'; + +/* eslint-disable @typescript-eslint/camelcase */ +import { objects, exclude_export_details, file_name } from './schemas'; +/* eslint-disable @typescript-eslint/camelcase */ + +export const exportRulesSchema = Joi.object({ + objects, +}) + .min(1) + .allow(null); + +export const exportRulesQuerySchema = Joi.object({ + file_name: file_name.default('export.ndjson'), + exclude_export_details: exclude_export_details.default(false), +}); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/import_rules_schema.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/import_rules_schema.test.ts new file mode 100644 index 0000000000000..09bc7a70711ec --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/import_rules_schema.test.ts @@ -0,0 +1,1327 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + importRulesSchema, + importRulesQuerySchema, + importRulesPayloadSchema, +} from './import_rules_schema'; +import { ThreatParams, RuleAlertParamsRest, ImportRuleAlertRest } from '../../types'; +import { ImportRulesRequest } from '../../rules/types'; + +describe('import rules schema', () => { + describe('importRulesSchema', () => { + test('empty objects do not validate', () => { + expect(importRulesSchema.validate>({}).error).toBeTruthy(); + }); + + test('made up values do not validate', () => { + expect( + importRulesSchema.validate>({ + madeUp: 'hi', + }).error + ).toBeTruthy(); + }); + + test('[rule_id] does not validate', () => { + expect( + importRulesSchema.validate>({ + rule_id: 'rule-1', + }).error + ).toBeTruthy(); + }); + + test('[rule_id, description] does not validate', () => { + expect( + importRulesSchema.validate>({ + rule_id: 'rule-1', + description: 'some description', + }).error + ).toBeTruthy(); + }); + + test('[rule_id, description, from] does not validate', () => { + expect( + importRulesSchema.validate>({ + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + }).error + ).toBeTruthy(); + }); + + test('[rule_id, description, from, to] does not validate', () => { + expect( + importRulesSchema.validate>({ + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + }).error + ).toBeTruthy(); + }); + + test('[rule_id, description, from, to, name] does not validate', () => { + expect( + importRulesSchema.validate>({ + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + name: 'some-name', + }).error + ).toBeTruthy(); + }); + + test('[rule_id, description, from, to, name, severity] does not validate', () => { + expect( + importRulesSchema.validate>({ + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + name: 'some-name', + severity: 'severity', + }).error + ).toBeTruthy(); + }); + + test('[rule_id, description, from, to, name, severity, type] does not validate', () => { + expect( + importRulesSchema.validate>({ + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + name: 'some-name', + severity: 'severity', + type: 'query', + }).error + ).toBeTruthy(); + }); + + test('[rule_id, description, from, to, name, severity, type, interval] does not validate', () => { + expect( + importRulesSchema.validate>({ + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + }).error + ).toBeTruthy(); + }); + + test('[rule_id, description, from, to, name, severity, type, interval, index] does not validate', () => { + expect( + importRulesSchema.validate>({ + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + name: 'some-name', + severity: 'severity', + type: 'query', + interval: '5m', + index: ['index-1'], + }).error + ).toBeTruthy(); + }); + + test('[rule_id, description, from, to, name, severity, type, query, index, interval] does validate', () => { + expect( + importRulesSchema.validate>({ + rule_id: 'rule-1', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + name: 'some-name', + severity: 'severity', + type: 'query', + query: 'some query', + index: ['index-1'], + interval: '5m', + }).error + ).toBeFalsy(); + }); + + test('[rule_id, description, from, to, index, name, severity, interval, type, query, language] does not validate', () => { + expect( + importRulesSchema.validate>({ + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + query: 'some query', + language: 'kuery', + }).error + ).toBeTruthy(); + }); + + test('[rule_id, description, from, to, index, name, severity, interval, type, query, language, risk_score] does validate', () => { + expect( + importRulesSchema.validate>({ + rule_id: 'rule-1', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + query: 'some query', + language: 'kuery', + }).error + ).toBeFalsy(); + }); + + test('[rule_id, description, from, to, index, name, severity, interval, type, query, language, risk_score, output_index] does validate', () => { + expect( + importRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + query: 'some query', + language: 'kuery', + }).error + ).toBeFalsy(); + }); + + test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score] does validate', () => { + expect( + importRulesSchema.validate>({ + rule_id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + risk_score: 50, + }).error + ).toBeFalsy(); + }); + + test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score, output_index] does validate', () => { + expect( + importRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + }).error + ).toBeFalsy(); + }); + test('You can send in an empty array to threats', () => { + expect( + importRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + threats: [], + }).error + ).toBeFalsy(); + }); + test('[rule_id, description, from, to, index, name, severity, interval, type, filter, risk_score, output_index, threats] does validate', () => { + expect( + importRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + threats: [ + { + framework: 'someFramework', + tactic: { + id: 'fakeId', + name: 'fakeName', + reference: 'fakeRef', + }, + techniques: [ + { + id: 'techniqueId', + name: 'techniqueName', + reference: 'techniqueRef', + }, + ], + }, + ], + }).error + ).toBeFalsy(); + }); + + test('allows references to be sent as valid', () => { + expect( + importRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + }).error + ).toBeFalsy(); + }); + + test('defaults references to an array', () => { + expect( + importRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + query: 'some-query', + language: 'kuery', + }).value.references + ).toEqual([]); + }); + + test('references cannot be numbers', () => { + expect( + importRulesSchema.validate< + Partial> & { references: number[] } + >({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + query: 'some-query', + language: 'kuery', + references: [5], + }).error + ).toBeTruthy(); + }); + + test('indexes cannot be numbers', () => { + expect( + importRulesSchema.validate< + Partial> & { index: number[] } + >({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: [5], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + query: 'some-query', + language: 'kuery', + }).error + ).toBeTruthy(); + }); + + test('defaults interval to 5 min', () => { + expect( + importRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + type: 'query', + }).value.interval + ).toEqual('5m'); + }); + + test('defaults max signals to 100', () => { + expect( + importRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + }).value.max_signals + ).toEqual(100); + }); + + test('saved_id is required when type is saved_query and will not validate without out', () => { + expect( + importRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'saved_query', + }).error + ).toBeTruthy(); + }); + + test('saved_id is required when type is saved_query and validates with it', () => { + expect( + importRulesSchema.validate>({ + rule_id: 'rule-1', + risk_score: 50, + output_index: '.siem-signals', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'saved_query', + saved_id: 'some id', + }).error + ).toBeFalsy(); + }); + + test('saved_query type can have filters with it', () => { + expect( + importRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'saved_query', + saved_id: 'some id', + filters: [], + }).error + ).toBeFalsy(); + }); + + test('filters cannot be a string', () => { + expect( + importRulesSchema.validate< + Partial & { filters: string }> + >({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'saved_query', + saved_id: 'some id', + filters: 'some string', + }).error + ).toBeTruthy(); + }); + + test('language validates with kuery', () => { + expect( + importRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + }).error + ).toBeFalsy(); + }); + + test('language validates with lucene', () => { + expect( + importRulesSchema.validate>({ + rule_id: 'rule-1', + risk_score: 50, + output_index: '.siem-signals', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'lucene', + }).error + ).toBeFalsy(); + }); + + test('language does not validate with something made up', () => { + expect( + importRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'something-made-up', + }).error + ).toBeTruthy(); + }); + + test('max_signals cannot be negative', () => { + expect( + importRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: -1, + }).error + ).toBeTruthy(); + }); + + test('max_signals cannot be zero', () => { + expect( + importRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 0, + }).error + ).toBeTruthy(); + }); + + test('max_signals can be 1', () => { + expect( + importRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + }).error + ).toBeFalsy(); + }); + + test('You can optionally send in an array of tags', () => { + expect( + importRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + tags: ['tag_1', 'tag_2'], + }).error + ).toBeFalsy(); + }); + + test('You cannot send in an array of tags that are numbers', () => { + expect( + importRulesSchema.validate> & { tags: number[] }>( + { + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + tags: [0, 1, 2], + } + ).error + ).toBeTruthy(); + }); + + test('You cannot send in an array of threats that are missing "framework"', () => { + expect( + importRulesSchema.validate< + Partial> & { + threats: Array>>; + } + >({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + threats: [ + { + tactic: { + id: 'fakeId', + name: 'fakeName', + reference: 'fakeRef', + }, + techniques: [ + { + id: 'techniqueId', + name: 'techniqueName', + reference: 'techniqueRef', + }, + ], + }, + ], + }).error + ).toBeTruthy(); + }); + test('You cannot send in an array of threats that are missing "tactic"', () => { + expect( + importRulesSchema.validate< + Partial> & { + threats: Array>>; + } + >({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + threats: [ + { + framework: 'fake', + techniques: [ + { + id: 'techniqueId', + name: 'techniqueName', + reference: 'techniqueRef', + }, + ], + }, + ], + }).error + ).toBeTruthy(); + }); + test('You cannot send in an array of threats that are missing "techniques"', () => { + expect( + importRulesSchema.validate< + Partial> & { + threats: Array>>; + } + >({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + threats: [ + { + framework: 'fake', + tactic: { + id: 'fakeId', + name: 'fakeName', + reference: 'fakeRef', + }, + }, + ], + }).error + ).toBeTruthy(); + }); + + test('You can optionally send in an array of false positives', () => { + expect( + importRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + false_positives: ['false_1', 'false_2'], + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + }).error + ).toBeFalsy(); + }); + + test('You cannot send in an array of false positives that are numbers', () => { + expect( + importRulesSchema.validate< + Partial> & { false_positives: number[] } + >({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + false_positives: [5, 4], + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + }).error + ).toBeTruthy(); + }); + + test('You can optionally set the immutable to be true', () => { + expect( + importRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + immutable: true, + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + }).error + ).toBeFalsy(); + }); + + test('You cannot set the immutable to be a number', () => { + expect( + importRulesSchema.validate< + Partial> & { immutable: number } + >({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + immutable: 5, + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + }).error + ).toBeTruthy(); + }); + + test('You cannot set the risk_score to 101', () => { + expect( + importRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 101, + description: 'some description', + from: 'now-5m', + to: 'now', + immutable: true, + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + }).error + ).toBeTruthy(); + }); + + test('You cannot set the risk_score to -1', () => { + expect( + importRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: -1, + description: 'some description', + from: 'now-5m', + to: 'now', + immutable: true, + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + }).error + ).toBeTruthy(); + }); + + test('You can set the risk_score to 0', () => { + expect( + importRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 0, + description: 'some description', + from: 'now-5m', + to: 'now', + immutable: true, + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + }).error + ).toBeFalsy(); + }); + + test('You can set the risk_score to 100', () => { + expect( + importRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 100, + description: 'some description', + from: 'now-5m', + to: 'now', + immutable: true, + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + }).error + ).toBeFalsy(); + }); + + test('You can set meta to any object you want', () => { + expect( + importRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + immutable: true, + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + meta: { + somethingMadeUp: { somethingElse: true }, + }, + }).error + ).toBeFalsy(); + }); + + test('You cannot create meta as a string', () => { + expect( + importRulesSchema.validate & { meta: string }>>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + immutable: true, + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + max_signals: 1, + meta: 'should not work', + }).error + ).toBeTruthy(); + }); + + test('You can omit the query string when filters are present', () => { + expect( + importRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + immutable: true, + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + language: 'kuery', + filters: [], + max_signals: 1, + }).error + ).toBeFalsy(); + }); + + test('validates with timeline_id and timeline_title', () => { + expect( + importRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + timeline_id: 'timeline-id', + timeline_title: 'timeline-title', + }).error + ).toBeFalsy(); + }); + + test('You cannot omit timeline_title when timeline_id is present', () => { + expect( + importRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + timeline_id: 'some_id', + }).error + ).toBeTruthy(); + }); + + test('You cannot have a null value for timeline_title when timeline_id is present', () => { + expect( + importRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + timeline_id: 'some_id', + timeline_title: null, + }).error + ).toBeTruthy(); + }); + + test('You cannot have empty string for timeline_title when timeline_id is present', () => { + expect( + importRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + timeline_id: 'some_id', + timeline_title: '', + }).error + ).toBeTruthy(); + }); + + test('You cannot have timeline_title with an empty timeline_id', () => { + expect( + importRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + timeline_id: '', + timeline_title: 'some-title', + }).error + ).toBeTruthy(); + }); + + test('You cannot have timeline_title without timeline_id', () => { + expect( + importRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + timeline_title: 'some-title', + }).error + ).toBeTruthy(); + }); + + test('rule_id is required and you cannot get by with just id', () => { + expect( + importRulesSchema.validate>({ + id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + }).error + ).toBeTruthy(); + }); + + test('it validates with created_at, updated_at, created_by, updated_by values', () => { + expect( + importRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + created_at: '2020-01-09T06:15:24.749Z', + updated_at: '2020-01-09T06:15:24.749Z', + created_by: 'Braden Hassanabad', + updated_by: 'Evan Hassanabad', + }).error + ).toBeFalsy(); + }); + + test('it does not validate with epoch strings for created_at', () => { + expect( + importRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + created_at: '1578550728650', + updated_at: '2020-01-09T06:15:24.749Z', + created_by: 'Braden Hassanabad', + updated_by: 'Evan Hassanabad', + }).error + ).toBeTruthy(); + }); + + test('it does not validate with epoch strings for updated_at', () => { + expect( + importRulesSchema.validate>({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + references: ['index-1'], + query: 'some query', + language: 'kuery', + created_at: '2020-01-09T06:15:24.749Z', + updated_at: '1578550728650', + created_by: 'Braden Hassanabad', + updated_by: 'Evan Hassanabad', + }).error + ).toBeTruthy(); + }); + }); + + describe('importRulesQuerySchema', () => { + test('overwrite gets a default value of false', () => { + expect( + importRulesQuerySchema.validate>({}).value.overwrite + ).toEqual(false); + }); + + test('overwrite validates with a boolean true', () => { + expect( + importRulesQuerySchema.validate>({ + overwrite: true, + }).error + ).toBeFalsy(); + }); + + test('overwrite does not validate with a weird string', () => { + expect( + importRulesQuerySchema.validate< + Partial< + Omit & { + overwrite: string; + } + > + >({ + overwrite: 'blah', + }).error + ).toBeTruthy(); + }); + }); + + describe('importRulesPayloadSchema', () => { + test('does not validate with an empty object', () => { + expect(importRulesPayloadSchema.validate({}).error).toBeTruthy(); + }); + + test('does validate with a file object', () => { + expect(importRulesPayloadSchema.validate({ file: {} }).error).toBeFalsy(); + }); + }); +}); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/import_rules_schema.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/import_rules_schema.ts new file mode 100644 index 0000000000000..df825c442fff6 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/import_rules_schema.ts @@ -0,0 +1,100 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import Joi from 'joi'; + +/* eslint-disable @typescript-eslint/camelcase */ +import { + id, + created_at, + updated_at, + created_by, + updated_by, + enabled, + description, + false_positives, + filters, + from, + immutable, + index, + rule_id, + interval, + query, + language, + output_index, + saved_id, + timeline_id, + timeline_title, + meta, + risk_score, + max_signals, + name, + severity, + tags, + to, + type, + threats, + references, + version, +} from './schemas'; +/* eslint-enable @typescript-eslint/camelcase */ + +import { DEFAULT_MAX_SIGNALS } from '../../../../../common/constants'; + +/** + * Differences from this and the createRulesSchema are + * - rule_id is required + * - id is optional (but ignored in the import code - rule_id is exclusively used for imports) + * - created_at is optional (but ignored in the import code) + * - updated_at is optional (but ignored in the import code) + * - created_by is optional (but ignored in the import code) + * - updated_by is optional (but ignored in the import code) + */ +export const importRulesSchema = Joi.object({ + id, + description: description.required(), + enabled: enabled.default(true), + false_positives: false_positives.default([]), + filters, + from: from.required(), + rule_id: rule_id.required(), + immutable: immutable.default(false), + index, + interval: interval.default('5m'), + query: query.allow('').default(''), + language: language.default('kuery'), + output_index, + saved_id: saved_id.when('type', { + is: 'saved_query', + then: Joi.required(), + otherwise: Joi.forbidden(), + }), + timeline_id, + timeline_title, + meta, + risk_score: risk_score.required(), + max_signals: max_signals.default(DEFAULT_MAX_SIGNALS), + name: name.required(), + severity: severity.required(), + tags: tags.default([]), + to: to.required(), + type: type.required(), + threats: threats.default([]), + references: references.default([]), + version: version.default(1), + created_at, + updated_at, + created_by, + updated_by, +}); + +export const importRulesQuerySchema = Joi.object({ + overwrite: Joi.boolean().default(false), +}); + +export const importRulesPayloadSchema = Joi.object({ + file: Joi.object().required(), +}); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/schemas.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/schemas.ts index 68d3166c74d6d..ecca661d2b856 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/schemas.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/schemas.ts @@ -9,7 +9,9 @@ import Joi from 'joi'; /* eslint-disable @typescript-eslint/camelcase */ export const description = Joi.string(); export const enabled = Joi.boolean(); +export const exclude_export_details = Joi.boolean(); export const false_positives = Joi.array().items(Joi.string()); +export const file_name = Joi.string(); export const filters = Joi.array(); export const from = Joi.string(); export const immutable = Joi.boolean(); @@ -21,9 +23,19 @@ export const index = Joi.array() export const interval = Joi.string(); export const query = Joi.string(); export const language = Joi.string().valid('kuery', 'lucene'); +export const objects = Joi.array().items( + Joi.object({ + rule_id, + }).required() +); export const output_index = Joi.string(); export const saved_id = Joi.string(); export const timeline_id = Joi.string(); +export const timeline_title = Joi.string().when('timeline_id', { + is: Joi.exist(), + then: Joi.required(), + otherwise: Joi.forbidden(), +}); export const meta = Joi.object(); export const max_signals = Joi.number().greater(0); export const name = Joi.string(); @@ -70,7 +82,6 @@ export const threat_technique = Joi.object({ reference: threat_technique_reference.required(), }); export const threat_techniques = Joi.array().items(threat_technique.required()); - export const threats = Joi.array().items( Joi.object({ framework: threat_framework.required(), @@ -78,5 +89,12 @@ export const threats = Joi.array().items( techniques: threat_techniques.required(), }) ); - +export const created_at = Joi.string() + .isoDate() + .strict(); +export const updated_at = Joi.string() + .isoDate() + .strict(); +export const created_by = Joi.string(); +export const updated_by = Joi.string(); export const version = Joi.number().min(1); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/update_rules_schema.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/update_rules_schema.test.ts index 1f00e0a13866a..f713840ab43f9 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/update_rules_schema.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/update_rules_schema.test.ts @@ -867,7 +867,7 @@ describe('update rules schema', () => { ).toBeTruthy(); }); - test('timeline_id validates', () => { + test('validates with timeline_id and timeline_title', () => { expect( updateRulesSchema.validate>({ id: 'rule-1', @@ -881,7 +881,101 @@ describe('update rules schema', () => { type: 'saved_query', saved_id: 'some id', timeline_id: 'some-id', + timeline_title: 'some-title', }).error ).toBeFalsy(); }); + + test('You cannot omit timeline_title when timeline_id is present', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'saved_query', + saved_id: 'some id', + timeline_id: 'some-id', + }).error + ).toBeTruthy(); + }); + + test('You cannot have a null value for timeline_title when timeline_id is present', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'saved_query', + saved_id: 'some id', + timeline_id: 'timeline-id', + timeline_title: null, + }).error + ).toBeTruthy(); + }); + + test('You cannot have empty string for timeline_title when timeline_id is present', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'saved_query', + saved_id: 'some id', + timeline_id: 'some-id', + timeline_title: '', + }).error + ).toBeTruthy(); + }); + + test('You cannot have timeline_title with an empty timeline_id', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'saved_query', + saved_id: 'some id', + timeline_id: '', + timeline_title: 'some-title', + }).error + ).toBeTruthy(); + }); + + test('You cannot have timeline_title without timeline_id', () => { + expect( + updateRulesSchema.validate>({ + id: 'rule-1', + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'saved_query', + saved_id: 'some id', + timeline_title: 'some-title', + }).error + ).toBeTruthy(); + }); }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/update_rules_schema.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/update_rules_schema.ts index afd8a5fce4833..9c3188738faea 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/update_rules_schema.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/schemas/update_rules_schema.ts @@ -22,6 +22,7 @@ import { output_index, saved_id, timeline_id, + timeline_title, meta, risk_score, max_signals, @@ -53,6 +54,7 @@ export const updateRulesSchema = Joi.object({ output_index, saved_id, timeline_id, + timeline_title, meta, risk_score, max_signals, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.test.ts index fa95c77f646d6..ffd0c791c5bb6 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.test.ts @@ -6,7 +6,15 @@ import Boom from 'boom'; -import { transformError, transformBulkError, BulkError } from './utils'; +import { + transformError, + transformBulkError, + BulkError, + createSuccessObject, + ImportSuccessError, + createImportErrorObject, + transformImportError, +} from './utils'; describe('utils', () => { describe('transformError', () => { @@ -63,8 +71,8 @@ describe('utils', () => { const boom = new Boom('some boom message', { statusCode: 400 }); const transformed = transformBulkError('rule-1', boom); const expected: BulkError = { - id: 'rule-1', - error: { message: 'some boom message', statusCode: 400 }, + rule_id: 'rule-1', + error: { message: 'some boom message', status_code: 400 }, }; expect(transformed).toEqual(expected); }); @@ -77,8 +85,8 @@ describe('utils', () => { }; const transformed = transformBulkError('rule-1', error); const expected: BulkError = { - id: 'rule-1', - error: { message: 'some message', statusCode: 403 }, + rule_id: 'rule-1', + error: { message: 'some message', status_code: 403 }, }; expect(transformed).toEqual(expected); }); @@ -90,8 +98,8 @@ describe('utils', () => { }; const transformed = transformBulkError('rule-1', error); const expected: BulkError = { - id: 'rule-1', - error: { message: 'some message', statusCode: 500 }, + rule_id: 'rule-1', + error: { message: 'some message', status_code: 500 }, }; expect(transformed).toEqual(expected); }); @@ -100,8 +108,168 @@ describe('utils', () => { const error: TypeError = new TypeError('I have a type error'); const transformed = transformBulkError('rule-1', error); const expected: BulkError = { - id: 'rule-1', - error: { message: 'I have a type error', statusCode: 400 }, + rule_id: 'rule-1', + error: { message: 'I have a type error', status_code: 400 }, + }; + expect(transformed).toEqual(expected); + }); + }); + + describe('createSuccessObject', () => { + test('it should increment the existing success object by 1', () => { + const success = createSuccessObject({ + success_count: 0, + success: true, + errors: [], + }); + const expected: ImportSuccessError = { + success_count: 1, + success: true, + errors: [], + }; + expect(success).toEqual(expected); + }); + + test('it should increment the existing success object by 1 and not touch the boolean or errors', () => { + const success = createSuccessObject({ + success_count: 0, + success: false, + errors: [ + { rule_id: 'rule-1', error: { status_code: 500, message: 'some sad sad sad error' } }, + ], + }); + const expected: ImportSuccessError = { + success_count: 1, + success: false, + errors: [ + { rule_id: 'rule-1', error: { status_code: 500, message: 'some sad sad sad error' } }, + ], + }; + expect(success).toEqual(expected); + }); + }); + + describe('createImportErrorObject', () => { + test('it creates an error message and does not increment the success count', () => { + const error = createImportErrorObject({ + ruleId: 'some-rule-id', + statusCode: 400, + message: 'some-message', + existingImportSuccessError: { + success_count: 1, + success: true, + errors: [], + }, + }); + const expected: ImportSuccessError = { + success_count: 1, + success: false, + errors: [{ rule_id: 'some-rule-id', error: { status_code: 400, message: 'some-message' } }], + }; + expect(error).toEqual(expected); + }); + + test('appends a second error message and does not increment the success count', () => { + const error = createImportErrorObject({ + ruleId: 'some-rule-id', + statusCode: 400, + message: 'some-message', + existingImportSuccessError: { + success_count: 1, + success: false, + errors: [ + { rule_id: 'rule-1', error: { status_code: 500, message: 'some sad sad sad error' } }, + ], + }, + }); + const expected: ImportSuccessError = { + success_count: 1, + success: false, + errors: [ + { rule_id: 'rule-1', error: { status_code: 500, message: 'some sad sad sad error' } }, + { rule_id: 'some-rule-id', error: { status_code: 400, message: 'some-message' } }, + ], + }; + expect(error).toEqual(expected); + }); + }); + + describe('transformImportError', () => { + test('returns transformed object if it is a boom object', () => { + const boom = new Boom('some boom message', { statusCode: 400 }); + const transformed = transformImportError('rule-1', boom, { + success_count: 1, + success: false, + errors: [{ rule_id: 'some-rule-id', error: { status_code: 400, message: 'some-message' } }], + }); + const expected: ImportSuccessError = { + success_count: 1, + success: false, + errors: [ + { rule_id: 'some-rule-id', error: { status_code: 400, message: 'some-message' } }, + { rule_id: 'rule-1', error: { status_code: 400, message: 'some boom message' } }, + ], + }; + expect(transformed).toEqual(expected); + }); + + test('returns a normal error if it is some non boom object that has a statusCode', () => { + const error: Error & { statusCode?: number } = { + statusCode: 403, + name: 'some name', + message: 'some message', + }; + const transformed = transformImportError('rule-1', error, { + success_count: 1, + success: false, + errors: [{ rule_id: 'some-rule-id', error: { status_code: 400, message: 'some-message' } }], + }); + const expected: ImportSuccessError = { + success_count: 1, + success: false, + errors: [ + { rule_id: 'some-rule-id', error: { status_code: 400, message: 'some-message' } }, + { rule_id: 'rule-1', error: { status_code: 403, message: 'some message' } }, + ], + }; + expect(transformed).toEqual(expected); + }); + + test('returns a 500 if the status code is not set', () => { + const error: Error & { statusCode?: number } = { + name: 'some name', + message: 'some message', + }; + const transformed = transformImportError('rule-1', error, { + success_count: 1, + success: false, + errors: [{ rule_id: 'some-rule-id', error: { status_code: 400, message: 'some-message' } }], + }); + const expected: ImportSuccessError = { + success_count: 1, + success: false, + errors: [ + { rule_id: 'some-rule-id', error: { status_code: 400, message: 'some-message' } }, + { rule_id: 'rule-1', error: { status_code: 500, message: 'some message' } }, + ], + }; + expect(transformed).toEqual(expected); + }); + + test('it detects a TypeError and returns a Boom status of 400', () => { + const error: TypeError = new TypeError('I have a type error'); + const transformed = transformImportError('rule-1', error, { + success_count: 1, + success: false, + errors: [{ rule_id: 'some-rule-id', error: { status_code: 400, message: 'some-message' } }], + }); + const expected: ImportSuccessError = { + success_count: 1, + success: false, + errors: [ + { rule_id: 'some-rule-id', error: { status_code: 400, message: 'some-message' } }, + { rule_id: 'rule-1', error: { status_code: 400, message: 'I have a type error' } }, + ], }; expect(transformed).toEqual(expected); }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.ts index d9a8efd673883..19cd972b60e1a 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/routes/utils.ts @@ -27,12 +27,13 @@ export const transformError = (err: Error & { statusCode?: number }) => { }; export interface BulkError { - id: string; + rule_id: string; error: { - statusCode: number; + status_code: number; message: string; }; } + export const createBulkErrorObject = ({ ruleId, statusCode, @@ -43,14 +44,84 @@ export const createBulkErrorObject = ({ message: string; }): BulkError => { return { - id: ruleId, + rule_id: ruleId, error: { - statusCode, + status_code: statusCode, message, }, }; }; +export interface ImportSuccessError { + success: boolean; + success_count: number; + errors: BulkError[]; +} + +export const createSuccessObject = ( + existingImportSuccessError: ImportSuccessError +): ImportSuccessError => { + return { + success_count: existingImportSuccessError.success_count + 1, + success: existingImportSuccessError.success, + errors: existingImportSuccessError.errors, + }; +}; + +export const createImportErrorObject = ({ + ruleId, + statusCode, + message, + existingImportSuccessError, +}: { + ruleId: string; + statusCode: number; + message: string; + existingImportSuccessError: ImportSuccessError; +}): ImportSuccessError => { + return { + success: false, + errors: [ + ...existingImportSuccessError.errors, + createBulkErrorObject({ + ruleId, + statusCode, + message, + }), + ], + success_count: existingImportSuccessError.success_count, + }; +}; + +export const transformImportError = ( + ruleId: string, + err: Error & { statusCode?: number }, + existingImportSuccessError: ImportSuccessError +): ImportSuccessError => { + if (Boom.isBoom(err)) { + return createImportErrorObject({ + ruleId, + statusCode: err.output.statusCode, + message: err.message, + existingImportSuccessError, + }); + } else if (err instanceof TypeError) { + return createImportErrorObject({ + ruleId, + statusCode: 400, + message: err.message, + existingImportSuccessError, + }); + } else { + return createImportErrorObject({ + ruleId, + statusCode: err.statusCode ?? 500, + message: err.message, + existingImportSuccessError, + }); + } +}; + export const transformBulkError = ( ruleId: string, err: Error & { statusCode?: number } @@ -76,13 +147,19 @@ export const transformBulkError = ( } }; -export const getIndex = (request: RequestFacade, server: ServerFacade): string => { +export const getIndex = ( + request: RequestFacade | Omit, + server: ServerFacade +): string => { const spaceId = server.plugins.spaces.getSpaceId(request); const signalsIndex = server.config().get(`xpack.${APP_ID}.${SIGNALS_INDEX_KEY}`); return `${signalsIndex}-${spaceId}`; }; -export const callWithRequestFactory = (request: RequestFacade, server: ServerFacade) => { +export const callWithRequestFactory = ( + request: RequestFacade | Omit, + server: ServerFacade +) => { const { callWithRequest } = server.plugins.elasticsearch.getCluster('data'); return (endpoint: string, params: T, options?: U) => { return callWithRequest(request, endpoint, params, options); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/create_rules.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/create_rules.ts index 07cf0b0c716cc..d2f76907d7aa3 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/create_rules.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/create_rules.ts @@ -19,6 +19,7 @@ export const createRules = async ({ language, savedId, timelineId, + timelineTitle, meta, filters, ruleId, @@ -56,6 +57,7 @@ export const createRules = async ({ outputIndex, savedId, timelineId, + timelineTitle, meta, filters, maxSignals, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/create_rules_stream_from_ndjson.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/create_rules_stream_from_ndjson.test.ts new file mode 100644 index 0000000000000..fce3c90ef18e7 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/create_rules_stream_from_ndjson.test.ts @@ -0,0 +1,375 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { Readable } from 'stream'; +import { createRulesStreamFromNdJson } from './create_rules_stream_from_ndjson'; +import { createPromiseFromStreams, createConcatStream } from 'src/legacy/utils/streams'; +import { ImportRuleAlertRest } from '../types'; + +const readStreamToCompletion = (stream: Readable) => { + return createPromiseFromStreams([stream, createConcatStream([])]); +}; + +export const getOutputSample = (): Partial => ({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', +}); + +export const getSampleAsNdjson = (sample: Partial): string => { + return `${JSON.stringify(sample)}\n`; +}; + +describe('create_rules_stream_from_ndjson', () => { + describe('createRulesStreamFromNdJson', () => { + test('transforms an ndjson stream into a stream of rule objects', async () => { + const sample1 = getOutputSample(); + const sample2 = getOutputSample(); + sample2.rule_id = 'rule-2'; + const ndJsonStream = new Readable({ + read() { + this.push(getSampleAsNdjson(sample1)); + this.push(getSampleAsNdjson(sample2)); + this.push(null); + }, + }); + const rulesObjectsStream = createRulesStreamFromNdJson(ndJsonStream, 1000); + const result = await readStreamToCompletion(rulesObjectsStream); + expect(result).toEqual([ + { + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + enabled: true, + false_positives: [], + immutable: false, + query: '', + language: 'kuery', + max_signals: 100, + tags: [], + threats: [], + references: [], + version: 1, + }, + { + rule_id: 'rule-2', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + enabled: true, + false_positives: [], + immutable: false, + query: '', + language: 'kuery', + max_signals: 100, + tags: [], + threats: [], + references: [], + version: 1, + }, + ]); + }); + + test('skips empty lines', async () => { + const sample1 = getOutputSample(); + const sample2 = getOutputSample(); + sample2.rule_id = 'rule-2'; + const ndJsonStream = new Readable({ + read() { + this.push(getSampleAsNdjson(sample1)); + this.push('\n'); + this.push(getSampleAsNdjson(sample2)); + this.push(''); + this.push(null); + }, + }); + const rulesObjectsStream = createRulesStreamFromNdJson(ndJsonStream, 1000); + const result = await readStreamToCompletion(rulesObjectsStream); + expect(result).toEqual([ + { + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + enabled: true, + false_positives: [], + immutable: false, + query: '', + language: 'kuery', + max_signals: 100, + tags: [], + threats: [], + references: [], + version: 1, + }, + { + rule_id: 'rule-2', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + enabled: true, + false_positives: [], + immutable: false, + query: '', + language: 'kuery', + max_signals: 100, + tags: [], + threats: [], + references: [], + version: 1, + }, + ]); + }); + + test('filters the export details entry from the stream', async () => { + const sample1 = getOutputSample(); + const sample2 = getOutputSample(); + sample2.rule_id = 'rule-2'; + const ndJsonStream = new Readable({ + read() { + this.push(getSampleAsNdjson(sample1)); + this.push(getSampleAsNdjson(sample2)); + this.push('{"exported_count":1,"missing_rules":[],"missing_rules_count":0}\n'); + this.push(null); + }, + }); + const rulesObjectsStream = createRulesStreamFromNdJson(ndJsonStream, 1000); + const result = await readStreamToCompletion(rulesObjectsStream); + expect(result).toEqual([ + { + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + enabled: true, + false_positives: [], + immutable: false, + query: '', + language: 'kuery', + max_signals: 100, + tags: [], + threats: [], + references: [], + version: 1, + }, + { + rule_id: 'rule-2', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + enabled: true, + false_positives: [], + immutable: false, + query: '', + language: 'kuery', + max_signals: 100, + tags: [], + threats: [], + references: [], + version: 1, + }, + ]); + }); + + test('handles non parsable JSON strings and inserts the error as part of the return array', async () => { + const sample1 = getOutputSample(); + const sample2 = getOutputSample(); + sample2.rule_id = 'rule-2'; + const ndJsonStream = new Readable({ + read() { + this.push(getSampleAsNdjson(sample1)); + this.push('{,,,,\n'); + this.push(getSampleAsNdjson(sample2)); + this.push(null); + }, + }); + const rulesObjectsStream = createRulesStreamFromNdJson(ndJsonStream, 1000); + const result = await readStreamToCompletion(rulesObjectsStream); + const resultOrError = result as Error[]; + expect(resultOrError[0]).toEqual({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + enabled: true, + false_positives: [], + immutable: false, + query: '', + language: 'kuery', + max_signals: 100, + tags: [], + threats: [], + references: [], + version: 1, + }); + expect(resultOrError[1].message).toEqual('Unexpected token , in JSON at position 1'); + expect(resultOrError[2]).toEqual({ + rule_id: 'rule-2', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + enabled: true, + false_positives: [], + immutable: false, + query: '', + language: 'kuery', + max_signals: 100, + tags: [], + threats: [], + references: [], + version: 1, + }); + }); + + test('handles non-validated data', async () => { + const sample1 = getOutputSample(); + const sample2 = getOutputSample(); + sample2.rule_id = 'rule-2'; + const ndJsonStream = new Readable({ + read() { + this.push(getSampleAsNdjson(sample1)); + this.push(`{}\n`); + this.push(getSampleAsNdjson(sample2)); + this.push(null); + }, + }); + const rulesObjectsStream = createRulesStreamFromNdJson(ndJsonStream, 1000); + const result = await readStreamToCompletion(rulesObjectsStream); + const resultOrError = result as TypeError[]; + expect(resultOrError[0]).toEqual({ + rule_id: 'rule-1', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + enabled: true, + false_positives: [], + immutable: false, + query: '', + language: 'kuery', + max_signals: 100, + tags: [], + threats: [], + references: [], + version: 1, + }); + expect(resultOrError[1].message).toEqual( + 'child "description" fails because ["description" is required]' + ); + expect(resultOrError[2]).toEqual({ + rule_id: 'rule-2', + output_index: '.siem-signals', + risk_score: 50, + description: 'some description', + from: 'now-5m', + to: 'now', + index: ['index-1'], + name: 'some-name', + severity: 'severity', + interval: '5m', + type: 'query', + enabled: true, + false_positives: [], + immutable: false, + query: '', + language: 'kuery', + max_signals: 100, + tags: [], + threats: [], + references: [], + version: 1, + }); + }); + + test('non validated data is an instanceof TypeError', async () => { + const sample1 = getOutputSample(); + const sample2 = getOutputSample(); + sample2.rule_id = 'rule-2'; + const ndJsonStream = new Readable({ + read() { + this.push(getSampleAsNdjson(sample1)); + this.push(`{}\n`); + this.push(getSampleAsNdjson(sample2)); + this.push(null); + }, + }); + const rulesObjectsStream = createRulesStreamFromNdJson(ndJsonStream, 1000); + const result = await readStreamToCompletion(rulesObjectsStream); + const resultOrError = result as TypeError[]; + expect(resultOrError[1] instanceof TypeError).toEqual(true); + }); + }); +}); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/create_rules_stream_from_ndjson.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/create_rules_stream_from_ndjson.ts new file mode 100644 index 0000000000000..6d58171a3245d --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/create_rules_stream_from_ndjson.ts @@ -0,0 +1,88 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { Readable, Transform } from 'stream'; +import { has, isString } from 'lodash/fp'; +import { ImportRuleAlertRest } from '../types'; +import { + createSplitStream, + createMapStream, + createFilterStream, + createConcatStream, +} from '../../../../../../../../src/legacy/utils/streams'; +import { importRulesSchema } from '../routes/schemas/import_rules_schema'; + +export interface RulesObjectsExportResultDetails { + /** number of successfully exported objects */ + exportedCount: number; +} + +export const parseNdjsonStrings = (): Transform => { + return createMapStream((ndJsonStr: string) => { + if (isString(ndJsonStr) && ndJsonStr.trim() !== '') { + try { + return JSON.parse(ndJsonStr); + } catch (err) { + return err; + } + } + }); +}; + +export const filterExportedCounts = (): Transform => { + return createFilterStream( + obj => obj != null && !has('exported_count', obj) + ); +}; + +export const validateRules = (): Transform => { + return createMapStream((obj: ImportRuleAlertRest) => { + if (!(obj instanceof Error)) { + const validated = importRulesSchema.validate(obj); + if (validated.error != null) { + return new TypeError(validated.error.message); + } else { + return validated.value; + } + } else { + return obj; + } + }); +}; + +// Adaptation from: saved_objects/import/create_limit_stream.ts +export const createLimitStream = (limit: number): Transform => { + let counter = 0; + return new Transform({ + objectMode: true, + async transform(obj, _, done) { + if (counter >= limit) { + return done(new Error(`Can't import more than ${limit} rules`)); + } + counter++; + done(undefined, obj); + }, + }); +}; + +// TODO: Capture both the line number and the rule_id if you have that information for the error message +// eventually and then pass it down so we can give error messages on the line number + +/** + * Inspiration and the pattern of code followed is from: + * saved_objects/lib/create_saved_objects_stream_from_ndjson.ts + */ +export const createRulesStreamFromNdJson = ( + ndJsonStream: Readable, + ruleLimit: number +): Transform => { + return ndJsonStream + .pipe(createSplitStream('\n')) + .pipe(parseNdjsonStrings()) + .pipe(filterExportedCounts()) + .pipe(validateRules()) + .pipe(createLimitStream(ruleLimit)) + .pipe(createConcatStream([])); +}; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/find_rules.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/find_rules.ts index c1058bd353e8c..e193e123f4281 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/find_rules.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/find_rules.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import { FindResult } from '../../../../../alerting/server/alerts_client'; import { SIGNALS_ID } from '../../../../common/constants'; import { FindRuleParams } from './types'; @@ -23,7 +24,7 @@ export const findRules = async ({ filter, sortField, sortOrder, -}: FindRuleParams) => { +}: FindRuleParams): Promise => { return alertsClient.find({ options: { fields, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_existing_prepackaged_rules.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_existing_prepackaged_rules.test.ts index bb28a5575f51e..dc308263baab6 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_existing_prepackaged_rules.test.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_existing_prepackaged_rules.test.ts @@ -11,7 +11,13 @@ import { getFindResultWithSingleHit, getFindResultWithMultiHits, } from '../routes/__mocks__/request_responses'; -import { getExistingPrepackagedRules } from './get_existing_prepackaged_rules'; +import { + getExistingPrepackagedRules, + getNonPackagedRules, + getRules, + getRulesCount, + getNonPackagedRulesCount, +} from './get_existing_prepackaged_rules'; describe('get_existing_prepackaged_rules', () => { afterEach(() => { @@ -33,9 +39,11 @@ describe('get_existing_prepackaged_rules', () => { const alertsClient = alertsClientMock.create(); const result1 = getResult(); + result1.params.immutable = true; result1.id = '4baa53f8-96da-44ee-ad58-41bccb7f9f3d'; const result2 = getResult(); + result2.params.immutable = true; result2.id = '5baa53f8-96da-44ee-ad58-41bccb7f9f3d'; alertsClient.find.mockResolvedValueOnce( @@ -56,12 +64,15 @@ describe('get_existing_prepackaged_rules', () => { const alertsClient = alertsClientMock.create(); const result1 = getResult(); + result1.params.immutable = true; result1.id = '4baa53f8-96da-44ee-ad58-41bccb7f9f3d'; const result2 = getResult(); + result2.params.immutable = true; result2.id = '5baa53f8-96da-44ee-ad58-41bccb7f9f3d'; const result3 = getResult(); + result3.params.immutable = true; result3.id = 'f3e1bf0b-b95f-43da-b1de-5d2f0af2287a'; alertsClient.find.mockResolvedValueOnce( @@ -87,12 +98,15 @@ describe('get_existing_prepackaged_rules', () => { const alertsClient = alertsClientMock.create(); const result1 = getResult(); + result1.params.immutable = true; result1.id = '4baa53f8-96da-44ee-ad58-41bccb7f9f3d'; const result2 = getResult(); + result2.params.immutable = true; result2.id = '5baa53f8-96da-44ee-ad58-41bccb7f9f3d'; const result3 = getResult(); + result3.params.immutable = true; result3.id = 'f3e1bf0b-b95f-43da-b1de-5d2f0af2287a'; alertsClient.find.mockResolvedValueOnce( @@ -111,4 +125,221 @@ describe('get_existing_prepackaged_rules', () => { expect(rules).toEqual([result1, result2, result3]); }); }); + + describe('getNonPackagedRules', () => { + test('should return a single item in a single page', async () => { + const alertsClient = alertsClientMock.create(); + alertsClient.find.mockResolvedValue(getFindResultWithSingleHit()); + const unsafeCast: AlertsClient = (alertsClient as unknown) as AlertsClient; + const rules = await getNonPackagedRules({ + alertsClient: unsafeCast, + }); + expect(rules).toEqual([getResult()]); + }); + + test('should return 2 items over two pages, one per page', async () => { + const alertsClient = alertsClientMock.create(); + + const result1 = getResult(); + result1.id = '4baa53f8-96da-44ee-ad58-41bccb7f9f3d'; + + const result2 = getResult(); + result2.id = '5baa53f8-96da-44ee-ad58-41bccb7f9f3d'; + + alertsClient.find.mockResolvedValueOnce( + getFindResultWithMultiHits({ data: [result1], perPage: 1, page: 1, total: 2 }) + ); + alertsClient.find.mockResolvedValueOnce( + getFindResultWithMultiHits({ data: [result2], perPage: 1, page: 2, total: 2 }) + ); + + const unsafeCast: AlertsClient = (alertsClient as unknown) as AlertsClient; + const rules = await getNonPackagedRules({ + alertsClient: unsafeCast, + }); + expect(rules).toEqual([result1, result2]); + }); + + test('should return 3 items with over 3 pages one per page', async () => { + const alertsClient = alertsClientMock.create(); + + const result1 = getResult(); + result1.id = '4baa53f8-96da-44ee-ad58-41bccb7f9f3d'; + + const result2 = getResult(); + result2.id = '5baa53f8-96da-44ee-ad58-41bccb7f9f3d'; + + const result3 = getResult(); + result3.id = 'f3e1bf0b-b95f-43da-b1de-5d2f0af2287a'; + + alertsClient.find.mockResolvedValueOnce( + getFindResultWithMultiHits({ data: [result1], perPage: 1, page: 1, total: 3 }) + ); + + alertsClient.find.mockResolvedValueOnce( + getFindResultWithMultiHits({ data: [result2], perPage: 1, page: 2, total: 3 }) + ); + + alertsClient.find.mockResolvedValueOnce( + getFindResultWithMultiHits({ data: [result3], perPage: 1, page: 2, total: 3 }) + ); + + const unsafeCast: AlertsClient = (alertsClient as unknown) as AlertsClient; + const rules = await getNonPackagedRules({ + alertsClient: unsafeCast, + }); + expect(rules).toEqual([result1, result2, result3]); + }); + + test('should return 3 items over 1 pages with all on one page', async () => { + const alertsClient = alertsClientMock.create(); + + const result1 = getResult(); + result1.id = '4baa53f8-96da-44ee-ad58-41bccb7f9f3d'; + + const result2 = getResult(); + result2.id = '5baa53f8-96da-44ee-ad58-41bccb7f9f3d'; + + const result3 = getResult(); + result3.id = 'f3e1bf0b-b95f-43da-b1de-5d2f0af2287a'; + + alertsClient.find.mockResolvedValueOnce( + getFindResultWithMultiHits({ + data: [result1, result2, result3], + perPage: 3, + page: 1, + total: 3, + }) + ); + + const unsafeCast: AlertsClient = (alertsClient as unknown) as AlertsClient; + const rules = await getNonPackagedRules({ + alertsClient: unsafeCast, + }); + expect(rules).toEqual([result1, result2, result3]); + }); + }); + + describe('getRules', () => { + test('should return a single item in a single page', async () => { + const alertsClient = alertsClientMock.create(); + alertsClient.find.mockResolvedValue(getFindResultWithSingleHit()); + const unsafeCast: AlertsClient = (alertsClient as unknown) as AlertsClient; + const rules = await getRules({ + alertsClient: unsafeCast, + filter: '', + }); + expect(rules).toEqual([getResult()]); + }); + + test('should return 2 items over two pages, one per page', async () => { + const alertsClient = alertsClientMock.create(); + + const result1 = getResult(); + result1.id = '4baa53f8-96da-44ee-ad58-41bccb7f9f3d'; + + const result2 = getResult(); + result2.id = '5baa53f8-96da-44ee-ad58-41bccb7f9f3d'; + + alertsClient.find.mockResolvedValueOnce( + getFindResultWithMultiHits({ data: [result1], perPage: 1, page: 1, total: 2 }) + ); + alertsClient.find.mockResolvedValueOnce( + getFindResultWithMultiHits({ data: [result2], perPage: 1, page: 2, total: 2 }) + ); + + const unsafeCast: AlertsClient = (alertsClient as unknown) as AlertsClient; + const rules = await getRules({ + alertsClient: unsafeCast, + filter: '', + }); + expect(rules).toEqual([result1, result2]); + }); + + test('should return 3 items with over 3 pages one per page', async () => { + const alertsClient = alertsClientMock.create(); + + const result1 = getResult(); + result1.id = '4baa53f8-96da-44ee-ad58-41bccb7f9f3d'; + + const result2 = getResult(); + result2.id = '5baa53f8-96da-44ee-ad58-41bccb7f9f3d'; + + const result3 = getResult(); + result3.id = 'f3e1bf0b-b95f-43da-b1de-5d2f0af2287a'; + + alertsClient.find.mockResolvedValueOnce( + getFindResultWithMultiHits({ data: [result1], perPage: 1, page: 1, total: 3 }) + ); + + alertsClient.find.mockResolvedValueOnce( + getFindResultWithMultiHits({ data: [result2], perPage: 1, page: 2, total: 3 }) + ); + + alertsClient.find.mockResolvedValueOnce( + getFindResultWithMultiHits({ data: [result3], perPage: 1, page: 2, total: 3 }) + ); + + const unsafeCast: AlertsClient = (alertsClient as unknown) as AlertsClient; + const rules = await getRules({ + alertsClient: unsafeCast, + filter: '', + }); + expect(rules).toEqual([result1, result2, result3]); + }); + + test('should return 3 items over 1 pages with all on one page', async () => { + const alertsClient = alertsClientMock.create(); + + const result1 = getResult(); + result1.id = '4baa53f8-96da-44ee-ad58-41bccb7f9f3d'; + + const result2 = getResult(); + result2.id = '5baa53f8-96da-44ee-ad58-41bccb7f9f3d'; + + const result3 = getResult(); + result3.id = 'f3e1bf0b-b95f-43da-b1de-5d2f0af2287a'; + + alertsClient.find.mockResolvedValueOnce( + getFindResultWithMultiHits({ + data: [result1, result2, result3], + perPage: 3, + page: 1, + total: 3, + }) + ); + + const unsafeCast: AlertsClient = (alertsClient as unknown) as AlertsClient; + const rules = await getRules({ + alertsClient: unsafeCast, + filter: '', + }); + expect(rules).toEqual([result1, result2, result3]); + }); + }); + + describe('getRulesCount', () => { + test('it returns a count', async () => { + const alertsClient = alertsClientMock.create(); + alertsClient.find.mockResolvedValue(getFindResultWithSingleHit()); + const unsafeCast: AlertsClient = (alertsClient as unknown) as AlertsClient; + const rules = await getRulesCount({ + alertsClient: unsafeCast, + filter: '', + }); + expect(rules).toEqual(1); + }); + }); + + describe('getNonPackagedRulesCount', () => { + test('it returns a count', async () => { + const alertsClient = alertsClientMock.create(); + alertsClient.find.mockResolvedValue(getFindResultWithSingleHit()); + const unsafeCast: AlertsClient = (alertsClient as unknown) as AlertsClient; + const rules = await getNonPackagedRulesCount({ + alertsClient: unsafeCast, + }); + expect(rules).toEqual(1); + }); + }); }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_existing_prepackaged_rules.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_existing_prepackaged_rules.ts index fa2e2124d0539..b7ab6a97634a8 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_existing_prepackaged_rules.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_existing_prepackaged_rules.ts @@ -9,18 +9,46 @@ import { AlertsClient } from '../../../../../alerting'; import { RuleAlertType, isAlertTypes } from './types'; import { findRules } from './find_rules'; -export const DEFAULT_PER_PAGE: number = 100; +export const DEFAULT_PER_PAGE = 100; +export const FILTER_NON_PREPACKED_RULES = `alert.attributes.tags: "${INTERNAL_IMMUTABLE_KEY}:false"`; +export const FILTER_PREPACKED_RULES = `alert.attributes.tags: "${INTERNAL_IMMUTABLE_KEY}:true"`; -export const getExistingPrepackagedRules = async ({ +export const getNonPackagedRulesCount = async ({ + alertsClient, +}: { + alertsClient: AlertsClient; +}): Promise => { + return getRulesCount({ alertsClient, filter: FILTER_NON_PREPACKED_RULES }); +}; + +export const getRulesCount = async ({ + alertsClient, + filter, +}: { + alertsClient: AlertsClient; + filter: string; +}): Promise => { + const firstRule = await findRules({ + alertsClient, + filter, + perPage: 1, + page: 1, + }); + return firstRule.total; +}; + +export const getRules = async ({ alertsClient, perPage = DEFAULT_PER_PAGE, + filter, }: { alertsClient: AlertsClient; perPage?: number; + filter: string; }): Promise => { const firstPrepackedRules = await findRules({ alertsClient, - filter: `alert.attributes.tags: "${INTERNAL_IMMUTABLE_KEY}:true"`, + filter, perPage, page: 1, }); @@ -40,7 +68,7 @@ export const getExistingPrepackagedRules = async ({ // page index starts at 2 as we already got the first page and we have more pages to go return findRules({ alertsClient, - filter: `alert.attributes.tags: "${INTERNAL_IMMUTABLE_KEY}:true"`, + filter, perPage, page: page + 2, }); @@ -58,3 +86,31 @@ export const getExistingPrepackagedRules = async ({ } } }; + +export const getNonPackagedRules = async ({ + alertsClient, + perPage = DEFAULT_PER_PAGE, +}: { + alertsClient: AlertsClient; + perPage?: number; +}): Promise => { + return getRules({ + alertsClient, + perPage, + filter: FILTER_NON_PREPACKED_RULES, + }); +}; + +export const getExistingPrepackagedRules = async ({ + alertsClient, + perPage = DEFAULT_PER_PAGE, +}: { + alertsClient: AlertsClient; + perPage?: number; +}): Promise => { + return getRules({ + alertsClient, + perPage, + filter: FILTER_PREPACKED_RULES, + }); +}; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_export_all.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_export_all.test.ts new file mode 100644 index 0000000000000..eb9756af8fde1 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_export_all.test.ts @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { alertsClientMock } from '../../../../../alerting/server/alerts_client.mock'; +import { + getResult, + getFindResultWithSingleHit, + FindHit, +} from '../routes/__mocks__/request_responses'; +import { AlertsClient } from '../../../../../alerting'; +import { getExportAll } from './get_export_all'; + +describe('getExportAll', () => { + test('it exports everything from the alerts client', async () => { + const alertsClient = alertsClientMock.create(); + alertsClient.get.mockResolvedValue(getResult()); + alertsClient.find.mockResolvedValue(getFindResultWithSingleHit()); + + const unsafeCast: AlertsClient = (alertsClient as unknown) as AlertsClient; + const exports = await getExportAll(unsafeCast); + expect(exports).toEqual({ + rulesNdjson: + '{"created_at":"2019-12-13T16:40:33.400Z","updated_at":"2019-12-13T16:40:33.400Z","created_by":"elastic","description":"Detecting root and admin users","enabled":true,"false_positives":[],"filters":[{"query":{"match_phrase":{"host.name":"some-host"}}}],"from":"now-6m","id":"04128c15-0d1b-4716-a4c5-46997ac7f3bd","immutable":false,"index":["auditbeat-*","filebeat-*","packetbeat-*","winlogbeat-*"],"interval":"5m","rule_id":"rule-1","language":"kuery","output_index":".siem-signals","max_signals":100,"risk_score":50,"name":"Detect Root/Admin Users","query":"user.name: root or user.name: admin","references":["http://www.example.com","https://ww.example.com"],"saved_id":"some-id","timeline_id":"some-timeline-id","timeline_title":"some-timeline-title","meta":{"someMeta":"someField"},"severity":"high","updated_by":"elastic","tags":[],"to":"now","type":"query","threats":[{"framework":"MITRE ATT&CK","tactic":{"id":"TA0040","name":"impact","reference":"https://attack.mitre.org/tactics/TA0040/"},"techniques":[{"id":"T1499","name":"endpoint denial of service","reference":"https://attack.mitre.org/techniques/T1499/"}]}],"version":1}\n', + exportDetails: '{"exported_count":1,"missing_rules":[],"missing_rules_count":0}\n', + }); + }); + + test('it will export empty rules', async () => { + const alertsClient = alertsClientMock.create(); + const findResult: FindHit = { + page: 1, + perPage: 1, + total: 0, + data: [], + }; + + alertsClient.find.mockResolvedValue(findResult); + + const unsafeCast: AlertsClient = (alertsClient as unknown) as AlertsClient; + const exports = await getExportAll(unsafeCast); + expect(exports).toEqual({ + rulesNdjson: '', + exportDetails: '{"exported_count":0,"missing_rules":[],"missing_rules_count":0}\n', + }); + }); +}); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_export_all.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_export_all.ts new file mode 100644 index 0000000000000..dca6eba4e6556 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_export_all.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { AlertsClient } from '../../../../../alerting'; +import { getNonPackagedRules } from './get_existing_prepackaged_rules'; +import { getExportDetailsNdjson } from './get_export_details_ndjson'; +import { transformAlertsToRules, transformRulesToNdjson } from '../routes/rules/utils'; + +export const getExportAll = async ( + alertsClient: AlertsClient +): Promise<{ + rulesNdjson: string; + exportDetails: string; +}> => { + const ruleAlertTypes = await getNonPackagedRules({ alertsClient }); + const rules = transformAlertsToRules(ruleAlertTypes); + const rulesNdjson = transformRulesToNdjson(rules); + const exportDetails = getExportDetailsNdjson(rules); + return { rulesNdjson, exportDetails }; +}; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_export_by_object_ids.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_export_by_object_ids.test.ts new file mode 100644 index 0000000000000..a861d80a66fd5 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_export_by_object_ids.test.ts @@ -0,0 +1,173 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { alertsClientMock } from '../../../../../alerting/server/alerts_client.mock'; +import { getExportByObjectIds, getRulesFromObjects, RulesErrors } from './get_export_by_object_ids'; +import { + getResult, + getFindResultWithSingleHit, + FindHit, +} from '../routes/__mocks__/request_responses'; +import { AlertsClient } from '../../../../../alerting'; + +describe('get_export_by_object_ids', () => { + describe('getExportByObjectIds', () => { + test('it exports object ids into an expected string with new line characters', async () => { + const alertsClient = alertsClientMock.create(); + alertsClient.get.mockResolvedValue(getResult()); + alertsClient.find.mockResolvedValue(getFindResultWithSingleHit()); + + const unsafeCast: AlertsClient = (alertsClient as unknown) as AlertsClient; + const objects = [{ rule_id: 'rule-1' }]; + const exports = await getExportByObjectIds(unsafeCast, objects); + expect(exports).toEqual({ + rulesNdjson: + '{"created_at":"2019-12-13T16:40:33.400Z","updated_at":"2019-12-13T16:40:33.400Z","created_by":"elastic","description":"Detecting root and admin users","enabled":true,"false_positives":[],"filters":[{"query":{"match_phrase":{"host.name":"some-host"}}}],"from":"now-6m","id":"04128c15-0d1b-4716-a4c5-46997ac7f3bd","immutable":false,"index":["auditbeat-*","filebeat-*","packetbeat-*","winlogbeat-*"],"interval":"5m","rule_id":"rule-1","language":"kuery","output_index":".siem-signals","max_signals":100,"risk_score":50,"name":"Detect Root/Admin Users","query":"user.name: root or user.name: admin","references":["http://www.example.com","https://ww.example.com"],"saved_id":"some-id","timeline_id":"some-timeline-id","timeline_title":"some-timeline-title","meta":{"someMeta":"someField"},"severity":"high","updated_by":"elastic","tags":[],"to":"now","type":"query","threats":[{"framework":"MITRE ATT&CK","tactic":{"id":"TA0040","name":"impact","reference":"https://attack.mitre.org/tactics/TA0040/"},"techniques":[{"id":"T1499","name":"endpoint denial of service","reference":"https://attack.mitre.org/techniques/T1499/"}]}],"version":1}\n', + exportDetails: '{"exported_count":1,"missing_rules":[],"missing_rules_count":0}\n', + }); + }); + + test('it does not export immutable rules', async () => { + const alertsClient = alertsClientMock.create(); + const result = getResult(); + result.params.immutable = true; + + const findResult: FindHit = { + page: 1, + perPage: 1, + total: 0, + data: [result], + }; + + alertsClient.get.mockResolvedValue(getResult()); + alertsClient.find.mockResolvedValue(findResult); + + const unsafeCast: AlertsClient = (alertsClient as unknown) as AlertsClient; + const objects = [{ rule_id: 'rule-1' }]; + const exports = await getExportByObjectIds(unsafeCast, objects); + expect(exports).toEqual({ + rulesNdjson: '', + exportDetails: + '{"exported_count":0,"missing_rules":[{"rule_id":"rule-1"}],"missing_rules_count":1}\n', + }); + }); + }); + + describe('getRulesFromObjects', () => { + test('it returns transformed rules from objects sent in', async () => { + const alertsClient = alertsClientMock.create(); + alertsClient.get.mockResolvedValue(getResult()); + alertsClient.find.mockResolvedValue(getFindResultWithSingleHit()); + + const unsafeCast: AlertsClient = (alertsClient as unknown) as AlertsClient; + const objects = [{ rule_id: 'rule-1' }]; + const exports = await getRulesFromObjects(unsafeCast, objects); + const expected: RulesErrors = { + missingRules: [], + rules: [ + { + created_at: '2019-12-13T16:40:33.400Z', + updated_at: '2019-12-13T16:40:33.400Z', + created_by: 'elastic', + description: 'Detecting root and admin users', + enabled: true, + false_positives: [], + filters: [{ query: { match_phrase: { 'host.name': 'some-host' } } }], + from: 'now-6m', + id: '04128c15-0d1b-4716-a4c5-46997ac7f3bd', + immutable: false, + index: ['auditbeat-*', 'filebeat-*', 'packetbeat-*', 'winlogbeat-*'], + interval: '5m', + rule_id: 'rule-1', + language: 'kuery', + output_index: '.siem-signals', + max_signals: 100, + risk_score: 50, + name: 'Detect Root/Admin Users', + query: 'user.name: root or user.name: admin', + references: ['http://www.example.com', 'https://ww.example.com'], + saved_id: 'some-id', + timeline_id: 'some-timeline-id', + timeline_title: 'some-timeline-title', + meta: { someMeta: 'someField' }, + severity: 'high', + updated_by: 'elastic', + tags: [], + to: 'now', + type: 'query', + threats: [ + { + framework: 'MITRE ATT&CK', + tactic: { + id: 'TA0040', + name: 'impact', + reference: 'https://attack.mitre.org/tactics/TA0040/', + }, + techniques: [ + { + id: 'T1499', + name: 'endpoint denial of service', + reference: 'https://attack.mitre.org/techniques/T1499/', + }, + ], + }, + ], + version: 1, + }, + ], + }; + expect(exports).toEqual(expected); + }); + + test('it does not transform the rule if the rule is an immutable rule and designates it as a missing rule', async () => { + const alertsClient = alertsClientMock.create(); + const result = getResult(); + result.params.immutable = true; + + const findResult: FindHit = { + page: 1, + perPage: 1, + total: 0, + data: [result], + }; + + alertsClient.get.mockResolvedValue(result); + alertsClient.find.mockResolvedValue(findResult); + + const unsafeCast: AlertsClient = (alertsClient as unknown) as AlertsClient; + const objects = [{ rule_id: 'rule-1' }]; + const exports = await getRulesFromObjects(unsafeCast, objects); + const expected: RulesErrors = { + missingRules: [{ rule_id: 'rule-1' }], + rules: [], + }; + expect(exports).toEqual(expected); + }); + + test('it exports missing rules', async () => { + const alertsClient = alertsClientMock.create(); + + const findResult: FindHit = { + page: 1, + perPage: 1, + total: 0, + data: [], + }; + + alertsClient.get.mockRejectedValue({ output: { statusCode: 404 } }); + alertsClient.find.mockResolvedValue(findResult); + + const unsafeCast: AlertsClient = (alertsClient as unknown) as AlertsClient; + const objects = [{ rule_id: 'rule-1' }]; + const exports = await getRulesFromObjects(unsafeCast, objects); + const expected: RulesErrors = { + missingRules: [{ rule_id: 'rule-1' }], + rules: [], + }; + expect(exports).toEqual(expected); + }); + }); +}); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_export_by_object_ids.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_export_by_object_ids.ts new file mode 100644 index 0000000000000..a5cf1bbfb7858 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_export_by_object_ids.ts @@ -0,0 +1,60 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { AlertsClient } from '../../../../../alerting'; +import { getExportDetailsNdjson } from './get_export_details_ndjson'; +import { isAlertType } from '../rules/types'; +import { readRules } from './read_rules'; +import { transformRulesToNdjson, transformAlertToRule } from '../routes/rules/utils'; +import { OutputRuleAlertRest } from '../types'; + +export interface RulesErrors { + missingRules: Array<{ rule_id: string }>; + rules: Array>; +} + +export const getExportByObjectIds = async ( + alertsClient: AlertsClient, + objects: Array<{ rule_id: string }> +): Promise<{ + rulesNdjson: string; + exportDetails: string; +}> => { + const rulesAndErrors = await getRulesFromObjects(alertsClient, objects); + const rulesNdjson = transformRulesToNdjson(rulesAndErrors.rules); + const exportDetails = getExportDetailsNdjson(rulesAndErrors.rules, rulesAndErrors.missingRules); + return { rulesNdjson, exportDetails }; +}; + +export const getRulesFromObjects = async ( + alertsClient: AlertsClient, + objects: Array<{ rule_id: string }> +): Promise => { + const alertsAndErrors = await objects.reduce>( + async (accumPromise, object) => { + const accum = await accumPromise; + const rule = await readRules({ alertsClient, ruleId: object.rule_id }); + if (rule != null && isAlertType(rule) && rule.params.immutable !== true) { + const transformedRule = transformAlertToRule(rule); + return { + missingRules: accum.missingRules, + rules: [...accum.rules, transformedRule], + }; + } else { + return { + missingRules: [...accum.missingRules, { rule_id: object.rule_id }], + rules: accum.rules, + }; + } + }, + Promise.resolve({ + exportedCount: 0, + missingRules: [], + rules: [], + }) + ); + return alertsAndErrors; +}; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_export_details_ndjson.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_export_details_ndjson.test.ts new file mode 100644 index 0000000000000..431b3776fd9e2 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_export_details_ndjson.test.ts @@ -0,0 +1,56 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { sampleRule } from '../signals/__mocks__/es_results'; +import { getExportDetailsNdjson } from './get_export_details_ndjson'; + +describe('getExportDetailsNdjson', () => { + test('it ends with a new line character', () => { + const rule = sampleRule(); + const details = getExportDetailsNdjson([rule]); + expect(details.endsWith('\n')).toEqual(true); + }); + + test('it exports a correct count given a single rule and no missing rules', () => { + const rule = sampleRule(); + const details = getExportDetailsNdjson([rule]); + const reParsed = JSON.parse(details); + expect(reParsed).toEqual({ + exported_count: 1, + missing_rules: [], + missing_rules_count: 0, + }); + }); + + test('it exports a correct count given a no rules and a single missing rule', () => { + const missingRule = { rule_id: 'rule-1' }; + const details = getExportDetailsNdjson([], [missingRule]); + const reParsed = JSON.parse(details); + expect(reParsed).toEqual({ + exported_count: 0, + missing_rules: [{ rule_id: 'rule-1' }], + missing_rules_count: 1, + }); + }); + + test('it exports a correct count given multiple rules and multiple missing rules', () => { + const rule1 = sampleRule(); + const rule2 = sampleRule(); + rule2.rule_id = 'some other id'; + rule2.id = 'some other id'; + + const missingRule1 = { rule_id: 'rule-1' }; + const missingRule2 = { rule_id: 'rule-2' }; + + const details = getExportDetailsNdjson([rule1, rule2], [missingRule1, missingRule2]); + const reParsed = JSON.parse(details); + expect(reParsed).toEqual({ + exported_count: 2, + missing_rules: [missingRule1, missingRule2], + missing_rules_count: 2, + }); + }); +}); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_export_details_ndjson.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_export_details_ndjson.ts new file mode 100644 index 0000000000000..a39541d044bc3 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/get_export_details_ndjson.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { OutputRuleAlertRest } from '../types'; + +export const getExportDetailsNdjson = ( + rules: Array>, + missingRules: Array<{ rule_id: string }> = [] +): string => { + const stringified = JSON.stringify({ + exported_count: rules.length, + missing_rules: missingRules, + missing_rules_count: missingRules.length, + }); + return `${stringified}\n`; +}; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/install_prepacked_rules.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/install_prepacked_rules.ts index 9acfbf8c43221..9c3be64f71a0d 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/install_prepacked_rules.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/install_prepacked_rules.ts @@ -26,6 +26,7 @@ export const installPrepackagedRules = async ( language, saved_id: savedId, timeline_id: timelineId, + timeline_title: timelineTitle, meta, filters, rule_id: ruleId, @@ -55,6 +56,7 @@ export const installPrepackagedRules = async ( outputIndex, savedId, timelineId, + timelineTitle, meta, filters, ruleId, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/index.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/index.ts index 4e370bfdc5bc9..49b3c5d6802b4 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/index.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/index.ts @@ -283,6 +283,7 @@ import rule273 from './splunk_detect_use_of_cmdexe_to_launch_script_interpreters import rule274 from './splunk_child_processes_of_spoolsvexe.json'; import rule275 from './splunk_detect_psexec_with_accepteula_flag.json'; import rule276 from './splunk_processes_created_by_netsh.json'; +import rule277 from './process_execution_via_wmi.json'; export const rawRules = [ rule1, @@ -561,4 +562,5 @@ export const rawRules = [ rule274, rule275, rule276, + rule277, ]; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_irc_internet_relay_chat_protocol_activity_to_the_internet.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_irc_internet_relay_chat_protocol_activity_to_the_internet.json index dee04ee4fea8a..00976ea21cd44 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_irc_internet_relay_chat_protocol_activity_to_the_internet.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_irc_internet_relay_chat_protocol_activity_to_the_internet.json @@ -9,7 +9,7 @@ "type": "query", "from": "now-6m", "to": "now", - "query": "(destination.port:6665 or destination.port:6666 or destination.port:6667 or destination.port:6668 or destination.port:6669) and not destination.ip:10.0.0.0/8 and not destination.ip:172.16.0.0/12 and not destination.ip:192.168.0.0/16", + "query": "(destination.port:20 or destination.port:21) and not destination.ip:10.0.0.0/8 and not destination.ip:172.16.0.0/12 and not destination.ip:192.168.0.0/16", "language": "kuery", "filters": [], "enabled": false, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_rdp_remote_desktop_protocol_from_the_internet.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_rdp_remote_desktop_protocol_from_the_internet.json index c5a16bfef7248..69383d91ccbb9 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_rdp_remote_desktop_protocol_from_the_internet.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/network_rdp_remote_desktop_protocol_from_the_internet.json @@ -9,7 +9,7 @@ "type": "query", "from": "now-6m", "to": "now", - "query": "destination.port:3389 and not source.ip:10.0.0.0/8 and not source.ip:172.16.0.0/12 and not source.ip:192.168.0.0/16", + "query": "(destination.port:8080 or destination.port:3128) and not destination.ip:10.0.0.0/8 and not destination.ip:172.16.0.0/12 and not destination.ip:192.168.0.0/16", "language": "kuery", "filters": [], "enabled": false, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/process_execution_via_wmi.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/process_execution_via_wmi.json new file mode 100644 index 0000000000000..d6743c1ead4ac --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/prepackaged_rules/process_execution_via_wmi.json @@ -0,0 +1,17 @@ +{ + "rule_id": "14ba7cd9-1489-459b-99a4-153c7a3f9abb", + "risk_score": 50, + "description": "Process Execution via WMI", + "immutable": true, + "interval": "5m", + "name": "Process Execution via WMI", + "severity": "low", + "type": "query", + "from": "now-6m", + "to": "now", + "query": "process.name:scrcons.exe", + "language": "kuery", + "filters": [], + "enabled": false, + "version": 1 +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/read_rules.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/read_rules.ts index 9c83ae924486d..e8fa0b562bd24 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/read_rules.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/read_rules.ts @@ -4,9 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ +import { Alert } from '../../../../../alerting/server/types'; import { INTERNAL_RULE_ID_KEY } from '../../../../common/constants'; import { findRules } from './find_rules'; -import { RuleAlertType, ReadRuleParams, isAlertType } from './types'; +import { ReadRuleParams, isAlertType } from './types'; /** * This reads the rules through a cascade try of what is fastest to what is slowest. @@ -20,7 +21,7 @@ export const readRules = async ({ alertsClient, id, ruleId, -}: ReadRuleParams): Promise => { +}: ReadRuleParams): Promise => { if (id != null) { try { const rule = await alertsClient.get({ id }); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/types.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/types.ts index b0578174e1f65..4d9073e4b38d9 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/types.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/types.ts @@ -5,6 +5,7 @@ */ import { get } from 'lodash/fp'; +import { Readable } from 'stream'; import { SIGNALS_ID } from '../../../../common/constants'; import { AlertsClient } from '../../../../../alerting/server/alerts_client'; @@ -35,10 +36,9 @@ export interface BulkUpdateRulesRequest extends RequestFacade { payload: UpdateRuleAlertParamsRest[]; } -export type RuleAlertType = Alert & { - id: string; +export interface RuleAlertType extends Alert { params: RuleTypeParams; -}; +} export interface RulesRequest extends RequestFacade { payload: RuleAlertParamsRest; @@ -48,6 +48,24 @@ export interface BulkRulesRequest extends RequestFacade { payload: RuleAlertParamsRest[]; } +export interface HapiReadableStream extends Readable { + hapi: { + filename: string; + }; +} +export interface ImportRulesRequest extends Omit { + query: { overwrite: boolean }; + payload: { file: HapiReadableStream }; +} + +export interface ExportRulesRequest extends Omit { + payload: { objects: Array<{ rule_id: string }> | null | undefined }; + query: { + file_name: string; + exclude_export_details: boolean; + }; +} + export type QueryRequest = Omit & { query: { id: string | undefined; rule_id: string | undefined }; }; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/update_rules.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/update_rules.ts index c9dac82b6eb8f..0fe4b15437af8 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/update_rules.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/rules/update_rules.ts @@ -74,6 +74,7 @@ export const updateRules = async ({ outputIndex, savedId, timelineId, + timelineTitle, meta, filters, from, @@ -118,6 +119,7 @@ export const updateRules = async ({ outputIndex, savedId, timelineId, + timelineTitle, meta, filters, index, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/export_rules.sh b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/export_rules.sh new file mode 100755 index 0000000000000..b46b5a0e80639 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/export_rules.sh @@ -0,0 +1,25 @@ +#!/bin/sh + +# +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License; +# you may not use this file except in compliance with the Elastic License. +# + +set -e +./check_env_variables.sh + +EXCLUDE_DETAILS=${1:-false} + +# Note: This file does not use jq on purpose for testing and pipe redirections + +# Example get all the rules except pre-packaged rules +# ./export_rules.sh + +# Example get the export details at the end +# ./export_rules.sh false + +curl -s -k \ + -H 'kbn-xsrf: 123' \ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ + -X POST ${KIBANA_URL}${SPACE_URL}/api/detection_engine/rules/_export?exclude_export_details=${EXCLUDE_DETAILS} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/export_rules_by_rule_id.sh b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/export_rules_by_rule_id.sh new file mode 100755 index 0000000000000..bed9753b1b6f9 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/export_rules_by_rule_id.sh @@ -0,0 +1,29 @@ +#!/bin/sh + +# +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License; +# you may not use this file except in compliance with the Elastic License. +# + +set -e +./check_env_variables.sh + + +# Uses a default if no argument is specified +RULES=${1:-./rules/export/ruleid_queries.json} +EXCLUDE_DETAILS=${2:-false} + +# Note: This file does not use jq on purpose for testing and pipe redirections + +# Example get all the rules except pre-packaged rules +# ./export_rules_by_rule_id.sh + +# Example get the export details at the end +# ./export_rules_by_rule_id.sh ./rules/export/ruleid_queries.json false +curl -s -k \ + -H 'Content-Type: application/json' \ + -H 'kbn-xsrf: 123' \ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ + -X POST ${KIBANA_URL}${SPACE_URL}/api/detection_engine/rules/_export?exclude_export_details=${EXCLUDE_DETAILS} \ + -d @${RULES} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/export_rules_by_rule_id_to_file.sh b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/export_rules_by_rule_id_to_file.sh new file mode 100755 index 0000000000000..614024996cb39 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/export_rules_by_rule_id_to_file.sh @@ -0,0 +1,27 @@ +#!/bin/sh + +# +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License; +# you may not use this file except in compliance with the Elastic License. +# + +set -e +./check_env_variables.sh + +# Uses a defaults if no arguments are specified +RULES=${1:-./rules/export/ruleid_queries.json} +FILENAME=${2:-test.ndjson} +EXCLUDE_DETAILS=${3:-false} + +# Example export to the file named test.ndjson +# ./export_rules_by_rule_id_to_file.sh + +# Example export to the file named test.ndjson with export details appended +# ./export_rules_by_rule_id_to_file.sh ./rules/export/ruleid_queries.json test.ndjson false +curl -s -k -OJ \ + -H 'Content-Type: application/json' \ + -H 'kbn-xsrf: 123' \ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ + -X POST "${KIBANA_URL}${SPACE_URL}/api/detection_engine/rules/_export?exclude_export_details=${EXCLUDE_DETAILS}&file_name=${FILENAME}" \ + -d @${RULES} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/export_rules_to_file.sh b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/export_rules_to_file.sh new file mode 100755 index 0000000000000..a45e8036d004e --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/export_rules_to_file.sh @@ -0,0 +1,23 @@ +#!/bin/sh + +# +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License; +# you may not use this file except in compliance with the Elastic License. +# + +set -e +./check_env_variables.sh + +FILENAME=${1:-test.ndjson} +EXCLUDE_DETAILS=${2:-false} + +# Example export to the file named test.ndjson +# ./export_rules_to_file.sh + +# Example export to the file named test.ndjson with export details appended +# ./export_rules_to_file.sh test.ndjson false +curl -s -k -OJ \ + -H 'kbn-xsrf: 123' \ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ + -X POST "${KIBANA_URL}${SPACE_URL}/api/detection_engine/rules/_export?exclude_export_details=${EXCLUDE_DETAILS}&file_name=${FILENAME}" diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/import_rules.sh b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/import_rules.sh new file mode 100755 index 0000000000000..5bb9ec589a7e3 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/import_rules.sh @@ -0,0 +1,26 @@ +#!/bin/sh + +# +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License; +# you may not use this file except in compliance with the Elastic License. +# + +set -e +./check_env_variables.sh + +# Uses a defaults if no argument is specified +RULES=${1:-./rules/import/multiple_ruleid_queries.ndjson} +OVERWRITE=${2:-true} + +# Example to import and overwrite everything from ./rules/import/multiple_ruleid_queries.ndjson +# ./import_rules.sh + +# Example to not overwrite everything if it exists from ./rules/import/multiple_ruleid_queries.ndjson +# ./import_rules.sh ./rules/import/multiple_ruleid_queries.ndjson false +curl -s -k \ + -H 'kbn-xsrf: 123' \ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ + -X POST "${KIBANA_URL}${SPACE_URL}/api/detection_engine/rules/_import?overwrite=${OVERWRITE}" \ + --form file=@${RULES} \ + | jq .; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/import_rules_no_overwrite.sh b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/import_rules_no_overwrite.sh new file mode 100755 index 0000000000000..dfc58bb5c1ab8 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/import_rules_no_overwrite.sh @@ -0,0 +1,21 @@ +#!/bin/sh + +# +# Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one +# or more contributor license agreements. Licensed under the Elastic License; +# you may not use this file except in compliance with the Elastic License. +# + +set -e +./check_env_variables.sh + +# Uses a default if no argument is specified +RULES=${1:-./rules/import/multiple_ruleid_queries.ndjson} + +# Example: ./import_rules_no_overwrite.sh +curl -s -k \ + -H 'kbn-xsrf: 123' \ + -u ${ELASTICSEARCH_USERNAME}:${ELASTICSEARCH_PASSWORD} \ + -X POST ${KIBANA_URL}${SPACE_URL}/api/detection_engine/rules/_import \ + --form file=@${RULES} \ + | jq .; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/export/ruleid_queries.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/export/ruleid_queries.json new file mode 100644 index 0000000000000..fabc37d9f5766 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/export/ruleid_queries.json @@ -0,0 +1,10 @@ +{ + "objects": [ + { + "rule_id": "query-rule-id-1" + }, + { + "rule_id": "query-rule-id-2" + } + ] +} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/import/multiple_ruleid_queries.ndjson b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/import/multiple_ruleid_queries.ndjson new file mode 100644 index 0000000000000..a9de8b1e475a3 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/import/multiple_ruleid_queries.ndjson @@ -0,0 +1,3 @@ +{"created_at":"2020-01-09T01:38:00.740Z","updated_at":"2020-01-09T01:38:00.740Z","created_by":"elastic_kibana","description":"Query with a rule_id that acts like an external id","enabled":true,"false_positives":[],"from":"now-6m","id":"6688f367-1aa2-4895-a5a8-b3701eecf57d","immutable":false,"interval":"5m","rule_id":"query-rule-id-1","language":"kuery","output_index":".siem-signals-frank-hassanabad-default","max_signals":100,"risk_score":1,"name":"Query with a rule id Number 1","query":"user.name: root or user.name: admin","references":[],"severity":"high","updated_by":"elastic_kibana","tags":[],"to":"now","type":"query","threats":[],"version":1} +{"created_at":"2020-01-09T01:38:00.745Z","updated_at":"2020-01-09T01:38:00.745Z","created_by":"elastic_kibana","description":"Query with a rule_id that acts like an external id","enabled":true,"false_positives":[],"from":"now-6m","id":"7a912444-6cfa-4c8f-83f4-2b26fb2a2ed9","immutable":false,"interval":"5m","rule_id":"query-rule-id-2","language":"kuery","output_index":".siem-signals-frank-hassanabad-default","max_signals":100,"risk_score":2,"name":"Query with a rule id Number 2","query":"user.name: root or user.name: admin","references":[],"severity":"low","updated_by":"elastic_kibana","tags":[],"to":"now","type":"query","threats":[],"version":1} +{"exported_count":2,"missing_rules":[],"missing_rules_count":0} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_timelineid.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_timelineid.json index 2f995029447ff..eb87a14e0c688 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_timelineid.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_timelineid.json @@ -7,5 +7,6 @@ "from": "now-6m", "to": "now", "query": "user.name: root or user.name: admin", - "timeline_id": "timeline-id" + "timeline_id": "timeline-id", + "timeline_title": "timeline_title" } diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_everything.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_everything.json index 60095a0a6a833..e9d955f920571 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_everything.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/queries/query_with_everything.json @@ -21,7 +21,7 @@ } ], "enabled": false, - "immutable": true, + "immutable": false, "index": ["auditbeat-*", "filebeat-*"], "interval": "5m", "query": "user.name: root or user.name: admin", @@ -78,5 +78,6 @@ "Some plain text string here explaining why this is a valid thing to look out for" ], "timeline_id": "timeline_id", + "timeline_title": "timeline_title", "version": 1 } diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_with_everything.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_with_everything.json index 2628b69eb064d..16d5d6cc2b36a 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_with_everything.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/saved_queries/saved_query_with_everything.json @@ -78,5 +78,6 @@ "Some plain text string here explaining why this is a valid thing to look out for" ], "saved_id": "test-saved-id", - "timeline_id": "test-timeline-id" + "timeline_id": "test-timeline-id", + "timeline_title": "test-timeline-title" } diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/multiple_ruleid_queries_corrupted.ndjson b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/multiple_ruleid_queries_corrupted.ndjson new file mode 100644 index 0000000000000..94fc36ef6f7bf --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/test_cases/multiple_ruleid_queries_corrupted.ndjson @@ -0,0 +1,4 @@ +{"created_at":"2020-01-09T01:38:00.740Z","updated_at":"2020-01-09T01:38:00.740Z","created_by":"elastic_kibana","description":"Query with a rule_id that acts like an external id","enabled":true,"false_positives":[],"from":"now-6m","id":"6688f367-1aa2-4895-a5a8-b3701eecf57d","immutable":false,"interval":"5m","rule_id":"query-rule-id-1","language":"kuery","output_index":".siem-signals-frank-hassanabad-default","max_signals":100,"risk_score":1,"name":"Query with a rule id Number 1","query":"user.name: root or user.name: admin","references":[],"severity":"high","updated_by":"elastic_kibana","tags":[],"to":"now","type":"query","threats":[],"version":1}, +{"created_at":"2020-01-09T01:38:00.745Z","updated_at":"2020-01-09T01:38:00.745Z","created_by":"elastic_kibana","enabled":true,"false_positives":[],"from":"now-6m","id":"7a912444-6cfa-4c8f-83f4-2b26fb2a2ed9","immutable":false,"interval":"5m","rule_id":"query-rule-id-2","language":"kuery","output_index":".siem-signals-frank-hassanabad-default","max_signals":100,"risk_score":2,"name":"Query with a rule id Number 2","query":"user.name: root or user.name: admin","references":[],"severity":"low","updated_by":"elastic_kibana","tags":[],"to":"now","type":"query","threats":[],"version":1} +{"created_at":"2020-01-09T01:38:00.745Z","updated_at":"2020-01-09T01:38:00.745Z","created_by":"elastic_kibana","description":"Query with a rule_id that acts like an external id","enabled":true,"false_positives":[],"from":"now-6m","id":"7a912444-6cfa-4c8f-83f4-2b26fb2a2ed9","immutable":false,"interval":"5m","rule_id":"query-rule-id-3","language":"kuery","output_index":".siem-signals-frank-hassanabad-default","max_signals":100,"risk_score":2,"name":"Query with a rule id Number 2","query":"user.name: root or user.name: admin","references":[],"severity":"low","updated_by":"elastic_kibana","tags":[],"to":"now","type":"query","threats":[],"version":1} +{"exported_count":2,"missing_rules":[],"missing_rules_count":0} diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/update_query_everything.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/update_query_everything.json index 4da285e5b09bf..7fc8de9fe8f9e 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/update_query_everything.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/update_query_everything.json @@ -78,5 +78,6 @@ "Some plain text string here explaining why this is a valid thing to look out for" ], "timeline_id": "other-timeline-id", + "timeline_title": "other-timeline-title", "version": 42 } diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/update_timelineid.json b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/update_timelineid.json index 8cfa3303f54a6..27dee7dd81463 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/update_timelineid.json +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/scripts/rules/updates/update_timelineid.json @@ -1,4 +1,5 @@ { "rule_id": "query-rule-id", - "timeline_id": "other-timeline-id" + "timeline_id": "other-timeline-id", + "timeline_title": "other-timeline-title" } diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/__mocks__/es_results.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/__mocks__/es_results.ts index 5e50b65b51717..ede82a597b238 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/__mocks__/es_results.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/__mocks__/es_results.ts @@ -31,6 +31,7 @@ export const sampleRuleAlertParams = ( filters: undefined, savedId: undefined, timelineId: undefined, + timelineTitle: undefined, meta: undefined, threats: undefined, version: 1, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_rule.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_rule.ts index 0a3526d32e511..1093ff3a8a462 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_rule.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/build_rule.ts @@ -34,6 +34,7 @@ export const buildRule = ({ false_positives: ruleParams.falsePositives, saved_id: ruleParams.savedId, timeline_id: ruleParams.timelineId, + timeline_title: ruleParams.timelineTitle, meta: ruleParams.meta, max_signals: ruleParams.maxSignals, risk_score: ruleParams.riskScore, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.ts index 87d31abbc5371..774afb6d7deb0 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/signal_rule_alert_type.ts @@ -6,6 +6,7 @@ import { schema } from '@kbn/config-schema'; import { Logger } from 'src/core/server'; +import moment from 'moment'; import { SIGNALS_ID, DEFAULT_MAX_SIGNALS, @@ -17,6 +18,7 @@ import { getInputIndex } from './get_input_output_index'; import { searchAfterAndBulkCreate } from './search_after_bulk_create'; import { getFilter } from './get_filter'; import { SignalRuleAlertTypeDefinition } from './types'; +import { getGapBetweenRuns } from './utils'; export const signalRulesAlertType = ({ logger, @@ -42,6 +44,7 @@ export const signalRulesAlertType = ({ outputIndex: schema.nullable(schema.string()), savedId: schema.nullable(schema.string()), timelineId: schema.nullable(schema.string()), + timelineTitle: schema.nullable(schema.string()), meta: schema.nullable(schema.object({}, { allowUnknowns: true })), query: schema.nullable(schema.string()), filters: schema.nullable(schema.arrayOf(schema.object({}, { allowUnknowns: true }))), @@ -56,7 +59,8 @@ export const signalRulesAlertType = ({ version: schema.number({ defaultValue: 1 }), }), }, - async executor({ alertId, services, params }) { + // fun fact: previousStartedAt is not actually a Date but a String of a date + async executor({ previousStartedAt, alertId, services, params }) { const { from, ruleId, @@ -69,7 +73,6 @@ export const signalRulesAlertType = ({ to, type, } = params; - // TODO: Remove this hard extraction of name once this is fixed: https://github.com/elastic/kibana/issues/50522 const savedObject = await services.savedObjectsClient.get('alert', alertId); const name: string = savedObject.attributes.name; @@ -77,9 +80,19 @@ export const signalRulesAlertType = ({ const createdBy: string = savedObject.attributes.createdBy; const updatedBy: string = savedObject.attributes.updatedBy; - const interval: string = savedObject.attributes.interval; + const interval: string = savedObject.attributes.schedule.interval; const enabled: boolean = savedObject.attributes.enabled; - + const gap = getGapBetweenRuns({ + previousStartedAt: previousStartedAt != null ? moment(previousStartedAt) : null, // TODO: Remove this once previousStartedAt is no longer a string + interval, + from, + to, + }); + if (gap != null && gap.asMilliseconds() > 0) { + logger.warn( + `Signal rule name: "${name}", id: "${alertId}", rule_id: "${ruleId}" has a time gap of ${gap.humanize()} (${gap.asMilliseconds()}ms), and could be missing signals within that time. Consider increasing your look behind time or adding more Kibana instances.` + ); + } // set searchAfter page size to be the lesser of default page size or maxSignals. const searchAfterSize = DEFAULT_SEARCH_AFTER_PAGE_SIZE <= params.maxSignals @@ -154,7 +167,7 @@ export const signalRulesAlertType = ({ // TODO: Error handling and writing of errors into a signal that has error // handling/conditions logger.error( - `Error from signal rule name: "${name}", id: "${alertId}", rule_id: "${ruleId}"` + `Error from signal rule name: "${name}", id: "${alertId}", rule_id: "${ruleId}" message: ${err.message}` ); } }, diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/utils.test.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/utils.test.ts new file mode 100644 index 0000000000000..d6a3da5a393f8 --- /dev/null +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/utils.test.ts @@ -0,0 +1,258 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import moment from 'moment'; + +import { generateId, parseInterval, getDriftTolerance, getGapBetweenRuns } from './utils'; + +describe('utils', () => { + let nowDate = moment('2020-01-01T00:00:00.000Z'); + + beforeEach(() => { + nowDate = moment('2020-01-01T00:00:00.000Z'); + }); + + describe('generateId', () => { + test('it generates expected output', () => { + const id = generateId('index-123', 'doc-123', 'version-123', 'rule-123'); + expect(id).toEqual('10622e7d06c9e38a532e71fc90e3426c1100001fb617aec8cb974075da52db06'); + }); + + test('expected output is a hex', () => { + const id = generateId('index-123', 'doc-123', 'version-123', 'rule-123'); + expect(id).toMatch(/[a-f0-9]+/); + }); + }); + + describe('getIntervalMilliseconds', () => { + test('it returns a duration when given one that is valid', () => { + const duration = parseInterval('5m'); + expect(duration).not.toBeNull(); + expect(duration?.asMilliseconds()).toEqual(moment.duration(5, 'minutes').asMilliseconds()); + }); + + test('it returns null given an invalid duration', () => { + const duration = parseInterval('junk'); + expect(duration).toBeNull(); + }); + }); + + describe('getDriftToleranceMilliseconds', () => { + test('it returns a drift tolerance in milliseconds of 1 minute when from overlaps to by 1 minute and the interval is 5 minutes', () => { + const drift = getDriftTolerance({ + from: 'now-6m', + to: 'now', + interval: moment.duration(5, 'minutes'), + }); + expect(drift).not.toBeNull(); + expect(drift?.asMilliseconds()).toEqual(moment.duration(1, 'minute').asMilliseconds()); + }); + + test('it returns a drift tolerance of 0 when from equals the interval', () => { + const drift = getDriftTolerance({ + from: 'now-5m', + to: 'now', + interval: moment.duration(5, 'minutes'), + }); + expect(drift?.asMilliseconds()).toEqual(0); + }); + + test('it returns a drift tolerance of 5 minutes when from is 10 minutes but the interval is 5 minutes', () => { + const drift = getDriftTolerance({ + from: 'now-10m', + to: 'now', + interval: moment.duration(5, 'minutes'), + }); + expect(drift).not.toBeNull(); + expect(drift?.asMilliseconds()).toEqual(moment.duration(5, 'minutes').asMilliseconds()); + }); + + test('it returns a drift tolerance of 10 minutes when from is 10 minutes ago and the interval is 0', () => { + const drift = getDriftTolerance({ + from: 'now-10m', + to: 'now', + interval: moment.duration(0, 'milliseconds'), + }); + expect(drift).not.toBeNull(); + expect(drift?.asMilliseconds()).toEqual(moment.duration(10, 'minutes').asMilliseconds()); + }); + + test('returns null if the "to" is not "now" since we have limited support for date math', () => { + const drift = getDriftTolerance({ + from: 'now-6m', + to: 'invalid', // if not set to "now" this function returns null + interval: moment.duration(1000, 'milliseconds'), + }); + expect(drift).toBeNull(); + }); + + test('returns null if the "from" does not start with "now-" since we have limited support for date math', () => { + const drift = getDriftTolerance({ + from: 'valid', // if not set to "now-x" where x is an interval such as 6m + to: 'now', + interval: moment.duration(1000, 'milliseconds'), + }); + expect(drift).toBeNull(); + }); + + test('returns null if the "from" starts with "now-" but has a string instead of an integer', () => { + const drift = getDriftTolerance({ + from: 'now-dfdf', // if not set to "now-x" where x is an interval such as 6m + to: 'now', + interval: moment.duration(1000, 'milliseconds'), + }); + expect(drift).toBeNull(); + }); + }); + + describe('getGapBetweenRuns', () => { + test('it returns a gap of 0 when from and interval match each other and the previous started was from the previous interval time', () => { + const gap = getGapBetweenRuns({ + previousStartedAt: nowDate.clone().subtract(5, 'minutes'), + interval: '5m', + from: 'now-5m', + to: 'now', + now: nowDate.clone(), + }); + expect(gap).not.toBeNull(); + expect(gap?.asMilliseconds()).toEqual(0); + }); + + test('it returns a negative gap of 1 minute when from overlaps to by 1 minute and the previousStartedAt was 5 minutes ago', () => { + const gap = getGapBetweenRuns({ + previousStartedAt: nowDate.clone().subtract(5, 'minutes'), + interval: '5m', + from: 'now-6m', + to: 'now', + now: nowDate.clone(), + }); + expect(gap).not.toBeNull(); + expect(gap?.asMilliseconds()).toEqual(moment.duration(-1, 'minute').asMilliseconds()); + }); + + test('it returns a negative gap of 5 minutes when from overlaps to by 1 minute and the previousStartedAt was 5 minutes ago', () => { + const gap = getGapBetweenRuns({ + previousStartedAt: nowDate.clone().subtract(5, 'minutes'), + interval: '5m', + from: 'now-10m', + to: 'now', + now: nowDate.clone(), + }); + expect(gap).not.toBeNull(); + expect(gap?.asMilliseconds()).toEqual(moment.duration(-5, 'minute').asMilliseconds()); + }); + + test('it returns a negative gap of 1 minute when from overlaps to by 1 minute and the previousStartedAt was 10 minutes ago and so was the interval', () => { + const gap = getGapBetweenRuns({ + previousStartedAt: nowDate.clone().subtract(10, 'minutes'), + interval: '10m', + from: 'now-11m', + to: 'now', + now: nowDate.clone(), + }); + expect(gap).not.toBeNull(); + expect(gap?.asMilliseconds()).toEqual(moment.duration(-1, 'minute').asMilliseconds()); + }); + + test('it returns a gap of only -30 seconds when the from overlaps with now by 1 minute, the interval is 5 minutes but the previous started is 30 seconds more', () => { + const gap = getGapBetweenRuns({ + previousStartedAt: nowDate + .clone() + .subtract(5, 'minutes') + .subtract(30, 'seconds'), + interval: '5m', + from: 'now-6m', + to: 'now', + now: nowDate.clone(), + }); + expect(gap).not.toBeNull(); + expect(gap?.asMilliseconds()).toEqual(moment.duration(-30, 'seconds').asMilliseconds()); + }); + + test('it returns an exact 0 gap when the from overlaps with now by 1 minute, the interval is 5 minutes but the previous started is one minute late', () => { + const gap = getGapBetweenRuns({ + previousStartedAt: nowDate.clone().subtract(6, 'minutes'), + interval: '5m', + from: 'now-6m', + to: 'now', + now: nowDate.clone(), + }); + expect(gap).not.toBeNull(); + expect(gap?.asMilliseconds()).toEqual(moment.duration(0, 'minute').asMilliseconds()); + }); + + test('it returns a gap of 30 seconds when the from overlaps with now by 1 minute, the interval is 5 minutes but the previous started is one minute and 30 seconds late', () => { + const gap = getGapBetweenRuns({ + previousStartedAt: nowDate + .clone() + .subtract(6, 'minutes') + .subtract(30, 'seconds'), + interval: '5m', + from: 'now-6m', + to: 'now', + now: nowDate.clone(), + }); + expect(gap).not.toBeNull(); + expect(gap?.asMilliseconds()).toEqual(moment.duration(30, 'seconds').asMilliseconds()); + }); + + test('it returns a gap of 1 minute when the from overlaps with now by 1 minute, the interval is 5 minutes but the previous started is two minutes late', () => { + const gap = getGapBetweenRuns({ + previousStartedAt: nowDate.clone().subtract(7, 'minutes'), + interval: '5m', + from: 'now-6m', + to: 'now', + now: nowDate.clone(), + }); + expect(gap?.asMilliseconds()).not.toBeNull(); + expect(gap?.asMilliseconds()).toEqual(moment.duration(1, 'minute').asMilliseconds()); + }); + + test('it returns null if given a previousStartedAt of null', () => { + const gap = getGapBetweenRuns({ + previousStartedAt: null, + interval: '5m', + from: 'now-5m', + to: 'now', + now: nowDate.clone(), + }); + expect(gap).toBeNull(); + }); + + test('it returns null if the interval is an invalid string such as "invalid"', () => { + const gap = getGapBetweenRuns({ + previousStartedAt: nowDate.clone(), + interval: 'invalid', // if not set to "x" where x is an interval such as 6m + from: 'now-5m', + to: 'now', + now: nowDate.clone(), + }); + expect(gap).toBeNull(); + }); + + test('it returns null if from is an invalid string such as "invalid"', () => { + const gap = getGapBetweenRuns({ + previousStartedAt: nowDate.clone(), + interval: '5m', + from: 'invalid', // if not set to "now-x" where x is an interval such as 6m + to: 'now', + now: nowDate.clone(), + }); + expect(gap).toBeNull(); + }); + + test('it returns null if to is an invalid string such as "invalid"', () => { + const gap = getGapBetweenRuns({ + previousStartedAt: nowDate.clone(), + interval: '5m', + from: 'now-5m', + to: 'invalid', // if not set to "now" this function returns null + now: nowDate.clone(), + }); + expect(gap).toBeNull(); + }); + }); +}); diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/utils.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/utils.ts index f25ce1d905466..5a4c67ebaaa36 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/utils.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/signals/utils.ts @@ -4,6 +4,9 @@ * you may not use this file except in compliance with the Elastic License. */ import { createHash } from 'crypto'; +import moment from 'moment'; + +import { parseDuration } from '../../../../../alerting/server/lib'; export const generateId = ( docIndex: string, @@ -14,3 +17,66 @@ export const generateId = ( createHash('sha256') .update(docIndex.concat(docId, version, ruleId)) .digest('hex'); + +export const parseInterval = (intervalString: string): moment.Duration | null => { + try { + return moment.duration(parseDuration(intervalString)); + } catch (err) { + return null; + } +}; + +export const getDriftTolerance = ({ + from, + to, + interval, +}: { + from: string; + to: string; + interval: moment.Duration; +}): moment.Duration | null => { + if (to.trim() !== 'now') { + // we only support 'now' for drift detection + return null; + } + if (!from.trim().startsWith('now-')) { + // we only support from tha starts with now for drift detection + return null; + } + const split = from.split('-'); + const duration = parseInterval(split[1]); + if (duration !== null) { + return duration.subtract(interval); + } else { + return null; + } +}; + +export const getGapBetweenRuns = ({ + previousStartedAt, + interval, + from, + to, + now = moment(), +}: { + previousStartedAt: moment.Moment | undefined | null; + interval: string; + from: string; + to: string; + now?: moment.Moment; +}): moment.Duration | null => { + if (previousStartedAt == null) { + return null; + } + const intervalDuration = parseInterval(interval); + if (intervalDuration == null) { + return null; + } + const driftTolerance = getDriftTolerance({ from, to, interval: intervalDuration }); + if (driftTolerance == null) { + return null; + } + const diff = moment.duration(now.diff(previousStartedAt)); + const drift = diff.subtract(intervalDuration); + return drift.subtract(driftTolerance); +}; diff --git a/x-pack/legacy/plugins/siem/server/lib/detection_engine/types.ts b/x-pack/legacy/plugins/siem/server/lib/detection_engine/types.ts index f4a8263da6ba4..968c7d9cb1cf0 100644 --- a/x-pack/legacy/plugins/siem/server/lib/detection_engine/types.ts +++ b/x-pack/legacy/plugins/siem/server/lib/detection_engine/types.ts @@ -44,6 +44,7 @@ export interface RuleAlertParams { tags: string[]; to: string; timelineId: string | undefined | null; + timelineTitle: string | undefined | null; threats: ThreatParams[] | undefined | null; type: 'query' | 'saved_query'; version: number; @@ -60,6 +61,7 @@ export type RuleAlertParamsRest = Omit< | 'savedId' | 'riskScore' | 'timelineId' + | 'timelineTitle' | 'outputIndex' | 'updatedAt' | 'createdAt' @@ -68,6 +70,7 @@ export type RuleAlertParamsRest = Omit< false_positives: RuleAlertParams['falsePositives']; saved_id: RuleAlertParams['savedId']; timeline_id: RuleAlertParams['timelineId']; + timeline_title: RuleAlertParams['timelineTitle']; max_signals: RuleAlertParams['maxSignals']; risk_score: RuleAlertParams['riskScore']; output_index: RuleAlertParams['outputIndex']; @@ -81,4 +84,9 @@ export type OutputRuleAlertRest = RuleAlertParamsRest & { updated_by: string | undefined | null; }; +export type ImportRuleAlertRest = Omit & { + id: string | undefined | null; + rule_id: string; +}; + export type CallWithRequest = (endpoint: string, params: T, options?: U) => Promise; diff --git a/x-pack/legacy/plugins/siem/server/plugin.ts b/x-pack/legacy/plugins/siem/server/plugin.ts index 8a47aa2a27082..9d1983cf1d4da 100644 --- a/x-pack/legacy/plugins/siem/server/plugin.ts +++ b/x-pack/legacy/plugins/siem/server/plugin.ts @@ -60,7 +60,16 @@ export class Plugin { ], read: ['config'], }, - ui: ['show'], + ui: [ + 'show', + 'crud', + 'alerting:show', + 'actions:show', + 'alerting:save', + 'actions:save', + 'alerting:delete', + 'actions:delete', + ], }, read: { api: ['siem', 'actions-read', 'actions-all', 'alerting-read', 'alerting-all'], @@ -73,7 +82,15 @@ export class Plugin { timelineSavedObjectType, ], }, - ui: ['show'], + ui: [ + 'show', + 'alerting:show', + 'actions:show', + 'alerting:save', + 'actions:save', + 'alerting:delete', + 'actions:delete', + ], }, }, }); diff --git a/x-pack/legacy/plugins/snapshot_restore/__jest__/client_integration/helpers/home.helpers.ts b/x-pack/legacy/plugins/snapshot_restore/__jest__/client_integration/helpers/home.helpers.ts index 79a4eeb6dc48b..777471e209adc 100644 --- a/x-pack/legacy/plugins/snapshot_restore/__jest__/client_integration/helpers/home.helpers.ts +++ b/x-pack/legacy/plugins/snapshot_restore/__jest__/client_integration/helpers/home.helpers.ts @@ -99,7 +99,7 @@ export const setup = async (): Promise => { const tabs = ['snapshots', 'repositories']; testBed - .find('tab') + .find(`${tab}_tab`) .at(tabs.indexOf(tab)) .simulate('click'); }; @@ -360,7 +360,10 @@ export type TestSubjects = | 'state' | 'state.title' | 'state.value' - | 'tab' + | 'repositories_tab' + | 'snapshots_tab' + | 'policies_tab' + | 'restore_status_tab' | 'tableHeaderCell_durationInMillis_3' | 'tableHeaderCell_durationInMillis_3.tableHeaderSortButton' | 'tableHeaderCell_indices_4' diff --git a/x-pack/legacy/plugins/snapshot_restore/__jest__/client_integration/helpers/http_requests.ts b/x-pack/legacy/plugins/snapshot_restore/__jest__/client_integration/helpers/http_requests.ts index d9f2c1b510a14..cb2e94df75609 100644 --- a/x-pack/legacy/plugins/snapshot_restore/__jest__/client_integration/helpers/http_requests.ts +++ b/x-pack/legacy/plugins/snapshot_restore/__jest__/client_integration/helpers/http_requests.ts @@ -95,6 +95,16 @@ const registerHttpRequestMockHelpers = (server: SinonFakeServer) => { ]); }; + const setCleanupRepositoryResponse = (response?: HttpResponse, error?: any) => { + const status = error ? error.status || 503 : 200; + + server.respondWith('POST', `${API_BASE_PATH}repositories/:name/cleanup`, [ + status, + { 'Content-Type': 'application/json' }, + JSON.stringify(response), + ]); + }; + const setGetPolicyResponse = (response?: HttpResponse) => { server.respondWith('GET', `${API_BASE_PATH}policy/:name`, [ 200, @@ -113,6 +123,7 @@ const registerHttpRequestMockHelpers = (server: SinonFakeServer) => { setLoadIndicesResponse, setAddPolicyResponse, setGetPolicyResponse, + setCleanupRepositoryResponse, }; }; diff --git a/x-pack/legacy/plugins/snapshot_restore/__jest__/client_integration/home.test.ts b/x-pack/legacy/plugins/snapshot_restore/__jest__/client_integration/home.test.ts index 69b5ca9e2b68f..517c7a0059a7e 100644 --- a/x-pack/legacy/plugins/snapshot_restore/__jest__/client_integration/home.test.ts +++ b/x-pack/legacy/plugins/snapshot_restore/__jest__/client_integration/home.test.ts @@ -17,16 +17,25 @@ import { } from './helpers'; import { HomeTestBed } from './helpers/home.helpers'; import { REPOSITORY_NAME } from './helpers/constant'; -import moment from 'moment-timezone'; const { setup } = pageHelpers.home; jest.mock('ui/new_platform'); + jest.mock('ui/i18n', () => { const I18nContext = ({ children }: any) => children; return { I18nContext }; }); +// Mocking FormattedDate and FormattedTime due to timezone differences on CI +jest.mock('@kbn/i18n/react', () => { + return { + ...jest.requireActual('@kbn/i18n/react'), + FormattedDate: () => '', + FormattedTime: () => '', + }; +}); + const removeWhiteSpaceOnArrayValues = (array: any[]) => array.map(value => { if (!value.trim) { @@ -35,9 +44,7 @@ const removeWhiteSpaceOnArrayValues = (array: any[]) => return value.trim(); }); -// We need to skip the tests until react 16.9.0 is released -// which supports asynchronous code inside act() -describe.skip('', () => { +describe('', () => { const { server, httpRequestsMockHelpers } = setupEnvironment(); let testBed: HomeTestBed; @@ -81,8 +88,15 @@ describe.skip('', () => { test('should have 4 tabs', () => { const { find } = testBed; - expect(find('tab').length).toBe(4); - expect(find('tab').map(t => t.text())).toEqual([ + const tabs = [ + find('snapshots_tab'), + find('repositories_tab'), + find('policies_tab'), + find('restore_status_tab'), + ]; + + expect(tabs.length).toBe(4); + expect(tabs.map(t => t.text())).toEqual([ 'Snapshots', 'Repositories', 'Policies', @@ -455,12 +469,10 @@ describe.skip('', () => { test('should list them in the table', async () => { const { table } = testBed; - const { tableCellsValues } = table.getMetaData('snapshotTable'); + tableCellsValues.forEach((row, i) => { const snapshot = snapshots[i]; - const startTime = moment(new Date(snapshot.startTimeInMillis)); - const timezone = moment.tz.guess(); expect(row).toEqual([ '', // Checkbox @@ -469,7 +481,7 @@ describe.skip('', () => { snapshot.indices.length.toString(), // Indices snapshot.shards.total.toString(), // Shards snapshot.shards.failed.toString(), // Failed shards - startTime.tz(timezone).format('MMMM D, YYYY h:mm A z'), // Start time + ' ', // Mocked start time `${Math.ceil(snapshot.durationInMillis / 1000).toString()}s`, // Duration '', ]); @@ -597,19 +609,9 @@ describe.skip('', () => { describe('summary tab', () => { test('should set the correct summary values', () => { - const { - version, - versionId, - uuid, - indices, - endTimeInMillis, - startTimeInMillis, - } = snapshot1; + const { version, versionId, uuid, indices } = snapshot1; const { find } = testBed; - const startTime = moment(new Date(startTimeInMillis)); - const endTime = moment(new Date(endTimeInMillis)); - const timezone = moment.tz.guess(); expect(find('snapshotDetail.version.value').text()).toBe( `${version} / ${versionId}` @@ -623,12 +625,6 @@ describe.skip('', () => { expect(find('snapshotDetail.indices.value').text()).toContain( indices.splice(0, 10).join('') ); - expect(find('snapshotDetail.startTime.value').text()).toBe( - startTime.tz(timezone).format('MMMM D, YYYY h:mm A z') - ); - expect(find('snapshotDetail.endTime.value').text()).toBe( - endTime.tz(timezone).format('MMMM D, YYYY h:mm A z') - ); }); test('should indicate the different snapshot states', async () => { diff --git a/x-pack/legacy/plugins/snapshot_restore/__jest__/client_integration/policy_add.test.ts b/x-pack/legacy/plugins/snapshot_restore/__jest__/client_integration/policy_add.test.ts index 3dc7d94ce67d5..09757c4774314 100644 --- a/x-pack/legacy/plugins/snapshot_restore/__jest__/client_integration/policy_add.test.ts +++ b/x-pack/legacy/plugins/snapshot_restore/__jest__/client_integration/policy_add.test.ts @@ -27,9 +27,7 @@ const MAX_COUNT = '10'; const EXPIRE_AFTER_VALUE = '30'; const repository = fixtures.getRepository({ name: `a${getRandomString()}`, type: 'fs' }); -// We need to skip the tests until react 16.9.0 is released -// which supports asynchronous code inside act() -describe.skip('', () => { +describe('', () => { let testBed: PolicyFormTestBed; const { server, httpRequestsMockHelpers } = setupEnvironment(); @@ -195,6 +193,7 @@ describe.skip('', () => { const expected = { config: {}, + isManagedPolicy: false, name: POLICY_NAME, repository: repository.name, retention: { diff --git a/x-pack/legacy/plugins/snapshot_restore/__jest__/client_integration/policy_edit.test.ts b/x-pack/legacy/plugins/snapshot_restore/__jest__/client_integration/policy_edit.test.ts index 1222697956ffb..a5af9e5e5c3aa 100644 --- a/x-pack/legacy/plugins/snapshot_restore/__jest__/client_integration/policy_edit.test.ts +++ b/x-pack/legacy/plugins/snapshot_restore/__jest__/client_integration/policy_edit.test.ts @@ -24,9 +24,7 @@ jest.mock('ui/i18n', () => { return { I18nContext }; }); -// We need to skip the tests until react 16.9.0 is released -// which supports asynchronous code inside act() -describe.skip('', () => { +describe('', () => { let testBed: PolicyFormTestBed; let testBedPolicyAdd: PolicyFormTestBed; const { server, httpRequestsMockHelpers } = setupEnvironment(); diff --git a/x-pack/legacy/plugins/snapshot_restore/__jest__/client_integration/repository_add.test.ts b/x-pack/legacy/plugins/snapshot_restore/__jest__/client_integration/repository_add.test.ts index 1f70755d5fd85..82c090bc552bb 100644 --- a/x-pack/legacy/plugins/snapshot_restore/__jest__/client_integration/repository_add.test.ts +++ b/x-pack/legacy/plugins/snapshot_restore/__jest__/client_integration/repository_add.test.ts @@ -21,9 +21,7 @@ jest.mock('ui/i18n', () => { return { I18nContext }; }); -// We need to skip the tests until react 16.9.0 is released -// which supports asynchronous code inside act() -describe.skip('', () => { +describe('', () => { let testBed: RepositoryAddTestBed; const { server, httpRequestsMockHelpers } = setupEnvironment(); @@ -215,7 +213,7 @@ describe.skip('', () => { // Fill step 2 form.setInputValue('locationInput', repository.settings.location); - form.selectCheckBox('compressToggle'); + form.toggleEuiSwitch('compressToggle'); await act(async () => { actions.clickSubmitButton(); @@ -240,7 +238,7 @@ describe.skip('', () => { const { component, form, actions, find, exists } = testBed; form.setInputValue('locationInput', repository.settings.location); - form.selectCheckBox('compressToggle'); + form.toggleEuiSwitch('compressToggle'); const error = { status: 400, @@ -266,7 +264,7 @@ describe.skip('', () => { // Fill step 1 required fields and go to step 2 testBed.form.setInputValue('nameInput', repository.name); testBed.actions.selectRepositoryType(repository.type); - testBed.form.selectCheckBox('sourceOnlyToggle'); // toggle source + testBed.form.toggleEuiSwitch('sourceOnlyToggle'); // toggle source testBed.actions.clickNextButton(); }); diff --git a/x-pack/legacy/plugins/snapshot_restore/__jest__/client_integration/repository_edit.test.ts b/x-pack/legacy/plugins/snapshot_restore/__jest__/client_integration/repository_edit.test.ts index cca81c0e74285..b850114115893 100644 --- a/x-pack/legacy/plugins/snapshot_restore/__jest__/client_integration/repository_edit.test.ts +++ b/x-pack/legacy/plugins/snapshot_restore/__jest__/client_integration/repository_edit.test.ts @@ -22,9 +22,7 @@ jest.mock('ui/i18n', () => { return { I18nContext }; }); -// We need to skip the tests until react 16.9.0 is released -// which supports asynchronous code inside act() -describe.skip('', () => { +describe('', () => { let testBed: TestBed; let testBedRepositoryAdd: TestBed; const { server, httpRequestsMockHelpers } = setupEnvironment(); @@ -101,7 +99,7 @@ describe.skip('', () => { const { find } = testBed; expect(find('locationInput').props().defaultValue).toBe(settings.location); - expect(find('compressToggle').props().checked).toBe(settings.compress); + expect(find('compressToggle').props()['aria-checked']).toBe(settings.compress); expect(find('chunkSizeInput').props().defaultValue).toBe(settings.chunkSize); expect(find('maxSnapshotBytesInput').props().defaultValue).toBe( settings.maxSnapshotBytesPerSec @@ -109,7 +107,7 @@ describe.skip('', () => { expect(find('maxRestoreBytesInput').props().defaultValue).toBe( settings.maxRestoreBytesPerSec ); - expect(find('readOnlyToggle').props().checked).toBe(settings.readonly); + expect(find('readOnlyToggle').props()['aria-checked']).toBe(settings.readonly); }); it('readonly repository', async () => { @@ -145,7 +143,7 @@ describe.skip('', () => { expect(find('clientInput').props().defaultValue).toBe(settings.client); expect(find('containerInput').props().defaultValue).toBe(settings.container); expect(find('basePathInput').props().defaultValue).toBe(settings.basePath); - expect(find('compressToggle').props().checked).toBe(settings.compress); + expect(find('compressToggle').props()['aria-checked']).toBe(settings.compress); expect(find('chunkSizeInput').props().defaultValue).toBe(settings.chunkSize); expect(find('maxSnapshotBytesInput').props().defaultValue).toBe( settings.maxSnapshotBytesPerSec @@ -154,7 +152,7 @@ describe.skip('', () => { settings.maxRestoreBytesPerSec ); expect(find('locationModeSelect').props().value).toBe(settings.locationMode); - expect(find('readOnlyToggle').props().checked).toBe(settings.readonly); + expect(find('readOnlyToggle').props()['aria-checked']).toBe(settings.readonly); }); it('gcs repository', async () => { @@ -176,7 +174,7 @@ describe.skip('', () => { expect(find('clientInput').props().defaultValue).toBe(settings.client); expect(find('bucketInput').props().defaultValue).toBe(settings.bucket); expect(find('basePathInput').props().defaultValue).toBe(settings.basePath); - expect(find('compressToggle').props().checked).toBe(settings.compress); + expect(find('compressToggle').props()['aria-checked']).toBe(settings.compress); expect(find('chunkSizeInput').props().defaultValue).toBe(settings.chunkSize); expect(find('maxSnapshotBytesInput').props().defaultValue).toBe( settings.maxSnapshotBytesPerSec @@ -184,7 +182,7 @@ describe.skip('', () => { expect(find('maxRestoreBytesInput').props().defaultValue).toBe( settings.maxRestoreBytesPerSec ); - expect(find('readOnlyToggle').props().checked).toBe(settings.readonly); + expect(find('readOnlyToggle').props()['aria-checked']).toBe(settings.readonly); }); it('hdfs repository', async () => { @@ -209,8 +207,8 @@ describe.skip('', () => { expect(find('uriInput').props().defaultValue).toBe('elastic.co'); expect(find('pathInput').props().defaultValue).toBe(settings.path); - expect(find('loadDefaultsToggle').props().checked).toBe(settings.loadDefault); - expect(find('compressToggle').props().checked).toBe(settings.compress); + expect(find('loadDefaultsToggle').props()['aria-checked']).toBe(settings.loadDefault); + expect(find('compressToggle').props()['aria-checked']).toBe(settings.compress); expect(find('chunkSizeInput').props().defaultValue).toBe(settings.chunkSize); expect(find('securityPrincipalInput').props().defaultValue).toBe( settings['security.principal'] @@ -221,7 +219,7 @@ describe.skip('', () => { expect(find('maxRestoreBytesInput').props().defaultValue).toBe( settings.maxRestoreBytesPerSec ); - expect(find('readOnlyToggle').props().checked).toBe(settings.readonly); + expect(find('readOnlyToggle').props()['aria-checked']).toBe(settings.readonly); const codeEditor = testBed.component.find('EuiCodeEditor'); expect(JSON.parse(codeEditor.props().value as string)).toEqual({ @@ -254,9 +252,9 @@ describe.skip('', () => { expect(find('clientInput').props().defaultValue).toBe(settings.client); expect(find('bucketInput').props().defaultValue).toBe(settings.bucket); expect(find('basePathInput').props().defaultValue).toBe(settings.basePath); - expect(find('compressToggle').props().checked).toBe(settings.compress); + expect(find('compressToggle').props()['aria-checked']).toBe(settings.compress); expect(find('chunkSizeInput').props().defaultValue).toBe(settings.chunkSize); - expect(find('serverSideEncryptionToggle').props().checked).toBe( + expect(find('serverSideEncryptionToggle').props()['aria-checked']).toBe( settings.serverSideEncryption ); expect(find('bufferSizeInput').props().defaultValue).toBe(settings.bufferSize); @@ -268,7 +266,7 @@ describe.skip('', () => { expect(find('maxRestoreBytesInput').props().defaultValue).toBe( settings.maxRestoreBytesPerSec ); - expect(find('readOnlyToggle').props().checked).toBe(settings.readonly); + expect(find('readOnlyToggle').props()['aria-checked']).toBe(settings.readonly); }); }); }); diff --git a/x-pack/legacy/plugins/snapshot_restore/common/types/repository.ts b/x-pack/legacy/plugins/snapshot_restore/common/types/repository.ts index 5900d53afa0b4..b9b26c5590324 100644 --- a/x-pack/legacy/plugins/snapshot_restore/common/types/repository.ts +++ b/x-pack/legacy/plugins/snapshot_restore/common/types/repository.ts @@ -157,3 +157,15 @@ export interface InvalidRepositoryVerification { } export type RepositoryVerification = ValidRepositoryVerification | InvalidRepositoryVerification; + +export interface SuccessfulRepositoryCleanup { + cleaned: true; + response: object; +} + +export interface FailedRepositoryCleanup { + cleaned: false; + error: object; +} + +export type RepositoryCleanup = FailedRepositoryCleanup | SuccessfulRepositoryCleanup; diff --git a/x-pack/legacy/plugins/snapshot_restore/public/app/components/policy_form/steps/step_logistics.tsx b/x-pack/legacy/plugins/snapshot_restore/public/app/components/policy_form/steps/step_logistics.tsx index 2206d6de341c8..111b46d596e56 100644 --- a/x-pack/legacy/plugins/snapshot_restore/public/app/components/policy_form/steps/step_logistics.tsx +++ b/x-pack/legacy/plugins/snapshot_restore/public/app/components/policy_form/steps/step_logistics.tsx @@ -347,7 +347,7 @@ export const PolicyStepLogistics: React.FunctionComponent = ({ onChange={e => { updatePolicy( { - snapshotName: e.target.value.toLowerCase(), + snapshotName: e.target.value, }, { managedRepository, diff --git a/x-pack/legacy/plugins/snapshot_restore/public/app/constants/index.ts b/x-pack/legacy/plugins/snapshot_restore/public/app/constants/index.ts index 844394deb4f8d..481516479df4e 100644 --- a/x-pack/legacy/plugins/snapshot_restore/public/app/constants/index.ts +++ b/x-pack/legacy/plugins/snapshot_restore/public/app/constants/index.ts @@ -103,6 +103,7 @@ export const UIM_REPOSITORY_DELETE = 'repository_delete'; export const UIM_REPOSITORY_DELETE_MANY = 'repository_delete_many'; export const UIM_REPOSITORY_SHOW_DETAILS_CLICK = 'repository_show_details_click'; export const UIM_REPOSITORY_DETAIL_PANEL_VERIFY = 'repository_detail_panel_verify'; +export const UIM_REPOSITORY_DETAIL_PANEL_CLEANUP = 'repository_detail_panel_cleanup'; export const UIM_SNAPSHOT_LIST_LOAD = 'snapshot_list_load'; export const UIM_SNAPSHOT_SHOW_DETAILS_CLICK = 'snapshot_show_details_click'; export const UIM_SNAPSHOT_DETAIL_PANEL_SUMMARY_TAB = 'snapshot_detail_panel_summary_tab'; diff --git a/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/home.tsx b/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/home.tsx index 35d5c0b610b3c..f89aa869b3366 100644 --- a/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/home.tsx +++ b/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/home.tsx @@ -150,7 +150,7 @@ export const SnapshotRestoreHome: React.FunctionComponent onSectionChange(tab.id)} isSelected={tab.id === section} key={tab.id} - data-test-subj="tab" + data-test-subj={tab.id.toLowerCase() + '_tab'} > {tab.name} diff --git a/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/repository_list/repository_details/repository_details.tsx b/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/repository_list/repository_details/repository_details.tsx index c03162bae8bd2..0a3fcfc2ec6e7 100644 --- a/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/repository_list/repository_details/repository_details.tsx +++ b/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/repository_list/repository_details/repository_details.tsx @@ -8,7 +8,6 @@ import { EuiButton, EuiButtonEmpty, EuiCallOut, - EuiCodeEditor, EuiFlexGroup, EuiFlexItem, EuiFlyout, @@ -19,6 +18,8 @@ import { EuiLink, EuiSpacer, EuiTitle, + EuiCodeBlock, + EuiText, } from '@elastic/eui'; import 'brace/theme/textmate'; @@ -28,12 +29,17 @@ import { documentationLinksService } from '../../../../services/documentation'; import { useLoadRepository, verifyRepository as verifyRepositoryRequest, + cleanupRepository as cleanupRepositoryRequest, } from '../../../../services/http'; import { textService } from '../../../../services/text'; import { linkToSnapshots, linkToEditRepository } from '../../../../services/navigation'; import { REPOSITORY_TYPES } from '../../../../../../common/constants'; -import { Repository, RepositoryVerification } from '../../../../../../common/types'; +import { + Repository, + RepositoryVerification, + RepositoryCleanup, +} from '../../../../../../common/types'; import { RepositoryDeleteProvider, SectionError, @@ -61,7 +67,9 @@ export const RepositoryDetails: React.FunctionComponent = ({ const { FormattedMessage } = i18n; const { error, data: repositoryDetails } = useLoadRepository(repositoryName); const [verification, setVerification] = useState(undefined); + const [cleanup, setCleanup] = useState(undefined); const [isLoadingVerification, setIsLoadingVerification] = useState(false); + const [isLoadingCleanup, setIsLoadingCleanup] = useState(false); const verifyRepository = async () => { setIsLoadingVerification(true); @@ -70,11 +78,20 @@ export const RepositoryDetails: React.FunctionComponent = ({ setIsLoadingVerification(false); }; - // Reset verification state when repository name changes, either from adjust URL or clicking + const cleanupRepository = async () => { + setIsLoadingCleanup(true); + const { data } = await cleanupRepositoryRequest(repositoryName); + setCleanup(data.cleanup); + setIsLoadingCleanup(false); + }; + + // Reset verification state and cleanup when repository name changes, either from adjust URL or clicking // into a different repository in table list. useEffect(() => { setVerification(undefined); setIsLoadingVerification(false); + setCleanup(undefined); + setIsLoadingCleanup(false); }, [repositoryName]); const renderBody = () => { @@ -231,6 +248,8 @@ export const RepositoryDetails: React.FunctionComponent = ({ {renderVerification()} + + {renderCleanup()} ); }; @@ -260,36 +279,13 @@ export const RepositoryDetails: React.FunctionComponent = ({ {verification ? ( - + {JSON.stringify( verification.valid ? verification.response : verification.error, null, 2 )} - setOptions={{ - showLineNumbers: false, - tabSize: 2, - maxLines: Infinity, - }} - editorProps={{ - $blockScrolling: Infinity, - }} - showGutter={false} - minLines={6} - aria-label={ - - } - /> + ) : null} @@ -318,6 +314,78 @@ export const RepositoryDetails: React.FunctionComponent = ({ ); + const renderCleanup = () => ( + <> + +

+ +

+
+ + +

+ +

+
+ {cleanup ? ( + <> + + {cleanup.cleaned ? ( +
+ +

+ +

+
+ + {JSON.stringify(cleanup.response, null, 2)} + +
+ ) : ( + +

+ {cleanup.error + ? JSON.stringify(cleanup.error) + : i18n.translate('xpack.snapshotRestore.repositoryDetails.cleanupUnknownError', { + defaultMessage: '503: Unknown error', + })} +

+
+ )} + + ) : null} + + + + + + ); + const renderFooter = () => { return ( diff --git a/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/repository_list/repository_list.tsx b/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/repository_list/repository_list.tsx index dbbc0e09111eb..e387e844bda8c 100644 --- a/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/repository_list/repository_list.tsx +++ b/x-pack/legacy/plugins/snapshot_restore/public/app/sections/home/repository_list/repository_list.tsx @@ -135,7 +135,7 @@ export const RepositoryList: React.FunctionComponent = ({ }, }, { + field: 'actions', name: i18n.translate('xpack.snapshotRestore.repositoryList.table.actionsColumnTitle', { defaultMessage: 'Actions', }), @@ -302,8 +303,8 @@ export const RepositoryTable: React.FunctionComponent = ({ rowProps={() => ({ 'data-test-subj': 'row', })} - cellProps={() => ({ - 'data-test-subj': 'cell', + cellProps={(item, field) => ({ + 'data-test-subj': `${field.name}_cell`, })} data-test-subj="repositoryTable" /> diff --git a/x-pack/legacy/plugins/snapshot_restore/public/app/services/http/repository_requests.ts b/x-pack/legacy/plugins/snapshot_restore/public/app/services/http/repository_requests.ts index 171e949ccee75..b92f21ea6a9b6 100644 --- a/x-pack/legacy/plugins/snapshot_restore/public/app/services/http/repository_requests.ts +++ b/x-pack/legacy/plugins/snapshot_restore/public/app/services/http/repository_requests.ts @@ -11,6 +11,7 @@ import { UIM_REPOSITORY_DELETE, UIM_REPOSITORY_DELETE_MANY, UIM_REPOSITORY_DETAIL_PANEL_VERIFY, + UIM_REPOSITORY_DETAIL_PANEL_CLEANUP, } from '../../constants'; import { uiMetricService } from '../ui_metric'; import { httpService } from './http'; @@ -44,6 +45,20 @@ export const verifyRepository = async (name: Repository['name']) => { return result; }; +export const cleanupRepository = async (name: Repository['name']) => { + const result = await sendRequest({ + path: httpService.addBasePath( + `${API_BASE_PATH}repositories/${encodeURIComponent(name)}/cleanup` + ), + method: 'post', + body: undefined, + }); + + const { trackUiMetric } = uiMetricService; + trackUiMetric(UIM_REPOSITORY_DETAIL_PANEL_CLEANUP); + return result; +}; + export const useLoadRepositoryTypes = () => { return useRequest({ path: httpService.addBasePath(`${API_BASE_PATH}repository_types`), diff --git a/x-pack/legacy/plugins/snapshot_restore/public/app/services/validation/validate_policy.ts b/x-pack/legacy/plugins/snapshot_restore/public/app/services/validation/validate_policy.ts index 7d44979e697a7..0720994ca7669 100644 --- a/x-pack/legacy/plugins/snapshot_restore/public/app/services/validation/validate_policy.ts +++ b/x-pack/legacy/plugins/snapshot_restore/public/app/services/validation/validate_policy.ts @@ -15,6 +15,16 @@ const isStringEmpty = (str: string | null): boolean => { return str ? !Boolean(str.trim()) : true; }; +// strExcludeDate is the concat results of the SnapshotName ...{...}>... without the date +// This way we can check only the SnapshotName portion for lowercasing +// For example: would give strExcludeDate = + +const isSnapshotNameNotLowerCase = (str: string): boolean => { + const strExcludeDate = + str.substring(0, str.search('{')) + str.substring(str.search('}>') + 1, str.length); + return strExcludeDate !== strExcludeDate.toLowerCase() ? true : false; +}; + export const validatePolicy = ( policy: SlmPolicyPayload, validationHelperData: { @@ -61,6 +71,14 @@ export const validatePolicy = ( ); } + if (isSnapshotNameNotLowerCase(snapshotName)) { + validation.errors.snapshotName.push( + i18n.translate('xpack.snapshotRestore.policyValidation.snapshotNameLowerCaseErrorMessage', { + defaultMessage: 'Snapshot name needs to be lowercase.', + }) + ); + } + if (isStringEmpty(schedule)) { validation.errors.schedule.push( i18n.translate('xpack.snapshotRestore.policyValidation.scheduleRequiredErrorMessage', { diff --git a/x-pack/legacy/plugins/snapshot_restore/server/client/elasticsearch_slm.ts b/x-pack/legacy/plugins/snapshot_restore/server/client/elasticsearch_slm.ts deleted file mode 100644 index 82fe30aaa7d2e..0000000000000 --- a/x-pack/legacy/plugins/snapshot_restore/server/client/elasticsearch_slm.ts +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export const elasticsearchJsPlugin = (Client: any, config: any, components: any) => { - const ca = components.clientAction.factory; - - Client.prototype.slm = components.clientAction.namespaceFactory(); - const slm = Client.prototype.slm.prototype; - - slm.policies = ca({ - urls: [ - { - fmt: '/_slm/policy', - }, - ], - method: 'GET', - }); - - slm.policy = ca({ - urls: [ - { - fmt: '/_slm/policy/<%=name%>', - req: { - name: { - type: 'string', - }, - }, - }, - ], - method: 'GET', - }); - - slm.deletePolicy = ca({ - urls: [ - { - fmt: '/_slm/policy/<%=name%>', - req: { - name: { - type: 'string', - }, - }, - }, - ], - method: 'DELETE', - }); - - slm.executePolicy = ca({ - urls: [ - { - fmt: '/_slm/policy/<%=name%>/_execute', - req: { - name: { - type: 'string', - }, - }, - }, - ], - method: 'PUT', - }); - - slm.updatePolicy = ca({ - urls: [ - { - fmt: '/_slm/policy/<%=name%>', - req: { - name: { - type: 'string', - }, - }, - }, - ], - method: 'PUT', - }); - - slm.executeRetention = ca({ - urls: [ - { - fmt: '/_slm/_execute_retention', - }, - ], - method: 'POST', - }); -}; diff --git a/x-pack/legacy/plugins/snapshot_restore/server/client/elasticsearch_sr.ts b/x-pack/legacy/plugins/snapshot_restore/server/client/elasticsearch_sr.ts new file mode 100644 index 0000000000000..794bf99c3d918 --- /dev/null +++ b/x-pack/legacy/plugins/snapshot_restore/server/client/elasticsearch_sr.ts @@ -0,0 +1,100 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const elasticsearchJsPlugin = (Client: any, config: any, components: any) => { + const ca = components.clientAction.factory; + + Client.prototype.sr = components.clientAction.namespaceFactory(); + const sr = Client.prototype.sr.prototype; + + sr.policies = ca({ + urls: [ + { + fmt: '/_slm/policy', + }, + ], + method: 'GET', + }); + + sr.policy = ca({ + urls: [ + { + fmt: '/_slm/policy/<%=name%>', + req: { + name: { + type: 'string', + }, + }, + }, + ], + method: 'GET', + }); + + sr.deletePolicy = ca({ + urls: [ + { + fmt: '/_slm/policy/<%=name%>', + req: { + name: { + type: 'string', + }, + }, + }, + ], + method: 'DELETE', + }); + + sr.executePolicy = ca({ + urls: [ + { + fmt: '/_slm/policy/<%=name%>/_execute', + req: { + name: { + type: 'string', + }, + }, + }, + ], + method: 'PUT', + }); + + sr.updatePolicy = ca({ + urls: [ + { + fmt: '/_slm/policy/<%=name%>', + req: { + name: { + type: 'string', + }, + }, + }, + ], + method: 'PUT', + }); + + sr.executeRetention = ca({ + urls: [ + { + fmt: '/_slm/_execute_retention', + }, + ], + method: 'POST', + }); + + sr.cleanupRepository = ca({ + urls: [ + { + fmt: '/_snapshot/<%=name%>/_cleanup', + req: { + name: { + type: 'string', + }, + }, + }, + ], + method: 'POST', + }); +}; diff --git a/x-pack/legacy/plugins/snapshot_restore/server/routes/api/policy.ts b/x-pack/legacy/plugins/snapshot_restore/server/routes/api/policy.ts index bbfc82b8a6de9..9f434ac10c16a 100644 --- a/x-pack/legacy/plugins/snapshot_restore/server/routes/api/policy.ts +++ b/x-pack/legacy/plugins/snapshot_restore/server/routes/api/policy.ts @@ -40,7 +40,7 @@ export const getAllHandler: RouterRouteHandler = async ( // Get policies const policiesByName: { [key: string]: SlmPolicyEs; - } = await callWithRequest('slm.policies', { + } = await callWithRequest('sr.policies', { human: true, }); @@ -62,7 +62,7 @@ export const getOneHandler: RouterRouteHandler = async ( const { name } = req.params; const policiesByName: { [key: string]: SlmPolicyEs; - } = await callWithRequest('slm.policy', { + } = await callWithRequest('sr.policy', { name, human: true, }); @@ -82,7 +82,7 @@ export const getOneHandler: RouterRouteHandler = async ( export const executeHandler: RouterRouteHandler = async (req, callWithRequest) => { const { name } = req.params; - const { snapshot_name: snapshotName } = await callWithRequest('slm.executePolicy', { + const { snapshot_name: snapshotName } = await callWithRequest('sr.executePolicy', { name, }); return { snapshotName }; @@ -98,7 +98,7 @@ export const deleteHandler: RouterRouteHandler = async (req, callWithRequest) => await Promise.all( policyNames.map(name => { - return callWithRequest('slm.deletePolicy', { name }) + return callWithRequest('sr.deletePolicy', { name }) .then(() => response.itemsDeleted.push(name)) .catch(e => response.errors.push({ @@ -122,7 +122,7 @@ export const createHandler: RouterRouteHandler = async (req, callWithRequest) => // Check that policy with the same name doesn't already exist try { - const policyByName = await callWithRequest('slm.policy', { name }); + const policyByName = await callWithRequest('sr.policy', { name }); if (policyByName[name]) { throw conflictError; } @@ -134,7 +134,7 @@ export const createHandler: RouterRouteHandler = async (req, callWithRequest) => } // Otherwise create new policy - return await callWithRequest('slm.updatePolicy', { + return await callWithRequest('sr.updatePolicy', { name, body: serializePolicy(policy), }); @@ -146,10 +146,10 @@ export const updateHandler: RouterRouteHandler = async (req, callWithRequest) => // Check that policy with the given name exists // If it doesn't exist, 404 will be thrown by ES and will be returned - await callWithRequest('slm.policy', { name }); + await callWithRequest('sr.policy', { name }); // Otherwise update policy - return await callWithRequest('slm.updatePolicy', { + return await callWithRequest('sr.updatePolicy', { name, body: serializePolicy(policy), }); @@ -210,5 +210,5 @@ export const updateRetentionSettingsHandler: RouterRouteHandler = async (req, ca }; export const executeRetentionHandler: RouterRouteHandler = async (_req, callWithRequest) => { - return await callWithRequest('slm.executeRetention'); + return await callWithRequest('sr.executeRetention'); }; diff --git a/x-pack/legacy/plugins/snapshot_restore/server/routes/api/repositories.ts b/x-pack/legacy/plugins/snapshot_restore/server/routes/api/repositories.ts index f6ac946ab07d5..3d67494da4aad 100644 --- a/x-pack/legacy/plugins/snapshot_restore/server/routes/api/repositories.ts +++ b/x-pack/legacy/plugins/snapshot_restore/server/routes/api/repositories.ts @@ -15,6 +15,7 @@ import { RepositoryType, RepositoryVerification, SlmPolicyEs, + RepositoryCleanup, } from '../../../common/types'; import { Plugins } from '../../shim'; @@ -34,6 +35,7 @@ export function registerRepositoriesRoutes(router: Router, plugins: Plugins) { router.get('repositories', getAllHandler); router.get('repositories/{name}', getOneHandler); router.get('repositories/{name}/verify', getVerificationHandler); + router.post('repositories/{name}/cleanup', getCleanupHandler); router.put('repositories', createHandler); router.put('repositories/{name}', updateHandler); router.delete('repositories/{names}', deleteHandler); @@ -74,7 +76,7 @@ export const getAllHandler: RouterRouteHandler = async ( try { const policiesByName: { [key: string]: SlmPolicyEs; - } = await callWithRequest('slm.policies', { + } = await callWithRequest('sr.policies', { human: true, }); const managedRepositoryPolicy = Object.entries(policiesByName) @@ -172,6 +174,31 @@ export const getVerificationHandler: RouterRouteHandler = async ( }; }; +export const getCleanupHandler: RouterRouteHandler = async ( + req, + callWithRequest +): Promise<{ + cleanup: RepositoryCleanup | {}; +}> => { + const { name } = req.params; + + const cleanupResults = await callWithRequest('sr.cleanupRepository', { + name, + }).catch(e => ({ + cleaned: false, + error: e.response ? JSON.parse(e.response) : e, + })); + + return { + cleanup: cleanupResults.error + ? cleanupResults + : { + cleaned: true, + response: cleanupResults, + }, + }; +}; + export const getTypesHandler: RouterRouteHandler = async () => { // In ECE/ESS, do not enable the default types const types: RepositoryType[] = isCloudEnabled ? [] : [...DEFAULT_REPOSITORY_TYPES]; diff --git a/x-pack/legacy/plugins/snapshot_restore/server/routes/api/snapshots.ts b/x-pack/legacy/plugins/snapshot_restore/server/routes/api/snapshots.ts index 042a2dfeaf6b5..0d34d6a6b1b31 100644 --- a/x-pack/legacy/plugins/snapshot_restore/server/routes/api/snapshots.ts +++ b/x-pack/legacy/plugins/snapshot_restore/server/routes/api/snapshots.ts @@ -38,7 +38,7 @@ export const getAllHandler: RouterRouteHandler = async ( // Attempt to retrieve policies // This could fail if user doesn't have access to read SLM policies try { - const policiesByName = await callWithRequest('slm.policies'); + const policiesByName = await callWithRequest('sr.policies'); policies = Object.keys(policiesByName); } catch (e) { // Silently swallow error as policy names aren't required in UI diff --git a/x-pack/legacy/plugins/snapshot_restore/server/shim.ts b/x-pack/legacy/plugins/snapshot_restore/server/shim.ts index 84c9ddf8e0bea..d64f35c64f11e 100644 --- a/x-pack/legacy/plugins/snapshot_restore/server/shim.ts +++ b/x-pack/legacy/plugins/snapshot_restore/server/shim.ts @@ -8,7 +8,7 @@ import { i18n } from '@kbn/i18n'; import { Legacy } from 'kibana'; import { createRouter, Router } from '../../../server/lib/create_router'; import { registerLicenseChecker } from '../../../server/lib/register_license_checker'; -import { elasticsearchJsPlugin } from './client/elasticsearch_slm'; +import { elasticsearchJsPlugin } from './client/elasticsearch_sr'; import { CloudSetup } from '../../../../plugins/cloud/server'; export interface Core { http: { diff --git a/x-pack/legacy/plugins/spaces/index.ts b/x-pack/legacy/plugins/spaces/index.ts index 0083847cfb441..934b44b4accaf 100644 --- a/x-pack/legacy/plugins/spaces/index.ts +++ b/x-pack/legacy/plugins/spaces/index.ts @@ -49,12 +49,12 @@ export const spaces = (kibana: Record) => uiExports: { styleSheetPaths: resolve(__dirname, 'public/index.scss'), - managementSections: ['plugins/spaces/views/management'], + managementSections: [], apps: [ { id: 'space_selector', title: 'Spaces', - main: 'plugins/spaces/views/space_selector', + main: 'plugins/spaces/space_selector', url: 'space_selector', hidden: true, }, diff --git a/x-pack/legacy/plugins/spaces/public/advanced_settings/advanced_settings_service.test.tsx b/x-pack/legacy/plugins/spaces/public/advanced_settings/advanced_settings_service.test.tsx new file mode 100644 index 0000000000000..aa3c6acf26236 --- /dev/null +++ b/x-pack/legacy/plugins/spaces/public/advanced_settings/advanced_settings_service.test.tsx @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { AdvancedSettingsService } from './advanced_settings_service'; +jest.mock('ui/management', () => { + return { + PAGE_TITLE_COMPONENT: 'page_title_component', + PAGE_SUBTITLE_COMPONENT: 'page_subtitle_component', + }; +}); + +describe('Advanced Settings Service', () => { + describe('#setup', () => { + it('registers space-aware components to augment the advanced settings screen', () => { + const deps = { + getActiveSpace: jest.fn().mockResolvedValue({ id: 'foo', name: 'foo-space' }), + registerSettingsComponent: jest.fn(), + }; + + const advancedSettingsService = new AdvancedSettingsService(); + advancedSettingsService.setup(deps); + + expect(deps.registerSettingsComponent).toHaveBeenCalledTimes(2); + expect(deps.registerSettingsComponent).toHaveBeenCalledWith( + 'page_title_component', + expect.any(Function), + true + ); + + expect(deps.registerSettingsComponent).toHaveBeenCalledWith( + 'page_subtitle_component', + expect.any(Function), + true + ); + }); + }); +}); diff --git a/x-pack/legacy/plugins/spaces/public/advanced_settings/advanced_settings_service.tsx b/x-pack/legacy/plugins/spaces/public/advanced_settings/advanced_settings_service.tsx new file mode 100644 index 0000000000000..9c6c2fcc2cdda --- /dev/null +++ b/x-pack/legacy/plugins/spaces/public/advanced_settings/advanced_settings_service.tsx @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React from 'react'; +import { PAGE_TITLE_COMPONENT, PAGE_SUBTITLE_COMPONENT } from 'ui/management'; +import { Space } from '../../common/model/space'; +import { AdvancedSettingsTitle, AdvancedSettingsSubtitle } from './components'; + +interface SetupDeps { + getActiveSpace: () => Promise; + registerSettingsComponent: ( + id: string, + component: string | React.FC, + allowOverride: boolean + ) => void; +} + +export class AdvancedSettingsService { + public setup({ getActiveSpace, registerSettingsComponent }: SetupDeps) { + const PageTitle = () => ; + const SubTitle = () => ; + + registerSettingsComponent(PAGE_TITLE_COMPONENT, PageTitle, true); + registerSettingsComponent(PAGE_SUBTITLE_COMPONENT, SubTitle, true); + } +} diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/advanced_settings_subtitle/advanced_settings_subtitle.test.tsx b/x-pack/legacy/plugins/spaces/public/advanced_settings/components/advanced_settings_subtitle/advanced_settings_subtitle.test.tsx similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/management/components/advanced_settings_subtitle/advanced_settings_subtitle.test.tsx rename to x-pack/legacy/plugins/spaces/public/advanced_settings/components/advanced_settings_subtitle/advanced_settings_subtitle.test.tsx diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/advanced_settings_subtitle/advanced_settings_subtitle.tsx b/x-pack/legacy/plugins/spaces/public/advanced_settings/components/advanced_settings_subtitle/advanced_settings_subtitle.tsx similarity index 95% rename from x-pack/legacy/plugins/spaces/public/views/management/components/advanced_settings_subtitle/advanced_settings_subtitle.tsx rename to x-pack/legacy/plugins/spaces/public/advanced_settings/components/advanced_settings_subtitle/advanced_settings_subtitle.tsx index 433f8a8ccf0a2..e35d67c7214cf 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/components/advanced_settings_subtitle/advanced_settings_subtitle.tsx +++ b/x-pack/legacy/plugins/spaces/public/advanced_settings/components/advanced_settings_subtitle/advanced_settings_subtitle.tsx @@ -7,7 +7,7 @@ import { EuiCallOut, EuiSpacer } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import React, { Fragment, useState, useEffect } from 'react'; -import { Space } from '../../../../../common/model/space'; +import { Space } from '../../../../common/model/space'; interface Props { getActiveSpace: () => Promise; diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/advanced_settings_subtitle/index.ts b/x-pack/legacy/plugins/spaces/public/advanced_settings/components/advanced_settings_subtitle/index.ts similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/management/components/advanced_settings_subtitle/index.ts rename to x-pack/legacy/plugins/spaces/public/advanced_settings/components/advanced_settings_subtitle/index.ts diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/advanced_settings_title/advanced_settings_title.test.tsx b/x-pack/legacy/plugins/spaces/public/advanced_settings/components/advanced_settings_title/advanced_settings_title.test.tsx similarity index 94% rename from x-pack/legacy/plugins/spaces/public/views/management/components/advanced_settings_title/advanced_settings_title.test.tsx rename to x-pack/legacy/plugins/spaces/public/advanced_settings/components/advanced_settings_title/advanced_settings_title.test.tsx index bf792ca2cdacf..b772ff433abec 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/components/advanced_settings_title/advanced_settings_title.test.tsx +++ b/x-pack/legacy/plugins/spaces/public/advanced_settings/components/advanced_settings_title/advanced_settings_title.test.tsx @@ -6,7 +6,7 @@ import React from 'react'; import { mountWithIntl, nextTick } from 'test_utils/enzyme_helpers'; import { AdvancedSettingsTitle } from './advanced_settings_title'; -import { SpaceAvatar } from '../../../../components'; +import { SpaceAvatar } from '../../../space_avatar'; import { act } from '@testing-library/react'; describe('AdvancedSettingsTitle', () => { diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/advanced_settings_title/advanced_settings_title.tsx b/x-pack/legacy/plugins/spaces/public/advanced_settings/components/advanced_settings_title/advanced_settings_title.tsx similarity index 90% rename from x-pack/legacy/plugins/spaces/public/views/management/components/advanced_settings_title/advanced_settings_title.tsx rename to x-pack/legacy/plugins/spaces/public/advanced_settings/components/advanced_settings_title/advanced_settings_title.tsx index af6fa42cce07b..b74524db81d81 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/components/advanced_settings_title/advanced_settings_title.tsx +++ b/x-pack/legacy/plugins/spaces/public/advanced_settings/components/advanced_settings_title/advanced_settings_title.tsx @@ -7,8 +7,8 @@ import { EuiFlexGroup, EuiFlexItem, EuiTitle } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import React, { useState, useEffect } from 'react'; -import { Space } from '../../../../../common/model/space'; -import { SpaceAvatar } from '../../../../components'; +import { Space } from '../../../../../../../plugins/spaces/common/model/space'; +import { SpaceAvatar } from '../../../space_avatar'; interface Props { getActiveSpace: () => Promise; diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/advanced_settings_title/index.ts b/x-pack/legacy/plugins/spaces/public/advanced_settings/components/advanced_settings_title/index.ts similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/management/components/advanced_settings_title/index.ts rename to x-pack/legacy/plugins/spaces/public/advanced_settings/components/advanced_settings_title/index.ts diff --git a/x-pack/legacy/plugins/spaces/public/advanced_settings/components/index.ts b/x-pack/legacy/plugins/spaces/public/advanced_settings/components/index.ts new file mode 100644 index 0000000000000..6678be7fa34e4 --- /dev/null +++ b/x-pack/legacy/plugins/spaces/public/advanced_settings/components/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { AdvancedSettingsSubtitle } from './advanced_settings_subtitle'; +export { AdvancedSettingsTitle } from './advanced_settings_title'; diff --git a/x-pack/legacy/plugins/spaces/public/advanced_settings/index.ts b/x-pack/legacy/plugins/spaces/public/advanced_settings/index.ts new file mode 100644 index 0000000000000..546831a84fa82 --- /dev/null +++ b/x-pack/legacy/plugins/spaces/public/advanced_settings/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { AdvancedSettingsService } from './advanced_settings_service'; diff --git a/x-pack/legacy/plugins/spaces/public/components/index.ts b/x-pack/legacy/plugins/spaces/public/components/index.ts deleted file mode 100644 index 2e73f0c704f8c..0000000000000 --- a/x-pack/legacy/plugins/spaces/public/components/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export { SpaceAvatar } from './space_avatar'; -export { ManageSpacesButton } from './manage_spaces_button'; diff --git a/x-pack/legacy/plugins/spaces/public/lib/constants.ts b/x-pack/legacy/plugins/spaces/public/constants.ts similarity index 100% rename from x-pack/legacy/plugins/spaces/public/lib/constants.ts rename to x-pack/legacy/plugins/spaces/public/constants.ts diff --git a/src/legacy/ui/public/management/_index.scss b/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/_index.scss similarity index 100% rename from src/legacy/ui/public/management/_index.scss rename to x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/_index.scss diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/_index.scss b/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/_copy_to_space.scss similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/_index.scss rename to x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/_copy_to_space.scss diff --git a/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/_index.scss b/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/_index.scss new file mode 100644 index 0000000000000..92b19a8c8c6a3 --- /dev/null +++ b/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/_index.scss @@ -0,0 +1 @@ +@import './copy_to_space'; \ No newline at end of file diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_status_indicator.tsx b/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/copy_status_indicator.tsx similarity index 96% rename from x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_status_indicator.tsx rename to x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/copy_status_indicator.tsx index f9da25409d60e..ff9035ff02be5 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_status_indicator.tsx +++ b/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/copy_status_indicator.tsx @@ -7,10 +7,7 @@ import React from 'react'; import { EuiLoadingSpinner, EuiText, EuiIconTip } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { - SummarizedCopyToSpaceResult, - SummarizedSavedObjectResult, -} from '../../../../lib/copy_saved_objects_to_space'; +import { SummarizedCopyToSpaceResult, SummarizedSavedObjectResult } from '..'; interface Props { summarizedCopyResult: SummarizedCopyToSpaceResult; diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_status_summary_indicator.tsx b/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/copy_status_summary_indicator.tsx similarity index 94% rename from x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_status_summary_indicator.tsx rename to x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/copy_status_summary_indicator.tsx index 0ad5f72ba3e45..9d73c216c73ce 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_status_summary_indicator.tsx +++ b/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/copy_status_summary_indicator.tsx @@ -7,8 +7,8 @@ import React from 'react'; import { EuiLoadingSpinner, EuiIconTip } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { Space } from '../../../../../common/model/space'; -import { SummarizedCopyToSpaceResult } from '../../../../lib/copy_saved_objects_to_space'; +import { Space } from '../../../common/model/space'; +import { SummarizedCopyToSpaceResult } from '..'; interface Props { space: Space; diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_to_space_flyout.test.tsx b/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout.test.tsx similarity index 97% rename from x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_to_space_flyout.test.tsx rename to x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout.test.tsx index b3fd345b1d2b4..28011911e212e 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_to_space_flyout.test.tsx +++ b/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout.test.tsx @@ -6,20 +6,20 @@ import React from 'react'; import Boom from 'boom'; import { mountWithIntl, nextTick } from 'test_utils/enzyme_helpers'; -import { mockManagementPlugin } from '../../../../../../../../../src/legacy/core_plugins/management/public/np_ready/mocks'; +import { mockManagementPlugin } from '../../../../../../../src/legacy/core_plugins/management/public/np_ready/mocks'; import { CopySavedObjectsToSpaceFlyout } from './copy_to_space_flyout'; import { CopyToSpaceForm } from './copy_to_space_form'; import { EuiLoadingSpinner, EuiEmptyPrompt } from '@elastic/eui'; -import { Space } from '../../../../../common/model/space'; +import { Space } from '../../../common/model/space'; import { findTestSubject } from 'test_utils/find_test_subject'; import { SelectableSpacesControl } from './selectable_spaces_control'; import { act } from '@testing-library/react'; import { ProcessingCopyToSpace } from './processing_copy_to_space'; -import { spacesManagerMock } from '../../../../lib/mocks'; -import { SpacesManager } from '../../../../lib'; +import { spacesManagerMock } from '../../spaces_manager/mocks'; +import { SpacesManager } from '../../spaces_manager'; import { ToastNotifications } from 'ui/notify/toasts/toast_notifications'; -jest.mock('../../../../../../../../../src/legacy/core_plugins/management/public/legacy', () => ({ +jest.mock('../../../../../../../src/legacy/core_plugins/management/public/legacy', () => ({ setup: mockManagementPlugin.createSetupContract(), start: mockManagementPlugin.createStartContract(), })); @@ -404,6 +404,7 @@ describe('CopyToSpaceFlyout', () => { id: 'my-viz', error: { type: 'missing_references', + blocking: [], references: [{ type: 'index-pattern', id: 'missing-index-pattern' }], }, }, diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_to_space_flyout.tsx b/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout.tsx similarity index 95% rename from x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_to_space_flyout.tsx rename to x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout.tsx index 5a43e5878ab83..f486f2f24f13d 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_to_space_flyout.tsx +++ b/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout.tsx @@ -22,17 +22,17 @@ import { mapValues } from 'lodash'; import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { ToastNotifications } from 'ui/notify/toasts/toast_notifications'; -import { SavedObjectsManagementRecord } from '../../../../../../../../../src/legacy/core_plugins/management/public'; +import { SavedObjectsManagementRecord } from '../../../../../../../src/legacy/core_plugins/management/public'; import { ProcessedImportResponse, processImportResponse, -} from '../../../../../../../../../src/legacy/core_plugins/management/public'; -import { Space } from '../../../../../common/model/space'; -import { SpacesManager } from '../../../../lib'; +} from '../../../../../../../src/legacy/core_plugins/management/public'; +import { Space } from '../../../common/model/space'; +import { SpacesManager } from '../../spaces_manager'; import { ProcessingCopyToSpace } from './processing_copy_to_space'; import { CopyToSpaceFlyoutFooter } from './copy_to_space_flyout_footer'; import { CopyToSpaceForm } from './copy_to_space_form'; -import { CopyOptions, ImportRetry } from '../../../../lib/copy_saved_objects_to_space/types'; +import { CopyOptions, ImportRetry } from '../types'; interface Props { onClose: () => void; diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_to_space_flyout_footer.tsx b/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout_footer.tsx similarity index 97% rename from x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_to_space_flyout_footer.tsx rename to x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout_footer.tsx index 5853bebe3c669..56f39ce3ed4fb 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_to_space_flyout_footer.tsx +++ b/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_flyout_footer.tsx @@ -8,8 +8,8 @@ import React, { Fragment } from 'react'; import { EuiButton, EuiFlexGroup, EuiFlexItem, EuiStat, EuiHorizontalRule } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; -import { ProcessedImportResponse } from '../../../../../../../../../src/legacy/core_plugins/management/public'; -import { ImportRetry } from '../../../../lib/copy_saved_objects_to_space/types'; +import { ProcessedImportResponse } from '../../../../../../../src/legacy/core_plugins/management/public'; +import { ImportRetry } from '../types'; interface Props { copyInProgress: boolean; diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_to_space_form.tsx b/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_form.tsx similarity index 94% rename from x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_to_space_form.tsx rename to x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_form.tsx index 2a7e17c253f0b..f680793e27fe0 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/copy_to_space_form.tsx +++ b/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/copy_to_space_form.tsx @@ -14,8 +14,8 @@ import { EuiListGroupItem, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { CopyOptions } from '../../../../lib/copy_saved_objects_to_space/types'; -import { Space } from '../../../../../common/model/space'; +import { CopyOptions } from '../types'; +import { Space } from '../../../common/model/space'; import { SelectableSpacesControl } from './selectable_spaces_control'; interface Props { diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/index.ts b/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/index.ts similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/index.ts rename to x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/index.ts diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/processing_copy_to_space.tsx b/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/processing_copy_to_space.tsx similarity index 90% rename from x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/processing_copy_to_space.tsx rename to x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/processing_copy_to_space.tsx index b04c9598559b3..285abb828a011 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/processing_copy_to_space.tsx +++ b/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/processing_copy_to_space.tsx @@ -13,12 +13,12 @@ import { EuiHorizontalRule, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { SavedObjectsManagementRecord } from '../../../../../../../../../src/legacy/core_plugins/management/public'; -import { ProcessedImportResponse } from '../../../../../../../../../src/legacy/core_plugins/management/public'; -import { summarizeCopyResult } from '../../../../lib/copy_saved_objects_to_space'; -import { Space } from '../../../../../common/model/space'; -import { CopyOptions, ImportRetry } from '../../../../lib/copy_saved_objects_to_space/types'; +import { SavedObjectsManagementRecord } from '../../../../../../../src/legacy/core_plugins/management/public'; +import { ProcessedImportResponse } from '../../../../../../../src/legacy/core_plugins/management/public'; +import { Space } from '../../../common/model/space'; +import { CopyOptions, ImportRetry } from '../types'; import { SpaceResult } from './space_result'; +import { summarizeCopyResult } from '..'; interface Props { savedObject: SavedObjectsManagementRecord; diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/selectable_spaces_control.tsx b/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/selectable_spaces_control.tsx similarity index 95% rename from x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/selectable_spaces_control.tsx rename to x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/selectable_spaces_control.tsx index 42d5707531380..9cf81b1cc4486 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/selectable_spaces_control.tsx +++ b/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/selectable_spaces_control.tsx @@ -6,8 +6,8 @@ import React, { Fragment, useState } from 'react'; import { EuiSelectable, EuiLoadingSpinner } from '@elastic/eui'; -import { SpaceAvatar } from '../../../../components'; -import { Space } from '../../../../../common/model/space'; +import { SpaceAvatar } from '../../space_avatar'; +import { Space } from '../../../common/model/space'; interface Props { spaces: Space[]; diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/space_result.tsx b/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/space_result.tsx similarity index 86% rename from x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/space_result.tsx rename to x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/space_result.tsx index f71be12276be5..22f0767ba196e 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/space_result.tsx +++ b/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/space_result.tsx @@ -6,13 +6,13 @@ import React from 'react'; import { EuiAccordion, EuiFlexGroup, EuiFlexItem, EuiText, EuiSpacer } from '@elastic/eui'; -import { SavedObjectsManagementRecord } from '../../../../../../../../../src/legacy/core_plugins/management/public'; -import { SummarizedCopyToSpaceResult } from '../../../../lib/copy_saved_objects_to_space'; -import { SpaceAvatar } from '../../../../components'; -import { Space } from '../../../../../common/model/space'; +import { SavedObjectsManagementRecord } from '../../../../../../../src/legacy/core_plugins/management/public'; +import { SummarizedCopyToSpaceResult } from '../index'; +import { SpaceAvatar } from '../../space_avatar'; +import { Space } from '../../../common/model/space'; import { CopyStatusSummaryIndicator } from './copy_status_summary_indicator'; import { SpaceCopyResultDetails } from './space_result_details'; -import { ImportRetry } from '../../../../lib/copy_saved_objects_to_space/types'; +import { ImportRetry } from '../types'; interface Props { savedObject: SavedObjectsManagementRecord; diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/space_result_details.tsx b/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/space_result_details.tsx similarity index 93% rename from x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/space_result_details.tsx rename to x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/space_result_details.tsx index 66ec38331c89a..d3ab406b87c3e 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/components/copy_saved_objects_to_space/space_result_details.tsx +++ b/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/components/space_result_details.tsx @@ -5,13 +5,13 @@ */ import React from 'react'; -import { SummarizedCopyToSpaceResult } from 'plugins/spaces/lib/copy_saved_objects_to_space'; import { EuiText, EuiFlexGroup, EuiFlexItem, EuiButtonEmpty } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { SavedObjectsManagementRecord } from '../../../../../../../../../src/legacy/core_plugins/management/public'; -import { Space } from '../../../../../common/model/space'; +import { SummarizedCopyToSpaceResult } from '../index'; +import { SavedObjectsManagementRecord } from '../../../../../../../src/legacy/core_plugins/management/public'; +import { Space } from '../../../common/model/space'; import { CopyStatusIndicator } from './copy_status_indicator'; -import { ImportRetry } from '../../../../lib/copy_saved_objects_to_space/types'; +import { ImportRetry } from '../types'; interface Props { savedObject: SavedObjectsManagementRecord; diff --git a/x-pack/legacy/plugins/spaces/public/lib/copy_saved_objects_to_space/copy_saved_objects_to_space_action.tsx b/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/copy_saved_objects_to_space_action.tsx similarity index 89% rename from x-pack/legacy/plugins/spaces/public/lib/copy_saved_objects_to_space/copy_saved_objects_to_space_action.tsx rename to x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/copy_saved_objects_to_space_action.tsx index 3b0fffa38e785..c016494a4cdf9 100644 --- a/x-pack/legacy/plugins/spaces/public/lib/copy_saved_objects_to_space/copy_saved_objects_to_space_action.tsx +++ b/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/copy_saved_objects_to_space_action.tsx @@ -9,8 +9,8 @@ import { toastNotifications } from 'ui/notify'; import { SavedObjectsManagementAction, SavedObjectsManagementRecord, -} from '../../../../../../../src/legacy/core_plugins/management/public'; -import { CopySavedObjectsToSpaceFlyout } from '../../views/management/components/copy_saved_objects_to_space'; +} from '../../../../../../src/legacy/core_plugins/management/public'; +import { CopySavedObjectsToSpaceFlyout } from './components'; import { SpacesManager } from '../spaces_manager'; export class CopyToSpaceSavedObjectsManagementAction extends SavedObjectsManagementAction { diff --git a/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/copy_saved_objects_to_space_service.test.ts b/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/copy_saved_objects_to_space_service.test.ts new file mode 100644 index 0000000000000..63a59344dfe5d --- /dev/null +++ b/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/copy_saved_objects_to_space_service.test.ts @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ManagementSetup } from 'src/legacy/core_plugins/management/public'; +import { CopyToSpaceSavedObjectsManagementAction } from './copy_saved_objects_to_space_action'; +import { spacesManagerMock } from '../spaces_manager/mocks'; +import { CopySavedObjectsToSpaceService } from '.'; + +describe('CopySavedObjectsToSpaceService', () => { + describe('#setup', () => { + it('registers the CopyToSpaceSavedObjectsManagementAction', () => { + const deps = { + spacesManager: spacesManagerMock.create(), + // we don't have a proper NP mock for this yet + managementSetup: ({ + savedObjects: { + registry: { + has: jest.fn().mockReturnValue(false), + register: jest.fn(), + }, + }, + } as unknown) as ManagementSetup, + }; + + const service = new CopySavedObjectsToSpaceService(); + service.setup(deps); + + expect(deps.managementSetup.savedObjects.registry.register).toHaveBeenCalledTimes(1); + expect(deps.managementSetup.savedObjects.registry.register).toHaveBeenCalledWith( + expect.any(CopyToSpaceSavedObjectsManagementAction) + ); + }); + }); +}); diff --git a/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/copy_saved_objects_to_space_service.ts b/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/copy_saved_objects_to_space_service.ts new file mode 100644 index 0000000000000..37354f985a2fc --- /dev/null +++ b/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/copy_saved_objects_to_space_service.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ManagementSetup } from 'src/legacy/core_plugins/management/public'; +import { CopyToSpaceSavedObjectsManagementAction } from './copy_saved_objects_to_space_action'; +import { SpacesManager } from '../spaces_manager'; + +interface SetupDeps { + spacesManager: SpacesManager; + managementSetup: ManagementSetup; +} + +export class CopySavedObjectsToSpaceService { + public setup({ spacesManager, managementSetup }: SetupDeps) { + const action = new CopyToSpaceSavedObjectsManagementAction(spacesManager); + managementSetup.savedObjects.registry.register(action); + } +} diff --git a/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/index.ts b/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/index.ts new file mode 100644 index 0000000000000..06969a52a3d8d --- /dev/null +++ b/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export * from './summarize_copy_result'; +export { CopySavedObjectsToSpaceService } from './copy_saved_objects_to_space_service'; diff --git a/x-pack/legacy/plugins/spaces/public/lib/copy_saved_objects_to_space/summarize_copy_result.test.ts b/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/summarize_copy_result.test.ts similarity index 98% rename from x-pack/legacy/plugins/spaces/public/lib/copy_saved_objects_to_space/summarize_copy_result.test.ts rename to x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/summarize_copy_result.test.ts index 0352902072790..0244a35711e6f 100644 --- a/x-pack/legacy/plugins/spaces/public/lib/copy_saved_objects_to_space/summarize_copy_result.test.ts +++ b/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/summarize_copy_result.test.ts @@ -5,7 +5,7 @@ */ import { summarizeCopyResult } from './summarize_copy_result'; -import { ProcessedImportResponse } from '../../../../../../../src/legacy/core_plugins/management/public'; +import { ProcessedImportResponse } from '../../../../../../src/legacy/core_plugins/management/public'; const createSavedObjectsManagementRecord = () => ({ type: 'dashboard', diff --git a/x-pack/legacy/plugins/spaces/public/lib/copy_saved_objects_to_space/summarize_copy_result.ts b/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/summarize_copy_result.ts similarity index 96% rename from x-pack/legacy/plugins/spaces/public/lib/copy_saved_objects_to_space/summarize_copy_result.ts rename to x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/summarize_copy_result.ts index 8807489157d71..7bc47d35efc6c 100644 --- a/x-pack/legacy/plugins/spaces/public/lib/copy_saved_objects_to_space/summarize_copy_result.ts +++ b/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/summarize_copy_result.ts @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SavedObjectsManagementRecord } from '../../../../../../../src/legacy/core_plugins/management/public'; -import { ProcessedImportResponse } from '../../../../../../../src/legacy/core_plugins/management/public'; +import { SavedObjectsManagementRecord } from '../../../../../../src/legacy/core_plugins/management/public'; +import { ProcessedImportResponse } from '../../../../../../src/legacy/core_plugins/management/public'; export interface SummarizedSavedObjectResult { type: string; diff --git a/x-pack/legacy/plugins/spaces/public/lib/copy_saved_objects_to_space/types.ts b/x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/types.ts similarity index 100% rename from x-pack/legacy/plugins/spaces/public/lib/copy_saved_objects_to_space/types.ts rename to x-pack/legacy/plugins/spaces/public/copy_saved_objects_to_space/types.ts diff --git a/x-pack/legacy/plugins/spaces/public/create_feature_catalogue_entry.ts b/x-pack/legacy/plugins/spaces/public/create_feature_catalogue_entry.ts index 1f41bb89d7707..464066d2221de 100644 --- a/x-pack/legacy/plugins/spaces/public/create_feature_catalogue_entry.ts +++ b/x-pack/legacy/plugins/spaces/public/create_feature_catalogue_entry.ts @@ -9,7 +9,7 @@ import { FeatureCatalogueEntry, FeatureCatalogueCategory, } from '../../../../../src/plugins/home/public'; -import { getSpacesFeatureDescription } from './lib/constants'; +import { getSpacesFeatureDescription } from './constants'; export const createSpacesFeatureCatalogueEntry = (): FeatureCatalogueEntry => { return { diff --git a/x-pack/legacy/plugins/spaces/public/index.scss b/x-pack/legacy/plugins/spaces/public/index.scss index 7a40872b760cb..26269f1d31aa3 100644 --- a/x-pack/legacy/plugins/spaces/public/index.scss +++ b/x-pack/legacy/plugins/spaces/public/index.scss @@ -10,4 +10,7 @@ // spcChart__legend--small // spcChart__legend-isLoading -@import './views/index'; +@import './management/index'; +@import './nav_control/index'; +@import './space_selector/index'; +@import './copy_saved_objects_to_space/index'; diff --git a/x-pack/legacy/plugins/spaces/public/index.ts b/x-pack/legacy/plugins/spaces/public/index.ts index 9233aae9fb12f..53cb906a619d3 100644 --- a/x-pack/legacy/plugins/spaces/public/index.ts +++ b/x-pack/legacy/plugins/spaces/public/index.ts @@ -5,6 +5,8 @@ */ import { SpacesPlugin } from './plugin'; +export { SpaceAvatar } from './space_avatar'; + export const plugin = () => { return new SpacesPlugin(); }; diff --git a/x-pack/legacy/plugins/spaces/public/legacy.ts b/x-pack/legacy/plugins/spaces/public/legacy.ts index 99419206093e9..1dffbd2661714 100644 --- a/x-pack/legacy/plugins/spaces/public/legacy.ts +++ b/x-pack/legacy/plugins/spaces/public/legacy.ts @@ -4,15 +4,26 @@ * you may not use this file except in compliance with the Elastic License. */ +import { registerSettingsComponent } from 'ui/management'; import { npSetup, npStart } from 'ui/new_platform'; +import { setup as managementSetup } from '../../../../../src/legacy/core_plugins/management/public/legacy'; import { plugin } from '.'; -import { SpacesPlugin, PluginsSetup } from './plugin'; +import { SpacesPlugin, PluginsSetup, PluginsStart } from './plugin'; +import './management/legacy_page_routes'; const spacesPlugin: SpacesPlugin = plugin(); -const plugins: PluginsSetup = { +const pluginsSetup: PluginsSetup = { home: npSetup.plugins.home, + management: managementSetup, + __managementLegacyCompat: { + registerSettingsComponent, + }, }; -export const setup = spacesPlugin.setup(npSetup.core, plugins); -export const start = spacesPlugin.start(npStart.core); +const pluginsStart: PluginsStart = { + management: npStart.plugins.management, +}; + +export const setup = spacesPlugin.setup(npSetup.core, pluginsSetup); +export const start = spacesPlugin.start(npStart.core, pluginsStart); diff --git a/x-pack/legacy/plugins/spaces/public/lib/copy_saved_objects_to_space/index.ts b/x-pack/legacy/plugins/spaces/public/lib/copy_saved_objects_to_space/index.ts deleted file mode 100644 index be23d90cc242a..0000000000000 --- a/x-pack/legacy/plugins/spaces/public/lib/copy_saved_objects_to_space/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export * from './summarize_copy_result'; -export { CopyToSpaceSavedObjectsManagementAction } from './copy_saved_objects_to_space_action'; diff --git a/x-pack/legacy/plugins/spaces/public/lib/index.ts b/x-pack/legacy/plugins/spaces/public/lib/index.ts deleted file mode 100644 index 56ac7b8ff37f4..0000000000000 --- a/x-pack/legacy/plugins/spaces/public/lib/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export { SpacesManager } from './spaces_manager'; -export { getSpaceInitials, getSpaceColor, getSpaceImageUrl } from './space_attributes'; diff --git a/x-pack/legacy/plugins/spaces/public/management/_index.scss b/x-pack/legacy/plugins/spaces/public/management/_index.scss new file mode 100644 index 0000000000000..72deb1f1cde8d --- /dev/null +++ b/x-pack/legacy/plugins/spaces/public/management/_index.scss @@ -0,0 +1,4 @@ +@import './components/confirm_delete_modal/confirm_delete_modal'; +@import './edit_space/enabled_features/index'; +@import './edit_space/section_panel/section_panel'; + diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/__snapshots__/confirm_delete_modal.test.tsx.snap b/x-pack/legacy/plugins/spaces/public/management/components/confirm_delete_modal/__snapshots__/confirm_delete_modal.test.tsx.snap similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/management/components/__snapshots__/confirm_delete_modal.test.tsx.snap rename to x-pack/legacy/plugins/spaces/public/management/components/confirm_delete_modal/__snapshots__/confirm_delete_modal.test.tsx.snap diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/_confirm_delete_modal.scss b/x-pack/legacy/plugins/spaces/public/management/components/confirm_delete_modal/_confirm_delete_modal.scss similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/management/components/_confirm_delete_modal.scss rename to x-pack/legacy/plugins/spaces/public/management/components/confirm_delete_modal/_confirm_delete_modal.scss diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/confirm_delete_modal.test.tsx b/x-pack/legacy/plugins/spaces/public/management/components/confirm_delete_modal/confirm_delete_modal.test.tsx similarity index 94% rename from x-pack/legacy/plugins/spaces/public/views/management/components/confirm_delete_modal.test.tsx rename to x-pack/legacy/plugins/spaces/public/management/components/confirm_delete_modal/confirm_delete_modal.test.tsx index f0ab2c99ac2e2..331435b54edb7 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/components/confirm_delete_modal.test.tsx +++ b/x-pack/legacy/plugins/spaces/public/management/components/confirm_delete_modal/confirm_delete_modal.test.tsx @@ -7,8 +7,8 @@ import React from 'react'; import { mountWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers'; import { ConfirmDeleteModal } from './confirm_delete_modal'; -import { spacesManagerMock } from '../../../lib/mocks'; -import { SpacesManager } from '../../../lib'; +import { spacesManagerMock } from '../../../spaces_manager/mocks'; +import { SpacesManager } from '../../../spaces_manager'; describe('ConfirmDeleteModal', () => { it('renders as expected', () => { diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/confirm_delete_modal.tsx b/x-pack/legacy/plugins/spaces/public/management/components/confirm_delete_modal/confirm_delete_modal.tsx similarity index 98% rename from x-pack/legacy/plugins/spaces/public/views/management/components/confirm_delete_modal.tsx rename to x-pack/legacy/plugins/spaces/public/management/components/confirm_delete_modal/confirm_delete_modal.tsx index 0c76cb4a828fe..6eed58a784212 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/components/confirm_delete_modal.tsx +++ b/x-pack/legacy/plugins/spaces/public/management/components/confirm_delete_modal/confirm_delete_modal.tsx @@ -25,8 +25,8 @@ import { } from '@elastic/eui'; import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react'; import React, { ChangeEvent, Component } from 'react'; -import { Space } from '../../../../common/model/space'; -import { SpacesManager } from '../../../lib'; +import { SpacesManager } from '../../../spaces_manager'; +import { Space } from '../../../../../../plugins/spaces/common/model/space'; interface Props { space: Space; diff --git a/x-pack/legacy/plugins/spaces/public/management/components/confirm_delete_modal/index.ts b/x-pack/legacy/plugins/spaces/public/management/components/confirm_delete_modal/index.ts new file mode 100644 index 0000000000000..651455d00e9f2 --- /dev/null +++ b/x-pack/legacy/plugins/spaces/public/management/components/confirm_delete_modal/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { ConfirmDeleteModal } from './confirm_delete_modal'; diff --git a/x-pack/legacy/plugins/spaces/public/management/components/index.ts b/x-pack/legacy/plugins/spaces/public/management/components/index.ts new file mode 100644 index 0000000000000..7f9f80f470d12 --- /dev/null +++ b/x-pack/legacy/plugins/spaces/public/management/components/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { ConfirmDeleteModal } from './confirm_delete_modal'; +export { UnauthorizedPrompt } from './unauthorized_prompt'; +export { SecureSpaceMessage } from './secure_space_message'; diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/secure_space_message/__snapshots__/secure_space_message.test.tsx.snap b/x-pack/legacy/plugins/spaces/public/management/components/secure_space_message/__snapshots__/secure_space_message.test.tsx.snap similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/management/components/secure_space_message/__snapshots__/secure_space_message.test.tsx.snap rename to x-pack/legacy/plugins/spaces/public/management/components/secure_space_message/__snapshots__/secure_space_message.test.tsx.snap diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/secure_space_message/index.ts b/x-pack/legacy/plugins/spaces/public/management/components/secure_space_message/index.ts similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/management/components/secure_space_message/index.ts rename to x-pack/legacy/plugins/spaces/public/management/components/secure_space_message/index.ts diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/secure_space_message/secure_space_message.test.tsx b/x-pack/legacy/plugins/spaces/public/management/components/secure_space_message/secure_space_message.test.tsx similarity index 93% rename from x-pack/legacy/plugins/spaces/public/views/management/components/secure_space_message/secure_space_message.test.tsx rename to x-pack/legacy/plugins/spaces/public/management/components/secure_space_message/secure_space_message.test.tsx index 1cc6f6c1f72be..b43010fe5f326 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/components/secure_space_message/secure_space_message.test.tsx +++ b/x-pack/legacy/plugins/spaces/public/management/components/secure_space_message/secure_space_message.test.tsx @@ -8,7 +8,7 @@ import { shallowWithIntl } from 'test_utils/enzyme_helpers'; import { SecureSpaceMessage } from './secure_space_message'; let mockShowLinks: boolean = true; -jest.mock('../../../../../../xpack_main/public/services/xpack_info', () => { +jest.mock('../../../../../xpack_main/public/services/xpack_info', () => { return { xpackInfo: { get: jest.fn().mockImplementation((key: string) => { diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/secure_space_message/secure_space_message.tsx b/x-pack/legacy/plugins/spaces/public/management/components/secure_space_message/secure_space_message.tsx similarity index 94% rename from x-pack/legacy/plugins/spaces/public/views/management/components/secure_space_message/secure_space_message.tsx rename to x-pack/legacy/plugins/spaces/public/management/components/secure_space_message/secure_space_message.tsx index 6bbc423968b4b..746b7e2ac4c98 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/components/secure_space_message/secure_space_message.tsx +++ b/x-pack/legacy/plugins/spaces/public/management/components/secure_space_message/secure_space_message.tsx @@ -9,7 +9,7 @@ import { FormattedMessage } from '@kbn/i18n/react'; import { i18n } from '@kbn/i18n'; import React, { Fragment } from 'react'; // @ts-ignore -import { xpackInfo } from '../../../../../../xpack_main/public/services/xpack_info'; +import { xpackInfo } from '../../../../../xpack_main/public/services/xpack_info'; export const SecureSpaceMessage = ({}) => { const showSecurityLinks = xpackInfo.get('features.security.showLinks'); diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/__snapshots__/unauthorized_prompt.test.tsx.snap b/x-pack/legacy/plugins/spaces/public/management/components/unauthorized_prompt/__snapshots__/unauthorized_prompt.test.tsx.snap similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/management/components/__snapshots__/unauthorized_prompt.test.tsx.snap rename to x-pack/legacy/plugins/spaces/public/management/components/unauthorized_prompt/__snapshots__/unauthorized_prompt.test.tsx.snap diff --git a/x-pack/legacy/plugins/spaces/public/management/components/unauthorized_prompt/index.ts b/x-pack/legacy/plugins/spaces/public/management/components/unauthorized_prompt/index.ts new file mode 100644 index 0000000000000..5a8120a77804b --- /dev/null +++ b/x-pack/legacy/plugins/spaces/public/management/components/unauthorized_prompt/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { UnauthorizedPrompt } from './unauthorized_prompt'; diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/unauthorized_prompt.test.tsx b/x-pack/legacy/plugins/spaces/public/management/components/unauthorized_prompt/unauthorized_prompt.test.tsx similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/management/components/unauthorized_prompt.test.tsx rename to x-pack/legacy/plugins/spaces/public/management/components/unauthorized_prompt/unauthorized_prompt.test.tsx diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/unauthorized_prompt.tsx b/x-pack/legacy/plugins/spaces/public/management/components/unauthorized_prompt/unauthorized_prompt.tsx similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/management/components/unauthorized_prompt.tsx rename to x-pack/legacy/plugins/spaces/public/management/components/unauthorized_prompt/unauthorized_prompt.tsx diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/__snapshots__/delete_spaces_button.test.tsx.snap b/x-pack/legacy/plugins/spaces/public/management/edit_space/__snapshots__/delete_spaces_button.test.tsx.snap similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/management/edit_space/__snapshots__/delete_spaces_button.test.tsx.snap rename to x-pack/legacy/plugins/spaces/public/management/edit_space/__snapshots__/delete_spaces_button.test.tsx.snap diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/confirm_alter_active_space_modal/__snapshots__/confirm_alter_active_space_modal.test.tsx.snap b/x-pack/legacy/plugins/spaces/public/management/edit_space/confirm_alter_active_space_modal/__snapshots__/confirm_alter_active_space_modal.test.tsx.snap similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/management/edit_space/confirm_alter_active_space_modal/__snapshots__/confirm_alter_active_space_modal.test.tsx.snap rename to x-pack/legacy/plugins/spaces/public/management/edit_space/confirm_alter_active_space_modal/__snapshots__/confirm_alter_active_space_modal.test.tsx.snap diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/confirm_alter_active_space_modal/confirm_alter_active_space_modal.test.tsx b/x-pack/legacy/plugins/spaces/public/management/edit_space/confirm_alter_active_space_modal/confirm_alter_active_space_modal.test.tsx similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/management/edit_space/confirm_alter_active_space_modal/confirm_alter_active_space_modal.test.tsx rename to x-pack/legacy/plugins/spaces/public/management/edit_space/confirm_alter_active_space_modal/confirm_alter_active_space_modal.test.tsx diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/confirm_alter_active_space_modal/confirm_alter_active_space_modal.tsx b/x-pack/legacy/plugins/spaces/public/management/edit_space/confirm_alter_active_space_modal/confirm_alter_active_space_modal.tsx similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/management/edit_space/confirm_alter_active_space_modal/confirm_alter_active_space_modal.tsx rename to x-pack/legacy/plugins/spaces/public/management/edit_space/confirm_alter_active_space_modal/confirm_alter_active_space_modal.tsx diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/confirm_alter_active_space_modal/index.ts b/x-pack/legacy/plugins/spaces/public/management/edit_space/confirm_alter_active_space_modal/index.ts similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/management/edit_space/confirm_alter_active_space_modal/index.ts rename to x-pack/legacy/plugins/spaces/public/management/edit_space/confirm_alter_active_space_modal/index.ts diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/customize_space/__snapshots__/customize_space_avatar.test.tsx.snap b/x-pack/legacy/plugins/spaces/public/management/edit_space/customize_space/__snapshots__/customize_space_avatar.test.tsx.snap similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/management/edit_space/customize_space/__snapshots__/customize_space_avatar.test.tsx.snap rename to x-pack/legacy/plugins/spaces/public/management/edit_space/customize_space/__snapshots__/customize_space_avatar.test.tsx.snap diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/customize_space/__snapshots__/space_identifier.test.tsx.snap b/x-pack/legacy/plugins/spaces/public/management/edit_space/customize_space/__snapshots__/space_identifier.test.tsx.snap similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/management/edit_space/customize_space/__snapshots__/space_identifier.test.tsx.snap rename to x-pack/legacy/plugins/spaces/public/management/edit_space/customize_space/__snapshots__/space_identifier.test.tsx.snap diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/customize_space/customize_space.tsx b/x-pack/legacy/plugins/spaces/public/management/edit_space/customize_space/customize_space.tsx similarity index 97% rename from x-pack/legacy/plugins/spaces/public/views/management/edit_space/customize_space/customize_space.tsx rename to x-pack/legacy/plugins/spaces/public/management/edit_space/customize_space/customize_space.tsx index 8a7e384d44b35..b0d74afaa90aa 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/customize_space/customize_space.tsx +++ b/x-pack/legacy/plugins/spaces/public/management/edit_space/customize_space/customize_space.tsx @@ -18,9 +18,9 @@ import { } from '@elastic/eui'; import { FormattedMessage, InjectedIntl } from '@kbn/i18n/react'; import React, { ChangeEvent, Component, Fragment } from 'react'; -import { isReservedSpace } from '../../../../../common'; -import { Space } from '../../../../../common/model/space'; -import { SpaceAvatar } from '../../../../components'; +import { isReservedSpace } from '../../../../common'; +import { Space } from '../../../../common/model/space'; +import { SpaceAvatar } from '../../../space_avatar'; import { SpaceValidator, toSpaceIdentifier } from '../../lib'; import { SectionPanel } from '../section_panel'; import { CustomizeSpaceAvatar } from './customize_space_avatar'; diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/customize_space/customize_space_avatar.test.tsx b/x-pack/legacy/plugins/spaces/public/management/edit_space/customize_space/customize_space_avatar.test.tsx similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/management/edit_space/customize_space/customize_space_avatar.test.tsx rename to x-pack/legacy/plugins/spaces/public/management/edit_space/customize_space/customize_space_avatar.test.tsx diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/customize_space/customize_space_avatar.tsx b/x-pack/legacy/plugins/spaces/public/management/edit_space/customize_space/customize_space_avatar.tsx similarity index 95% rename from x-pack/legacy/plugins/spaces/public/views/management/edit_space/customize_space/customize_space_avatar.tsx rename to x-pack/legacy/plugins/spaces/public/management/edit_space/customize_space/customize_space_avatar.tsx index 12fa0193b59a4..c3207c82bf95e 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/customize_space/customize_space_avatar.tsx +++ b/x-pack/legacy/plugins/spaces/public/management/edit_space/customize_space/customize_space_avatar.tsx @@ -17,12 +17,10 @@ import { isValidHex, } from '@elastic/eui'; import { InjectedIntl, injectI18n } from '@kbn/i18n/react'; - -import { getSpaceColor, getSpaceInitials } from '../../../../lib/space_attributes'; -import { encode, imageTypes } from '../../../../../common/lib/dataurl'; - -import { MAX_SPACE_INITIALS } from '../../../../../common/constants'; -import { Space } from '../../../../../common/model/space'; +import { imageTypes, encode } from '../../../../common/lib/dataurl'; +import { getSpaceColor, getSpaceInitials } from '../../../space_avatar'; +import { Space } from '../../../../../../../plugins/spaces/common/model/space'; +import { MAX_SPACE_INITIALS } from '../../../../../../../plugins/spaces/common'; interface Props { space: Partial; diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/customize_space/index.ts b/x-pack/legacy/plugins/spaces/public/management/edit_space/customize_space/index.ts similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/management/edit_space/customize_space/index.ts rename to x-pack/legacy/plugins/spaces/public/management/edit_space/customize_space/index.ts diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/customize_space/space_identifier.test.tsx b/x-pack/legacy/plugins/spaces/public/management/edit_space/customize_space/space_identifier.test.tsx similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/management/edit_space/customize_space/space_identifier.test.tsx rename to x-pack/legacy/plugins/spaces/public/management/edit_space/customize_space/space_identifier.test.tsx diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/customize_space/space_identifier.tsx b/x-pack/legacy/plugins/spaces/public/management/edit_space/customize_space/space_identifier.tsx similarity index 98% rename from x-pack/legacy/plugins/spaces/public/views/management/edit_space/customize_space/space_identifier.tsx rename to x-pack/legacy/plugins/spaces/public/management/edit_space/customize_space/space_identifier.tsx index a717570b19c5d..1d6664273d21e 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/customize_space/space_identifier.tsx +++ b/x-pack/legacy/plugins/spaces/public/management/edit_space/customize_space/space_identifier.tsx @@ -7,7 +7,7 @@ import { EuiFieldText, EuiFormRow, EuiLink } from '@elastic/eui'; import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react'; import React, { ChangeEvent, Component, Fragment } from 'react'; -import { Space } from '../../../../../common/model/space'; +import { Space } from '../../../../common/model/space'; import { SpaceValidator, toSpaceIdentifier } from '../../lib'; interface Props { diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/delete_spaces_button.test.tsx b/x-pack/legacy/plugins/spaces/public/management/edit_space/delete_spaces_button.test.tsx similarity index 88% rename from x-pack/legacy/plugins/spaces/public/views/management/edit_space/delete_spaces_button.test.tsx rename to x-pack/legacy/plugins/spaces/public/management/edit_space/delete_spaces_button.test.tsx index e7c7dfc5eb1b0..364145d6495b8 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/delete_spaces_button.test.tsx +++ b/x-pack/legacy/plugins/spaces/public/management/edit_space/delete_spaces_button.test.tsx @@ -7,8 +7,8 @@ import React from 'react'; import { shallowWithIntl } from 'test_utils/enzyme_helpers'; import { DeleteSpacesButton } from './delete_spaces_button'; -import { spacesManagerMock } from '../../../lib/mocks'; -import { SpacesManager } from '../../../lib'; +import { spacesManagerMock } from '../../spaces_manager/mocks'; +import { SpacesManager } from '../../spaces_manager'; const space = { id: 'my-space', diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/delete_spaces_button.tsx b/x-pack/legacy/plugins/spaces/public/management/edit_space/delete_spaces_button.tsx similarity index 96% rename from x-pack/legacy/plugins/spaces/public/views/management/edit_space/delete_spaces_button.tsx rename to x-pack/legacy/plugins/spaces/public/management/edit_space/delete_spaces_button.tsx index 216dd7c41f124..56a858eb4ccf6 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/delete_spaces_button.tsx +++ b/x-pack/legacy/plugins/spaces/public/management/edit_space/delete_spaces_button.tsx @@ -7,10 +7,9 @@ import { EuiButton, EuiButtonIcon, EuiButtonIconProps } from '@elastic/eui'; import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react'; import React, { Component, Fragment } from 'react'; -// @ts-ignore import { toastNotifications } from 'ui/notify'; -import { Space } from '../../../../common/model/space'; -import { SpacesManager } from '../../../lib/spaces_manager'; +import { Space } from '../../../common/model/space'; +import { SpacesManager } from '../../spaces_manager'; import { ConfirmDeleteModal } from '../components/confirm_delete_modal'; interface Props { diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/enabled_features/__snapshots__/enabled_features.test.tsx.snap b/x-pack/legacy/plugins/spaces/public/management/edit_space/enabled_features/__snapshots__/enabled_features.test.tsx.snap similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/management/edit_space/enabled_features/__snapshots__/enabled_features.test.tsx.snap rename to x-pack/legacy/plugins/spaces/public/management/edit_space/enabled_features/__snapshots__/enabled_features.test.tsx.snap diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/enabled_features/_index.scss b/x-pack/legacy/plugins/spaces/public/management/edit_space/enabled_features/_index.scss similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/management/edit_space/enabled_features/_index.scss rename to x-pack/legacy/plugins/spaces/public/management/edit_space/enabled_features/_index.scss diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/enabled_features/enabled_features.test.tsx b/x-pack/legacy/plugins/spaces/public/management/edit_space/enabled_features/enabled_features.test.tsx similarity index 95% rename from x-pack/legacy/plugins/spaces/public/views/management/edit_space/enabled_features/enabled_features.test.tsx rename to x-pack/legacy/plugins/spaces/public/management/edit_space/enabled_features/enabled_features.test.tsx index f8bd4b889394a..f770857d9313d 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/enabled_features/enabled_features.test.tsx +++ b/x-pack/legacy/plugins/spaces/public/management/edit_space/enabled_features/enabled_features.test.tsx @@ -7,8 +7,8 @@ import { EuiLink } from '@elastic/eui'; import React from 'react'; import { mountWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers'; -import { Feature } from '../../../../../../../../plugins/features/public'; -import { Space } from '../../../../../common/model/space'; +import { Feature } from '../../../../../../../plugins/features/public'; +import { Space } from '../../../../common/model/space'; import { SectionPanel } from '../section_panel'; import { EnabledFeatures } from './enabled_features'; diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/enabled_features/enabled_features.tsx b/x-pack/legacy/plugins/spaces/public/management/edit_space/enabled_features/enabled_features.tsx similarity index 97% rename from x-pack/legacy/plugins/spaces/public/views/management/edit_space/enabled_features/enabled_features.tsx rename to x-pack/legacy/plugins/spaces/public/management/edit_space/enabled_features/enabled_features.tsx index 628be759b7c5c..70312296f757b 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/enabled_features/enabled_features.tsx +++ b/x-pack/legacy/plugins/spaces/public/management/edit_space/enabled_features/enabled_features.tsx @@ -8,8 +8,8 @@ import { EuiFlexGroup, EuiFlexItem, EuiLink, EuiSpacer, EuiText, EuiTitle } from import { FormattedMessage, InjectedIntl } from '@kbn/i18n/react'; import React, { Component, Fragment, ReactNode } from 'react'; import { Capabilities } from 'src/core/public'; -import { Feature } from '../../../../../../../../plugins/features/public'; -import { Space } from '../../../../../common/model/space'; +import { Feature } from '../../../../../../../plugins/features/public'; +import { Space } from '../../../../common/model/space'; import { getEnabledFeatures } from '../../lib/feature_utils'; import { SectionPanel } from '../section_panel'; import { FeatureTable } from './feature_table'; diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/enabled_features/feature_table.tsx b/x-pack/legacy/plugins/spaces/public/management/edit_space/enabled_features/feature_table.tsx similarity index 92% rename from x-pack/legacy/plugins/spaces/public/views/management/edit_space/enabled_features/feature_table.tsx rename to x-pack/legacy/plugins/spaces/public/management/edit_space/enabled_features/feature_table.tsx index b3654b4d35bd3..2866d0bfa8cf3 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/enabled_features/feature_table.tsx +++ b/x-pack/legacy/plugins/spaces/public/management/edit_space/enabled_features/feature_table.tsx @@ -3,13 +3,13 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -// @ts-ignore -import { EuiCheckbox, EuiIcon, EuiInMemoryTable, EuiSwitch, EuiText, IconType } from '@elastic/eui'; + +import { EuiIcon, EuiInMemoryTable, EuiSwitch, EuiText, IconType } from '@elastic/eui'; import { FormattedMessage, InjectedIntl } from '@kbn/i18n/react'; import _ from 'lodash'; import React, { ChangeEvent, Component } from 'react'; -import { Feature } from '../../../../../../../../plugins/features/public'; -import { Space } from '../../../../../common/model/space'; +import { Feature } from '../../../../../../../plugins/features/public'; +import { Space } from '../../../../common/model/space'; import { ToggleAllFeatures } from './toggle_all_features'; interface Props { diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/enabled_features/index.ts b/x-pack/legacy/plugins/spaces/public/management/edit_space/enabled_features/index.ts similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/management/edit_space/enabled_features/index.ts rename to x-pack/legacy/plugins/spaces/public/management/edit_space/enabled_features/index.ts diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/enabled_features/toggle_all_features.tsx b/x-pack/legacy/plugins/spaces/public/management/edit_space/enabled_features/toggle_all_features.tsx similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/management/edit_space/enabled_features/toggle_all_features.tsx rename to x-pack/legacy/plugins/spaces/public/management/edit_space/enabled_features/toggle_all_features.tsx diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/index.ts b/x-pack/legacy/plugins/spaces/public/management/edit_space/index.ts similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/management/edit_space/index.ts rename to x-pack/legacy/plugins/spaces/public/management/edit_space/index.ts diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/manage_space_page.test.tsx b/x-pack/legacy/plugins/spaces/public/management/edit_space/manage_space_page.test.tsx similarity index 95% rename from x-pack/legacy/plugins/spaces/public/views/management/edit_space/manage_space_page.test.tsx rename to x-pack/legacy/plugins/spaces/public/management/edit_space/manage_space_page.test.tsx index c69a885ae0587..d24e932bce112 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/manage_space_page.test.tsx +++ b/x-pack/legacy/plugins/spaces/public/management/edit_space/manage_space_page.test.tsx @@ -6,7 +6,7 @@ jest.mock('ui/kfetch', () => ({ kfetch: () => Promise.resolve([{ id: 'feature-1', name: 'feature 1', icon: 'spacesApp' }]), })); -import '../../../__mocks__/xpack_info'; +import '../../__mocks__/xpack_info'; import { EuiButton, EuiLink, EuiSwitch } from '@elastic/eui'; import { ReactWrapper } from 'enzyme'; import React from 'react'; @@ -14,8 +14,8 @@ import { mountWithIntl } from 'test_utils/enzyme_helpers'; import { ConfirmAlterActiveSpaceModal } from './confirm_alter_active_space_modal'; import { ManageSpacePage } from './manage_space_page'; import { SectionPanel } from './section_panel'; -import { spacesManagerMock } from '../../../lib/mocks'; -import { SpacesManager } from '../../../lib'; +import { spacesManagerMock } from '../../spaces_manager/mocks'; +import { SpacesManager } from '../../spaces_manager'; const space = { id: 'my-space', @@ -65,21 +65,28 @@ describe('ManageSpacePage', () => { }); it('allows a space to be updated', async () => { - const spacesManager = spacesManagerMock.create(); - spacesManager.getSpace = jest.fn().mockResolvedValue({ + const spaceToUpdate = { id: 'existing-space', name: 'Existing Space', description: 'hey an existing space', color: '#aabbcc', initials: 'AB', disabledFeatures: [], + }; + + const spacesManager = spacesManagerMock.create(); + spacesManager.getSpace = jest.fn().mockResolvedValue({ + ...spaceToUpdate, }); spacesManager.getActiveSpace = jest.fn().mockResolvedValue(space); + const onLoadSpace = jest.fn(); + const wrapper = mountWithIntl( { await waitForDataLoad(wrapper); expect(spacesManager.getSpace).toHaveBeenCalledWith('existing-space'); + expect(onLoadSpace).toHaveBeenCalledWith({ + ...spaceToUpdate, + }); await Promise.resolve(); diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/manage_space_page.tsx b/x-pack/legacy/plugins/spaces/public/management/edit_space/manage_space_page.tsx similarity index 93% rename from x-pack/legacy/plugins/spaces/public/views/management/edit_space/manage_space_page.tsx rename to x-pack/legacy/plugins/spaces/public/management/edit_space/manage_space_page.tsx index a5d60d1a731ba..6bbb32ccd654f 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/manage_space_page.tsx +++ b/x-pack/legacy/plugins/spaces/public/management/edit_space/manage_space_page.tsx @@ -17,17 +17,15 @@ import { import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react'; import _ from 'lodash'; import React, { Component, Fragment } from 'react'; -import { Breadcrumb } from 'ui/chrome'; import { kfetch } from 'ui/kfetch'; import { toastNotifications } from 'ui/notify'; import { Capabilities } from 'src/core/public'; -import { Feature } from '../../../../../../../plugins/features/public'; -import { isReservedSpace } from '../../../../common'; -import { Space } from '../../../../common/model/space'; -import { SpacesManager } from '../../../lib'; -import { SecureSpaceMessage } from '../components/secure_space_message'; -import { UnauthorizedPrompt } from '../components/unauthorized_prompt'; -import { getEditBreadcrumbs, toSpaceIdentifier } from '../lib'; +import { Feature } from '../../../../../../plugins/features/public'; +import { isReservedSpace } from '../../../common'; +import { Space } from '../../../common/model/space'; +import { SpacesManager } from '../../spaces_manager'; +import { SecureSpaceMessage, UnauthorizedPrompt } from '../components'; +import { toSpaceIdentifier } from '../lib'; import { SpaceValidator } from '../lib/validate_space'; import { ConfirmAlterActiveSpaceModal } from './confirm_alter_active_space_modal'; import { CustomizeSpace } from './customize_space'; @@ -39,7 +37,7 @@ interface Props { spacesManager: SpacesManager; spaceId?: string; intl: InjectedIntl; - setBreadcrumbs?: (breadcrumbs: Breadcrumb[]) => void; + onLoadSpace?: (space: Space) => void; capabilities: Capabilities; } @@ -76,7 +74,7 @@ class ManageSpacePageUI extends Component { return; } - const { spaceId, spacesManager, intl, setBreadcrumbs } = this.props; + const { spaceId, spacesManager, intl, onLoadSpace } = this.props; const getFeatures = kfetch({ method: 'get', pathname: '/api/features' }); @@ -84,8 +82,8 @@ class ManageSpacePageUI extends Component { try { const [space, features] = await Promise.all([spacesManager.getSpace(spaceId), getFeatures]); if (space) { - if (setBreadcrumbs) { - setBreadcrumbs(getEditBreadcrumbs(space)); + if (onLoadSpace) { + onLoadSpace(space); } this.setState({ diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/reserved_space_badge.test.tsx b/x-pack/legacy/plugins/spaces/public/management/edit_space/reserved_space_badge.test.tsx similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/management/edit_space/reserved_space_badge.test.tsx rename to x-pack/legacy/plugins/spaces/public/management/edit_space/reserved_space_badge.test.tsx diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/reserved_space_badge.tsx b/x-pack/legacy/plugins/spaces/public/management/edit_space/reserved_space_badge.tsx similarity index 89% rename from x-pack/legacy/plugins/spaces/public/views/management/edit_space/reserved_space_badge.tsx rename to x-pack/legacy/plugins/spaces/public/management/edit_space/reserved_space_badge.tsx index e4b2dda3a668b..38bf351902096 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/reserved_space_badge.tsx +++ b/x-pack/legacy/plugins/spaces/public/management/edit_space/reserved_space_badge.tsx @@ -8,8 +8,8 @@ import React from 'react'; import { EuiIcon, EuiToolTip } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import { isReservedSpace } from '../../../../common'; -import { Space } from '../../../../common/model/space'; +import { isReservedSpace } from '../../../common'; +import { Space } from '../../../common/model/space'; interface Props { space?: Space; diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/section_panel/__snapshots__/section_panel.test.tsx.snap b/x-pack/legacy/plugins/spaces/public/management/edit_space/section_panel/__snapshots__/section_panel.test.tsx.snap similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/management/edit_space/section_panel/__snapshots__/section_panel.test.tsx.snap rename to x-pack/legacy/plugins/spaces/public/management/edit_space/section_panel/__snapshots__/section_panel.test.tsx.snap diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/section_panel/_section_panel.scss b/x-pack/legacy/plugins/spaces/public/management/edit_space/section_panel/_section_panel.scss similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/management/edit_space/section_panel/_section_panel.scss rename to x-pack/legacy/plugins/spaces/public/management/edit_space/section_panel/_section_panel.scss diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/section_panel/index.ts b/x-pack/legacy/plugins/spaces/public/management/edit_space/section_panel/index.ts similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/management/edit_space/section_panel/index.ts rename to x-pack/legacy/plugins/spaces/public/management/edit_space/section_panel/index.ts diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/section_panel/section_panel.test.tsx b/x-pack/legacy/plugins/spaces/public/management/edit_space/section_panel/section_panel.test.tsx similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/management/edit_space/section_panel/section_panel.test.tsx rename to x-pack/legacy/plugins/spaces/public/management/edit_space/section_panel/section_panel.test.tsx diff --git a/x-pack/legacy/plugins/spaces/public/views/management/edit_space/section_panel/section_panel.tsx b/x-pack/legacy/plugins/spaces/public/management/edit_space/section_panel/section_panel.tsx similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/management/edit_space/section_panel/section_panel.tsx rename to x-pack/legacy/plugins/spaces/public/management/edit_space/section_panel/section_panel.tsx diff --git a/x-pack/legacy/plugins/spaces/public/management/index.ts b/x-pack/legacy/plugins/spaces/public/management/index.ts new file mode 100644 index 0000000000000..ad3cc6b245619 --- /dev/null +++ b/x-pack/legacy/plugins/spaces/public/management/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { ManagementService } from './management_service'; diff --git a/x-pack/legacy/plugins/spaces/public/management/legacy_page_routes.tsx b/x-pack/legacy/plugins/spaces/public/management/legacy_page_routes.tsx new file mode 100644 index 0000000000000..8cf4a129e5b8f --- /dev/null +++ b/x-pack/legacy/plugins/spaces/public/management/legacy_page_routes.tsx @@ -0,0 +1,149 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +// @ts-ignore +import template from 'plugins/spaces/management/template.html'; +import React from 'react'; +import { render, unmountComponentAtNode } from 'react-dom'; +import { I18nContext } from 'ui/i18n'; +// @ts-ignore +import routes from 'ui/routes'; +import { MANAGEMENT_BREADCRUMB } from 'ui/management/breadcrumbs'; +import { npStart } from 'ui/new_platform'; +import { ManageSpacePage } from './edit_space'; +import { SpacesGridPage } from './spaces_grid'; + +import { start as spacesNPStart } from '../legacy'; +import { Space } from '../../common/model/space'; + +const reactRootNodeId = 'manageSpacesReactRoot'; + +function getListBreadcrumbs() { + return [ + MANAGEMENT_BREADCRUMB, + { + text: 'Spaces', + href: '#/management/spaces/list', + }, + ]; +} + +function getCreateBreadcrumbs() { + return [ + ...getListBreadcrumbs(), + { + text: 'Create', + }, + ]; +} + +function getEditBreadcrumbs(space?: Space) { + return [ + ...getListBreadcrumbs(), + { + text: space ? space.name : '...', + }, + ]; +} + +routes.when('/management/spaces/list', { + template, + k7Breadcrumbs: getListBreadcrumbs, + requireUICapability: 'management.kibana.spaces', + controller($scope: any) { + $scope.$$postDigest(() => { + const domNode = document.getElementById(reactRootNodeId); + + const { spacesManager } = spacesNPStart; + + render( + + + , + domNode + ); + + // unmount react on controller destroy + $scope.$on('$destroy', () => { + if (domNode) { + unmountComponentAtNode(domNode); + } + }); + }); + }, +}); + +routes.when('/management/spaces/create', { + template, + k7Breadcrumbs: getCreateBreadcrumbs, + requireUICapability: 'management.kibana.spaces', + controller($scope: any) { + $scope.$$postDigest(() => { + const domNode = document.getElementById(reactRootNodeId); + + const { spacesManager } = spacesNPStart; + + render( + + + , + domNode + ); + + // unmount react on controller destroy + $scope.$on('$destroy', () => { + if (domNode) { + unmountComponentAtNode(domNode); + } + }); + }); + }, +}); + +routes.when('/management/spaces/edit', { + redirectTo: '/management/spaces/list', +}); + +routes.when('/management/spaces/edit/:spaceId', { + template, + k7Breadcrumbs: () => getEditBreadcrumbs(), + requireUICapability: 'management.kibana.spaces', + controller($scope: any, $route: any) { + $scope.$$postDigest(async () => { + const domNode = document.getElementById(reactRootNodeId); + + const { spaceId } = $route.current.params; + + const { spacesManager } = await spacesNPStart; + + render( + + { + npStart.core.chrome.setBreadcrumbs(getEditBreadcrumbs(space)); + }} + capabilities={npStart.core.application.capabilities} + /> + , + domNode + ); + + // unmount react on controller destroy + $scope.$on('$destroy', () => { + if (domNode) { + unmountComponentAtNode(domNode); + } + }); + }); + }, +}); diff --git a/x-pack/legacy/plugins/spaces/public/views/management/lib/feature_utils.test.ts b/x-pack/legacy/plugins/spaces/public/management/lib/feature_utils.test.ts similarity index 94% rename from x-pack/legacy/plugins/spaces/public/views/management/lib/feature_utils.test.ts rename to x-pack/legacy/plugins/spaces/public/management/lib/feature_utils.test.ts index 8621ec5614368..ce874956d0ef2 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/lib/feature_utils.test.ts +++ b/x-pack/legacy/plugins/spaces/public/management/lib/feature_utils.test.ts @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Feature } from '../../../../../../../plugins/features/public'; import { getEnabledFeatures } from './feature_utils'; +import { Feature } from '../../../../../../plugins/features/public'; const buildFeatures = () => [ diff --git a/x-pack/legacy/plugins/spaces/public/management/lib/feature_utils.ts b/x-pack/legacy/plugins/spaces/public/management/lib/feature_utils.ts new file mode 100644 index 0000000000000..ff1688637ef73 --- /dev/null +++ b/x-pack/legacy/plugins/spaces/public/management/lib/feature_utils.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Feature } from '../../../../../../plugins/features/common'; + +import { Space } from '../../../../../../plugins/spaces/common/model/space'; + +export function getEnabledFeatures(features: Feature[], space: Partial) { + return features.filter(feature => !(space.disabledFeatures || []).includes(feature.id)); +} diff --git a/x-pack/legacy/plugins/spaces/public/management/lib/index.ts b/x-pack/legacy/plugins/spaces/public/management/lib/index.ts new file mode 100644 index 0000000000000..4a158168febd8 --- /dev/null +++ b/x-pack/legacy/plugins/spaces/public/management/lib/index.ts @@ -0,0 +1,9 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { toSpaceIdentifier, isValidSpaceIdentifier } from './space_identifier_utils'; + +export { SpaceValidator } from './validate_space'; diff --git a/x-pack/legacy/plugins/spaces/public/views/management/lib/space_identifier_utils.test.ts b/x-pack/legacy/plugins/spaces/public/management/lib/space_identifier_utils.test.ts similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/management/lib/space_identifier_utils.test.ts rename to x-pack/legacy/plugins/spaces/public/management/lib/space_identifier_utils.test.ts diff --git a/x-pack/legacy/plugins/spaces/public/views/management/lib/space_identifier_utils.ts b/x-pack/legacy/plugins/spaces/public/management/lib/space_identifier_utils.ts similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/management/lib/space_identifier_utils.ts rename to x-pack/legacy/plugins/spaces/public/management/lib/space_identifier_utils.ts diff --git a/x-pack/legacy/plugins/spaces/public/views/management/lib/validate_space.test.ts b/x-pack/legacy/plugins/spaces/public/management/lib/validate_space.test.ts similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/management/lib/validate_space.test.ts rename to x-pack/legacy/plugins/spaces/public/management/lib/validate_space.test.ts diff --git a/x-pack/legacy/plugins/spaces/public/views/management/lib/validate_space.ts b/x-pack/legacy/plugins/spaces/public/management/lib/validate_space.ts similarity index 96% rename from x-pack/legacy/plugins/spaces/public/views/management/lib/validate_space.ts rename to x-pack/legacy/plugins/spaces/public/management/lib/validate_space.ts index e7b9116131431..43d42dacdd36d 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/lib/validate_space.ts +++ b/x-pack/legacy/plugins/spaces/public/management/lib/validate_space.ts @@ -4,8 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ import { i18n } from '@kbn/i18n'; -import { isReservedSpace } from '../../../../common/is_reserved_space'; -import { Space } from '../../../../common/model/space'; +import { isReservedSpace } from '../../../common/is_reserved_space'; +import { Space } from '../../../common/model/space'; import { isValidSpaceIdentifier } from './space_identifier_utils'; interface SpaceValidatorOptions { diff --git a/x-pack/legacy/plugins/spaces/public/management/management_service.test.ts b/x-pack/legacy/plugins/spaces/public/management/management_service.test.ts new file mode 100644 index 0000000000000..fbd39db6969bd --- /dev/null +++ b/x-pack/legacy/plugins/spaces/public/management/management_service.test.ts @@ -0,0 +1,123 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ManagementService } from '.'; + +const mockSections = { + getSection: jest.fn(), + getAllSections: jest.fn(), + navigateToApp: jest.fn(), +}; + +describe('ManagementService', () => { + describe('#start', () => { + it('registers the spaces management page under the kibana section', () => { + const mockKibanaSection = { + hasItem: jest.fn().mockReturnValue(false), + register: jest.fn(), + }; + + const managementStart = { + legacy: { + getSection: jest.fn().mockReturnValue(mockKibanaSection), + }, + sections: mockSections, + }; + + const deps = { + managementStart, + }; + + const service = new ManagementService(); + service.start(deps); + + expect(deps.managementStart.legacy.getSection).toHaveBeenCalledTimes(1); + expect(deps.managementStart.legacy.getSection).toHaveBeenCalledWith('kibana'); + + expect(mockKibanaSection.register).toHaveBeenCalledTimes(1); + expect(mockKibanaSection.register).toHaveBeenCalledWith('spaces', { + name: 'spacesManagementLink', + order: 10, + display: 'Spaces', + url: `#/management/spaces/list`, + }); + }); + + it('will not register the spaces management page twice', () => { + const mockKibanaSection = { + hasItem: jest.fn().mockReturnValue(true), + register: jest.fn(), + }; + + const managementStart = { + legacy: { + getSection: jest.fn().mockReturnValue(mockKibanaSection), + }, + sections: mockSections, + }; + + const deps = { + managementStart, + }; + + const service = new ManagementService(); + service.start(deps); + + expect(mockKibanaSection.register).toHaveBeenCalledTimes(0); + }); + + it('will not register the spaces management page if the kibana section is missing', () => { + const managementStart = { + legacy: { + getSection: jest.fn().mockReturnValue(undefined), + }, + sections: mockSections, + }; + + const deps = { + managementStart, + }; + + const service = new ManagementService(); + service.start(deps); + + expect(deps.managementStart.legacy.getSection).toHaveBeenCalledTimes(1); + }); + }); + + describe('#stop', () => { + it('deregisters the spaces management page', () => { + const mockKibanaSection = { + hasItem: jest + .fn() + .mockReturnValueOnce(false) + .mockReturnValueOnce(true), + register: jest.fn(), + deregister: jest.fn(), + }; + + const managementStart = { + legacy: { + getSection: jest.fn().mockReturnValue(mockKibanaSection), + }, + sections: mockSections, + }; + + const deps = { + managementStart, + }; + + const service = new ManagementService(); + service.start(deps); + + service.stop(); + + expect(mockKibanaSection.register).toHaveBeenCalledTimes(1); + expect(mockKibanaSection.deregister).toHaveBeenCalledTimes(1); + expect(mockKibanaSection.deregister).toHaveBeenCalledWith('spaces'); + }); + }); +}); diff --git a/x-pack/legacy/plugins/spaces/public/management/management_service.ts b/x-pack/legacy/plugins/spaces/public/management/management_service.ts new file mode 100644 index 0000000000000..ada38f5cf3387 --- /dev/null +++ b/x-pack/legacy/plugins/spaces/public/management/management_service.ts @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { i18n } from '@kbn/i18n'; +import { ManagementStart } from 'src/plugins/management/public'; + +interface StartDeps { + managementStart: ManagementStart; +} + +const MANAGE_SPACES_KEY = 'spaces'; + +export class ManagementService { + private kibanaSection!: any; + + public start({ managementStart }: StartDeps) { + this.kibanaSection = managementStart.legacy.getSection('kibana'); + if (this.kibanaSection && !this.kibanaSection.hasItem(MANAGE_SPACES_KEY)) { + this.kibanaSection.register(MANAGE_SPACES_KEY, { + name: 'spacesManagementLink', + order: 10, + display: i18n.translate('xpack.spaces.displayName', { + defaultMessage: 'Spaces', + }), + url: `#/management/spaces/list`, + }); + } + } + + public stop() { + if (this.kibanaSection && this.kibanaSection.hasItem(MANAGE_SPACES_KEY)) { + this.kibanaSection.deregister(MANAGE_SPACES_KEY); + } + } +} diff --git a/x-pack/legacy/plugins/spaces/public/views/management/spaces_grid/__snapshots__/spaces_grid_pages.test.tsx.snap b/x-pack/legacy/plugins/spaces/public/management/spaces_grid/__snapshots__/spaces_grid_pages.test.tsx.snap similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/management/spaces_grid/__snapshots__/spaces_grid_pages.test.tsx.snap rename to x-pack/legacy/plugins/spaces/public/management/spaces_grid/__snapshots__/spaces_grid_pages.test.tsx.snap diff --git a/x-pack/legacy/plugins/spaces/public/views/management/spaces_grid/index.ts b/x-pack/legacy/plugins/spaces/public/management/spaces_grid/index.ts similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/management/spaces_grid/index.ts rename to x-pack/legacy/plugins/spaces/public/management/spaces_grid/index.ts diff --git a/x-pack/legacy/plugins/spaces/public/views/management/spaces_grid/spaces_grid_page.tsx b/x-pack/legacy/plugins/spaces/public/management/spaces_grid/spaces_grid_page.tsx similarity index 96% rename from x-pack/legacy/plugins/spaces/public/views/management/spaces_grid/spaces_grid_page.tsx rename to x-pack/legacy/plugins/spaces/public/management/spaces_grid/spaces_grid_page.tsx index 9fa03b1a9b74a..6ca1877642bdc 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/spaces_grid/spaces_grid_page.tsx +++ b/x-pack/legacy/plugins/spaces/public/management/spaces_grid/spaces_grid_page.tsx @@ -22,13 +22,13 @@ import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react'; import { kfetch } from 'ui/kfetch'; import { toastNotifications } from 'ui/notify'; import { Capabilities } from 'src/core/public'; -import { Feature } from '../../../../../../../plugins/features/public'; -import { isReservedSpace } from '../../../../common'; -import { DEFAULT_SPACE_ID } from '../../../../common/constants'; -import { Space } from '../../../../common/model/space'; -import { SpaceAvatar } from '../../../components'; -import { getSpacesFeatureDescription } from '../../../lib/constants'; -import { SpacesManager } from '../../../lib/spaces_manager'; +import { Feature } from '../../../../../../plugins/features/public'; +import { isReservedSpace } from '../../../common'; +import { DEFAULT_SPACE_ID } from '../../../common/constants'; +import { Space } from '../../../common/model/space'; +import { SpaceAvatar } from '../../space_avatar'; +import { getSpacesFeatureDescription } from '../../constants'; +import { SpacesManager } from '../..//spaces_manager'; import { ConfirmDeleteModal } from '../components/confirm_delete_modal'; import { SecureSpaceMessage } from '../components/secure_space_message'; import { UnauthorizedPrompt } from '../components/unauthorized_prompt'; diff --git a/x-pack/legacy/plugins/spaces/public/views/management/spaces_grid/spaces_grid_pages.test.tsx b/x-pack/legacy/plugins/spaces/public/management/spaces_grid/spaces_grid_pages.test.tsx similarity index 90% rename from x-pack/legacy/plugins/spaces/public/views/management/spaces_grid/spaces_grid_pages.test.tsx rename to x-pack/legacy/plugins/spaces/public/management/spaces_grid/spaces_grid_pages.test.tsx index 4add607707b24..7856d2e7bee01 100644 --- a/x-pack/legacy/plugins/spaces/public/views/management/spaces_grid/spaces_grid_pages.test.tsx +++ b/x-pack/legacy/plugins/spaces/public/management/spaces_grid/spaces_grid_pages.test.tsx @@ -6,12 +6,12 @@ jest.mock('ui/kfetch', () => ({ kfetch: () => Promise.resolve([]), })); -import '../../../__mocks__/xpack_info'; +import '../../__mocks__/xpack_info'; import React from 'react'; import { mountWithIntl, shallowWithIntl } from 'test_utils/enzyme_helpers'; -import { SpaceAvatar } from '../../../components'; -import { spacesManagerMock } from '../../../lib/mocks'; -import { SpacesManager } from '../../../lib'; +import { SpaceAvatar } from '../../space_avatar'; +import { spacesManagerMock } from '../../spaces_manager/mocks'; +import { SpacesManager } from '../../spaces_manager'; import { SpacesGridPage } from './spaces_grid_page'; const spaces = [ diff --git a/x-pack/legacy/plugins/spaces/public/views/management/template.html b/x-pack/legacy/plugins/spaces/public/management/template.html similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/management/template.html rename to x-pack/legacy/plugins/spaces/public/management/template.html diff --git a/x-pack/legacy/plugins/spaces/public/views/nav_control/__snapshots__/nav_control_popover.test.tsx.snap b/x-pack/legacy/plugins/spaces/public/nav_control/__snapshots__/nav_control_popover.test.tsx.snap similarity index 92% rename from x-pack/legacy/plugins/spaces/public/views/nav_control/__snapshots__/nav_control_popover.test.tsx.snap rename to x-pack/legacy/plugins/spaces/public/nav_control/__snapshots__/nav_control_popover.test.tsx.snap index 5cad4e794cfda..45daa03e94c2e 100644 --- a/x-pack/legacy/plugins/spaces/public/views/nav_control/__snapshots__/nav_control_popover.test.tsx.snap +++ b/x-pack/legacy/plugins/spaces/public/nav_control/__snapshots__/nav_control_popover.test.tsx.snap @@ -5,7 +5,7 @@ exports[`NavControlPopover renders without crashing 1`] = ` anchorPosition="downRight" button={ diff --git a/x-pack/legacy/plugins/spaces/public/nav_control/_index.scss b/x-pack/legacy/plugins/spaces/public/nav_control/_index.scss new file mode 100644 index 0000000000000..d0471da325cec --- /dev/null +++ b/x-pack/legacy/plugins/spaces/public/nav_control/_index.scss @@ -0,0 +1,2 @@ +@import './components/index'; +@import './nav_control'; \ No newline at end of file diff --git a/x-pack/legacy/plugins/spaces/public/views/nav_control/_nav_control.scss b/x-pack/legacy/plugins/spaces/public/nav_control/_nav_control.scss similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/nav_control/_nav_control.scss rename to x-pack/legacy/plugins/spaces/public/nav_control/_nav_control.scss diff --git a/x-pack/legacy/plugins/spaces/public/components/__snapshots__/manage_spaces_button.test.tsx.snap b/x-pack/legacy/plugins/spaces/public/nav_control/components/__snapshots__/manage_spaces_button.test.tsx.snap similarity index 100% rename from x-pack/legacy/plugins/spaces/public/components/__snapshots__/manage_spaces_button.test.tsx.snap rename to x-pack/legacy/plugins/spaces/public/nav_control/components/__snapshots__/manage_spaces_button.test.tsx.snap diff --git a/x-pack/legacy/plugins/spaces/public/views/nav_control/components/__snapshots__/spaces_description.test.tsx.snap b/x-pack/legacy/plugins/spaces/public/nav_control/components/__snapshots__/spaces_description.test.tsx.snap similarity index 98% rename from x-pack/legacy/plugins/spaces/public/views/nav_control/components/__snapshots__/spaces_description.test.tsx.snap rename to x-pack/legacy/plugins/spaces/public/nav_control/components/__snapshots__/spaces_description.test.tsx.snap index 079dab701cc1d..8e78f64ac59cb 100644 --- a/x-pack/legacy/plugins/spaces/public/views/nav_control/components/__snapshots__/spaces_description.test.tsx.snap +++ b/x-pack/legacy/plugins/spaces/public/nav_control/components/__snapshots__/spaces_description.test.tsx.snap @@ -4,6 +4,7 @@ exports[`SpacesDescription renders without crashing 1`] = ` diff --git a/x-pack/legacy/plugins/spaces/public/views/nav_control/components/_index.scss b/x-pack/legacy/plugins/spaces/public/nav_control/components/_index.scss similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/nav_control/components/_index.scss rename to x-pack/legacy/plugins/spaces/public/nav_control/components/_index.scss diff --git a/x-pack/legacy/plugins/spaces/public/views/nav_control/components/_spaces_description.scss b/x-pack/legacy/plugins/spaces/public/nav_control/components/_spaces_description.scss similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/nav_control/components/_spaces_description.scss rename to x-pack/legacy/plugins/spaces/public/nav_control/components/_spaces_description.scss diff --git a/x-pack/legacy/plugins/spaces/public/views/nav_control/components/_spaces_menu.scss b/x-pack/legacy/plugins/spaces/public/nav_control/components/_spaces_menu.scss similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/nav_control/components/_spaces_menu.scss rename to x-pack/legacy/plugins/spaces/public/nav_control/components/_spaces_menu.scss diff --git a/x-pack/legacy/plugins/spaces/public/components/manage_spaces_button.test.tsx b/x-pack/legacy/plugins/spaces/public/nav_control/components/manage_spaces_button.test.tsx similarity index 100% rename from x-pack/legacy/plugins/spaces/public/components/manage_spaces_button.test.tsx rename to x-pack/legacy/plugins/spaces/public/nav_control/components/manage_spaces_button.test.tsx diff --git a/x-pack/legacy/plugins/spaces/public/components/manage_spaces_button.tsx b/x-pack/legacy/plugins/spaces/public/nav_control/components/manage_spaces_button.tsx similarity index 96% rename from x-pack/legacy/plugins/spaces/public/components/manage_spaces_button.tsx rename to x-pack/legacy/plugins/spaces/public/nav_control/components/manage_spaces_button.tsx index 91a0803c20bc9..857d0c1f828a6 100644 --- a/x-pack/legacy/plugins/spaces/public/components/manage_spaces_button.tsx +++ b/x-pack/legacy/plugins/spaces/public/nav_control/components/manage_spaces_button.tsx @@ -8,7 +8,7 @@ import { EuiButton } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; import React, { Component, CSSProperties } from 'react'; import { Capabilities } from 'src/core/public'; -import { getManageSpacesUrl } from '../lib/constants'; +import { getManageSpacesUrl } from '../../constants'; interface Props { isDisabled?: boolean; diff --git a/x-pack/legacy/plugins/spaces/public/views/nav_control/components/spaces_description.test.tsx b/x-pack/legacy/plugins/spaces/public/nav_control/components/spaces_description.test.tsx similarity index 97% rename from x-pack/legacy/plugins/spaces/public/views/nav_control/components/spaces_description.test.tsx rename to x-pack/legacy/plugins/spaces/public/nav_control/components/spaces_description.test.tsx index aacf3845e0e0f..157dcab3e0be1 100644 --- a/x-pack/legacy/plugins/spaces/public/views/nav_control/components/spaces_description.test.tsx +++ b/x-pack/legacy/plugins/spaces/public/nav_control/components/spaces_description.test.tsx @@ -13,6 +13,7 @@ describe('SpacesDescription', () => { expect( shallow( void; capabilities: Capabilities; } export const SpacesDescription: FC = (props: Props) => { const panelProps = { + id: props.id, className: 'spcDescription', title: 'Spaces', }; diff --git a/x-pack/legacy/plugins/spaces/public/views/nav_control/components/spaces_menu.tsx b/x-pack/legacy/plugins/spaces/public/nav_control/components/spaces_menu.tsx similarity index 95% rename from x-pack/legacy/plugins/spaces/public/views/nav_control/components/spaces_menu.tsx rename to x-pack/legacy/plugins/spaces/public/nav_control/components/spaces_menu.tsx index 9a26f6802abdf..4d89f57d4ccf1 100644 --- a/x-pack/legacy/plugins/spaces/public/views/nav_control/components/spaces_menu.tsx +++ b/x-pack/legacy/plugins/spaces/public/nav_control/components/spaces_menu.tsx @@ -14,11 +14,13 @@ import { import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react'; import React, { Component, ReactElement } from 'react'; import { Capabilities } from 'src/core/public'; -import { SPACE_SEARCH_COUNT_THRESHOLD } from '../../../../common/constants'; -import { Space } from '../../../../common/model/space'; -import { ManageSpacesButton, SpaceAvatar } from '../../../components'; +import { SPACE_SEARCH_COUNT_THRESHOLD } from '../../../common/constants'; +import { Space } from '../../../common/model/space'; +import { ManageSpacesButton } from './manage_spaces_button'; +import { SpaceAvatar } from '../../space_avatar'; interface Props { + id: string; spaces: Space[]; isLoading: boolean; onSelectSpace: (space: Space) => void; @@ -47,6 +49,7 @@ class SpacesMenuUI extends Component { : this.getVisibleSpaces(searchTerm).map(this.renderSpaceMenuItem); const panelProps = { + id: this.props.id, className: 'spcMenu', title: intl.formatMessage({ id: 'xpack.spaces.navControl.spacesMenu.changeCurrentSpaceTitle', diff --git a/x-pack/legacy/plugins/spaces/public/views/nav_control/index.ts b/x-pack/legacy/plugins/spaces/public/nav_control/index.ts similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/nav_control/index.ts rename to x-pack/legacy/plugins/spaces/public/nav_control/index.ts diff --git a/x-pack/legacy/plugins/spaces/public/views/nav_control/nav_control.tsx b/x-pack/legacy/plugins/spaces/public/nav_control/nav_control.tsx similarity index 94% rename from x-pack/legacy/plugins/spaces/public/views/nav_control/nav_control.tsx rename to x-pack/legacy/plugins/spaces/public/nav_control/nav_control.tsx index 0df077e0d2da0..9ec070eff3fed 100644 --- a/x-pack/legacy/plugins/spaces/public/views/nav_control/nav_control.tsx +++ b/x-pack/legacy/plugins/spaces/public/nav_control/nav_control.tsx @@ -4,10 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { SpacesManager } from 'plugins/spaces/lib/spaces_manager'; import React from 'react'; import ReactDOM from 'react-dom'; import { CoreStart } from 'src/core/public'; +import { SpacesManager } from '../spaces_manager'; import { NavControlPopover } from './nav_control_popover'; export function initSpacesNavControl(spacesManager: SpacesManager, core: CoreStart) { diff --git a/x-pack/legacy/plugins/spaces/public/views/nav_control/nav_control_popover.test.tsx b/x-pack/legacy/plugins/spaces/public/nav_control/nav_control_popover.test.tsx similarity index 91% rename from x-pack/legacy/plugins/spaces/public/views/nav_control/nav_control_popover.test.tsx rename to x-pack/legacy/plugins/spaces/public/nav_control/nav_control_popover.test.tsx index a04f28242f984..5ce141abb713e 100644 --- a/x-pack/legacy/plugins/spaces/public/views/nav_control/nav_control_popover.test.tsx +++ b/x-pack/legacy/plugins/spaces/public/nav_control/nav_control_popover.test.tsx @@ -7,9 +7,9 @@ import * as Rx from 'rxjs'; import { shallow } from 'enzyme'; import React from 'react'; -import { SpaceAvatar } from '../../components'; -import { spacesManagerMock } from '../../lib/mocks'; -import { SpacesManager } from '../../lib'; +import { SpaceAvatar } from '../space_avatar'; +import { spacesManagerMock } from '../spaces_manager/mocks'; +import { SpacesManager } from '../spaces_manager'; import { NavControlPopover } from './nav_control_popover'; import { EuiHeaderSectionItemButton } from '@elastic/eui'; import { mountWithIntl } from 'test_utils/enzyme_helpers'; @@ -42,6 +42,7 @@ describe('NavControlPopover', () => { disabledFeatures: [], }, ]); + // @ts-ignore readonly check spacesManager.onActiveSpaceChange$ = Rx.of({ id: 'foo-space', name: 'foo', diff --git a/x-pack/legacy/plugins/spaces/public/views/nav_control/nav_control_popover.tsx b/x-pack/legacy/plugins/spaces/public/nav_control/nav_control_popover.tsx similarity index 93% rename from x-pack/legacy/plugins/spaces/public/views/nav_control/nav_control_popover.tsx rename to x-pack/legacy/plugins/spaces/public/nav_control/nav_control_popover.tsx index b37458aace2a2..59c8052a644da 100644 --- a/x-pack/legacy/plugins/spaces/public/views/nav_control/nav_control_popover.tsx +++ b/x-pack/legacy/plugins/spaces/public/nav_control/nav_control_popover.tsx @@ -13,9 +13,9 @@ import { import React, { Component } from 'react'; import { Capabilities } from 'src/core/public'; import { Subscription } from 'rxjs'; -import { Space } from '../../../common/model/space'; -import { SpaceAvatar } from '../../components'; -import { SpacesManager } from '../../lib/spaces_manager'; +import { Space } from '../../common/model/space'; +import { SpaceAvatar } from '../space_avatar'; +import { SpacesManager } from '../spaces_manager'; import { SpacesDescription } from './components/spaces_description'; import { SpacesMenu } from './components/spaces_menu'; @@ -32,6 +32,8 @@ interface State { spaces: Space[]; } +const popoutContentId = 'headerSpacesMenuContent'; + export class NavControlPopover extends Component { private activeSpace$?: Subscription; @@ -71,6 +73,7 @@ export class NavControlPopover extends Component { if (!this.state.loading && this.state.spaces.length < 2) { element = ( @@ -78,6 +81,7 @@ export class NavControlPopover extends Component { } else { element = ( { private getButton = (linkIcon: JSX.Element, linkTitle: string) => { return ( , + allowOverride: boolean + ) => void; + }; +} + +export interface PluginsStart { + management: ManagementStart; } export class SpacesPlugin implements Plugin { - private spacesManager: SpacesManager | null = null; + private spacesManager!: SpacesManager; - public async start(core: CoreStart) { - const serverBasePath = core.injectedMetadata.getInjectedVar('serverBasePath') as string; + private managementService?: ManagementService; + public setup(core: CoreSetup, plugins: PluginsSetup) { + const serverBasePath = core.injectedMetadata.getInjectedVar('serverBasePath') as string; this.spacesManager = new SpacesManager(serverBasePath, core.http); + + const copySavedObjectsToSpaceService = new CopySavedObjectsToSpaceService(); + copySavedObjectsToSpaceService.setup({ + spacesManager: this.spacesManager, + managementSetup: plugins.management, + }); + + const advancedSettingsService = new AdvancedSettingsService(); + advancedSettingsService.setup({ + getActiveSpace: () => this.spacesManager.getActiveSpace(), + registerSettingsComponent: plugins.__managementLegacyCompat.registerSettingsComponent, + }); + + if (plugins.home) { + plugins.home.featureCatalogue.register(createSpacesFeatureCatalogueEntry()); + } + } + + public start(core: CoreStart, plugins: PluginsStart) { initSpacesNavControl(this.spacesManager, core); + this.managementService = new ManagementService(); + this.managementService.start({ managementStart: plugins.management }); + return { spacesManager: this.spacesManager, }; } - public async setup(core: CoreSetup, plugins: PluginsSetup) { - if (plugins.home) { - plugins.home.featureCatalogue.register(createSpacesFeatureCatalogueEntry()); + public stop() { + if (this.managementService) { + this.managementService.stop(); + this.managementService = undefined; } } } diff --git a/x-pack/legacy/plugins/spaces/public/components/__snapshots__/space_avatar.test.tsx.snap b/x-pack/legacy/plugins/spaces/public/space_avatar/__snapshots__/space_avatar.test.tsx.snap similarity index 100% rename from x-pack/legacy/plugins/spaces/public/components/__snapshots__/space_avatar.test.tsx.snap rename to x-pack/legacy/plugins/spaces/public/space_avatar/__snapshots__/space_avatar.test.tsx.snap diff --git a/x-pack/legacy/plugins/spaces/public/space_avatar/index.ts b/x-pack/legacy/plugins/spaces/public/space_avatar/index.ts new file mode 100644 index 0000000000000..1525f2c8c6186 --- /dev/null +++ b/x-pack/legacy/plugins/spaces/public/space_avatar/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { SpaceAvatar } from './space_avatar'; +export * from './space_attributes'; diff --git a/x-pack/legacy/plugins/spaces/public/lib/space_attributes.test.ts b/x-pack/legacy/plugins/spaces/public/space_avatar/space_attributes.test.ts similarity index 100% rename from x-pack/legacy/plugins/spaces/public/lib/space_attributes.test.ts rename to x-pack/legacy/plugins/spaces/public/space_avatar/space_attributes.test.ts diff --git a/x-pack/legacy/plugins/spaces/public/lib/space_attributes.ts b/x-pack/legacy/plugins/spaces/public/space_avatar/space_attributes.ts similarity index 100% rename from x-pack/legacy/plugins/spaces/public/lib/space_attributes.ts rename to x-pack/legacy/plugins/spaces/public/space_avatar/space_attributes.ts diff --git a/x-pack/legacy/plugins/spaces/public/components/space_avatar.test.tsx b/x-pack/legacy/plugins/spaces/public/space_avatar/space_avatar.test.tsx similarity index 100% rename from x-pack/legacy/plugins/spaces/public/components/space_avatar.test.tsx rename to x-pack/legacy/plugins/spaces/public/space_avatar/space_avatar.test.tsx diff --git a/x-pack/legacy/plugins/spaces/public/components/space_avatar.tsx b/x-pack/legacy/plugins/spaces/public/space_avatar/space_avatar.tsx similarity index 98% rename from x-pack/legacy/plugins/spaces/public/components/space_avatar.tsx rename to x-pack/legacy/plugins/spaces/public/space_avatar/space_avatar.tsx index 0d9751ca43db9..c89f492a8fc99 100644 --- a/x-pack/legacy/plugins/spaces/public/components/space_avatar.tsx +++ b/x-pack/legacy/plugins/spaces/public/space_avatar/space_avatar.tsx @@ -8,7 +8,7 @@ import { EuiAvatar, isValidHex } from '@elastic/eui'; import React, { FC } from 'react'; import { MAX_SPACE_INITIALS } from '../../common'; import { Space } from '../../common/model/space'; -import { getSpaceColor, getSpaceInitials, getSpaceImageUrl } from '../lib/space_attributes'; +import { getSpaceColor, getSpaceInitials, getSpaceImageUrl } from './space_attributes'; interface Props { space: Partial; diff --git a/x-pack/legacy/plugins/spaces/public/views/space_selector/__snapshots__/space_selector.test.tsx.snap b/x-pack/legacy/plugins/spaces/public/space_selector/__snapshots__/space_selector.test.tsx.snap similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/space_selector/__snapshots__/space_selector.test.tsx.snap rename to x-pack/legacy/plugins/spaces/public/space_selector/__snapshots__/space_selector.test.tsx.snap diff --git a/x-pack/legacy/plugins/spaces/public/space_selector/_index.scss b/x-pack/legacy/plugins/spaces/public/space_selector/_index.scss new file mode 100644 index 0000000000000..0621aa2a3efd7 --- /dev/null +++ b/x-pack/legacy/plugins/spaces/public/space_selector/_index.scss @@ -0,0 +1,2 @@ +@import './space_selector'; +@import './components/index'; diff --git a/x-pack/legacy/plugins/spaces/public/views/space_selector/_space_selector.scss b/x-pack/legacy/plugins/spaces/public/space_selector/_space_selector.scss similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/space_selector/_space_selector.scss rename to x-pack/legacy/plugins/spaces/public/space_selector/_space_selector.scss diff --git a/x-pack/legacy/plugins/spaces/public/views/components/_index.scss b/x-pack/legacy/plugins/spaces/public/space_selector/components/_index.scss similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/components/_index.scss rename to x-pack/legacy/plugins/spaces/public/space_selector/components/_index.scss diff --git a/x-pack/legacy/plugins/spaces/public/views/components/_space_card.scss b/x-pack/legacy/plugins/spaces/public/space_selector/components/_space_card.scss similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/components/_space_card.scss rename to x-pack/legacy/plugins/spaces/public/space_selector/components/_space_card.scss diff --git a/x-pack/legacy/plugins/spaces/public/views/components/_space_cards.scss b/x-pack/legacy/plugins/spaces/public/space_selector/components/_space_cards.scss similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/components/_space_cards.scss rename to x-pack/legacy/plugins/spaces/public/space_selector/components/_space_cards.scss diff --git a/x-pack/legacy/plugins/spaces/public/views/components/index.ts b/x-pack/legacy/plugins/spaces/public/space_selector/components/index.ts similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/components/index.ts rename to x-pack/legacy/plugins/spaces/public/space_selector/components/index.ts diff --git a/x-pack/legacy/plugins/spaces/public/views/components/space_card.test.tsx b/x-pack/legacy/plugins/spaces/public/space_selector/components/space_card.test.tsx similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/components/space_card.test.tsx rename to x-pack/legacy/plugins/spaces/public/space_selector/components/space_card.test.tsx diff --git a/x-pack/legacy/plugins/spaces/public/views/components/space_card.tsx b/x-pack/legacy/plugins/spaces/public/space_selector/components/space_card.tsx similarity index 90% rename from x-pack/legacy/plugins/spaces/public/views/components/space_card.tsx rename to x-pack/legacy/plugins/spaces/public/space_selector/components/space_card.tsx index 2386f6a6fe9d0..f898ba87c60bd 100644 --- a/x-pack/legacy/plugins/spaces/public/views/components/space_card.tsx +++ b/x-pack/legacy/plugins/spaces/public/space_selector/components/space_card.tsx @@ -4,14 +4,10 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - // FIXME: need updated typedefs - // @ts-ignore - EuiCard, -} from '@elastic/eui'; +import { EuiCard } from '@elastic/eui'; import React from 'react'; import { Space } from '../../../common/model/space'; -import { SpaceAvatar } from '../../components'; +import { SpaceAvatar } from '../../space_avatar'; interface Props { space: Space; diff --git a/x-pack/legacy/plugins/spaces/public/views/components/space_cards.test.tsx b/x-pack/legacy/plugins/spaces/public/space_selector/components/space_cards.test.tsx similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/components/space_cards.test.tsx rename to x-pack/legacy/plugins/spaces/public/space_selector/components/space_cards.test.tsx diff --git a/x-pack/legacy/plugins/spaces/public/views/components/space_cards.tsx b/x-pack/legacy/plugins/spaces/public/space_selector/components/space_cards.tsx similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/components/space_cards.tsx rename to x-pack/legacy/plugins/spaces/public/space_selector/components/space_cards.tsx diff --git a/x-pack/legacy/plugins/spaces/public/space_selector/index.tsx b/x-pack/legacy/plugins/spaces/public/space_selector/index.tsx new file mode 100644 index 0000000000000..c1c1b6dc3a2f3 --- /dev/null +++ b/x-pack/legacy/plugins/spaces/public/space_selector/index.tsx @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +// @ts-ignore +import template from 'plugins/spaces/space_selector/space_selector.html'; +import chrome from 'ui/chrome'; +import { I18nContext } from 'ui/i18n'; +// @ts-ignore +import { uiModules } from 'ui/modules'; + +import React from 'react'; +import { render, unmountComponentAtNode } from 'react-dom'; +import { SpaceSelector } from './space_selector'; + +import { start as spacesNPStart } from '../legacy'; + +const module = uiModules.get('spaces_selector', []); +module.controller('spacesSelectorController', ($scope: any) => { + $scope.$$postDigest(() => { + const domNode = document.getElementById('spaceSelectorRoot'); + + const { spacesManager } = spacesNPStart; + + render( + + + , + domNode + ); + + // unmount react on controller destroy + $scope.$on('$destroy', () => { + if (domNode) { + unmountComponentAtNode(domNode); + } + }); + }); +}); + +chrome.setVisible(false).setRootTemplate(template); diff --git a/x-pack/legacy/plugins/spaces/public/views/space_selector/space_selector.html b/x-pack/legacy/plugins/spaces/public/space_selector/space_selector.html similarity index 100% rename from x-pack/legacy/plugins/spaces/public/views/space_selector/space_selector.html rename to x-pack/legacy/plugins/spaces/public/space_selector/space_selector.html diff --git a/x-pack/legacy/plugins/spaces/public/views/space_selector/space_selector.test.tsx b/x-pack/legacy/plugins/spaces/public/space_selector/space_selector.test.tsx similarity index 92% rename from x-pack/legacy/plugins/spaces/public/views/space_selector/space_selector.test.tsx rename to x-pack/legacy/plugins/spaces/public/space_selector/space_selector.test.tsx index 829312061ca98..b4d0f96307500 100644 --- a/x-pack/legacy/plugins/spaces/public/views/space_selector/space_selector.test.tsx +++ b/x-pack/legacy/plugins/spaces/public/space_selector/space_selector.test.tsx @@ -6,8 +6,8 @@ import React from 'react'; import { shallowWithIntl } from 'test_utils/enzyme_helpers'; -import { Space } from '../../../common/model/space'; -import { spacesManagerMock } from '../../lib/mocks'; +import { Space } from '../../common/model/space'; +import { spacesManagerMock } from '../spaces_manager/mocks'; import { SpaceSelector } from './space_selector'; function getSpacesManager(spaces: Space[] = []) { diff --git a/x-pack/legacy/plugins/spaces/public/views/space_selector/space_selector.tsx b/x-pack/legacy/plugins/spaces/public/space_selector/space_selector.tsx similarity index 95% rename from x-pack/legacy/plugins/spaces/public/views/space_selector/space_selector.tsx rename to x-pack/legacy/plugins/spaces/public/space_selector/space_selector.tsx index d665752b3c8a6..206d38454fa8c 100644 --- a/x-pack/legacy/plugins/spaces/public/views/space_selector/space_selector.tsx +++ b/x-pack/legacy/plugins/spaces/public/space_selector/space_selector.tsx @@ -19,11 +19,11 @@ import { EuiLoadingSpinner, } from '@elastic/eui'; import { FormattedMessage, InjectedIntl, injectI18n } from '@kbn/i18n/react'; -import { SpacesManager } from 'plugins/spaces/lib'; import React, { Component, Fragment } from 'react'; -import { SPACE_SEARCH_COUNT_THRESHOLD } from '../../../common/constants'; -import { Space } from '../../../common/model/space'; -import { SpaceCards } from '../components/space_cards'; +import { SpacesManager } from '../spaces_manager'; +import { Space } from '../../common/model/space'; +import { SpaceCards } from './components'; +import { SPACE_SEARCH_COUNT_THRESHOLD } from '../../common/constants'; interface Props { spacesManager: SpacesManager; diff --git a/x-pack/legacy/plugins/spaces/public/spaces_manager/index.ts b/x-pack/legacy/plugins/spaces/public/spaces_manager/index.ts new file mode 100644 index 0000000000000..538dd77e053f5 --- /dev/null +++ b/x-pack/legacy/plugins/spaces/public/spaces_manager/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { SpacesManager } from './spaces_manager'; diff --git a/x-pack/legacy/plugins/spaces/public/lib/mocks.ts b/x-pack/legacy/plugins/spaces/public/spaces_manager/mocks.ts similarity index 100% rename from x-pack/legacy/plugins/spaces/public/lib/mocks.ts rename to x-pack/legacy/plugins/spaces/public/spaces_manager/mocks.ts diff --git a/x-pack/legacy/plugins/spaces/public/lib/spaces_manager.mock.ts b/x-pack/legacy/plugins/spaces/public/spaces_manager/spaces_manager.mock.ts similarity index 87% rename from x-pack/legacy/plugins/spaces/public/lib/spaces_manager.mock.ts rename to x-pack/legacy/plugins/spaces/public/spaces_manager/spaces_manager.mock.ts index 69c6f7a452fdd..56879af33916f 100644 --- a/x-pack/legacy/plugins/spaces/public/lib/spaces_manager.mock.ts +++ b/x-pack/legacy/plugins/spaces/public/spaces_manager/spaces_manager.mock.ts @@ -6,9 +6,10 @@ import { of, Observable } from 'rxjs'; import { Space } from '../../common/model/space'; +import { SpacesManager } from './spaces_manager'; function createSpacesManagerMock() { - return { + return ({ onActiveSpaceChange$: (of(undefined) as unknown) as Observable, getSpaces: jest.fn().mockResolvedValue([]), getSpace: jest.fn().mockResolvedValue(undefined), @@ -19,7 +20,8 @@ function createSpacesManagerMock() { copySavedObjects: jest.fn().mockResolvedValue(undefined), resolveCopySavedObjectsErrors: jest.fn().mockResolvedValue(undefined), redirectToSpaceSelector: jest.fn().mockResolvedValue(undefined), - }; + changeSelectedSpace: jest.fn(), + } as unknown) as jest.Mocked; } export const spacesManagerMock = { diff --git a/x-pack/legacy/plugins/spaces/public/lib/spaces_manager.test.ts b/x-pack/legacy/plugins/spaces/public/spaces_manager/spaces_manager.test.ts similarity index 100% rename from x-pack/legacy/plugins/spaces/public/lib/spaces_manager.test.ts rename to x-pack/legacy/plugins/spaces/public/spaces_manager/spaces_manager.test.ts diff --git a/x-pack/legacy/plugins/spaces/public/lib/spaces_manager.ts b/x-pack/legacy/plugins/spaces/public/spaces_manager/spaces_manager.ts similarity index 97% rename from x-pack/legacy/plugins/spaces/public/lib/spaces_manager.ts rename to x-pack/legacy/plugins/spaces/public/spaces_manager/spaces_manager.ts index ccc1b00dabb29..e9c738cf40c69 100644 --- a/x-pack/legacy/plugins/spaces/public/lib/spaces_manager.ts +++ b/x-pack/legacy/plugins/spaces/public/spaces_manager/spaces_manager.ts @@ -9,9 +9,9 @@ import { HttpSetup } from 'src/core/public'; import { SavedObjectsManagementRecord } from '../../../../../../src/legacy/core_plugins/management/public'; import { Space } from '../../common/model/space'; import { GetSpacePurpose } from '../../common/model/types'; -import { CopySavedObjectsToSpaceResponse } from './copy_saved_objects_to_space/types'; import { ENTER_SPACE_PATH } from '../../common/constants'; import { addSpaceIdToPath } from '../../../../../plugins/spaces/common'; +import { CopySavedObjectsToSpaceResponse } from '../copy_saved_objects_to_space/types'; export class SpacesManager { private activeSpace$: BehaviorSubject = new BehaviorSubject(null); diff --git a/x-pack/legacy/plugins/spaces/public/views/_index.scss b/x-pack/legacy/plugins/spaces/public/views/_index.scss deleted file mode 100644 index 0cc8ccb10246b..0000000000000 --- a/x-pack/legacy/plugins/spaces/public/views/_index.scss +++ /dev/null @@ -1,4 +0,0 @@ -@import './components/index'; -@import './management/index'; -@import './nav_control/index'; -@import './space_selector/index' diff --git a/x-pack/legacy/plugins/spaces/public/views/management/_index.scss b/x-pack/legacy/plugins/spaces/public/views/management/_index.scss deleted file mode 100644 index e7cbdfe2de7e8..0000000000000 --- a/x-pack/legacy/plugins/spaces/public/views/management/_index.scss +++ /dev/null @@ -1,3 +0,0 @@ -@import './components/confirm_delete_modal'; -@import './edit_space/enabled_features/index'; -@import './components/copy_saved_objects_to_space/index'; diff --git a/x-pack/legacy/plugins/spaces/public/views/management/components/index.ts b/x-pack/legacy/plugins/spaces/public/views/management/components/index.ts deleted file mode 100644 index 91f4964e1da06..0000000000000 --- a/x-pack/legacy/plugins/spaces/public/views/management/components/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export { ConfirmDeleteModal } from './confirm_delete_modal'; -export { UnauthorizedPrompt } from './unauthorized_prompt'; diff --git a/x-pack/legacy/plugins/spaces/public/views/management/index.tsx b/x-pack/legacy/plugins/spaces/public/views/management/index.tsx deleted file mode 100644 index bf33273c614d6..0000000000000 --- a/x-pack/legacy/plugins/spaces/public/views/management/index.tsx +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import { i18n } from '@kbn/i18n'; -import 'plugins/spaces/views/management/page_routes'; -import React from 'react'; -import { - management, - PAGE_SUBTITLE_COMPONENT, - PAGE_TITLE_COMPONENT, - registerSettingsComponent, -} from 'ui/management'; -// @ts-ignore -import routes from 'ui/routes'; -import { setup as managementSetup } from '../../../../../../../src/legacy/core_plugins/management/public/legacy'; -import { AdvancedSettingsSubtitle } from './components/advanced_settings_subtitle'; -import { AdvancedSettingsTitle } from './components/advanced_settings_title'; -import { start as spacesNPStart } from '../../legacy'; -import { CopyToSpaceSavedObjectsManagementAction } from '../../lib/copy_saved_objects_to_space'; - -const MANAGE_SPACES_KEY = 'spaces'; - -routes.defaults(/\/management/, { - resolve: { - spacesManagementSection() { - function getKibanaSection() { - return management.getSection('kibana'); - } - - function deregisterSpaces() { - getKibanaSection().deregister(MANAGE_SPACES_KEY); - } - - function ensureSpagesRegistered() { - const kibanaSection = getKibanaSection(); - - if (!kibanaSection.hasItem(MANAGE_SPACES_KEY)) { - kibanaSection.register(MANAGE_SPACES_KEY, { - name: 'spacesManagementLink', - order: 10, - display: i18n.translate('xpack.spaces.displayName', { - defaultMessage: 'Spaces', - }), - url: `#/management/spaces/list`, - }); - } - - // Customize Saved Objects Management - spacesNPStart.then(({ spacesManager }) => { - const action = new CopyToSpaceSavedObjectsManagementAction(spacesManager!); - // This route resolve function executes any time the management screen is loaded, and we want to ensure - // that this action is only registered once. - if (!managementSetup.savedObjects.registry.has(action.id)) { - managementSetup.savedObjects.registry.register(action); - } - }); - - const getActiveSpace = async () => { - const { spacesManager } = await spacesNPStart; - return spacesManager!.getActiveSpace(); - }; - - const PageTitle = () => ; - registerSettingsComponent(PAGE_TITLE_COMPONENT, PageTitle, true); - - const SubTitle = () => ; - registerSettingsComponent(PAGE_SUBTITLE_COMPONENT, SubTitle, true); - } - - deregisterSpaces(); - - ensureSpagesRegistered(); - }, - }, -}); diff --git a/x-pack/legacy/plugins/spaces/public/views/management/lib/breadcrumbs.ts b/x-pack/legacy/plugins/spaces/public/views/management/lib/breadcrumbs.ts deleted file mode 100644 index a4e8ba508b617..0000000000000 --- a/x-pack/legacy/plugins/spaces/public/views/management/lib/breadcrumbs.ts +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { MANAGEMENT_BREADCRUMB } from 'ui/management/breadcrumbs'; -import { Space } from '../../../../common/model/space'; - -export function getListBreadcrumbs() { - return [ - MANAGEMENT_BREADCRUMB, - { - text: 'Spaces', - href: '#/management/spaces/list', - }, - ]; -} - -export function getCreateBreadcrumbs() { - return [ - ...getListBreadcrumbs(), - { - text: 'Create', - }, - ]; -} - -export function getEditBreadcrumbs(space?: Space) { - return [ - ...getListBreadcrumbs(), - { - text: space ? space.name : '...', - }, - ]; -} diff --git a/x-pack/legacy/plugins/spaces/public/views/management/lib/feature_utils.ts b/x-pack/legacy/plugins/spaces/public/views/management/lib/feature_utils.ts deleted file mode 100644 index ef46a53967744..0000000000000 --- a/x-pack/legacy/plugins/spaces/public/views/management/lib/feature_utils.ts +++ /dev/null @@ -1,13 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { Feature } from '../../../../../../../plugins/features/public'; - -import { Space } from '../../../../common/model/space'; - -export function getEnabledFeatures(features: Feature[], space: Partial) { - return features.filter(feature => !(space.disabledFeatures || []).includes(feature.id)); -} diff --git a/x-pack/legacy/plugins/spaces/public/views/management/lib/index.ts b/x-pack/legacy/plugins/spaces/public/views/management/lib/index.ts deleted file mode 100644 index f65757f5dba26..0000000000000 --- a/x-pack/legacy/plugins/spaces/public/views/management/lib/index.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export { toSpaceIdentifier, isValidSpaceIdentifier } from './space_identifier_utils'; - -export { SpaceValidator } from './validate_space'; - -export { getCreateBreadcrumbs, getEditBreadcrumbs, getListBreadcrumbs } from './breadcrumbs'; diff --git a/x-pack/legacy/plugins/spaces/public/views/management/page_routes.tsx b/x-pack/legacy/plugins/spaces/public/views/management/page_routes.tsx deleted file mode 100644 index d8fd0298df2fc..0000000000000 --- a/x-pack/legacy/plugins/spaces/public/views/management/page_routes.tsx +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -// @ts-ignore -import template from 'plugins/spaces/views/management/template.html'; -import React from 'react'; -import { render, unmountComponentAtNode } from 'react-dom'; -import { I18nContext } from 'ui/i18n'; -// @ts-ignore -import routes from 'ui/routes'; -import { npStart } from 'ui/new_platform'; -import { ManageSpacePage } from './edit_space'; -import { getCreateBreadcrumbs, getEditBreadcrumbs, getListBreadcrumbs } from './lib'; -import { SpacesGridPage } from './spaces_grid'; - -import { start as spacesNPStart } from '../../legacy'; - -const reactRootNodeId = 'manageSpacesReactRoot'; - -routes.when('/management/spaces/list', { - template, - k7Breadcrumbs: getListBreadcrumbs, - requireUICapability: 'management.kibana.spaces', - controller($scope: any) { - $scope.$$postDigest(async () => { - const domNode = document.getElementById(reactRootNodeId); - - const { spacesManager } = await spacesNPStart; - - render( - - - , - domNode - ); - - // unmount react on controller destroy - $scope.$on('$destroy', () => { - if (domNode) { - unmountComponentAtNode(domNode); - } - }); - }); - }, -}); - -routes.when('/management/spaces/create', { - template, - k7Breadcrumbs: getCreateBreadcrumbs, - requireUICapability: 'management.kibana.spaces', - controller($scope: any) { - $scope.$$postDigest(async () => { - const domNode = document.getElementById(reactRootNodeId); - - const { spacesManager } = await spacesNPStart; - - render( - - - , - domNode - ); - - // unmount react on controller destroy - $scope.$on('$destroy', () => { - if (domNode) { - unmountComponentAtNode(domNode); - } - }); - }); - }, -}); - -routes.when('/management/spaces/edit', { - redirectTo: '/management/spaces/list', -}); - -routes.when('/management/spaces/edit/:spaceId', { - template, - k7Breadcrumbs: () => getEditBreadcrumbs(), - requireUICapability: 'management.kibana.spaces', - controller($scope: any, $route: any) { - $scope.$$postDigest(async () => { - const domNode = document.getElementById(reactRootNodeId); - - const { spaceId } = $route.current.params; - - const { spacesManager } = await spacesNPStart; - - render( - - - , - domNode - ); - - // unmount react on controller destroy - $scope.$on('$destroy', () => { - if (domNode) { - unmountComponentAtNode(domNode); - } - }); - }); - }, -}); diff --git a/x-pack/legacy/plugins/spaces/public/views/nav_control/_index.scss b/x-pack/legacy/plugins/spaces/public/views/nav_control/_index.scss deleted file mode 100644 index 192091fb04e3c..0000000000000 --- a/x-pack/legacy/plugins/spaces/public/views/nav_control/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import './components/index'; diff --git a/x-pack/legacy/plugins/spaces/public/views/space_selector/_index.scss b/x-pack/legacy/plugins/spaces/public/views/space_selector/_index.scss deleted file mode 100644 index f23ac662dce1d..0000000000000 --- a/x-pack/legacy/plugins/spaces/public/views/space_selector/_index.scss +++ /dev/null @@ -1 +0,0 @@ -@import './space_selector'; diff --git a/x-pack/legacy/plugins/spaces/public/views/space_selector/index.tsx b/x-pack/legacy/plugins/spaces/public/views/space_selector/index.tsx deleted file mode 100644 index c520c2683c965..0000000000000 --- a/x-pack/legacy/plugins/spaces/public/views/space_selector/index.tsx +++ /dev/null @@ -1,43 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -// @ts-ignore -import template from 'plugins/spaces/views/space_selector/space_selector.html'; -import chrome from 'ui/chrome'; -import { I18nContext } from 'ui/i18n'; -// @ts-ignore -import { uiModules } from 'ui/modules'; - -import React from 'react'; -import { render, unmountComponentAtNode } from 'react-dom'; -import { SpaceSelector } from './space_selector'; - -import { start as spacesNPStart } from '../../legacy'; - -const module = uiModules.get('spaces_selector', []); -module.controller('spacesSelectorController', ($scope: any) => { - $scope.$$postDigest(async () => { - const domNode = document.getElementById('spaceSelectorRoot'); - - const { spacesManager } = await spacesNPStart; - - render( - - - , - domNode - ); - - // unmount react on controller destroy - $scope.$on('$destroy', () => { - if (domNode) { - unmountComponentAtNode(domNode); - } - }); - }); -}); - -chrome.setVisible(false).setRootTemplate(template); diff --git a/x-pack/legacy/plugins/task_manager/README.md b/x-pack/legacy/plugins/task_manager/README.md deleted file mode 100644 index 3afcb758260c0..0000000000000 --- a/x-pack/legacy/plugins/task_manager/README.md +++ /dev/null @@ -1,377 +0,0 @@ -# Kibana task manager - -The task manager is a generic system for running background tasks. It supports: - -- Single-run and recurring tasks -- Scheduling tasks to run after a specified datetime -- Basic retry logic -- Recovery of stalled tasks / timeouts -- Tracking task state across multiple runs -- Configuring the run-parameters for specific tasks -- Basic coordination to prevent the same task instance from running on more than one Kibana system at a time - -## Implementation details - -At a high-level, the task manager works like this: - -- Every `{poll_interval}` milliseconds, check the `{index}` for any tasks that need to be run: - - `runAt` is past - - `attempts` is less than the configured threshold -- Attempt to claim the task by using optimistic concurrency to set: - - status to `running` - - `startedAt` to now - - `retryAt` to next time task should retry if it times out and is still in `running` status -- Execute the task, if the previous claim succeeded -- If the task fails, increment the `attempts` count and reschedule it -- If the task succeeds: - - If it is recurring, store the result of the run, and reschedule - - If it is not recurring, remove it from the index - -## Pooling - -Each task manager instance runs tasks in a pool which ensures that at most N tasks are run at a time, where N is configurable. This prevents the system from running too many tasks at once in resource constrained environments. In addition to this, each individual task type definition can have `numWorkers` specified, which tells the system how many workers are consumed by a single running instance of a task. This effectively limits how many tasks of a given type can be run at once. - -For example, we may have a system with a `max_workers` of 10, but a super expensive task (such as `reporting`) which specifies a `numWorkers` of 10. In this case, `reporting` tasks will run one at a time. - -If a task specifies a higher `numWorkers` than the system supports, the system's `max_workers` setting will be substituted for it. - -## Config options - -The task_manager can be configured via `taskManager` config options (e.g. `taskManager.maxAttempts`): - -- `max_attempts` - The maximum number of times a task will be attempted before being abandoned as failed -- `poll_interval` - How often the background worker should check the task_manager index for more work -- `index` - The name of the index that the task_manager -- `max_workers` - The maximum number of tasks a Kibana will run concurrently (defaults to 10) -- `credentials` - Encrypted user credentials. All tasks will run in the security context of this user. See [this issue](https://github.com/elastic/dev/issues/1045) for a discussion on task scheduler security. -- `override_num_workers`: An object of `taskType: number` that overrides the `num_workers` for tasks - - For example: `task_manager.override_num_workers.reporting: 2` would override the number of workers occupied by tasks of type `reporting` - - This allows sysadmins to tweak the operational performance of Kibana, allowing more or fewer tasks of a specific type to run simultaneously - -## Task definitions - -Plugins define tasks by calling the `registerTaskDefinitions` method on the `server.plugins.task_manager` object. - -A sample task can be found in the [x-pack/test/plugin_api_integration/plugins/task_manager](../../test/plugin_api_integration/plugins/task_manager/index.js) folder. - -```js -const taskManager = server.plugins.task_manager; -taskManager.registerTaskDefinitions({ - // clusterMonitoring is the task type, and must be unique across the entire system - clusterMonitoring: { - // Human friendly name, used to represent this task in logs, UI, etc - title: 'Human friendly name', - - // Optional, human-friendly, more detailed description - description: 'Amazing!!', - - // Optional, how long, in minutes or seconds, the system should wait before - // a running instance of this task is considered to be timed out. - // This defaults to 5 minutes. - timeout: '5m', - - // Optional, how many attempts before marking task as failed. - // This defaults to what is configured at the task manager level. - maxAttempts: 5, - - // The clusterMonitoring task occupies 2 workers, so if the system has 10 worker slots, - // 5 clusterMonitoring tasks could run concurrently per Kibana instance. This value is - // overridden by the `override_num_workers` config value, if specified. - numWorkers: 2, - - // The createTaskRunner function / method returns an object that is responsible for - // performing the work of the task. context: { taskInstance }, is documented below. - createTaskRunner(context) { - return { - // Perform the work of the task. The return value should fit the TaskResult interface, documented - // below. Invalid return values will result in a logged warning. - async run() { - // Do some work - // Conditionally send some alerts - // Return some result or other... - }, - - // Optional, will be called if a running instance of this task times out, allowing the task - // to attempt to clean itself up. - async cancel() { - // Do whatever is required to cancel this task, such as killing any spawned processes - }, - }; - }, - }, -}); -``` - -When Kibana attempts to claim and run a task instance, it looks its definition up, and executes its createTaskRunner's method, passing it a run context which looks like this: - -```js -{ - // The data associated with this instance of the task, with two properties being most notable: - // - // params: - // An object, specific to this task instance, used by the - // task to determine exactly what work should be performed. - // e.g. a cluster-monitoring task might have a `clusterName` - // property in here, but a movie-monitoring task might have - // a `directorName` property. - // - // state: - // The state returned from the previous run of this task instance. - // If this task instance has never succesfully run, this will - // be an empty object: {} - taskInstance, -} -``` - -## Task result - -The task runner's `run` method is expected to return a promise that resolves to undefined or to an object that looks like the following: -```js -{ - // Optional, if specified, this is used as the tasks' nextRun, overriding - // the default system scheduler. - runAt: "2020-07-24T17:34:35.272Z", - - // Optional, an error object, logged out as a warning. The pressence of this - // property indicates that the task did not succeed. - error: { message: 'Hrumph!' }, - - // Optional, this will be passed into the next run of the task, if - // this is a recurring task. - state: { - anything: 'goes here', - }, -} -``` - -Other return values will result in a warning, but the system should continue to work. - -## Task instances - -The task_manager module will store scheduled task instances in an index. This allows for recovery of failed tasks, coordination across Kibana clusters, persistence across Kibana reboots, etc. - -The data stored for a task instance looks something like this: - -```js -{ - // The type of task that will run this instance. - taskType: 'clusterMonitoring', - - // The next time this task instance should run. It is not guaranteed - // to run at this time, but it is guaranteed not to run earlier than - // this. - runAt: "2020-07-24T17:34:35.272Z", - - // Indicates that this is a recurring task. We currently only support - // interval syntax with either minutes such as `5m` or seconds `10s`. - schedule: { interval: '5m' }, - - // How many times this task has been unsuccesfully attempted, - // this will be reset to 0 if the task ever succesfully completes. - // This is incremented if a task fails or times out. - attempts: 0, - - // Currently, this is either idle | claiming | running | failed. It is used to - // coordinate which Kibana instance owns / is running a specific - // task instance. - // idle: Task Instance isn't being worked on - // claiming: A Kibana instance has claimed ownership but hasn't started running - // the Task Instance yet - // running: A Kibana instance has began working on the Task Instance - // failed: The last run of the Task Instance failed, waiting to retry - status: 'idle', - - // The params specific to this task instance, which will be - // passed to the task when it runs, and will be used by the - // task to determine exactly what work should be performed. - // This is a JSON blob, and will be different per task type. - // e.g. a cluster-monitoring task might have a `clusterName` - // property in here, but a movie-monitoring task might have - // a `directorName` property. - params: '{ "task": "specific stuff here" }', - - // The result of the previous run of this task instance. This - // will be passed to the next run of the task, along with the - // params, and could be used by a task to do special logic If - // the task state changes (e.g. from green to red, or foo to bar) - // If there was no previous run (e.g. the instance has never succesfully - // completed, this will be an empty object.). This is a JSON blob, - // and will be different per task type. - state: '{ "status": "green" }', - - // An extension point for 3rd parties to build in security features on - // top of the task manager. For example, this might be the token of the user - // who scheduled this task. - userContext: 'the token of the user who scheduled this task', - - // An extension point for 3rd parties to build in security features on - // top of the task manager, and is expected to be the id of the user, if any, - // that scheduled this task. - user: '23lk3l42', - - // An application-specific designation, allowing different Kibana - // plugins / apps to query for only those tasks they care about. - scope: ['alerting'], - - // The Kibana UUID of the Kibana instance who last claimed ownership for running this task. - ownerId: '123e4567-e89b-12d3-a456-426655440000' -} -``` - -## Programmatic access - -The task manager mixin exposes a taskManager object on the Kibana server which plugins can use to manage scheduled tasks. Each method takes an optional `scope` argument and ensures that only tasks with the specified scope(s) will be affected. - -### schedule -Using `schedule` you can instruct TaskManger to schedule an instance of a TaskType at some point in the future. - -```js -const taskManager = server.plugins.task_manager; -// Schedules a task. All properties are as documented in the previous -// storage section, except that here, params is an object, not a JSON -// string. -const task = await taskManager.schedule({ - taskType, - runAt, - schedule, - params, - scope: ['my-fanci-app'], -}); - -// Removes the specified task -await manager.remove(task.id); - -// Fetches tasks, supports pagination, via the search-after API: -// https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-search-after.html -// If scope is not specified, all tasks are returned, otherwise only tasks -// with the given scope are returned. -const results = await manager.find({ scope: 'my-fanci-app', searchAfter: ['ids'] }); - -// results look something like this: -{ - searchAfter: ['233322'], - // Tasks is an array of task instances - tasks: [{ - id: '3242342', - taskType: 'reporting', - // etc - }] -} -``` - -### ensureScheduling -When using the `schedule` api to schedule a Task you can provide a hard coded `id` on the Task. This tells TaskManager to use this `id` to identify the Task Instance rather than generate an `id` on its own. -The danger is that in such a situation, a Task with that same `id` might already have been scheduled at some earlier point, and this would result in an error. In some cases, this is the expected behavior, but often you only care about ensuring the task has been _scheduled_ and don't need it to be scheduled a fresh. - -To achieve this you should use the `ensureScheduling` api which has the exact same behavior as `schedule`, except it allows the scheduling of a Task with an `id` that's already in assigned to another Task and it will assume that the existing Task is the one you wished to `schedule`, treating this as a successful operation. - -### runNow -Using `runNow` you can instruct TaskManger to run an existing task on-demand, without waiting for its scheduled time to be reached. - -```js -const taskManager = server.plugins.task_manager; - -try { - const taskRunResult = await taskManager.runNow('91760f10-ba42-de9799'); - // If no error is thrown, the task has completed successfully. -} catch(err: Error) { - // If running the task has failed, we throw an error with an appropriate message. - // For example, if the requested task doesnt exist: `Error: failed to run task "91760f10-ba42-de9799" as it does not exist` - // Or if, for example, the task is already running: `Error: failed to run task "91760f10-ba42-de9799" as it is currently running` -} -``` - - -### more options - -More custom access to the tasks can be done directly via Elasticsearch, though that won't be officially supported, as we can change the document structure at any time. - -## Middleware - -The task manager exposes a middleware layer that allows modifying tasks before they are scheduled / persisted to the task manager index, and modifying tasks / the run context before a task is run. - -For example: - -```js -// In your plugin's init -server.plugins.task_manager.addMiddleware({ - async beforeSave({ taskInstance, ...opts }) { - console.log(`About to save a task of type ${taskInstance.taskType}`); - - return { - ...opts, - taskInstance: { - ...taskInstance, - params: { - ...taskInstance.params, - example: 'Added to params!', - }, - }, - }; - }, - - async beforeRun({ taskInstance, ...opts }) { - console.log(`About to run ${taskInstance.taskType} ${taskInstance.id}`); - const { example, ...taskWithoutExampleProp } = taskInstance; - - return { - ...opts, - taskInstance: taskWithoutExampleProp, - }; - }, -}); -``` - -## Task Poller: polling for work -TaskManager used to work in a `pull` model, but it now needs to support both `push` and `pull`, so it has been remodeled internally to support a single `push` model. - -Task Manager's _push_ mechanism is driven by the following operations: - -1. A polling interval has been reached. -2. A new Task is scheduled. -3. A Task is run using `runNow`. - -The polling interval is straight forward: TaskPoller is configured to emit an event at a fixed interval. -That said, if there are no workers available, we want to ignore these events, so we'll throttle the interval on worker availability. - -Whenever a user uses the `schedule` api to schedule a new Task, we want to trigger an early polling in order to respond to the newly scheduled task as soon as possible, but this too we only wish to do if there are available workers, so we can throttle this too. - -When a `runNow` call is made we need to force a poll as the user will now be waiting on the result of the `runNow` call, but -there is a complexity here- we don't want to force polling (as there might not be any worker capacity and it's possible that a polling cycle is already running), but we also can't throttle, as we can't afford to "drop" these requests, so we'll have to buffer these. - -We now want to respond to all three of these push events, but we still need to balance against our worker capacity, so if there are too many requests buffered, we only want to `take` as many requests as we have capacity to handle. -Luckily, `Polling Interval` and `Task Scheduled` simply denote a request to "poll for work as soon as possible", unlike `Run Task Now` which also means "poll for these specific tasks", so our worker capacity only needs to be applied to `Run Task Now`. - -We achieve this model by buffering requests into a queue using a Set (which removes duplicated). As we don't want an unbounded queue in our system, we have limited the size of this queue (configurable by the `xpack.task_manager.request_capacity` config, defaulting to 1,000 requests) which forces us to throw an error once this cap is reachedand to all subsequent calls to `runNow` until the queue drain bellow the cap. - -Our current model, then, is this: -``` - Polling Interval --> filter(availableWorkers > 0) - mapTo([]) -------\\ - Task Scheduled --> filter(availableWorkers > 0) - mapTo([]) --------||==>Set([]+[]+[`1`,`2`]) ==> work([`1`,`2`]) - Run Task `1` Now --\ // - ----> buffer(availableWorkers > 0) -- [`1`,`2`] -// - Run Task `2` Now --/ -``` - -## Limitations in v1.0 - -In v1, the system only understands 1 minute increments (e.g. '1m', '7m'). Tasks which need something more robust will need to specify their own "runAt" in their run method's return value. - -There is only a rudimentary mechanism for coordinating tasks and handling expired tasks. Tasks are considered expired if their runAt has arrived, and their status is still 'running'. - -There is no task history. Each run overwrites the previous run's state. One-time tasks are removed from the index upon completion regardless of success / failure. - -The task manager's public API is create / delete / list. Updates aren't directly supported, and listing should be scoped so that users only see their own tasks. - -## Testing - -- Unit tests: - ``` - cd x-pack - node scripts/jest --testPathPattern=task_manager --watch - ``` -- Integration tests: - ``` - node scripts/functional_tests_server.js --config x-pack/test/plugin_api_integration/config.js - node scripts/functional_test_runner --config x-pack/test/plugin_api_integration/config.js - ``` diff --git a/x-pack/legacy/plugins/task_manager/index.ts b/x-pack/legacy/plugins/task_manager/index.ts index 6f2bc3704bb67..276d1ea3accea 100644 --- a/x-pack/legacy/plugins/task_manager/index.ts +++ b/x-pack/legacy/plugins/task_manager/index.ts @@ -4,97 +4,4 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Root } from 'joi'; -import { Legacy } from 'kibana'; -import { Plugin, PluginSetupContract } from './plugin'; -import { SavedObjectsSerializer, SavedObjectsSchema } from '../../../../src/core/server'; -import mappings from './mappings.json'; -import { migrations } from './migrations'; - -export { PluginSetupContract as TaskManager }; -export { - TaskInstance, - ConcreteTaskInstance, - TaskRunCreatorFunction, - TaskStatus, - RunContext, -} from './task'; - -export function taskManager(kibana: any) { - return new kibana.Plugin({ - id: 'task_manager', - require: ['kibana', 'elasticsearch', 'xpack_main'], - configPrefix: 'xpack.task_manager', - config(Joi: Root) { - return Joi.object({ - enabled: Joi.boolean().default(true), - max_attempts: Joi.number() - .description( - 'The maximum number of times a task will be attempted before being abandoned as failed' - ) - .min(1) - .default(3), - poll_interval: Joi.number() - .description('How often, in milliseconds, the task manager will look for more work.') - .min(100) - .default(3000), - request_capacity: Joi.number() - .description('How many requests can Task Manager buffer before it rejects new requests.') - .min(1) - // a nice round contrived number, feel free to change as we learn how it behaves - .default(1000), - index: Joi.string() - .description('The name of the index used to store task information.') - .default('.kibana_task_manager') - .invalid(['.tasks']), - max_workers: Joi.number() - .description( - 'The maximum number of tasks that this Kibana instance will run simultaneously.' - ) - .min(1) // disable the task manager rather than trying to specify it with 0 workers - .default(10), - }).default(); - }, - init(server: Legacy.Server) { - const plugin = new Plugin({ - logger: { - get: () => ({ - info: (message: string) => server.log(['info', 'task_manager'], message), - debug: (message: string) => server.log(['debug', 'task_manager'], message), - warn: (message: string) => server.log(['warn', 'task_manager'], message), - error: (message: string) => server.log(['error', 'task_manager'], message), - }), - }, - }); - const schema = new SavedObjectsSchema(this.kbnServer.uiExports.savedObjectSchemas); - const serializer = new SavedObjectsSerializer(schema); - const setupContract = plugin.setup( - {}, - { - serializer, - config: server.config(), - elasticsearch: server.plugins.elasticsearch, - savedObjects: server.savedObjects, - } - ); - this.kbnServer.afterPluginsInit(() => { - plugin.start(); - }); - server.expose(setupContract); - }, - uiExports: { - mappings, - migrations, - savedObjectSchemas: { - task: { - hidden: true, - isNamespaceAgnostic: true, - convertToAliasScript: `ctx._id = ctx._source.type + ':' + ctx._id`, - indexPattern(config: any) { - return config.get('xpack.task_manager.index'); - }, - }, - }, - }, - }); -} +export * from './server/'; diff --git a/x-pack/legacy/plugins/task_manager/migrations.ts b/x-pack/legacy/plugins/task_manager/migrations.ts deleted file mode 100644 index 2853be0c4d5b2..0000000000000 --- a/x-pack/legacy/plugins/task_manager/migrations.ts +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -// Task manager uses an unconventional directory structure so the linter marks this as a violation, server files should -// be moved under task_manager/server/ -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { SavedObject } from 'src/core/server'; - -export const migrations = { - task: { - '7.4.0': (doc: SavedObject) => ({ - ...doc, - updated_at: new Date().toISOString(), - }), - '7.6.0': moveIntervalIntoSchedule, - }, -}; - -function moveIntervalIntoSchedule({ - attributes: { interval, ...attributes }, - ...doc -}: SavedObject) { - return { - ...doc, - attributes: { - ...attributes, - ...(interval - ? { - schedule: { - interval, - }, - } - : {}), - }, - }; -} diff --git a/x-pack/legacy/plugins/task_manager/plugin.test.ts b/x-pack/legacy/plugins/task_manager/plugin.test.ts deleted file mode 100644 index f7c5b35da50c2..0000000000000 --- a/x-pack/legacy/plugins/task_manager/plugin.test.ts +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { Plugin, LegacyDeps } from './plugin'; -import { mockLogger } from './test_utils'; -import { TaskManager } from './task_manager'; - -jest.mock('./task_manager'); - -describe('Task Manager Plugin', () => { - let plugin: Plugin; - const mockCoreSetup = {}; - const mockLegacyDeps: LegacyDeps = { - config: { - get: jest.fn(), - }, - serializer: {}, - elasticsearch: { - getCluster: jest.fn(), - }, - savedObjects: { - getSavedObjectsRepository: jest.fn(), - }, - }; - - beforeEach(() => { - jest.resetAllMocks(); - mockLegacyDeps.elasticsearch.getCluster.mockReturnValue({ callWithInternalUser: jest.fn() }); - plugin = new Plugin({ - logger: { - get: mockLogger, - }, - }); - }); - - describe('setup()', () => { - test('exposes proper contract', async () => { - const setupResult = plugin.setup(mockCoreSetup, mockLegacyDeps); - expect(setupResult).toMatchInlineSnapshot(` - Object { - "addMiddleware": [Function], - "ensureScheduled": [Function], - "fetch": [Function], - "registerTaskDefinitions": [Function], - "remove": [Function], - "runNow": [Function], - "schedule": [Function], - } - `); - }); - }); - - describe('start()', () => { - test('properly starts up the task manager', async () => { - plugin.setup(mockCoreSetup, mockLegacyDeps); - plugin.start(); - const taskManager = (TaskManager as any).mock.instances[0]; - expect(taskManager.start).toHaveBeenCalled(); - }); - }); - - describe('stop()', () => { - test('properly stops up the task manager', async () => { - plugin.setup(mockCoreSetup, mockLegacyDeps); - plugin.stop(); - const taskManager = (TaskManager as any).mock.instances[0]; - expect(taskManager.stop).toHaveBeenCalled(); - }); - }); -}); diff --git a/x-pack/legacy/plugins/task_manager/plugin.ts b/x-pack/legacy/plugins/task_manager/plugin.ts deleted file mode 100644 index 08382d1d825b6..0000000000000 --- a/x-pack/legacy/plugins/task_manager/plugin.ts +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { Logger } from './types'; -import { TaskManager } from './task_manager'; - -export interface PluginSetupContract { - fetch: TaskManager['fetch']; - remove: TaskManager['remove']; - schedule: TaskManager['schedule']; - runNow: TaskManager['runNow']; - ensureScheduled: TaskManager['ensureScheduled']; - addMiddleware: TaskManager['addMiddleware']; - registerTaskDefinitions: TaskManager['registerTaskDefinitions']; -} - -export interface LegacyDeps { - config: any; - serializer: any; - elasticsearch: any; - savedObjects: any; -} - -interface PluginInitializerContext { - logger: { - get: () => Logger; - }; -} - -export class Plugin { - private logger: Logger; - private taskManager?: TaskManager; - - constructor(initializerContext: PluginInitializerContext) { - this.logger = initializerContext.logger.get(); - } - - // TODO: Make asynchronous like new platform - public setup( - core: {}, - { config, serializer, elasticsearch, savedObjects }: LegacyDeps - ): PluginSetupContract { - const { callWithInternalUser } = elasticsearch.getCluster('admin'); - const savedObjectsRepository = savedObjects.getSavedObjectsRepository(callWithInternalUser, [ - 'task', - ]); - - const taskManager = new TaskManager({ - config, - savedObjectsRepository, - serializer, - callWithInternalUser, - logger: this.logger, - }); - this.taskManager = taskManager; - - return { - fetch: (...args) => taskManager.fetch(...args), - remove: (...args) => taskManager.remove(...args), - schedule: (...args) => taskManager.schedule(...args), - runNow: (...args) => taskManager.runNow(...args), - ensureScheduled: (...args) => taskManager.ensureScheduled(...args), - addMiddleware: (...args) => taskManager.addMiddleware(...args), - registerTaskDefinitions: (...args) => taskManager.registerTaskDefinitions(...args), - }; - } - - public start() { - if (this.taskManager) { - this.taskManager.start(); - } - } - - public stop() { - if (this.taskManager) { - this.taskManager.stop(); - } - } -} diff --git a/x-pack/legacy/plugins/task_manager/server/index.ts b/x-pack/legacy/plugins/task_manager/server/index.ts new file mode 100644 index 0000000000000..56135bb27326b --- /dev/null +++ b/x-pack/legacy/plugins/task_manager/server/index.ts @@ -0,0 +1,75 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Root } from 'joi'; +import { Legacy } from 'kibana'; +import mappings from './mappings.json'; +import { migrations } from './migrations'; + +import { createLegacyApi, getTaskManagerSetup } from './legacy'; +export { LegacyTaskManagerApi, getTaskManagerSetup, getTaskManagerStart } from './legacy'; + +// Once all plugins are migrated to NP, this can be removed +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { TaskManager } from '../../../../plugins/task_manager/server/task_manager'; + +const savedObjectSchemas = { + task: { + hidden: true, + isNamespaceAgnostic: true, + convertToAliasScript: `ctx._id = ctx._source.type + ':' + ctx._id`, + indexPattern(config: any) { + return config.get('xpack.task_manager.index'); + }, + }, +}; + +export function taskManager(kibana: any) { + return new kibana.Plugin({ + id: 'task_manager', + require: ['kibana', 'elasticsearch', 'xpack_main'], + configPrefix: 'xpack.task_manager', + config(Joi: Root) { + return Joi.object({ + enabled: Joi.boolean().default(true), + index: Joi.string() + .description('The name of the index used to store task information.') + .default('.kibana_task_manager') + .invalid(['.tasks']), + }).default(); + }, + init(server: Legacy.Server) { + /* + * We must expose the New Platform Task Manager Plugin via the legacy Api + * as removing it now would be a breaking change - we'll remove this in v8.0.0 + */ + server.expose( + createLegacyApi( + getTaskManagerSetup(server)! + .registerLegacyAPI({ + savedObjectSchemas, + }) + .then((taskManagerPlugin: TaskManager) => { + // we can't tell the Kibana Platform Task Manager plugin to + // to wait to `start` as that happens before legacy plugins + // instead we will start the internal Task Manager plugin when + // all legacy plugins have finished initializing + // Once all plugins are migrated to NP, this can be removed + this.kbnServer.afterPluginsInit(() => { + taskManagerPlugin.start(); + }); + return taskManagerPlugin; + }) + ) + ); + }, + uiExports: { + mappings, + migrations, + savedObjectSchemas, + }, + }); +} diff --git a/x-pack/legacy/plugins/task_manager/server/legacy.ts b/x-pack/legacy/plugins/task_manager/server/legacy.ts new file mode 100644 index 0000000000000..772309d67c334 --- /dev/null +++ b/x-pack/legacy/plugins/task_manager/server/legacy.ts @@ -0,0 +1,57 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Server } from 'src/legacy/server/kbn_server'; +import { + TaskManagerSetupContract, + TaskManagerStartContract, +} from '../../../../plugins/task_manager/server'; + +import { Middleware } from '../../../../plugins/task_manager/server/lib/middleware.js'; +import { + TaskDictionary, + TaskInstanceWithDeprecatedFields, + TaskInstanceWithId, + TaskDefinition, +} from '../../../../plugins/task_manager/server/task.js'; +import { FetchOpts } from '../../../../plugins/task_manager/server/task_store.js'; + +// Once all plugins are migrated to NP and we can remove Legacy TaskManager in version 8.0.0, +// this can be removed +// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { TaskManager } from '../../../../plugins/task_manager/server/task_manager'; + +export type LegacyTaskManagerApi = Pick< + TaskManagerSetupContract, + 'addMiddleware' | 'registerTaskDefinitions' +> & + TaskManagerStartContract; + +export function getTaskManagerSetup(server: Server): TaskManagerSetupContract | undefined { + return server?.newPlatform?.setup?.plugins?.taskManager as TaskManagerSetupContract; +} + +export function getTaskManagerStart(server: Server): TaskManagerStartContract | undefined { + return server?.newPlatform?.start?.plugins?.taskManager as TaskManagerStartContract; +} + +export function createLegacyApi(legacyTaskManager: Promise): LegacyTaskManagerApi { + return { + addMiddleware: (middleware: Middleware) => { + legacyTaskManager.then((tm: TaskManager) => tm.addMiddleware(middleware)); + }, + registerTaskDefinitions: (taskDefinitions: TaskDictionary) => { + legacyTaskManager.then((tm: TaskManager) => tm.registerTaskDefinitions(taskDefinitions)); + }, + fetch: (opts: FetchOpts) => legacyTaskManager.then((tm: TaskManager) => tm.fetch(opts)), + remove: (id: string) => legacyTaskManager.then((tm: TaskManager) => tm.remove(id)), + schedule: (taskInstance: TaskInstanceWithDeprecatedFields, options?: any) => + legacyTaskManager.then((tm: TaskManager) => tm.schedule(taskInstance, options)), + runNow: (taskId: string) => legacyTaskManager.then((tm: TaskManager) => tm.runNow(taskId)), + ensureScheduled: (taskInstance: TaskInstanceWithId, options?: any) => + legacyTaskManager.then((tm: TaskManager) => tm.ensureScheduled(taskInstance, options)), + }; +} diff --git a/x-pack/legacy/plugins/task_manager/mappings.json b/x-pack/legacy/plugins/task_manager/server/mappings.json similarity index 100% rename from x-pack/legacy/plugins/task_manager/mappings.json rename to x-pack/legacy/plugins/task_manager/server/mappings.json diff --git a/x-pack/legacy/plugins/task_manager/server/migrations.ts b/x-pack/legacy/plugins/task_manager/server/migrations.ts new file mode 100644 index 0000000000000..7dce763ddf309 --- /dev/null +++ b/x-pack/legacy/plugins/task_manager/server/migrations.ts @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { SavedObject } from '../../../../../src/core/server'; + +export const migrations = { + task: { + '7.4.0': (doc: SavedObject) => ({ + ...doc, + updated_at: new Date().toISOString(), + }), + '7.6.0': moveIntervalIntoSchedule, + }, +}; + +function moveIntervalIntoSchedule({ + attributes: { interval, ...attributes }, + ...doc +}: SavedObject) { + return { + ...doc, + attributes: { + ...attributes, + ...(interval + ? { + schedule: { + interval, + }, + } + : {}), + }, + }; +} diff --git a/x-pack/legacy/plugins/task_manager/server/task_manager.mock.ts b/x-pack/legacy/plugins/task_manager/server/task_manager.mock.ts new file mode 100644 index 0000000000000..a4b80d902d098 --- /dev/null +++ b/x-pack/legacy/plugins/task_manager/server/task_manager.mock.ts @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + TaskManagerSetupContract, + TaskManagerStartContract, +} from '../../../../plugins/task_manager/server'; +import { Subject } from 'rxjs'; + +export const taskManagerMock = { + setup(overrides: Partial> = {}) { + const mocked: jest.Mocked = { + registerTaskDefinitions: jest.fn(), + addMiddleware: jest.fn(), + config$: new Subject(), + registerLegacyAPI: jest.fn(), + ...overrides, + }; + return mocked; + }, + start(overrides: Partial> = {}) { + const mocked: jest.Mocked = { + ensureScheduled: jest.fn(), + schedule: jest.fn(), + fetch: jest.fn(), + runNow: jest.fn(), + remove: jest.fn(), + ...overrides, + }; + return mocked; + }, +}; diff --git a/x-pack/legacy/plugins/task_manager/task_manager.mock.ts b/x-pack/legacy/plugins/task_manager/task_manager.mock.ts deleted file mode 100644 index 4837e75fd3160..0000000000000 --- a/x-pack/legacy/plugins/task_manager/task_manager.mock.ts +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { TaskManager } from './types'; - -const createTaskManagerMock = () => { - const mocked: jest.Mocked = { - registerTaskDefinitions: jest.fn(), - addMiddleware: jest.fn(), - ensureScheduled: jest.fn(), - schedule: jest.fn(), - fetch: jest.fn(), - runNow: jest.fn(), - remove: jest.fn(), - start: jest.fn(), - stop: jest.fn(), - }; - return mocked; -}; - -export const taskManagerMock = { - create: createTaskManagerMock, -}; diff --git a/x-pack/legacy/plugins/task_manager/task_runner.test.ts b/x-pack/legacy/plugins/task_manager/task_runner.test.ts deleted file mode 100644 index f3f9b9a88c68c..0000000000000 --- a/x-pack/legacy/plugins/task_manager/task_runner.test.ts +++ /dev/null @@ -1,952 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import _ from 'lodash'; -import sinon from 'sinon'; -import { minutesFromNow } from './lib/intervals'; -import { asOk, asErr } from './lib/result_type'; -import { TaskEvent, asTaskRunEvent, asTaskMarkRunningEvent } from './task_events'; -import { ConcreteTaskInstance, TaskStatus } from './task'; -import { TaskManagerRunner } from './task_runner'; -import { mockLogger } from './test_utils'; -// Task manager uses an unconventional directory structure so the linter marks this as a violation, server files should -// be moved under task_manager/server/ -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { SavedObjectsErrorHelpers } from '../../../../src/core/server'; - -let fakeTimer: sinon.SinonFakeTimers; - -beforeAll(() => { - fakeTimer = sinon.useFakeTimers(); -}); - -afterAll(() => fakeTimer.restore()); - -describe('TaskManagerRunner', () => { - test('provides details about the task that is running', () => { - const { runner } = testOpts({ - instance: { - id: 'foo', - taskType: 'bar', - }, - }); - - expect(runner.id).toEqual('foo'); - expect(runner.taskType).toEqual('bar'); - expect(runner.toString()).toEqual('bar "foo"'); - }); - - test('warns if the task returns an unexpected result', async () => { - await allowsReturnType(undefined); - await allowsReturnType({}); - await allowsReturnType({ - runAt: new Date(), - }); - await allowsReturnType({ - error: new Error('Dang it!'), - }); - await allowsReturnType({ - state: { shazm: true }, - }); - await disallowsReturnType('hm....'); - await disallowsReturnType({ - whatIsThis: '?!!?', - }); - }); - - test('queues a reattempt if the task fails', async () => { - const initialAttempts = _.random(0, 2); - const id = Date.now().toString(); - const { runner, store } = testOpts({ - instance: { - id, - attempts: initialAttempts, - params: { a: 'b' }, - state: { hey: 'there' }, - }, - definitions: { - bar: { - createTaskRunner: () => ({ - async run() { - throw new Error('Dangit!'); - }, - }), - }, - }, - }); - - await runner.run(); - - sinon.assert.calledOnce(store.update); - const instance = store.update.args[0][0]; - - expect(instance.id).toEqual(id); - expect(instance.runAt.getTime()).toEqual(minutesFromNow(initialAttempts * 5).getTime()); - expect(instance.params).toEqual({ a: 'b' }); - expect(instance.state).toEqual({ hey: 'there' }); - }); - - test('reschedules tasks that have an schedule', async () => { - const { runner, store } = testOpts({ - instance: { - schedule: { interval: '10m' }, - status: TaskStatus.Running, - startedAt: new Date(), - }, - definitions: { - bar: { - createTaskRunner: () => ({ - async run() { - return; - }, - }), - }, - }, - }); - - await runner.run(); - - sinon.assert.calledOnce(store.update); - const instance = store.update.args[0][0]; - - expect(instance.runAt.getTime()).toBeGreaterThan(minutesFromNow(9).getTime()); - expect(instance.runAt.getTime()).toBeLessThanOrEqual(minutesFromNow(10).getTime()); - }); - - test('reschedules tasks that return a runAt', async () => { - const runAt = minutesFromNow(_.random(1, 10)); - const { runner, store } = testOpts({ - definitions: { - bar: { - createTaskRunner: () => ({ - async run() { - return { runAt }; - }, - }), - }, - }, - }); - - await runner.run(); - - sinon.assert.calledOnce(store.update); - sinon.assert.calledWithMatch(store.update, { runAt }); - }); - - test('tasks that return runAt override the schedule', async () => { - const runAt = minutesFromNow(_.random(5)); - const { runner, store } = testOpts({ - instance: { - schedule: { interval: '20m' }, - }, - definitions: { - bar: { - createTaskRunner: () => ({ - async run() { - return { runAt }; - }, - }), - }, - }, - }); - - await runner.run(); - - sinon.assert.calledOnce(store.update); - sinon.assert.calledWithMatch(store.update, { runAt }); - }); - - test('removes non-recurring tasks after they complete', async () => { - const id = _.random(1, 20).toString(); - const { runner, store } = testOpts({ - instance: { - id, - schedule: undefined, - }, - definitions: { - bar: { - createTaskRunner: () => ({ - async run() { - return undefined; - }, - }), - }, - }, - }); - - await runner.run(); - - sinon.assert.calledOnce(store.remove); - sinon.assert.calledWith(store.remove, id); - }); - - test('cancel cancels the task runner, if it is cancellable', async () => { - let wasCancelled = false; - const { runner, logger } = testOpts({ - definitions: { - bar: { - createTaskRunner: () => ({ - async run() { - const promise = new Promise(r => setTimeout(r, 1000)); - fakeTimer.tick(1000); - await promise; - }, - async cancel() { - wasCancelled = true; - }, - }), - }, - }, - }); - - const promise = runner.run(); - await Promise.resolve(); - await runner.cancel(); - await promise; - - expect(wasCancelled).toBeTruthy(); - expect(logger.warn).not.toHaveBeenCalled(); - }); - - test('warns if cancel is called on a non-cancellable task', async () => { - const { runner, logger } = testOpts({ - definitions: { - bar: { - createTaskRunner: () => ({ - run: async () => undefined, - }), - }, - }, - }); - - const promise = runner.run(); - await runner.cancel(); - await promise; - - expect(logger.warn).toHaveBeenCalledTimes(1); - expect(logger.warn.mock.calls[0][0]).toMatchInlineSnapshot( - `"The task bar \\"foo\\" is not cancellable."` - ); - }); - - test('sets startedAt, status, attempts and retryAt when claiming a task', async () => { - const timeoutMinutes = 1; - const id = _.random(1, 20).toString(); - const initialAttempts = _.random(0, 2); - const { runner, store } = testOpts({ - instance: { - id, - attempts: initialAttempts, - schedule: undefined, - }, - definitions: { - bar: { - timeout: `${timeoutMinutes}m`, - createTaskRunner: () => ({ - run: async () => undefined, - }), - }, - }, - }); - - await runner.markTaskAsRunning(); - - sinon.assert.calledOnce(store.update); - const instance = store.update.args[0][0]; - - expect(instance.attempts).toEqual(initialAttempts + 1); - expect(instance.status).toBe('running'); - expect(instance.startedAt.getTime()).toEqual(Date.now()); - expect(instance.retryAt.getTime()).toEqual( - minutesFromNow((initialAttempts + 1) * 5).getTime() + timeoutMinutes * 60 * 1000 - ); - }); - - test('uses getRetry function (returning date) on error when defined', async () => { - const initialAttempts = _.random(1, 3); - const nextRetry = new Date(Date.now() + _.random(15, 100) * 1000); - const id = Date.now().toString(); - const getRetryStub = sinon.stub().returns(nextRetry); - const error = new Error('Dangit!'); - const { runner, store } = testOpts({ - instance: { - id, - attempts: initialAttempts, - }, - definitions: { - bar: { - getRetry: getRetryStub, - createTaskRunner: () => ({ - async run() { - throw error; - }, - }), - }, - }, - }); - - await runner.run(); - - sinon.assert.calledOnce(store.update); - sinon.assert.calledWith(getRetryStub, initialAttempts, error); - const instance = store.update.args[0][0]; - - expect(instance.runAt.getTime()).toEqual(nextRetry.getTime()); - }); - - test('uses getRetry function (returning true) on error when defined', async () => { - const initialAttempts = _.random(1, 3); - const id = Date.now().toString(); - const getRetryStub = sinon.stub().returns(true); - const error = new Error('Dangit!'); - const { runner, store } = testOpts({ - instance: { - id, - attempts: initialAttempts, - }, - definitions: { - bar: { - getRetry: getRetryStub, - createTaskRunner: () => ({ - async run() { - throw error; - }, - }), - }, - }, - }); - - await runner.run(); - - sinon.assert.calledOnce(store.update); - sinon.assert.calledWith(getRetryStub, initialAttempts, error); - const instance = store.update.args[0][0]; - - const expectedRunAt = new Date(Date.now() + initialAttempts * 5 * 60 * 1000); - expect(instance.runAt.getTime()).toEqual(expectedRunAt.getTime()); - }); - - test('uses getRetry function (returning false) on error when defined', async () => { - const initialAttempts = _.random(1, 3); - const id = Date.now().toString(); - const getRetryStub = sinon.stub().returns(false); - const error = new Error('Dangit!'); - const { runner, store } = testOpts({ - instance: { - id, - attempts: initialAttempts, - }, - definitions: { - bar: { - getRetry: getRetryStub, - createTaskRunner: () => ({ - async run() { - throw error; - }, - }), - }, - }, - }); - - await runner.run(); - - sinon.assert.calledOnce(store.update); - sinon.assert.calledWith(getRetryStub, initialAttempts, error); - const instance = store.update.args[0][0]; - - expect(instance.status).toBe('failed'); - }); - - test('bypasses getRetry function (returning false) on error of a recurring task', async () => { - const initialAttempts = _.random(1, 3); - const id = Date.now().toString(); - const getRetryStub = sinon.stub().returns(false); - const error = new Error('Dangit!'); - const { runner, store } = testOpts({ - instance: { - id, - attempts: initialAttempts, - schedule: { interval: '1m' }, - startedAt: new Date(), - }, - definitions: { - bar: { - getRetry: getRetryStub, - createTaskRunner: () => ({ - async run() { - throw error; - }, - }), - }, - }, - }); - - await runner.run(); - - sinon.assert.calledOnce(store.update); - sinon.assert.notCalled(getRetryStub); - const instance = store.update.args[0][0]; - - const nextIntervalDelay = 60000; // 1m - const expectedRunAt = new Date(Date.now() + nextIntervalDelay); - expect(instance.runAt.getTime()).toEqual(expectedRunAt.getTime()); - }); - - test('uses getRetry (returning date) to set retryAt when defined', async () => { - const id = _.random(1, 20).toString(); - const initialAttempts = _.random(1, 3); - const nextRetry = new Date(Date.now() + _.random(15, 100) * 1000); - const timeoutMinutes = 1; - const getRetryStub = sinon.stub().returns(nextRetry); - const { runner, store } = testOpts({ - instance: { - id, - attempts: initialAttempts, - schedule: undefined, - }, - definitions: { - bar: { - timeout: `${timeoutMinutes}m`, - getRetry: getRetryStub, - createTaskRunner: () => ({ - run: async () => undefined, - }), - }, - }, - }); - - await runner.markTaskAsRunning(); - - sinon.assert.calledOnce(store.update); - sinon.assert.calledWith(getRetryStub, initialAttempts + 1); - const instance = store.update.args[0][0]; - - expect(instance.retryAt.getTime()).toEqual( - new Date(nextRetry.getTime() + timeoutMinutes * 60 * 1000).getTime() - ); - }); - - test('it returns false when markTaskAsRunning fails due to VERSION_CONFLICT_STATUS', async () => { - const id = _.random(1, 20).toString(); - const initialAttempts = _.random(1, 3); - const nextRetry = new Date(Date.now() + _.random(15, 100) * 1000); - const timeoutMinutes = 1; - const getRetryStub = sinon.stub().returns(nextRetry); - const { runner, store } = testOpts({ - instance: { - id, - attempts: initialAttempts, - schedule: undefined, - }, - definitions: { - bar: { - timeout: `${timeoutMinutes}m`, - getRetry: getRetryStub, - createTaskRunner: () => ({ - run: async () => undefined, - }), - }, - }, - }); - - store.update = sinon - .stub() - .throws( - SavedObjectsErrorHelpers.decorateConflictError(new Error('repo error')).output.payload - ); - - expect(await runner.markTaskAsRunning()).toEqual(false); - }); - - test('it throw when markTaskAsRunning fails for unexpected reasons', async () => { - const id = _.random(1, 20).toString(); - const initialAttempts = _.random(1, 3); - const nextRetry = new Date(Date.now() + _.random(15, 100) * 1000); - const timeoutMinutes = 1; - const getRetryStub = sinon.stub().returns(nextRetry); - const { runner, store } = testOpts({ - instance: { - id, - attempts: initialAttempts, - schedule: undefined, - }, - definitions: { - bar: { - timeout: `${timeoutMinutes}m`, - getRetry: getRetryStub, - createTaskRunner: () => ({ - run: async () => undefined, - }), - }, - }, - }); - - store.update = sinon - .stub() - .throws(SavedObjectsErrorHelpers.createGenericNotFoundError('type', 'id').output.payload); - - return expect(runner.markTaskAsRunning()).rejects.toMatchInlineSnapshot(` - Object { - "error": "Not Found", - "message": "Saved object [type/id] not found", - "statusCode": 404, - } - `); - }); - - test('uses getRetry (returning true) to set retryAt when defined', async () => { - const id = _.random(1, 20).toString(); - const initialAttempts = _.random(1, 3); - const timeoutMinutes = 1; - const getRetryStub = sinon.stub().returns(true); - const { runner, store } = testOpts({ - instance: { - id, - attempts: initialAttempts, - schedule: undefined, - }, - definitions: { - bar: { - timeout: `${timeoutMinutes}m`, - getRetry: getRetryStub, - createTaskRunner: () => ({ - run: async () => undefined, - }), - }, - }, - }); - - await runner.markTaskAsRunning(); - - sinon.assert.calledOnce(store.update); - sinon.assert.calledWith(getRetryStub, initialAttempts + 1); - const instance = store.update.args[0][0]; - - const attemptDelay = (initialAttempts + 1) * 5 * 60 * 1000; - const timeoutDelay = timeoutMinutes * 60 * 1000; - expect(instance.retryAt.getTime()).toEqual( - new Date(Date.now() + attemptDelay + timeoutDelay).getTime() - ); - }); - - test('uses getRetry (returning false) to set retryAt when defined', async () => { - const id = _.random(1, 20).toString(); - const initialAttempts = _.random(1, 3); - const timeoutMinutes = 1; - const getRetryStub = sinon.stub().returns(false); - const { runner, store } = testOpts({ - instance: { - id, - attempts: initialAttempts, - schedule: undefined, - }, - definitions: { - bar: { - timeout: `${timeoutMinutes}m`, - getRetry: getRetryStub, - createTaskRunner: () => ({ - run: async () => undefined, - }), - }, - }, - }); - - await runner.markTaskAsRunning(); - - sinon.assert.calledOnce(store.update); - sinon.assert.calledWith(getRetryStub, initialAttempts + 1); - const instance = store.update.args[0][0]; - - expect(instance.retryAt).toBeNull(); - expect(instance.status).toBe('running'); - }); - - test('bypasses getRetry (returning false) of a recurring task to set retryAt when defined', async () => { - const id = _.random(1, 20).toString(); - const initialAttempts = _.random(1, 3); - const timeoutMinutes = 1; - const getRetryStub = sinon.stub().returns(false); - const { runner, store } = testOpts({ - instance: { - id, - attempts: initialAttempts, - schedule: { interval: '1m' }, - startedAt: new Date(), - }, - definitions: { - bar: { - timeout: `${timeoutMinutes}m`, - getRetry: getRetryStub, - createTaskRunner: () => ({ - run: async () => undefined, - }), - }, - }, - }); - - await runner.markTaskAsRunning(); - - sinon.assert.calledOnce(store.update); - sinon.assert.notCalled(getRetryStub); - const instance = store.update.args[0][0]; - - const timeoutDelay = timeoutMinutes * 60 * 1000; - expect(instance.retryAt.getTime()).toEqual(new Date(Date.now() + timeoutDelay).getTime()); - }); - - test('Fails non-recurring task when maxAttempts reached', async () => { - const id = _.random(1, 20).toString(); - const initialAttempts = 3; - const { runner, store } = testOpts({ - instance: { - id, - attempts: initialAttempts, - schedule: undefined, - }, - definitions: { - bar: { - maxAttempts: 3, - createTaskRunner: () => ({ - run: async () => { - throw new Error(); - }, - }), - }, - }, - }); - - await runner.run(); - - sinon.assert.calledOnce(store.update); - const instance = store.update.args[0][0]; - expect(instance.attempts).toEqual(3); - expect(instance.status).toEqual('failed'); - expect(instance.retryAt).toBeNull(); - expect(instance.runAt.getTime()).toBeLessThanOrEqual(Date.now()); - }); - - test(`Doesn't fail recurring tasks when maxAttempts reached`, async () => { - const id = _.random(1, 20).toString(); - const initialAttempts = 3; - const intervalSeconds = 10; - const { runner, store } = testOpts({ - instance: { - id, - attempts: initialAttempts, - schedule: { interval: `${intervalSeconds}s` }, - startedAt: new Date(), - }, - definitions: { - bar: { - maxAttempts: 3, - createTaskRunner: () => ({ - run: async () => { - throw new Error(); - }, - }), - }, - }, - }); - - await runner.run(); - - sinon.assert.calledOnce(store.update); - const instance = store.update.args[0][0]; - expect(instance.attempts).toEqual(3); - expect(instance.status).toEqual('idle'); - expect(instance.runAt.getTime()).toEqual( - new Date(Date.now() + intervalSeconds * 1000).getTime() - ); - }); - - describe('TaskEvents', () => { - test('emits TaskEvent when a task is marked as running', async () => { - const id = _.random(1, 20).toString(); - const onTaskEvent = jest.fn(); - const { runner, instance, store } = testOpts({ - onTaskEvent, - instance: { - id, - }, - definitions: { - bar: { - timeout: `1m`, - getRetry: () => {}, - createTaskRunner: () => ({ - run: async () => undefined, - }), - }, - }, - }); - - store.update.returns(instance); - - await runner.markTaskAsRunning(); - - expect(onTaskEvent).toHaveBeenCalledWith(asTaskMarkRunningEvent(id, asOk(instance))); - }); - - test('emits TaskEvent when a task fails to be marked as running', async () => { - expect.assertions(2); - - const id = _.random(1, 20).toString(); - const onTaskEvent = jest.fn(); - const { runner, store } = testOpts({ - onTaskEvent, - instance: { - id, - }, - definitions: { - bar: { - timeout: `1m`, - getRetry: () => {}, - createTaskRunner: () => ({ - run: async () => undefined, - }), - }, - }, - }); - - store.update.throws(new Error('cant mark as running')); - - try { - await runner.markTaskAsRunning(); - } catch (err) { - expect(onTaskEvent).toHaveBeenCalledWith(asTaskMarkRunningEvent(id, asErr(err))); - } - expect(onTaskEvent).toHaveBeenCalledTimes(1); - }); - - test('emits TaskEvent when a task is run successfully', async () => { - const id = _.random(1, 20).toString(); - const onTaskEvent = jest.fn(); - const { runner, instance } = testOpts({ - onTaskEvent, - instance: { - id, - }, - definitions: { - bar: { - createTaskRunner: () => ({ - async run() { - return {}; - }, - }), - }, - }, - }); - - await runner.run(); - - expect(onTaskEvent).toHaveBeenCalledWith(asTaskRunEvent(id, asOk(instance))); - }); - - test('emits TaskEvent when a recurring task is run successfully', async () => { - const id = _.random(1, 20).toString(); - const runAt = minutesFromNow(_.random(5)); - const onTaskEvent = jest.fn(); - const { runner, instance } = testOpts({ - onTaskEvent, - instance: { - id, - schedule: { interval: '1m' }, - }, - definitions: { - bar: { - createTaskRunner: () => ({ - async run() { - return { runAt }; - }, - }), - }, - }, - }); - - await runner.run(); - - expect(onTaskEvent).toHaveBeenCalledWith(asTaskRunEvent(id, asOk(instance))); - }); - - test('emits TaskEvent when a task run throws an error', async () => { - const id = _.random(1, 20).toString(); - const error = new Error('Dangit!'); - const onTaskEvent = jest.fn(); - const { runner } = testOpts({ - onTaskEvent, - instance: { - id, - }, - definitions: { - bar: { - createTaskRunner: () => ({ - async run() { - throw error; - }, - }), - }, - }, - }); - await runner.run(); - - expect(onTaskEvent).toHaveBeenCalledWith(asTaskRunEvent(id, asErr(error))); - expect(onTaskEvent).toHaveBeenCalledTimes(1); - }); - - test('emits TaskEvent when a task run returns an error', async () => { - const id = _.random(1, 20).toString(); - const error = new Error('Dangit!'); - const onTaskEvent = jest.fn(); - const { runner } = testOpts({ - onTaskEvent, - instance: { - id, - schedule: { interval: '1m' }, - startedAt: new Date(), - }, - definitions: { - bar: { - createTaskRunner: () => ({ - async run() { - return { error }; - }, - }), - }, - }, - }); - - await runner.run(); - - expect(onTaskEvent).toHaveBeenCalledWith(asTaskRunEvent(id, asErr(error))); - expect(onTaskEvent).toHaveBeenCalledTimes(1); - }); - - test('emits TaskEvent when a task returns an error and is marked as failed', async () => { - const id = _.random(1, 20).toString(); - const error = new Error('Dangit!'); - const onTaskEvent = jest.fn(); - const { runner, store } = testOpts({ - onTaskEvent, - instance: { - id, - startedAt: new Date(), - }, - definitions: { - bar: { - getRetry: () => false, - createTaskRunner: () => ({ - async run() { - return { error }; - }, - }), - }, - }, - }); - - await runner.run(); - - const instance = store.update.args[0][0]; - expect(instance.status).toBe('failed'); - - expect(onTaskEvent).toHaveBeenCalledWith(asTaskRunEvent(id, asErr(error))); - expect(onTaskEvent).toHaveBeenCalledTimes(1); - }); - }); - - interface TestOpts { - instance?: Partial; - definitions?: any; - onTaskEvent?: (event: TaskEvent) => void; - } - - function testOpts(opts: TestOpts) { - const callCluster = sinon.stub(); - const createTaskRunner = sinon.stub(); - const logger = mockLogger(); - - const instance = Object.assign( - { - id: 'foo', - taskType: 'bar', - sequenceNumber: 32, - primaryTerm: 32, - runAt: new Date(), - scheduledAt: new Date(), - startedAt: null, - retryAt: null, - attempts: 0, - params: {}, - scope: ['reporting'], - state: {}, - status: 'idle', - user: 'example', - ownerId: null, - }, - opts.instance || {} - ); - - const store = { - update: sinon.stub(), - remove: sinon.stub(), - maxAttempts: 5, - }; - - store.update.returns(instance); - - const runner = new TaskManagerRunner({ - beforeRun: context => Promise.resolve(context), - beforeMarkRunning: context => Promise.resolve(context), - logger, - store, - instance, - definitions: Object.assign(opts.definitions || {}, { - testbar: { - type: 'bar', - title: 'Bar!', - createTaskRunner, - }, - }), - onTaskEvent: opts.onTaskEvent, - }); - - return { - callCluster, - createTaskRunner, - runner, - logger, - store, - instance, - }; - } - - async function testReturn(result: any, shouldBeValid: boolean) { - const { runner, logger } = testOpts({ - definitions: { - bar: { - createTaskRunner: () => ({ - run: async () => result, - }), - }, - }, - }); - - await runner.run(); - - if (shouldBeValid) { - expect(logger.warn).not.toHaveBeenCalled(); - } else { - expect(logger.warn).toHaveBeenCalledTimes(1); - expect(logger.warn.mock.calls[0][0]).toMatch(/invalid task result/i); - } - } - - function allowsReturnType(result: any) { - return testReturn(result, true); - } - - function disallowsReturnType(result: any) { - return testReturn(result, false); - } -}); diff --git a/x-pack/legacy/plugins/transform/public/app/common/request.ts b/x-pack/legacy/plugins/transform/public/app/common/request.ts index 5f0c778695523..5d508f3d245d3 100644 --- a/x-pack/legacy/plugins/transform/public/app/common/request.ts +++ b/x-pack/legacy/plugins/transform/public/app/common/request.ts @@ -6,14 +6,14 @@ import { DefaultOperator } from 'elasticsearch'; -import { IndexPattern } from 'ui/index_patterns'; - import { dictionaryToArray } from '../../../common/types/common'; import { SavedSearchQuery } from '../lib/kibana'; import { StepDefineExposedState } from '../sections/create_transform/components/step_define/step_define_form'; import { StepDetailsExposedState } from '../sections/create_transform/components/step_details/step_details_form'; +import { IndexPattern } from '../../../../../../../src/plugins/data/public'; + import { getEsAggFromAggConfig, getEsAggFromGroupByConfig, diff --git a/x-pack/legacy/plugins/transform/public/app/lib/kibana/common.ts b/x-pack/legacy/plugins/transform/public/app/lib/kibana/common.ts index 62c87413c9bce..3e55d509a94ab 100644 --- a/x-pack/legacy/plugins/transform/public/app/lib/kibana/common.ts +++ b/x-pack/legacy/plugins/transform/public/app/lib/kibana/common.ts @@ -5,8 +5,11 @@ */ import { SavedObjectsClientContract, IUiSettingsClient } from 'src/core/public'; -import { IndexPattern as IndexPatternType } from 'ui/index_patterns'; -import { esQuery, IndexPatternsContract } from '../../../../../../../../src/plugins/data/public'; +import { + IndexPattern, + esQuery, + IndexPatternsContract, +} from '../../../../../../../../src/plugins/data/public'; type IndexPatternId = string; type SavedSearchId = string; @@ -68,7 +71,7 @@ export function loadCurrentSavedSearch(savedSearches: any, savedSearchId: SavedS // Helper for creating the items used for searching and job creation. export function createSearchItems( - indexPattern: IndexPatternType | undefined, + indexPattern: IndexPattern | undefined, savedSearch: any, config: IUiSettingsClient ) { diff --git a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/common.test.ts b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/common.test.ts index c1a7fb3424c3a..78ad217a69e3d 100644 --- a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/common.test.ts +++ b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/common.test.ts @@ -14,7 +14,7 @@ import { } from '../../../../common'; import { getPivotPreviewDevConsoleStatement, getPivotDropdownOptions } from './common'; -import { IndexPattern } from 'ui/index_patterns'; +import { IndexPattern } from '../../../../../../../../../../src/plugins/data/public'; describe('Transform: Define Pivot Common', () => { test('getPivotDropdownOptions()', () => { diff --git a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/common.ts b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/common.ts index 54b810b7b6686..b4b03c1f0d571 100644 --- a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/common.ts +++ b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/common.ts @@ -5,10 +5,10 @@ */ import { get } from 'lodash'; import { EuiComboBoxOptionProps } from '@elastic/eui'; - -import { IndexPattern } from 'ui/index_patterns'; - -import { KBN_FIELD_TYPES } from '../../../../../../../../../../src/plugins/data/public'; +import { + IndexPattern, + KBN_FIELD_TYPES, +} from '../../../../../../../../../../src/plugins/data/public'; import { PreviewRequestBody, diff --git a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/use_pivot_preview_data.test.tsx b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/use_pivot_preview_data.test.tsx index 824ff0462cbba..1ad8ed099b241 100644 --- a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/use_pivot_preview_data.test.tsx +++ b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/use_pivot_preview_data.test.tsx @@ -14,7 +14,7 @@ import { UsePivotPreviewDataReturnType, } from './use_pivot_preview_data'; -import { IndexPattern } from 'ui/index_patterns'; +import { IndexPattern } from '../../../../../../../../../../src/plugins/data/public'; jest.mock('../../../../hooks/use_api'); diff --git a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/use_pivot_preview_data.ts b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/use_pivot_preview_data.ts index e02f2473fc10b..84fafcad8151e 100644 --- a/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/use_pivot_preview_data.ts +++ b/x-pack/legacy/plugins/transform/public/app/sections/create_transform/components/step_define/use_pivot_preview_data.ts @@ -6,13 +6,15 @@ import { useEffect, useState } from 'react'; -import { IndexPattern } from 'ui/index_patterns'; - import { dictionaryToArray } from '../../../../../../common/types/common'; import { useApi } from '../../../../hooks/use_api'; import { Dictionary } from '../../../../../../common/types/common'; -import { ES_FIELD_TYPES } from '../../../../../../../../../../src/plugins/data/public'; +import { + IndexPattern, + ES_FIELD_TYPES, +} from '../../../../../../../../../../src/plugins/data/public'; + import { getPreviewRequestBody, PreviewRequestBody, diff --git a/x-pack/legacy/plugins/transform/public/shim.ts b/x-pack/legacy/plugins/transform/public/shim.ts index f8edc752c9a21..d739dd2edddcc 100644 --- a/x-pack/legacy/plugins/transform/public/shim.ts +++ b/x-pack/legacy/plugins/transform/public/shim.ts @@ -47,6 +47,7 @@ export interface Core extends npCore { esDocBasePath: string; esPluginDocBasePath: string; esStackOverviewDocBasePath: string; + esMLDocBasePath: string; }; docTitle: { change: typeof docTitle.change; @@ -93,6 +94,7 @@ export function createPublicShim(): { core: Core; plugins: Plugins } { esDocBasePath: `${ELASTIC_WEBSITE_URL}guide/en/elasticsearch/reference/${DOC_LINK_VERSION}/`, esPluginDocBasePath: `${ELASTIC_WEBSITE_URL}guide/en/elasticsearch/plugins/${DOC_LINK_VERSION}/`, esStackOverviewDocBasePath: `${ELASTIC_WEBSITE_URL}guide/en/elastic-stack-overview/${DOC_LINK_VERSION}/`, + esMLDocBasePath: `${ELASTIC_WEBSITE_URL}guide/en/machine-learning/${DOC_LINK_VERSION}/`, }, docTitle: { change: docTitle.change, diff --git a/x-pack/legacy/plugins/triggers_actions_ui/index.ts b/x-pack/legacy/plugins/triggers_actions_ui/index.ts new file mode 100644 index 0000000000000..c6ac3649a1477 --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/index.ts @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Legacy } from 'kibana'; +import { Root } from 'joi'; +import { resolve } from 'path'; + +export function triggersActionsUI(kibana: any) { + return new kibana.Plugin({ + id: 'triggers_actions_ui', + configPrefix: 'xpack.triggers_actions_ui', + isEnabled(config: Legacy.KibanaConfig) { + return ( + config.get('xpack.triggers_actions_ui.enabled') && + (config.get('xpack.actions.enabled') || config.get('xpack.alerting.enabled')) + ); + }, + publicDir: resolve(__dirname, 'public'), + require: ['kibana'], + config(Joi: Root) { + return Joi.object() + .keys({ + enabled: Joi.boolean().default(false), + createAlertUiEnabled: Joi.boolean().default(false), + }) + .default(); + }, + uiExports: { + home: ['plugins/triggers_actions_ui/hacks/register'], + managementSections: ['plugins/triggers_actions_ui/legacy'], + styleSheetPaths: resolve(__dirname, 'public/index.scss'), + injectDefaultVars(server: Legacy.Server) { + const serverConfig = server.config(); + return { + createAlertUiEnabled: serverConfig.get('xpack.triggers_actions_ui.createAlertUiEnabled'), + }; + }, + }, + }); +} diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/kibana.json b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/kibana.json new file mode 100644 index 0000000000000..3fd7389aef494 --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/kibana.json @@ -0,0 +1,6 @@ +{ + "id": "triggers_actions_ui", + "version": "kibana", + "server": false, + "ui": true + } diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/action_type_registry.mock.ts b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/action_type_registry.mock.ts new file mode 100644 index 0000000000000..8ebfd7f933cd3 --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/action_type_registry.mock.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ActionTypeRegistryContract } from '../types'; + +const createActionTypeRegistryMock = () => { + const mocked: jest.Mocked = { + has: jest.fn(x => true), + register: jest.fn(), + get: jest.fn(), + list: jest.fn(), + }; + return mocked; +}; + +export const actionTypeRegistryMock = { + create: createActionTypeRegistryMock, +}; diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/alert_type_registry.mock.ts b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/alert_type_registry.mock.ts new file mode 100644 index 0000000000000..89eca7563a4e1 --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/alert_type_registry.mock.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { AlertTypeRegistryContract } from '../types'; + +const createAlertTypeRegistryMock = () => { + const mocked: jest.Mocked = { + has: jest.fn(), + register: jest.fn(), + get: jest.fn(), + list: jest.fn(), + }; + return mocked; +}; + +export const alertTypeRegistryMock = { + create: createAlertTypeRegistryMock, +}; diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/app.tsx b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/app.tsx new file mode 100644 index 0000000000000..3ad6b5b7c697d --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/app.tsx @@ -0,0 +1,64 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React from 'react'; +import { Switch, Route, Redirect, HashRouter } from 'react-router-dom'; +import { + ChromeStart, + DocLinksStart, + ToastsSetup, + HttpSetup, + IUiSettingsClient, +} from 'kibana/public'; +import { BASE_PATH, Section } from './constants'; +import { TriggersActionsUIHome } from './home'; +import { AppContextProvider, useAppDependencies } from './app_context'; +import { hasShowAlertsCapability } from './lib/capabilities'; +import { LegacyDependencies, ActionTypeModel, AlertTypeModel } from '../types'; +import { TypeRegistry } from './type_registry'; + +export interface AppDeps { + chrome: ChromeStart; + docLinks: DocLinksStart; + toastNotifications: ToastsSetup; + injectedMetadata: any; + http: HttpSetup; + uiSettings: IUiSettingsClient; + legacy: LegacyDependencies; + actionTypeRegistry: TypeRegistry; + alertTypeRegistry: TypeRegistry; +} + +export const App = (appDeps: AppDeps) => { + const sections: Section[] = ['alerts', 'connectors']; + + const sectionsRegex = sections.join('|'); + + return ( + + + + + + ); +}; + +export const AppWithoutRouter = ({ sectionsRegex }: any) => { + const { + legacy: { capabilities }, + } = useAppDependencies(); + const canShowAlerts = hasShowAlertsCapability(capabilities.get()); + const DEFAULT_SECTION: Section = canShowAlerts ? 'alerts' : 'connectors'; + return ( + + + + + ); +}; diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/app_context.tsx b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/app_context.tsx new file mode 100644 index 0000000000000..bf2e0c7274e7b --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/app_context.tsx @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { createContext, useContext } from 'react'; +import { AppDeps } from './app'; + +const AppContext = createContext(null); + +export const AppContextProvider = ({ + children, + appDeps, +}: { + appDeps: AppDeps | null; + children: React.ReactNode; +}) => { + return appDeps ? {children} : null; +}; + +export const useAppDependencies = (): AppDeps => { + const ctx = useContext(AppContext); + if (!ctx) { + throw new Error( + 'The app dependencies Context has not been set. Use the "setAppDependencies()" method when bootstrapping the app.' + ); + } + return ctx; +}; diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/boot.tsx b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/boot.tsx new file mode 100644 index 0000000000000..a37bedbfbdda8 --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/boot.tsx @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { render, unmountComponentAtNode } from 'react-dom'; +import { SavedObjectsClientContract } from 'src/core/public'; + +import { App, AppDeps } from './app'; +import { setSavedObjectsClient } from '../application/components/builtin_alert_types/threshold/lib/api'; +import { LegacyDependencies } from '../types'; + +interface BootDeps extends AppDeps { + element: HTMLElement; + savedObjects: SavedObjectsClientContract; + I18nContext: any; + legacy: LegacyDependencies; +} + +export const boot = (bootDeps: BootDeps) => { + const { I18nContext, element, legacy, savedObjects, ...appDeps } = bootDeps; + + setSavedObjectsClient(savedObjects); + + render( + + + , + element + ); + return () => unmountComponentAtNode(element); +}; diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/email.test.tsx b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/email.test.tsx new file mode 100644 index 0000000000000..5c924982c3536 --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/email.test.tsx @@ -0,0 +1,228 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React from 'react'; +import { mountWithIntl } from 'test_utils/enzyme_helpers'; +import { TypeRegistry } from '../../type_registry'; +import { registerBuiltInActionTypes } from './index'; +import { ActionTypeModel, ActionConnector } from '../../../types'; + +const ACTION_TYPE_ID = '.email'; +let actionTypeModel: ActionTypeModel; + +beforeAll(() => { + const actionTypeRegistry = new TypeRegistry(); + registerBuiltInActionTypes({ actionTypeRegistry }); + const getResult = actionTypeRegistry.get(ACTION_TYPE_ID); + if (getResult !== null) { + actionTypeModel = getResult; + } +}); + +describe('actionTypeRegistry.get() works', () => { + test('action type static data is as expected', () => { + expect(actionTypeModel.id).toEqual(ACTION_TYPE_ID); + expect(actionTypeModel.iconClass).toEqual('email'); + }); +}); + +describe('connector validation', () => { + test('connector validation succeeds when connector config is valid', () => { + const actionConnector = { + secrets: { + user: 'user', + password: 'pass', + }, + id: 'test', + actionTypeId: '.email', + name: 'email', + config: { + from: 'test@test.com', + port: '2323', + host: 'localhost', + test: 'test', + }, + } as ActionConnector; + + expect(actionTypeModel.validateConnector(actionConnector)).toEqual({ + errors: { + from: [], + service: [], + port: [], + host: [], + user: [], + password: [], + }, + }); + + delete actionConnector.config.test; + actionConnector.config.host = 'elastic.co'; + actionConnector.config.port = 8080; + expect(actionTypeModel.validateConnector(actionConnector)).toEqual({ + errors: { + from: [], + service: [], + port: [], + host: [], + user: [], + password: [], + }, + }); + delete actionConnector.config.host; + delete actionConnector.config.port; + actionConnector.config.service = 'testService'; + expect(actionTypeModel.validateConnector(actionConnector)).toEqual({ + errors: { + from: [], + service: [], + port: [], + host: [], + user: [], + password: [], + }, + }); + }); + + test('connector validation fails when connector config is not valid', () => { + const actionConnector = { + secrets: { + user: 'user', + password: 'pass', + }, + id: 'test', + actionTypeId: '.email', + name: 'email', + config: { + from: 'test@test.com', + }, + } as ActionConnector; + + expect(actionTypeModel.validateConnector(actionConnector)).toEqual({ + errors: { + from: [], + service: ['Service is required.'], + port: ['Port is required.'], + host: ['Host is required.'], + user: [], + password: [], + }, + }); + }); +}); + +describe('action params validation', () => { + test('action params validation succeeds when action params is valid', () => { + const actionParams = { + to: [], + cc: ['test1@test.com'], + message: 'message {test}', + subject: 'test', + }; + + expect(actionTypeModel.validateParams(actionParams)).toEqual({ + errors: { + to: [], + cc: [], + bcc: [], + message: [], + subject: [], + }, + }); + }); + + test('action params validation fails when action params is not valid', () => { + const actionParams = { + to: ['test@test.com'], + subject: 'test', + }; + + expect(actionTypeModel.validateParams(actionParams)).toEqual({ + errors: { + to: [], + cc: [], + bcc: [], + message: ['Message is required.'], + subject: [], + }, + }); + }); +}); + +describe('EmailActionConnectorFields renders', () => { + test('all connector fields is rendered', () => { + expect(actionTypeModel.actionConnectorFields).not.toBeNull(); + if (!actionTypeModel.actionConnectorFields) { + return; + } + const ConnectorFields = actionTypeModel.actionConnectorFields; + const actionConnector = { + secrets: { + user: 'user', + password: 'pass', + }, + id: 'test', + actionTypeId: '.email', + name: 'email', + config: { + from: 'test@test.com', + }, + } as ActionConnector; + const wrapper = mountWithIntl( + {}} + editActionSecrets={() => {}} + hasErrors={false} + /> + ); + expect(wrapper.find('[data-test-subj="emailFromInput"]').length > 0).toBeTruthy(); + expect( + wrapper + .find('[data-test-subj="emailFromInput"]') + .first() + .prop('value') + ).toBe('test@test.com'); + expect(wrapper.find('[data-test-subj="emailHostInput"]').length > 0).toBeTruthy(); + expect(wrapper.find('[data-test-subj="emailPortInput"]').length > 0).toBeTruthy(); + expect(wrapper.find('[data-test-subj="emailUserInput"]').length > 0).toBeTruthy(); + expect(wrapper.find('[data-test-subj="emailPasswordInput"]').length > 0).toBeTruthy(); + }); +}); + +describe('EmailParamsFields renders', () => { + test('all params fields is rendered', () => { + expect(actionTypeModel.actionParamsFields).not.toBeNull(); + if (!actionTypeModel.actionParamsFields) { + return; + } + const ParamsFields = actionTypeModel.actionParamsFields; + const actionParams = { + to: ['test@test.com'], + subject: 'test', + message: 'test message', + }; + const wrapper = mountWithIntl( + {}} + index={0} + hasErrors={false} + /> + ); + expect(wrapper.find('[data-test-subj="toEmailAddressInput"]').length > 0).toBeTruthy(); + expect( + wrapper + .find('[data-test-subj="toEmailAddressInput"]') + .first() + .prop('selectedOptions') + ).toStrictEqual([{ label: 'test@test.com' }]); + expect(wrapper.find('[data-test-subj="ccEmailAddressInput"]').length > 0).toBeTruthy(); + expect(wrapper.find('[data-test-subj="bccEmailAddressInput"]').length > 0).toBeTruthy(); + expect(wrapper.find('[data-test-subj="emailSubjectInput"]').length > 0).toBeTruthy(); + expect(wrapper.find('[data-test-subj="emailMessageInput"]').length > 0).toBeTruthy(); + }); +}); diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/email.tsx b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/email.tsx new file mode 100644 index 0000000000000..a6750ccf96deb --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/email.tsx @@ -0,0 +1,545 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React, { Fragment } from 'react'; +import { + EuiFieldText, + EuiFlexItem, + EuiFlexGroup, + EuiFieldNumber, + EuiFieldPassword, + EuiComboBox, + EuiTextArea, + EuiSwitch, + EuiFormRow, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { + ActionTypeModel, + ActionConnectorFieldsProps, + ActionConnector, + ValidationResult, + ActionParamsProps, +} from '../../../types'; + +export function getActionType(): ActionTypeModel { + const mailformat = /^[^@\s]+@[^@\s]+$/; + return { + id: '.email', + iconClass: 'email', + selectMessage: i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.emailAction.selectMessageText', + { + defaultMessage: 'Send email from your server.', + } + ), + validateConnector: (action: ActionConnector): ValidationResult => { + const validationResult = { errors: {} }; + const errors = { + from: new Array(), + service: new Array(), + port: new Array(), + host: new Array(), + user: new Array(), + password: new Array(), + }; + validationResult.errors = errors; + if (!action.config.from) { + errors.from.push( + i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.error.requiredFromText', + { + defaultMessage: 'Sender is required.', + } + ) + ); + } + if (action.config.from && !action.config.from.trim().match(mailformat)) { + errors.from.push( + i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.error.formatFromText', + { + defaultMessage: 'Sender is not a valid email address.', + } + ) + ); + } + if (!action.config.port && !action.config.service) { + errors.port.push( + i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.error.requiredPortText', + { + defaultMessage: 'Port is required.', + } + ) + ); + } + if (!action.config.service && (!action.config.port || !action.config.host)) { + errors.service.push( + i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.error.requiredServiceText', + { + defaultMessage: 'Service is required.', + } + ) + ); + } + if (!action.config.host && !action.config.service) { + errors.host.push( + i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.error.requiredHostText', + { + defaultMessage: 'Host is required.', + } + ) + ); + } + if (!action.secrets.user) { + errors.user.push( + i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.error.requiredUserText', + { + defaultMessage: 'Username is required.', + } + ) + ); + } + if (!action.secrets.password) { + errors.password.push( + i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.error.requiredPasswordText', + { + defaultMessage: 'Password is required.', + } + ) + ); + } + return validationResult; + }, + validateParams: (actionParams: any): ValidationResult => { + const validationResult = { errors: {} }; + const errors = { + to: new Array(), + cc: new Array(), + bcc: new Array(), + message: new Array(), + subject: new Array(), + }; + validationResult.errors = errors; + if ( + (!(actionParams.to instanceof Array) || actionParams.to.length === 0) && + (!(actionParams.cc instanceof Array) || actionParams.cc.length === 0) && + (!(actionParams.bcc instanceof Array) || actionParams.bcc.length === 0) + ) { + const errorText = i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.error.requiredEntryText', + { + defaultMessage: 'No [to], [cc], or [bcc] entries. At least one entry is required.', + } + ); + errors.to.push(errorText); + errors.cc.push(errorText); + errors.bcc.push(errorText); + } + if (!actionParams.message) { + errors.message.push( + i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.error.requiredMessageText', + { + defaultMessage: 'Message is required.', + } + ) + ); + } + if (!actionParams.subject) { + errors.subject.push( + i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.error.requiredSubjectText', + { + defaultMessage: 'Subject is required.', + } + ) + ); + } + return validationResult; + }, + actionConnectorFields: EmailActionConnectorFields, + actionParamsFields: EmailParamsFields, + }; +} + +const EmailActionConnectorFields: React.FunctionComponent = ({ + action, + editActionConfig, + editActionSecrets, + errors, +}) => { + const { from, host, port, secure } = action.config; + const { user, password } = action.secrets; + + return ( + + + + 0 && from !== undefined} + label={i18n.translate( + 'xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.fromTextFieldLabel', + { + defaultMessage: 'Sender', + } + )} + > + 0 && from !== undefined} + name="from" + value={from || ''} + data-test-subj="emailFromInput" + onChange={e => { + editActionConfig('from', e.target.value); + }} + onBlur={() => { + if (!from) { + editActionConfig('from', ''); + } + }} + /> + + + + + + 0 && host !== undefined} + label={i18n.translate( + 'xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.hostTextFieldLabel', + { + defaultMessage: 'Host', + } + )} + > + 0 && host !== undefined} + name="host" + value={host || ''} + data-test-subj="emailHostInput" + onChange={e => { + editActionConfig('host', e.target.value); + }} + onBlur={() => { + if (!host) { + editActionConfig('host', ''); + } + }} + /> + + + + + + 0 && port !== undefined} + label={i18n.translate( + 'xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.portTextFieldLabel', + { + defaultMessage: 'Port', + } + )} + > + 0 && port !== undefined} + fullWidth + name="port" + value={port || ''} + data-test-subj="emailPortInput" + onChange={e => { + editActionConfig('port', parseInt(e.target.value, 10)); + }} + onBlur={() => { + if (!port) { + editActionConfig('port', ''); + } + }} + /> + + + + + + { + editActionConfig('secure', e.target.checked); + }} + /> + + + + + + + + + 0 && user !== undefined} + label={i18n.translate( + 'xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.userTextFieldLabel', + { + defaultMessage: 'Username', + } + )} + > + 0 && user !== undefined} + name="user" + value={user || ''} + data-test-subj="emailUserInput" + onChange={e => { + editActionSecrets('user', e.target.value); + }} + onBlur={() => { + if (!user) { + editActionSecrets('user', ''); + } + }} + /> + + + + 0 && password !== undefined} + label={i18n.translate( + 'xpack.triggersActionsUI.sections.builtinActionTypes.emailAction.passwordFieldLabel', + { + defaultMessage: 'Password', + } + )} + > + 0 && password !== undefined} + name="password" + value={password || ''} + data-test-subj="emailPasswordInput" + onChange={e => { + editActionSecrets('password', e.target.value); + }} + onBlur={() => { + if (!password) { + editActionSecrets('password', ''); + } + }} + /> + + + + + ); +}; + +const EmailParamsFields: React.FunctionComponent = ({ + action, + editAction, + index, + errors, + hasErrors, +}) => { + const { to, cc, bcc, subject, message } = action; + const toOptions = to ? to.map((label: string) => ({ label })) : []; + const ccOptions = cc ? cc.map((label: string) => ({ label })) : []; + const bccOptions = bcc ? bcc.map((label: string) => ({ label })) : []; + + return ( + + + { + const newOptions = [...toOptions, { label: searchValue }]; + editAction( + 'to', + newOptions.map(newOption => newOption.label), + index + ); + }} + onChange={(selectedOptions: Array<{ label: string }>) => { + editAction( + 'to', + selectedOptions.map(selectedOption => selectedOption.label), + index + ); + }} + onBlur={() => { + if (!to) { + editAction('to', [], index); + } + }} + /> + + + { + const newOptions = [...ccOptions, { label: searchValue }]; + editAction( + 'cc', + newOptions.map(newOption => newOption.label), + index + ); + }} + onChange={(selectedOptions: Array<{ label: string }>) => { + editAction( + 'cc', + selectedOptions.map(selectedOption => selectedOption.label), + index + ); + }} + onBlur={() => { + if (!cc) { + editAction('cc', [], index); + } + }} + /> + + + { + const newOptions = [...bccOptions, { label: searchValue }]; + editAction( + 'bcc', + newOptions.map(newOption => newOption.label), + index + ); + }} + onChange={(selectedOptions: Array<{ label: string }>) => { + editAction( + 'bcc', + selectedOptions.map(selectedOption => selectedOption.label), + index + ); + }} + onBlur={() => { + if (!bcc) { + editAction('bcc', [], index); + } + }} + /> + + + { + editAction('subject', e.target.value, index); + }} + /> + + + { + editAction('message', e.target.value, index); + }} + /> + + + ); +}; diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/es_index.test.tsx b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/es_index.test.tsx new file mode 100644 index 0000000000000..b6a7c4d82aca4 --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/es_index.test.tsx @@ -0,0 +1,140 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React from 'react'; +import { mountWithIntl } from 'test_utils/enzyme_helpers'; +import { TypeRegistry } from '../../type_registry'; +import { registerBuiltInActionTypes } from './index'; +import { ActionTypeModel, ActionConnector } from '../../../types'; + +const ACTION_TYPE_ID = '.index'; +let actionTypeModel: ActionTypeModel; + +beforeAll(() => { + const actionTypeRegistry = new TypeRegistry(); + registerBuiltInActionTypes({ actionTypeRegistry }); + const getResult = actionTypeRegistry.get(ACTION_TYPE_ID); + if (getResult !== null) { + actionTypeModel = getResult; + } +}); + +describe('actionTypeRegistry.get() works', () => { + test('action type .index is registered', () => { + expect(actionTypeModel.id).toEqual(ACTION_TYPE_ID); + expect(actionTypeModel.iconClass).toEqual('indexOpen'); + }); +}); + +describe('index connector validation', () => { + test('connector validation succeeds when connector config is valid', () => { + const actionConnector = { + secrets: {}, + id: 'test', + actionTypeId: '.index', + name: 'es_index', + config: { + index: 'test_es_index', + }, + } as ActionConnector; + + expect(actionTypeModel.validateConnector(actionConnector)).toEqual({ + errors: {}, + }); + + delete actionConnector.config.index; + expect(actionTypeModel.validateConnector(actionConnector)).toEqual({ + errors: {}, + }); + }); +}); + +describe('action params validation', () => { + test('action params validation succeeds when action params is valid', () => { + const actionParams = { + index: 'test', + refresh: false, + executionTimeField: '1', + documents: ['test'], + }; + + expect(actionTypeModel.validateParams(actionParams)).toEqual({ + errors: {}, + }); + + const emptyActionParams = {}; + + expect(actionTypeModel.validateParams(emptyActionParams)).toEqual({ + errors: {}, + }); + }); +}); + +describe('IndexActionConnectorFields renders', () => { + test('all connector fields is rendered', () => { + expect(actionTypeModel.actionConnectorFields).not.toBeNull(); + if (!actionTypeModel.actionConnectorFields) { + return; + } + const ConnectorFields = actionTypeModel.actionConnectorFields; + const actionConnector = { + secrets: {}, + id: 'test', + actionTypeId: '.index', + name: 'es_index', + config: { + index: 'test', + }, + } as ActionConnector; + const wrapper = mountWithIntl( + {}} + editActionSecrets={() => {}} + hasErrors={false} + /> + ); + expect(wrapper.find('[data-test-subj="indexInput"]').length > 0).toBeTruthy(); + expect( + wrapper + .find('[data-test-subj="indexInput"]') + .first() + .prop('value') + ).toBe('test'); + }); +}); + +describe('IndexParamsFields renders', () => { + test('all params fields is rendered', () => { + expect(actionTypeModel.actionParamsFields).not.toBeNull(); + if (!actionTypeModel.actionParamsFields) { + return; + } + const ParamsFields = actionTypeModel.actionParamsFields; + const actionParams = { + index: 'test_index', + refresh: false, + documents: ['test'], + }; + const wrapper = mountWithIntl( + {}} + index={0} + hasErrors={false} + /> + ); + expect(wrapper.find('[data-test-subj="indexInput"]').length > 0).toBeTruthy(); + expect( + wrapper + .find('[data-test-subj="indexInput"]') + .first() + .prop('value') + ).toBe('test_index'); + expect(wrapper.find('[data-test-subj="indexRefreshCheckbox"]').length > 0).toBeTruthy(); + }); +}); diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/es_index.tsx b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/es_index.tsx new file mode 100644 index 0000000000000..aa15195cdc286 --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/es_index.tsx @@ -0,0 +1,124 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React, { Fragment } from 'react'; +import { EuiFieldText, EuiFormRow, EuiSwitch } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; +import { + ActionTypeModel, + ActionConnectorFieldsProps, + ValidationResult, + ActionParamsProps, +} from '../../../types'; + +export function getActionType(): ActionTypeModel { + return { + id: '.index', + iconClass: 'indexOpen', + selectMessage: i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.indexAction.selectMessageText', + { + defaultMessage: 'Index data into Elasticsearch.', + } + ), + validateConnector: (): ValidationResult => { + return { errors: {} }; + }, + actionConnectorFields: IndexActionConnectorFields, + actionParamsFields: IndexParamsFields, + validateParams: (actionParams: any): ValidationResult => { + const validationResult = { errors: {} }; + return validationResult; + }, + }; +} + +const IndexActionConnectorFields: React.FunctionComponent = ({ + action, + editActionConfig, +}) => { + const { index } = action.config; + return ( + + ) => { + editActionConfig('index', e.target.value); + }} + onBlur={() => { + if (!index) { + editActionConfig('index', ''); + } + }} + /> + + ); +}; + +const IndexParamsFields: React.FunctionComponent = ({ + action, + index, + editAction, + errors, + hasErrors, +}) => { + const { refresh } = action; + return ( + + + ) => { + editAction('index', e.target.value, index); + }} + onBlur={() => { + if (!action.index) { + editAction('index', '', index); + } + }} + /> + + { + editAction('refresh', e.target.checked, index); + }} + label={ + + } + /> + + ); +}; diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/index.ts b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/index.ts new file mode 100644 index 0000000000000..6ffd9b2c9ffde --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/index.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { getActionType as getServerLogActionType } from './server_log'; +import { getActionType as getSlackActionType } from './slack'; +import { getActionType as getEmailActionType } from './email'; +import { getActionType as getIndexActionType } from './es_index'; +import { getActionType as getPagerDutyActionType } from './pagerduty'; +import { getActionType as getWebhookActionType } from './webhook'; +import { TypeRegistry } from '../../type_registry'; +import { ActionTypeModel } from '../../../types'; + +export function registerBuiltInActionTypes({ + actionTypeRegistry, +}: { + actionTypeRegistry: TypeRegistry; +}) { + actionTypeRegistry.register(getServerLogActionType()); + actionTypeRegistry.register(getSlackActionType()); + actionTypeRegistry.register(getEmailActionType()); + actionTypeRegistry.register(getIndexActionType()); + actionTypeRegistry.register(getPagerDutyActionType()); + actionTypeRegistry.register(getWebhookActionType()); +} diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/pagerduty.test.tsx b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/pagerduty.test.tsx new file mode 100644 index 0000000000000..582315c95812a --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/pagerduty.test.tsx @@ -0,0 +1,179 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React from 'react'; +import { mountWithIntl } from 'test_utils/enzyme_helpers'; +import { TypeRegistry } from '../../type_registry'; +import { registerBuiltInActionTypes } from './index'; +import { ActionTypeModel, ActionConnector } from '../../../types'; + +const ACTION_TYPE_ID = '.pagerduty'; +let actionTypeModel: ActionTypeModel; + +beforeAll(() => { + const actionTypeRegistry = new TypeRegistry(); + registerBuiltInActionTypes({ actionTypeRegistry }); + const getResult = actionTypeRegistry.get(ACTION_TYPE_ID); + if (getResult !== null) { + actionTypeModel = getResult; + } +}); + +describe('actionTypeRegistry.get() works', () => { + test('action type static data is as expected', () => { + expect(actionTypeModel.id).toEqual(ACTION_TYPE_ID); + expect(actionTypeModel.iconClass).toEqual('apps'); + }); +}); + +describe('pagerduty connector validation', () => { + test('connector validation succeeds when connector config is valid', () => { + const actionConnector = { + secrets: { + routingKey: 'test', + }, + id: 'test', + actionTypeId: '.pagerduty', + name: 'pagerduty', + config: { + apiUrl: 'http:\\test', + }, + } as ActionConnector; + + expect(actionTypeModel.validateConnector(actionConnector)).toEqual({ + errors: { + routingKey: [], + }, + }); + + delete actionConnector.config.apiUrl; + actionConnector.secrets.routingKey = 'test1'; + expect(actionTypeModel.validateConnector(actionConnector)).toEqual({ + errors: { + routingKey: [], + }, + }); + }); + + test('connector validation fails when connector config is not valid', () => { + const actionConnector = { + secrets: {}, + id: 'test', + actionTypeId: '.pagerduty', + name: 'pagerduty', + config: { + apiUrl: 'http:\\test', + }, + } as ActionConnector; + + expect(actionTypeModel.validateConnector(actionConnector)).toEqual({ + errors: { + routingKey: ['A routing key is required.'], + }, + }); + }); +}); + +describe('pagerduty action params validation', () => { + test('action params validation succeeds when action params is valid', () => { + const actionParams = { + eventAction: 'trigger', + dedupKey: 'test', + summary: '2323', + source: 'source', + severity: 'critical', + timestamp: '234654564654', + component: 'test', + group: 'group', + class: 'test class', + }; + + expect(actionTypeModel.validateParams(actionParams)).toEqual({ + errors: {}, + }); + }); +}); + +describe('PagerDutyActionConnectorFields renders', () => { + test('all connector fields is rendered', () => { + expect(actionTypeModel.actionConnectorFields).not.toBeNull(); + if (!actionTypeModel.actionConnectorFields) { + return; + } + const ConnectorFields = actionTypeModel.actionConnectorFields; + const actionConnector = { + secrets: { + routingKey: 'test', + }, + id: 'test', + actionTypeId: '.pagerduty', + name: 'pagerduty', + config: { + apiUrl: 'http:\\test', + }, + } as ActionConnector; + const wrapper = mountWithIntl( + {}} + editActionSecrets={() => {}} + hasErrors={false} + /> + ); + expect(wrapper.find('[data-test-subj="pagerdutyApiUrlInput"]').length > 0).toBeTruthy(); + expect( + wrapper + .find('[data-test-subj="pagerdutyApiUrlInput"]') + .first() + .prop('value') + ).toBe('http:\\test'); + expect(wrapper.find('[data-test-subj="pagerdutyRoutingKeyInput"]').length > 0).toBeTruthy(); + }); +}); + +describe('PagerDutyParamsFields renders', () => { + test('all params fields is rendered', () => { + expect(actionTypeModel.actionParamsFields).not.toBeNull(); + if (!actionTypeModel.actionParamsFields) { + return; + } + const ParamsFields = actionTypeModel.actionParamsFields; + const actionParams = { + eventAction: 'trigger', + dedupKey: 'test', + summary: '2323', + source: 'source', + severity: 'critical', + timestamp: '234654564654', + component: 'test', + group: 'group', + class: 'test class', + }; + const wrapper = mountWithIntl( + {}} + index={0} + hasErrors={false} + /> + ); + expect(wrapper.find('[data-test-subj="severitySelect"]').length > 0).toBeTruthy(); + expect( + wrapper + .find('[data-test-subj="severitySelect"]') + .first() + .prop('value') + ).toStrictEqual('critical'); + expect(wrapper.find('[data-test-subj="eventActionSelect"]').length > 0).toBeTruthy(); + expect(wrapper.find('[data-test-subj="dedupKeyInput"]').length > 0).toBeTruthy(); + expect(wrapper.find('[data-test-subj="timestampInput"]').length > 0).toBeTruthy(); + expect(wrapper.find('[data-test-subj="componentInput"]').length > 0).toBeTruthy(); + expect(wrapper.find('[data-test-subj="groupInput"]').length > 0).toBeTruthy(); + expect(wrapper.find('[data-test-subj="sourceInput"]').length > 0).toBeTruthy(); + expect(wrapper.find('[data-test-subj="pagerdutyDescriptionInput"]').length > 0).toBeTruthy(); + }); +}); diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/pagerduty.tsx b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/pagerduty.tsx new file mode 100644 index 0000000000000..69c7ec166df60 --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/pagerduty.tsx @@ -0,0 +1,361 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React, { Fragment } from 'react'; +import { + EuiFieldText, + EuiFlexGroup, + EuiFlexItem, + EuiFormRow, + EuiSelect, + EuiLink, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { + ActionTypeModel, + ActionConnectorFieldsProps, + ActionConnector, + ValidationResult, + ActionParamsProps, +} from '../../../types'; + +export function getActionType(): ActionTypeModel { + return { + id: '.pagerduty', + iconClass: 'apps', + selectMessage: i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.selectMessageText', + { + defaultMessage: 'Send an event in PagerDuty.', + } + ), + validateConnector: (action: ActionConnector): ValidationResult => { + const validationResult = { errors: {} }; + const errors = { + routingKey: new Array(), + }; + validationResult.errors = errors; + if (!action.secrets.routingKey) { + errors.routingKey.push( + i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.error.requiredRoutingKeyText', + { + defaultMessage: 'A routing key is required.', + } + ) + ); + } + return validationResult; + }, + validateParams: (actionParams: any): ValidationResult => { + const validationResult = { errors: {} }; + return validationResult; + }, + actionConnectorFields: PagerDutyActionConnectorFields, + actionParamsFields: PagerDutyParamsFields, + }; +} + +const PagerDutyActionConnectorFields: React.FunctionComponent = ({ + errors, + action, + editActionConfig, + editActionSecrets, +}) => { + const { apiUrl } = action.config; + const { routingKey } = action.secrets; + return ( + + + ) => { + editActionConfig('apiUrl', e.target.value); + }} + onBlur={() => { + if (!apiUrl) { + editActionConfig('apiUrl', ''); + } + }} + /> + + + + + } + error={errors.routingKey} + isInvalid={errors.routingKey.length > 0 && routingKey !== undefined} + label={i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.pagerDutyAction.routingKeyTextFieldLabel', + { + defaultMessage: 'Routing key', + } + )} + > + 0 && routingKey !== undefined} + name="routingKey" + value={routingKey || ''} + data-test-subj="pagerdutyRoutingKeyInput" + onChange={(e: React.ChangeEvent) => { + editActionSecrets('routingKey', e.target.value); + }} + onBlur={() => { + if (!routingKey) { + editActionSecrets('routingKey', ''); + } + }} + /> + + + ); +}; + +const PagerDutyParamsFields: React.FunctionComponent = ({ + action, + editAction, + index, + errors, + hasErrors, +}) => { + const { eventAction, dedupKey, summary, source, severity, timestamp, component, group } = action; + const severityOptions = [ + { value: 'critical', text: 'Critical' }, + { value: 'info', text: 'Info' }, + { value: 'warning', text: 'Warning' }, + { value: 'error', text: 'Error' }, + ]; + const eventActionOptions = [ + { value: 'trigger', text: 'Trigger' }, + { value: 'resolve', text: 'Resolve' }, + { value: 'acknowledge', text: 'Acknowledge' }, + ]; + return ( + + + + + { + editAction('severity', e.target.value, index); + }} + /> + + + + + { + editAction('eventAction', e.target.value, index); + }} + /> + + + + + + + ) => { + editAction('dedupKey', e.target.value, index); + }} + onBlur={() => { + if (!index) { + editAction('dedupKey', '', index); + } + }} + /> + + + + + ) => { + editAction('timestamp', e.target.value, index); + }} + onBlur={() => { + if (!index) { + editAction('timestamp', '', index); + } + }} + /> + + + + + ) => { + editAction('component', e.target.value, index); + }} + onBlur={() => { + if (!index) { + editAction('component', '', index); + } + }} + /> + + + ) => { + editAction('group', e.target.value, index); + }} + onBlur={() => { + if (!index) { + editAction('group', '', index); + } + }} + /> + + + ) => { + editAction('source', e.target.value, index); + }} + onBlur={() => { + if (!index) { + editAction('source', '', index); + } + }} + /> + + + ) => { + editAction('summary', e.target.value, index); + }} + onBlur={() => { + if (!summary) { + editAction('summary', '', index); + } + }} + /> + + + ); +}; diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/server_log.test.tsx b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/server_log.test.tsx new file mode 100644 index 0000000000000..b79be4eef523b --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/server_log.test.tsx @@ -0,0 +1,130 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React from 'react'; +import { mountWithIntl } from 'test_utils/enzyme_helpers'; +import { TypeRegistry } from '../../type_registry'; +import { registerBuiltInActionTypes } from './index'; +import { ActionTypeModel, ActionConnector } from '../../../types'; + +const ACTION_TYPE_ID = '.server-log'; +let actionTypeModel: ActionTypeModel; + +beforeAll(() => { + const actionTypeRegistry = new TypeRegistry(); + registerBuiltInActionTypes({ actionTypeRegistry }); + const getResult = actionTypeRegistry.get(ACTION_TYPE_ID); + if (getResult !== null) { + actionTypeModel = getResult; + } +}); + +describe('actionTypeRegistry.get() works', () => { + test('action type static data is as expected', () => { + expect(actionTypeModel.id).toEqual(ACTION_TYPE_ID); + expect(actionTypeModel.iconClass).toEqual('logsApp'); + }); +}); + +describe('server-log connector validation', () => { + test('connector validation succeeds when connector config is valid', () => { + const actionConnector = { + secrets: {}, + id: 'test', + actionTypeId: '.server-log', + name: 'server-log', + config: {}, + } as ActionConnector; + + expect(actionTypeModel.validateConnector(actionConnector)).toEqual({ + errors: {}, + }); + }); +}); + +describe('action params validation', () => { + test('action params validation succeeds when action params is valid', () => { + const actionParams = { + message: 'test message', + level: 'trace', + }; + + expect(actionTypeModel.validateParams(actionParams)).toEqual({ + errors: { message: [] }, + }); + }); +}); + +describe('ServerLogParamsFields renders', () => { + test('all params fields is rendered', () => { + expect(actionTypeModel.actionParamsFields).not.toBeNull(); + if (!actionTypeModel.actionParamsFields) { + return; + } + const ParamsFields = actionTypeModel.actionParamsFields; + const actionParams = { + message: 'test message', + level: 'trace', + }; + const wrapper = mountWithIntl( + {}} + index={0} + hasErrors={false} + /> + ); + expect(wrapper.find('[data-test-subj="loggingLevelSelect"]').length > 0).toBeTruthy(); + expect( + wrapper + .find('[data-test-subj="loggingLevelSelect"]') + .first() + .prop('value') + ).toStrictEqual('trace'); + expect(wrapper.find('[data-test-subj="loggingMessageInput"]').length > 0).toBeTruthy(); + }); + + test('level param field is rendered with default value if not selected', () => { + expect(actionTypeModel.actionParamsFields).not.toBeNull(); + if (!actionTypeModel.actionParamsFields) { + return; + } + const ParamsFields = actionTypeModel.actionParamsFields; + const actionParams = { + message: 'test message', + level: 'info', + }; + const wrapper = mountWithIntl( + {}} + index={0} + hasErrors={false} + /> + ); + expect(wrapper.find('[data-test-subj="loggingLevelSelect"]').length > 0).toBeTruthy(); + expect( + wrapper + .find('[data-test-subj="loggingLevelSelect"]') + .first() + .prop('value') + ).toStrictEqual('info'); + expect(wrapper.find('[data-test-subj="loggingMessageInput"]').length > 0).toBeTruthy(); + }); + + test('params validation fails when message is not valid', () => { + const actionParams = { + message: '', + }; + + expect(actionTypeModel.validateParams(actionParams)).toEqual({ + errors: { + message: ['Message is required.'], + }, + }); + }); +}); diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/server_log.tsx b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/server_log.tsx new file mode 100644 index 0000000000000..885061aa81924 --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/server_log.tsx @@ -0,0 +1,116 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React, { Fragment } from 'react'; +import { i18n } from '@kbn/i18n'; +import { EuiSelect, EuiTextArea, EuiFormRow } from '@elastic/eui'; +import { ActionTypeModel, ValidationResult, ActionParamsProps } from '../../../types'; + +export function getActionType(): ActionTypeModel { + return { + id: '.server-log', + iconClass: 'logsApp', + selectMessage: i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.serverLogAction.selectMessageText', + { + defaultMessage: 'Add a message to a Kibana log.', + } + ), + validateConnector: (): ValidationResult => { + return { errors: {} }; + }, + validateParams: (actionParams: any): ValidationResult => { + const validationResult = { errors: {} }; + const errors = { + message: new Array(), + }; + validationResult.errors = errors; + if (!actionParams.message || actionParams.message.length === 0) { + errors.message.push( + i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.error.requiredServerLogMessageText', + { + defaultMessage: 'Message is required.', + } + ) + ); + } + return validationResult; + }, + actionConnectorFields: null, + actionParamsFields: ServerLogParamsFields, + }; +} + +export const ServerLogParamsFields: React.FunctionComponent = ({ + action, + editAction, + index, + errors, + hasErrors, +}) => { + const { message, level } = action; + const levelOptions = [ + { value: 'trace', text: 'Trace' }, + { value: 'debug', text: 'Debug' }, + { value: 'info', text: 'Info' }, + { value: 'warn', text: 'Warning' }, + { value: 'error', text: 'Error' }, + { value: 'fatal', text: 'Fatal' }, + ]; + + // Set default value 'info' for level param + editAction('level', 'info', index); + + return ( + + + { + editAction('level', e.target.value, index); + }} + /> + + + { + editAction('message', e.target.value, index); + }} + /> + + + ); +}; diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/slack.test.tsx b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/slack.test.tsx new file mode 100644 index 0000000000000..36beea4d2f2be --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/slack.test.tsx @@ -0,0 +1,152 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React from 'react'; +import { mountWithIntl } from 'test_utils/enzyme_helpers'; +import { TypeRegistry } from '../../type_registry'; +import { registerBuiltInActionTypes } from './index'; +import { ActionTypeModel, ActionConnector } from '../../../types'; + +const ACTION_TYPE_ID = '.slack'; +let actionTypeModel: ActionTypeModel; + +beforeAll(() => { + const actionTypeRegistry = new TypeRegistry(); + registerBuiltInActionTypes({ actionTypeRegistry }); + const getResult = actionTypeRegistry.get(ACTION_TYPE_ID); + if (getResult !== null) { + actionTypeModel = getResult; + } +}); + +describe('actionTypeRegistry.get() works', () => { + test('action type static data is as expected', () => { + expect(actionTypeModel.id).toEqual(ACTION_TYPE_ID); + expect(actionTypeModel.iconClass).toEqual('logoSlack'); + }); +}); + +describe('slack connector validation', () => { + test('connector validation succeeds when connector config is valid', () => { + const actionConnector = { + secrets: { + webhookUrl: 'http:\\test', + }, + id: 'test', + actionTypeId: '.email', + name: 'email', + config: {}, + } as ActionConnector; + + expect(actionTypeModel.validateConnector(actionConnector)).toEqual({ + errors: { + webhookUrl: [], + }, + }); + }); + + test('connector validation fails when connector config is not valid', () => { + const actionConnector = { + secrets: {}, + id: 'test', + actionTypeId: '.email', + name: 'email', + config: {}, + } as ActionConnector; + + expect(actionTypeModel.validateConnector(actionConnector)).toEqual({ + errors: { + webhookUrl: ['Webhook URL is required.'], + }, + }); + }); +}); + +describe('slack action params validation', () => { + test('if action params validation succeeds when action params is valid', () => { + const actionParams = { + message: 'message {test}', + }; + + expect(actionTypeModel.validateParams(actionParams)).toEqual({ + errors: { message: [] }, + }); + }); +}); + +describe('SlackActionFields renders', () => { + test('all connector fields is rendered', () => { + expect(actionTypeModel.actionConnectorFields).not.toBeNull(); + if (!actionTypeModel.actionConnectorFields) { + return; + } + const ConnectorFields = actionTypeModel.actionConnectorFields; + const actionConnector = { + secrets: { + webhookUrl: 'http:\\test', + }, + id: 'test', + actionTypeId: '.email', + name: 'email', + config: {}, + } as ActionConnector; + const wrapper = mountWithIntl( + {}} + editActionSecrets={() => {}} + /> + ); + expect(wrapper.find('[data-test-subj="slackWebhookUrlInput"]').length > 0).toBeTruthy(); + expect( + wrapper + .find('[data-test-subj="slackWebhookUrlInput"]') + .first() + .prop('value') + ).toBe('http:\\test'); + }); +}); + +describe('SlackParamsFields renders', () => { + test('all params fields is rendered', () => { + expect(actionTypeModel.actionParamsFields).not.toBeNull(); + if (!actionTypeModel.actionParamsFields) { + return; + } + const ParamsFields = actionTypeModel.actionParamsFields; + const actionParams = { + message: 'test message', + }; + const wrapper = mountWithIntl( + {}} + index={0} + hasErrors={false} + /> + ); + expect(wrapper.find('[data-test-subj="slackMessageTextarea"]').length > 0).toBeTruthy(); + expect( + wrapper + .find('[data-test-subj="slackMessageTextarea"]') + .first() + .prop('value') + ).toStrictEqual('test message'); + }); + + test('params validation fails when message is not valid', () => { + const actionParams = { + message: '', + }; + + expect(actionTypeModel.validateParams(actionParams)).toEqual({ + errors: { + message: ['Message is required.'], + }, + }); + }); +}); diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/slack.tsx b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/slack.tsx new file mode 100644 index 0000000000000..0ae51725169bf --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/slack.tsx @@ -0,0 +1,175 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React, { Fragment } from 'react'; +import { + EuiFieldText, + EuiTextArea, + EuiFlexGroup, + EuiFlexItem, + EuiButtonIcon, + EuiFormRow, + EuiLink, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { + ActionTypeModel, + ActionConnectorFieldsProps, + ActionConnector, + ValidationResult, + ActionParamsProps, +} from '../../../types'; + +export function getActionType(): ActionTypeModel { + return { + id: '.slack', + iconClass: 'logoSlack', + selectMessage: i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.slackAction.selectMessageText', + { + defaultMessage: 'Send a message to a Slack channel or user.', + } + ), + validateConnector: (action: ActionConnector): ValidationResult => { + const validationResult = { errors: {} }; + const errors = { + webhookUrl: new Array(), + }; + validationResult.errors = errors; + if (!action.secrets.webhookUrl) { + errors.webhookUrl.push( + i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.slackAction.error.requiredWebhookUrlText', + { + defaultMessage: 'Webhook URL is required.', + } + ) + ); + } + return validationResult; + }, + validateParams: (actionParams: any): ValidationResult => { + const validationResult = { errors: {} }; + const errors = { + message: new Array(), + }; + validationResult.errors = errors; + if (!actionParams.message || actionParams.message.length === 0) { + errors.message.push( + i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.error.requiredSlackMessageText', + { + defaultMessage: 'Message is required.', + } + ) + ); + } + return validationResult; + }, + actionConnectorFields: SlackActionFields, + actionParamsFields: SlackParamsFields, + }; +} + +const SlackActionFields: React.FunctionComponent = ({ + action, + editActionSecrets, + errors, +}) => { + const { webhookUrl } = action.secrets; + + return ( + + + + + } + error={errors.webhookUrl} + isInvalid={errors.webhookUrl.length > 0 && webhookUrl !== undefined} + label={i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.slackAction.webhookUrlTextFieldLabel', + { + defaultMessage: 'Webhook URL', + } + )} + > + 0 && webhookUrl !== undefined} + name="webhookUrl" + placeholder="URL like https://hooks.slack.com/services" + value={webhookUrl || ''} + data-test-subj="slackWebhookUrlInput" + onChange={e => { + editActionSecrets('webhookUrl', e.target.value); + }} + onBlur={() => { + if (!webhookUrl) { + editActionSecrets('webhookUrl', ''); + } + }} + /> + + + ); +}; + +const SlackParamsFields: React.FunctionComponent = ({ + action, + editAction, + index, + errors, + hasErrors, +}) => { + const { message } = action; + + return ( + + + + window.alert('Button clicked')} + iconType="indexOpen" + aria-label="Add variable" + /> + + + + { + editAction('message', e.target.value, index); + }} + /> + + + ); +}; diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/webhook.test.tsx b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/webhook.test.tsx new file mode 100644 index 0000000000000..cd342f2e19969 --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/webhook.test.tsx @@ -0,0 +1,174 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React from 'react'; +import { mountWithIntl } from 'test_utils/enzyme_helpers'; +import { TypeRegistry } from '../../type_registry'; +import { registerBuiltInActionTypes } from './index'; +import { ActionTypeModel, ActionConnector } from '../../../types'; + +const ACTION_TYPE_ID = '.webhook'; +let actionTypeModel: ActionTypeModel; + +beforeAll(() => { + const actionTypeRegistry = new TypeRegistry(); + registerBuiltInActionTypes({ actionTypeRegistry }); + const getResult = actionTypeRegistry.get(ACTION_TYPE_ID); + if (getResult !== null) { + actionTypeModel = getResult; + } +}); + +describe('actionTypeRegistry.get() works', () => { + test('action type static data is as expected', () => { + expect(actionTypeModel.id).toEqual(ACTION_TYPE_ID); + expect(actionTypeModel.iconClass).toEqual('logoWebhook'); + }); +}); + +describe('webhook connector validation', () => { + test('connector validation succeeds when connector config is valid', () => { + const actionConnector = { + secrets: { + user: 'user', + password: 'pass', + }, + id: 'test', + actionTypeId: '.webhook', + name: 'webhook', + config: { + method: 'PUT', + url: 'http:\\test', + headers: ['content-type: text'], + }, + } as ActionConnector; + + expect(actionTypeModel.validateConnector(actionConnector)).toEqual({ + errors: { + url: [], + method: [], + user: [], + password: [], + }, + }); + }); + + test('connector validation fails when connector config is not valid', () => { + const actionConnector = { + secrets: { + user: 'user', + }, + id: 'test', + actionTypeId: '.webhook', + name: 'webhook', + config: { + method: 'PUT', + }, + } as ActionConnector; + + expect(actionTypeModel.validateConnector(actionConnector)).toEqual({ + errors: { + url: ['URL is required.'], + method: [], + user: [], + password: ['Password is required.'], + }, + }); + }); +}); + +describe('webhook action params validation', () => { + test('action params validation succeeds when action params is valid', () => { + const actionParams = { + body: 'message {test}', + }; + + expect(actionTypeModel.validateParams(actionParams)).toEqual({ + errors: { body: [] }, + }); + }); +}); + +describe('WebhookActionConnectorFields renders', () => { + test('all connector fields is rendered', () => { + expect(actionTypeModel.actionConnectorFields).not.toBeNull(); + if (!actionTypeModel.actionConnectorFields) { + return; + } + const ConnectorFields = actionTypeModel.actionConnectorFields; + const actionConnector = { + secrets: { + user: 'user', + password: 'pass', + }, + id: 'test', + actionTypeId: '.webhook', + name: 'webhook', + config: { + method: 'PUT', + url: 'http:\\test', + headers: ['content-type: text'], + }, + } as ActionConnector; + const wrapper = mountWithIntl( + {}} + editActionSecrets={() => {}} + /> + ); + expect(wrapper.find('[data-test-subj="webhookViewHeadersSwitch"]').length > 0).toBeTruthy(); + wrapper + .find('[data-test-subj="webhookViewHeadersSwitch"]') + .first() + .simulate('click'); + expect(wrapper.find('[data-test-subj="webhookMethodSelect"]').length > 0).toBeTruthy(); + expect(wrapper.find('[data-test-subj="webhookUrlText"]').length > 0).toBeTruthy(); + expect(wrapper.find('[data-test-subj="webhookUserInput"]').length > 0).toBeTruthy(); + expect(wrapper.find('[data-test-subj="webhookPasswordInput"]').length > 0).toBeTruthy(); + }); +}); + +describe('WebhookParamsFields renders', () => { + test('all params fields is rendered', () => { + expect(actionTypeModel.actionParamsFields).not.toBeNull(); + if (!actionTypeModel.actionParamsFields) { + return; + } + const ParamsFields = actionTypeModel.actionParamsFields; + const actionParams = { + body: 'test message', + }; + const wrapper = mountWithIntl( + {}} + index={0} + hasErrors={false} + /> + ); + expect(wrapper.find('[data-test-subj="webhookBodyEditor"]').length > 0).toBeTruthy(); + expect( + wrapper + .find('[data-test-subj="webhookBodyEditor"]') + .first() + .prop('value') + ).toStrictEqual('test message'); + }); + + test('params validation fails when body is not valid', () => { + const actionParams = { + body: '', + }; + + expect(actionTypeModel.validateParams(actionParams)).toEqual({ + errors: { + body: ['Body is required.'], + }, + }); + }); +}); diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/webhook.tsx b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/webhook.tsx new file mode 100644 index 0000000000000..70a9a6f3d75b3 --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_action_types/webhook.tsx @@ -0,0 +1,501 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React, { Fragment, useState } from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; + +import { + EuiFieldPassword, + EuiFieldText, + EuiFormRow, + EuiSelect, + EuiFlexGroup, + EuiFlexItem, + EuiSpacer, + EuiButtonIcon, + EuiDescriptionList, + EuiDescriptionListDescription, + EuiDescriptionListTitle, + EuiTitle, + EuiCodeEditor, + EuiSwitch, + EuiButtonEmpty, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { + ActionTypeModel, + ActionConnectorFieldsProps, + ActionConnector, + ValidationResult, + ActionParamsProps, +} from '../../../types'; + +const HTTP_VERBS = ['post', 'put']; + +export function getActionType(): ActionTypeModel { + return { + id: '.webhook', + iconClass: 'logoWebhook', + selectMessage: i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.selectMessageText', + { + defaultMessage: 'Send a request to a web service.', + } + ), + validateConnector: (action: ActionConnector): ValidationResult => { + const validationResult = { errors: {} }; + const errors = { + url: new Array(), + method: new Array(), + user: new Array(), + password: new Array(), + }; + validationResult.errors = errors; + if (!action.config.url) { + errors.url.push( + i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.error.requiredUrlText', + { + defaultMessage: 'URL is required.', + } + ) + ); + } + if (!action.config.method) { + errors.method.push( + i18n.translate( + 'xpack.triggersActionsUI.sections.addAction.webhookAction.error.requiredMethodText', + { + defaultMessage: 'Method is required.', + } + ) + ); + } + if (!action.secrets.user) { + errors.user.push( + i18n.translate( + 'xpack.triggersActionsUI.sections.addAction.webhookAction.error.requiredHostText', + { + defaultMessage: 'Username is required.', + } + ) + ); + } + if (!action.secrets.password) { + errors.password.push( + i18n.translate( + 'xpack.triggersActionsUI.sections.addAction.webhookAction.error.requiredPasswordText', + { + defaultMessage: 'Password is required.', + } + ) + ); + } + return validationResult; + }, + validateParams: (actionParams: any): ValidationResult => { + const validationResult = { errors: {} }; + const errors = { + body: new Array(), + }; + validationResult.errors = errors; + if (!actionParams.body || actionParams.body.length === 0) { + errors.body.push( + i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.error.requiredWebhookBodyText', + { + defaultMessage: 'Body is required.', + } + ) + ); + } + return validationResult; + }, + actionConnectorFields: WebhookActionConnectorFields, + actionParamsFields: WebhookParamsFields, + }; +} + +const WebhookActionConnectorFields: React.FunctionComponent = ({ + action, + editActionConfig, + editActionSecrets, + errors, +}) => { + const [httpHeaderKey, setHttpHeaderKey] = useState(''); + const [httpHeaderValue, setHttpHeaderValue] = useState(''); + const [hasHeaders, setHasHeaders] = useState(false); + + const { user, password } = action.secrets; + const { method, url, headers } = action.config; + + editActionConfig('method', 'post'); // set method to POST by default + + const headerErrors = { + keyHeader: new Array(), + valueHeader: new Array(), + }; + if (!httpHeaderKey && httpHeaderValue) { + headerErrors.keyHeader.push( + i18n.translate( + 'xpack.triggersActionsUI.sections.addAction.webhookAction.error.requiredHeaderKeyText', + { + defaultMessage: 'Header key is required.', + } + ) + ); + } + if (httpHeaderKey && !httpHeaderValue) { + headerErrors.valueHeader.push( + i18n.translate( + 'xpack.triggersActionsUI.sections.addAction.webhookAction.error.requiredHeaderValueText', + { + defaultMessage: 'Header value is required.', + } + ) + ); + } + const hasHeaderErrors = headerErrors.keyHeader.length > 0 || headerErrors.valueHeader.length > 0; + + function addHeader() { + if (headers && !!Object.keys(headers).find(key => key === httpHeaderKey)) { + return; + } + const updatedHeaders = headers + ? { ...headers, [httpHeaderKey]: httpHeaderValue } + : { [httpHeaderKey]: httpHeaderValue }; + editActionConfig('headers', updatedHeaders); + setHttpHeaderKey(''); + setHttpHeaderValue(''); + } + + function viewHeaders() { + setHasHeaders(!hasHeaders); + if (!hasHeaders) { + editActionConfig('headers', {}); + } + } + + function removeHeader(keyToRemove: string) { + const updatedHeaders = Object.keys(headers) + .filter(key => key !== keyToRemove) + .reduce((headerToRemove: Record, key: string) => { + headerToRemove[key] = headers[key]; + return headerToRemove; + }, {}); + editActionConfig('headers', updatedHeaders); + } + + let headerControl; + if (hasHeaders) { + headerControl = ( + + +
+ +
+
+ + + + + { + setHttpHeaderKey(e.target.value); + }} + /> + + + + + { + setHttpHeaderValue(e.target.value); + }} + /> + + + + + addHeader()} + > + + + + + +
+ ); + } + + const headersList = Object.keys(headers || {}).map((key: string) => { + return ( + + + removeHeader(key)} + /> + + + + {key} + {headers[key]} + + + + ); + }); + + return ( + + + + + ({ text: verb.toUpperCase(), value: verb }))} + onChange={e => { + editActionConfig('method', e.target.value); + }} + /> + + + + 0 && url !== undefined} + label={i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.urlTextFieldLabel', + { + defaultMessage: 'URL', + } + )} + > + 0 && url !== undefined} + fullWidth + value={url || ''} + data-test-subj="webhookUrlText" + onChange={e => { + editActionConfig('url', e.target.value); + }} + onBlur={() => { + if (!url) { + editActionConfig('url', ''); + } + }} + /> + + + + + + 0 && user !== undefined} + label={i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.userTextFieldLabel', + { + defaultMessage: 'Username', + } + )} + > + 0 && user !== undefined} + name="user" + value={user || ''} + data-test-subj="webhookUserInput" + onChange={e => { + editActionSecrets('user', e.target.value); + }} + onBlur={() => { + if (!user) { + editActionSecrets('user', ''); + } + }} + /> + + + + 0 && password !== undefined} + label={i18n.translate( + 'xpack.triggersActionsUI.components.builtinActionTypes.webhookAction.passwordTextFieldLabel', + { + defaultMessage: 'Password', + } + )} + > + 0 && password !== undefined} + value={password || ''} + data-test-subj="webhookPasswordInput" + onChange={e => { + editActionSecrets('password', e.target.value); + }} + onBlur={() => { + if (!password) { + editActionSecrets('password', ''); + } + }} + /> + + + + + + viewHeaders()} + /> + + +
+ {hasHeaders && Object.keys(headers || {}).length > 0 ? ( + + + +
+ +
+
+ + {headersList} +
+ ) : null} + + {headerControl} + +
+
+ ); +}; + +const WebhookParamsFields: React.FunctionComponent = ({ + action, + editAction, + index, + errors, + hasErrors, +}) => { + const { body } = action; + + return ( + + + { + editAction('body', json, index); + }} + /> + + + ); +}; diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_alert_types/index.ts b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_alert_types/index.ts new file mode 100644 index 0000000000000..6c5d440e47888 --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_alert_types/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { getActionType as getThresholdAlertType } from './threshold/expression'; +import { TypeRegistry } from '../../type_registry'; +import { AlertTypeModel } from '../../../types'; + +export function registerBuiltInAlertTypes({ + alertTypeRegistry, +}: { + alertTypeRegistry: TypeRegistry; +}) { + alertTypeRegistry.register(getThresholdAlertType()); +} diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_alert_types/threshold/constants/aggregation_types.ts b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_alert_types/threshold/constants/aggregation_types.ts new file mode 100644 index 0000000000000..68c2818502b2c --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_alert_types/threshold/constants/aggregation_types.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const AGGREGATION_TYPES: { [key: string]: string } = { + COUNT: 'count', + + AVERAGE: 'avg', + + SUM: 'sum', + + MIN: 'min', + + MAX: 'max', +}; diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_alert_types/threshold/constants/comparators.ts b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_alert_types/threshold/constants/comparators.ts new file mode 100644 index 0000000000000..21b350c0f8ce4 --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_alert_types/threshold/constants/comparators.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const COMPARATORS: { [key: string]: string } = { + GREATER_THAN: '>', + GREATER_THAN_OR_EQUALS: '>=', + BETWEEN: 'between', + LESS_THAN: '<', + LESS_THAN_OR_EQUALS: '<=', +}; diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_alert_types/threshold/constants/index.ts b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_alert_types/threshold/constants/index.ts new file mode 100644 index 0000000000000..f88ee5ee23f90 --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_alert_types/threshold/constants/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { COMPARATORS } from './comparators'; +export { AGGREGATION_TYPES } from './aggregation_types'; diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_alert_types/threshold/expression.tsx b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_alert_types/threshold/expression.tsx new file mode 100644 index 0000000000000..907a61677b263 --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_alert_types/threshold/expression.tsx @@ -0,0 +1,1000 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useState, Fragment, useEffect } from 'react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { + EuiFlexItem, + EuiFlexGroup, + EuiExpression, + EuiPopover, + EuiPopoverTitle, + EuiSelect, + EuiSpacer, + EuiComboBox, + EuiFieldNumber, + EuiComboBoxOptionProps, + EuiText, + EuiFormRow, + EuiCallOut, +} from '@elastic/eui'; +import { AlertTypeModel, Alert, ValidationResult } from '../../../../types'; +import { Comparator, AggregationType, GroupByType } from './types'; +import { AGGREGATION_TYPES, COMPARATORS } from './constants'; +import { + getMatchingIndicesForThresholdAlertType, + getThresholdAlertTypeFields, + loadIndexPatterns, +} from './lib/api'; +import { useAppDependencies } from '../../../app_context'; +import { getTimeOptions, getTimeFieldOptions } from '../../../lib/get_time_options'; +import { getTimeUnitLabel } from '../../../lib/get_time_unit_label'; +import { ThresholdVisualization } from './visualization'; + +const DEFAULT_VALUES = { + AGGREGATION_TYPE: 'count', + TERM_SIZE: 5, + THRESHOLD_COMPARATOR: COMPARATORS.GREATER_THAN, + TIME_WINDOW_SIZE: 5, + TIME_WINDOW_UNIT: 'm', + TRIGGER_INTERVAL_SIZE: 1, + TRIGGER_INTERVAL_UNIT: 'm', + THRESHOLD: [1000, 5000], + GROUP_BY: 'all', +}; + +const expressionFieldsWithValidation = [ + 'index', + 'timeField', + 'aggField', + 'termSize', + 'termField', + 'threshold0', + 'threshold1', + 'timeWindowSize', +]; + +const validateAlertType = (alert: Alert): ValidationResult => { + const { + index, + timeField, + aggType, + aggField, + groupBy, + termSize, + termField, + threshold, + timeWindowSize, + } = alert.params; + const validationResult = { errors: {} }; + const errors = { + aggField: new Array(), + termSize: new Array(), + termField: new Array(), + timeWindowSize: new Array(), + threshold0: new Array(), + threshold1: new Array(), + index: new Array(), + timeField: new Array(), + }; + validationResult.errors = errors; + if (!index) { + errors.index.push( + i18n.translate('xpack.triggersActionsUI.sections.addAlert.error.requiredIndexText', { + defaultMessage: 'Index is required.', + }) + ); + } + if (!timeField) { + errors.timeField.push( + i18n.translate('xpack.triggersActionsUI.sections.addAlert.error.requiredTimeFieldText', { + defaultMessage: 'Time field is required.', + }) + ); + } + if (aggType && aggregationTypes[aggType].fieldRequired && !aggField) { + errors.aggField.push( + i18n.translate('xpack.triggersActionsUI.sections.addAlert.error.requiredAggFieldText', { + defaultMessage: 'Aggregation field is required.', + }) + ); + } + if (!termSize) { + errors.termSize.push( + i18n.translate('xpack.triggersActionsUI.sections.addAlert.error.requiredTermSizedText', { + defaultMessage: 'Term size is required.', + }) + ); + } + if (groupBy && groupByTypes[groupBy].sizeRequired && !termField) { + errors.termField.push( + i18n.translate('xpack.triggersActionsUI.sections.addAlert.error.requiredtTermFieldText', { + defaultMessage: 'Term field is required.', + }) + ); + } + if (!timeWindowSize) { + errors.timeWindowSize.push( + i18n.translate('xpack.triggersActionsUI.sections.addAlert.error.requiredTimeWindowSizeText', { + defaultMessage: 'Time window size is required.', + }) + ); + } + if (threshold && threshold.length > 0 && !threshold[0]) { + errors.threshold0.push( + i18n.translate('xpack.triggersActionsUI.sections.addAlert.error.requiredThreshold0Text', { + defaultMessage: 'Threshold0, is required.', + }) + ); + } + if (threshold && threshold.length > 1 && !threshold[1]) { + errors.threshold1.push( + i18n.translate('xpack.triggersActionsUI.sections.addAlert.error.requiredThreshold1Text', { + defaultMessage: 'Threshold1 is required.', + }) + ); + } + return validationResult; +}; + +export function getActionType(): AlertTypeModel { + return { + id: 'threshold', + name: 'Index Threshold', + iconClass: 'alert', + alertParamsExpression: IndexThresholdAlertTypeExpression, + validate: validateAlertType, + }; +} + +export const aggregationTypes: { [key: string]: AggregationType } = { + count: { + text: 'count()', + fieldRequired: false, + value: AGGREGATION_TYPES.COUNT, + validNormalizedTypes: [], + }, + avg: { + text: 'average()', + fieldRequired: true, + validNormalizedTypes: ['number'], + value: AGGREGATION_TYPES.AVERAGE, + }, + sum: { + text: 'sum()', + fieldRequired: true, + validNormalizedTypes: ['number'], + value: AGGREGATION_TYPES.SUM, + }, + min: { + text: 'min()', + fieldRequired: true, + validNormalizedTypes: ['number', 'date'], + value: AGGREGATION_TYPES.MIN, + }, + max: { + text: 'max()', + fieldRequired: true, + validNormalizedTypes: ['number', 'date'], + value: AGGREGATION_TYPES.MAX, + }, +}; + +export const comparators: { [key: string]: Comparator } = { + [COMPARATORS.GREATER_THAN]: { + text: i18n.translate( + 'xpack.triggersActionsUI.sections.alertAdd.threshold.comparators.isAboveLabel', + { + defaultMessage: 'Is above', + } + ), + value: COMPARATORS.GREATER_THAN, + requiredValues: 1, + }, + [COMPARATORS.GREATER_THAN_OR_EQUALS]: { + text: i18n.translate( + 'xpack.triggersActionsUI.sections.alertAdd.threshold.comparators.isAboveOrEqualsLabel', + { + defaultMessage: 'Is above or equals', + } + ), + value: COMPARATORS.GREATER_THAN_OR_EQUALS, + requiredValues: 1, + }, + [COMPARATORS.LESS_THAN]: { + text: i18n.translate( + 'xpack.triggersActionsUI.sections.alertAdd.threshold.comparators.isBelowLabel', + { + defaultMessage: 'Is below', + } + ), + value: COMPARATORS.LESS_THAN, + requiredValues: 1, + }, + [COMPARATORS.LESS_THAN_OR_EQUALS]: { + text: i18n.translate( + 'xpack.triggersActionsUI.sections.alertAdd.threshold.comparators.isBelowOrEqualsLabel', + { + defaultMessage: 'Is below or equals', + } + ), + value: COMPARATORS.LESS_THAN_OR_EQUALS, + requiredValues: 1, + }, + [COMPARATORS.BETWEEN]: { + text: i18n.translate( + 'xpack.triggersActionsUI.sections.alertAdd.threshold.comparators.isBetweenLabel', + { + defaultMessage: 'Is between', + } + ), + value: COMPARATORS.BETWEEN, + requiredValues: 2, + }, +}; + +export const groupByTypes: { [key: string]: GroupByType } = { + all: { + text: i18n.translate( + 'xpack.triggersActionsUI.sections.alertAdd.threshold.groupByLabel.allDocumentsLabel', + { + defaultMessage: 'all documents', + } + ), + sizeRequired: false, + value: 'all', + validNormalizedTypes: [], + }, + top: { + text: i18n.translate( + 'xpack.triggersActionsUI.sections.alertAdd.threshold.groupByLabel.topLabel', + { + defaultMessage: 'top', + } + ), + sizeRequired: true, + value: 'top', + validNormalizedTypes: ['number', 'date', 'keyword'], + }, +}; + +interface Props { + alert: Alert; + setAlertParams: (property: string, value: any) => void; + setAlertProperty: (key: string, value: any) => void; + errors: { [key: string]: string[] }; + hasErrors?: boolean; +} + +export const IndexThresholdAlertTypeExpression: React.FunctionComponent = ({ + alert, + setAlertParams, + setAlertProperty, + errors, + hasErrors, +}) => { + const { http } = useAppDependencies(); + + const { + index, + timeField, + aggType, + aggField, + groupBy, + termSize, + termField, + thresholdComparator, + threshold, + timeWindowSize, + timeWindowUnit, + } = alert.params; + + const firstFieldOption = { + text: i18n.translate( + 'xpack.triggersActionsUI.sections.alertAdd.threshold.timeFieldOptionLabel', + { + defaultMessage: 'Select a field', + } + ), + value: '', + }; + + const [aggTypePopoverOpen, setAggTypePopoverOpen] = useState(false); + const [indexPopoverOpen, setIndexPopoverOpen] = useState(false); + const [indexPatterns, setIndexPatterns] = useState([]); + const [esFields, setEsFields] = useState>([]); + const [indexOptions, setIndexOptions] = useState([]); + const [timeFieldOptions, setTimeFieldOptions] = useState([firstFieldOption]); + const [isIndiciesLoading, setIsIndiciesLoading] = useState(false); + const [alertThresholdPopoverOpen, setAlertThresholdPopoverOpen] = useState(false); + const [alertDurationPopoverOpen, setAlertDurationPopoverOpen] = useState(false); + const [aggFieldPopoverOpen, setAggFieldPopoverOpen] = useState(false); + const [groupByPopoverOpen, setGroupByPopoverOpen] = useState(false); + + const andThresholdText = i18n.translate( + 'xpack.triggersActionsUI.sections.alertAdd.threshold.andLabel', + { + defaultMessage: 'AND', + } + ); + + const hasExpressionErrors = !!Object.keys(errors).find( + errorKey => expressionFieldsWithValidation.includes(errorKey) && errors[errorKey].length >= 1 + ); + + const getIndexPatterns = async () => { + const indexPatternObjects = await loadIndexPatterns(); + const titles = indexPatternObjects.map((indexPattern: any) => indexPattern.attributes.title); + setIndexPatterns(titles); + }; + + const expressionErrorMessage = i18n.translate( + 'xpack.triggersActionsUI.sections.alertAdd.threshold.fixErrorInExpressionBelowValidationMessage', + { + defaultMessage: 'Expression contains errors.', + } + ); + + const setDefaultExpressionValues = () => { + setAlertProperty('params', { + aggType: DEFAULT_VALUES.AGGREGATION_TYPE, + termSize: DEFAULT_VALUES.TERM_SIZE, + thresholdComparator: DEFAULT_VALUES.THRESHOLD_COMPARATOR, + timeWindowSize: DEFAULT_VALUES.TIME_WINDOW_SIZE, + timeWindowUnit: DEFAULT_VALUES.TIME_WINDOW_UNIT, + triggerIntervalUnit: DEFAULT_VALUES.TRIGGER_INTERVAL_UNIT, + groupBy: DEFAULT_VALUES.GROUP_BY, + threshold: DEFAULT_VALUES.THRESHOLD, + }); + }; + + const getFields = async (indexes: string[]) => { + return await getThresholdAlertTypeFields({ indexes, http }); + }; + + useEffect(() => { + getIndexPatterns(); + }, []); + + useEffect(() => { + setDefaultExpressionValues(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + interface IOption { + label: string; + options: Array<{ value: string; label: string }>; + } + + const getIndexOptions = async (pattern: string, indexPatternsParam: string[]) => { + const options: IOption[] = []; + + if (!pattern) { + return options; + } + + const matchingIndices = (await getMatchingIndicesForThresholdAlertType({ + pattern, + http, + })) as string[]; + const matchingIndexPatterns = indexPatternsParam.filter(anIndexPattern => { + return anIndexPattern.includes(pattern); + }) as string[]; + + if (matchingIndices.length || matchingIndexPatterns.length) { + const matchingOptions = _.uniq([...matchingIndices, ...matchingIndexPatterns]); + + options.push({ + label: i18n.translate( + 'xpack.triggersActionsUI.sections.alertAdd.threshold.indicesAndIndexPatternsLabel', + { + defaultMessage: 'Based on your indices and index patterns', + } + ), + options: matchingOptions.map(match => { + return { + label: match, + value: match, + }; + }), + }); + } + + options.push({ + label: i18n.translate('xpack.triggersActionsUI.sections.alertAdd.threshold.chooseLabel', { + defaultMessage: 'Choose…', + }), + options: [ + { + value: pattern, + label: pattern, + }, + ], + }); + + return options; + }; + + const indexPopover = ( + + + + + + } + isInvalid={hasErrors && index !== undefined} + error={errors.index} + helpText={ + + } + > + { + return { + label: anIndex, + value: anIndex, + }; + })} + onChange={async (selected: EuiComboBoxOptionProps[]) => { + setAlertParams( + 'index', + selected.map(aSelected => aSelected.value) + ); + const indices = selected.map(s => s.value as string); + + // reset time field and expression fields if indices are deleted + if (indices.length === 0) { + setTimeFieldOptions([firstFieldOption]); + setAlertParams('timeFields', []); + + setDefaultExpressionValues(); + return; + } + const currentEsFields = await getFields(indices); + const timeFields = getTimeFieldOptions(currentEsFields as any); + + setEsFields(currentEsFields); + setAlertParams('timeFields', timeFields); + setTimeFieldOptions([firstFieldOption, ...timeFields]); + }} + onSearchChange={async search => { + setIsIndiciesLoading(true); + setIndexOptions(await getIndexOptions(search, indexPatterns)); + setIsIndiciesLoading(false); + }} + onBlur={() => { + if (!index) { + setAlertParams('index', []); + } + }} + /> + + + + + } + isInvalid={hasErrors && timeField !== undefined} + error={errors.timeField} + > + { + setAlertParams('timeField', e.target.value); + }} + onBlur={() => { + if (timeField === undefined) { + setAlertParams('timeField', ''); + } + }} + /> + + + + + + ); + + return ( + + {hasExpressionErrors ? ( + + + + + + ) : null} + + + { + setIndexPopoverOpen(true); + }} + color={index ? 'secondary' : 'danger'} + /> + } + isOpen={indexPopoverOpen} + closePopover={() => { + setIndexPopoverOpen(false); + }} + ownFocus + withTitle + anchorPosition="downLeft" + zIndex={8000} + > +
+ + {i18n.translate( + 'xpack.triggersActionsUI.sections.alertAdd.threshold.indexButtonLabel', + { + defaultMessage: 'index', + } + )} + + {indexPopover} +
+
+
+ + { + setAggTypePopoverOpen(true); + }} + /> + } + isOpen={aggTypePopoverOpen} + closePopover={() => { + setAggTypePopoverOpen(false); + }} + ownFocus + withTitle + anchorPosition="downLeft" + > +
+ + {i18n.translate( + 'xpack.triggersActionsUI.sections.alertAdd.threshold.whenButtonLabel', + { + defaultMessage: 'when', + } + )} + + { + setAlertParams('aggType', e.target.value); + setAggTypePopoverOpen(false); + }} + options={Object.values(aggregationTypes).map(({ text, value }) => { + return { + text, + value, + }; + })} + /> +
+
+
+ {aggType && aggregationTypes[aggType].fieldRequired ? ( + + { + setAggFieldPopoverOpen(true); + }} + color={aggField ? 'secondary' : 'danger'} + /> + } + isOpen={aggFieldPopoverOpen} + closePopover={() => { + setAggFieldPopoverOpen(false); + }} + anchorPosition="downLeft" + > +
+ + {i18n.translate( + 'xpack.triggersActionsUI.sections.alertAdd.threshold.ofButtonLabel', + { + defaultMessage: 'of', + } + )} + + + + + { + if ( + aggregationTypes[aggType].validNormalizedTypes.includes( + field.normalizedType + ) + ) { + esFieldOptions.push({ + label: field.name, + }); + } + return esFieldOptions; + }, [])} + selectedOptions={aggField ? [{ label: aggField }] : []} + onChange={selectedOptions => { + setAlertParams( + 'aggField', + selectedOptions.length === 1 ? selectedOptions[0].label : undefined + ); + setAggFieldPopoverOpen(false); + }} + /> + + + +
+
+
+ ) : null} + + { + setGroupByPopoverOpen(true); + }} + color={groupBy === 'all' || (termSize && termField) ? 'secondary' : 'danger'} + /> + } + isOpen={groupByPopoverOpen} + closePopover={() => { + setGroupByPopoverOpen(false); + }} + ownFocus + withTitle + anchorPosition="downLeft" + > +
+ + {i18n.translate( + 'xpack.triggersActionsUI.sections.alertAdd.threshold.overButtonLabel', + { + defaultMessage: 'over', + } + )} + + + + { + setAlertParams('termSize', null); + setAlertParams('termField', null); + setAlertParams('groupBy', e.target.value); + }} + options={Object.values(groupByTypes).map(({ text, value }) => { + return { + text, + value, + }; + })} + /> + + + {groupByTypes[groupBy || DEFAULT_VALUES.GROUP_BY].sizeRequired ? ( + + + + { + const { value } = e.target; + const termSizeVal = value !== '' ? parseFloat(value) : value; + setAlertParams('termSize', termSizeVal); + }} + min={1} + /> + + + + + { + setAlertParams('termField', e.target.value); + }} + options={esFields.reduce( + (options: any, field: any) => { + if ( + groupByTypes[ + groupBy || DEFAULT_VALUES.GROUP_BY + ].validNormalizedTypes.includes(field.normalizedType) + ) { + options.push({ + text: field.name, + value: field.name, + }); + } + return options; + }, + [firstFieldOption] + )} + /> + + + + ) : null} + +
+
+
+ + { + setAlertThresholdPopoverOpen(true); + }} + color={ + (errors.threshold0 && errors.threshold0.length) || + (errors.threshold1 && errors.threshold1.length) + ? 'danger' + : 'secondary' + } + /> + } + isOpen={alertThresholdPopoverOpen} + closePopover={() => { + setAlertThresholdPopoverOpen(false); + }} + ownFocus + withTitle + anchorPosition="downLeft" + > +
+ + {comparators[thresholdComparator || DEFAULT_VALUES.THRESHOLD_COMPARATOR].text} + + + + { + setAlertParams('thresholdComparator', e.target.value); + }} + options={Object.values(comparators).map(({ text, value }) => { + return { text, value }; + })} + /> + + {Array.from( + Array( + comparators[thresholdComparator || DEFAULT_VALUES.THRESHOLD_COMPARATOR] + .requiredValues + ) + ).map((_notUsed, i) => { + return ( + + {i > 0 ? ( + + {andThresholdText} + {hasErrors && } + + ) : null} + + + { + const { value } = e.target; + const thresholdVal = value !== '' ? parseFloat(value) : value; + const newThreshold = [...threshold]; + newThreshold[i] = thresholdVal; + setAlertParams('threshold', newThreshold); + }} + /> + + + + ); + })} + +
+
+
+ + { + setAlertDurationPopoverOpen(true); + }} + color={timeWindowSize ? 'secondary' : 'danger'} + /> + } + isOpen={alertDurationPopoverOpen} + closePopover={() => { + setAlertDurationPopoverOpen(false); + }} + ownFocus + withTitle + anchorPosition="downLeft" + > +
+ + + + + + + { + const { value } = e.target; + const timeWindowSizeVal = value !== '' ? parseInt(value, 10) : value; + setAlertParams('timeWindowSize', timeWindowSizeVal); + }} + /> + + + + { + setAlertParams('timeWindowUnit', e.target.value); + }} + options={getTimeOptions(timeWindowSize)} + /> + + +
+
+
+
+ {hasExpressionErrors ? null : ( + + + + )} +
+ ); +}; diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_alert_types/threshold/lib/api.ts b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_alert_types/threshold/lib/api.ts new file mode 100644 index 0000000000000..956007049a821 --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_alert_types/threshold/lib/api.ts @@ -0,0 +1,79 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { HttpSetup } from 'kibana/public'; + +const WATCHER_API_ROOT = '/api/watcher'; + +// TODO: replace watcher api with the proper from alerts + +export async function getMatchingIndicesForThresholdAlertType({ + pattern, + http, +}: { + pattern: string; + http: HttpSetup; +}): Promise> { + if (!pattern.startsWith('*')) { + pattern = `*${pattern}`; + } + if (!pattern.endsWith('*')) { + pattern = `${pattern}*`; + } + const { indices } = await http.post(`${WATCHER_API_ROOT}/indices`, { + body: JSON.stringify({ pattern }), + }); + return indices; +} + +export async function getThresholdAlertTypeFields({ + indexes, + http, +}: { + indexes: string[]; + http: HttpSetup; +}): Promise> { + const { fields } = await http.post(`${WATCHER_API_ROOT}/fields`, { + body: JSON.stringify({ indexes }), + }); + return fields; +} + +let savedObjectsClient: any; + +export const setSavedObjectsClient = (aSavedObjectsClient: any) => { + savedObjectsClient = aSavedObjectsClient; +}; + +export const getSavedObjectsClient = () => { + return savedObjectsClient; +}; + +export const loadIndexPatterns = async () => { + const { savedObjects } = await getSavedObjectsClient().find({ + type: 'index-pattern', + fields: ['title'], + perPage: 10000, + }); + return savedObjects; +}; + +export async function getThresholdAlertVisualizationData({ + model, + visualizeOptions, + http, +}: { + model: any; + visualizeOptions: any; + http: HttpSetup; +}): Promise> { + const { visualizeData } = await http.post(`${WATCHER_API_ROOT}/watch/visualize`, { + body: JSON.stringify({ + watch: model, + options: visualizeOptions, + }), + }); + return visualizeData; +} diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_alert_types/threshold/types.ts b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_alert_types/threshold/types.ts new file mode 100644 index 0000000000000..fd2a401fe59f3 --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_alert_types/threshold/types.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export interface Comparator { + text: string; + value: string; + requiredValues: number; +} + +export interface AggregationType { + text: string; + fieldRequired: boolean; + value: string; + validNormalizedTypes: string[]; +} + +export interface GroupByType { + text: string; + sizeRequired: boolean; + value: string; + validNormalizedTypes: string[]; +} diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_alert_types/threshold/visualization.tsx b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_alert_types/threshold/visualization.tsx new file mode 100644 index 0000000000000..8433c585ef3e5 --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/builtin_alert_types/threshold/visualization.tsx @@ -0,0 +1,303 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { Fragment, useEffect, useState } from 'react'; +import { IUiSettingsClient } from 'kibana/public'; +import { i18n } from '@kbn/i18n'; +import { + AnnotationDomainTypes, + Axis, + getAxisId, + getSpecId, + Chart, + LineAnnotation, + LineSeries, + Position, + ScaleType, + Settings, +} from '@elastic/charts'; +import { TimeBuckets } from 'ui/time_buckets'; +import dateMath from '@elastic/datemath'; +import moment from 'moment-timezone'; +import { EuiCallOut, EuiLoadingChart, EuiSpacer, EuiEmptyPrompt, EuiText } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { npStart } from 'ui/new_platform'; +import { getThresholdAlertVisualizationData } from './lib/api'; +import { comparators, aggregationTypes } from './expression'; +import { useAppDependencies } from '../../../app_context'; +import { Alert } from '../../../../types'; + +const customTheme = () => { + return { + lineSeriesStyle: { + line: { + strokeWidth: 3, + }, + point: { + visible: false, + }, + }, + }; +}; + +const getTimezone = (uiSettings: IUiSettingsClient) => { + const config = uiSettings; + const DATE_FORMAT_CONFIG_KEY = 'dateFormat:tz'; + const isCustomTimezone = !config.isDefault(DATE_FORMAT_CONFIG_KEY); + if (isCustomTimezone) { + return config.get(DATE_FORMAT_CONFIG_KEY); + } + + const detectedTimezone = moment.tz.guess(); + if (detectedTimezone) { + return detectedTimezone; + } + // default to UTC if we can't figure out the timezone + const tzOffset = moment().format('Z'); + return tzOffset; +}; + +const getDomain = (alertParams: any) => { + const VISUALIZE_TIME_WINDOW_MULTIPLIER = 5; + const fromExpression = `now-${alertParams.timeWindowSize * VISUALIZE_TIME_WINDOW_MULTIPLIER}${ + alertParams.timeWindowUnit + }`; + const toExpression = 'now'; + const fromMoment = dateMath.parse(fromExpression); + const toMoment = dateMath.parse(toExpression); + const visualizeTimeWindowFrom = fromMoment ? fromMoment.valueOf() : 0; + const visualizeTimeWindowTo = toMoment ? toMoment.valueOf() : 0; + return { + min: visualizeTimeWindowFrom, + max: visualizeTimeWindowTo, + }; +}; + +const getThreshold = (alertParams: any) => { + return alertParams.threshold.slice( + 0, + comparators[alertParams.thresholdComparator].requiredValues + ); +}; + +const getTimeBuckets = (alertParams: any) => { + const domain = getDomain(alertParams); + const timeBuckets = new TimeBuckets(); + timeBuckets.setBounds(domain); + return timeBuckets; +}; + +interface Props { + alert: Alert; +} + +export const ThresholdVisualization: React.FunctionComponent = ({ alert }) => { + const { http, uiSettings, toastNotifications } = useAppDependencies(); + const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(undefined); + const [visualizationData, setVisualizationData] = useState>([]); + + const chartsTheme = npStart.plugins.eui_utils.useChartsTheme(); + const { + index, + timeField, + triggerIntervalSize, + triggerIntervalUnit, + aggType, + aggField, + termSize, + termField, + thresholdComparator, + timeWindowSize, + timeWindowUnit, + groupBy, + threshold, + } = alert.params; + + const domain = getDomain(alert.params); + const timeBuckets = new TimeBuckets(); + timeBuckets.setBounds(domain); + const interval = timeBuckets.getInterval().expression; + const visualizeOptions = { + rangeFrom: domain.min, + rangeTo: domain.max, + interval, + timezone: getTimezone(uiSettings), + }; + + // Fetching visualization data is independent of alert actions + const alertWithoutActions = { ...alert.params, actions: [], type: 'threshold' }; + + useEffect(() => { + (async () => { + try { + setIsLoading(true); + setVisualizationData( + await getThresholdAlertVisualizationData({ + model: alertWithoutActions, + visualizeOptions, + http, + }) + ); + } catch (e) { + toastNotifications.addDanger({ + title: i18n.translate( + 'xpack.triggersActionsUI.sections.alertAdd.unableToLoadVisualizationMessage', + { defaultMessage: 'Unable to load visualization' } + ), + }); + setError(e); + } finally { + setIsLoading(false); + } + })(); + /* eslint-disable react-hooks/exhaustive-deps */ + }, [ + index, + timeField, + triggerIntervalSize, + triggerIntervalUnit, + aggType, + aggField, + termSize, + termField, + thresholdComparator, + timeWindowSize, + timeWindowUnit, + groupBy, + threshold, + ]); + /* eslint-enable react-hooks/exhaustive-deps */ + + if (isLoading) { + return ( + } + body={ + + + + } + /> + ); + } + + if (error) { + return ( + + + + } + color="danger" + iconType="alert" + > + {error} + + + + ); + } + + if (visualizationData) { + const alertVisualizationDataKeys = Object.keys(visualizationData); + const timezone = getTimezone(uiSettings); + const actualThreshold = getThreshold(alert.params); + let maxY = actualThreshold[actualThreshold.length - 1]; + + (Object.values(visualizationData) as number[][][]).forEach(data => { + data.forEach(([, y]) => { + if (y > maxY) { + maxY = y; + } + }); + }); + const dateFormatter = (d: number) => { + return moment(d) + .tz(timezone) + .format(getTimeBuckets(alert.params).getScaledDateFormat()); + }; + const aggLabel = aggregationTypes[aggType].text; + return ( +
+ + {alertVisualizationDataKeys.length ? ( + + + + + {alertVisualizationDataKeys.map((key: string) => { + return ( + + ); + })} + {actualThreshold.map((_value: any, i: number) => { + const specId = i === 0 ? 'threshold' : `threshold${i}`; + return ( + + ); + })} + + ) : ( + + } + color="warning" + > + + + )} + +
+ ); + } + + return null; +}; diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/delete_connectors_modal.tsx b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/delete_connectors_modal.tsx new file mode 100644 index 0000000000000..b7d1a4ffe2966 --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/delete_connectors_modal.tsx @@ -0,0 +1,91 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { EuiConfirmModal, EuiOverlayMask } from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import React from 'react'; +import { useAppDependencies } from '../app_context'; +import { deleteActions } from '../lib/action_connector_api'; + +export const DeleteConnectorsModal = ({ + connectorsToDelete, + callback, +}: { + connectorsToDelete: string[]; + callback: (deleted?: string[]) => void; +}) => { + const { http, toastNotifications } = useAppDependencies(); + const numConnectorsToDelete = connectorsToDelete.length; + if (!numConnectorsToDelete) { + return null; + } + const confirmModalText = i18n.translate( + 'xpack.triggersActionsUI.deleteSelectedConnectorsConfirmModal.descriptionText', + { + defaultMessage: + "You can't recover {numConnectorsToDelete, plural, one {a deleted connector} other {deleted connectors}}.", + values: { numConnectorsToDelete }, + } + ); + const confirmButtonText = i18n.translate( + 'xpack.triggersActionsUI.deleteSelectedConnectorsConfirmModal.deleteButtonLabel', + { + defaultMessage: + 'Delete {numConnectorsToDelete, plural, one {connector} other {# connectors}} ', + values: { numConnectorsToDelete }, + } + ); + const cancelButtonText = i18n.translate( + 'xpack.triggersActionsUI.deleteSelectedConnectorsConfirmModal.cancelButtonLabel', + { + defaultMessage: 'Cancel', + } + ); + return ( + + callback()} + onConfirm={async () => { + const { successes, errors } = await deleteActions({ ids: connectorsToDelete, http }); + const numSuccesses = successes.length; + const numErrors = errors.length; + callback(successes); + if (numSuccesses > 0) { + toastNotifications.addSuccess( + i18n.translate( + 'xpack.triggersActionsUI.sections.connectorsList.deleteSelectedConnectorsSuccessNotification.descriptionText', + { + defaultMessage: + 'Deleted {numSuccesses, number} {numSuccesses, plural, one {connector} other {connectors}}', + values: { numSuccesses }, + } + ) + ); + } + + if (numErrors > 0) { + toastNotifications.addDanger( + i18n.translate( + 'xpack.triggersActionsUI.sections.connectorsList.deleteSelectedConnectorsErrorNotification.descriptionText', + { + defaultMessage: + 'Failed to delete {numErrors, number} {numErrors, plural, one {connector} other {connectors}}', + values: { numErrors }, + } + ) + ); + } + }} + cancelButtonText={cancelButtonText} + confirmButtonText={confirmButtonText} + > + {confirmModalText} + + + ); +}; diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/section_loading.tsx b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/section_loading.tsx new file mode 100644 index 0000000000000..4c6273682a0e4 --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/components/section_loading.tsx @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; + +import { EuiEmptyPrompt, EuiLoadingSpinner, EuiText } from '@elastic/eui'; + +interface Props { + children: React.ReactNode; +} + +export const SectionLoading: React.FunctionComponent = ({ children }) => { + return ( + } + body={{children}} + data-test-subj="sectionLoading" + /> + ); +}; diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/constants/action_groups.ts b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/constants/action_groups.ts new file mode 100644 index 0000000000000..83a03010d55ad --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/constants/action_groups.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export enum ACTION_GROUPS { + ALERT = 'alert', + WARNING = 'warning', + UNACKNOWLEDGED = 'unacknowledged', +} diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/constants/index.ts b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/constants/index.ts new file mode 100644 index 0000000000000..a8364ffe21019 --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/constants/index.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const BASE_PATH = '/management/kibana/triggersActions'; +export const BASE_ACTION_API_PATH = '/api/action'; +export const BASE_ALERT_API_PATH = '/api/alert'; + +export type Section = 'connectors' | 'alerts'; + +export const routeToHome = `${BASE_PATH}`; +export const routeToConnectors = `${BASE_PATH}/connectors`; +export const routeToAlerts = `${BASE_PATH}/alerts`; + +export { TIME_UNITS } from './time_units'; +export enum SORT_ORDERS { + ASCENDING = 'asc', + DESCENDING = 'desc', +} diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/constants/plugin.ts b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/constants/plugin.ts new file mode 100644 index 0000000000000..63ba7df2556de --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/constants/plugin.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const PLUGIN = { + ID: 'triggers_actions_ui', + getI18nName: (i18n: any): string => { + return i18n.translate('xpack.triggersActionsUI.appName', { + defaultMessage: 'Alerts and Actions', + }); + }, +}; diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/constants/time_units.ts b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/constants/time_units.ts new file mode 100644 index 0000000000000..2a4f8fbd421ed --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/constants/time_units.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export enum TIME_UNITS { + SECOND = 's', + MINUTE = 'm', + HOUR = 'h', + DAY = 'd', +} diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/context/actions_connectors_context.tsx b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/context/actions_connectors_context.tsx new file mode 100644 index 0000000000000..11786950d0f26 --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/context/actions_connectors_context.tsx @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { createContext, useContext } from 'react'; +import { ActionType } from '../../types'; + +export interface ActionsConnectorsContextValue { + addFlyoutVisible: boolean; + editFlyoutVisible: boolean; + setEditFlyoutVisibility: React.Dispatch>; + setAddFlyoutVisibility: React.Dispatch>; + actionTypesIndex: Record | undefined; + reloadConnectors: () => Promise; +} + +const ActionsConnectorsContext = createContext(null as any); + +export const ActionsConnectorsContextProvider = ({ + children, + value, +}: { + value: ActionsConnectorsContextValue; + children: React.ReactNode; +}) => { + return ( + {children} + ); +}; + +export const useActionsConnectorsContext = () => { + const ctx = useContext(ActionsConnectorsContext); + if (!ctx) { + throw new Error('ActionsConnectorsContext has not been set.'); + } + return ctx; +}; diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/context/alerts_context.tsx b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/context/alerts_context.tsx new file mode 100644 index 0000000000000..06be1bb7c5851 --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/context/alerts_context.tsx @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useContext, createContext } from 'react'; + +export interface AlertsContextValue { + alertFlyoutVisible: boolean; + setAlertFlyoutVisibility: React.Dispatch>; +} + +const AlertsContext = createContext(null as any); + +export const AlertsContextProvider = ({ + children, + value, +}: { + value: AlertsContextValue; + children: React.ReactNode; +}) => { + return {children}; +}; + +export const useAlertsContext = () => { + const ctx = useContext(AlertsContext); + if (!ctx) { + throw new Error('ActionsConnectorsContext has not been set.'); + } + return ctx; +}; diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/home.tsx b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/home.tsx new file mode 100644 index 0000000000000..3312f1a103b29 --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/home.tsx @@ -0,0 +1,126 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useEffect } from 'react'; +import { Route, RouteComponentProps, Switch } from 'react-router-dom'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { + EuiPageBody, + EuiPageContent, + EuiPageContentHeader, + EuiPageContentHeaderSection, + EuiSpacer, + EuiTab, + EuiTabs, + EuiTitle, +} from '@elastic/eui'; + +import { BASE_PATH, Section, routeToConnectors, routeToAlerts } from './constants'; +import { getCurrentBreadcrumb } from './lib/breadcrumb'; +import { getCurrentDocTitle } from './lib/doc_title'; +import { useAppDependencies } from './app_context'; +import { hasShowActionsCapability, hasShowAlertsCapability } from './lib/capabilities'; + +import { ActionsConnectorsList } from './sections/actions_connectors_list/components/actions_connectors_list'; +import { AlertsList } from './sections/alerts_list/components/alerts_list'; + +interface MatchParams { + section: Section; +} + +export const TriggersActionsUIHome: React.FunctionComponent> = ({ + match: { + params: { section }, + }, + history, +}) => { + const { + chrome, + legacy: { MANAGEMENT_BREADCRUMB, capabilities }, + } = useAppDependencies(); + + const canShowActions = hasShowActionsCapability(capabilities.get()); + const canShowAlerts = hasShowAlertsCapability(capabilities.get()); + const tabs: Array<{ + id: Section; + name: React.ReactNode; + }> = []; + + if (canShowAlerts) { + tabs.push({ + id: 'alerts', + name: ( + + ), + }); + } + + if (canShowActions) { + tabs.push({ + id: 'connectors', + name: ( + + ), + }); + } + + const onSectionChange = (newSection: Section) => { + history.push(`${BASE_PATH}/${newSection}`); + }; + + // Set breadcrumb and page title + useEffect(() => { + chrome.setBreadcrumbs([MANAGEMENT_BREADCRUMB, getCurrentBreadcrumb(section || 'home')]); + chrome.docTitle.change(getCurrentDocTitle(section || 'home')); + }, [section, chrome, MANAGEMENT_BREADCRUMB]); + + return ( + + + + + +

+ +

+
+
+
+ + + {tabs.map(tab => ( + onSectionChange(tab.id)} + isSelected={tab.id === section} + key={tab.id} + data-test-subj={`${tab.id}Tab`} + > + {tab.name} + + ))} + + + + + + {canShowActions && ( + + )} + {canShowAlerts && } + +
+
+ ); +}; diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/lib/action_connector_api.test.ts b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/lib/action_connector_api.test.ts new file mode 100644 index 0000000000000..bc2949917edea --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/lib/action_connector_api.test.ts @@ -0,0 +1,135 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ActionConnector, ActionConnectorWithoutId, ActionType } from '../../types'; +import { httpServiceMock } from '../../../../../../../../src/core/public/mocks'; +import { + createActionConnector, + deleteActions, + loadActionTypes, + loadAllActions, + updateActionConnector, +} from './action_connector_api'; + +const http = httpServiceMock.createStartContract(); + +beforeEach(() => jest.resetAllMocks()); + +describe('loadActionTypes', () => { + test('should call get types API', async () => { + const resolvedValue: ActionType[] = [ + { + id: 'test', + name: 'Test', + }, + ]; + http.get.mockResolvedValueOnce(resolvedValue); + + const result = await loadActionTypes({ http }); + expect(result).toEqual(resolvedValue); + expect(http.get.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + "/api/action/types", + ] + `); + }); +}); + +describe('loadAllActions', () => { + test('should call find actions API', async () => { + const resolvedValue = { + page: 1, + perPage: 10000, + total: 0, + data: [], + }; + http.get.mockResolvedValueOnce(resolvedValue); + + const result = await loadAllActions({ http }); + expect(result).toEqual(resolvedValue); + expect(http.get.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + "/api/action/_find", + Object { + "query": Object { + "per_page": 10000, + }, + }, + ] + `); + }); +}); + +describe('createActionConnector', () => { + test('should call create action API', async () => { + const connector: ActionConnectorWithoutId = { + actionTypeId: 'test', + name: 'My test', + config: {}, + secrets: {}, + }; + const resolvedValue: ActionConnector = { ...connector, id: '123' }; + http.post.mockResolvedValueOnce(resolvedValue); + + const result = await createActionConnector({ http, connector }); + expect(result).toEqual(resolvedValue); + expect(http.post.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + "/api/action", + Object { + "body": "{\\"actionTypeId\\":\\"test\\",\\"name\\":\\"My test\\",\\"config\\":{},\\"secrets\\":{}}", + }, + ] + `); + }); +}); + +describe('updateActionConnector', () => { + test('should call the update API', async () => { + const id = '123'; + const connector: ActionConnectorWithoutId = { + actionTypeId: 'test', + name: 'My test', + config: {}, + secrets: {}, + }; + const resolvedValue = { ...connector, id }; + http.put.mockResolvedValueOnce(resolvedValue); + + const result = await updateActionConnector({ http, connector, id }); + expect(result).toEqual(resolvedValue); + expect(http.put.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + "/api/action/123", + Object { + "body": "{\\"name\\":\\"My test\\",\\"config\\":{},\\"secrets\\":{}}", + }, + ] + `); + }); +}); + +describe('deleteActions', () => { + test('should call delete API per action', async () => { + const ids = ['1', '2', '3']; + + const result = await deleteActions({ ids, http }); + expect(result).toEqual({ errors: [], successes: [undefined, undefined, undefined] }); + expect(http.delete.mock.calls).toMatchInlineSnapshot(` + Array [ + Array [ + "/api/action/1", + ], + Array [ + "/api/action/2", + ], + Array [ + "/api/action/3", + ], + ] + `); + }); +}); diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/lib/action_connector_api.ts b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/lib/action_connector_api.ts new file mode 100644 index 0000000000000..5b2b59603d281 --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/lib/action_connector_api.ts @@ -0,0 +1,85 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { HttpSetup } from 'kibana/public'; +import { BASE_ACTION_API_PATH } from '../constants'; +import { ActionConnector, ActionConnectorWithoutId, ActionType } from '../../types'; + +// We are assuming there won't be many actions. This is why we will load +// all the actions in advance and assume the total count to not go over 100 or so. +// We'll set this max setting assuming it's never reached. +const MAX_ACTIONS_RETURNED = 10000; + +export async function loadActionTypes({ http }: { http: HttpSetup }): Promise { + return await http.get(`${BASE_ACTION_API_PATH}/types`); +} + +export async function loadAllActions({ + http, +}: { + http: HttpSetup; +}): Promise<{ + page: number; + perPage: number; + total: number; + data: ActionConnector[]; +}> { + return await http.get(`${BASE_ACTION_API_PATH}/_find`, { + query: { + per_page: MAX_ACTIONS_RETURNED, + }, + }); +} + +export async function createActionConnector({ + http, + connector, +}: { + http: HttpSetup; + connector: Omit; +}): Promise { + return await http.post(`${BASE_ACTION_API_PATH}`, { + body: JSON.stringify(connector), + }); +} + +export async function updateActionConnector({ + http, + connector, + id, +}: { + http: HttpSetup; + connector: Pick; + id: string; +}): Promise { + return await http.put(`${BASE_ACTION_API_PATH}/${id}`, { + body: JSON.stringify({ + name: connector.name, + config: connector.config, + secrets: connector.secrets, + }), + }); +} + +export async function deleteActions({ + ids, + http, +}: { + ids: string[]; + http: HttpSetup; +}): Promise<{ successes: string[]; errors: string[] }> { + const successes: string[] = []; + const errors: string[] = []; + await Promise.all(ids.map(id => http.delete(`${BASE_ACTION_API_PATH}/${id}`))).then( + function(fulfilled) { + successes.push(...fulfilled); + }, + function(rejected) { + errors.push(...rejected); + } + ); + return { successes, errors }; +} diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/lib/alert_api.test.ts b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/lib/alert_api.test.ts new file mode 100644 index 0000000000000..858c90258247e --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/lib/alert_api.test.ts @@ -0,0 +1,406 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Alert, AlertType } from '../../types'; +import { httpServiceMock } from '../../../../../../../../src/core/public/mocks'; +import { + createAlert, + deleteAlerts, + disableAlerts, + enableAlerts, + loadAlerts, + loadAlertTypes, + muteAlerts, + unmuteAlerts, + updateAlert, +} from './alert_api'; + +const http = httpServiceMock.createStartContract(); + +beforeEach(() => jest.resetAllMocks()); + +describe('loadAlertTypes', () => { + test('should call get alert types API', async () => { + const resolvedValue: AlertType[] = [ + { + id: 'test', + name: 'Test', + }, + ]; + http.get.mockResolvedValueOnce(resolvedValue); + + const result = await loadAlertTypes({ http }); + expect(result).toEqual(resolvedValue); + expect(http.get.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + "/api/alert/types", + ] + `); + }); +}); + +describe('loadAlerts', () => { + test('should call find API with base parameters', async () => { + const resolvedValue = { + page: 1, + perPage: 10, + total: 0, + data: [], + }; + http.get.mockResolvedValueOnce(resolvedValue); + + const result = await loadAlerts({ http, page: { index: 0, size: 10 } }); + expect(result).toEqual(resolvedValue); + expect(http.get.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + "/api/alert/_find", + Object { + "query": Object { + "default_search_operator": "AND", + "filter": undefined, + "page": 1, + "per_page": 10, + "search": undefined, + "search_fields": undefined, + }, + }, + ] + `); + }); + + test('should call find API with searchText', async () => { + const resolvedValue = { + page: 1, + perPage: 10, + total: 0, + data: [], + }; + http.get.mockResolvedValueOnce(resolvedValue); + + const result = await loadAlerts({ http, searchText: 'apples', page: { index: 0, size: 10 } }); + expect(result).toEqual(resolvedValue); + expect(http.get.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + "/api/alert/_find", + Object { + "query": Object { + "default_search_operator": "AND", + "filter": undefined, + "page": 1, + "per_page": 10, + "search": "apples", + "search_fields": "[\\"name\\",\\"tags\\"]", + }, + }, + ] + `); + }); + + test('should call find API with actionTypesFilter', async () => { + const resolvedValue = { + page: 1, + perPage: 10, + total: 0, + data: [], + }; + http.get.mockResolvedValueOnce(resolvedValue); + + const result = await loadAlerts({ + http, + searchText: 'foo', + page: { index: 0, size: 10 }, + }); + expect(result).toEqual(resolvedValue); + expect(http.get.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + "/api/alert/_find", + Object { + "query": Object { + "default_search_operator": "AND", + "filter": undefined, + "page": 1, + "per_page": 10, + "search": "foo", + "search_fields": "[\\"name\\",\\"tags\\"]", + }, + }, + ] + `); + }); + + test('should call find API with typesFilter', async () => { + const resolvedValue = { + page: 1, + perPage: 10, + total: 0, + data: [], + }; + http.get.mockResolvedValueOnce(resolvedValue); + + const result = await loadAlerts({ + http, + typesFilter: ['foo', 'bar'], + page: { index: 0, size: 10 }, + }); + expect(result).toEqual(resolvedValue); + expect(http.get.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + "/api/alert/_find", + Object { + "query": Object { + "default_search_operator": "AND", + "filter": "alert.attributes.alertTypeId:(foo or bar)", + "page": 1, + "per_page": 10, + "search": undefined, + "search_fields": undefined, + }, + }, + ] + `); + }); + + test('should call find API with actionTypesFilter and typesFilter', async () => { + const resolvedValue = { + page: 1, + perPage: 10, + total: 0, + data: [], + }; + http.get.mockResolvedValueOnce(resolvedValue); + + const result = await loadAlerts({ + http, + searchText: 'baz', + typesFilter: ['foo', 'bar'], + page: { index: 0, size: 10 }, + }); + expect(result).toEqual(resolvedValue); + expect(http.get.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + "/api/alert/_find", + Object { + "query": Object { + "default_search_operator": "AND", + "filter": "alert.attributes.alertTypeId:(foo or bar)", + "page": 1, + "per_page": 10, + "search": "baz", + "search_fields": "[\\"name\\",\\"tags\\"]", + }, + }, + ] + `); + }); + + test('should call find API with searchText and tagsFilter and typesFilter', async () => { + const resolvedValue = { + page: 1, + perPage: 10, + total: 0, + data: [], + }; + http.get.mockResolvedValueOnce(resolvedValue); + + const result = await loadAlerts({ + http, + searchText: 'apples, foo, baz', + typesFilter: ['foo', 'bar'], + page: { index: 0, size: 10 }, + }); + expect(result).toEqual(resolvedValue); + expect(http.get.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + "/api/alert/_find", + Object { + "query": Object { + "default_search_operator": "AND", + "filter": "alert.attributes.alertTypeId:(foo or bar)", + "page": 1, + "per_page": 10, + "search": "apples, foo, baz", + "search_fields": "[\\"name\\",\\"tags\\"]", + }, + }, + ] + `); + }); +}); + +describe('deleteAlerts', () => { + test('should call delete API for each alert', async () => { + const ids = ['1', '2', '3']; + const result = await deleteAlerts({ http, ids }); + expect(result).toEqual(undefined); + expect(http.delete.mock.calls).toMatchInlineSnapshot(` + Array [ + Array [ + "/api/alert/1", + ], + Array [ + "/api/alert/2", + ], + Array [ + "/api/alert/3", + ], + ] + `); + }); +}); + +describe('createAlert', () => { + test('should call create alert API', async () => { + const alertToCreate = { + name: 'test', + tags: ['foo'], + enabled: true, + alertTypeId: 'test', + interval: '1m', + actions: [], + params: {}, + throttle: null, + }; + const resolvedValue: Alert = { + ...alertToCreate, + id: '123', + createdBy: null, + updatedBy: null, + muteAll: false, + mutedInstanceIds: [], + }; + http.post.mockResolvedValueOnce(resolvedValue); + + const result = await createAlert({ http, alert: alertToCreate }); + expect(result).toEqual(resolvedValue); + expect(http.post.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + "/api/alert", + Object { + "body": "{\\"name\\":\\"test\\",\\"tags\\":[\\"foo\\"],\\"enabled\\":true,\\"alertTypeId\\":\\"test\\",\\"interval\\":\\"1m\\",\\"actions\\":[],\\"params\\":{},\\"throttle\\":null}", + }, + ] + `); + }); +}); + +describe('updateAlert', () => { + test('should call alert update API', async () => { + const alertToUpdate = { + throttle: '1m', + name: 'test', + tags: ['foo'], + interval: '1m', + params: {}, + actions: [], + }; + const resolvedValue: Alert = { + ...alertToUpdate, + id: '123', + enabled: true, + alertTypeId: 'test', + createdBy: null, + updatedBy: null, + muteAll: false, + mutedInstanceIds: [], + }; + http.put.mockResolvedValueOnce(resolvedValue); + + const result = await updateAlert({ http, id: '123', alert: alertToUpdate }); + expect(result).toEqual(resolvedValue); + expect(http.put.mock.calls[0]).toMatchInlineSnapshot(` + Array [ + "/api/alert/123", + Object { + "body": "{\\"throttle\\":\\"1m\\",\\"name\\":\\"test\\",\\"tags\\":[\\"foo\\"],\\"interval\\":\\"1m\\",\\"params\\":{},\\"actions\\":[]}", + }, + ] + `); + }); +}); + +describe('enableAlerts', () => { + test('should call enable alert API per alert', async () => { + const ids = ['1', '2', '3']; + const result = await enableAlerts({ http, ids }); + expect(result).toEqual(undefined); + expect(http.post.mock.calls).toMatchInlineSnapshot(` + Array [ + Array [ + "/api/alert/1/_enable", + ], + Array [ + "/api/alert/2/_enable", + ], + Array [ + "/api/alert/3/_enable", + ], + ] + `); + }); +}); + +describe('disableAlerts', () => { + test('should call disable alert API per alert', async () => { + const ids = ['1', '2', '3']; + const result = await disableAlerts({ http, ids }); + expect(result).toEqual(undefined); + expect(http.post.mock.calls).toMatchInlineSnapshot(` + Array [ + Array [ + "/api/alert/1/_disable", + ], + Array [ + "/api/alert/2/_disable", + ], + Array [ + "/api/alert/3/_disable", + ], + ] + `); + }); +}); + +describe('muteAlerts', () => { + test('should call mute alert API per alert', async () => { + const ids = ['1', '2', '3']; + const result = await muteAlerts({ http, ids }); + expect(result).toEqual(undefined); + expect(http.post.mock.calls).toMatchInlineSnapshot(` + Array [ + Array [ + "/api/alert/1/_mute_all", + ], + Array [ + "/api/alert/2/_mute_all", + ], + Array [ + "/api/alert/3/_mute_all", + ], + ] + `); + }); +}); + +describe('unmuteAlerts', () => { + test('should call unmute alert API per alert', async () => { + const ids = ['1', '2', '3']; + const result = await unmuteAlerts({ http, ids }); + expect(result).toEqual(undefined); + expect(http.post.mock.calls).toMatchInlineSnapshot(` + Array [ + Array [ + "/api/alert/1/_unmute_all", + ], + Array [ + "/api/alert/2/_unmute_all", + ], + Array [ + "/api/alert/3/_unmute_all", + ], + ] + `); + }); +}); diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/lib/alert_api.ts b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/lib/alert_api.ts new file mode 100644 index 0000000000000..9867acbd7a622 --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/lib/alert_api.ts @@ -0,0 +1,126 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { HttpSetup } from 'kibana/public'; +import { BASE_ALERT_API_PATH } from '../constants'; +import { Alert, AlertType, AlertWithoutId } from '../../types'; + +export async function loadAlertTypes({ http }: { http: HttpSetup }): Promise { + return await http.get(`${BASE_ALERT_API_PATH}/types`); +} + +export async function loadAlerts({ + http, + page, + searchText, + typesFilter, + actionTypesFilter, +}: { + http: HttpSetup; + page: { index: number; size: number }; + searchText?: string; + typesFilter?: string[]; + actionTypesFilter?: string[]; +}): Promise<{ + page: number; + perPage: number; + total: number; + data: Alert[]; +}> { + const filters = []; + if (typesFilter && typesFilter.length) { + filters.push(`alert.attributes.alertTypeId:(${typesFilter.join(' or ')})`); + } + if (actionTypesFilter && actionTypesFilter.length) { + filters.push( + [ + '(', + actionTypesFilter.map(id => `alert.attributes.actions:{ actionTypeId:${id} }`).join(' OR '), + ')', + ].join('') + ); + } + return await http.get(`${BASE_ALERT_API_PATH}/_find`, { + query: { + page: page.index + 1, + per_page: page.size, + search_fields: searchText ? JSON.stringify(['name', 'tags']) : undefined, + search: searchText, + filter: filters.length ? filters.join(' and ') : undefined, + default_search_operator: 'AND', + }, + }); +} + +export async function deleteAlerts({ + ids, + http, +}: { + ids: string[]; + http: HttpSetup; +}): Promise { + await Promise.all(ids.map(id => http.delete(`${BASE_ALERT_API_PATH}/${id}`))); +} + +export async function createAlert({ + http, + alert, +}: { + http: HttpSetup; + alert: Omit; +}): Promise { + return await http.post(`${BASE_ALERT_API_PATH}`, { + body: JSON.stringify(alert), + }); +} + +export async function updateAlert({ + http, + alert, + id, +}: { + http: HttpSetup; + alert: Pick; + id: string; +}): Promise { + return await http.put(`${BASE_ALERT_API_PATH}/${id}`, { + body: JSON.stringify(alert), + }); +} + +export async function enableAlerts({ + ids, + http, +}: { + ids: string[]; + http: HttpSetup; +}): Promise { + await Promise.all(ids.map(id => http.post(`${BASE_ALERT_API_PATH}/${id}/_enable`))); +} + +export async function disableAlerts({ + ids, + http, +}: { + ids: string[]; + http: HttpSetup; +}): Promise { + await Promise.all(ids.map(id => http.post(`${BASE_ALERT_API_PATH}/${id}/_disable`))); +} + +export async function muteAlerts({ ids, http }: { ids: string[]; http: HttpSetup }): Promise { + await Promise.all(ids.map(id => http.post(`${BASE_ALERT_API_PATH}/${id}/_mute_all`))); +} + +export async function unmuteAlerts({ + ids, + http, +}: { + ids: string[]; + http: HttpSetup; +}): Promise { + await Promise.all(ids.map(id => http.post(`${BASE_ALERT_API_PATH}/${id}/_unmute_all`))); +} diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/lib/breadcrumb.test.ts b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/lib/breadcrumb.test.ts new file mode 100644 index 0000000000000..b75e014640d72 --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/lib/breadcrumb.test.ts @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { getCurrentBreadcrumb } from './breadcrumb'; +import { i18n } from '@kbn/i18n'; +import { routeToConnectors, routeToAlerts, routeToHome } from '../constants'; + +describe('getCurrentBreadcrumb', () => { + test('if change calls return proper breadcrumb title ', async () => { + expect(getCurrentBreadcrumb('connectors')).toMatchObject({ + text: i18n.translate('xpack.triggersActionsUI.connectors.breadcrumbTitle', { + defaultMessage: 'Connectors', + }), + href: `#${routeToConnectors}`, + }); + expect(getCurrentBreadcrumb('alerts')).toMatchObject({ + text: i18n.translate('xpack.triggersActionsUI.alerts.breadcrumbTitle', { + defaultMessage: 'Alerts', + }), + href: `#${routeToAlerts}`, + }); + expect(getCurrentBreadcrumb('home')).toMatchObject({ + text: i18n.translate('xpack.triggersActionsUI.home.breadcrumbTitle', { + defaultMessage: 'Alerts and Actions', + }), + href: `#${routeToHome}`, + }); + }); +}); diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/lib/breadcrumb.ts b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/lib/breadcrumb.ts new file mode 100644 index 0000000000000..f833ae9eb39ac --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/lib/breadcrumb.ts @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import { routeToHome, routeToConnectors, routeToAlerts } from '../constants'; + +export const getCurrentBreadcrumb = (type: string): { text: string; href: string } => { + // Home and sections + switch (type) { + case 'connectors': + return { + text: i18n.translate('xpack.triggersActionsUI.connectors.breadcrumbTitle', { + defaultMessage: 'Connectors', + }), + href: `#${routeToConnectors}`, + }; + case 'alerts': + return { + text: i18n.translate('xpack.triggersActionsUI.alerts.breadcrumbTitle', { + defaultMessage: 'Alerts', + }), + href: `#${routeToAlerts}`, + }; + default: + return { + text: i18n.translate('xpack.triggersActionsUI.home.breadcrumbTitle', { + defaultMessage: 'Alerts and Actions', + }), + href: `#${routeToHome}`, + }; + } +}; diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/lib/capabilities.ts b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/lib/capabilities.ts new file mode 100644 index 0000000000000..e5693e31c2d66 --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/lib/capabilities.ts @@ -0,0 +1,53 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/** + * NOTE: Applications that want to show the alerting UIs will need to add + * check against their features here until we have a better solution. This + * will possibly go away with https://github.com/elastic/kibana/issues/52300. + */ + +export function hasShowAlertsCapability(capabilities: any): boolean { + if (capabilities.siem && capabilities.siem['alerting:show']) { + return true; + } + return false; +} + +export function hasShowActionsCapability(capabilities: any): boolean { + if (capabilities.siem && capabilities.siem['actions:show']) { + return true; + } + return false; +} + +export function hasSaveAlertsCapability(capabilities: any): boolean { + if (capabilities.siem && capabilities.siem['alerting:save']) { + return true; + } + return false; +} + +export function hasSaveActionsCapability(capabilities: any): boolean { + if (capabilities.siem && capabilities.siem['actions:save']) { + return true; + } + return false; +} + +export function hasDeleteAlertsCapability(capabilities: any): boolean { + if (capabilities.siem && capabilities.siem['alerting:delete']) { + return true; + } + return false; +} + +export function hasDeleteActionsCapability(capabilities: any): boolean { + if (capabilities.siem && capabilities.siem['actions:delete']) { + return true; + } + return false; +} diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/lib/doc_title.test.ts b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/lib/doc_title.test.ts new file mode 100644 index 0000000000000..f351adf79eb2c --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/lib/doc_title.test.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { getCurrentDocTitle } from './doc_title'; + +describe('getCurrentDocTitle', () => { + test('if change calls return the proper doc title ', async () => { + expect(getCurrentDocTitle('home') === 'Alerts and Actions').toBeTruthy(); + expect(getCurrentDocTitle('connectors') === 'Connectors').toBeTruthy(); + expect(getCurrentDocTitle('alerts') === 'Alerts').toBeTruthy(); + }); +}); diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/lib/doc_title.ts b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/lib/doc_title.ts new file mode 100644 index 0000000000000..15bd6bc77b132 --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/lib/doc_title.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { i18n } from '@kbn/i18n'; + +export const getCurrentDocTitle = (page: string): string => { + let updatedTitle: string; + + switch (page) { + case 'connectors': + updatedTitle = i18n.translate('xpack.triggersActionsUI.connectors.breadcrumbTitle', { + defaultMessage: 'Connectors', + }); + break; + case 'alerts': + updatedTitle = i18n.translate('xpack.triggersActionsUI.alerts.breadcrumbTitle', { + defaultMessage: 'Alerts', + }); + break; + default: + updatedTitle = i18n.translate('xpack.triggersActionsUI.home.breadcrumbTitle', { + defaultMessage: 'Alerts and Actions', + }); + } + return updatedTitle; +}; diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/lib/get_time_options.test.ts b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/lib/get_time_options.test.ts new file mode 100644 index 0000000000000..3ed7eea026db4 --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/lib/get_time_options.test.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { getTimeOptions, getTimeFieldOptions } from './get_time_options'; + +describe('get_time_options', () => { + test('if getTimeOptions return single unit time options', () => { + const timeUnitValue = getTimeOptions('1'); + expect(timeUnitValue).toMatchObject([ + { text: 'second', value: 's' }, + { text: 'minute', value: 'm' }, + { text: 'hour', value: 'h' }, + { text: 'day', value: 'd' }, + ]); + }); + + test('if getTimeOptions return multiple unit time options', () => { + const timeUnitValue = getTimeOptions('10'); + expect(timeUnitValue).toMatchObject([ + { text: 'seconds', value: 's' }, + { text: 'minutes', value: 'm' }, + { text: 'hours', value: 'h' }, + { text: 'days', value: 'd' }, + ]); + }); + + test('if getTimeFieldOptions return only date type fields', () => { + const timeOnlyTypeFields = getTimeFieldOptions([ + { type: 'date', name: 'order_date' }, + { type: 'number', name: 'sum' }, + ]); + expect(timeOnlyTypeFields).toMatchObject([{ text: 'order_date', value: 'order_date' }]); + }); +}); diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/lib/get_time_options.ts b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/lib/get_time_options.ts new file mode 100644 index 0000000000000..d24f20a4fc289 --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/lib/get_time_options.ts @@ -0,0 +1,37 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { getTimeUnitLabel } from './get_time_unit_label'; +import { TIME_UNITS } from '../constants'; + +export const getTimeOptions = (unitSize: string) => + Object.entries(TIME_UNITS).map(([_key, value]) => { + return { + text: getTimeUnitLabel(value, unitSize), + value, + }; + }); + +interface TimeFieldOptions { + text: string; + value: string; +} + +export const getTimeFieldOptions = ( + fields: Array<{ type: string; name: string }> +): TimeFieldOptions[] => { + const options: TimeFieldOptions[] = []; + + fields.forEach((field: { type: string; name: string }) => { + if (field.type === 'date') { + options.push({ + text: field.name, + value: field.name, + }); + } + }); + return options; +}; diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/lib/get_time_unit_label.ts b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/lib/get_time_unit_label.ts new file mode 100644 index 0000000000000..a621855415328 --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/lib/get_time_unit_label.ts @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import { TIME_UNITS } from '../constants'; + +export function getTimeUnitLabel(timeUnit = TIME_UNITS.SECOND, timeValue = '0') { + switch (timeUnit) { + case TIME_UNITS.SECOND: + return i18n.translate('xpack.triggersActionsUI.timeUnits.secondLabel', { + defaultMessage: '{timeValue, plural, one {second} other {seconds}}', + values: { timeValue }, + }); + case TIME_UNITS.MINUTE: + return i18n.translate('xpack.triggersActionsUI.timeUnits.minuteLabel', { + defaultMessage: '{timeValue, plural, one {minute} other {minutes}}', + values: { timeValue }, + }); + case TIME_UNITS.HOUR: + return i18n.translate('xpack.triggersActionsUI.timeUnits.hourLabel', { + defaultMessage: '{timeValue, plural, one {hour} other {hours}}', + values: { timeValue }, + }); + case TIME_UNITS.DAY: + return i18n.translate('xpack.triggersActionsUI.timeUnits.dayLabel', { + defaultMessage: '{timeValue, plural, one {day} other {days}}', + values: { timeValue }, + }); + } +} diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/action_connector_form/action_connector_form.test.tsx b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/action_connector_form/action_connector_form.test.tsx new file mode 100644 index 0000000000000..c129ce73c7176 --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/action_connector_form/action_connector_form.test.tsx @@ -0,0 +1,113 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import * as React from 'react'; +import { mountWithIntl } from 'test_utils/enzyme_helpers'; +import { coreMock } from '../../../../../../../../../src/core/public/mocks'; +import { ReactWrapper } from 'enzyme'; +import { act } from 'react-dom/test-utils'; +import { ActionsConnectorsContextProvider } from '../../context/actions_connectors_context'; +import { actionTypeRegistryMock } from '../../action_type_registry.mock'; +import { ValidationResult, ActionConnector } from '../../../types'; +import { ActionConnectorForm } from './action_connector_form'; +import { AppContextProvider } from '../../app_context'; +const actionTypeRegistry = actionTypeRegistryMock.create(); + +describe('action_connector_form', () => { + let wrapper: ReactWrapper; + + beforeAll(async () => { + const mockes = coreMock.createSetup(); + const [{ chrome, docLinks }] = await mockes.getStartServices(); + const deps = { + chrome, + docLinks, + toastNotifications: mockes.notifications.toasts, + injectedMetadata: mockes.injectedMetadata, + http: mockes.http, + uiSettings: mockes.uiSettings, + legacy: { + capabilities: { + get() { + return { + actions: { + delete: true, + save: true, + show: true, + }, + }; + }, + } as any, + MANAGEMENT_BREADCRUMB: { set: () => {} } as any, + }, + actionTypeRegistry: actionTypeRegistry as any, + alertTypeRegistry: {} as any, + }; + + const actionType = { + id: 'my-action-type', + iconClass: 'test', + selectMessage: 'test', + validateConnector: (): ValidationResult => { + return { errors: {} }; + }, + validateParams: (): ValidationResult => { + const validationResult = { errors: {} }; + return validationResult; + }, + actionConnectorFields: null, + actionParamsFields: null, + }; + actionTypeRegistry.get.mockReturnValue(actionType); + actionTypeRegistry.has.mockReturnValue(true); + + const initialConnector = { + actionTypeId: actionType.id, + config: {}, + secrets: {}, + } as ActionConnector; + + await act(async () => { + wrapper = mountWithIntl( + + {}, + editFlyoutVisible: false, + setEditFlyoutVisibility: () => {}, + actionTypesIndex: { + 'my-action-type': { id: 'my-action-type', name: 'my-action-type-name' }, + }, + reloadConnectors: () => { + return new Promise(() => {}); + }, + }} + > + {}} + /> + + + ); + }); + + await waitForRender(wrapper); + }); + + it('renders action_connector_form', () => { + const connectorNameField = wrapper.find('[data-test-subj="nameInput"]'); + expect(connectorNameField.exists()).toBeTruthy(); + expect(connectorNameField.first().prop('value')).toBe(''); + }); +}); + +async function waitForRender(wrapper: ReactWrapper) { + await Promise.resolve(); + await Promise.resolve(); + wrapper.update(); +} diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/action_connector_form/action_connector_form.tsx b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/action_connector_form/action_connector_form.tsx new file mode 100644 index 0000000000000..682c1fbb54b67 --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/action_connector_form/action_connector_form.tsx @@ -0,0 +1,270 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React, { Fragment, useState, useReducer } from 'react'; +import { + EuiButton, + EuiFlexGroup, + EuiFlexItem, + EuiForm, + EuiCallOut, + EuiLink, + EuiText, + EuiSpacer, + EuiButtonEmpty, + EuiFlyoutFooter, + EuiFieldText, + EuiFlyoutBody, + EuiFormRow, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { createActionConnector, updateActionConnector } from '../../lib/action_connector_api'; +import { useAppDependencies } from '../../app_context'; +import { connectorReducer } from './connector_reducer'; +import { useActionsConnectorsContext } from '../../context/actions_connectors_context'; +import { ActionConnector, IErrorObject } from '../../../types'; +import { hasSaveActionsCapability } from '../../lib/capabilities'; + +interface ActionConnectorProps { + initialConnector: ActionConnector; + actionTypeName: string; + setFlyoutVisibility: React.Dispatch>; +} + +export const ActionConnectorForm = ({ + initialConnector, + actionTypeName, + setFlyoutVisibility, +}: ActionConnectorProps) => { + const { + http, + toastNotifications, + legacy: { capabilities }, + actionTypeRegistry, + } = useAppDependencies(); + + const { reloadConnectors } = useActionsConnectorsContext(); + const canSave = hasSaveActionsCapability(capabilities.get()); + + // hooks + const [{ connector }, dispatch] = useReducer(connectorReducer, { connector: initialConnector }); + + const setActionProperty = (key: string, value: any) => { + dispatch({ command: { type: 'setProperty' }, payload: { key, value } }); + }; + + const setActionConfigProperty = (key: string, value: any) => { + dispatch({ command: { type: 'setConfigProperty' }, payload: { key, value } }); + }; + + const setActionSecretsProperty = (key: string, value: any) => { + dispatch({ command: { type: 'setSecretsProperty' }, payload: { key, value } }); + }; + + const [isSaving, setIsSaving] = useState(false); + const [serverError, setServerError] = useState<{ + body: { message: string; error: string }; + } | null>(null); + + const actionTypeRegistered = actionTypeRegistry.get(initialConnector.actionTypeId); + if (!actionTypeRegistered) return null; + + function validateBaseProperties(actionObject: ActionConnector) { + const validationResult = { errors: {} }; + const errors = { + name: new Array(), + }; + validationResult.errors = errors; + if (!actionObject.name) { + errors.name.push( + i18n.translate( + 'xpack.triggersActionsUI.sections.actionConnectorForm.error.requiredNameText', + { + defaultMessage: 'Name is required.', + } + ) + ); + } + return validationResult; + } + + const FieldsComponent = actionTypeRegistered.actionConnectorFields; + const errors = { + ...actionTypeRegistered.validateConnector(connector).errors, + ...validateBaseProperties(connector).errors, + } as IErrorObject; + const hasErrors = !!Object.keys(errors).find(errorKey => errors[errorKey].length >= 1); + + async function onActionConnectorSave(): Promise { + let message: string; + let savedConnector: ActionConnector | undefined; + let error; + if (connector.id === undefined) { + await createActionConnector({ http, connector }) + .then(res => { + savedConnector = res; + }) + .catch(errorRes => { + error = errorRes; + }); + + message = 'Created'; + } else { + await updateActionConnector({ http, connector, id: connector.id }) + .then(res => { + savedConnector = res; + }) + .catch(errorRes => { + error = errorRes; + }); + message = 'Updated'; + } + if (error) { + return { + error, + }; + } + toastNotifications.addSuccess( + i18n.translate( + 'xpack.triggersActionsUI.sections.actionConnectorForm.updateSuccessNotificationText', + { + defaultMessage: "{message} '{connectorName}'", + values: { + message, + connectorName: savedConnector ? savedConnector.name : '', + }, + } + ) + ); + return savedConnector; + } + + return ( + + + + + } + isInvalid={errors.name.length > 0 && connector.name !== undefined} + error={errors.name} + > + 0 && connector.name !== undefined} + name="name" + placeholder="Untitled" + data-test-subj="nameInput" + value={connector.name || ''} + onChange={e => { + setActionProperty('name', e.target.value); + }} + onBlur={() => { + if (!connector.name) { + setActionProperty('name', ''); + } + }} + /> + + + {FieldsComponent !== null ? ( + + {initialConnector.actionTypeId === null ? ( + + + +

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

+
+
+ +
+ ) : null} +
+ ) : null} +
+
+ + + + setFlyoutVisibility(false)}> + {i18n.translate( + 'xpack.triggersActionsUI.sections.actionConnectorForm.cancelButtonLabel', + { + defaultMessage: 'Cancel', + } + )} + + + {canSave ? ( + + { + setIsSaving(true); + const savedAction = await onActionConnectorSave(); + setIsSaving(false); + if (savedAction && savedAction.error) { + return setServerError(savedAction.error); + } + setFlyoutVisibility(false); + reloadConnectors(); + }} + > + + + + ) : null} + + +
+ ); +}; diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/action_connector_form/action_type_menu.test.tsx b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/action_connector_form/action_type_menu.test.tsx new file mode 100644 index 0000000000000..a9e2afb061720 --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/action_connector_form/action_type_menu.test.tsx @@ -0,0 +1,91 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import * as React from 'react'; +import { mountWithIntl } from 'test_utils/enzyme_helpers'; +import { coreMock } from '../../../../../../../../../src/core/public/mocks'; +import { ActionsConnectorsContextProvider } from '../../context/actions_connectors_context'; +import { actionTypeRegistryMock } from '../../action_type_registry.mock'; +import { ActionTypeMenu } from './action_type_menu'; +import { ValidationResult } from '../../../types'; +import { AppContextProvider } from '../../app_context'; +const actionTypeRegistry = actionTypeRegistryMock.create(); + +describe('connector_add_flyout', () => { + let deps: any; + + beforeAll(async () => { + const mockes = coreMock.createSetup(); + const [{ chrome, docLinks }] = await mockes.getStartServices(); + deps = { + chrome, + docLinks, + toastNotifications: mockes.notifications.toasts, + injectedMetadata: mockes.injectedMetadata, + http: mockes.http, + uiSettings: mockes.uiSettings, + legacy: { + capabilities: { + get() { + return { + actions: { + delete: true, + save: true, + show: true, + }, + }; + }, + } as any, + MANAGEMENT_BREADCRUMB: { set: () => {} } as any, + }, + actionTypeRegistry: actionTypeRegistry as any, + alertTypeRegistry: {} as any, + }; + }); + + it('renders action type menu with proper EuiCards for registered action types', () => { + const onActionTypeChange = jest.fn(); + const actionType = { + id: 'my-action-type', + iconClass: 'test', + selectMessage: 'test', + validateConnector: (): ValidationResult => { + return { errors: {} }; + }, + validateParams: (): ValidationResult => { + const validationResult = { errors: {} }; + return validationResult; + }, + actionConnectorFields: null, + actionParamsFields: null, + }; + actionTypeRegistry.get.mockReturnValueOnce(actionType); + + const wrapper = mountWithIntl( + + {}, + editFlyoutVisible: false, + setEditFlyoutVisibility: state => {}, + actionTypesIndex: { + 'first-action-type': { id: 'first-action-type', name: 'first' }, + 'second-action-type': { id: 'second-action-type', name: 'second' }, + }, + reloadConnectors: () => { + return new Promise(() => {}); + }, + }} + > + + + + ); + + expect(wrapper.find('[data-test-subj="first-action-type-card"]').exists()).toBeTruthy(); + expect(wrapper.find('[data-test-subj="second-action-type-card"]').exists()).toBeTruthy(); + }); +}); diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/action_connector_form/action_type_menu.tsx b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/action_connector_form/action_type_menu.tsx new file mode 100644 index 0000000000000..19373fda79b9e --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/action_connector_form/action_type_menu.tsx @@ -0,0 +1,83 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React, { Fragment } from 'react'; +import { + EuiFlexGroup, + EuiFlexItem, + EuiCard, + EuiIcon, + EuiFlexGrid, + EuiFlyoutBody, + EuiFlyoutFooter, + EuiButtonEmpty, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { ActionType } from '../../../types'; +import { useActionsConnectorsContext } from '../../context/actions_connectors_context'; +import { useAppDependencies } from '../../app_context'; + +interface Props { + onActionTypeChange: (actionType: ActionType) => void; +} + +export const ActionTypeMenu = ({ onActionTypeChange }: Props) => { + const { actionTypeRegistry } = useAppDependencies(); + const { actionTypesIndex, setAddFlyoutVisibility } = useActionsConnectorsContext(); + if (!actionTypesIndex) { + return null; + } + + const actionTypes = Object.entries(actionTypesIndex) + .filter(([index]) => actionTypeRegistry.has(index)) + .map(([index, actionType]) => { + const actionTypeModel = actionTypeRegistry.get(index); + return { + iconClass: actionTypeModel ? actionTypeModel.iconClass : '', + selectMessage: actionTypeModel ? actionTypeModel.selectMessage : '', + actionType, + name: actionType.name, + typeName: index.replace('.', ''), + }; + }); + + const cardNodes = actionTypes + .sort((a, b) => a.name.localeCompare(b.name)) + .map((item, index): any => { + return ( + + } + title={item.name} + description={item.selectMessage} + onClick={() => onActionTypeChange(item.actionType)} + /> + + ); + }); + + return ( + + + {cardNodes} + + + + + setAddFlyoutVisibility(false)}> + {i18n.translate( + 'xpack.triggersActionsUI.sections.actionConnectorForm.cancelButtonLabel', + { + defaultMessage: 'Cancel', + } + )} + + + + + + ); +}; diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/action_connector_form/connector_add_flyout.test.tsx b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/action_connector_form/connector_add_flyout.test.tsx new file mode 100644 index 0000000000000..5095cc140f9c9 --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/action_connector_form/connector_add_flyout.test.tsx @@ -0,0 +1,100 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import * as React from 'react'; +import { mountWithIntl } from 'test_utils/enzyme_helpers'; +import { coreMock } from '../../../../../../../../../src/core/public/mocks'; +import { ReactWrapper } from 'enzyme'; +import { act } from 'react-dom/test-utils'; +import { ConnectorAddFlyout } from './connector_add_flyout'; +import { ActionsConnectorsContextProvider } from '../../context/actions_connectors_context'; +import { actionTypeRegistryMock } from '../../action_type_registry.mock'; +import { ValidationResult } from '../../../types'; +import { AppContextProvider } from '../../app_context'; +const actionTypeRegistry = actionTypeRegistryMock.create(); + +describe('connector_add_flyout', () => { + let wrapper: ReactWrapper; + + beforeAll(async () => { + const mockes = coreMock.createSetup(); + const [{ chrome, docLinks }] = await mockes.getStartServices(); + const deps = { + chrome, + docLinks, + toastNotifications: mockes.notifications.toasts, + injectedMetadata: mockes.injectedMetadata, + http: mockes.http, + uiSettings: mockes.uiSettings, + legacy: { + capabilities: { + get() { + return { + actions: { + delete: true, + save: true, + show: true, + }, + }; + }, + } as any, + MANAGEMENT_BREADCRUMB: { set: () => {} } as any, + }, + actionTypeRegistry: actionTypeRegistry as any, + alertTypeRegistry: {} as any, + }; + + await act(async () => { + wrapper = mountWithIntl( + + {}, + editFlyoutVisible: false, + setEditFlyoutVisibility: state => {}, + actionTypesIndex: { 'my-action-type': { id: 'my-action-type', name: 'test' } }, + reloadConnectors: () => { + return new Promise(() => {}); + }, + }} + > + + + + ); + }); + + await waitForRender(wrapper); + }); + + it('renders action type menu on flyout open', () => { + const actionType = { + id: 'my-action-type', + iconClass: 'test', + selectMessage: 'test', + validateConnector: (): ValidationResult => { + return { errors: {} }; + }, + validateParams: (): ValidationResult => { + const validationResult = { errors: {} }; + return validationResult; + }, + actionConnectorFields: null, + actionParamsFields: null, + }; + actionTypeRegistry.get.mockReturnValueOnce(actionType); + actionTypeRegistry.has.mockReturnValue(true); + + expect(wrapper.find('ActionTypeMenu')).toHaveLength(1); + expect(wrapper.find('[data-test-subj="my-action-type-card"]').exists()).toBeTruthy(); + }); +}); + +async function waitForRender(wrapper: ReactWrapper) { + await Promise.resolve(); + await Promise.resolve(); + wrapper.update(); +} diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/action_connector_form/connector_add_flyout.tsx b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/action_connector_form/connector_add_flyout.tsx new file mode 100644 index 0000000000000..a3ec7ab4b3ab9 --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/action_connector_form/connector_add_flyout.tsx @@ -0,0 +1,104 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React, { useCallback, useState, Fragment } from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { + EuiTitle, + EuiFlyoutHeader, + EuiFlyout, + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiText, +} from '@elastic/eui'; +import { useActionsConnectorsContext } from '../../context/actions_connectors_context'; +import { ActionTypeMenu } from './action_type_menu'; +import { ActionConnectorForm } from './action_connector_form'; +import { ActionType, ActionConnector } from '../../../types'; +import { useAppDependencies } from '../../app_context'; + +export const ConnectorAddFlyout = () => { + const { actionTypeRegistry } = useAppDependencies(); + const { addFlyoutVisible, setAddFlyoutVisibility } = useActionsConnectorsContext(); + const [actionType, setActionType] = useState(undefined); + const closeFlyout = useCallback(() => { + setAddFlyoutVisibility(false); + setActionType(undefined); + }, [setAddFlyoutVisibility, setActionType]); + + if (!addFlyoutVisible) { + return null; + } + + function onActionTypeChange(newActionType: ActionType) { + setActionType(newActionType); + } + + let currentForm; + let actionTypeModel; + if (!actionType) { + currentForm = ; + } else { + actionTypeModel = actionTypeRegistry.get(actionType.id); + const initialConnector = { + actionTypeId: actionType.id, + config: {}, + secrets: {}, + } as ActionConnector; + + currentForm = ( + + ); + } + + return ( + + + + {actionTypeModel && actionTypeModel.iconClass ? ( + + + + ) : null} + + {actionTypeModel && actionType ? ( + + +

+ +

+
+ + {actionTypeModel.selectMessage} + +
+ ) : ( + +

+ +

+
+ )} +
+
+
+ {currentForm} +
+ ); +}; diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/action_connector_form/connector_edit_flyout.test.tsx b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/action_connector_form/connector_edit_flyout.test.tsx new file mode 100644 index 0000000000000..d01539d7232fa --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/action_connector_form/connector_edit_flyout.test.tsx @@ -0,0 +1,100 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import * as React from 'react'; +import { mountWithIntl } from 'test_utils/enzyme_helpers'; +import { coreMock } from '../../../../../../../../../src/core/public/mocks'; +import { ActionsConnectorsContextProvider } from '../../context/actions_connectors_context'; +import { actionTypeRegistryMock } from '../../action_type_registry.mock'; +import { ValidationResult } from '../../../types'; +import { ConnectorEditFlyout } from './connector_edit_flyout'; +import { AppContextProvider } from '../../app_context'; +const actionTypeRegistry = actionTypeRegistryMock.create(); +let deps: any; + +describe('connector_edit_flyout', () => { + beforeAll(async () => { + const mockes = coreMock.createSetup(); + const [{ chrome, docLinks }] = await mockes.getStartServices(); + deps = { + chrome, + docLinks, + toastNotifications: mockes.notifications.toasts, + injectedMetadata: mockes.injectedMetadata, + http: mockes.http, + uiSettings: mockes.uiSettings, + legacy: { + capabilities: { + get() { + return { + actions: { + delete: true, + save: true, + show: true, + }, + }; + }, + } as any, + MANAGEMENT_BREADCRUMB: { set: () => {} } as any, + }, + actionTypeRegistry: actionTypeRegistry as any, + alertTypeRegistry: {} as any, + }; + }); + + test('if input connector render correct in the edit form', () => { + const connector = { + secrets: {}, + id: 'test', + actionTypeId: 'test-action-type-id', + actionType: 'test-action-type-name', + name: 'action-connector', + referencedByCount: 0, + config: {}, + }; + + const actionType = { + id: 'test-action-type-id', + iconClass: 'test', + selectMessage: 'test', + validateConnector: (): ValidationResult => { + return { errors: {} }; + }, + validateParams: (): ValidationResult => { + const validationResult = { errors: {} }; + return validationResult; + }, + actionConnectorFields: null, + actionParamsFields: null, + }; + actionTypeRegistry.get.mockReturnValue(actionType); + actionTypeRegistry.has.mockReturnValue(true); + + const wrapper = mountWithIntl( + + {}, + editFlyoutVisible: true, + setEditFlyoutVisibility: state => {}, + actionTypesIndex: { + 'test-action-type-id': { id: 'test-action-type-id', name: 'test' }, + }, + reloadConnectors: () => { + return new Promise(() => {}); + }, + }} + > + + + + ); + + const connectorNameField = wrapper.find('[data-test-subj="nameInput"]'); + expect(connectorNameField.exists()).toBeTruthy(); + expect(connectorNameField.first().prop('value')).toBe('action-connector'); + }); +}); diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/action_connector_form/connector_edit_flyout.tsx b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/action_connector_form/connector_edit_flyout.tsx new file mode 100644 index 0000000000000..408989609d2ec --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/action_connector_form/connector_edit_flyout.tsx @@ -0,0 +1,68 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React, { useCallback } from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { + EuiTitle, + EuiFlyoutHeader, + EuiFlyout, + EuiFlexGroup, + EuiFlexItem, + EuiIcon, +} from '@elastic/eui'; +import { useActionsConnectorsContext } from '../../context/actions_connectors_context'; +import { ActionConnectorForm } from './action_connector_form'; +import { useAppDependencies } from '../../app_context'; +import { ActionConnectorTableItem } from '../../../types'; + +export interface ConnectorEditProps { + connector: ActionConnectorTableItem; +} + +export const ConnectorEditFlyout = ({ connector }: ConnectorEditProps) => { + const { actionTypeRegistry } = useAppDependencies(); + const { editFlyoutVisible, setEditFlyoutVisibility } = useActionsConnectorsContext(); + const closeFlyout = useCallback(() => setEditFlyoutVisibility(false), [setEditFlyoutVisibility]); + + if (!editFlyoutVisible) { + return null; + } + + const actionTypeModel = actionTypeRegistry.get(connector.actionTypeId); + + return ( + + + + {actionTypeModel ? ( + + + + ) : null} + + +

+ +

+
+
+
+
+ +
+ ); +}; diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/action_connector_form/connector_reducer.test.ts b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/action_connector_form/connector_reducer.test.ts new file mode 100644 index 0000000000000..df7e5d8fe9a78 --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/action_connector_form/connector_reducer.test.ts @@ -0,0 +1,91 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { connectorReducer } from './connector_reducer'; +import { ActionConnector } from '../../../types'; + +describe('connector reducer', () => { + let initialConnector: ActionConnector; + beforeAll(() => { + initialConnector = { + secrets: {}, + id: 'test', + actionTypeId: 'test-action-type-id', + name: 'action-connector', + referencedByCount: 0, + config: {}, + }; + }); + + test('if property name was changed', () => { + const updatedConnector = connectorReducer( + { connector: initialConnector }, + { + command: { type: 'setProperty' }, + payload: { + key: 'name', + value: 'new name', + }, + } + ); + expect(updatedConnector.connector.name).toBe('new name'); + }); + + test('if config property was added and updated', () => { + const updatedConnector = connectorReducer( + { connector: initialConnector }, + { + command: { type: 'setConfigProperty' }, + payload: { + key: 'testConfig', + value: 'new test config property', + }, + } + ); + expect(updatedConnector.connector.config.testConfig).toBe('new test config property'); + + const updatedConnectorUpdatedProperty = connectorReducer( + { connector: updatedConnector.connector }, + { + command: { type: 'setConfigProperty' }, + payload: { + key: 'testConfig', + value: 'test config property updated', + }, + } + ); + expect(updatedConnectorUpdatedProperty.connector.config.testConfig).toBe( + 'test config property updated' + ); + }); + + test('if secrets property was added', () => { + const updatedConnector = connectorReducer( + { connector: initialConnector }, + { + command: { type: 'setSecretsProperty' }, + payload: { + key: 'testSecret', + value: 'new test secret property', + }, + } + ); + expect(updatedConnector.connector.secrets.testSecret).toBe('new test secret property'); + + const updatedConnectorUpdatedProperty = connectorReducer( + { connector: updatedConnector.connector }, + { + command: { type: 'setSecretsProperty' }, + payload: { + key: 'testSecret', + value: 'test secret property updated', + }, + } + ); + expect(updatedConnectorUpdatedProperty.connector.secrets.testSecret).toBe( + 'test secret property updated' + ); + }); +}); diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/action_connector_form/connector_reducer.ts b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/action_connector_form/connector_reducer.ts new file mode 100644 index 0000000000000..4a2610f965735 --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/action_connector_form/connector_reducer.ts @@ -0,0 +1,78 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { isEqual } from 'lodash'; + +interface CommandType { + type: 'setProperty' | 'setConfigProperty' | 'setSecretsProperty'; +} + +export interface ActionState { + connector: any; +} + +export interface ReducerAction { + command: CommandType; + payload: { + key: string; + value: any; + }; +} + +export const connectorReducer = (state: ActionState, action: ReducerAction) => { + const { command, payload } = action; + const { connector } = state; + + switch (command.type) { + case 'setProperty': { + const { key, value } = payload; + if (isEqual(connector[key], value)) { + return state; + } else { + return { + ...state, + connector: { + ...connector, + [key]: value, + }, + }; + } + } + case 'setConfigProperty': { + const { key, value } = payload; + if (isEqual(connector.config[key], value)) { + return state; + } else { + return { + ...state, + connector: { + ...connector, + config: { + ...connector.config, + [key]: value, + }, + }, + }; + } + } + case 'setSecretsProperty': { + const { key, value } = payload; + if (isEqual(connector.secrets[key], value)) { + return state; + } else { + return { + ...state, + connector: { + ...connector, + secrets: { + ...connector.secrets, + [key]: value, + }, + }, + }; + } + } + } +}; diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/action_connector_form/index.ts b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/action_connector_form/index.ts new file mode 100644 index 0000000000000..aac7a514948d1 --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/action_connector_form/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { ConnectorAddFlyout } from './connector_add_flyout'; +export { ConnectorEditFlyout } from './connector_edit_flyout'; diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/actions_connectors_list/components/_index.scss b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/actions_connectors_list/components/_index.scss new file mode 100644 index 0000000000000..98c6c2a307a74 --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/actions_connectors_list/components/_index.scss @@ -0,0 +1 @@ +@import 'actions_connectors_list'; diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/actions_connectors_list/components/actions_connectors_list.scss b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/actions_connectors_list/components/actions_connectors_list.scss new file mode 100644 index 0000000000000..7a824aaeaa8d8 --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/actions_connectors_list/components/actions_connectors_list.scss @@ -0,0 +1,3 @@ +.actConnectorsList__logo + .actConnectorsList__logo { + margin-left: $euiSize; +} diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/actions_connectors_list/components/actions_connectors_list.test.tsx b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/actions_connectors_list/components/actions_connectors_list.test.tsx new file mode 100644 index 0000000000000..511deb8cf3b0d --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/actions_connectors_list/components/actions_connectors_list.test.tsx @@ -0,0 +1,362 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import * as React from 'react'; +import { mountWithIntl } from 'test_utils/enzyme_helpers'; +import { ActionsConnectorsList } from './actions_connectors_list'; +import { coreMock } from '../../../../../../../../../../src/core/public/mocks'; +import { ReactWrapper } from 'enzyme'; +import { act } from 'react-dom/test-utils'; +import { actionTypeRegistryMock } from '../../../action_type_registry.mock'; +import { AppContextProvider } from '../../../app_context'; +jest.mock('../../../lib/action_connector_api', () => ({ + loadAllActions: jest.fn(), + loadActionTypes: jest.fn(), +})); + +const actionTypeRegistry = actionTypeRegistryMock.create(); + +describe('actions_connectors_list component empty', () => { + let wrapper: ReactWrapper; + + beforeAll(async () => { + const { loadAllActions, loadActionTypes } = jest.requireMock( + '../../../lib/action_connector_api' + ); + loadAllActions.mockResolvedValueOnce({ + page: 1, + perPage: 10000, + total: 0, + data: [], + }); + loadActionTypes.mockResolvedValueOnce([ + { + id: 'test', + name: 'Test', + }, + { + id: 'test2', + name: 'Test2', + }, + ]); + const mockes = coreMock.createSetup(); + const [{ chrome, docLinks }] = await mockes.getStartServices(); + const deps = { + chrome, + docLinks, + toastNotifications: mockes.notifications.toasts, + injectedMetadata: mockes.injectedMetadata, + http: mockes.http, + uiSettings: mockes.uiSettings, + legacy: { + capabilities: { + get() { + return { + siem: { + 'actions:show': true, + 'actions:save': true, + 'actions:delete': true, + }, + }; + }, + } as any, + MANAGEMENT_BREADCRUMB: { set: () => {} } as any, + }, + actionTypeRegistry: actionTypeRegistry as any, + alertTypeRegistry: {} as any, + }; + actionTypeRegistry.has.mockReturnValue(true); + + await act(async () => { + wrapper = mountWithIntl( + + + + ); + }); + + await waitForRender(wrapper); + }); + + it('renders empty prompt', () => { + expect(wrapper.find('EuiEmptyPrompt')).toHaveLength(1); + expect( + wrapper.find('[data-test-subj="createFirstActionButton"]').find('EuiButton') + ).toHaveLength(1); + }); + + test('if click create button should render ConnectorAddFlyout', () => { + wrapper + .find('[data-test-subj="createFirstActionButton"]') + .first() + .simulate('click'); + expect(wrapper.find('ConnectorAddFlyout')).toHaveLength(1); + }); +}); + +describe('actions_connectors_list component with items', () => { + let wrapper: ReactWrapper; + + beforeAll(async () => { + const { loadAllActions, loadActionTypes } = jest.requireMock( + '../../../lib/action_connector_api' + ); + loadAllActions.mockResolvedValueOnce({ + page: 1, + perPage: 10000, + total: 2, + data: [ + { + id: '1', + actionTypeId: 'test', + description: 'My test', + referencedByCount: 1, + config: {}, + }, + { + id: '2', + actionTypeId: 'test2', + description: 'My test 2', + referencedByCount: 1, + config: {}, + }, + ], + }); + loadActionTypes.mockResolvedValueOnce([ + { + id: 'test', + name: 'Test', + }, + { + id: 'test2', + name: 'Test2', + }, + ]); + + const mockes = coreMock.createSetup(); + const [{ chrome, docLinks }] = await mockes.getStartServices(); + const deps = { + chrome, + docLinks, + toastNotifications: mockes.notifications.toasts, + injectedMetadata: mockes.injectedMetadata, + http: mockes.http, + uiSettings: mockes.uiSettings, + legacy: { + capabilities: { + get() { + return { + siem: { + 'actions:show': true, + 'actions:save': true, + 'actions:delete': true, + }, + }; + }, + } as any, + MANAGEMENT_BREADCRUMB: { set: () => {} } as any, + }, + actionTypeRegistry: { + get() { + return null; + }, + } as any, + alertTypeRegistry: {} as any, + }; + + await act(async () => { + wrapper = mountWithIntl( + + + + ); + }); + + await waitForRender(wrapper); + + expect(loadAllActions).toHaveBeenCalled(); + }); + + it('renders table of connectors', () => { + expect(wrapper.find('EuiInMemoryTable')).toHaveLength(1); + expect(wrapper.find('EuiTableRow')).toHaveLength(2); + }); + + test('if select item for edit should render ConnectorEditFlyout', () => { + wrapper + .find('[data-test-subj="edit1"]') + .first() + .simulate('click'); + expect(wrapper.find('ConnectorEditFlyout')).toHaveLength(1); + }); +}); + +describe('actions_connectors_list component empty with show only capability', () => { + let wrapper: ReactWrapper; + + beforeAll(async () => { + const { loadAllActions, loadActionTypes } = jest.requireMock( + '../../../lib/action_connector_api' + ); + loadAllActions.mockResolvedValueOnce({ + page: 1, + perPage: 10000, + total: 0, + data: [], + }); + loadActionTypes.mockResolvedValueOnce([ + { + id: 'test', + name: 'Test', + }, + { + id: 'test2', + name: 'Test2', + }, + ]); + const mockes = coreMock.createSetup(); + const [{ chrome, docLinks }] = await mockes.getStartServices(); + const deps = { + chrome, + docLinks, + toastNotifications: mockes.notifications.toasts, + injectedMetadata: mockes.injectedMetadata, + http: mockes.http, + uiSettings: mockes.uiSettings, + legacy: { + capabilities: { + get() { + return { + siem: { + 'actions:show': true, + 'actions:save': false, + 'actions:delete': false, + }, + }; + }, + } as any, + MANAGEMENT_BREADCRUMB: { set: () => {} } as any, + }, + actionTypeRegistry: { + get() { + return null; + }, + } as any, + alertTypeRegistry: {} as any, + }; + + await act(async () => { + wrapper = mountWithIntl( + + + + ); + }); + + await waitForRender(wrapper); + }); + + it('renders no permissions to create connector', () => { + expect(wrapper.find('[defaultMessage="No permissions to create connector"]')).toHaveLength(1); + expect(wrapper.find('[data-test-subj="createActionButton"]')).toHaveLength(0); + }); +}); + +describe('actions_connectors_list with show only capability', () => { + let wrapper: ReactWrapper; + + beforeAll(async () => { + const { loadAllActions, loadActionTypes } = jest.requireMock( + '../../../lib/action_connector_api' + ); + loadAllActions.mockResolvedValueOnce({ + page: 1, + perPage: 10000, + total: 2, + data: [ + { + id: '1', + actionTypeId: 'test', + description: 'My test', + referencedByCount: 1, + config: {}, + }, + { + id: '2', + actionTypeId: 'test2', + description: 'My test 2', + referencedByCount: 1, + config: {}, + }, + ], + }); + loadActionTypes.mockResolvedValueOnce([ + { + id: 'test', + name: 'Test', + }, + { + id: 'test2', + name: 'Test2', + }, + ]); + const mockes = coreMock.createSetup(); + const [{ chrome, docLinks }] = await mockes.getStartServices(); + const deps = { + chrome, + docLinks, + toastNotifications: mockes.notifications.toasts, + injectedMetadata: mockes.injectedMetadata, + http: mockes.http, + uiSettings: mockes.uiSettings, + legacy: { + capabilities: { + get() { + return { + siem: { + 'actions:show': true, + 'actions:save': false, + 'actions:delete': false, + }, + }; + }, + } as any, + MANAGEMENT_BREADCRUMB: { set: () => {} } as any, + }, + actionTypeRegistry: { + get() { + return null; + }, + } as any, + alertTypeRegistry: {} as any, + }; + + await act(async () => { + wrapper = mountWithIntl( + + + + ); + }); + + await waitForRender(wrapper); + }); + + it('renders table of connectors with delete button disabled', () => { + expect(wrapper.find('EuiInMemoryTable')).toHaveLength(1); + expect(wrapper.find('EuiTableRow')).toHaveLength(2); + wrapper.find('EuiTableRow').forEach(elem => { + const deleteButton = elem.find('[data-test-subj="deleteConnector"]').first(); + expect(deleteButton).toBeTruthy(); + expect(deleteButton.prop('isDisabled')).toBeTruthy(); + }); + }); +}); + +async function waitForRender(wrapper: ReactWrapper) { + await Promise.resolve(); + await Promise.resolve(); + wrapper.update(); +} diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx new file mode 100644 index 0000000000000..1990ffefdf84e --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/actions_connectors_list/components/actions_connectors_list.tsx @@ -0,0 +1,399 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { Fragment, useState, useEffect } from 'react'; +import { + EuiBadge, + EuiInMemoryTable, + EuiSpacer, + EuiButton, + EuiIcon, + EuiEmptyPrompt, + EuiTitle, + EuiLink, +} from '@elastic/eui'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { ActionsConnectorsContextProvider } from '../../../context/actions_connectors_context'; +import { useAppDependencies } from '../../../app_context'; +import { loadAllActions, loadActionTypes } from '../../../lib/action_connector_api'; +import { ActionConnector, ActionConnectorTableItem, ActionTypeIndex } from '../../../../types'; +import { ConnectorAddFlyout, ConnectorEditFlyout } from '../../action_connector_form'; +import { hasDeleteActionsCapability, hasSaveActionsCapability } from '../../../lib/capabilities'; +import { DeleteConnectorsModal } from '../../../components/delete_connectors_modal'; + +export const ActionsConnectorsList: React.FunctionComponent = () => { + const { + http, + toastNotifications, + legacy: { capabilities }, + } = useAppDependencies(); + const canDelete = hasDeleteActionsCapability(capabilities.get()); + const canSave = hasSaveActionsCapability(capabilities.get()); + + const [actionTypesIndex, setActionTypesIndex] = useState(undefined); + const [actions, setActions] = useState([]); + const [data, setData] = useState([]); + const [selectedItems, setSelectedItems] = useState([]); + const [isLoadingActionTypes, setIsLoadingActionTypes] = useState(false); + const [isLoadingActions, setIsLoadingActions] = useState(false); + const [editFlyoutVisible, setEditFlyoutVisibility] = useState(false); + const [addFlyoutVisible, setAddFlyoutVisibility] = useState(false); + const [actionTypesList, setActionTypesList] = useState>( + [] + ); + const [editedConnectorItem, setEditedConnectorItem] = useState< + ActionConnectorTableItem | undefined + >(undefined); + const [connectorsToDelete, setConnectorsToDelete] = useState([]); + + useEffect(() => { + loadActions(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + useEffect(() => { + (async () => { + try { + setIsLoadingActionTypes(true); + const actionTypes = await loadActionTypes({ http }); + const index: ActionTypeIndex = {}; + for (const actionTypeItem of actionTypes) { + index[actionTypeItem.id] = actionTypeItem; + } + setActionTypesIndex(index); + } catch (e) { + toastNotifications.addDanger({ + title: i18n.translate( + 'xpack.triggersActionsUI.sections.actionsConnectorsList.unableToLoadActionTypesMessage', + { defaultMessage: 'Unable to load action types' } + ), + }); + } finally { + setIsLoadingActionTypes(false); + } + })(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + useEffect(() => { + // Avoid flickering before action types load + if (typeof actionTypesIndex === 'undefined') { + return; + } + // Update the data for the table + const updatedData = actions.map(action => { + return { + ...action, + actionType: actionTypesIndex[action.actionTypeId] + ? actionTypesIndex[action.actionTypeId].name + : action.actionTypeId, + }; + }); + setData(updatedData); + // Update the action types list for the filter + const actionTypes = Object.values(actionTypesIndex) + .map(actionType => ({ + value: actionType.id, + name: `${actionType.name} (${getActionsCountByActionType(actions, actionType.id)})`, + })) + .sort((a, b) => a.name.localeCompare(b.name)); + setActionTypesList(actionTypes); + }, [actions, actionTypesIndex]); + + async function loadActions() { + setIsLoadingActions(true); + try { + const actionsResponse = await loadAllActions({ http }); + setActions(actionsResponse.data); + } catch (e) { + toastNotifications.addDanger({ + title: i18n.translate( + 'xpack.triggersActionsUI.sections.actionsConnectorsList.unableToLoadActionsMessage', + { + defaultMessage: 'Unable to load actions', + } + ), + }); + } finally { + setIsLoadingActions(false); + } + } + + async function editItem(connectorTableItem: ActionConnectorTableItem) { + setEditedConnectorItem(connectorTableItem); + setEditFlyoutVisibility(true); + } + + const actionsTableColumns = [ + { + field: 'name', + 'data-test-subj': 'connectorsTableCell-name', + name: i18n.translate( + 'xpack.triggersActionsUI.sections.actionsConnectorsList.connectorsListTable.columns.nameTitle', + { + defaultMessage: 'Name', + } + ), + sortable: false, + truncateText: true, + render: (value: string, item: ActionConnectorTableItem) => { + return ( + editItem(item)} key={item.id}> + {value} + + ); + }, + }, + { + field: 'actionType', + 'data-test-subj': 'connectorsTableCell-actionType', + name: i18n.translate( + 'xpack.triggersActionsUI.sections.actionsConnectorsList.connectorsListTable.columns.actionTypeTitle', + { + defaultMessage: 'Type', + } + ), + sortable: false, + truncateText: true, + }, + { + field: 'referencedByCount', + 'data-test-subj': 'connectorsTableCell-referencedByCount', + name: i18n.translate( + 'xpack.triggersActionsUI.sections.actionsConnectorsList.connectorsListTable.columns.referencedByCountTitle', + { defaultMessage: 'Actions' } + ), + sortable: false, + truncateText: true, + render: (value: number, item: ActionConnectorTableItem) => { + return ( + + {value} + + ); + }, + }, + { + field: '', + name: '', + actions: [ + { + enabled: () => canDelete, + 'data-test-subj': 'deleteConnector', + name: i18n.translate( + 'xpack.triggersActionsUI.sections.actionsConnectorsList.connectorsListTable.columns.actions.deleteActionName', + { defaultMessage: 'Delete' } + ), + description: canDelete + ? i18n.translate( + 'xpack.triggersActionsUI.sections.actionsConnectorsList.connectorsListTable.columns.actions.deleteActionDescription', + { defaultMessage: 'Delete this action' } + ) + : i18n.translate( + 'xpack.triggersActionsUI.sections.actionsConnectorsList.connectorsListTable.columns.actions.deleteActionDisabledDescription', + { defaultMessage: 'Unable to delete actions' } + ), + type: 'icon', + icon: 'trash', + color: 'danger', + onClick: (item: ActionConnectorTableItem) => setConnectorsToDelete([item.id]), + }, + ], + }, + ]; + + const table = ( + ({ + 'data-test-subj': 'connectors-row', + })} + cellProps={() => ({ + 'data-test-subj': 'cell', + })} + data-test-subj="actionsTable" + pagination={true} + selection={ + canDelete + ? { + onSelectionChange(updatedSelectedItemsList: ActionConnectorTableItem[]) { + setSelectedItems(updatedSelectedItemsList); + }, + } + : undefined + } + search={{ + filters: [ + { + type: 'field_value_selection', + field: 'actionTypeId', + name: i18n.translate( + 'xpack.triggersActionsUI.sections.actionsConnectorsList.filters.actionTypeIdName', + { defaultMessage: 'Type' } + ), + multiSelect: 'or', + options: actionTypesList, + }, + ], + toolsLeft: + selectedItems.length === 0 || !canDelete + ? [] + : [ + { + setConnectorsToDelete(selectedItems.map((selected: any) => selected.id)); + }} + title={ + canDelete + ? undefined + : i18n.translate( + 'xpack.triggersActionsUI.sections.actionsConnectorsList.buttons.deleteDisabledTitle', + { defaultMessage: 'Unable to delete actions' } + ) + } + > + + , + ], + toolsRight: [ + setAddFlyoutVisibility(true)} + > + + , + ], + }} + /> + ); + + const emptyPrompt = ( + + + + + + +

+ +

+
+ + } + body={ +

+ +

+ } + actions={ + setAddFlyoutVisibility(true)} + > + + + } + /> + ); + + const noPermissionPrompt = ( +

+ +

+ ); + + return ( +
+ { + if (deleted) { + if (selectedItems.length === 0 || selectedItems.length === deleted.length) { + const updatedActions = actions.filter( + action => action.id && !connectorsToDelete.includes(action.id) + ); + setActions(updatedActions); + setSelectedItems([]); + } else { + toastNotifications.addDanger({ + title: i18n.translate( + 'xpack.triggersActionsUI.sections.actionsConnectorsList.failedToDeleteActionsMessage', + { defaultMessage: 'Failed to delete action(s)' } + ), + }); + // Refresh the actions from the server, some actions may have beend deleted + loadActions(); + } + } + setConnectorsToDelete([]); + }} + connectorsToDelete={connectorsToDelete} + /> + + {/* Render the view based on if there's data or if they can save */} + {data.length !== 0 && table} + {data.length === 0 && canSave && emptyPrompt} + {data.length === 0 && !canSave && noPermissionPrompt} + + + {editedConnectorItem ? : null} + +
+ ); +}; + +function getActionsCountByActionType(actions: ActionConnector[], actionTypeId: string) { + return actions.filter(action => action.actionTypeId === actionTypeId).length; +} diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/alert_add/alert_add.tsx b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/alert_add/alert_add.tsx new file mode 100644 index 0000000000000..9380392112c8e --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/alert_add/alert_add.tsx @@ -0,0 +1,803 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import React, { Fragment, useState, useCallback, useReducer, useEffect } from 'react'; +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { + EuiButton, + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiTitle, + EuiForm, + EuiSpacer, + EuiButtonEmpty, + EuiFlyoutFooter, + EuiFlyoutBody, + EuiFlyoutHeader, + EuiFlyout, + EuiFieldText, + EuiFlexGrid, + EuiFormRow, + EuiComboBox, + EuiKeyPadMenuItem, + EuiTabs, + EuiTab, + EuiLink, + EuiFieldNumber, + EuiSelect, + EuiIconTip, + EuiPortal, + EuiAccordion, + EuiButtonIcon, +} from '@elastic/eui'; +import { useAppDependencies } from '../../app_context'; +import { createAlert } from '../../lib/alert_api'; +import { loadActionTypes, loadAllActions } from '../../lib/action_connector_api'; +import { useAlertsContext } from '../../context/alerts_context'; +import { alertReducer } from './alert_reducer'; +import { + AlertTypeModel, + Alert, + IErrorObject, + ActionTypeModel, + AlertAction, + ActionTypeIndex, + ActionConnector, +} from '../../../types'; +import { ACTION_GROUPS } from '../../constants/action_groups'; +import { getTimeOptions } from '../../lib/get_time_options'; +import { SectionLoading } from '../../components/section_loading'; + +interface Props { + refreshList: () => Promise; +} + +function validateBaseProperties(alertObject: Alert) { + const validationResult = { errors: {} }; + const errors = { + name: new Array(), + interval: new Array(), + alertTypeId: new Array(), + actionConnectors: new Array(), + }; + validationResult.errors = errors; + if (!alertObject.name) { + errors.name.push( + i18n.translate('xpack.triggersActionsUI.sections.alertAdd.error.requiredNameText', { + defaultMessage: 'Name is required.', + }) + ); + } + if (!alertObject.interval) { + errors.interval.push( + i18n.translate('xpack.triggersActionsUI.sections.alertAdd.error.requiredIntervalText', { + defaultMessage: 'Check interval is required.', + }) + ); + } + if (!alertObject.alertTypeId) { + errors.alertTypeId.push( + i18n.translate('xpack.triggersActionsUI.sections.alertAdd.error.requiredAlertTypeIdText', { + defaultMessage: 'Alert trigger is required.', + }) + ); + } + return validationResult; +} + +export const AlertAdd = ({ refreshList }: Props) => { + const { http, toastNotifications, alertTypeRegistry, actionTypeRegistry } = useAppDependencies(); + const initialAlert = { + params: {}, + alertTypeId: null, + interval: '1m', + actions: [], + tags: [], + }; + + const { alertFlyoutVisible, setAlertFlyoutVisibility } = useAlertsContext(); + // hooks + const [alertType, setAlertType] = useState(undefined); + const [{ alert }, dispatch] = useReducer(alertReducer, { alert: initialAlert }); + const [isSaving, setIsSaving] = useState(false); + const [isLoadingActionTypes, setIsLoadingActionTypes] = useState(false); + const [selectedTabId, setSelectedTabId] = useState('alert'); + const [actionTypesIndex, setActionTypesIndex] = useState(undefined); + const [alertInterval, setAlertInterval] = useState(null); + const [alertIntervalUnit, setAlertIntervalUnit] = useState('m'); + const [alertThrottle, setAlertThrottle] = useState(null); + const [alertThrottleUnit, setAlertThrottleUnit] = useState(''); + const [serverError, setServerError] = useState<{ + body: { message: string; error: string }; + } | null>(null); + const [isAddActionPanelOpen, setIsAddActionPanelOpen] = useState(true); + const [connectors, setConnectors] = useState([]); + + useEffect(() => { + (async () => { + try { + setIsLoadingActionTypes(true); + const actionTypes = await loadActionTypes({ http }); + const index: ActionTypeIndex = {}; + for (const actionTypeItem of actionTypes) { + index[actionTypeItem.id] = actionTypeItem; + } + setActionTypesIndex(index); + } catch (e) { + toastNotifications.addDanger({ + title: i18n.translate( + 'xpack.triggersActionsUI.sections.alertAdd.unableToLoadActionTypesMessage', + { defaultMessage: 'Unable to load action types' } + ), + }); + } finally { + setIsLoadingActionTypes(false); + } + })(); + }, [toastNotifications, http]); + + useEffect(() => { + dispatch({ + command: { type: 'setAlert' }, + payload: { + key: 'alert', + value: { + params: {}, + alertTypeId: null, + interval: '1m', + actions: [], + tags: [], + }, + }, + }); + }, [alertFlyoutVisible]); + + useEffect(() => { + loadConnectors(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [alertFlyoutVisible]); + + const setAlertProperty = (key: string, value: any) => { + dispatch({ command: { type: 'setProperty' }, payload: { key, value } }); + }; + + const setAlertParams = (key: string, value: any) => { + dispatch({ command: { type: 'setAlertParams' }, payload: { key, value } }); + }; + + const setActionParamsProperty = (key: string, value: any, index: number) => { + dispatch({ command: { type: 'setAlertActionParams' }, payload: { key, value, index } }); + }; + + const setActionProperty = (key: string, value: any, index: number) => { + dispatch({ command: { type: 'setAlertActionProperty' }, payload: { key, value, index } }); + }; + + const closeFlyout = useCallback(() => { + setAlertFlyoutVisibility(false); + setAlertType(undefined); + setIsAddActionPanelOpen(true); + setSelectedTabId('alert'); + setServerError(null); + }, [setAlertFlyoutVisibility]); + + if (!alertFlyoutVisible) { + return null; + } + + const tagsOptions = alert.tags ? alert.tags.map((label: string) => ({ label })) : []; + + async function loadConnectors() { + try { + const actionsResponse = await loadAllActions({ http }); + setConnectors(actionsResponse.data); + } catch (e) { + toastNotifications.addDanger({ + title: i18n.translate( + 'xpack.triggersActionsUI.sections.alertAdd.unableToLoadActionsMessage', + { + defaultMessage: 'Unable to load connectors', + } + ), + }); + } + } + + const AlertParamsExpressionComponent = alertType ? alertType.alertParamsExpression : null; + + const errors = { + ...(alertType ? alertType.validate(alert).errors : []), + ...validateBaseProperties(alert).errors, + } as IErrorObject; + const hasErrors = !!Object.keys(errors).find(errorKey => errors[errorKey].length >= 1); + + const actionErrors = alert.actions.reduce((acc: any, alertAction: AlertAction) => { + const actionTypeConnectors = connectors.find(field => field.id === alertAction.id); + if (!actionTypeConnectors) { + return []; + } + const actionType = actionTypeRegistry.get(actionTypeConnectors.actionTypeId); + if (!actionType) { + return []; + } + const actionValidationErrors = actionType.validateParams(alertAction.params); + acc[alertAction.id] = actionValidationErrors; + return acc; + }, {}); + + const hasActionErrors = !!Object.keys(actionErrors).find(actionError => { + return !!Object.keys(actionErrors[actionError]).find((actionErrorKey: string) => { + return actionErrors[actionError][actionErrorKey].length >= 1; + }); + }); + + const tabs = [ + { + id: ACTION_GROUPS.ALERT, + name: i18n.translate('xpack.triggersActionsUI.sections.alertAdd.alertTabText', { + defaultMessage: 'Alert', + }), + }, + { + id: ACTION_GROUPS.WARNING, + name: i18n.translate('xpack.triggersActionsUI.sections.alertAdd.warningTabText', { + defaultMessage: 'Warning', + }), + }, + { + id: ACTION_GROUPS.UNACKNOWLEDGED, + name: i18n.translate('xpack.triggersActionsUI.sections.alertAdd.unacknowledgedTabText', { + defaultMessage: 'If unacknowledged', + }), + disabled: false, + }, + ]; + + async function onSaveAlert(): Promise { + try { + const newAlert = await createAlert({ http, alert }); + toastNotifications.addSuccess( + i18n.translate('xpack.triggersActionsUI.sections.alertAdd.saveSuccessNotificationText', { + defaultMessage: "Saved '{alertName}'", + values: { + alertName: newAlert.id, + }, + }) + ); + return newAlert; + } catch (error) { + return { + error, + }; + } + } + + function addActionType(actionTypeModel: ActionTypeModel) { + setIsAddActionPanelOpen(false); + const actionTypeConnectors = connectors.filter( + field => field.actionTypeId === actionTypeModel.id + ); + if (actionTypeConnectors.length > 0) { + alert.actions.push({ id: actionTypeConnectors[0].id, group: selectedTabId, params: {} }); + } + } + + const alertTypeNodes = alertTypeRegistry.list().map(function(item, index) { + return ( + { + setAlertProperty('alertTypeId', item.id); + setAlertType(item); + }} + > + + + ); + }); + + const actionTypeNodes = actionTypeRegistry.list().map(function(item, index) { + return ( + addActionType(item)} + > + + + ); + }); + + const alertTabs = tabs.map(function(tab, index): any { + return ( + { + setSelectedTabId(tab.id); + if (!alert.actions.find((action: AlertAction) => action.group === tab.id)) { + setIsAddActionPanelOpen(true); + } else { + setIsAddActionPanelOpen(false); + } + }} + isSelected={tab.id === selectedTabId} + disabled={tab.disabled} + key={index} + > + {tab.name} + + ); + }); + + const alertTypeDetails = ( + + + + +
+ +
+
+
+ + { + setAlertProperty('alertTypeId', null); + setAlertType(undefined); + }} + > + + + +
+ {AlertParamsExpressionComponent ? ( + + ) : null} +
+ ); + + const getSelectedOptions = (actionItemId: string) => { + const val = connectors.find(connector => connector.id === actionItemId); + if (!val) { + return []; + } + return [ + { + label: val.name, + value: val.name, + id: actionItemId, + }, + ]; + }; + + const actionsListForGroup = ( + + {alert.actions.map((actionItem: AlertAction, index: number) => { + const actionConnector = connectors.find(field => field.id === actionItem.id); + if (!actionConnector) { + return null; + } + const optionsList = connectors + .filter(field => field.actionTypeId === actionConnector.actionTypeId) + .map(({ name, id }) => ({ + label: name, + key: id, + id, + })); + const actionTypeRegisterd = actionTypeRegistry.get(actionConnector.actionTypeId); + if (actionTypeRegisterd === null || actionItem.group !== selectedTabId) return null; + const ParamsFieldsComponent = actionTypeRegisterd.actionParamsFields; + const actionParamsErrors = + Object.keys(actionErrors).length > 0 ? actionErrors[actionItem.id] : []; + const hasActionParamsErrors = !!Object.keys(actionParamsErrors).find( + errorKey => actionParamsErrors[errorKey].length >= 1 + ); + return ( + + + + + + +
+ +
+
+
+
+ } + extraAction={ + { + const updatedActions = alert.actions.filter( + (item: AlertAction) => item.id !== actionItem.id + ); + setAlertProperty('actions', updatedActions); + }} + /> + } + paddingSize="l" + > + + } + // errorKey="name" + // isShowingErrors={hasErrors} + // errors={errors} + > + { + setActionProperty('id', selectedOptions[0].id, index); + }} + isClearable={false} + /> + + + {ParamsFieldsComponent ? ( + + ) : null} + + ); + })} + + {!isAddActionPanelOpen ? ( + setIsAddActionPanelOpen(true)} + > + + + ) : null} + + ); + + let alertTypeArea; + if (alertType) { + alertTypeArea = {alertTypeDetails}; + } else { + alertTypeArea = ( + + +
+ +
+
+ + + {alertTypeNodes} + +
+ ); + } + + const labelForAlertChecked = ( + <> + {' '} + + + ); + + const labelForAlertRenotify = ( + <> + {' '} + + + ); + + return ( + + + + +

+ +

+
+
+ + + + + + } + isInvalid={hasErrors && alert.name !== undefined} + error={errors.name} + > + { + setAlertProperty('name', e.target.value); + }} + onBlur={() => { + if (!alert.name) { + setAlertProperty('name', ''); + } + }} + /> + + + + + { + const newOptions = [...tagsOptions, { label: searchValue }]; + setAlertProperty( + 'tags', + newOptions.map(newOption => newOption.label) + ); + }} + onChange={(selectedOptions: Array<{ label: string }>) => { + setAlertProperty( + 'tags', + selectedOptions.map(selectedOption => selectedOption.label) + ); + }} + onBlur={() => { + if (!alert.tags) { + setAlertProperty('tags', []); + } + }} + /> + + + + + + + + + + { + const interval = + e.target.value !== '' ? parseInt(e.target.value, 10) : null; + setAlertInterval(interval); + setAlertProperty('interval', `${e.target.value}${alertIntervalUnit}`); + }} + /> + + + { + setAlertIntervalUnit(e.target.value); + setAlertProperty('interval', `${alertInterval}${e.target.value}`); + }} + /> + + + + + + + + + { + const throttle = + e.target.value !== '' ? parseInt(e.target.value, 10) : null; + setAlertThrottle(throttle); + setAlertProperty('throttle', `${e.target.value}${alertThrottleUnit}`); + }} + /> + + + { + setAlertThrottleUnit(e.target.value); + setAlertProperty('throttle', `${alertThrottle}${e.target.value}`); + }} + /> + + + + + + + {alertTabs} + + {alertTypeArea} + + {actionsListForGroup} + {isAddActionPanelOpen ? ( + + +
+ +
+
+ + + {isLoadingActionTypes ? ( + + + + ) : ( + actionTypeNodes + )} + +
+ ) : null} +
+
+ + + + + {i18n.translate('xpack.triggersActionsUI.sections.alertAdd.cancelButtonLabel', { + defaultMessage: 'Cancel', + })} + + + + { + setIsSaving(true); + const savedAlert = await onSaveAlert(); + setIsSaving(false); + if (savedAlert && savedAlert.error) { + return setServerError(savedAlert.error); + } + closeFlyout(); + refreshList(); + }} + > + + + + + +
+
+ ); +}; diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/alert_add/alert_reducer.ts b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/alert_add/alert_reducer.ts new file mode 100644 index 0000000000000..9c2260f0178be --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/alert_add/alert_reducer.ts @@ -0,0 +1,121 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { isEqual } from 'lodash'; + +interface CommandType { + type: + | 'setAlert' + | 'setProperty' + | 'setAlertParams' + | 'setAlertActionParams' + | 'setAlertActionProperty'; +} + +export interface AlertState { + alert: any; +} + +export interface AlertReducerAction { + command: CommandType; + payload: { + key: string; + value: {}; + index?: number; + }; +} + +export const alertReducer = (state: any, action: AlertReducerAction) => { + const { command, payload } = action; + const { alert } = state; + + switch (command.type) { + case 'setAlert': { + const { key, value } = payload; + if (key === 'alert') { + return { + ...state, + alert: value, + }; + } else { + return state; + } + } + case 'setProperty': { + const { key, value } = payload; + if (isEqual(alert[key], value)) { + return state; + } else { + return { + ...state, + alert: { + ...alert, + [key]: value, + }, + }; + } + } + case 'setAlertParams': { + const { key, value } = payload; + if (isEqual(alert.params[key], value)) { + return state; + } else { + return { + ...state, + alert: { + ...alert, + params: { + ...alert.params, + [key]: value, + }, + }, + }; + } + } + case 'setAlertActionParams': { + const { key, value, index } = payload; + if (index === undefined || isEqual(alert.actions[index][key], value)) { + return state; + } else { + const oldAction = alert.actions.splice(index, 1)[0]; + const updatedAction = { + ...oldAction, + params: { + ...oldAction.params, + [key]: value, + }, + }; + alert.actions.splice(index, 0, updatedAction); + return { + ...state, + alert: { + ...alert, + actions: [...alert.actions], + }, + }; + } + } + case 'setAlertActionProperty': { + const { key, value, index } = payload; + if (index === undefined || isEqual(alert.actions[index][key], value)) { + return state; + } else { + const oldAction = alert.actions.splice(index, 1)[0]; + const updatedAction = { + ...oldAction, + [key]: value, + }; + alert.actions.splice(index, 0, updatedAction); + return { + ...state, + alert: { + ...alert, + actions: [...alert.actions], + }, + }; + } + } + } +}; diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/alert_add/index.ts b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/alert_add/index.ts new file mode 100644 index 0000000000000..f88a8bb1c49d0 --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/alert_add/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { AlertAdd } from './alert_add'; diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/alerts_list/components/action_type_filter.tsx b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/alerts_list/components/action_type_filter.tsx new file mode 100644 index 0000000000000..7a25a241b0162 --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/alerts_list/components/action_type_filter.tsx @@ -0,0 +1,72 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useEffect, useState } from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiFilterGroup, EuiPopover, EuiFilterButton, EuiFilterSelectItem } from '@elastic/eui'; +import { ActionType } from '../../../../types'; + +interface ActionTypeFilterProps { + actionTypes: ActionType[]; + onChange?: (selectedActionTypeIds: string[]) => void; +} + +export const ActionTypeFilter: React.FunctionComponent = ({ + actionTypes, + onChange, +}: ActionTypeFilterProps) => { + const [selectedValues, setSelectedValues] = useState([]); + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + + useEffect(() => { + if (onChange) { + onChange(selectedValues); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [selectedValues]); + + return ( + + setIsPopoverOpen(false)} + button={ + 0} + numActiveFilters={selectedValues.length} + numFilters={selectedValues.length} + onClick={() => setIsPopoverOpen(!isPopoverOpen)} + > + + + } + > +
+ {actionTypes.map(item => ( + { + const isPreviouslyChecked = selectedValues.includes(item.id); + if (isPreviouslyChecked) { + setSelectedValues(selectedValues.filter(val => val !== item.id)); + } else { + setSelectedValues(selectedValues.concat(item.id)); + } + }} + checked={selectedValues.includes(item.id) ? 'on' : undefined} + > + {item.name} + + ))} +
+
+
+ ); +}; diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/alerts_list/components/alerts_list.test.tsx b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/alerts_list/components/alerts_list.test.tsx new file mode 100644 index 0000000000000..8f8aef5a16bd5 --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/alerts_list/components/alerts_list.test.tsx @@ -0,0 +1,453 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import * as React from 'react'; +import { mountWithIntl } from 'test_utils/enzyme_helpers'; +import { coreMock } from '../../../../../../../../../../src/core/public/mocks'; +import { ReactWrapper } from 'enzyme'; +import { act } from 'react-dom/test-utils'; +import { actionTypeRegistryMock } from '../../../action_type_registry.mock'; +import { alertTypeRegistryMock } from '../../../alert_type_registry.mock'; +import { AlertsList } from './alerts_list'; +import { ValidationResult } from '../../../../types'; +import { AppContextProvider } from '../../../app_context'; +jest.mock('../../../lib/action_connector_api', () => ({ + loadActionTypes: jest.fn(), + loadAllActions: jest.fn(), +})); +jest.mock('../../../lib/alert_api', () => ({ + loadAlerts: jest.fn(), + loadAlertTypes: jest.fn(), +})); + +const actionTypeRegistry = actionTypeRegistryMock.create(); +const alertTypeRegistry = alertTypeRegistryMock.create(); + +const alertType = { + id: 'test_alert_type', + name: 'some alert type', + iconClass: 'test', + validate: (): ValidationResult => { + return { errors: {} }; + }, + alertParamsExpression: () => null, +}; +alertTypeRegistry.list.mockReturnValue([alertType]); +actionTypeRegistry.list.mockReturnValue([]); + +describe('alerts_list component empty', () => { + let wrapper: ReactWrapper; + + beforeEach(async () => { + const { loadAlerts, loadAlertTypes } = jest.requireMock('../../../lib/alert_api'); + const { loadActionTypes, loadAllActions } = jest.requireMock( + '../../../lib/action_connector_api' + ); + loadAlerts.mockResolvedValue({ + page: 1, + perPage: 10000, + total: 0, + data: [], + }); + loadActionTypes.mockResolvedValue([ + { + id: 'test', + name: 'Test', + }, + { + id: 'test2', + name: 'Test2', + }, + ]); + loadAlertTypes.mockResolvedValue([{ id: 'test_alert_type', name: 'some alert type' }]); + loadAllActions.mockResolvedValue({ + page: 1, + perPage: 10000, + total: 0, + data: [], + }); + + const mockes = coreMock.createSetup(); + const [{ chrome, docLinks }] = await mockes.getStartServices(); + const deps = { + chrome, + docLinks, + toastNotifications: mockes.notifications.toasts, + injectedMetadata: { + getInjectedVar(name: string) { + if (name === 'createAlertUiEnabled') { + return true; + } + }, + } as any, + http: mockes.http, + uiSettings: mockes.uiSettings, + legacy: { + capabilities: { + get() { + return { + siem: { + 'alerting:show': true, + 'alerting:save': true, + 'alerting:delete': true, + }, + }; + }, + } as any, + MANAGEMENT_BREADCRUMB: { set: () => {} } as any, + }, + actionTypeRegistry: actionTypeRegistry as any, + alertTypeRegistry: alertTypeRegistry as any, + }; + + await act(async () => { + wrapper = mountWithIntl( + + + + ); + }); + + await waitForRender(wrapper); + }); + + it('renders empty list', () => { + expect(wrapper.find('[data-test-subj="createAlertButton"]').find('EuiButton')).toHaveLength(1); + }); + + test('if click create button should render AlertAdd', () => { + wrapper + .find('[data-test-subj="createAlertButton"]') + .first() + .simulate('click'); + expect(wrapper.find('AlertAdd')).toHaveLength(1); + }); +}); + +describe('alerts_list component with items', () => { + let wrapper: ReactWrapper; + + beforeEach(async () => { + const { loadAlerts, loadAlertTypes } = jest.requireMock('../../../lib/alert_api'); + const { loadActionTypes, loadAllActions } = jest.requireMock( + '../../../lib/action_connector_api' + ); + loadAlerts.mockResolvedValue({ + page: 1, + perPage: 10000, + total: 2, + data: [ + { + id: '1', + name: 'test alert', + tags: ['tag1'], + enabled: true, + alertTypeId: 'test_alert_type', + interval: '5d', + actions: [], + params: { name: 'test alert type name' }, + scheduledTaskId: null, + createdBy: null, + updatedBy: null, + apiKeyOwner: null, + throttle: '1m', + muteAll: false, + mutedInstanceIds: [], + }, + { + id: '2', + name: 'test alert 2', + tags: ['tag1'], + enabled: true, + alertTypeId: 'test_alert_type', + interval: '5d', + actions: [{ id: 'test', group: 'alert', params: { message: 'test' } }], + params: { name: 'test alert type name' }, + scheduledTaskId: null, + createdBy: null, + updatedBy: null, + apiKeyOwner: null, + throttle: '1m', + muteAll: false, + mutedInstanceIds: [], + }, + ], + }); + loadActionTypes.mockResolvedValue([ + { + id: 'test', + name: 'Test', + }, + { + id: 'test2', + name: 'Test2', + }, + ]); + loadAlertTypes.mockResolvedValue([{ id: 'test_alert_type', name: 'some alert type' }]); + loadAllActions.mockResolvedValue({ + page: 1, + perPage: 10000, + total: 0, + data: [], + }); + const mockes = coreMock.createSetup(); + const [{ chrome, docLinks }] = await mockes.getStartServices(); + const deps = { + chrome, + docLinks, + toastNotifications: mockes.notifications.toasts, + injectedMetadata: { + getInjectedVar(name: string) { + if (name === 'createAlertUiEnabled') { + return true; + } + }, + } as any, + http: mockes.http, + uiSettings: mockes.uiSettings, + legacy: { + capabilities: { + get() { + return { + siem: { + 'alerting:show': true, + 'alerting:save': true, + 'alerting:delete': true, + }, + }; + }, + } as any, + MANAGEMENT_BREADCRUMB: { set: () => {} } as any, + }, + actionTypeRegistry: actionTypeRegistry as any, + alertTypeRegistry: alertTypeRegistry as any, + }; + + await act(async () => { + wrapper = mountWithIntl( + + + + ); + }); + + await waitForRender(wrapper); + + expect(loadAlerts).toHaveBeenCalled(); + expect(loadActionTypes).toHaveBeenCalled(); + }); + + it('renders table of connectors', () => { + expect(wrapper.find('EuiBasicTable')).toHaveLength(1); + expect(wrapper.find('EuiTableRow')).toHaveLength(2); + }); +}); + +describe('alerts_list component empty with show only capability', () => { + let wrapper: ReactWrapper; + + beforeEach(async () => { + const { loadAlerts, loadAlertTypes } = jest.requireMock('../../../lib/alert_api'); + const { loadActionTypes, loadAllActions } = jest.requireMock( + '../../../lib/action_connector_api' + ); + loadAlerts.mockResolvedValue({ + page: 1, + perPage: 10000, + total: 0, + data: [], + }); + loadActionTypes.mockResolvedValue([ + { + id: 'test', + name: 'Test', + }, + { + id: 'test2', + name: 'Test2', + }, + ]); + loadAlertTypes.mockResolvedValue([{ id: 'test_alert_type', name: 'some alert type' }]); + loadAllActions.mockResolvedValue({ + page: 1, + perPage: 10000, + total: 0, + data: [], + }); + const mockes = coreMock.createSetup(); + const [{ chrome, docLinks }] = await mockes.getStartServices(); + const deps = { + chrome, + docLinks, + toastNotifications: mockes.notifications.toasts, + injectedMetadata: { + getInjectedVar(name: string) { + if (name === 'createAlertUiEnabled') { + return true; + } + }, + } as any, + http: mockes.http, + uiSettings: mockes.uiSettings, + legacy: { + capabilities: { + get() { + return { + siem: { + 'alerting:show': true, + 'alerting:save': false, + 'alerting:delete': false, + }, + }; + }, + } as any, + MANAGEMENT_BREADCRUMB: { set: () => {} } as any, + }, + actionTypeRegistry: { + get() { + return null; + }, + } as any, + alertTypeRegistry: {} as any, + }; + + await act(async () => { + wrapper = mountWithIntl( + + + + ); + }); + + await waitForRender(wrapper); + }); + + it('not renders create alert button', () => { + expect(wrapper.find('[data-test-subj="createAlertButton"]')).toHaveLength(0); + }); +}); + +describe('alerts_list with show only capability', () => { + let wrapper: ReactWrapper; + + beforeEach(async () => { + const { loadAlerts, loadAlertTypes } = jest.requireMock('../../../lib/alert_api'); + const { loadActionTypes, loadAllActions } = jest.requireMock( + '../../../lib/action_connector_api' + ); + loadAlerts.mockResolvedValue({ + page: 1, + perPage: 10000, + total: 2, + data: [ + { + id: '1', + name: 'test alert', + tags: ['tag1'], + enabled: true, + alertTypeId: 'test_alert_type', + interval: '5d', + actions: [], + params: { name: 'test alert type name' }, + scheduledTaskId: null, + createdBy: null, + updatedBy: null, + apiKeyOwner: null, + throttle: '1m', + muteAll: false, + mutedInstanceIds: [], + }, + { + id: '2', + name: 'test alert 2', + tags: ['tag1'], + enabled: true, + alertTypeId: 'test_alert_type', + interval: '5d', + actions: [{ id: 'test', group: 'alert', params: { message: 'test' } }], + params: { name: 'test alert type name' }, + scheduledTaskId: null, + createdBy: null, + updatedBy: null, + apiKeyOwner: null, + throttle: '1m', + muteAll: false, + mutedInstanceIds: [], + }, + ], + }); + loadActionTypes.mockResolvedValue([ + { + id: 'test', + name: 'Test', + }, + { + id: 'test2', + name: 'Test2', + }, + ]); + loadAlertTypes.mockResolvedValue([{ id: 'test_alert_type', name: 'some alert type' }]); + loadAllActions.mockResolvedValue({ + page: 1, + perPage: 10000, + total: 0, + data: [], + }); + const mockes = coreMock.createSetup(); + const [{ chrome, docLinks }] = await mockes.getStartServices(); + const deps = { + chrome, + docLinks, + toastNotifications: mockes.notifications.toasts, + injectedMetadata: { + getInjectedVar(name: string) { + if (name === 'createAlertUiEnabled') { + return true; + } + }, + } as any, + http: mockes.http, + uiSettings: mockes.uiSettings, + legacy: { + capabilities: { + get() { + return { + siem: { + 'alerting:show': true, + 'alerting:save': false, + 'alerting:delete': false, + }, + }; + }, + } as any, + MANAGEMENT_BREADCRUMB: { set: () => {} } as any, + }, + actionTypeRegistry: actionTypeRegistry as any, + alertTypeRegistry: alertTypeRegistry as any, + }; + + await act(async () => { + wrapper = mountWithIntl( + + + + ); + }); + + await waitForRender(wrapper); + }); + + it('renders table of alerts with delete button disabled', () => { + expect(wrapper.find('EuiBasicTable')).toHaveLength(1); + expect(wrapper.find('EuiTableRow')).toHaveLength(2); + // TODO: check delete button + }); +}); + +async function waitForRender(wrapper: ReactWrapper) { + await Promise.resolve(); + await Promise.resolve(); + wrapper.update(); +} diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/alerts_list/components/alerts_list.tsx b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/alerts_list/components/alerts_list.tsx new file mode 100644 index 0000000000000..64f06521c0f9d --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/alerts_list/components/alerts_list.tsx @@ -0,0 +1,330 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import { FormattedMessage } from '@kbn/i18n/react'; +import React, { Fragment, useEffect, useState } from 'react'; +import { + EuiBasicTable, + EuiButton, + EuiFieldText, + EuiFlexGroup, + EuiFlexItem, + EuiIcon, + EuiSpacer, +} from '@elastic/eui'; + +import { AlertsContextProvider } from '../../../context/alerts_context'; +import { useAppDependencies } from '../../../app_context'; +import { ActionType, Alert, AlertTableItem, AlertTypeIndex, Pagination } from '../../../../types'; +import { AlertAdd } from '../../alert_add'; +import { BulkActionPopover } from './bulk_action_popover'; +import { CollapsedItemActions } from './collapsed_item_actions'; +import { TypeFilter } from './type_filter'; +import { ActionTypeFilter } from './action_type_filter'; +import { loadAlerts, loadAlertTypes } from '../../../lib/alert_api'; +import { loadActionTypes } from '../../../lib/action_connector_api'; +import { hasDeleteAlertsCapability, hasSaveAlertsCapability } from '../../../lib/capabilities'; + +const ENTER_KEY = 13; + +export const AlertsList: React.FunctionComponent = () => { + const { + http, + injectedMetadata, + toastNotifications, + legacy: { capabilities }, + } = useAppDependencies(); + const canDelete = hasDeleteAlertsCapability(capabilities.get()); + const canSave = hasSaveAlertsCapability(capabilities.get()); + const createAlertUiEnabled = injectedMetadata.getInjectedVar('createAlertUiEnabled'); + + const [actionTypes, setActionTypes] = useState([]); + const [alertTypesIndex, setAlertTypesIndex] = useState(undefined); + const [alerts, setAlerts] = useState([]); + const [data, setData] = useState([]); + const [selectedIds, setSelectedIds] = useState([]); + const [isLoadingAlertTypes, setIsLoadingAlertTypes] = useState(false); + const [isLoadingAlerts, setIsLoadingAlerts] = useState(false); + const [isPerformingAction, setIsPerformingAction] = useState(false); + const [totalItemCount, setTotalItemCount] = useState(0); + const [page, setPage] = useState({ index: 0, size: 10 }); + const [searchText, setSearchText] = useState(); + const [inputText, setInputText] = useState(); + const [typesFilter, setTypesFilter] = useState([]); + const [actionTypesFilter, setActionTypesFilter] = useState([]); + const [alertFlyoutVisible, setAlertFlyoutVisibility] = useState(false); + + useEffect(() => { + loadAlertsData(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [page, searchText, typesFilter, actionTypesFilter]); + + useEffect(() => { + (async () => { + try { + setIsLoadingAlertTypes(true); + const alertTypes = await loadAlertTypes({ http }); + const index: AlertTypeIndex = {}; + for (const alertType of alertTypes) { + index[alertType.id] = alertType; + } + setAlertTypesIndex(index); + } catch (e) { + toastNotifications.addDanger({ + title: i18n.translate( + 'xpack.triggersActionsUI.sections.alertsList.unableToLoadAlertTypesMessage', + { defaultMessage: 'Unable to load alert types' } + ), + }); + } finally { + setIsLoadingAlertTypes(false); + } + })(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + useEffect(() => { + (async () => { + try { + const result = await loadActionTypes({ http }); + setActionTypes(result); + } catch (e) { + toastNotifications.addDanger({ + title: i18n.translate( + 'xpack.triggersActionsUI.sections.alertsList.unableToLoadActionTypesMessage', + { defaultMessage: 'Unable to load action types' } + ), + }); + } + })(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []); + + useEffect(() => { + // Avoid flickering before alert types load + if (typeof alertTypesIndex === 'undefined') { + return; + } + const updatedData = alerts.map(alert => ({ + ...alert, + tagsText: alert.tags.join(', '), + alertType: alertTypesIndex[alert.alertTypeId] + ? alertTypesIndex[alert.alertTypeId].name + : alert.alertTypeId, + })); + setData(updatedData); + }, [alerts, alertTypesIndex]); + + async function loadAlertsData() { + setIsLoadingAlerts(true); + try { + const alertsResponse = await loadAlerts({ + http, + page, + searchText, + typesFilter, + actionTypesFilter, + }); + setAlerts(alertsResponse.data); + setTotalItemCount(alertsResponse.total); + } catch (e) { + toastNotifications.addDanger({ + title: i18n.translate( + 'xpack.triggersActionsUI.sections.alertsList.unableToLoadAlertsMessage', + { + defaultMessage: 'Unable to load alerts', + } + ), + }); + } finally { + setIsLoadingAlerts(false); + } + } + + const alertsTableColumns = [ + { + field: 'name', + name: i18n.translate( + 'xpack.triggersActionsUI.sections.alertsList.alertsListTable.columns.nameTitle', + { defaultMessage: 'Name' } + ), + sortable: false, + truncateText: true, + 'data-test-subj': 'alertsTableCell-name', + }, + { + field: 'tagsText', + name: i18n.translate( + 'xpack.triggersActionsUI.sections.alertsList.alertsListTable.columns.tagsText', + { defaultMessage: 'Tags' } + ), + sortable: false, + 'data-test-subj': 'alertsTableCell-tagsText', + }, + { + field: 'alertType', + name: i18n.translate( + 'xpack.triggersActionsUI.sections.alertsList.alertsListTable.columns.alertTypeTitle', + { defaultMessage: 'Type' } + ), + sortable: false, + truncateText: true, + 'data-test-subj': 'alertsTableCell-alertType', + }, + { + field: 'schedule.interval', + name: i18n.translate( + 'xpack.triggersActionsUI.sections.alertsList.alertsListTable.columns.intervalTitle', + { defaultMessage: 'Runs every' } + ), + sortable: false, + truncateText: false, + 'data-test-subj': 'alertsTableCell-interval', + }, + { + name: '', + width: '40px', + render(item: AlertTableItem) { + return ( + loadAlertsData()} /> + ); + }, + }, + ]; + + const toolsRight = [ + setTypesFilter(types)} + options={Object.values(alertTypesIndex || {}) + .map(alertType => ({ + value: alertType.id, + name: alertType.name, + })) + .sort((a, b) => a.name.localeCompare(b.name))} + />, + setActionTypesFilter(ids)} + />, + ]; + + if (canSave && createAlertUiEnabled) { + toolsRight.push( + setAlertFlyoutVisibility(true)} + > + + + ); + } + + return ( +
+ + + + + {selectedIds.length > 0 && canDelete && ( + + setIsPerformingAction(true)} + onActionPerformed={() => { + loadAlertsData(); + setIsPerformingAction(false); + }} + /> + + )} + + } + onChange={e => setInputText(e.target.value)} + onKeyUp={e => { + if (e.keyCode === ENTER_KEY) { + setSearchText(inputText); + } + }} + placeholder={i18n.translate( + 'xpack.triggersActionsUI.sections.alertsList.searchPlaceholderTitle', + { defaultMessage: 'Search...' } + )} + /> + + + + {toolsRight.map((tool, index: number) => ( + + {tool} + + ))} + + + + + {/* Large to remain consistent with ActionsList table spacing */} + + + ({ + 'data-test-subj': 'alert-row', + })} + cellProps={() => ({ + 'data-test-subj': 'cell', + })} + data-test-subj="alertsList" + pagination={{ + pageIndex: page.index, + pageSize: page.size, + totalItemCount, + }} + selection={ + canDelete + ? { + onSelectionChange(updatedSelectedItemsList: AlertTableItem[]) { + setSelectedIds(updatedSelectedItemsList.map(item => item.id)); + }, + } + : undefined + } + onChange={({ page: changedPage }: { page: Pagination }) => { + setPage(changedPage); + }} + /> + + + +
+ ); +}; + +function pickFromData(data: AlertTableItem[], ids: string[]): AlertTableItem[] { + const result: AlertTableItem[] = []; + for (const id of ids) { + const match = data.find(item => item.id === id); + if (match) { + result.push(match); + } + } + return result; +} diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/alerts_list/components/bulk_action_popover.tsx b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/alerts_list/components/bulk_action_popover.tsx new file mode 100644 index 0000000000000..59ec52ac83a6c --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/alerts_list/components/bulk_action_popover.tsx @@ -0,0 +1,253 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import React, { useState } from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiButton, EuiButtonEmpty, EuiFormRow, EuiPopover } from '@elastic/eui'; + +import { AlertTableItem } from '../../../../types'; +import { useAppDependencies } from '../../../app_context'; +import { + deleteAlerts, + disableAlerts, + enableAlerts, + muteAlerts, + unmuteAlerts, +} from '../../../lib/alert_api'; + +export interface ComponentOpts { + selectedItems: AlertTableItem[]; + onPerformingAction: () => void; + onActionPerformed: () => void; +} + +export const BulkActionPopover: React.FunctionComponent = ({ + selectedItems, + onPerformingAction, + onActionPerformed, +}: ComponentOpts) => { + const { http, toastNotifications } = useAppDependencies(); + + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + const [isMutingAlerts, setIsMutingAlerts] = useState(false); + const [isUnmutingAlerts, setIsUnmutingAlerts] = useState(false); + const [isEnablingAlerts, setIsEnablingAlerts] = useState(false); + const [isDisablingAlerts, setIsDisablingAlerts] = useState(false); + const [isDeletingAlerts, setIsDeletingAlerts] = useState(false); + + const allAlertsMuted = selectedItems.every(isAlertMuted); + const allAlertsDisabled = selectedItems.every(isAlertDisabled); + const isPerformingAction = + isMutingAlerts || isUnmutingAlerts || isEnablingAlerts || isDisablingAlerts || isDeletingAlerts; + + async function onmMuteAllClick() { + onPerformingAction(); + setIsMutingAlerts(true); + const ids = selectedItems.filter(item => !isAlertMuted(item)).map(item => item.id); + try { + await muteAlerts({ http, ids }); + } catch (e) { + toastNotifications.addDanger({ + title: i18n.translate( + 'xpack.triggersActionsUI.sections.alertsList.bulkActionPopover.failedToMuteAlertsMessage', + { + defaultMessage: 'Failed to mute alert(s)', + } + ), + }); + } finally { + setIsMutingAlerts(false); + onActionPerformed(); + } + } + + async function onUnmuteAllClick() { + onPerformingAction(); + setIsUnmutingAlerts(true); + const ids = selectedItems.filter(isAlertMuted).map(item => item.id); + try { + await unmuteAlerts({ http, ids }); + } catch (e) { + toastNotifications.addDanger({ + title: i18n.translate( + 'xpack.triggersActionsUI.sections.alertsList.bulkActionPopover.failedToUnmuteAlertsMessage', + { + defaultMessage: 'Failed to unmute alert(s)', + } + ), + }); + } finally { + setIsUnmutingAlerts(false); + onActionPerformed(); + } + } + + async function onEnableAllClick() { + onPerformingAction(); + setIsEnablingAlerts(true); + const ids = selectedItems.filter(isAlertDisabled).map(item => item.id); + try { + await enableAlerts({ http, ids }); + } catch (e) { + toastNotifications.addDanger({ + title: i18n.translate( + 'xpack.triggersActionsUI.sections.alertsList.bulkActionPopover.failedToEnableAlertsMessage', + { + defaultMessage: 'Failed to enable alert(s)', + } + ), + }); + } finally { + setIsEnablingAlerts(false); + onActionPerformed(); + } + } + + async function onDisableAllClick() { + onPerformingAction(); + setIsDisablingAlerts(true); + const ids = selectedItems.filter(item => !isAlertDisabled(item)).map(item => item.id); + try { + await disableAlerts({ http, ids }); + } catch (e) { + toastNotifications.addDanger({ + title: i18n.translate( + 'xpack.triggersActionsUI.sections.alertsList.bulkActionPopover.failedToDisableAlertsMessage', + { + defaultMessage: 'Failed to disable alert(s)', + } + ), + }); + } finally { + setIsDisablingAlerts(false); + onActionPerformed(); + } + } + + async function deleteSelectedItems() { + onPerformingAction(); + setIsDeletingAlerts(true); + const ids = selectedItems.map(item => item.id); + try { + await deleteAlerts({ http, ids }); + } catch (e) { + toastNotifications.addDanger({ + title: i18n.translate( + 'xpack.triggersActionsUI.sections.alertsList.bulkActionPopover.failedToDeleteAlertsMessage', + { + defaultMessage: 'Failed to delete alert(s)', + } + ), + }); + } finally { + setIsDeletingAlerts(false); + onActionPerformed(); + } + } + + return ( + setIsPopoverOpen(false)} + data-test-subj="bulkAction" + button={ + setIsPopoverOpen(!isPopoverOpen)} + > + + + } + > + {!allAlertsMuted && ( + + + + + + )} + {allAlertsMuted && ( + + + + + + )} + {allAlertsDisabled && ( + + + + + + )} + {!allAlertsDisabled && ( + + + + + + )} + + + + + + + ); +}; + +function isAlertDisabled(alert: AlertTableItem) { + return alert.enabled === false; +} + +function isAlertMuted(alert: AlertTableItem) { + return alert.muteAll === true; +} diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/alerts_list/components/collapsed_item_actions.tsx b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/alerts_list/components/collapsed_item_actions.tsx new file mode 100644 index 0000000000000..f063ab4f7cde3 --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/alerts_list/components/collapsed_item_actions.tsx @@ -0,0 +1,140 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import React, { useState } from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { + EuiButtonEmpty, + EuiButtonIcon, + EuiFormRow, + EuiPopover, + EuiPopoverFooter, + EuiSwitch, +} from '@elastic/eui'; + +import { AlertTableItem } from '../../../../types'; +import { useAppDependencies } from '../../../app_context'; +import { hasDeleteAlertsCapability, hasSaveAlertsCapability } from '../../../lib/capabilities'; +import { + deleteAlerts, + disableAlerts, + enableAlerts, + muteAlerts, + unmuteAlerts, +} from '../../../lib/alert_api'; + +export interface ComponentOpts { + item: AlertTableItem; + onAlertChanged: () => void; +} + +export const CollapsedItemActions: React.FunctionComponent = ({ + item, + onAlertChanged, +}: ComponentOpts) => { + const { + http, + legacy: { capabilities }, + } = useAppDependencies(); + + const canDelete = hasDeleteAlertsCapability(capabilities.get()); + const canSave = hasSaveAlertsCapability(capabilities.get()); + + const [isEnabled, setIsEnabled] = useState(item.enabled); + const [isMuted, setIsMuted] = useState(item.muteAll); + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + + const button = ( + setIsPopoverOpen(!isPopoverOpen)} + aria-label={i18n.translate( + 'xpack.triggersActionsUI.sections.alertsList.collapsedItemActons.popoverButtonTitle', + { defaultMessage: 'Actions' } + )} + /> + ); + + return ( + setIsPopoverOpen(false)} + ownFocus + data-test-subj="collapsedItemActions" + > + + { + if (isEnabled) { + setIsEnabled(false); + await disableAlerts({ http, ids: [item.id] }); + } else { + setIsEnabled(true); + await enableAlerts({ http, ids: [item.id] }); + } + onAlertChanged(); + }} + label={ + + } + /> + + + { + if (isMuted) { + setIsMuted(false); + await unmuteAlerts({ http, ids: [item.id] }); + } else { + setIsMuted(true); + await muteAlerts({ http, ids: [item.id] }); + } + onAlertChanged(); + }} + label={ + + } + /> + + + + { + await deleteAlerts({ http, ids: [item.id] }); + onAlertChanged(); + }} + > + + + + + + ); +}; diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/alerts_list/components/type_filter.tsx b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/alerts_list/components/type_filter.tsx new file mode 100644 index 0000000000000..f9cf7a6efd461 --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/sections/alerts_list/components/type_filter.tsx @@ -0,0 +1,74 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useEffect, useState } from 'react'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { EuiFilterGroup, EuiPopover, EuiFilterButton, EuiFilterSelectItem } from '@elastic/eui'; + +interface TypeFilterProps { + options: Array<{ + value: string; + name: string; + }>; + onChange?: (selectedTags: string[]) => void; +} + +export const TypeFilter: React.FunctionComponent = ({ + options, + onChange, +}: TypeFilterProps) => { + const [selectedValues, setSelectedValues] = useState([]); + const [isPopoverOpen, setIsPopoverOpen] = useState(false); + + useEffect(() => { + if (onChange) { + onChange(selectedValues); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [selectedValues]); + + return ( + + setIsPopoverOpen(false)} + button={ + 0} + numActiveFilters={selectedValues.length} + numFilters={selectedValues.length} + onClick={() => setIsPopoverOpen(!isPopoverOpen)} + > + + + } + > +
+ {options.map((item, index) => ( + { + const isPreviouslyChecked = selectedValues.includes(item.value); + if (isPreviouslyChecked) { + setSelectedValues(selectedValues.filter(val => val !== item.value)); + } else { + setSelectedValues(selectedValues.concat(item.value)); + } + }} + checked={selectedValues.includes(item.value) ? 'on' : undefined} + > + {item.name} + + ))} +
+
+
+ ); +}; diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/type_registry.test.ts b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/type_registry.test.ts new file mode 100644 index 0000000000000..efe58aedb8353 --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/type_registry.test.ts @@ -0,0 +1,117 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { TypeRegistry } from './type_registry'; +import { ValidationResult, AlertTypeModel, ActionTypeModel } from '../types'; + +export const ExpressionComponent: React.FunctionComponent = () => { + return null; +}; + +const getTestAlertType = (id?: string, name?: string, iconClass?: string) => { + return { + id: id || 'test-alet-type', + name: name || 'Test alert type', + iconClass: iconClass || 'icon', + validate: (): ValidationResult => { + return { errors: {} }; + }, + alertParamsExpression: ExpressionComponent, + }; +}; + +const getTestActionType = (id?: string, iconClass?: string, selectedMessage?: string) => { + return { + id: id || 'my-action-type', + iconClass: iconClass || 'test', + selectMessage: selectedMessage || 'test', + validateConnector: (): ValidationResult => { + return { errors: {} }; + }, + validateParams: (): ValidationResult => { + const validationResult = { errors: {} }; + return validationResult; + }, + actionConnectorFields: null, + actionParamsFields: null, + }; +}; + +beforeEach(() => jest.resetAllMocks()); + +describe('register()', () => { + test('able to register alert types', () => { + const alertTypeRegistry = new TypeRegistry(); + alertTypeRegistry.register(getTestAlertType()); + expect(alertTypeRegistry.has('test-alet-type')).toEqual(true); + }); + + test('throws error if alert type already registered', () => { + const alertTypeRegistry = new TypeRegistry(); + alertTypeRegistry.register(getTestAlertType('my-test-alert-type-1')); + expect(() => + alertTypeRegistry.register(getTestAlertType('my-test-alert-type-1')) + ).toThrowErrorMatchingInlineSnapshot( + `"Object type \\"my-test-alert-type-1\\" is already registered."` + ); + }); +}); + +describe('get()', () => { + test('returns action type', () => { + const actionTypeRegistry = new TypeRegistry(); + actionTypeRegistry.register(getTestActionType('my-action-type-snapshot')); + const actionType = actionTypeRegistry.get('my-action-type-snapshot'); + expect(actionType).toMatchInlineSnapshot(` + Object { + "actionConnectorFields": null, + "actionParamsFields": null, + "iconClass": "test", + "id": "my-action-type-snapshot", + "selectMessage": "test", + "validateConnector": [Function], + "validateParams": [Function], + } + `); + }); + + test(`return null when action type doesn't exist`, () => { + const actionTypeRegistry = new TypeRegistry(); + expect(actionTypeRegistry.get('not-exist-action-type')).toBeNull(); + }); +}); + +describe('list()', () => { + test('returns list of action types', () => { + const actionTypeRegistry = new TypeRegistry(); + actionTypeRegistry.register(getTestActionType()); + const actionTypes = actionTypeRegistry.list(); + expect(actionTypes).toEqual([ + { + id: 'my-action-type', + iconClass: 'test', + selectMessage: 'test', + actionConnectorFields: null, + actionParamsFields: null, + validateConnector: actionTypes[0].validateConnector, + validateParams: actionTypes[0].validateParams, + }, + ]); + }); +}); + +describe('has()', () => { + test('returns false for unregistered alert types', () => { + const alertTypeRegistry = new TypeRegistry(); + expect(alertTypeRegistry.has('my-alert-type')).toEqual(false); + }); + + test('returns true after registering an alert type', () => { + const alertTypeRegistry = new TypeRegistry(); + alertTypeRegistry.register(getTestAlertType()); + expect(alertTypeRegistry.has('test-alet-type')); + }); +}); diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/type_registry.ts b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/type_registry.ts new file mode 100644 index 0000000000000..3390d8910a45f --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/application/type_registry.ts @@ -0,0 +1,56 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; + +interface BaseObjectType { + id: string; +} + +export class TypeRegistry { + private readonly objectTypes: Map = new Map(); + + /** + * Returns if the object type registry has the given type registered + */ + public has(id: string) { + return this.objectTypes.has(id); + } + + /** + * Registers an object type to the type registry + */ + public register(objectType: T) { + if (this.has(objectType.id)) { + throw new Error( + i18n.translate( + 'xpack.triggersActionsUI.typeRegistry.register.duplicateObjectTypeErrorMessage', + { + defaultMessage: 'Object type "{id}" is already registered.', + values: { + id: objectType.id, + }, + } + ) + ); + } + this.objectTypes.set(objectType.id, objectType); + } + + /** + * Returns an object type, null if not registered + */ + public get(id: string): T | null { + if (!this.has(id)) { + return null; + } + return this.objectTypes.get(id)!; + } + + public list() { + return Array.from(this.objectTypes).map(([id, objectType]) => objectType); + } +} diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/index.ts b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/index.ts new file mode 100644 index 0000000000000..7eed516019dd0 --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/index.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { PluginInitializerContext } from 'src/core/public'; +import { Plugin } from './plugin'; + +export function plugin(ctx: PluginInitializerContext) { + return new Plugin(ctx); +} + +export { Plugin }; +export * from './plugin'; diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/plugin.ts b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/plugin.ts new file mode 100644 index 0000000000000..0b0f8a4ee6790 --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/plugin.ts @@ -0,0 +1,107 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + CoreSetup, + CoreStart, + Plugin as CorePlugin, + PluginInitializerContext, +} from 'src/core/public'; + +import { i18n } from '@kbn/i18n'; +import { registerBuiltInActionTypes } from './application/components/builtin_action_types'; +import { registerBuiltInAlertTypes } from './application/components/builtin_alert_types'; +import { hasShowActionsCapability, hasShowAlertsCapability } from './application/lib/capabilities'; +import { PLUGIN } from './application/constants/plugin'; +import { LegacyDependencies, ActionTypeModel, AlertTypeModel } from './types'; +import { TypeRegistry } from './application/type_registry'; + +export type Setup = void; +export type Start = void; + +interface LegacyPlugins { + __LEGACY: LegacyDependencies; +} + +export class Plugin implements CorePlugin { + private actionTypeRegistry: TypeRegistry; + private alertTypeRegistry: TypeRegistry; + + constructor(initializerContext: PluginInitializerContext) { + const actionTypeRegistry = new TypeRegistry(); + this.actionTypeRegistry = actionTypeRegistry; + + const alertTypeRegistry = new TypeRegistry(); + this.alertTypeRegistry = alertTypeRegistry; + } + + public setup( + { application, notifications, http, uiSettings, injectedMetadata }: CoreSetup, + { __LEGACY }: LegacyPlugins + ): Setup { + const canShowActions = hasShowActionsCapability(__LEGACY.capabilities.get()); + const canShowAlerts = hasShowAlertsCapability(__LEGACY.capabilities.get()); + + if (!canShowActions && !canShowAlerts) { + return; + } + registerBuiltInActionTypes({ + actionTypeRegistry: this.actionTypeRegistry, + }); + + registerBuiltInAlertTypes({ + alertTypeRegistry: this.alertTypeRegistry, + }); + application.register({ + id: PLUGIN.ID, + title: PLUGIN.getI18nName(i18n), + mount: async ( + { + core: { + docLinks, + chrome, + // Waiting for types to be updated. + // @ts-ignore + savedObjects, + i18n: { Context: I18nContext }, + }, + }, + { element } + ) => { + const { boot } = await import('./application/boot'); + return boot({ + element, + toastNotifications: notifications.toasts, + injectedMetadata, + http, + uiSettings, + docLinks, + chrome, + savedObjects: savedObjects.client, + I18nContext, + legacy: { + ...__LEGACY, + }, + actionTypeRegistry: this.actionTypeRegistry, + alertTypeRegistry: this.alertTypeRegistry, + }); + }, + }); + } + + public start(core: CoreStart, { __LEGACY }: LegacyPlugins) { + const { capabilities } = __LEGACY; + const canShowActions = hasShowActionsCapability(capabilities.get()); + const canShowAlerts = hasShowAlertsCapability(capabilities.get()); + + // Don't register routes when user doesn't have access to the application + if (!canShowActions && !canShowAlerts) { + return; + } + } + + public stop() {} +} diff --git a/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/types.ts b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/types.ts new file mode 100644 index 0000000000000..4cf28d3bbd06f --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/np_ready/public/types.ts @@ -0,0 +1,120 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { capabilities } from 'ui/capabilities'; +import { TypeRegistry } from './application/type_registry'; + +export type ActionTypeIndex = Record; +export type AlertTypeIndex = Record; +export type ActionTypeRegistryContract = PublicMethodsOf>; +export type AlertTypeRegistryContract = PublicMethodsOf>; + +export interface ActionConnectorFieldsProps { + action: ActionConnector; + editActionConfig: (property: string, value: any) => void; + editActionSecrets: (property: string, value: any) => void; + errors: { [key: string]: string[] }; + hasErrors?: boolean; +} + +export interface ActionParamsProps { + action: any; + index: number; + editAction: (property: string, value: any, index: number) => void; + errors: { [key: string]: string[] }; + hasErrors?: boolean; +} + +export interface Pagination { + index: number; + size: number; +} + +export interface ActionTypeModel { + id: string; + iconClass: string; + selectMessage: string; + validateConnector: (action: ActionConnector) => ValidationResult; + validateParams: (actionParams: any) => ValidationResult; + actionConnectorFields: React.FunctionComponent | null; + actionParamsFields: React.FunctionComponent | null; +} + +export interface ValidationResult { + errors: Record; +} + +export interface ActionType { + id: string; + name: string; +} + +export interface ActionConnector { + secrets: Record; + id: string; + actionTypeId: string; + name: string; + referencedByCount?: number; + config: Record; +} + +export type ActionConnectorWithoutId = Omit; + +export interface ActionConnectorTableItem extends ActionConnector { + actionType: ActionType['name']; +} + +export interface AlertType { + id: string; + name: string; +} + +export interface AlertAction { + group: string; + id: string; + params: Record; +} + +export interface Alert { + id: string; + name: string; + tags: string[]; + enabled: boolean; + alertTypeId: string; + interval: string; + actions: AlertAction[]; + params: Record; + scheduledTaskId?: string; + createdBy: string | null; + updatedBy: string | null; + apiKeyOwner?: string; + throttle: string | null; + muteAll: boolean; + mutedInstanceIds: string[]; +} + +export type AlertWithoutId = Omit; + +export interface AlertTableItem extends Alert { + alertType: AlertType['name']; + tagsText: string; +} + +export interface AlertTypeModel { + id: string; + name: string; + iconClass: string; + validate: (alert: Alert) => ValidationResult; + alertParamsExpression: React.FunctionComponent; +} + +export interface IErrorObject { + [key: string]: string[]; +} + +export interface LegacyDependencies { + MANAGEMENT_BREADCRUMB: { text: string; href?: string }; + capabilities: typeof capabilities; +} diff --git a/x-pack/legacy/plugins/triggers_actions_ui/public/hacks/register.ts b/x-pack/legacy/plugins/triggers_actions_ui/public/hacks/register.ts new file mode 100644 index 0000000000000..7991604fcc667 --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/public/hacks/register.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { i18n } from '@kbn/i18n'; +import { + FeatureCatalogueRegistryProvider, + FeatureCatalogueCategory, +} from 'ui/registry/feature_catalogue'; + +FeatureCatalogueRegistryProvider.register(() => { + return { + id: 'triggersActions', + title: 'Alerts and Actions', // This is a product name so we don't translate it. + description: i18n.translate('xpack.triggersActionsUI.triggersActionsDescription', { + defaultMessage: 'Data by creating, managing, and monitoring triggers and actions.', + }), + icon: 'triggersActionsApp', + path: '/app/kibana#/management/kibana/triggersActions', + showOnHomePage: true, + category: FeatureCatalogueCategory.ADMIN, + }; +}); diff --git a/x-pack/legacy/plugins/triggers_actions_ui/public/index.scss b/x-pack/legacy/plugins/triggers_actions_ui/public/index.scss new file mode 100644 index 0000000000000..6faad81630b2b --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/public/index.scss @@ -0,0 +1,5 @@ +// Imported EUI +@import 'src/legacy/ui/public/styles/_styling_constants'; + +// Styling within the app +@import '../np_ready/public/application/sections/actions_connectors_list/components/index'; diff --git a/x-pack/legacy/plugins/triggers_actions_ui/public/legacy.ts b/x-pack/legacy/plugins/triggers_actions_ui/public/legacy.ts new file mode 100644 index 0000000000000..bae9104081267 --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/public/legacy.ts @@ -0,0 +1,98 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { CoreSetup, App, AppUnmount } from 'src/core/public'; +import { capabilities } from 'ui/capabilities'; +import { i18n } from '@kbn/i18n'; + +/* Legacy UI imports */ +import { npSetup, npStart } from 'ui/new_platform'; +import routes from 'ui/routes'; +import { management, MANAGEMENT_BREADCRUMB } from 'ui/management'; +// @ts-ignore +import { xpackInfo } from 'plugins/xpack_main/services/xpack_info'; +/* Legacy UI imports */ + +import { plugin } from '../np_ready/public'; +import { manageAngularLifecycle } from './manage_angular_lifecycle'; +import { BASE_PATH } from '../np_ready/public/application/constants'; +import { + hasShowActionsCapability, + hasShowAlertsCapability, +} from '../np_ready/public/application/lib/capabilities'; + +const REACT_ROOT_ID = 'triggersActionsRoot'; +const canShowActions = hasShowActionsCapability(capabilities.get()); +const canShowAlerts = hasShowAlertsCapability(capabilities.get()); + +const template = ` +
+
`; + +let elem: HTMLElement; +let mountApp: () => AppUnmount | Promise; +let unmountApp: AppUnmount | Promise; +routes.when(`${BASE_PATH}:section?/:subsection?/:view?/:id?`, { + template, + controller: (() => { + return ($route: any, $scope: any) => { + const shimCore: CoreSetup = { + ...npSetup.core, + application: { + ...npSetup.core.application, + register(app: App): void { + mountApp = () => + app.mount(npStart as any, { + element: elem, + appBasePath: BASE_PATH, + onAppLeave: () => undefined, + }); + }, + }, + }; + + // clean up previously rendered React app if one exists + // this happens because of React Router redirects + if (elem) { + ((unmountApp as unknown) as AppUnmount)(); + } + + $scope.$$postDigest(() => { + elem = document.getElementById(REACT_ROOT_ID)!; + const instance = plugin({} as any); + instance.setup(shimCore, { + ...(npSetup.plugins as typeof npSetup.plugins), + __LEGACY: { + MANAGEMENT_BREADCRUMB, + capabilities, + }, + }); + + instance.start(npStart.core, { + ...(npSetup.plugins as typeof npSetup.plugins), + __LEGACY: { + MANAGEMENT_BREADCRUMB, + capabilities, + }, + }); + + (mountApp() as Promise).then(fn => (unmountApp = fn)); + + manageAngularLifecycle($scope, $route, elem); + }); + }; + })(), +}); + +if (canShowActions || canShowAlerts) { + management.getSection('kibana').register('triggersActions', { + display: i18n.translate('xpack.triggersActionsUI.managementSection.displayName', { + defaultMessage: 'Alerts and Actions', + }), + order: 7, + url: `#${BASE_PATH}`, + }); +} diff --git a/x-pack/legacy/plugins/triggers_actions_ui/public/manage_angular_lifecycle.ts b/x-pack/legacy/plugins/triggers_actions_ui/public/manage_angular_lifecycle.ts new file mode 100644 index 0000000000000..efd40eaf83daa --- /dev/null +++ b/x-pack/legacy/plugins/triggers_actions_ui/public/manage_angular_lifecycle.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { unmountComponentAtNode } from 'react-dom'; + +export const manageAngularLifecycle = ($scope: any, $route: any, elem: HTMLElement) => { + const lastRoute = $route.current; + + const deregister = $scope.$on('$locationChangeSuccess', () => { + const currentRoute = $route.current; + if (lastRoute.$$route.template === currentRoute.$$route.template) { + $route.current = lastRoute; + } + }); + + $scope.$on('$destroy', () => { + if (deregister) { + deregister(); + } + + if (elem) { + unmountComponentAtNode(elem); + } + }); +}; diff --git a/x-pack/legacy/plugins/uptime/README.md b/x-pack/legacy/plugins/uptime/README.md index 37e030c44db37..308f78ecdc368 100644 --- a/x-pack/legacy/plugins/uptime/README.md +++ b/x-pack/legacy/plugins/uptime/README.md @@ -3,7 +3,7 @@ ## Purpose The purpose of this plugin is to provide users of Heartbeat more visibility of what's happening -in their infrasturcture. It's primarily built using React and Apollo's GraphQL tools. +in their infrastructure. It's primarily built using React and Apollo's GraphQL tools. ## Layout @@ -44,7 +44,7 @@ From `~/kibana/x-pack`, run `node scripts/jest.js`. ### Functional tests In one shell, from **~/kibana/x-pack**: -`node scripts/functional_tests-server.js` +`node scripts/functional_tests_server.js` In another shell, from **~kibana/x-pack**: `node ../scripts/functional_test_runner.js --grep="{TEST_NAME}"`. diff --git a/x-pack/legacy/plugins/uptime/common/graphql/introspection.json b/x-pack/legacy/plugins/uptime/common/graphql/introspection.json index 4d1993233e9ca..19d9cf19cc7f8 100644 --- a/x-pack/legacy/plugins/uptime/common/graphql/introspection.json +++ b/x-pack/legacy/plugins/uptime/common/graphql/introspection.json @@ -352,25 +352,6 @@ "isDeprecated": false, "deprecationReason": null }, - { - "name": "getMonitorPageTitle", - "description": "", - "args": [ - { - "name": "monitorId", - "description": "", - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, - "defaultValue": null - } - ], - "type": { "kind": "OBJECT", "name": "MonitorPageTitle", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, { "name": "getMonitorStates", "description": "Fetches the current state of Uptime monitors for the given parameters.", @@ -2173,18 +2154,6 @@ "isDeprecated": false, "deprecationReason": null }, - { - "name": "mixed", - "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "Int", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, { "name": "total", "description": "", @@ -2561,45 +2530,6 @@ "enumValues": null, "possibleTypes": null }, - { - "kind": "OBJECT", - "name": "MonitorPageTitle", - "description": "", - "fields": [ - { - "name": "id", - "description": "", - "args": [], - "type": { - "kind": "NON_NULL", - "name": null, - "ofType": { "kind": "SCALAR", "name": "String", "ofType": null } - }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "url", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - }, - { - "name": "name", - "description": "", - "args": [], - "type": { "kind": "SCALAR", "name": "String", "ofType": null }, - "isDeprecated": false, - "deprecationReason": null - } - ], - "inputFields": null, - "interfaces": [], - "enumValues": null, - "possibleTypes": null - }, { "kind": "OBJECT", "name": "MonitorSummaryResult", diff --git a/x-pack/legacy/plugins/uptime/common/graphql/types.ts b/x-pack/legacy/plugins/uptime/common/graphql/types.ts index ed7c9ef19f484..92e27d20323a7 100644 --- a/x-pack/legacy/plugins/uptime/common/graphql/types.ts +++ b/x-pack/legacy/plugins/uptime/common/graphql/types.ts @@ -30,9 +30,6 @@ export interface Query { /** Fetch the most recent event data for a monitor ID, date range, location. */ getLatestMonitors: Ping[]; - getFilterBar?: FilterBar | null; - - getMonitorPageTitle?: MonitorPageTitle | null; /** Fetches the current state of Uptime monitors for the given parameters. */ getMonitorStates?: MonitorSummaryResult | null; /** Fetches details about the uptime index. */ @@ -419,8 +416,6 @@ export interface SnapshotCount { down: number; - mixed: number; - total: number; } @@ -470,29 +465,7 @@ export interface StatusData { /** The total down counts for this point. */ total?: number | null; } -/** The data used to enrich the filter bar. */ -export interface FilterBar { - /** A series of monitor IDs in the heartbeat indices. */ - ids?: string[] | null; - /** The location values users have configured for the agents. */ - locations?: string[] | null; - /** The ports of the monitored endpoints. */ - ports?: number[] | null; - /** The schemes used by the monitors. */ - schemes?: string[] | null; - /** The possible status values contained in the indices. */ - statuses?: string[] | null; - /** The list of URLs */ - urls?: string[] | null; -} - -export interface MonitorPageTitle { - id: string; - - url?: string | null; - name?: string | null; -} /** The primary object returned for monitor states. */ export interface MonitorSummaryResult { /** Used to go to the next page of results */ @@ -738,24 +711,12 @@ export interface GetMonitorChartsDataQueryArgs { location?: string | null; } -export interface GetLatestMonitorsQueryArgs { - /** The lower limit of the date range. */ - dateRangeStart: string; - /** The upper limit of the date range. */ - dateRangeEnd: string; - /** Optional: a specific monitor ID filter. */ - monitorId?: string | null; - /** Optional: a specific instance location filter. */ - location?: string | null; -} export interface GetFilterBarQueryArgs { dateRangeStart: string; dateRangeEnd: string; } -export interface GetMonitorPageTitleQueryArgs { - monitorId: string; -} + export interface GetMonitorStatesQueryArgs { dateRangeStart: string; diff --git a/x-pack/legacy/plugins/uptime/common/runtime_types/index.ts b/x-pack/legacy/plugins/uptime/common/runtime_types/index.ts index 224892eb91783..58f79abcf91ec 100644 --- a/x-pack/legacy/plugins/uptime/common/runtime_types/index.ts +++ b/x-pack/legacy/plugins/uptime/common/runtime_types/index.ts @@ -5,6 +5,6 @@ */ export * from './common'; +export * from './monitor'; +export * from './overview_filters'; export * from './snapshot'; -export * from './monitor/monitor_details'; -export * from './monitor/monitor_locations'; diff --git a/x-pack/legacy/plugins/uptime/common/runtime_types/monitor/details.ts b/x-pack/legacy/plugins/uptime/common/runtime_types/monitor/details.ts new file mode 100644 index 0000000000000..bf81c91bae633 --- /dev/null +++ b/x-pack/legacy/plugins/uptime/common/runtime_types/monitor/details.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as t from 'io-ts'; + +// IO type for validation +export const MonitorErrorType = t.partial({ + code: t.number, + message: t.string, + type: t.string, +}); + +// Typescript type for type checking +export type MonitorError = t.TypeOf; + +export const MonitorDetailsType = t.intersection([ + t.type({ monitorId: t.string }), + t.partial({ error: MonitorErrorType }), + t.partial({ timestamp: t.string }), +]); +export type MonitorDetails = t.TypeOf; diff --git a/x-pack/legacy/plugins/uptime/common/runtime_types/monitor/index.ts b/x-pack/legacy/plugins/uptime/common/runtime_types/monitor/index.ts new file mode 100644 index 0000000000000..80b48d09dc5b8 --- /dev/null +++ b/x-pack/legacy/plugins/uptime/common/runtime_types/monitor/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export * from './details'; +export * from './locations'; diff --git a/x-pack/legacy/plugins/uptime/common/runtime_types/monitor/locations.ts b/x-pack/legacy/plugins/uptime/common/runtime_types/monitor/locations.ts new file mode 100644 index 0000000000000..ea3cfe677ca99 --- /dev/null +++ b/x-pack/legacy/plugins/uptime/common/runtime_types/monitor/locations.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import * as t from 'io-ts'; +import { CheckGeoType, SummaryType } from '../common'; + +// IO type for validation +export const MonitorLocationType = t.partial({ + summary: SummaryType, + geo: CheckGeoType, + timestamp: t.string, +}); + +// Typescript type for type checking +export type MonitorLocation = t.TypeOf; + +export const MonitorLocationsType = t.intersection([ + t.type({ monitorId: t.string }), + t.partial({ locations: t.array(MonitorLocationType) }), +]); +export type MonitorLocations = t.TypeOf; diff --git a/x-pack/legacy/plugins/uptime/common/runtime_types/monitor/monitor_details.ts b/x-pack/legacy/plugins/uptime/common/runtime_types/monitor/monitor_details.ts deleted file mode 100644 index 0a57a67b898e0..0000000000000 --- a/x-pack/legacy/plugins/uptime/common/runtime_types/monitor/monitor_details.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import * as t from 'io-ts'; - -// IO type for validation -export const MonitorErrorType = t.partial({ - code: t.number, - message: t.string, - type: t.string, -}); - -// Typescript type for type checking -export type MonitorError = t.TypeOf; - -export const MonitorDetailsType = t.intersection([ - t.type({ monitorId: t.string }), - t.partial({ error: MonitorErrorType }), - t.partial({ timestamp: t.string }), -]); -export type MonitorDetails = t.TypeOf; diff --git a/x-pack/legacy/plugins/uptime/common/runtime_types/monitor/monitor_locations.ts b/x-pack/legacy/plugins/uptime/common/runtime_types/monitor/monitor_locations.ts deleted file mode 100644 index a40453b3671b7..0000000000000 --- a/x-pack/legacy/plugins/uptime/common/runtime_types/monitor/monitor_locations.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -import * as t from 'io-ts'; -import { CheckGeoType, SummaryType } from '../common'; - -// IO type for validation -export const MonitorLocationType = t.partial({ - summary: SummaryType, - geo: CheckGeoType, -}); - -// Typescript type for type checking -export type MonitorLocation = t.TypeOf; - -export const MonitorLocationsType = t.intersection([ - t.type({ monitorId: t.string }), - t.partial({ locations: t.array(MonitorLocationType) }), -]); -export type MonitorLocations = t.TypeOf; diff --git a/x-pack/legacy/plugins/uptime/common/runtime_types/overview_filters/index.ts b/x-pack/legacy/plugins/uptime/common/runtime_types/overview_filters/index.ts new file mode 100644 index 0000000000000..a803a0720959a --- /dev/null +++ b/x-pack/legacy/plugins/uptime/common/runtime_types/overview_filters/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { OverviewFiltersType, OverviewFilters } from './overview_filters'; diff --git a/x-pack/legacy/plugins/uptime/common/runtime_types/overview_filters/overview_filters.ts b/x-pack/legacy/plugins/uptime/common/runtime_types/overview_filters/overview_filters.ts new file mode 100644 index 0000000000000..9b9241494f001 --- /dev/null +++ b/x-pack/legacy/plugins/uptime/common/runtime_types/overview_filters/overview_filters.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import * as t from 'io-ts'; + +export const OverviewFiltersType = t.type({ + locations: t.array(t.string), + ports: t.array(t.number), + schemes: t.array(t.string), + tags: t.array(t.string), +}); + +export type OverviewFilters = t.TypeOf; diff --git a/x-pack/legacy/plugins/uptime/common/runtime_types/snapshot/snapshot_count.ts b/x-pack/legacy/plugins/uptime/common/runtime_types/snapshot/snapshot_count.ts index d4935c50ff5b8..3abc25530a2dc 100644 --- a/x-pack/legacy/plugins/uptime/common/runtime_types/snapshot/snapshot_count.ts +++ b/x-pack/legacy/plugins/uptime/common/runtime_types/snapshot/snapshot_count.ts @@ -8,7 +8,6 @@ import * as t from 'io-ts'; export const SnapshotType = t.type({ down: t.number, - mixed: t.number, total: t.number, up: t.number, }); diff --git a/x-pack/legacy/plugins/uptime/public/apps/plugin.ts b/x-pack/legacy/plugins/uptime/public/apps/plugin.ts index bc4e30b79cb15..c09fdf116e790 100644 --- a/x-pack/legacy/plugins/uptime/public/apps/plugin.ts +++ b/x-pack/legacy/plugins/uptime/public/apps/plugin.ts @@ -30,14 +30,8 @@ export class Plugin { } public start(start: StartObject): void { - const { - core, - plugins: { - data: { autocomplete }, - }, - } = start; const libs: UMFrontendLibs = { - framework: getKibanaFrameworkAdapter(core, autocomplete), + framework: getKibanaFrameworkAdapter(start.core, start.plugins), }; // @ts-ignore improper type description this.chrome.setRootTemplate(template); diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/__snapshots__/empty_status_bar.test.tsx.snap b/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/__snapshots__/empty_status_bar.test.tsx.snap deleted file mode 100644 index e18846e960122..0000000000000 --- a/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/__snapshots__/empty_status_bar.test.tsx.snap +++ /dev/null @@ -1,29 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`EmptyStatusBar component renders a default message when no message provided 1`] = ` - - - - No data found for monitor id mon_id - - - -`; - -exports[`EmptyStatusBar component renders a message when provided 1`] = ` - - - - foobarbaz - - - -`; diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/__snapshots__/monitor_ssl_certificate.test.tsx.snap b/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/__snapshots__/monitor_ssl_certificate.test.tsx.snap deleted file mode 100644 index 45c24fd11194d..0000000000000 --- a/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/__snapshots__/monitor_ssl_certificate.test.tsx.snap +++ /dev/null @@ -1,29 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`MonitorStatusBar component renders 1`] = ` -Array [ -
, - .c0 { - margin-left: 20px; -} - -
-
-
- SSL certificate expires in 2 months -
-
-
, -] -`; - -exports[`MonitorStatusBar component renders null if invalid date 1`] = `null`; diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/__snapshots__/monitor_status.bar.test.tsx.snap b/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/__snapshots__/monitor_status.bar.test.tsx.snap index e53235b1ed6f7..17588ae53ed00 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/__snapshots__/monitor_status.bar.test.tsx.snap +++ b/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/__snapshots__/monitor_status.bar.test.tsx.snap @@ -2,46 +2,24 @@ exports[`MonitorStatusBar component renders duration in ms, not us 1`] = `
-
-
- -
-
- Up -
-
+

+ Up in 2 Locations +

- 1234ms -
-
- 15 minutes ago + +

+ id1 +

+
+
+
`; diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/__snapshots__/uptime_date_picker.test.tsx.snap b/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/__snapshots__/uptime_date_picker.test.tsx.snap index a38ca85a3e9e7..9a4cb2e04f59b 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/__snapshots__/uptime_date_picker.test.tsx.snap +++ b/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/__snapshots__/uptime_date_picker.test.tsx.snap @@ -227,54 +227,24 @@ exports[`UptimeDatePicker component validates props with shallow render 1`] = ` commonlyUsedRanges={ Array [ Object { - "end": "now/d", + "end": "now", "label": "Today", "start": "now/d", }, - Object { - "end": "now/w", - "label": "This week", - "start": "now/w", - }, - Object { - "end": "now", - "label": "Last 15 minutes", - "start": "now-15m", - }, - Object { - "end": "now", - "label": "Last 30 minutes", - "start": "now-30m", - }, Object { "end": "now", - "label": "Last 1 hour", - "start": "now-1h", - }, - Object { - "end": "now", - "label": "Last 24 hours", - "start": "now-24h", - }, - Object { - "end": "now", - "label": "Last 7 days", - "start": "now-7d", - }, - Object { - "end": "now", - "label": "Last 30 days", - "start": "now-30d", + "label": "Week to date", + "start": "now/w", }, Object { "end": "now", - "label": "Last 90 days", - "start": "now-90d", + "label": "Month to date", + "start": "now/M", }, Object { "end": "now", - "label": "Last 2 year", - "start": "now-1y", + "label": "Year to date", + "start": "now/y", }, ] } diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/empty_status_bar.test.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/empty_status_bar.test.tsx deleted file mode 100644 index b815f0e38b8e2..0000000000000 --- a/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/empty_status_bar.test.tsx +++ /dev/null @@ -1,21 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import { shallowWithIntl } from 'test_utils/enzyme_helpers'; -import { EmptyStatusBar } from '../empty_status_bar'; - -describe('EmptyStatusBar component', () => { - it('renders a message when provided', () => { - const component = shallowWithIntl(); - expect(component).toMatchSnapshot(); - }); - - it('renders a default message when no message provided', () => { - const component = shallowWithIntl(); - expect(component).toMatchSnapshot(); - }); -}); diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/monitor_ssl_certificate.test.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/monitor_ssl_certificate.test.tsx deleted file mode 100644 index c4063208d046e..0000000000000 --- a/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/monitor_ssl_certificate.test.tsx +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import moment from 'moment'; -import { renderWithIntl } from 'test_utils/enzyme_helpers'; -import { PingTls } from '../../../../common/graphql/types'; -import { MonitorSSLCertificate } from '../monitor_status_bar'; - -describe('MonitorStatusBar component', () => { - let monitorTls: PingTls; - - beforeEach(() => { - const dateInTwoMonths = moment() - .add(2, 'month') - .toString(); - - monitorTls = { - certificate_not_valid_after: dateInTwoMonths, - }; - }); - - it('renders', () => { - const component = renderWithIntl(); - expect(component).toMatchSnapshot(); - }); - - it('renders null if invalid date', () => { - monitorTls = { - certificate_not_valid_after: 'i am so invalid date', - }; - const component = renderWithIntl(); - expect(component).toMatchSnapshot(); - }); -}); diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/monitor_status.bar.test.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/monitor_status.bar.test.tsx index 761dd8a65238f..545405f91d537 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/monitor_status.bar.test.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/monitor_status.bar.test.tsx @@ -8,34 +8,59 @@ import moment from 'moment'; import React from 'react'; import { renderWithIntl } from 'test_utils/enzyme_helpers'; import { Ping } from '../../../../common/graphql/types'; -import { MonitorStatusBarComponent } from '../monitor_status_bar'; +import { MonitorStatusBarComponent } from '../monitor_status_details/monitor_status_bar'; describe('MonitorStatusBar component', () => { - let monitorStatus: Ping[]; + let monitorStatus: Ping; + let monitorLocations: any; + let dateStart: string; + let dateEnd: string; beforeEach(() => { - monitorStatus = [ - { - id: 'id1', - timestamp: moment(new Date()) - .subtract(15, 'm') - .toString(), - monitor: { - duration: { - us: 1234567, - }, - status: 'up', - }, - url: { - full: 'https://www.example.com/', + monitorStatus = { + id: 'id1', + timestamp: moment(new Date()) + .subtract(15, 'm') + .toString(), + monitor: { + duration: { + us: 1234567, }, + status: 'up', + }, + url: { + full: 'https://www.example.com/', }, - ]; + }; + + monitorLocations = { + monitorId: 'secure-avc', + locations: [ + { + summary: { up: 4, down: 0 }, + geo: { name: 'Berlin', location: { lat: '52.487448', lon: ' 13.394798' } }, + }, + { + summary: { up: 4, down: 0 }, + geo: { name: 'st-paul', location: { lat: '52.487448', lon: ' 13.394798' } }, + }, + ], + }; + + dateStart = moment('01-01-2010').toString(); + dateEnd = moment('10-10-2010').toString(); }); it('renders duration in ms, not us', () => { const component = renderWithIntl( - + ); expect(component).toMatchSnapshot(); }); diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/snapshot.test.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/snapshot.test.tsx index 193f37c8fe56b..d645eb21ac776 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/snapshot.test.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/snapshot.test.tsx @@ -13,7 +13,6 @@ describe('Snapshot component', () => { const snapshot: Snapshot = { up: 8, down: 2, - mixed: 0, total: 10, }; diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/uptime_date_picker.test.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/uptime_date_picker.test.tsx index 93fa0b505a891..e3ca1a87850c8 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/uptime_date_picker.test.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/__tests__/uptime_date_picker.test.tsx @@ -6,37 +6,16 @@ import { shallowWithIntl, renderWithIntl } from 'test_utils/enzyme_helpers'; import React from 'react'; -import { UptimeDatePicker, CommonlyUsedRange } from '../uptime_date_picker'; +import { UptimeDatePicker } from '../uptime_date_picker'; describe('UptimeDatePicker component', () => { - let commonlyUsedRange: CommonlyUsedRange[]; - - beforeEach(() => { - commonlyUsedRange = [ - { from: 'now/d', to: 'now/d', display: 'Today' }, - { from: 'now/w', to: 'now/w', display: 'This week' }, - { from: 'now-15m', to: 'now', display: 'Last 15 minutes' }, - { from: 'now-30m', to: 'now', display: 'Last 30 minutes' }, - { from: 'now-1h', to: 'now', display: 'Last 1 hour' }, - { from: 'now-24h', to: 'now', display: 'Last 24 hours' }, - { from: 'now-7d', to: 'now', display: 'Last 7 days' }, - { from: 'now-30d', to: 'now', display: 'Last 30 days' }, - { from: 'now-90d', to: 'now', display: 'Last 90 days' }, - { from: 'now-1y', to: 'now', display: 'Last 2 year' }, - ]; - }); - it('validates props with shallow render', () => { - const component = shallowWithIntl( - - ); + const component = shallowWithIntl(); expect(component).toMatchSnapshot(); }); it('renders properly with mock data', () => { - const component = renderWithIntl( - - ); + const component = renderWithIntl(); expect(component).toMatchSnapshot(); }); diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/charts/__tests__/__snapshots__/donut_chart.test.tsx.snap b/x-pack/legacy/plugins/uptime/public/components/functional/charts/__tests__/__snapshots__/donut_chart.test.tsx.snap index c8c4177a4907e..9699a1842ccf1 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/charts/__tests__/__snapshots__/donut_chart.test.tsx.snap +++ b/x-pack/legacy/plugins/uptime/public/components/functional/charts/__tests__/__snapshots__/donut_chart.test.tsx.snap @@ -103,6 +103,7 @@ exports[`DonutChart component renders a donut chart 1`] = ` 32 @@ -150,6 +151,7 @@ exports[`DonutChart component renders a donut chart 1`] = ` 95 diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/charts/__tests__/__snapshots__/donut_chart_legend.test.tsx.snap b/x-pack/legacy/plugins/uptime/public/components/functional/charts/__tests__/__snapshots__/donut_chart_legend.test.tsx.snap index 3674497b538f3..e971576521b7d 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/charts/__tests__/__snapshots__/donut_chart_legend.test.tsx.snap +++ b/x-pack/legacy/plugins/uptime/public/components/functional/charts/__tests__/__snapshots__/donut_chart_legend.test.tsx.snap @@ -5,6 +5,7 @@ exports[`DonutChartLegend applies valid props as expected 1`] = ` diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/charts/__tests__/__snapshots__/donut_chart_legend_row.test.tsx.snap b/x-pack/legacy/plugins/uptime/public/components/functional/charts/__tests__/__snapshots__/donut_chart_legend_row.test.tsx.snap index 5f6508e299a28..bc6033ea7109a 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/charts/__tests__/__snapshots__/donut_chart_legend_row.test.tsx.snap +++ b/x-pack/legacy/plugins/uptime/public/components/functional/charts/__tests__/__snapshots__/donut_chart_legend_row.test.tsx.snap @@ -21,6 +21,7 @@ exports[`DonutChartLegendRow passes appropriate props 1`] = ` 23 diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/charts/__tests__/__snapshots__/monitor_bar_series.test.tsx.snap b/x-pack/legacy/plugins/uptime/public/components/functional/charts/__tests__/__snapshots__/monitor_bar_series.test.tsx.snap index 1f197294ebc15..78b2bfdecb87a 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/charts/__tests__/__snapshots__/monitor_bar_series.test.tsx.snap +++ b/x-pack/legacy/plugins/uptime/public/components/functional/charts/__tests__/__snapshots__/monitor_bar_series.test.tsx.snap @@ -13,7 +13,7 @@ exports[`MonitorBarSeries component renders a series when there are down items 1 - - - "A danger color", - } + Array [ + "A danger color", + ] } data={ Array [ diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/charts/__tests__/donut_chart_legend_row.test.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/charts/__tests__/donut_chart_legend_row.test.tsx index 82e2279edd892..49e887cc8f96c 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/charts/__tests__/donut_chart_legend_row.test.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/charts/__tests__/donut_chart_legend_row.test.tsx @@ -11,7 +11,7 @@ import React from 'react'; describe('DonutChartLegendRow', () => { it('passes appropriate props', () => { const wrapper = shallowWithIntl( - + ); expect(wrapper).toMatchSnapshot(); }); diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/charts/checks_chart.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/charts/checks_chart.tsx index c7ac0a59e4434..a88a9668660f7 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/charts/checks_chart.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/charts/checks_chart.tsx @@ -8,8 +8,6 @@ import { AreaSeries, Axis, Chart, - getAxisId, - getSpecId, Position, Settings, ScaleType, @@ -21,7 +19,6 @@ import { i18n } from '@kbn/i18n'; import { FormattedMessage } from '@kbn/i18n/react'; import { StatusData } from '../../../../common/graphql/types'; import { getChartDateLabel } from '../../../lib/helper'; -import { getColorsMap } from './get_colors_map'; import { useUrlParams } from '../../../hooks'; interface ChecksChartProps { @@ -45,8 +42,8 @@ interface ChecksChartProps { * @param props The props values required by this component. */ export const ChecksChart = ({ dangerColor, status, successColor }: ChecksChartProps) => { - const upSeriesSpecId = getSpecId('Up'); - const downSeriesSpecId = getSpecId('Down'); + const upSeriesSpecId = 'Up'; + const downSeriesSpecId = 'Down'; const [getUrlParams] = useUrlParams(); const { absoluteDateRangeStart: min, absoluteDateRangeEnd: max } = getUrlParams(); @@ -74,7 +71,7 @@ export const ChecksChart = ({ dangerColor, status, successColor }: ChecksChartPr Number(d).toFixed(0)} title={i18n.translate('xpack.uptime.monitorChart.checksChart.leftAxis.title', { @@ -93,7 +90,7 @@ export const ChecksChart = ({ dangerColor, status, successColor }: ChecksChartPr })} /> ({ x, [upString]: up || 0, @@ -107,7 +104,7 @@ export const ChecksChart = ({ dangerColor, status, successColor }: ChecksChartPr yScaleType={ScaleType.Linear} /> ({ x, [downString]: down || 0, diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/charts/donut_chart.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/charts/donut_chart.tsx index e2705e7cbacb3..50dca8577455d 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/charts/donut_chart.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/charts/donut_chart.tsx @@ -73,7 +73,7 @@ export const DonutChart = ({ height, down, up, width }: DonutChartProps) => { { ); diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/charts/donut_chart_legend_row.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/charts/donut_chart_legend_row.tsx index bf684a3446b9a..fc67a86db3b48 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/charts/donut_chart_legend_row.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/charts/donut_chart_legend_row.tsx @@ -23,9 +23,10 @@ interface Props { color: string; message: string; content: string | number; + 'data-test-subj': string; } -export const DonutChartLegendRow = ({ color, content, message }: Props) => ( +export const DonutChartLegendRow = ({ color, content, message, 'data-test-subj': dts }: Props) => ( @@ -33,6 +34,8 @@ export const DonutChartLegendRow = ({ color, content, message }: Props) => ( {message} - {content} + + {content} + ); diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/charts/duration_chart.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/charts/duration_chart.tsx index 012d848610940..775e8c0c06aa5 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/charts/duration_chart.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/charts/duration_chart.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { Axis, Chart, getAxisId, Position, timeFormatter, Settings } from '@elastic/charts'; +import { Axis, Chart, Position, timeFormatter, Settings } from '@elastic/charts'; import { EuiPanel, EuiTitle } from '@elastic/eui'; import React from 'react'; import { i18n } from '@kbn/i18n'; @@ -66,32 +66,32 @@ export const DurationChart = ({ - - - - getTickFormat(d)} - title={i18n.translate('xpack.uptime.monitorCharts.durationChart.leftAxis.title', { - defaultMessage: 'Duration ms', - })} - /> - {hasLines ? ( + {hasLines ? ( + + + + getTickFormat(d)} + title={i18n.translate('xpack.uptime.monitorCharts.durationChart.leftAxis.title', { + defaultMessage: 'Duration ms', + })} + /> - ) : ( - - )} - + + ) : ( + + )} diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/charts/duration_line_series_list.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/charts/duration_line_series_list.tsx index b6a65f193e913..872c79933d85a 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/charts/duration_line_series_list.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/charts/duration_line_series_list.tsx @@ -5,9 +5,8 @@ */ import React from 'react'; -import { LineSeries, CurveType, getSpecId } from '@elastic/charts'; +import { LineSeries, CurveType } from '@elastic/charts'; import { LocationDurationLine } from '../../../../common/graphql/types'; -import { getColorsMap } from './get_colors_map'; import { convertMicrosecondsToMilliseconds as microsToMillis } from '../../../lib/helper'; interface Props { @@ -21,9 +20,9 @@ export const DurationLineSeriesList = ({ lines, meanColor }: Props) => ( [x, microsToMillis(y || null)])} - id={getSpecId(`loc-avg-${name}`)} + id={`loc-avg-${name}`} key={`locline-${name}`} name={name} xAccessor={0} diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/charts/get_colors_map.ts b/x-pack/legacy/plugins/uptime/public/components/functional/charts/get_colors_map.ts deleted file mode 100644 index 83d181d91f8da..0000000000000 --- a/x-pack/legacy/plugins/uptime/public/components/functional/charts/get_colors_map.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { DataSeriesColorsValues, SpecId } from '@elastic/charts'; - -/** - * This is a helper function used to more easily define a basic map - * for color values for use with elastic charts. Support for multiple - * color values can be added in the future if needed. - * @param color a string containing a valid color value - * @param specId an ID generated by the elastic charts library - */ -export const getColorsMap = ( - color: string, - specId: SpecId -): Map => { - const map = new Map(); - map.set({ colorValues: [], specId }, color); - return map; -}; diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/charts/monitor_bar_series.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/charts/monitor_bar_series.tsx index 52b41416bd17b..a0cbdc5922123 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/charts/monitor_bar_series.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/charts/monitor_bar_series.tsx @@ -8,10 +8,8 @@ import { Axis, BarSeries, Chart, - getSpecId, ScaleType, Settings, - getAxisId, Position, timeFormatter, } from '@elastic/charts'; @@ -20,7 +18,6 @@ import React from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import { EuiText, EuiToolTip } from '@elastic/eui'; import { SummaryHistogramPoint } from '../../../../common/graphql/types'; -import { getColorsMap } from './get_colors_map'; import { getChartDateLabel, seriesHasDownValues } from '../../../lib/helper'; export interface MonitorBarSeriesProps { @@ -53,7 +50,7 @@ export const MonitorBarSeries = ({ dangerColor, histogramSeries, }: MonitorBarSeriesProps) => { - const id = getSpecId('downSeries'); + const id = 'downSeries'; return seriesHasDownValues(histogramSeries) ? (
@@ -61,14 +58,14 @@ export const MonitorBarSeries = ({ [timestamp, down])} id={id} + customSeriesColors={[dangerColor]} + data={(histogramSeries || []).map(({ timestamp, down }) => [timestamp, down])} name={i18n.translate('xpack.uptime.monitorList.downLineSeries.downLabel', { defaultMessage: 'Down checks', })} diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/charts/snapshot_histogram.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/charts/snapshot_histogram.tsx index bacd06dbe162d..52ba482ce7fc1 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/charts/snapshot_histogram.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/charts/snapshot_histogram.tsx @@ -4,22 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { - Axis, - BarSeries, - Chart, - getAxisId, - getSpecId, - Position, - timeFormatter, - Settings, -} from '@elastic/charts'; +import { Axis, BarSeries, Chart, Position, timeFormatter, Settings } from '@elastic/charts'; import { EuiEmptyPrompt, EuiTitle, EuiPanel } from '@elastic/eui'; import { i18n } from '@kbn/i18n'; import React, { useContext } from 'react'; import { FormattedMessage } from '@kbn/i18n/react'; import moment from 'moment'; -import { getColorsMap } from './get_colors_map'; import { getChartDateLabel } from '../../../lib/helper'; import { withUptimeGraphQL, UptimeGraphQLQueryProps } from '../../higher_order'; import { snapshotHistogramQuery } from '../../../queries/snapshot_histogram_query'; @@ -110,12 +100,12 @@ export const SnapshotHistogramComponent: React.FC = ({ const downMonitorsId = i18n.translate('xpack.uptime.snapshotHistogram.downMonitorsId', { defaultMessage: 'Down Monitors', }); - const downSpecId = getSpecId(downMonitorsId); + const downSpecId = downMonitorsId; const upMonitorsId = i18n.translate('xpack.uptime.snapshotHistogram.series.upLabel', { defaultMessage: 'Up', }); - const upSpecId = getSpecId(upMonitorsId); + const upSpecId = upMonitorsId; return ( <> @@ -148,21 +138,17 @@ export const SnapshotHistogramComponent: React.FC = ({ showLegend={false} /> = ({ })} /> [x, downCount || 0])} id={downSpecId} name={i18n.translate('xpack.uptime.snapshotHistogram.series.downLabel', { @@ -185,7 +171,7 @@ export const SnapshotHistogramComponent: React.FC = ({ yScaleType="linear" /> [x, upCount || 0])} id={upSpecId} name={upMonitorsId} diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/empty_state/__tests__/__snapshots__/data_missing.test.tsx.snap b/x-pack/legacy/plugins/uptime/public/components/functional/empty_state/__tests__/__snapshots__/data_missing.test.tsx.snap index 4dfc837c29b55..36c54758cf116 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/empty_state/__tests__/__snapshots__/data_missing.test.tsx.snap +++ b/x-pack/legacy/plugins/uptime/public/components/functional/empty_state/__tests__/__snapshots__/data_missing.test.tsx.snap @@ -20,7 +20,7 @@ exports[`DataMissing component renders basePath and headingMessage 1`] = ` values={ Object { "configureHeartbeatLink": @@ -301,7 +299,6 @@ exports[`EmptyState component does not render empty state with appropriate base exports[`EmptyState component doesn't render child components when count is falsey 1`] = ` @@ -783,7 +778,6 @@ exports[`EmptyState component renders child components when count is truthy 1`] exports[`EmptyState component renders error message when an error occurs 1`] = ` { it('renders basePath and headingMessage', () => { - const component = shallowWithIntl(); + const component = shallowWithIntl(); expect(component).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/empty_state/__tests__/empty_state.test.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/empty_state/__tests__/empty_state.test.tsx index 0077292d91a46..32d55519acf27 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/empty_state/__tests__/empty_state.test.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/empty_state/__tests__/empty_state.test.tsx @@ -24,7 +24,7 @@ describe('EmptyState component', () => { it('renders child components when count is truthy', () => { const component = shallowWithIntl( - +
Foo
Bar
Baz
@@ -35,7 +35,7 @@ describe('EmptyState component', () => { it(`doesn't render child components when count is falsey`, () => { const component = mountWithIntl( - +
Shouldn't be rendered
); @@ -57,7 +57,7 @@ describe('EmptyState component', () => { }, ]; const component = mountWithIntl( - +
Shouldn't appear...
); @@ -66,7 +66,7 @@ describe('EmptyState component', () => { it('renders loading state if no errors or doc count', () => { const component = mountWithIntl( - +
Should appear even while loading...
); @@ -81,7 +81,7 @@ describe('EmptyState component', () => { indexExists: true, }; const component = mountWithIntl( - +
If this is in the snapshot the test should fail
); @@ -91,7 +91,7 @@ describe('EmptyState component', () => { it('notifies when index does not exist', () => { statesIndexStatus.indexExists = false; const component = mountWithIntl( - +
This text should not render
); diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/empty_state/data_missing.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/empty_state/data_missing.tsx index 023edec023ddf..f8110953f6146 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/empty_state/data_missing.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/empty_state/data_missing.tsx @@ -14,48 +14,51 @@ import { EuiLink, } from '@elastic/eui'; import { FormattedMessage } from '@kbn/i18n/react'; -import React from 'react'; +import React, { useContext } from 'react'; +import { UptimeSettingsContext } from '../../../contexts'; interface DataMissingProps { - basePath: string; headingMessage: string; } -export const DataMissing = ({ basePath, headingMessage }: DataMissingProps) => ( - - - - - -

{headingMessage}

- - } - body={ -

- - - - ), - }} - /> -

- } - /> -
-
-
-); +export const DataMissing = ({ headingMessage }: DataMissingProps) => { + const { basePath } = useContext(UptimeSettingsContext); + return ( + + + + + +

{headingMessage}

+ + } + body={ +

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

+ } + /> +
+
+
+ ); +}; diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/empty_state/empty_state.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/empty_state/empty_state.tsx index 2a39226c5b746..d2d46dff3b9f5 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/empty_state/empty_state.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/empty_state/empty_state.tsx @@ -18,13 +18,12 @@ interface EmptyStateQueryResult { } interface EmptyStateProps { - basePath: string; children: JSX.Element[] | JSX.Element; } type Props = UptimeGraphQLQueryProps & EmptyStateProps; -export const EmptyStateComponent = ({ basePath, children, data, errors }: Props) => { +export const EmptyStateComponent = ({ children, data, errors }: Props) => { if (errors) { return ; } @@ -33,7 +32,6 @@ export const EmptyStateComponent = ({ basePath, children, data, errors }: Props) if (!indexExists) { return ( ( - - - - {!message - ? i18n.translate('xpack.uptime.emptyStatusBar.defaultMessage', { - defaultMessage: 'No data found for monitor id {monitorId}', - description: - 'This is the default message we display in a status bar when there is no data available for an uptime monitor.', - values: { monitorId }, - }) - : message} - - - -); diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/filter_group/__tests__/__snapshots__/filter_popover.test.tsx.snap b/x-pack/legacy/plugins/uptime/public/components/functional/filter_group/__tests__/__snapshots__/filter_popover.test.tsx.snap index 2022390d0e5d9..0e6ea3662b97e 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/filter_group/__tests__/__snapshots__/filter_popover.test.tsx.snap +++ b/x-pack/legacy/plugins/uptime/public/components/functional/filter_group/__tests__/__snapshots__/filter_popover.test.tsx.snap @@ -13,6 +13,7 @@ exports[`FilterPopover component does not show item list when loading 1`] = ` /> } closePopover={[Function]} + data-test-subj="filter-popover_test" display="inlineBlock" hasArrow={true} id="test" @@ -49,6 +50,7 @@ exports[`FilterPopover component renders without errors for valid props 1`] = ` /> } closePopover={[Function]} + data-test-subj="filter-popover_test" display="inlineBlock" hasArrow={true} id="test" @@ -83,6 +85,7 @@ exports[`FilterPopover component returns selected items on popover close 1`] = `
{ props = { fieldName: 'foo', id: 'test', - isLoading: false, + loading: false, items: ['first', 'second', 'third', 'fourth'], onFilterFieldChange: jest.fn(), selectedItems: ['first', 'third'], @@ -47,7 +47,7 @@ describe('FilterPopover component', () => { }); it('does not show item list when loading', () => { - props.isLoading = true; + props.loading = true; const wrapper = shallowWithIntl(); expect(wrapper).toMatchSnapshot(); }); diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/filter_group/__tests__/parse_filter_map.test.ts b/x-pack/legacy/plugins/uptime/public/components/functional/filter_group/__tests__/parse_filter_map.test.ts new file mode 100644 index 0000000000000..8deee25377850 --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/components/functional/filter_group/__tests__/parse_filter_map.test.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { parseFiltersMap } from '../parse_filter_map'; + +describe('parseFiltersMap', () => { + it('provides values from valid filter string', () => { + expect( + parseFiltersMap( + '[["url.port",["5601","80"]],["observer.geo.name",["us-east-2"]],["monitor.type",["http","tcp"]]]' + ) + ).toMatchSnapshot(); + }); + + it('returns an empty object for invalid filter', () => { + expect(() => parseFiltersMap('some invalid string')).toThrowErrorMatchingSnapshot(); + }); +}); diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/filter_group/filter_group.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/filter_group/filter_group.tsx index f27514bf76a11..351302fb38356 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/filter_group/filter_group.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/filter_group/filter_group.tsx @@ -5,35 +5,49 @@ */ import { EuiFilterGroup } from '@elastic/eui'; -import React from 'react'; -import { get } from 'lodash'; +import React, { useEffect } from 'react'; import { i18n } from '@kbn/i18n'; -import { FilterBar as FilterBarType } from '../../../../common/graphql/types'; -import { UptimeGraphQLQueryProps, withUptimeGraphQL } from '../../higher_order'; -import { filterBarQuery } from '../../../queries'; +import { connect } from 'react-redux'; import { FilterPopoverProps, FilterPopover } from './filter_popover'; import { FilterStatusButton } from './filter_status_button'; +import { OverviewFilters } from '../../../../common/runtime_types'; +import { fetchOverviewFilters, GetOverviewFiltersPayload } from '../../../state/actions'; +import { AppState } from '../../../state'; +import { useUrlParams } from '../../../hooks'; +import { parseFiltersMap } from './parse_filter_map'; -interface FilterBarQueryResult { - filters?: FilterBarType; +interface OwnProps { + currentFilter: any; + onFilterUpdate: any; + dateRangeStart: string; + dateRangeEnd: string; + filters?: string; + statusFilter?: string; } -interface FilterBarDropdownsProps { - currentFilter: string; - onFilterUpdate: (kuery: string) => void; +interface StoreProps { + esKuery: string; + lastRefresh: number; + loading: boolean; + overviewFilters: OverviewFilters; } -type Props = UptimeGraphQLQueryProps & FilterBarDropdownsProps; +interface DispatchProps { + loadFilterGroup: typeof fetchOverviewFilters; +} + +type Props = OwnProps & StoreProps & DispatchProps; + +type PresentationalComponentProps = Pick & + Pick; -export const FilterGroupComponent = ({ - loading: isLoading, +export const PresentationalComponent: React.FC = ({ currentFilter, - data, + overviewFilters, + loading, onFilterUpdate, -}: Props) => { - const locations = get(data, 'filterBar.locations', []); - const ports = get(data, 'filterBar.ports', []); - const schemes = get(data, 'filterBar.schemes', []); +}) => { + const { locations, ports, schemes, tags } = overviewFilters; let filterKueries: Map; try { @@ -67,36 +81,50 @@ export const FilterGroupComponent = ({ const filterPopoverProps: FilterPopoverProps[] = [ { + loading, + onFilterFieldChange, fieldName: 'observer.geo.name', id: 'location', - isLoading, items: locations, - onFilterFieldChange, selectedItems: getSelectedItems('observer.geo.name'), title: i18n.translate('xpack.uptime.filterBar.options.location.name', { defaultMessage: 'Location', }), }, { + loading, + onFilterFieldChange, fieldName: 'url.port', id: 'port', - isLoading, - items: ports, - onFilterFieldChange, + disabled: ports.length === 0, + items: ports.map((p: number) => p.toString()), selectedItems: getSelectedItems('url.port'), title: i18n.translate('xpack.uptime.filterBar.options.portLabel', { defaultMessage: 'Port' }), }, { + loading, + onFilterFieldChange, fieldName: 'monitor.type', id: 'scheme', - isLoading, + disabled: schemes.length === 0, items: schemes, - onFilterFieldChange, selectedItems: getSelectedItems('monitor.type'), title: i18n.translate('xpack.uptime.filterBar.options.schemeLabel', { defaultMessage: 'Scheme', }), }, + { + loading, + onFilterFieldChange, + fieldName: 'tags', + id: 'tags', + disabled: tags.length === 0, + items: tags, + selectedItems: getSelectedItems('tags'), + title: i18n.translate('xpack.uptime.filterBar.options.tagsLabel', { + defaultMessage: 'Tags', + }), + }, ]; return ( @@ -124,7 +152,59 @@ export const FilterGroupComponent = ({ ); }; -export const FilterGroup = withUptimeGraphQL( - FilterGroupComponent, - filterBarQuery -); +export const Container: React.FC = ({ + currentFilter, + esKuery, + filters, + loading, + loadFilterGroup, + dateRangeStart, + dateRangeEnd, + overviewFilters, + statusFilter, + onFilterUpdate, +}: Props) => { + const [getUrlParams] = useUrlParams(); + const { filters: urlFilters } = getUrlParams(); + useEffect(() => { + const filterSelections = parseFiltersMap(urlFilters); + loadFilterGroup({ + dateRangeStart, + dateRangeEnd, + locations: filterSelections.locations ?? [], + ports: filterSelections.ports ?? [], + schemes: filterSelections.schemes ?? [], + search: esKuery, + statusFilter, + tags: filterSelections.tags ?? [], + }); + }, [dateRangeStart, dateRangeEnd, esKuery, filters, statusFilter, urlFilters, loadFilterGroup]); + return ( + + ); +}; + +const mapStateToProps = ({ + overviewFilters: { loading, filters }, + ui: { esKuery, lastRefresh }, +}: AppState): StoreProps => ({ + esKuery, + overviewFilters: filters, + lastRefresh, + loading, +}); + +const mapDispatchToProps = (dispatch: any): DispatchProps => ({ + loadFilterGroup: (payload: GetOverviewFiltersPayload) => dispatch(fetchOverviewFilters(payload)), +}); + +export const FilterGroup = connect( + // @ts-ignore connect is expecting null | undefined for some reason + mapStateToProps, + mapDispatchToProps +)(Container); diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/filter_group/filter_popover.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/filter_group/filter_popover.tsx index 6e73090782b04..f96fef609fe76 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/filter_group/filter_popover.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/filter_group/filter_popover.tsx @@ -14,7 +14,8 @@ import { LocationLink } from '../monitor_list'; export interface FilterPopoverProps { fieldName: string; id: string; - isLoading: boolean; + loading: boolean; + disabled?: boolean; items: string[]; onFilterFieldChange: (fieldName: string, values: string[]) => void; selectedItems: string[]; @@ -27,7 +28,8 @@ const isItemSelected = (selectedItems: string[], item: string): 'on' | undefined export const FilterPopover = ({ fieldName, id, - isLoading, + disabled, + loading, items, onFilterFieldChange, selectedItems, @@ -48,10 +50,10 @@ export const FilterPopover = ({ }, [searchQuery, items]); return ( - // @ts-ignore zIndex prop is not described in the typing yet 0} numFilters={items.length} numActiveFilters={tempSelectedItems.length} @@ -66,6 +68,7 @@ export const FilterPopover = ({ setIsOpen(false); onFilterFieldChange(fieldName, tempSelectedItems); }} + data-test-subj={`filter-popover_${id}`} id={id} isOpen={isOpen} ownFocus={true} @@ -77,7 +80,7 @@ export const FilterPopover = ({ disabled={items.length === 0} onSearch={query => setSearchQuery(query)} placeholder={ - isLoading + loading ? i18n.translate('xpack.uptime.filterPopout.loadingMessage', { defaultMessage: 'Loading...', }) @@ -90,10 +93,11 @@ export const FilterPopover = ({ } /> - {!isLoading && + {!loading && itemsToDisplay.map(item => ( toggleSelectedItems(item, tempSelectedItems, setTempSelectedItems)} > diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/filter_group/filter_status_button.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/filter_group/filter_status_button.tsx index 95f4c30337d62..abbe72530fd80 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/filter_group/filter_status_button.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/filter_group/filter_status_button.tsx @@ -11,6 +11,7 @@ import { useUrlParams } from '../../../hooks'; export interface FilterStatusButtonProps { content: string; dataTestSubj: string; + isDisabled?: boolean; value: string; withNext: boolean; } @@ -18,6 +19,7 @@ export interface FilterStatusButtonProps { export const FilterStatusButton = ({ content, dataTestSubj, + isDisabled, value, withNext, }: FilterStatusButtonProps) => { @@ -27,6 +29,7 @@ export const FilterStatusButton = ({ { const nextFilter = { statusFilter: urlValue === value ? '' : value, pagination: '' }; setUrlParams(nextFilter); diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/filter_group/parse_filter_map.ts b/x-pack/legacy/plugins/uptime/public/components/functional/filter_group/parse_filter_map.ts new file mode 100644 index 0000000000000..08766521799ea --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/components/functional/filter_group/parse_filter_map.ts @@ -0,0 +1,38 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +interface FilterField { + name: string; + fieldName: string; +} + +/** + * These are the only filter fields we are looking to catch at the moment. + * If your code needs to support custom fields, introduce a second parameter to + * `parseFiltersMap` to take a list of FilterField objects. + */ +const filterWhitelist: FilterField[] = [ + { name: 'ports', fieldName: 'url.port' }, + { name: 'locations', fieldName: 'observer.geo.name' }, + { name: 'tags', fieldName: 'tags' }, + { name: 'schemes', fieldName: 'monitor.type' }, +]; + +export const parseFiltersMap = (filterMapString: string) => { + if (!filterMapString) { + return {}; + } + const filterSlices: { [key: string]: any } = {}; + try { + const map = new Map(JSON.parse(filterMapString)); + filterWhitelist.forEach(({ name, fieldName }) => { + filterSlices[name] = map.get(fieldName) ?? []; + }); + return filterSlices; + } catch { + throw new Error('Unable to parse invalid filter string'); + } +}; diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/filter_group/uptime_filter_button.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/filter_group/uptime_filter_button.tsx index fc0c6342bd6e9..0e05c17d57353 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/filter_group/uptime_filter_button.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/filter_group/uptime_filter_button.tsx @@ -8,6 +8,7 @@ import { EuiFilterButton } from '@elastic/eui'; import React from 'react'; interface UptimeFilterButtonProps { + isDisabled?: boolean; isSelected: boolean; numFilters: number; numActiveFilters: number; @@ -16,6 +17,7 @@ interface UptimeFilterButtonProps { } export const UptimeFilterButton = ({ + isDisabled, isSelected, numFilters, numActiveFilters, @@ -25,6 +27,7 @@ export const UptimeFilterButton = ({ (undefined); const [isLoadingIndexPattern, setIsLoadingIndexPattern] = useState(true); const [isLoadingSuggestions, setIsLoadingSuggestions] = useState(false); let currentRequestCheck: string; + useIndexPattern((result: any) => setIndexPattern(toStaticIndexPattern(result))); + useEffect(() => { - getIndexPattern(basePath, (result: any) => setIndexPattern(toStaticIndexPattern(result))); - setIsLoadingIndexPattern(false); - }, [basePath]); + if (indexPattern !== undefined) { + setIsLoadingIndexPattern(false); + } + }, [indexPattern]); const [getUrlParams, updateUrlParams] = useUrlParams(); const { search: kuery } = getUrlParams(); diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/location_map/__tests__/__snapshots__/location_status_tags.test.tsx.snap b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/__tests__/__snapshots__/location_status_tags.test.tsx.snap new file mode 100644 index 0000000000000..6228183e7c2b2 --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/__tests__/__snapshots__/location_status_tags.test.tsx.snap @@ -0,0 +1,577 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`StatusByLocation component renders when all locations are down 1`] = ` +.c3 { + display: inline-block; + margin-left: 4px; +} + +.c2 { + font-weight: 600; +} + +.c1 { + margin-bottom: 5px; +} + +.c0 { + padding: 10px; + max-height: 229px; + overflow: hidden; +} + +
+ +
+ + + +
+
+ Islamabad +
+
+
+
+
+ +
+
+ 3d ago +
+
+
+
+
+ + + +
+
+ Berlin +
+
+
+
+
+ +
+
+ 3d ago +
+
+
+
+
+ +
+`; + +exports[`StatusByLocation component renders when all locations are up 1`] = ` +.c3 { + display: inline-block; + margin-left: 4px; +} + +.c2 { + font-weight: 600; +} + +.c1 { + margin-bottom: 5px; +} + +.c0 { + padding: 10px; + max-height: 229px; + overflow: hidden; +} + +
+ + +
+ + + +
+
+ Islamabad +
+
+
+
+
+ +
+
+ 3d ago +
+
+
+
+
+ + + +
+
+ Berlin +
+
+
+
+
+ +
+
+ 3d ago +
+
+
+
+
+
+`; + +exports[`StatusByLocation component renders when there are many location 1`] = ` +Array [ + .c3 { + display: inline-block; + margin-left: 4px; +} + +.c2 { + font-weight: 600; +} + +.c1 { + margin-bottom: 5px; +} + +.c0 { + padding: 10px; + max-height: 229px; + overflow: hidden; +} + +
+ +
+ + + +
+
+ Islamabad +
+
+
+
+
+ +
+
+ 3d ago +
+
+
+
+
+ + + +
+
+ Berlin +
+
+
+
+
+ +
+
+ 3d ago +
+
+
+
+
+ + + +
+
+ st-paul +
+
+
+
+
+ +
+
+ 3d ago +
+
+
+
+
+ + + +
+
+ Tokya +
+
+
+
+
+ +
+
+ 3d ago +
+
+
+
+
+ + + +
+
+ New York +
+
+
+
+
+ +
+
+ 3d ago +
+
+
+
+
+ + + +
+
+ Toronto +
+
+
+
+
+ +
+
+ 3d ago +
+
+
+
+
+ + + +
+
+ Sydney +
+
+
+
+
+ +
+
+ 3d ago +
+
+
+
+
+ + + +
+
+ Paris +
+
+
+
+
+ +
+
+ 3d ago +
+
+
+
+
+ +
, + .c0 { + padding-left: 18px; +} + +
+
+
+

+ 1 Others ... +

+
+
+
, +] +`; diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/location_map/__tests__/location_status_tags.test.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/__tests__/location_status_tags.test.tsx new file mode 100644 index 0000000000000..21e5881654533 --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/__tests__/location_status_tags.test.tsx @@ -0,0 +1,101 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import moment from 'moment'; +import { renderWithIntl } from 'test_utils/enzyme_helpers'; +import { MonitorLocation } from '../../../../../common/runtime_types/monitor'; +import { LocationStatusTags } from '../'; + +describe('StatusByLocation component', () => { + let monitorLocations: MonitorLocation[]; + + const start = moment('2020-01-10T12:22:32.567Z'); + beforeAll(() => { + moment.prototype.fromNow = jest.fn((date: string) => start.from(date)); + }); + + it('renders when there are many location', () => { + monitorLocations = [ + { + summary: { up: 0, down: 1 }, + geo: { name: 'Islamabad', location: { lat: '52.487448', lon: ' 13.394798' } }, + timestamp: '2020-01-09T12:22:32.567Z', + }, + { + summary: { up: 0, down: 1 }, + geo: { name: 'Berlin', location: { lat: '52.487448', lon: ' 13.394798' } }, + timestamp: '2020-01-09T12:22:28.825Z', + }, + { + summary: { up: 0, down: 1 }, + geo: { name: 'st-paul', location: { lat: '52.487448', lon: ' 13.394798' } }, + timestamp: '2020-01-09T12:22:31.586Z', + }, + { + summary: { up: 0, down: 1 }, + geo: { name: 'Tokya', location: { lat: '52.487448', lon: ' 13.394798' } }, + timestamp: '2020-01-09T12:22:25.771Z', + }, + { + summary: { up: 0, down: 1 }, + geo: { name: 'New York', location: { lat: '52.487448', lon: ' 13.394798' } }, + timestamp: '2020-01-09T12:22:27.485Z', + }, + { + summary: { up: 0, down: 1 }, + geo: { name: 'Toronto', location: { lat: '52.487448', lon: ' 13.394798' } }, + timestamp: '2020-01-09T12:22:28.815Z', + }, + { + summary: { up: 0, down: 1 }, + geo: { name: 'Sydney', location: { lat: '52.487448', lon: ' 13.394798' } }, + timestamp: '2020-01-09T12:22:32.132Z', + }, + { + summary: { up: 0, down: 1 }, + geo: { name: 'Paris', location: { lat: '52.487448', lon: ' 13.394798' } }, + timestamp: '2020-01-09T12:22:32.973Z', + }, + ]; + const component = renderWithIntl(); + expect(component).toMatchSnapshot(); + }); + + it('renders when all locations are up', () => { + monitorLocations = [ + { + summary: { up: 4, down: 0 }, + geo: { name: 'Islamabad', location: { lat: '52.487448', lon: ' 13.394798' } }, + timestamp: '2020-01-09T12:22:32.567Z', + }, + { + summary: { up: 4, down: 0 }, + geo: { name: 'Berlin', location: { lat: '52.487448', lon: ' 13.394798' } }, + timestamp: '2020-01-08T12:22:28.825Z', + }, + ]; + const component = renderWithIntl(); + expect(component).toMatchSnapshot(); + }); + + it('renders when all locations are down', () => { + monitorLocations = [ + { + summary: { up: 0, down: 2 }, + geo: { name: 'Islamabad', location: { lat: '52.487448', lon: ' 13.394798' } }, + timestamp: '2020-01-06T12:22:32.567Z', + }, + { + summary: { up: 0, down: 2 }, + geo: { name: 'Berlin', location: { lat: '52.487448', lon: ' 13.394798' } }, + timestamp: '2020-01-09T12:22:28.825Z', + }, + ]; + const component = renderWithIntl(); + expect(component).toMatchSnapshot(); + }); +}); diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/__mocks__/mock.ts b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/__mocks__/mock.ts deleted file mode 100644 index 9b902651690bf..0000000000000 --- a/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/__mocks__/mock.ts +++ /dev/null @@ -1,185 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import lowPolyLayerFeatures from '../low_poly_layer.json'; - -export const mockDownPointsLayer = { - id: 'down_points', - label: 'Down Locations', - sourceDescriptor: { - type: 'GEOJSON_FILE', - __featureCollection: { - features: [ - { - type: 'feature', - geometry: { - type: 'Point', - coordinates: [13.399262, 52.487239], - }, - }, - { - type: 'feature', - geometry: { - type: 'Point', - coordinates: [13.399262, 55.487239], - }, - }, - { - type: 'feature', - geometry: { - type: 'Point', - coordinates: [14.399262, 54.487239], - }, - }, - ], - type: 'FeatureCollection', - }, - }, - visible: true, - style: { - type: 'VECTOR', - properties: { - fillColor: { - type: 'STATIC', - options: { - color: '#BC261E', - }, - }, - lineColor: { - type: 'STATIC', - options: { - color: '#fff', - }, - }, - lineWidth: { - type: 'STATIC', - options: { - size: 2, - }, - }, - iconSize: { - type: 'STATIC', - options: { - size: 6, - }, - }, - }, - }, - type: 'VECTOR', -}; - -export const mockUpPointsLayer = { - id: 'up_points', - label: 'Up Locations', - sourceDescriptor: { - type: 'GEOJSON_FILE', - __featureCollection: { - features: [ - { - type: 'feature', - geometry: { - type: 'Point', - coordinates: [13.399262, 52.487239], - }, - }, - { - type: 'feature', - geometry: { - type: 'Point', - coordinates: [13.399262, 55.487239], - }, - }, - { - type: 'feature', - geometry: { - type: 'Point', - coordinates: [14.399262, 54.487239], - }, - }, - ], - type: 'FeatureCollection', - }, - }, - visible: true, - style: { - type: 'VECTOR', - properties: { - fillColor: { - type: 'STATIC', - options: { - color: '#98A2B2', - }, - }, - lineColor: { - type: 'STATIC', - options: { - color: '#fff', - }, - }, - lineWidth: { - type: 'STATIC', - options: { - size: 2, - }, - }, - iconSize: { - type: 'STATIC', - options: { - size: 6, - }, - }, - }, - }, - type: 'VECTOR', -}; - -export const mockLayerList = [ - { - id: 'low_poly_layer', - label: 'World countries', - minZoom: 0, - maxZoom: 24, - alpha: 1, - sourceDescriptor: { - id: 'b7486535-171b-4d3b-bb2e-33c1a0a2854c', - type: 'GEOJSON_FILE', - __featureCollection: lowPolyLayerFeatures, - }, - visible: true, - style: { - type: 'VECTOR', - properties: { - fillColor: { - type: 'STATIC', - options: { - color: '#cad3e4', - }, - }, - lineColor: { - type: 'STATIC', - options: { - color: '#fff', - }, - }, - lineWidth: { - type: 'STATIC', - options: { - size: 0, - }, - }, - iconSize: { - type: 'STATIC', - options: { - size: 6, - }, - }, - }, - }, - type: 'VECTOR', - }, - mockDownPointsLayer, - mockUpPointsLayer, -]; diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/__tests__/__mocks__/mock.ts b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/__tests__/__mocks__/mock.ts new file mode 100644 index 0000000000000..291ab555fbdc6 --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/__tests__/__mocks__/mock.ts @@ -0,0 +1,185 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import lowPolyLayerFeatures from '../../low_poly_layer.json'; + +export const mockDownPointsLayer = { + id: 'down_points', + label: 'Down Locations', + sourceDescriptor: { + type: 'GEOJSON_FILE', + __featureCollection: { + features: [ + { + type: 'feature', + geometry: { + type: 'Point', + coordinates: [13.399262, 52.487239], + }, + }, + { + type: 'feature', + geometry: { + type: 'Point', + coordinates: [13.399262, 55.487239], + }, + }, + { + type: 'feature', + geometry: { + type: 'Point', + coordinates: [14.399262, 54.487239], + }, + }, + ], + type: 'FeatureCollection', + }, + }, + visible: true, + style: { + type: 'VECTOR', + properties: { + fillColor: { + type: 'STATIC', + options: { + color: '#BC261E', + }, + }, + lineColor: { + type: 'STATIC', + options: { + color: '#fff', + }, + }, + lineWidth: { + type: 'STATIC', + options: { + size: 2, + }, + }, + iconSize: { + type: 'STATIC', + options: { + size: 6, + }, + }, + }, + }, + type: 'VECTOR', +}; + +export const mockUpPointsLayer = { + id: 'up_points', + label: 'Up Locations', + sourceDescriptor: { + type: 'GEOJSON_FILE', + __featureCollection: { + features: [ + { + type: 'feature', + geometry: { + type: 'Point', + coordinates: [13.399262, 52.487239], + }, + }, + { + type: 'feature', + geometry: { + type: 'Point', + coordinates: [13.399262, 55.487239], + }, + }, + { + type: 'feature', + geometry: { + type: 'Point', + coordinates: [14.399262, 54.487239], + }, + }, + ], + type: 'FeatureCollection', + }, + }, + visible: true, + style: { + type: 'VECTOR', + properties: { + fillColor: { + type: 'STATIC', + options: { + color: '#98A2B2', + }, + }, + lineColor: { + type: 'STATIC', + options: { + color: '#fff', + }, + }, + lineWidth: { + type: 'STATIC', + options: { + size: 2, + }, + }, + iconSize: { + type: 'STATIC', + options: { + size: 6, + }, + }, + }, + }, + type: 'VECTOR', +}; + +export const mockLayerList = [ + { + id: 'low_poly_layer', + label: 'World countries', + minZoom: 0, + maxZoom: 24, + alpha: 1, + sourceDescriptor: { + id: 'b7486535-171b-4d3b-bb2e-33c1a0a2854c', + type: 'GEOJSON_FILE', + __featureCollection: lowPolyLayerFeatures, + }, + visible: true, + style: { + type: 'VECTOR', + properties: { + fillColor: { + type: 'STATIC', + options: { + color: '#cad3e4', + }, + }, + lineColor: { + type: 'STATIC', + options: { + color: '#fff', + }, + }, + lineWidth: { + type: 'STATIC', + options: { + size: 0, + }, + }, + iconSize: { + type: 'STATIC', + options: { + size: 6, + }, + }, + }, + }, + type: 'VECTOR', + }, + mockDownPointsLayer, + mockUpPointsLayer, +]; diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/__tests__/map_config.test.ts b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/__tests__/map_config.test.ts new file mode 100644 index 0000000000000..7d53d784ff338 --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/__tests__/map_config.test.ts @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { getLayerList } from '../map_config'; +import { mockLayerList } from './__mocks__/mock'; +import { LocationPoint } from '../embedded_map'; +import { UptimeAppColors } from '../../../../../uptime_app'; + +jest.mock('uuid', () => { + return { + v4: jest.fn(() => 'uuid.v4()'), + }; +}); + +describe('map_config', () => { + let upPoints: LocationPoint[]; + let downPoints: LocationPoint[]; + let colors: Pick; + + beforeEach(() => { + upPoints = [ + { lat: '52.487239', lon: '13.399262' }, + { lat: '55.487239', lon: '13.399262' }, + { lat: '54.487239', lon: '14.399262' }, + ]; + downPoints = [ + { lat: '52.487239', lon: '13.399262' }, + { lat: '55.487239', lon: '13.399262' }, + { lat: '54.487239', lon: '14.399262' }, + ]; + colors = { + danger: '#BC261E', + gray: '#000', + }; + }); + + describe('#getLayerList', () => { + test('it returns the low poly layer', () => { + const layerList = getLayerList(upPoints, downPoints, colors); + expect(layerList).toStrictEqual(mockLayerList); + }); + }); +}); diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/embedded_map.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/embedded_map.tsx index 93de1d478fb83..9b20651fadb86 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/embedded_map.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/embedded_map.tsx @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import React, { useEffect, useState } from 'react'; +import React, { useEffect, useState, useContext, useRef } from 'react'; import uuid from 'uuid'; import styled from 'styled-components'; @@ -15,6 +15,7 @@ import { MAP_SAVED_OBJECT_TYPE } from '../../../../../../maps/common/constants'; import { MapEmbeddable } from './types'; import { getLayerList } from './map_config'; +import { UptimeSettingsContext } from '../../../../contexts'; export interface EmbeddedMapProps { upPoints: LocationPoint[]; @@ -45,29 +46,44 @@ const EmbeddedPanel = styled.div` `; export const EmbeddedMap = ({ upPoints, downPoints }: EmbeddedMapProps) => { + const { colors } = useContext(UptimeSettingsContext); const [embeddable, setEmbeddable] = useState(); - const embeddableRoot: React.RefObject = React.createRef(); + const embeddableRoot: React.RefObject = useRef(null); const factory = start.getEmbeddableFactory(MAP_SAVED_OBJECT_TYPE); const input = { id: uuid.v4(), filters: [], hidePanelTitles: true, - query: { query: '', language: 'kuery' }, - refreshConfig: { value: 0, pause: false }, + query: { + query: '', + language: 'kuery', + }, + refreshConfig: { + value: 0, + pause: false, + }, viewMode: 'view', isLayerTOCOpen: false, hideFilterActions: true, - mapCenter: { lon: 11, lat: 47, zoom: 0 }, + // Zoom Lat/Lon values are set to make sure map is in center in the panel + // It wil also omit Greenland/Antarctica etc + mapCenter: { + lon: 11, + lat: 20, + zoom: 0, + }, disableInteractive: true, disableTooltipControl: true, hideToolbarOverlay: true, + hideLayerControl: true, + hideViewControl: true, }; useEffect(() => { async function setupEmbeddable() { const mapState = { - layerList: getLayerList(upPoints, downPoints), + layerList: getLayerList(upPoints, downPoints, colors), title: i18n.MAP_TITLE, }; // @ts-ignore @@ -76,16 +92,19 @@ export const EmbeddedMap = ({ upPoints, downPoints }: EmbeddedMapProps) => { setEmbeddable(embeddableObject); } setupEmbeddable(); + // we want this effect to execute exactly once after the component mounts // eslint-disable-next-line react-hooks/exhaustive-deps }, []); + // update map layers based on points useEffect(() => { if (embeddable) { - embeddable.setLayerList(getLayerList(upPoints, downPoints)); + embeddable.setLayerList(getLayerList(upPoints, downPoints, colors)); } - }, [upPoints, downPoints, embeddable]); + }, [upPoints, downPoints, embeddable, colors]); + // We can only render after embeddable has already initialized useEffect(() => { if (embeddableRoot.current && embeddable) { embeddable.render(embeddableRoot.current); diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/map_config.test.ts b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/map_config.test.ts deleted file mode 100644 index 1e8e5b6012a79..0000000000000 --- a/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/map_config.test.ts +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { getLayerList } from './map_config'; -import { mockLayerList } from './__mocks__/mock'; -import { LocationPoint } from './embedded_map'; - -jest.mock('uuid', () => { - return { - v4: jest.fn(() => 'uuid.v4()'), - }; -}); - -describe('map_config', () => { - let upPoints: LocationPoint[]; - let downPoints: LocationPoint[]; - - beforeEach(() => { - upPoints = [ - { lat: '52.487239', lon: '13.399262' }, - { lat: '55.487239', lon: '13.399262' }, - { lat: '54.487239', lon: '14.399262' }, - ]; - downPoints = [ - { lat: '52.487239', lon: '13.399262' }, - { lat: '55.487239', lon: '13.399262' }, - { lat: '54.487239', lon: '14.399262' }, - ]; - }); - - describe('#getLayerList', () => { - test('it returns the low poly layer', () => { - const layerList = getLayerList(upPoints, downPoints); - expect(layerList).toStrictEqual(mockLayerList); - }); - }); -}); diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/map_config.ts b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/map_config.ts index 608df8b235f00..d4601baefdf30 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/map_config.ts +++ b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/embeddables/map_config.ts @@ -6,14 +6,19 @@ import lowPolyLayerFeatures from './low_poly_layer.json'; import { LocationPoint } from './embedded_map'; +import { UptimeAppColors } from '../../../../uptime_app'; /** * Returns `Source/Destination Point-to-point` Map LayerList configuration, with a source, * destination, and line layer for each of the provided indexPatterns * */ -export const getLayerList = (upPoints: LocationPoint[], downPoints: LocationPoint[]) => { - return [getLowPolyLayer(), getDownPointsLayer(downPoints), getUpPointsLayer(upPoints)]; +export const getLayerList = ( + upPoints: LocationPoint[], + downPoints: LocationPoint[], + { gray, danger }: Pick +) => { + return [getLowPolyLayer(), getDownPointsLayer(downPoints, danger), getUpPointsLayer(upPoints)]; }; export const getLowPolyLayer = () => { @@ -62,7 +67,7 @@ export const getLowPolyLayer = () => { }; }; -export const getDownPointsLayer = (downPoints: LocationPoint[]) => { +export const getDownPointsLayer = (downPoints: LocationPoint[], dangerColor: string) => { const features = downPoints?.map(point => ({ type: 'feature', geometry: { @@ -87,7 +92,7 @@ export const getDownPointsLayer = (downPoints: LocationPoint[]) => { fillColor: { type: 'STATIC', options: { - color: '#BC261E', + color: dangerColor, }, }, lineColor: { diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/location_map/index.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/index.tsx index 1f4b88b971c4c..140d33bbeef66 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/location_map/index.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/index.tsx @@ -5,3 +5,4 @@ */ export * from './location_map'; +export * from './location_status_tags'; diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/location_map/location_map.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/location_map.tsx index b271632cb631f..9a9bf3fe71dc1 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/location_map/location_map.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/location_map.tsx @@ -6,15 +6,21 @@ import React from 'react'; import styled from 'styled-components'; +import { EuiFlexGroup, EuiFlexItem } from '@elastic/eui'; +import { LocationStatusTags } from './location_status_tags'; import { EmbeddedMap, LocationPoint } from './embeddables/embedded_map'; +import { MonitorLocations } from '../../../../common/runtime_types'; +// These height/width values are used to make sure map is in center of panel +// And to make sure, it doesn't take too much space const MapPanel = styled.div` - height: 400px; + height: 240px; width: 520px; + margin-right: 20px; `; interface LocationMapProps { - monitorLocations: any; + monitorLocations: MonitorLocations; } export const LocationMap = ({ monitorLocations }: LocationMapProps) => { @@ -31,8 +37,15 @@ export const LocationMap = ({ monitorLocations }: LocationMapProps) => { }); } return ( - - - + + + + + + + + + + ); }; diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/location_map/location_status_tags.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/location_status_tags.tsx new file mode 100644 index 0000000000000..6563c03ad7c34 --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/components/functional/location_map/location_status_tags.tsx @@ -0,0 +1,123 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { useContext } from 'react'; +import styled from 'styled-components'; +import { EuiBadge, EuiText } from '@elastic/eui'; +import moment from 'moment'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { UptimeSettingsContext } from '../../../contexts'; +import { MonitorLocation } from '../../../../common/runtime_types'; + +const TimeStampSpan = styled.span` + display: inline-block; + margin-left: 4px; +`; + +const TextStyle = styled.div` + font-weight: 600; +`; + +const BadgeItem = styled.div` + margin-bottom: 5px; +`; + +const TagContainer = styled.div` + padding: 10px; + max-height: 229px; + overflow: hidden; +`; + +const OtherLocationsDiv = styled.div` + padding-left: 18px; +`; + +interface Props { + locations: MonitorLocation[]; +} + +interface StatusTag { + label: string; + timestamp: number; +} + +export const LocationStatusTags = ({ locations }: Props) => { + const { + colors: { gray, danger }, + } = useContext(UptimeSettingsContext); + + const upLocations: StatusTag[] = []; + const downLocations: StatusTag[] = []; + + locations.forEach((item: any) => { + if (item.summary.down === 0) { + upLocations.push({ label: item.geo.name, timestamp: new Date(item.timestamp).valueOf() }); + } else { + downLocations.push({ label: item.geo.name, timestamp: new Date(item.timestamp).valueOf() }); + } + }); + + // Sort by recent timestamp + upLocations.sort((a, b) => { + return a.timestamp < b.timestamp ? 1 : b.timestamp < a.timestamp ? -1 : 0; + }); + + moment.locale('en', { + relativeTime: { + future: 'in %s', + past: '%s ago', + s: '%ds', + ss: '%ss', + m: '%dm', + mm: '%dm', + h: '%dh', + hh: '%dh', + d: '%dd', + dd: '%dd', + M: '%d Mon', + MM: '%d Mon', + y: '%d Yr', + yy: '%d Yr', + }, + }); + + const tagLabel = (item: StatusTag, ind: number, color: string) => ( + + + + {item.label} + + + + {moment(item.timestamp).fromNow()} + + + ); + + return ( + <> + + {downLocations.map((item, ind) => tagLabel(item, ind, danger))} + {upLocations.map((item, ind) => tagLabel(item, ind, gray))} + + {locations.length > 7 && ( + + +

+ +

+
+
+ )} + + ); +}; diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/monitor_list_drawer/__tests__/data.json b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/monitor_list_drawer/__tests__/data.json index 64adf3642fb22..a45e974685b9c 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/monitor_list_drawer/__tests__/data.json +++ b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/monitor_list_drawer/__tests__/data.json @@ -271,7 +271,6 @@ "monitor": { "id": null, "name": "elastic", - "status": "mixed", "type": null, "__typename": "MonitorState" }, @@ -377,7 +376,6 @@ "monitor": { "id": null, "name": null, - "status": "mixed", "type": null, "__typename": "MonitorState" }, diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/monitor_list_drawer/__tests__/monitor_list_drawer.test.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/monitor_list_drawer/__tests__/monitor_list_drawer.test.tsx index aca43f550aa14..5c606f2356dfc 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/monitor_list_drawer/__tests__/monitor_list_drawer.test.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/monitor_list_drawer/__tests__/monitor_list_drawer.test.tsx @@ -8,11 +8,12 @@ import { MonitorSummary, Check } from '../../../../../../common/graphql/types'; import { shallowWithIntl } from 'test_utils/enzyme_helpers'; import React from 'react'; import { MonitorListDrawerComponent } from '../monitor_list_drawer'; +import { MonitorDetails } from '../../../../../../common/runtime_types'; describe('MonitorListDrawer component', () => { let summary: MonitorSummary; let loadMonitorDetails: any; - let monitorDetails: any; + let monitorDetails: MonitorDetails; beforeEach(() => { summary = { diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/monitor_list_drawer/monitor_list_drawer.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/monitor_list_drawer/monitor_list_drawer.tsx index d793e60dcd089..35b649fa35795 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/monitor_list_drawer/monitor_list_drawer.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/monitor_list_drawer/monitor_list_drawer.tsx @@ -6,7 +6,6 @@ import React, { useEffect } from 'react'; import { EuiLink, EuiSpacer, EuiFlexGroup, EuiFlexItem, EuiIcon, EuiText } from '@elastic/eui'; -import { get } from 'lodash'; import styled from 'styled-components'; import { connect } from 'react-redux'; import { MonitorSummary } from '../../../../../common/graphql/types'; @@ -16,6 +15,8 @@ import { MostRecentError } from './most_recent_error'; import { getMonitorDetails } from '../../../../state/selectors'; import { MonitorStatusList } from './monitor_status_list'; import { MonitorDetails } from '../../../../../common/runtime_types'; +import { useUrlParams } from '../../../../hooks'; +import { MonitorDetailsActionPayload } from '../../../../state/actions/types'; import { MonitorListActionsPopover } from '../monitor_list_actions_popover'; const ContainerDiv = styled.div` @@ -50,19 +51,20 @@ export function MonitorListDrawerComponent({ monitorDetails, }: MonitorListDrawerProps) { const monitorId = summary?.monitor_id; - useEffect(() => { - if (monitorId) { - loadMonitorDetails(monitorId); - } - }, [loadMonitorDetails, monitorId]); + const [getUrlParams] = useUrlParams(); + const { dateRangeStart: dateStart, dateRangeEnd: dateEnd } = getUrlParams(); - if (!summary || !summary.state.checks) { - return null; - } + useEffect(() => { + loadMonitorDetails({ + dateStart, + dateEnd, + monitorId, + }); + }, [dateStart, dateEnd, monitorId, loadMonitorDetails]); - const monitorUrl: string | undefined = get(summary.state.url, 'full', undefined); + const monitorUrl = summary?.state?.url?.full || ''; - return ( + return summary && summary.state.checks ? ( @@ -87,7 +89,7 @@ export function MonitorListDrawerComponent({ /> )} - ); + ) : null; } const mapStateToProps = (state: AppState, { summary }: any) => ({ @@ -95,7 +97,8 @@ const mapStateToProps = (state: AppState, { summary }: any) => ({ }); const mapDispatchToProps = (dispatch: any) => ({ - loadMonitorDetails: (monitorId: string) => dispatch(fetchMonitorDetails(monitorId)), + loadMonitorDetails: (actionPayload: MonitorDetailsActionPayload) => + dispatch(fetchMonitorDetails(actionPayload)), }); export const MonitorListDrawer = connect( diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/monitor_list_status_column.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/monitor_list_status_column.tsx index 463048512e1e0..0a3a0962a4d09 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/monitor_list_status_column.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_list/monitor_list_status_column.tsx @@ -20,8 +20,6 @@ const getHealthColor = (status: string): string => { return 'success'; case 'down': return 'danger'; - case 'mixed': - return 'warning'; default: return ''; } @@ -37,10 +35,6 @@ const getHealthMessage = (status: string): string | null => { return i18n.translate('xpack.uptime.monitorList.statusColumn.downLabel', { defaultMessage: 'Down', }); - case 'mixed': - return i18n.translate('xpack.uptime.monitorList.statusColumn.mixedLabel', { - defaultMessage: 'Mixed', - }); default: return null; } diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_page_title.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_page_title.tsx deleted file mode 100644 index 9e30a3adbd776..0000000000000 --- a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_page_title.tsx +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { EuiTextColor, EuiTitle } from '@elastic/eui'; -import { EuiLoadingSpinner } from '@elastic/eui'; -import React from 'react'; -import { MonitorPageTitle as TitleType } from '../../../common/graphql/types'; -import { UptimeGraphQLQueryProps, withUptimeGraphQL } from '../higher_order'; -import { monitorPageTitleQuery } from '../../queries'; - -interface MonitorPageTitleQueryResult { - monitorPageTitle?: TitleType; -} - -interface MonitorPageTitleProps { - monitorId: string; -} - -type Props = MonitorPageTitleProps & UptimeGraphQLQueryProps; - -export const MonitorPageTitleComponent = ({ data }: Props) => - data && data.monitorPageTitle ? ( - - -

{data.monitorPageTitle.id}

-
-
- ) : ( - - ); - -export const MonitorPageTitle = withUptimeGraphQL< - MonitorPageTitleQueryResult, - MonitorPageTitleProps ->(MonitorPageTitleComponent, monitorPageTitleQuery); diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_bar/index.ts b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_bar/index.ts deleted file mode 100644 index 7087407407c55..0000000000000 --- a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_bar/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export { MonitorSSLCertificate } from './monitor_ssl_certificate'; -export { MonitorStatusBar, MonitorStatusBarComponent } from './monitor_status_bar'; diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_bar/monitor_ssl_certificate.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_bar/monitor_ssl_certificate.tsx deleted file mode 100644 index c50f7f1b00f0d..0000000000000 --- a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_bar/monitor_ssl_certificate.tsx +++ /dev/null @@ -1,63 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import React from 'react'; -import { get } from 'lodash'; -import moment from 'moment'; -import { EuiSpacer, EuiText } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { i18n } from '@kbn/i18n'; -import styled from 'styled-components'; - -import { PingTls } from '../../../../common/graphql/types'; - -interface Props { - /** - * TLS information coming from monitor in ES heartbeat index - */ - tls: PingTls | null | undefined; -} - -const TextContainer = styled.div` - margin-left: 20px; -`; - -export const MonitorSSLCertificate = ({ tls }: Props) => { - const certificateValidity: string | undefined = get( - tls, - 'certificate_not_valid_after', - undefined - ); - - const validExpiryDate = certificateValidity && !isNaN(new Date(certificateValidity).valueOf()); - - return validExpiryDate && certificateValidity ? ( - <> - - - - - - - - ) : null; -}; diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_bar/monitor_status_bar.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_bar/monitor_status_bar.tsx deleted file mode 100644 index f36f0dff6745f..0000000000000 --- a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_bar/monitor_status_bar.tsx +++ /dev/null @@ -1,80 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { EuiFlexGroup, EuiFlexItem, EuiHealth, EuiLink } from '@elastic/eui'; -import { FormattedMessage } from '@kbn/i18n/react'; -import { get } from 'lodash'; -import moment from 'moment'; -import React from 'react'; -import { Ping } from '../../../../common/graphql/types'; -import { UptimeGraphQLQueryProps, withUptimeGraphQL } from '../../higher_order'; -import { monitorStatusBarQuery } from '../../../queries'; -import { EmptyStatusBar } from '../empty_status_bar'; -import { convertMicrosecondsToMilliseconds } from '../../../lib/helper'; -import { MonitorSSLCertificate } from './monitor_ssl_certificate'; -import * as labels from './translations'; - -interface MonitorStatusBarQueryResult { - monitorStatus?: Ping[]; -} - -interface MonitorStatusBarProps { - monitorId: string; -} - -type Props = MonitorStatusBarProps & UptimeGraphQLQueryProps; - -export const MonitorStatusBarComponent = ({ data, monitorId }: Props) => { - if (data?.monitorStatus?.length) { - const { monitor, timestamp, tls } = data.monitorStatus[0]; - const duration: number | undefined = get(monitor, 'duration.us', undefined); - const status = get<'up' | 'down'>(monitor, 'status', 'down'); - const full = get(data.monitorStatus[0], 'url.full'); - - return ( - <> - - - - {status === 'up' ? labels.upLabel : labels.downLabel} - - - - - - {full} - - - - {!!duration && ( - - - - )} - - {moment(new Date(timestamp).valueOf()).fromNow()} - - - - - ); - } - return ; -}; - -export const MonitorStatusBar = withUptimeGraphQL< - MonitorStatusBarQueryResult, - MonitorStatusBarProps ->(MonitorStatusBarComponent, monitorStatusBarQuery); diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/__test__/__snapshots__/monitor_ssl_certificate.test.tsx.snap b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/__test__/__snapshots__/monitor_ssl_certificate.test.tsx.snap new file mode 100644 index 0000000000000..0cb0a7ec248df --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/__test__/__snapshots__/monitor_ssl_certificate.test.tsx.snap @@ -0,0 +1,30 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`MonitorStatusBar component renders 1`] = ` +Array [ +
, +
+ SSL certificate expires + + + + in 2 months + + + +
, +] +`; + +exports[`MonitorStatusBar component renders null if invalid date 1`] = `null`; diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/__test__/__snapshots__/status_by_location.test.tsx.snap b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/__test__/__snapshots__/status_by_location.test.tsx.snap new file mode 100644 index 0000000000000..c6b18501ffa0f --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/__test__/__snapshots__/status_by_location.test.tsx.snap @@ -0,0 +1,51 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`StatusByLocation component renders all locations are down 1`] = ` +
+

+ Down in 2 Locations +

+
+`; + +exports[`StatusByLocation component renders when down in some locations 1`] = ` +
+

+ Down in 1/2 Locations +

+
+`; + +exports[`StatusByLocation component renders when only one location and it is down 1`] = ` +
+

+ Down in 1 Location +

+
+`; + +exports[`StatusByLocation component renders when only one location and it is up 1`] = ` +
+

+ Up in 1 Location +

+
+`; + +exports[`StatusByLocation component renders when up in all locations 1`] = ` +
+

+ Up in 2 Locations +

+
+`; diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/__test__/monitor_ssl_certificate.test.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/__test__/monitor_ssl_certificate.test.tsx new file mode 100644 index 0000000000000..2eae14301fd4d --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/__test__/monitor_ssl_certificate.test.tsx @@ -0,0 +1,76 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import moment from 'moment'; +import { mountWithIntl } from 'test_utils/enzyme_helpers'; +import { EuiBadge } from '@elastic/eui'; +import { renderWithIntl } from 'test_utils/enzyme_helpers'; +import { PingTls } from '../../../../../common/graphql/types'; +import { MonitorSSLCertificate } from '../monitor_status_bar'; + +describe('MonitorStatusBar component', () => { + let monitorTls: PingTls; + + beforeEach(() => { + const dateInTwoMonths = moment() + .add(2, 'month') + .toString(); + + monitorTls = { + certificate_not_valid_after: dateInTwoMonths, + }; + }); + + it('renders', () => { + const component = renderWithIntl(); + expect(component).toMatchSnapshot(); + }); + + it('renders null if invalid date', () => { + monitorTls = { + certificate_not_valid_after: 'i am so invalid date', + }; + const component = renderWithIntl(); + expect(component).toMatchSnapshot(); + }); + + it('renders expiration date with a warning state if ssl expiry date is less than 30 days', () => { + const dateIn15Days = moment() + .add(15, 'day') + .toString(); + monitorTls = { + certificate_not_valid_after: dateIn15Days, + }; + const component = mountWithIntl(); + + const badgeComponent = component.find(EuiBadge); + expect(badgeComponent.props().color).toBe('warning'); + + const badgeComponentText = component.find('.euiBadge__text'); + expect(badgeComponentText.text()).toBe(moment(dateIn15Days).fromNow()); + + expect(badgeComponent.find('span.euiBadge--warning')).toBeTruthy(); + }); + + it('does not render the expiration date with a warning state if expiry date is greater than a month', () => { + const dateIn40Days = moment() + .add(40, 'day') + .toString(); + monitorTls = { + certificate_not_valid_after: dateIn40Days, + }; + const component = mountWithIntl(); + + const badgeComponent = component.find(EuiBadge); + expect(badgeComponent.props().color).toBe('default'); + + const badgeComponentText = component.find('.euiBadge__text'); + expect(badgeComponentText.text()).toBe(moment(dateIn40Days).fromNow()); + + expect(badgeComponent.find('span.euiBadge--warning')).toHaveLength(0); + }); +}); diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/__test__/status_by_location.test.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/__test__/status_by_location.test.tsx new file mode 100644 index 0000000000000..38864103564ca --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/__test__/status_by_location.test.tsx @@ -0,0 +1,88 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { renderWithIntl } from 'test_utils/enzyme_helpers'; +import { MonitorLocation } from '../../../../../common/runtime_types'; +import { StatusByLocations } from '../'; + +describe('StatusByLocation component', () => { + let monitorLocations: MonitorLocation[]; + + it('renders when up in all locations', () => { + monitorLocations = [ + { + summary: { up: 4, down: 0 }, + geo: { name: 'Berlin', location: { lat: '52.487448', lon: ' 13.394798' } }, + timestamp: '2020-01-09T12:22:32.567Z', + }, + { + summary: { up: 4, down: 0 }, + geo: { name: 'st-paul', location: { lat: '52.487448', lon: ' 13.394798' } }, + }, + ]; + const component = renderWithIntl(); + expect(component).toMatchSnapshot(); + }); + + it('renders when only one location and it is up', () => { + monitorLocations = [ + { + summary: { up: 4, down: 0 }, + geo: { name: 'Berlin', location: { lat: '52.487448', lon: ' 13.394798' } }, + timestamp: '2020-01-09T12:22:32.567Z', + }, + ]; + const component = renderWithIntl(); + expect(component).toMatchSnapshot(); + }); + + it('renders when only one location and it is down', () => { + monitorLocations = [ + { + summary: { up: 0, down: 4 }, + geo: { name: 'Berlin', location: { lat: '52.487448', lon: ' 13.394798' } }, + timestamp: '2020-01-09T12:22:32.567Z', + }, + ]; + const component = renderWithIntl(); + expect(component).toMatchSnapshot(); + }); + + it('renders all locations are down', () => { + monitorLocations = [ + { + summary: { up: 0, down: 4 }, + geo: { name: 'Berlin', location: { lat: '52.487448', lon: ' 13.394798' } }, + timestamp: '2020-01-09T12:22:32.567Z', + }, + { + summary: { up: 0, down: 4 }, + geo: { name: 'st-paul', location: { lat: '52.487448', lon: ' 13.394798' } }, + timestamp: '2020-01-09T12:22:32.567Z', + }, + ]; + const component = renderWithIntl(); + expect(component).toMatchSnapshot(); + }); + + it('renders when down in some locations', () => { + monitorLocations = [ + { + summary: { up: 0, down: 4 }, + geo: { name: 'Berlin', location: { lat: '52.487448', lon: ' 13.394798' } }, + timestamp: '2020-01-09T12:22:32.567Z', + }, + { + summary: { up: 4, down: 0 }, + geo: { name: 'st-paul', location: { lat: '52.487448', lon: ' 13.394798' } }, + timestamp: '2020-01-09T12:22:32.567Z', + }, + ]; + const component = renderWithIntl(); + expect(component).toMatchSnapshot(); + }); +}); diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/index.ts b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/index.ts index 234586e0b51f1..7b4e1ea353c11 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/index.ts +++ b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/index.ts @@ -5,12 +5,12 @@ */ import { connect } from 'react-redux'; import { AppState } from '../../../state'; -import { getMonitorLocations } from '../../../state/selectors'; +import { selectMonitorLocations } from '../../../state/selectors'; import { fetchMonitorLocations } from '../../../state/actions/monitor'; import { MonitorStatusDetailsComponent } from './monitor_status_details'; const mapStateToProps = (state: AppState, { monitorId }: any) => ({ - monitorLocations: getMonitorLocations(state, monitorId), + monitorLocations: selectMonitorLocations(state, monitorId), }); const mapDispatchToProps = (dispatch: any, ownProps: any) => ({ @@ -32,3 +32,5 @@ export const MonitorStatusDetails = connect( )(MonitorStatusDetailsComponent); export * from './monitor_status_details'; +export { MonitorStatusBar } from './monitor_status_bar'; +export { StatusByLocations } from './monitor_status_bar/status_by_location'; diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/monitor_status_bar/index.ts b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/monitor_status_bar/index.ts new file mode 100644 index 0000000000000..94bd7fa7f026b --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/monitor_status_bar/index.ts @@ -0,0 +1,50 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { connect } from 'react-redux'; +import { Dispatch } from 'redux'; +import { + StateProps, + DispatchProps, + MonitorStatusBarComponent, + MonitorStatusBarProps, +} from './monitor_status_bar'; +import { selectMonitorStatus, selectMonitorLocations } from '../../../../state/selectors'; +import { AppState } from '../../../../state'; +import { getMonitorStatus, getSelectedMonitor } from '../../../../state/actions'; + +const mapStateToProps = (state: AppState, ownProps: MonitorStatusBarProps) => ({ + monitorStatus: selectMonitorStatus(state), + monitorLocations: selectMonitorLocations(state, ownProps.monitorId), +}); + +const mapDispatchToProps = (dispatch: Dispatch, ownProps: MonitorStatusBarProps) => ({ + loadMonitorStatus: () => { + const { dateStart, dateEnd, monitorId } = ownProps; + dispatch( + getMonitorStatus({ + monitorId, + dateStart, + dateEnd, + }) + ); + dispatch( + getSelectedMonitor({ + monitorId, + }) + ); + }, +}); + +// @ts-ignore TODO: Investigate typescript issues here +export const MonitorStatusBar = connect( + // @ts-ignore TODO: Investigate typescript issues here + mapStateToProps, + mapDispatchToProps +)(MonitorStatusBarComponent); + +export { MonitorSSLCertificate } from './monitor_ssl_certificate'; +export * from './monitor_status_bar'; diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/monitor_status_bar/monitor_ssl_certificate.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/monitor_status_bar/monitor_ssl_certificate.tsx new file mode 100644 index 0000000000000..c57348c4ab4cd --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/monitor_status_bar/monitor_ssl_certificate.tsx @@ -0,0 +1,59 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import moment from 'moment'; +import { EuiSpacer, EuiText, EuiBadge } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { i18n } from '@kbn/i18n'; + +import { PingTls } from '../../../../../common/graphql/types'; + +interface Props { + /** + * TLS information coming from monitor in ES heartbeat index + */ + tls: PingTls | null | undefined; +} + +export const MonitorSSLCertificate = ({ tls }: Props) => { + const certValidityDate = new Date(tls?.certificate_not_valid_after ?? ''); + + const isValidDate = !isNaN(certValidityDate.valueOf()); + + const dateIn30Days = moment().add('30', 'days'); + + const isExpiringInMonth = isValidDate && dateIn30Days > moment(certValidityDate); + + return isValidDate ? ( + <> + + + + {moment(certValidityDate).fromNow()} + + ), + }} + /> + + + ) : null; +}; diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/monitor_status_bar/monitor_status_bar.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/monitor_status_bar/monitor_status_bar.tsx new file mode 100644 index 0000000000000..57ca909ffde55 --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/monitor_status_bar/monitor_status_bar.tsx @@ -0,0 +1,79 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + EuiLink, + EuiTitle, + EuiTextColor, + EuiSpacer, + EuiText, + EuiFlexGroup, + EuiFlexItem, +} from '@elastic/eui'; +import React, { useEffect } from 'react'; +import { MonitorSSLCertificate } from './monitor_ssl_certificate'; +import * as labels from './translations'; +import { StatusByLocations } from './status_by_location'; +import { Ping } from '../../../../../common/graphql/types'; +import { MonitorLocations } from '../../../../../common/runtime_types'; + +export interface StateProps { + monitorStatus: Ping; + monitorLocations: MonitorLocations; +} + +export interface DispatchProps { + loadMonitorStatus: () => void; +} + +export interface MonitorStatusBarProps { + monitorId: string; + dateStart: string; + dateEnd: string; +} + +type Props = MonitorStatusBarProps & StateProps & DispatchProps; + +export const MonitorStatusBarComponent: React.FC = ({ + dateStart, + dateEnd, + monitorId, + loadMonitorStatus, + monitorStatus, + monitorLocations, +}) => { + useEffect(() => { + loadMonitorStatus(); + }, [dateStart, dateEnd, loadMonitorStatus]); + + const full = monitorStatus?.url?.full ?? ''; + + return ( + + + + + + + + {full} + + + + + + +

{monitorId}

+
+
+
+ + + + +
+ ); +}; diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/monitor_status_bar/status_by_location.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/monitor_status_bar/status_by_location.tsx new file mode 100644 index 0000000000000..461ffc10124fd --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/monitor_status_bar/status_by_location.tsx @@ -0,0 +1,70 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React from 'react'; +import { EuiText } from '@elastic/eui'; +import { FormattedMessage } from '@kbn/i18n/react'; +import { MonitorLocation } from '../../../../../common/runtime_types'; + +interface StatusByLocationsProps { + locations: MonitorLocation[]; +} + +export const StatusByLocations = ({ locations }: StatusByLocationsProps) => { + const upLocations: string[] = []; + const downLocations: string[] = []; + + if (locations) + locations.forEach((item: any) => { + if (item.summary.down === 0) { + upLocations.push(item.geo.name); + } else { + downLocations.push(item.geo.name); + } + }); + + let statusMessage = ''; + let status = ''; + if (downLocations.length === 0) { + // for Messaging like 'Up in 1 Location' or 'Up in 2 Locations' + statusMessage = `${locations.length}`; + status = 'Up'; + } else if (downLocations.length > 0) { + // for Messaging like 'Down in 1/2 Locations' + status = 'Down'; + statusMessage = `${downLocations.length}/${locations.length}`; + if (downLocations.length === locations.length) { + // for Messaging like 'Down in 2 Locations' + statusMessage = `${locations.length}`; + } + } + + return ( + +

+ {locations.length <= 1 ? ( + + ) : ( + + )} +

+
+ ); +}; diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_bar/translations.ts b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/monitor_status_bar/translations.ts similarity index 100% rename from x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_bar/translations.ts rename to x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/monitor_status_bar/translations.ts diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/monitor_status_details.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/monitor_status_details.tsx index ed67c6364e958..bb87497d335ef 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/monitor_status_details.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/monitor_status_details/monitor_status_details.tsx @@ -7,7 +7,7 @@ import React, { useEffect } from 'react'; import { EuiFlexGroup, EuiFlexItem, EuiPanel } from '@elastic/eui'; import { LocationMap } from '../location_map'; -import { MonitorStatusBar } from '../monitor_status_bar'; +import { MonitorStatusBar } from './monitor_status_bar'; interface MonitorStatusBarProps { monitorId: string; @@ -34,7 +34,12 @@ export const MonitorStatusDetailsComponent = ({ - + diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/snapshot.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/snapshot.tsx index 4c1b482b198af..90d716001cff9 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/snapshot.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/snapshot.tsx @@ -43,7 +43,7 @@ interface StoreProps { /** * Contains functions that will dispatch actions used - * for this component's lifecyclel + * for this component's life cycle */ interface DispatchProps { loadSnapshotCount: typeof fetchSnapshotCount; diff --git a/x-pack/legacy/plugins/uptime/public/components/functional/uptime_date_picker.tsx b/x-pack/legacy/plugins/uptime/public/components/functional/uptime_date_picker.tsx index ebd0cd1e4ae85..c282ac9b9e155 100644 --- a/x-pack/legacy/plugins/uptime/public/components/functional/uptime_date_picker.tsx +++ b/x-pack/legacy/plugins/uptime/public/components/functional/uptime_date_picker.tsx @@ -5,9 +5,10 @@ */ import { EuiSuperDatePicker } from '@elastic/eui'; -import React from 'react'; +import React, { useContext } from 'react'; import { useUrlParams } from '../../hooks'; import { CLIENT_DEFAULTS } from '../../../common/constants'; +import { UptimeSettingsContext } from '../../contexts'; // TODO: when EUI exports types for this, this should be replaced interface SuperDateRangePickerRangeChangedEvent { @@ -26,16 +27,14 @@ export interface CommonlyUsedRange { display: string; } -interface Props { +interface UptimeDatePickerProps { refreshApp: () => void; - commonlyUsedRanges?: CommonlyUsedRange[]; } -type UptimeDatePickerProps = Props; - -export const UptimeDatePicker = ({ refreshApp, commonlyUsedRanges }: UptimeDatePickerProps) => { +export const UptimeDatePicker = ({ refreshApp }: UptimeDatePickerProps) => { const [getUrlParams, updateUrl] = useUrlParams(); const { autorefreshInterval, autorefreshIsPaused, dateRangeStart, dateRangeEnd } = getUrlParams(); + const { commonlyUsedRanges } = useContext(UptimeSettingsContext); const euiCommonlyUsedRanges = commonlyUsedRanges ? commonlyUsedRanges.map( diff --git a/x-pack/legacy/plugins/uptime/public/contexts/uptime_settings_context.ts b/x-pack/legacy/plugins/uptime/public/contexts/uptime_settings_context.ts index 0fd9be952ed40..c656391678aa2 100644 --- a/x-pack/legacy/plugins/uptime/public/contexts/uptime_settings_context.ts +++ b/x-pack/legacy/plugins/uptime/public/contexts/uptime_settings_context.ts @@ -9,6 +9,7 @@ import euiLightVars from '@elastic/eui/dist/eui_theme_light.json'; import { createContext } from 'react'; import { UptimeAppColors } from '../uptime_app'; import { CONTEXT_DEFAULTS } from '../../common/constants'; +import { CommonlyUsedRange } from '../components/functional/uptime_date_picker'; export interface UMSettingsContextValues { absoluteStartDate: number; @@ -23,7 +24,7 @@ export interface UMSettingsContextValues { isInfraAvailable: boolean; isLogsAvailable: boolean; refreshApp: () => void; - setHeadingText: (text: string) => void; + commonlyUsedRanges?: CommonlyUsedRange[]; } const { @@ -64,9 +65,6 @@ const defaultContext: UMSettingsContextValues = { refreshApp: () => { throw new Error('App refresh was not initialized, set it when you invoke the context'); }, - setHeadingText: () => { - throw new Error('setHeadingText was not initialized on UMSettingsContext.'); - }, }; export const UptimeSettingsContext = createContext(defaultContext); diff --git a/x-pack/legacy/plugins/uptime/public/hooks/index.ts b/x-pack/legacy/plugins/uptime/public/hooks/index.ts index 22de59833b08d..aa7bb0a220357 100644 --- a/x-pack/legacy/plugins/uptime/public/hooks/index.ts +++ b/x-pack/legacy/plugins/uptime/public/hooks/index.ts @@ -5,3 +5,5 @@ */ export { useUrlParams } from './use_url_params'; +export { useIndexPattern } from './use_index_pattern'; +export * from './use_telemetry'; diff --git a/x-pack/legacy/plugins/uptime/public/hooks/use_index_pattern.ts b/x-pack/legacy/plugins/uptime/public/hooks/use_index_pattern.ts new file mode 100644 index 0000000000000..f2b586b27dba6 --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/hooks/use_index_pattern.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { useEffect, Dispatch } from 'react'; +import { useKibana } from '../../../../../../src/plugins/kibana_react/public'; + +export const useIndexPattern = (setIndexPattern: Dispatch) => { + const core = useKibana(); + useEffect(() => { + const fetch = core.services.http?.fetch; + async function getIndexPattern() { + if (!fetch) throw new Error('Http core services are not defined'); + setIndexPattern(await fetch('/api/uptime/index_pattern', { method: 'GET' })); + } + getIndexPattern(); + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [core.services.http]); +}; diff --git a/x-pack/legacy/plugins/uptime/public/hooks/use_telemetry.ts b/x-pack/legacy/plugins/uptime/public/hooks/use_telemetry.ts new file mode 100644 index 0000000000000..15f276174e2cf --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/hooks/use_telemetry.ts @@ -0,0 +1,41 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { useEffect } from 'react'; +import { HttpHandler } from 'kibana/public'; +import { useKibana } from '../../../../../../src/plugins/kibana_react/public'; + +export enum UptimePage { + Overview = '/api/uptime/logOverview', + Monitor = '/api/uptime/logMonitor', + NotFound = '__not-found__', +} + +const getApiPath = (page?: UptimePage) => { + if (!page) throw new Error('Telemetry logging for this page not yet implemented'); + if (page === '__not-found__') + throw new Error('Telemetry logging for 404 page not yet implemented'); + return page.valueOf(); +}; + +const logPageLoad = async (fetch: HttpHandler, page?: UptimePage) => { + try { + await fetch(getApiPath(page), { + method: 'POST', + }); + } catch (e) { + throw e; + } +}; + +export const useUptimeTelemetry = (page?: UptimePage) => { + const kibana = useKibana(); + const fetch = kibana.services.http?.fetch; + useEffect(() => { + if (!fetch) throw new Error('Core http services are not defined'); + logPageLoad(fetch, page); + }, [fetch, page]); +}; diff --git a/x-pack/legacy/plugins/uptime/public/lib/adapters/framework/new_platform_adapter.tsx b/x-pack/legacy/plugins/uptime/public/lib/adapters/framework/new_platform_adapter.tsx index b7ff3b2aa6264..28179c229013b 100644 --- a/x-pack/legacy/plugins/uptime/public/lib/adapters/framework/new_platform_adapter.tsx +++ b/x-pack/legacy/plugins/uptime/public/lib/adapters/framework/new_platform_adapter.tsx @@ -4,12 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { ChromeBreadcrumb, CoreStart } from 'src/core/public'; +import { ChromeBreadcrumb, LegacyCoreStart } from 'src/core/public'; import React from 'react'; import ReactDOM from 'react-dom'; import { get } from 'lodash'; -import { AutocompleteProviderRegister } from 'src/plugins/data/public'; import { i18n as i18nFormatter } from '@kbn/i18n'; +import { PluginsStart } from 'ui/new_platform/new_platform'; import { CreateGraphQLClient } from './framework_adapter_types'; import { UptimeApp, UptimeAppProps } from '../../../uptime_app'; import { getIntegratedAppAvailability } from './capabilities_adapter'; @@ -19,13 +19,12 @@ import { DEFAULT_DARK_MODE, DEFAULT_TIMEPICKER_QUICK_RANGES, } from '../../../../common/constants'; -import { getTelemetryMonitorPageLogger, getTelemetryOverviewPageLogger } from '../telemetry'; import { UMFrameworkAdapter, BootstrapUptimeApp } from '../../lib'; import { createApolloClient } from './apollo_client_adapter'; export const getKibanaFrameworkAdapter = ( - core: CoreStart, - autocomplete: Pick + core: LegacyCoreStart, + plugins: PluginsStart ): UMFrameworkAdapter => { const { application: { capabilities }, @@ -44,10 +43,10 @@ export const getKibanaFrameworkAdapter = ( ); const canSave = get(capabilities, 'uptime.save', false); const props: UptimeAppProps = { - autocomplete, basePath: basePath.get(), canSave, client: createApolloClient(`${basePath.get()}/api/uptime/graphql`, 'true'), + core, darkMode: core.uiSettings.get(DEFAULT_DARK_MODE), commonlyUsedRanges: core.uiSettings.get(DEFAULT_TIMEPICKER_QUICK_RANGES), i18n, @@ -55,8 +54,7 @@ export const getKibanaFrameworkAdapter = ( isInfraAvailable: infrastructure, isLogsAvailable: logs, kibanaBreadcrumbs: breadcrumbs, - logMonitorPageLoad: getTelemetryMonitorPageLogger('true', basePath.get()), - logOverviewPageLoad: getTelemetryOverviewPageLogger('true', basePath.get()), + plugins, renderGlobalHelpControls: () => setHelpExtension({ appName: i18nFormatter.translate('xpack.uptime.header.appName', { diff --git a/x-pack/legacy/plugins/uptime/public/lib/adapters/index_pattern/__tests__/get_index_pattern.test.ts b/x-pack/legacy/plugins/uptime/public/lib/adapters/index_pattern/__tests__/get_index_pattern.test.ts deleted file mode 100644 index 6654def2f944b..0000000000000 --- a/x-pack/legacy/plugins/uptime/public/lib/adapters/index_pattern/__tests__/get_index_pattern.test.ts +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import axios, { AxiosRequestConfig } from 'axios'; -import { getIndexPattern } from '../get_index_pattern'; - -describe('getIndexPattern', () => { - let axiosSpy: jest.SpyInstance, [string, (AxiosRequestConfig | undefined)?]>; - beforeEach(() => { - axiosSpy = jest.spyOn(axios, 'get'); - }); - - afterEach(() => { - jest.clearAllMocks(); - }); - - it('returns expected data', async () => { - expect.assertions(3); - axiosSpy.mockReturnValue(new Promise(r => r({ data: { foo: 'bar' } }))); - expect(await getIndexPattern()).toEqual({ foo: 'bar' }); - expect(axiosSpy.mock.calls).toHaveLength(1); - expect(axiosSpy.mock.calls[0]).toEqual(['/api/uptime/index_pattern']); - }); - - it('handles the supplied basePath', async () => { - expect.assertions(2); - await getIndexPattern('foo'); - expect(axiosSpy.mock.calls).toHaveLength(1); - expect(axiosSpy.mock.calls[0]).toEqual(['foo/api/uptime/index_pattern']); - }); - - it('supplies the returned data to the given setter function', async () => { - const mockSetter = jest.fn(); - axiosSpy.mockReturnValue(new Promise(r => r({ data: { foo: 'bar' } }))); - await getIndexPattern(undefined, mockSetter); - expect(mockSetter).toHaveBeenCalled(); - expect(mockSetter).toHaveBeenCalledWith({ foo: 'bar' }); - }); - - it('returns undefined when there is an error fetching', async () => { - expect.assertions(1); - axiosSpy.mockReturnValue( - new Promise((resolve, reject) => reject('Request timeout, server could not be reached')) - ); - expect(await getIndexPattern()).toBeUndefined(); - }); -}); diff --git a/x-pack/legacy/plugins/uptime/public/lib/adapters/index_pattern/get_index_pattern.ts b/x-pack/legacy/plugins/uptime/public/lib/adapters/index_pattern/get_index_pattern.ts deleted file mode 100644 index fd4161b35f7dd..0000000000000 --- a/x-pack/legacy/plugins/uptime/public/lib/adapters/index_pattern/get_index_pattern.ts +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import axios from 'axios'; -import { getApiPath } from '../../helper'; - -/** - * Fetches and returns the uptime index pattern, optionally provides it to - * a given setter function. - * @param basePath - the base path, if any - * @param setter - a callback for use with non-async functions like `useEffect` - */ -export const getIndexPattern = async (basePath?: string, setter?: (data: unknown) => void) => { - try { - const { data } = await axios.get(getApiPath('/api/uptime/index_pattern', basePath)); - if (setter) { - setter(data); - } - return data; - } catch { - return undefined; - } -}; diff --git a/x-pack/legacy/plugins/uptime/public/lib/adapters/index_pattern/index.ts b/x-pack/legacy/plugins/uptime/public/lib/adapters/index_pattern/index.ts deleted file mode 100644 index 1c84a7bc3b727..0000000000000 --- a/x-pack/legacy/plugins/uptime/public/lib/adapters/index_pattern/index.ts +++ /dev/null @@ -1,7 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export { getIndexPattern } from './get_index_pattern'; diff --git a/x-pack/legacy/plugins/uptime/public/lib/adapters/telemetry/index.ts b/x-pack/legacy/plugins/uptime/public/lib/adapters/telemetry/index.ts deleted file mode 100644 index 08d8d9a5d4069..0000000000000 --- a/x-pack/legacy/plugins/uptime/public/lib/adapters/telemetry/index.ts +++ /dev/null @@ -1,8 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -export { getTelemetryMonitorPageLogger } from './log_monitor'; -export { getTelemetryOverviewPageLogger } from './log_overview'; diff --git a/x-pack/legacy/plugins/uptime/public/lib/adapters/telemetry/log_monitor.ts b/x-pack/legacy/plugins/uptime/public/lib/adapters/telemetry/log_monitor.ts deleted file mode 100644 index 20328497d69a8..0000000000000 --- a/x-pack/legacy/plugins/uptime/public/lib/adapters/telemetry/log_monitor.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import axios from 'axios'; -import { getApiPath } from '../../helper'; - -/** - * Generates a function to log a page load of the monitor page for Kibana telemetry. - * @returns a function that can log page loads - */ -export const getTelemetryMonitorPageLogger = (xsrf: string, basePath?: string) => async () => { - await axios.post(getApiPath('/api/uptime/logMonitor', basePath), undefined, { - headers: { 'kbn-xsrf': xsrf }, - }); -}; diff --git a/x-pack/legacy/plugins/uptime/public/lib/adapters/telemetry/log_overview.ts b/x-pack/legacy/plugins/uptime/public/lib/adapters/telemetry/log_overview.ts deleted file mode 100644 index fd9fd773a18b9..0000000000000 --- a/x-pack/legacy/plugins/uptime/public/lib/adapters/telemetry/log_overview.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import axios from 'axios'; -import { getApiPath } from '../../helper'; - -/** - * Generates a function to log a page load of the overview page for Kibana telemtry. - * @returns a function that can log page loads - */ -export const getTelemetryOverviewPageLogger = (xsrf: string, basePath?: string) => async () => { - await axios.post(getApiPath('/api/uptime/logOverview', basePath), undefined, { - headers: { 'kbn-xsrf': xsrf }, - }); -}; diff --git a/x-pack/legacy/plugins/uptime/public/lib/helper/__tests__/__snapshots__/parameterize_values.test.ts.snap b/x-pack/legacy/plugins/uptime/public/lib/helper/__tests__/__snapshots__/parameterize_values.test.ts.snap new file mode 100644 index 0000000000000..39c28a87f5e71 --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/lib/helper/__tests__/__snapshots__/parameterize_values.test.ts.snap @@ -0,0 +1,5 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`parameterizeValues parameterizes provided values for multiple fields 1`] = `"foo=bar&foo=baz&bar=foo&bar=baz"`; + +exports[`parameterizeValues parameterizes the provided values for a given field name 1`] = `"foo=bar&foo=baz"`; diff --git a/x-pack/legacy/plugins/uptime/public/lib/helper/__tests__/parameterize_values.test.ts b/x-pack/legacy/plugins/uptime/public/lib/helper/__tests__/parameterize_values.test.ts new file mode 100644 index 0000000000000..e550a1a6397e3 --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/lib/helper/__tests__/parameterize_values.test.ts @@ -0,0 +1,30 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { parameterizeValues } from '../parameterize_values'; + +describe('parameterizeValues', () => { + let params: URLSearchParams; + + beforeEach(() => { + params = new URLSearchParams(); + }); + + it('parameterizes the provided values for a given field name', () => { + parameterizeValues(params, { foo: ['bar', 'baz'] }); + expect(params.toString()).toMatchSnapshot(); + }); + + it('parameterizes provided values for multiple fields', () => { + parameterizeValues(params, { foo: ['bar', 'baz'], bar: ['foo', 'baz'] }); + expect(params.toString()).toMatchSnapshot(); + }); + + it('returns an empty string when there are no values provided', () => { + parameterizeValues(params, { foo: [] }); + expect(params.toString()).toBe(''); + }); +}); diff --git a/x-pack/legacy/plugins/uptime/public/lib/helper/index.ts b/x-pack/legacy/plugins/uptime/public/lib/helper/index.ts index a4cfb1c51b0ec..ced06ce7a1d7b 100644 --- a/x-pack/legacy/plugins/uptime/public/lib/helper/index.ts +++ b/x-pack/legacy/plugins/uptime/public/lib/helper/index.ts @@ -9,6 +9,7 @@ export { convertMicrosecondsToMilliseconds } from './convert_measurements'; export * from './observability_integration'; export { getApiPath } from './get_api_path'; export { getChartDateLabel } from './charts'; +export { parameterizeValues } from './parameterize_values'; export { seriesHasDownValues } from './series_has_down_values'; export { stringifyKueries } from './stringify_kueries'; export { toStaticIndexPattern } from './to_static_index_pattern'; diff --git a/x-pack/legacy/plugins/uptime/public/lib/helper/parameterize_values.ts b/x-pack/legacy/plugins/uptime/public/lib/helper/parameterize_values.ts new file mode 100644 index 0000000000000..4c9fa6838c2ed --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/lib/helper/parameterize_values.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const parameterizeValues = ( + params: URLSearchParams, + obj: Record +): void => { + Object.keys(obj).forEach(key => { + obj[key].forEach(val => { + params.append(key, val); + }); + }); +}; diff --git a/x-pack/legacy/plugins/uptime/public/pages/index.ts b/x-pack/legacy/plugins/uptime/public/pages/index.ts index bf5d55ebf17ae..a96be42eb0dee 100644 --- a/x-pack/legacy/plugins/uptime/public/pages/index.ts +++ b/x-pack/legacy/plugins/uptime/public/pages/index.ts @@ -7,3 +7,4 @@ export { MonitorPage } from './monitor'; export { OverviewPage } from './overview'; export { NotFoundPage } from './not_found'; +export { PageHeader } from './page_header'; diff --git a/x-pack/legacy/plugins/uptime/public/pages/monitor.tsx b/x-pack/legacy/plugins/uptime/public/pages/monitor.tsx index 8c5649f680fcb..1b4ad8d82ead1 100644 --- a/x-pack/legacy/plugins/uptime/public/pages/monitor.tsx +++ b/x-pack/legacy/plugins/uptime/public/pages/monitor.tsx @@ -5,66 +5,31 @@ */ import { EuiSpacer } from '@elastic/eui'; -import { ApolloQueryResult, OperationVariables, QueryOptions } from 'apollo-client'; -import gql from 'graphql-tag'; -import React, { Fragment, useContext, useEffect, useState } from 'react'; -import { getMonitorPageBreadcrumb } from '../breadcrumbs'; -import { MonitorCharts, MonitorPageTitle, PingList } from '../components/functional'; +import React, { Fragment, useContext, useState } from 'react'; +import { useParams } from 'react-router-dom'; +import { MonitorCharts, PingList } from '../components/functional'; import { UMUpdateBreadcrumbs } from '../lib/lib'; import { UptimeSettingsContext } from '../contexts'; -import { useUrlParams } from '../hooks'; -import { stringifyUrlParams } from '../lib/helper/stringify_url_params'; +import { useUptimeTelemetry, useUrlParams, UptimePage } from '../hooks'; import { useTrackPageview } from '../../../infra/public'; -import { getTitle } from '../lib/helper/get_title'; import { MonitorStatusDetails } from '../components/functional/monitor_status_details'; +import { PageHeader } from './page_header'; interface MonitorPageProps { - logMonitorPageLoad: () => void; - match: { params: { monitorId: string } }; - // this is the query function provided by Apollo's Client API - query: ( - options: QueryOptions - ) => Promise>; setBreadcrumbs: UMUpdateBreadcrumbs; } -export const MonitorPage = ({ - logMonitorPageLoad, - query, - setBreadcrumbs, - match, -}: MonitorPageProps) => { +export const MonitorPage = ({ setBreadcrumbs }: MonitorPageProps) => { // decode 64 base string, it was decoded to make it a valid url, since monitor id can be a url - const monitorId = atob(match.params.monitorId); + let { monitorId } = useParams(); + monitorId = atob(monitorId || ''); + const [pingListPageCount, setPingListPageCount] = useState(10); - const { colors, refreshApp, setHeadingText } = useContext(UptimeSettingsContext); + const { colors, refreshApp } = useContext(UptimeSettingsContext); const [getUrlParams, updateUrlParams] = useUrlParams(); const { absoluteDateRangeStart, absoluteDateRangeEnd, ...params } = getUrlParams(); const { dateRangeStart, dateRangeEnd, selectedPingStatus } = params; - useEffect(() => { - query({ - query: gql` - query MonitorPageTitle($monitorId: String!) { - monitorPageTitle: getMonitorPageTitle(monitorId: $monitorId) { - id - url - name - } - } - `, - variables: { monitorId }, - }).then((result: any) => { - const { name, url, id } = result.data.monitorPageTitle; - const heading: string = name || url || id; - document.title = getTitle(name); - setBreadcrumbs(getMonitorPageBreadcrumb(heading, stringifyUrlParams(params))); - if (setHeadingText) { - setHeadingText(heading); - } - }); - }, [monitorId, params, query, setBreadcrumbs, setHeadingText]); - const [selectedLocation, setSelectedLocation] = useState(undefined); const sharedVariables = { @@ -74,16 +39,14 @@ export const MonitorPage = ({ monitorId, }; - useEffect(() => { - logMonitorPageLoad(); - }, [logMonitorPageLoad]); + useUptimeTelemetry(UptimePage.Monitor); useTrackPageview({ app: 'uptime', path: 'monitor' }); useTrackPageview({ app: 'uptime', path: 'monitor', delay: 15000 }); return ( - + ; - history: any; - location: { - pathname: string; - search: string; - }; - logOverviewPageLoad: () => void; setBreadcrumbs: UMUpdateBreadcrumbs; } @@ -54,13 +47,8 @@ const EuiFlexItemStyled = styled(EuiFlexItem)` } `; -export const OverviewPage = ({ - basePath, - autocomplete, - logOverviewPageLoad, - setBreadcrumbs, -}: Props) => { - const { colors, setHeadingText } = useContext(UptimeSettingsContext); +export const OverviewPage = ({ autocomplete, setBreadcrumbs }: Props) => { + const { colors } = useContext(UptimeSettingsContext); const [getUrlParams, updateUrl] = useUrlParams(); const { absoluteDateRangeStart, absoluteDateRangeEnd, ...params } = getUrlParams(); const { @@ -72,25 +60,12 @@ export const OverviewPage = ({ filters: urlFilters, } = params; const [indexPattern, setIndexPattern] = useState(undefined); - - useEffect(() => { - getIndexPattern(basePath, setIndexPattern); - setBreadcrumbs(getOverviewPageBreadcrumbs()); - logOverviewPageLoad(); - if (setHeadingText) { - setHeadingText( - i18n.translate('xpack.uptime.overviewPage.headerText', { - defaultMessage: 'Overview', - description: `The text that will be displayed in the app's heading when the Overview page loads.`, - }) - ); - } - }, [basePath, logOverviewPageLoad, setBreadcrumbs, setHeadingText]); + useUptimeTelemetry(UptimePage.Overview); + useIndexPattern(setIndexPattern); useTrackPageview({ app: 'uptime', path: 'overview' }); useTrackPageview({ app: 'uptime', path: 'overview', delay: 15000 }); - const filterQueryString = search || ''; let error: any; let kueryString: string = ''; try { @@ -102,6 +77,7 @@ export const OverviewPage = ({ kueryString = ''; } + const filterQueryString = search || ''; let filters: any | undefined; try { if (filterQueryString || urlFilters) { @@ -111,6 +87,15 @@ export const OverviewPage = ({ const ast = esKuery.fromKueryExpression(combinedFilterString); const elasticsearchQuery = esKuery.toElasticsearchQuery(ast, staticIndexPattern); filters = JSON.stringify(elasticsearchQuery); + const searchDSL: string = filterQueryString + ? JSON.stringify( + esKuery.toElasticsearchQuery( + esKuery.fromKueryExpression(filterQueryString), + staticIndexPattern + ) + ) + : ''; + store.dispatch(setEsKueryString(searchDSL)); } } } catch (e) { @@ -128,20 +113,21 @@ export const OverviewPage = ({ return ( - + + { if (urlFilters !== filtersKuery) { updateUrl({ filters: filtersKuery, pagination: '' }); } }} - variables={sharedProps} /> {error && } diff --git a/x-pack/legacy/plugins/uptime/public/pages/page_header.tsx b/x-pack/legacy/plugins/uptime/public/pages/page_header.tsx new file mode 100644 index 0000000000000..250dacb8914e7 --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/pages/page_header.tsx @@ -0,0 +1,85 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { EuiTitle, EuiFlexGroup, EuiFlexItem, EuiSpacer } from '@elastic/eui'; +import React, { useEffect, useState, useContext } from 'react'; +import { connect } from 'react-redux'; +import { useRouteMatch, useParams } from 'react-router-dom'; +import { i18n } from '@kbn/i18n'; +import { UptimeDatePicker } from '../components/functional/uptime_date_picker'; +import { AppState } from '../state'; +import { selectSelectedMonitor } from '../state/selectors'; +import { getMonitorPageBreadcrumb, getOverviewPageBreadcrumbs } from '../breadcrumbs'; +import { stringifyUrlParams } from '../lib/helper/stringify_url_params'; +import { UptimeSettingsContext } from '../contexts'; +import { getTitle } from '../lib/helper/get_title'; +import { UMUpdateBreadcrumbs } from '../lib/lib'; +import { MONITOR_ROUTE } from '../routes'; + +interface PageHeaderProps { + monitorStatus?: any; + setBreadcrumbs: UMUpdateBreadcrumbs; +} + +export const PageHeaderComponent = ({ monitorStatus, setBreadcrumbs }: PageHeaderProps) => { + const monitorPage = useRouteMatch({ + path: MONITOR_ROUTE, + }); + const { refreshApp } = useContext(UptimeSettingsContext); + + const { absoluteDateRangeStart, absoluteDateRangeEnd, ...params } = useParams(); + + const headingText = i18n.translate('xpack.uptime.overviewPage.headerText', { + defaultMessage: 'Overview', + description: `The text that will be displayed in the app's heading when the Overview page loads.`, + }); + + const [headerText, setHeaderText] = useState(headingText); + + useEffect(() => { + if (monitorPage) { + setHeaderText(monitorStatus?.url?.full); + if (monitorStatus?.monitor) { + const { name, id } = monitorStatus.monitor; + document.title = getTitle(name || id); + } + } else { + document.title = getTitle(); + } + }, [monitorStatus, monitorPage, setHeaderText]); + + useEffect(() => { + if (monitorPage) { + if (headerText) { + setBreadcrumbs(getMonitorPageBreadcrumb(headerText, stringifyUrlParams(params))); + } + } else { + setBreadcrumbs(getOverviewPageBreadcrumbs()); + } + }, [headerText, setBreadcrumbs, params, monitorPage]); + + return ( + <> + + + +

{headerText}

+
+
+ + + +
+ + + ); +}; + +const mapStateToProps = (state: AppState) => ({ + monitorStatus: selectSelectedMonitor(state), +}); + +export const PageHeader = connect(mapStateToProps, null)(PageHeaderComponent); diff --git a/x-pack/legacy/plugins/uptime/public/queries/filter_bar_query.ts b/x-pack/legacy/plugins/uptime/public/queries/filter_bar_query.ts deleted file mode 100644 index a9b7e52c0f793..0000000000000 --- a/x-pack/legacy/plugins/uptime/public/queries/filter_bar_query.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import gql from 'graphql-tag'; - -export const filterBarQueryString = ` -query FilterBar($dateRangeStart: String!, $dateRangeEnd: String!) { - filterBar: getFilterBar(dateRangeStart: $dateRangeStart, dateRangeEnd: $dateRangeEnd) { - ids - locations - ports - schemes - urls - } -} -`; - -export const filterBarQuery = gql` - ${filterBarQueryString} -`; diff --git a/x-pack/legacy/plugins/uptime/public/queries/index.ts b/x-pack/legacy/plugins/uptime/public/queries/index.ts index b86522c03aba8..02c9c7cb23403 100644 --- a/x-pack/legacy/plugins/uptime/public/queries/index.ts +++ b/x-pack/legacy/plugins/uptime/public/queries/index.ts @@ -5,8 +5,5 @@ */ export { docCountQuery, docCountQueryString } from './doc_count_query'; -export { filterBarQuery, filterBarQueryString } from './filter_bar_query'; export { monitorChartsQuery, monitorChartsQueryString } from './monitor_charts_query'; -export { monitorPageTitleQuery } from './monitor_page_title_query'; -export { monitorStatusBarQuery, monitorStatusBarQueryString } from './monitor_status_bar_query'; export { pingsQuery, pingsQueryString } from './pings_query'; diff --git a/x-pack/legacy/plugins/uptime/public/queries/monitor_page_title_query.ts b/x-pack/legacy/plugins/uptime/public/queries/monitor_page_title_query.ts deleted file mode 100644 index 3b59ef80183f7..0000000000000 --- a/x-pack/legacy/plugins/uptime/public/queries/monitor_page_title_query.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import gql from 'graphql-tag'; - -export const monitorPageTitleQueryString = ` -query MonitorPageTitle($monitorId: String!) { - monitorPageTitle: getMonitorPageTitle(monitorId: $monitorId) { - id - url - name - } -}`; - -export const monitorPageTitleQuery = gql` - ${monitorPageTitleQueryString} -`; diff --git a/x-pack/legacy/plugins/uptime/public/queries/monitor_status_bar_query.ts b/x-pack/legacy/plugins/uptime/public/queries/monitor_status_bar_query.ts deleted file mode 100644 index 1f0eed6bb366a..0000000000000 --- a/x-pack/legacy/plugins/uptime/public/queries/monitor_status_bar_query.ts +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import gql from 'graphql-tag'; - -export const monitorStatusBarQueryString = ` -query MonitorStatus($dateRangeStart: String!, $dateRangeEnd: String!, $monitorId: String, $location: String) { - monitorStatus: getLatestMonitors( - dateRangeStart: $dateRangeStart - dateRangeEnd: $dateRangeEnd - monitorId: $monitorId - location: $location - ) { - timestamp - monitor { - status - duration { - us - } - } - observer { - geo { - name - } - } - tls { - certificate_not_valid_after - } - url { - full - } - } -} -`; - -export const monitorStatusBarQuery = gql` - ${monitorStatusBarQueryString} -`; diff --git a/x-pack/legacy/plugins/uptime/public/routes.tsx b/x-pack/legacy/plugins/uptime/public/routes.tsx new file mode 100644 index 0000000000000..08d752f5b32ab --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/routes.tsx @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import React, { FC } from 'react'; +import { Route, Switch } from 'react-router-dom'; +import { MonitorPage, OverviewPage, NotFoundPage } from './pages'; +import { AutocompleteProviderRegister } from '../../../../../src/plugins/data/public'; +import { UMUpdateBreadcrumbs } from './lib/lib'; + +export const MONITOR_ROUTE = '/monitor/:monitorId/:location?'; +export const OVERVIEW_ROUTE = '/'; + +interface RouterProps { + autocomplete: Pick; + basePath: string; + setBreadcrumbs: UMUpdateBreadcrumbs; +} + +export const PageRouter: FC = ({ autocomplete, basePath, setBreadcrumbs }) => ( + + + + + + + + + +); diff --git a/x-pack/legacy/plugins/uptime/public/state/actions/__tests__/__snapshots__/overview_filters.test.ts.snap b/x-pack/legacy/plugins/uptime/public/state/actions/__tests__/__snapshots__/overview_filters.test.ts.snap new file mode 100644 index 0000000000000..6fe2c8eaa362d --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/state/actions/__tests__/__snapshots__/overview_filters.test.ts.snap @@ -0,0 +1,61 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`overview filters action creators creates a fail action 1`] = ` +Object { + "payload": [Error: There was an error retrieving the overview filters], + "type": "FETCH_OVERVIEW_FILTERS_FAIL", +} +`; + +exports[`overview filters action creators creates a get action 1`] = ` +Object { + "payload": Object { + "dateRangeEnd": "now", + "dateRangeStart": "now-15m", + "locations": Array [ + "fairbanks", + "tokyo", + ], + "ports": Array [ + "80", + ], + "schemes": Array [ + "http", + "tcp", + ], + "search": "", + "statusFilter": "down", + "tags": Array [ + "api", + "dev", + ], + }, + "type": "FETCH_OVERVIEW_FILTERS", +} +`; + +exports[`overview filters action creators creates a success action 1`] = ` +Object { + "payload": Object { + "locations": Array [ + "fairbanks", + "tokyo", + "london", + ], + "ports": Array [ + 80, + 443, + ], + "schemes": Array [ + "http", + "tcp", + ], + "tags": Array [ + "api", + "dev", + "prod", + ], + }, + "type": "FETCH_OVERVIEW_FILTERS_SUCCESS", +} +`; diff --git a/x-pack/legacy/plugins/uptime/public/state/actions/__tests__/overview_filters.test.ts b/x-pack/legacy/plugins/uptime/public/state/actions/__tests__/overview_filters.test.ts new file mode 100644 index 0000000000000..4765e1327ce31 --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/state/actions/__tests__/overview_filters.test.ts @@ -0,0 +1,45 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + fetchOverviewFilters, + fetchOverviewFiltersSuccess, + fetchOverviewFiltersFail, +} from '../overview_filters'; + +describe('overview filters action creators', () => { + it('creates a get action', () => { + expect( + fetchOverviewFilters({ + dateRangeStart: 'now-15m', + dateRangeEnd: 'now', + statusFilter: 'down', + search: '', + locations: ['fairbanks', 'tokyo'], + ports: ['80'], + schemes: ['http', 'tcp'], + tags: ['api', 'dev'], + }) + ).toMatchSnapshot(); + }); + + it('creates a success action', () => { + expect( + fetchOverviewFiltersSuccess({ + locations: ['fairbanks', 'tokyo', 'london'], + ports: [80, 443], + schemes: ['http', 'tcp'], + tags: ['api', 'dev', 'prod'], + }) + ).toMatchSnapshot(); + }); + + it('creates a fail action', () => { + expect( + fetchOverviewFiltersFail(new Error('There was an error retrieving the overview filters')) + ).toMatchSnapshot(); + }); +}); diff --git a/x-pack/legacy/plugins/uptime/public/state/actions/index.ts b/x-pack/legacy/plugins/uptime/public/state/actions/index.ts index 6b896b07bb066..9874da1839c2f 100644 --- a/x-pack/legacy/plugins/uptime/public/state/actions/index.ts +++ b/x-pack/legacy/plugins/uptime/public/state/actions/index.ts @@ -4,5 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +export * from './overview_filters'; export * from './snapshot'; export * from './ui'; +export * from './monitor_status'; diff --git a/x-pack/legacy/plugins/uptime/public/state/actions/monitor.ts b/x-pack/legacy/plugins/uptime/public/state/actions/monitor.ts index 99855bb8c8df3..cf4525a08e43c 100644 --- a/x-pack/legacy/plugins/uptime/public/state/actions/monitor.ts +++ b/x-pack/legacy/plugins/uptime/public/state/actions/monitor.ts @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +import { MonitorDetailsActionPayload } from './types'; +import { MonitorError } from '../../../common/runtime_types'; import { MonitorLocations } from '../../../common/runtime_types'; import { QueryParams } from './types'; @@ -17,12 +19,12 @@ export const FETCH_MONITOR_LOCATIONS_FAIL = 'FETCH_MONITOR_LOCATIONS_FAIL'; export interface MonitorDetailsState { monitorId: string; - error: Error; + error: MonitorError; } interface GetMonitorDetailsAction { type: typeof FETCH_MONITOR_DETAILS; - payload: string; + payload: MonitorDetailsActionPayload; } interface GetMonitorDetailsSuccessAction { @@ -54,10 +56,10 @@ interface GetMonitorLocationsFailAction { payload: any; } -export function fetchMonitorDetails(monitorId: string): GetMonitorDetailsAction { +export function fetchMonitorDetails(payload: MonitorDetailsActionPayload): GetMonitorDetailsAction { return { type: FETCH_MONITOR_DETAILS, - payload: monitorId, + payload, }; } diff --git a/x-pack/legacy/plugins/uptime/public/state/actions/monitor_status.ts b/x-pack/legacy/plugins/uptime/public/state/actions/monitor_status.ts new file mode 100644 index 0000000000000..db103f6cb780e --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/state/actions/monitor_status.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { createAction } from 'redux-actions'; +import { QueryParams } from './types'; + +export const getSelectedMonitor = createAction<{ monitorId: string }>('GET_SELECTED_MONITOR'); +export const getSelectedMonitorSuccess = createAction('GET_SELECTED_MONITOR_SUCCESS'); +export const getSelectedMonitorFail = createAction('GET_SELECTED_MONITOR_FAIL'); + +export const getMonitorStatus = createAction('GET_MONITOR_STATUS'); +export const getMonitorStatusSuccess = createAction('GET_MONITOR_STATUS_SUCCESS'); +export const getMonitorStatusFail = createAction('GET_MONITOR_STATUS_FAIL'); diff --git a/x-pack/legacy/plugins/uptime/public/state/actions/overview_filters.ts b/x-pack/legacy/plugins/uptime/public/state/actions/overview_filters.ts new file mode 100644 index 0000000000000..dbbd01e34b4d4 --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/state/actions/overview_filters.ts @@ -0,0 +1,61 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { OverviewFilters } from '../../../common/runtime_types'; + +export const FETCH_OVERVIEW_FILTERS = 'FETCH_OVERVIEW_FILTERS'; +export const FETCH_OVERVIEW_FILTERS_FAIL = 'FETCH_OVERVIEW_FILTERS_FAIL'; +export const FETCH_OVERVIEW_FILTERS_SUCCESS = 'FETCH_OVERVIEW_FILTERS_SUCCESS'; + +export interface GetOverviewFiltersPayload { + dateRangeStart: string; + dateRangeEnd: string; + locations: string[]; + ports: string[]; + schemes: string[]; + search?: string; + statusFilter?: string; + tags: string[]; +} + +interface GetOverviewFiltersFetchAction { + type: typeof FETCH_OVERVIEW_FILTERS; + payload: GetOverviewFiltersPayload; +} + +interface GetOverviewFiltersSuccessAction { + type: typeof FETCH_OVERVIEW_FILTERS_SUCCESS; + payload: OverviewFilters; +} + +interface GetOverviewFiltersFailAction { + type: typeof FETCH_OVERVIEW_FILTERS_FAIL; + payload: Error; +} + +export type OverviewFiltersAction = + | GetOverviewFiltersFetchAction + | GetOverviewFiltersSuccessAction + | GetOverviewFiltersFailAction; + +export const fetchOverviewFilters = ( + payload: GetOverviewFiltersPayload +): GetOverviewFiltersFetchAction => ({ + type: FETCH_OVERVIEW_FILTERS, + payload, +}); + +export const fetchOverviewFiltersFail = (error: Error): GetOverviewFiltersFailAction => ({ + type: FETCH_OVERVIEW_FILTERS_FAIL, + payload: error, +}); + +export const fetchOverviewFiltersSuccess = ( + filters: OverviewFilters +): GetOverviewFiltersSuccessAction => ({ + type: FETCH_OVERVIEW_FILTERS_SUCCESS, + payload: filters, +}); diff --git a/x-pack/legacy/plugins/uptime/public/state/actions/snapshot.ts b/x-pack/legacy/plugins/uptime/public/state/actions/snapshot.ts index fe87a6a5960ee..57d2b4ce38204 100644 --- a/x-pack/legacy/plugins/uptime/public/state/actions/snapshot.ts +++ b/x-pack/legacy/plugins/uptime/public/state/actions/snapshot.ts @@ -5,6 +5,7 @@ */ import { Snapshot } from '../../../common/runtime_types'; + export const FETCH_SNAPSHOT_COUNT = 'FETCH_SNAPSHOT_COUNT'; export const FETCH_SNAPSHOT_COUNT_FAIL = 'FETCH_SNAPSHOT_COUNT_FAIL'; export const FETCH_SNAPSHOT_COUNT_SUCCESS = 'FETCH_SNAPSHOT_COUNT_SUCCESS'; diff --git a/x-pack/legacy/plugins/uptime/public/state/actions/types.ts b/x-pack/legacy/plugins/uptime/public/state/actions/types.ts index 7ec288583f9fe..dba70ed839ac5 100644 --- a/x-pack/legacy/plugins/uptime/public/state/actions/types.ts +++ b/x-pack/legacy/plugins/uptime/public/state/actions/types.ts @@ -5,8 +5,17 @@ */ export interface QueryParams { + monitorId: string; dateStart: string; dateEnd: string; filters?: string; statusFilter?: string; + location?: string; +} + +export interface MonitorDetailsActionPayload { + monitorId: string; + dateStart: string; + dateEnd: string; + location?: string; } diff --git a/x-pack/legacy/plugins/uptime/public/state/actions/ui.ts b/x-pack/legacy/plugins/uptime/public/state/actions/ui.ts index 0bb2d8447419b..d15d601737b2d 100644 --- a/x-pack/legacy/plugins/uptime/public/state/actions/ui.ts +++ b/x-pack/legacy/plugins/uptime/public/state/actions/ui.ts @@ -3,53 +3,21 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - -export const SET_INTEGRATION_POPOVER_STATE = 'SET_INTEGRATION_POPOVER_STATE'; -export const SET_BASE_PATH = 'SET_BASE_PATH'; -export const REFRESH_APP = 'REFRESH_APP'; +import { createAction } from 'redux-actions'; export interface PopoverState { id: string; open: boolean; } -interface SetBasePathAction { - type: typeof SET_BASE_PATH; - payload: string; -} +export type UiPayload = PopoverState & string & number & Map; -interface SetIntegrationPopoverAction { - type: typeof SET_INTEGRATION_POPOVER_STATE; - payload: PopoverState; -} +export const setBasePath = createAction('SET BASE PATH'); -interface TriggerAppRefreshAction { - type: typeof REFRESH_APP; - payload: number; -} +export const triggerAppRefresh = createAction('REFRESH APP'); -export type UiActionTypes = - | SetIntegrationPopoverAction - | SetBasePathAction - | TriggerAppRefreshAction; +export const setEsKueryString = createAction('SET ES KUERY STRING'); -export function toggleIntegrationsPopover(popoverState: PopoverState): SetIntegrationPopoverAction { - return { - type: SET_INTEGRATION_POPOVER_STATE, - payload: popoverState, - }; -} - -export function setBasePath(basePath: string): SetBasePathAction { - return { - type: SET_BASE_PATH, - payload: basePath, - }; -} - -export function triggerAppRefresh(refreshTime: number): TriggerAppRefreshAction { - return { - type: REFRESH_APP, - payload: refreshTime, - }; -} +export const toggleIntegrationsPopover = createAction( + 'TOGGLE INTEGRATION POPOVER STATE' +); diff --git a/x-pack/legacy/plugins/uptime/public/state/api/__tests__/__snapshots__/snapshot.test.ts.snap b/x-pack/legacy/plugins/uptime/public/state/api/__tests__/__snapshots__/snapshot.test.ts.snap index 53716681664c2..0d2392390c7e4 100644 --- a/x-pack/legacy/plugins/uptime/public/state/api/__tests__/__snapshots__/snapshot.test.ts.snap +++ b/x-pack/legacy/plugins/uptime/public/state/api/__tests__/__snapshots__/snapshot.test.ts.snap @@ -1,8 +1,7 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP exports[`snapshot API throws when server response doesn't correspond to expected type 1`] = ` -[Error: Invalid value undefined supplied to : { down: number, mixed: number, total: number, up: number }/down: number -Invalid value undefined supplied to : { down: number, mixed: number, total: number, up: number }/mixed: number -Invalid value undefined supplied to : { down: number, mixed: number, total: number, up: number }/total: number -Invalid value undefined supplied to : { down: number, mixed: number, total: number, up: number }/up: number] +[Error: Invalid value undefined supplied to : { down: number, total: number, up: number }/down: number +Invalid value undefined supplied to : { down: number, total: number, up: number }/total: number +Invalid value undefined supplied to : { down: number, total: number, up: number }/up: number] `; diff --git a/x-pack/legacy/plugins/uptime/public/state/api/__tests__/snapshot.test.ts b/x-pack/legacy/plugins/uptime/public/state/api/__tests__/snapshot.test.ts index f5fdfb172bc58..e9b1391a23e32 100644 --- a/x-pack/legacy/plugins/uptime/public/state/api/__tests__/snapshot.test.ts +++ b/x-pack/legacy/plugins/uptime/public/state/api/__tests__/snapshot.test.ts @@ -14,7 +14,7 @@ describe('snapshot API', () => { fetchMock = jest.spyOn(window, 'fetch'); mockResponse = { ok: true, - json: () => new Promise(r => r({ up: 3, down: 12, mixed: 0, total: 15 })), + json: () => new Promise(r => r({ up: 3, down: 12, total: 15 })), }; }); @@ -34,7 +34,7 @@ describe('snapshot API', () => { expect(fetchMock).toHaveBeenCalledWith( '/api/uptime/snapshot/count?dateRangeStart=now-15m&dateRangeEnd=now&filters=monitor.id%3A%22auto-http-0X21EE76EAC459873F%22&statusFilter=up' ); - expect(resp).toEqual({ up: 3, down: 12, mixed: 0, total: 15 }); + expect(resp).toEqual({ up: 3, down: 12, total: 15 }); }); it(`throws when server response doesn't correspond to expected type`, async () => { diff --git a/x-pack/legacy/plugins/uptime/public/state/api/index.ts b/x-pack/legacy/plugins/uptime/public/state/api/index.ts index a4429868494f1..1d0cac5f87854 100644 --- a/x-pack/legacy/plugins/uptime/public/state/api/index.ts +++ b/x-pack/legacy/plugins/uptime/public/state/api/index.ts @@ -5,4 +5,6 @@ */ export * from './monitor'; +export * from './overview_filters'; export * from './snapshot'; +export * from './monitor_status'; diff --git a/x-pack/legacy/plugins/uptime/public/state/api/monitor.ts b/x-pack/legacy/plugins/uptime/public/state/api/monitor.ts index 0fb00b935342e..8b1220830f091 100644 --- a/x-pack/legacy/plugins/uptime/public/state/api/monitor.ts +++ b/x-pack/legacy/plugins/uptime/public/state/api/monitor.ts @@ -6,6 +6,7 @@ import { ThrowReporter } from 'io-ts/lib/ThrowReporter'; import { getApiPath } from '../../lib/helper'; +import { BaseParams } from './types'; import { MonitorDetailsType, MonitorDetails, @@ -19,12 +20,23 @@ interface ApiRequest { basePath: string; } +export type MonitorQueryParams = BaseParams & ApiRequest; + export const fetchMonitorDetails = async ({ monitorId, basePath, -}: ApiRequest): Promise => { - const url = getApiPath(`/api/uptime/monitor/details?monitorId=${monitorId}`, basePath); - const response = await fetch(url); + dateStart, + dateEnd, +}: MonitorQueryParams): Promise => { + const url = getApiPath(`/api/uptime/monitor/details`, basePath); + const params = { + monitorId, + dateStart, + dateEnd, + }; + const urlParams = new URLSearchParams(params).toString(); + const response = await fetch(`${url}?${urlParams}`); + if (!response.ok) { throw new Error(response.statusText); } diff --git a/x-pack/legacy/plugins/uptime/public/state/api/monitor_status.ts b/x-pack/legacy/plugins/uptime/public/state/api/monitor_status.ts new file mode 100644 index 0000000000000..936e864b75619 --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/state/api/monitor_status.ts @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { getApiPath } from '../../lib/helper'; +import { QueryParams } from '../actions/types'; +import { Ping } from '../../../common/graphql/types'; + +export interface APIParams { + basePath: string; + monitorId: string; +} + +export const fetchSelectedMonitor = async ({ basePath, monitorId }: APIParams): Promise => { + const url = getApiPath(`/api/uptime/monitor/selected`, basePath); + const params = { + monitorId, + }; + const urlParams = new URLSearchParams(params).toString(); + const response = await fetch(`${url}?${urlParams}`); + if (!response.ok) { + throw new Error(response.statusText); + } + const responseData = await response.json(); + return responseData; +}; + +export const fetchMonitorStatus = async ({ + basePath, + monitorId, + dateStart, + dateEnd, +}: QueryParams & APIParams): Promise => { + const url = getApiPath(`/api/uptime/monitor/status`, basePath); + const params = { + monitorId, + dateStart, + dateEnd, + }; + const urlParams = new URLSearchParams(params).toString(); + const response = await fetch(`${url}?${urlParams}`); + if (!response.ok) { + throw new Error(response.statusText); + } + const responseData = await response.json(); + return responseData; +}; diff --git a/x-pack/legacy/plugins/uptime/public/state/api/overview_filters.ts b/x-pack/legacy/plugins/uptime/public/state/api/overview_filters.ts new file mode 100644 index 0000000000000..c3ef62fa88dcf --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/state/api/overview_filters.ts @@ -0,0 +1,52 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { ThrowReporter } from 'io-ts/lib/ThrowReporter'; +import { isRight } from 'fp-ts/lib/Either'; +import { GetOverviewFiltersPayload } from '../actions/overview_filters'; +import { getApiPath, parameterizeValues } from '../../lib/helper'; +import { OverviewFiltersType } from '../../../common/runtime_types'; + +type ApiRequest = GetOverviewFiltersPayload & { + basePath: string; +}; + +export const fetchOverviewFilters = async ({ + basePath, + dateRangeStart, + dateRangeEnd, + search, + schemes, + locations, + ports, + tags, +}: ApiRequest) => { + const url = getApiPath(`/api/uptime/filters`, basePath); + + const params = new URLSearchParams({ + dateRangeStart, + dateRangeEnd, + }); + + if (search) { + params.append('search', search); + } + + parameterizeValues(params, { schemes, locations, ports, tags }); + + const response = await fetch(`${url}?${params.toString()}`); + if (!response.ok) { + throw new Error(response.statusText); + } + const responseData = await response.json(); + const decoded = OverviewFiltersType.decode(responseData); + + ThrowReporter.report(decoded); + if (isRight(decoded)) { + return decoded.right; + } + throw new Error('`getOverviewFilters` response did not correspond to expected type'); +}; diff --git a/x-pack/legacy/plugins/uptime/public/state/api/types.ts b/x-pack/legacy/plugins/uptime/public/state/api/types.ts new file mode 100644 index 0000000000000..278cfce29986f --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/state/api/types.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export interface BaseParams { + basePath: string; + dateStart: string; + dateEnd: string; + filters?: string; + statusFilter?: string; + location?: string; +} diff --git a/x-pack/legacy/plugins/uptime/public/state/effects/fetch_effect.ts b/x-pack/legacy/plugins/uptime/public/state/effects/fetch_effect.ts new file mode 100644 index 0000000000000..d293cdbe451b5 --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/state/effects/fetch_effect.ts @@ -0,0 +1,43 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { call, put, select } from 'redux-saga/effects'; +import { Action } from 'redux-actions'; +import { getBasePath } from '../selectors'; + +/** + * Factory function for a fetch effect. It expects three action creators, + * one to call for a fetch, one to call for success, and one to handle failures. + * @param fetch creates a fetch action + * @param success creates a success action + * @param fail creates a failure action + * @template T the action type expected by the fetch action + * @template R the type that the API request should return on success + * @template S tye type of the success action + * @template F the type of the failure action + */ +export function fetchEffectFactory( + fetch: (request: T) => Promise, + success: (response: R) => Action, + fail: (error: Error) => Action +) { + return function*(action: Action) { + try { + if (!action.payload) { + yield put(fail(new Error('Cannot fetch snapshot for undefined parameters.'))); + return; + } + const { + payload: { ...params }, + } = action; + const basePath = yield select(getBasePath); + const response = yield call(fetch, { ...params, basePath }); + yield put(success(response)); + } catch (error) { + yield put(fail(error)); + } + }; +} diff --git a/x-pack/legacy/plugins/uptime/public/state/effects/index.ts b/x-pack/legacy/plugins/uptime/public/state/effects/index.ts index 4eb027d642974..41dda145edb4e 100644 --- a/x-pack/legacy/plugins/uptime/public/state/effects/index.ts +++ b/x-pack/legacy/plugins/uptime/public/state/effects/index.ts @@ -6,9 +6,13 @@ import { fork } from 'redux-saga/effects'; import { fetchMonitorDetailsEffect } from './monitor'; -import { fetchSnapshotCountSaga } from './snapshot'; +import { fetchOverviewFiltersEffect } from './overview_filters'; +import { fetchSnapshotCountEffect } from './snapshot'; +import { fetchMonitorStatusEffect } from './monitor_status'; export function* rootEffect() { yield fork(fetchMonitorDetailsEffect); - yield fork(fetchSnapshotCountSaga); + yield fork(fetchSnapshotCountEffect); + yield fork(fetchOverviewFiltersEffect); + yield fork(fetchMonitorStatusEffect); } diff --git a/x-pack/legacy/plugins/uptime/public/state/effects/monitor.ts b/x-pack/legacy/plugins/uptime/public/state/effects/monitor.ts index 210004bb343bb..1cac7424b4e5b 100644 --- a/x-pack/legacy/plugins/uptime/public/state/effects/monitor.ts +++ b/x-pack/legacy/plugins/uptime/public/state/effects/monitor.ts @@ -16,12 +16,18 @@ import { } from '../actions/monitor'; import { fetchMonitorDetails, fetchMonitorLocations } from '../api'; import { getBasePath } from '../selectors'; +import { MonitorDetailsActionPayload } from '../actions/types'; function* monitorDetailsEffect(action: Action) { - const monitorId: string = action.payload; + const { monitorId, dateStart, dateEnd }: MonitorDetailsActionPayload = action.payload; try { const basePath = yield select(getBasePath); - const response = yield call(fetchMonitorDetails, { monitorId, basePath }); + const response = yield call(fetchMonitorDetails, { + monitorId, + basePath, + dateStart, + dateEnd, + }); yield put({ type: FETCH_MONITOR_DETAILS_SUCCESS, payload: response }); } catch (error) { yield put({ type: FETCH_MONITOR_DETAILS_FAIL, payload: error.message }); diff --git a/x-pack/legacy/plugins/uptime/public/state/effects/monitor_status.ts b/x-pack/legacy/plugins/uptime/public/state/effects/monitor_status.ts new file mode 100644 index 0000000000000..cab32092a14cd --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/state/effects/monitor_status.ts @@ -0,0 +1,53 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { call, put, takeLatest, select } from 'redux-saga/effects'; +import { Action } from 'redux-actions'; +import { + getSelectedMonitor, + getSelectedMonitorSuccess, + getSelectedMonitorFail, + getMonitorStatus, + getMonitorStatusSuccess, + getMonitorStatusFail, +} from '../actions/monitor_status'; +import { fetchSelectedMonitor, fetchMonitorStatus } from '../api'; +import { getBasePath } from '../selectors'; + +function* selectedMonitorEffect(action: Action) { + const { monitorId } = action.payload; + try { + const basePath = yield select(getBasePath); + const response = yield call(fetchSelectedMonitor, { + monitorId, + basePath, + }); + yield put({ type: getSelectedMonitorSuccess, payload: response }); + } catch (error) { + yield put({ type: getSelectedMonitorFail, payload: error.message }); + } +} + +function* monitorStatusEffect(action: Action) { + const { monitorId, dateStart, dateEnd } = action.payload; + try { + const basePath = yield select(getBasePath); + const response = yield call(fetchMonitorStatus, { + monitorId, + basePath, + dateStart, + dateEnd, + }); + yield put({ type: getMonitorStatusSuccess, payload: response }); + } catch (error) { + yield put({ type: getMonitorStatusFail, payload: error.message }); + } +} + +export function* fetchMonitorStatusEffect() { + yield takeLatest(getMonitorStatus, monitorStatusEffect); + yield takeLatest(getSelectedMonitor, selectedMonitorEffect); +} diff --git a/x-pack/legacy/plugins/uptime/public/state/effects/overview_filters.ts b/x-pack/legacy/plugins/uptime/public/state/effects/overview_filters.ts new file mode 100644 index 0000000000000..92b578bafed2d --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/state/effects/overview_filters.ts @@ -0,0 +1,21 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { takeLatest } from 'redux-saga/effects'; +import { + FETCH_OVERVIEW_FILTERS, + fetchOverviewFiltersFail, + fetchOverviewFiltersSuccess, +} from '../actions'; +import { fetchOverviewFilters } from '../api'; +import { fetchEffectFactory } from './fetch_effect'; + +export function* fetchOverviewFiltersEffect() { + yield takeLatest( + FETCH_OVERVIEW_FILTERS, + fetchEffectFactory(fetchOverviewFilters, fetchOverviewFiltersSuccess, fetchOverviewFiltersFail) + ); +} diff --git a/x-pack/legacy/plugins/uptime/public/state/effects/snapshot.ts b/x-pack/legacy/plugins/uptime/public/state/effects/snapshot.ts index 23ac1016d2244..91df43dd9e826 100644 --- a/x-pack/legacy/plugins/uptime/public/state/effects/snapshot.ts +++ b/x-pack/legacy/plugins/uptime/public/state/effects/snapshot.ts @@ -4,42 +4,18 @@ * you may not use this file except in compliance with the Elastic License. */ -import { call, put, takeLatest, select } from 'redux-saga/effects'; -import { Action } from 'redux-actions'; +import { takeLatest } from 'redux-saga/effects'; import { FETCH_SNAPSHOT_COUNT, - GetSnapshotPayload, fetchSnapshotCountFail, fetchSnapshotCountSuccess, } from '../actions'; import { fetchSnapshotCount } from '../api'; -import { getBasePath } from '../selectors'; +import { fetchEffectFactory } from './fetch_effect'; -function* snapshotSaga(action: Action) { - try { - if (!action.payload) { - yield put( - fetchSnapshotCountFail(new Error('Cannot fetch snapshot for undefined parameters.')) - ); - return; - } - const { - payload: { dateRangeStart, dateRangeEnd, filters, statusFilter }, - } = action; - const basePath = yield select(getBasePath); - const response = yield call(fetchSnapshotCount, { - basePath, - dateRangeStart, - dateRangeEnd, - filters, - statusFilter, - }); - yield put(fetchSnapshotCountSuccess(response)); - } catch (error) { - yield put(fetchSnapshotCountFail(error)); - } -} - -export function* fetchSnapshotCountSaga() { - yield takeLatest(FETCH_SNAPSHOT_COUNT, snapshotSaga); +export function* fetchSnapshotCountEffect() { + yield takeLatest( + FETCH_SNAPSHOT_COUNT, + fetchEffectFactory(fetchSnapshotCount, fetchSnapshotCountSuccess, fetchSnapshotCountFail) + ); } diff --git a/x-pack/legacy/plugins/uptime/public/state/reducers/__tests__/__snapshots__/snapshot.test.ts.snap b/x-pack/legacy/plugins/uptime/public/state/reducers/__tests__/__snapshots__/snapshot.test.ts.snap index d3a21ec9eece3..7a3c72f93d86f 100644 --- a/x-pack/legacy/plugins/uptime/public/state/reducers/__tests__/__snapshots__/snapshot.test.ts.snap +++ b/x-pack/legacy/plugins/uptime/public/state/reducers/__tests__/__snapshots__/snapshot.test.ts.snap @@ -4,7 +4,6 @@ exports[`snapshot reducer appends a current error to existing errors list 1`] = Object { "count": Object { "down": 0, - "mixed": 0, "total": 0, "up": 0, }, @@ -19,7 +18,6 @@ exports[`snapshot reducer changes the count when a snapshot fetch succeeds 1`] = Object { "count": Object { "down": 15, - "mixed": 0, "total": 25, "up": 10, }, @@ -32,7 +30,6 @@ exports[`snapshot reducer sets the state's status to loading during a fetch 1`] Object { "count": Object { "down": 0, - "mixed": 0, "total": 0, "up": 0, }, @@ -45,7 +42,6 @@ exports[`snapshot reducer updates existing state 1`] = ` Object { "count": Object { "down": 1, - "mixed": 0, "total": 4, "up": 3, }, diff --git a/x-pack/legacy/plugins/uptime/public/state/reducers/__tests__/__snapshots__/ui.test.ts.snap b/x-pack/legacy/plugins/uptime/public/state/reducers/__tests__/__snapshots__/ui.test.ts.snap index 75516da18c633..5d03c0058c3c1 100644 --- a/x-pack/legacy/plugins/uptime/public/state/reducers/__tests__/__snapshots__/ui.test.ts.snap +++ b/x-pack/legacy/plugins/uptime/public/state/reducers/__tests__/__snapshots__/ui.test.ts.snap @@ -3,6 +3,7 @@ exports[`ui reducer adds integration popover status to state 1`] = ` Object { "basePath": "", + "esKuery": "", "integrationsPopoverOpen": Object { "id": "popover-2", "open": true, @@ -14,6 +15,7 @@ Object { exports[`ui reducer sets the application's base path 1`] = ` Object { "basePath": "yyz", + "esKuery": "", "integrationsPopoverOpen": null, "lastRefresh": 125, } @@ -21,7 +23,8 @@ Object { exports[`ui reducer updates the refresh value 1`] = ` Object { - "basePath": "", + "basePath": "abc", + "esKuery": "", "integrationsPopoverOpen": null, "lastRefresh": 125, } diff --git a/x-pack/legacy/plugins/uptime/public/state/reducers/__tests__/snapshot.test.ts b/x-pack/legacy/plugins/uptime/public/state/reducers/__tests__/snapshot.test.ts index a4b317d5af197..95c576e0fd72e 100644 --- a/x-pack/legacy/plugins/uptime/public/state/reducers/__tests__/snapshot.test.ts +++ b/x-pack/legacy/plugins/uptime/public/state/reducers/__tests__/snapshot.test.ts @@ -21,7 +21,7 @@ describe('snapshot reducer', () => { expect( snapshotReducer( { - count: { down: 1, mixed: 0, total: 4, up: 3 }, + count: { down: 1, total: 4, up: 3 }, errors: [], loading: false, }, @@ -47,7 +47,6 @@ describe('snapshot reducer', () => { payload: { up: 10, down: 15, - mixed: 0, total: 25, }, }; diff --git a/x-pack/legacy/plugins/uptime/public/state/reducers/__tests__/ui.test.ts b/x-pack/legacy/plugins/uptime/public/state/reducers/__tests__/ui.test.ts index 9be863f0b700d..417095b64ba2d 100644 --- a/x-pack/legacy/plugins/uptime/public/state/reducers/__tests__/ui.test.ts +++ b/x-pack/legacy/plugins/uptime/public/state/reducers/__tests__/ui.test.ts @@ -4,19 +4,18 @@ * you may not use this file except in compliance with the Elastic License. */ -import { UiActionTypes } from '../../actions'; +import { setBasePath, toggleIntegrationsPopover, triggerAppRefresh } from '../../actions'; import { uiReducer } from '../ui'; +import { Action } from 'redux-actions'; describe('ui reducer', () => { it(`sets the application's base path`, () => { - const action: UiActionTypes = { - type: 'SET_BASE_PATH', - payload: 'yyz', - }; + const action = setBasePath('yyz') as Action; expect( uiReducer( { basePath: 'abc', + esKuery: '', integrationsPopoverOpen: null, lastRefresh: 125, }, @@ -26,17 +25,15 @@ describe('ui reducer', () => { }); it('adds integration popover status to state', () => { - const action: UiActionTypes = { - type: 'SET_INTEGRATION_POPOVER_STATE', - payload: { - id: 'popover-2', - open: true, - }, - }; + const action = toggleIntegrationsPopover({ + id: 'popover-2', + open: true, + }) as Action; expect( uiReducer( { basePath: '', + esKuery: '', integrationsPopoverOpen: null, lastRefresh: 125, }, @@ -46,10 +43,17 @@ describe('ui reducer', () => { }); it('updates the refresh value', () => { - const action: UiActionTypes = { - type: 'REFRESH_APP', - payload: 125, - }; - expect(uiReducer(undefined, action)).toMatchSnapshot(); + const action = triggerAppRefresh(125) as Action; + expect( + uiReducer( + { + basePath: 'abc', + esKuery: '', + integrationsPopoverOpen: null, + lastRefresh: 125, + }, + action + ) + ).toMatchSnapshot(); }); }); diff --git a/x-pack/legacy/plugins/uptime/public/state/reducers/index.ts b/x-pack/legacy/plugins/uptime/public/state/reducers/index.ts index f0c3d1c2cbecf..5f915d970e543 100644 --- a/x-pack/legacy/plugins/uptime/public/state/reducers/index.ts +++ b/x-pack/legacy/plugins/uptime/public/state/reducers/index.ts @@ -6,11 +6,15 @@ import { combineReducers } from 'redux'; import { monitorReducer } from './monitor'; +import { overviewFiltersReducer } from './overview_filters'; import { snapshotReducer } from './snapshot'; import { uiReducer } from './ui'; +import { monitorStatusReducer } from './monitor_status'; export const rootReducer = combineReducers({ monitor: monitorReducer, + overviewFilters: overviewFiltersReducer, snapshot: snapshotReducer, ui: uiReducer, + monitorStatus: monitorStatusReducer, }); diff --git a/x-pack/legacy/plugins/uptime/public/state/reducers/monitor.ts b/x-pack/legacy/plugins/uptime/public/state/reducers/monitor.ts index 220ab0b205462..aac8a90598d0c 100644 --- a/x-pack/legacy/plugins/uptime/public/state/reducers/monitor.ts +++ b/x-pack/legacy/plugins/uptime/public/state/reducers/monitor.ts @@ -19,10 +19,10 @@ import { MonitorLocations } from '../../../common/runtime_types'; type MonitorLocationsList = Map; export interface MonitorState { - monitorDetailsList: MonitorDetailsState[]; - monitorLocationsList: MonitorLocationsList; loading: boolean; errors: any[]; + monitorDetailsList: MonitorDetailsState[]; + monitorLocationsList: MonitorLocationsList; } const initialState: MonitorState = { diff --git a/x-pack/legacy/plugins/uptime/public/state/reducers/monitor_status.ts b/x-pack/legacy/plugins/uptime/public/state/reducers/monitor_status.ts new file mode 100644 index 0000000000000..2688a0946dd61 --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/state/reducers/monitor_status.ts @@ -0,0 +1,67 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { handleActions, Action } from 'redux-actions'; +import { + getSelectedMonitor, + getSelectedMonitorSuccess, + getSelectedMonitorFail, + getMonitorStatus, + getMonitorStatusSuccess, + getMonitorStatusFail, +} from '../actions'; +import { Ping } from '../../../common/graphql/types'; +import { QueryParams } from '../actions/types'; + +export interface MonitorStatusState { + status: Ping | null; + monitor: Ping | null; + loading: boolean; +} + +const initialState: MonitorStatusState = { + status: null, + monitor: null, + loading: false, +}; + +type MonitorStatusPayload = QueryParams & Ping; + +export const monitorStatusReducer = handleActions( + { + [String(getSelectedMonitor)]: (state, action: Action) => ({ + ...state, + loading: true, + }), + + [String(getSelectedMonitorSuccess)]: (state, action: Action) => ({ + ...state, + loading: false, + monitor: { ...action.payload } as Ping, + }), + + [String(getSelectedMonitorFail)]: (state, action: Action) => ({ + ...state, + loading: false, + }), + + [String(getMonitorStatus)]: (state, action: Action) => ({ + ...state, + loading: true, + }), + + [String(getMonitorStatusSuccess)]: (state, action: Action) => ({ + ...state, + loading: false, + status: { ...action.payload } as Ping, + }), + + [String(getMonitorStatusFail)]: (state, action: Action) => ({ + ...state, + loading: false, + }), + }, + initialState +); diff --git a/x-pack/legacy/plugins/uptime/public/state/reducers/overview_filters.ts b/x-pack/legacy/plugins/uptime/public/state/reducers/overview_filters.ts new file mode 100644 index 0000000000000..b219421f4f4dc --- /dev/null +++ b/x-pack/legacy/plugins/uptime/public/state/reducers/overview_filters.ts @@ -0,0 +1,56 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { OverviewFilters } from '../../../common/runtime_types'; +import { + FETCH_OVERVIEW_FILTERS, + FETCH_OVERVIEW_FILTERS_FAIL, + FETCH_OVERVIEW_FILTERS_SUCCESS, + OverviewFiltersAction, +} from '../actions'; + +export interface OverviewFiltersState { + filters: OverviewFilters; + errors: Error[]; + loading: boolean; +} + +const initialState: OverviewFiltersState = { + filters: { + locations: [], + ports: [], + schemes: [], + tags: [], + }, + errors: [], + loading: false, +}; + +export function overviewFiltersReducer( + state = initialState, + action: OverviewFiltersAction +): OverviewFiltersState { + switch (action.type) { + case FETCH_OVERVIEW_FILTERS: + return { + ...state, + loading: true, + }; + case FETCH_OVERVIEW_FILTERS_SUCCESS: + return { + ...state, + filters: action.payload, + loading: false, + }; + case FETCH_OVERVIEW_FILTERS_FAIL: + return { + ...state, + errors: [...state.errors, action.payload], + }; + default: + return state; + } +} diff --git a/x-pack/legacy/plugins/uptime/public/state/reducers/snapshot.ts b/x-pack/legacy/plugins/uptime/public/state/reducers/snapshot.ts index dd9449325f4fb..2155d0e3a74e3 100644 --- a/x-pack/legacy/plugins/uptime/public/state/reducers/snapshot.ts +++ b/x-pack/legacy/plugins/uptime/public/state/reducers/snapshot.ts @@ -21,7 +21,6 @@ export interface SnapshotState { const initialState: SnapshotState = { count: { down: 0, - mixed: 0, total: 0, up: 0, }, diff --git a/x-pack/legacy/plugins/uptime/public/state/reducers/ui.ts b/x-pack/legacy/plugins/uptime/public/state/reducers/ui.ts index be95c8fff6bec..bb5bd22085ac6 100644 --- a/x-pack/legacy/plugins/uptime/public/state/reducers/ui.ts +++ b/x-pack/legacy/plugins/uptime/public/state/reducers/ui.ts @@ -4,49 +4,51 @@ * you may not use this file except in compliance with the Elastic License. */ +import { handleActions, Action } from 'redux-actions'; import { - UiActionTypes, PopoverState, - SET_INTEGRATION_POPOVER_STATE, - SET_BASE_PATH, - REFRESH_APP, + toggleIntegrationsPopover, + setBasePath, + setEsKueryString, + triggerAppRefresh, + UiPayload, } from '../actions/ui'; export interface UiState { integrationsPopoverOpen: PopoverState | null; basePath: string; + esKuery: string; lastRefresh: number; } const initialState: UiState = { integrationsPopoverOpen: null, basePath: '', + esKuery: '', lastRefresh: Date.now(), }; -export function uiReducer(state = initialState, action: UiActionTypes): UiState { - switch (action.type) { - case REFRESH_APP: - return { - ...state, - lastRefresh: action.payload, - }; - case SET_INTEGRATION_POPOVER_STATE: - const popoverState = action.payload; - return { - ...state, - integrationsPopoverOpen: { - id: popoverState.id, - open: popoverState.open, - }, - }; - case SET_BASE_PATH: - const basePath = action.payload; - return { - ...state, - basePath, - }; - default: - return state; - } -} +export const uiReducer = handleActions( + { + [String(toggleIntegrationsPopover)]: (state, action: Action) => ({ + ...state, + integrationsPopoverOpen: action.payload as PopoverState, + }), + + [String(setBasePath)]: (state, action: Action) => ({ + ...state, + basePath: action.payload as string, + }), + + [String(triggerAppRefresh)]: (state, action: Action) => ({ + ...state, + lastRefresh: action.payload as number, + }), + + [String(setEsKueryString)]: (state, action: Action) => ({ + ...state, + esKuery: action.payload as string, + }), + }, + initialState +); diff --git a/x-pack/legacy/plugins/uptime/public/state/selectors/__tests__/index.test.ts b/x-pack/legacy/plugins/uptime/public/state/selectors/__tests__/index.test.ts index b61ed83663435..38fb3edea4768 100644 --- a/x-pack/legacy/plugins/uptime/public/state/selectors/__tests__/index.test.ts +++ b/x-pack/legacy/plugins/uptime/public/state/selectors/__tests__/index.test.ts @@ -9,6 +9,16 @@ import { AppState } from '../../../state'; describe('state selectors', () => { const state: AppState = { + overviewFilters: { + filters: { + locations: [], + ports: [], + schemes: [], + tags: [], + }, + errors: [], + loading: false, + }, monitor: { monitorDetailsList: [], monitorLocationsList: new Map(), @@ -19,13 +29,22 @@ describe('state selectors', () => { count: { up: 2, down: 0, - mixed: 0, total: 2, }, errors: [], loading: false, }, - ui: { basePath: 'yyz', integrationsPopoverOpen: null, lastRefresh: 125 }, + ui: { + basePath: 'yyz', + esKuery: '', + integrationsPopoverOpen: null, + lastRefresh: 125, + }, + monitorStatus: { + status: null, + monitor: null, + loading: false, + }, }; it('selects base path from state', () => { diff --git a/x-pack/legacy/plugins/uptime/public/state/selectors/index.ts b/x-pack/legacy/plugins/uptime/public/state/selectors/index.ts index 1792c84c45220..337e99f6ede16 100644 --- a/x-pack/legacy/plugins/uptime/public/state/selectors/index.ts +++ b/x-pack/legacy/plugins/uptime/public/state/selectors/index.ts @@ -6,15 +6,25 @@ import { AppState } from '../../state'; +// UI Selectors export const getBasePath = ({ ui: { basePath } }: AppState) => basePath; export const isIntegrationsPopupOpen = ({ ui: { integrationsPopoverOpen } }: AppState) => integrationsPopoverOpen; +// Monitor Selectors export const getMonitorDetails = (state: AppState, summary: any) => { return state.monitor.monitorDetailsList[summary.monitor_id]; }; -export const getMonitorLocations = (state: AppState, monitorId: string) => { +export const selectMonitorLocations = (state: AppState, monitorId: string) => { return state.monitor.monitorLocationsList?.get(monitorId); }; + +export const selectSelectedMonitor = (state: AppState) => { + return state.monitorStatus.monitor; +}; + +export const selectMonitorStatus = (state: AppState) => { + return state.monitorStatus.status; +}; diff --git a/x-pack/legacy/plugins/uptime/public/uptime_app.tsx b/x-pack/legacy/plugins/uptime/public/uptime_app.tsx index f72055c52255d..25ff0e7177016 100644 --- a/x-pack/legacy/plugins/uptime/public/uptime_app.tsx +++ b/x-pack/legacy/plugins/uptime/public/uptime_app.tsx @@ -5,24 +5,25 @@ */ import DateMath from '@elastic/datemath'; -import { EuiFlexGroup, EuiFlexItem, EuiPage, EuiSpacer, EuiTitle } from '@elastic/eui'; +import { EuiPage } from '@elastic/eui'; import euiDarkVars from '@elastic/eui/dist/eui_theme_dark.json'; import euiLightVars from '@elastic/eui/dist/eui_theme_light.json'; import { i18n } from '@kbn/i18n'; import React, { useEffect, useState } from 'react'; import { ApolloProvider } from 'react-apollo'; import { Provider as ReduxProvider } from 'react-redux'; -import { BrowserRouter as Router, Route, RouteComponentProps, Switch } from 'react-router-dom'; -import { I18nStart, ChromeBreadcrumb } from 'src/core/public'; -import { AutocompleteProviderRegister } from 'src/plugins/data/public'; +import { BrowserRouter as Router, Route, RouteComponentProps } from 'react-router-dom'; +import { I18nStart, ChromeBreadcrumb, LegacyCoreStart } from 'src/core/public'; +import { PluginsStart } from 'ui/new_platform/new_platform'; +import { KibanaContextProvider } from '../../../../../src/plugins/kibana_react/public'; import { UMGraphQLClient, UMUpdateBreadcrumbs, UMUpdateBadge } from './lib/lib'; -import { MonitorPage, OverviewPage, NotFoundPage } from './pages'; import { UptimeRefreshContext, UptimeSettingsContext, UMSettingsContextValues } from './contexts'; -import { UptimeDatePicker, CommonlyUsedRange } from './components/functional/uptime_date_picker'; +import { CommonlyUsedRange } from './components/functional/uptime_date_picker'; import { useUrlParams } from './hooks'; import { getTitle } from './lib/helper/get_title'; import { store } from './state'; import { setBasePath, triggerAppRefresh } from './state/actions'; +import { PageRouter } from './routes'; export interface UptimeAppColors { danger: string; @@ -37,15 +38,14 @@ export interface UptimeAppProps { basePath: string; canSave: boolean; client: UMGraphQLClient; + core: LegacyCoreStart; darkMode: boolean; - autocomplete: Pick; i18n: I18nStart; isApmAvailable: boolean; isInfraAvailable: boolean; isLogsAvailable: boolean; kibanaBreadcrumbs: ChromeBreadcrumb[]; - logMonitorPageLoad: () => void; - logOverviewPageLoad: () => void; + plugins: PluginsStart; routerBasename: string; setBreadcrumbs: UMUpdateBreadcrumbs; setBadge: UMUpdateBadge; @@ -55,18 +55,17 @@ export interface UptimeAppProps { const Application = (props: UptimeAppProps) => { const { - autocomplete, basePath, canSave, client, + core, darkMode, commonlyUsedRanges, i18n: i18nCore, isApmAvailable, isInfraAvailable, isLogsAvailable, - logMonitorPageLoad, - logOverviewPageLoad, + plugins, renderGlobalHelpControls, routerBasename, setBreadcrumbs, @@ -94,7 +93,6 @@ const Application = (props: UptimeAppProps) => { }; } const [lastRefresh, setLastRefresh] = useState(Date.now()); - const [headingText, setHeadingText] = useState(undefined); useEffect(() => { renderGlobalHelpControls(); @@ -147,7 +145,7 @@ const Application = (props: UptimeAppProps) => { isInfraAvailable, isLogsAvailable, refreshApp, - setHeadingText, + commonlyUsedRanges, }; }; @@ -156,70 +154,32 @@ const Application = (props: UptimeAppProps) => { return ( - - { - return ( - - - - -
- - - -

{headingText}

-
-
- - - -
- - - ( - - )} + + + { + return ( + + + + +
+ - ( - - )} - /> - - -
-
-
-
-
- ); - }} - /> -
+
+
+
+
+
+ ); + }} + /> +
+
); diff --git a/x-pack/legacy/plugins/uptime/server/graphql/monitors/resolvers.ts b/x-pack/legacy/plugins/uptime/server/graphql/monitors/resolvers.ts index 8b685d8e08a2b..897d67dde807e 100644 --- a/x-pack/legacy/plugins/uptime/server/graphql/monitors/resolvers.ts +++ b/x-pack/legacy/plugins/uptime/server/graphql/monitors/resolvers.ts @@ -7,14 +7,9 @@ import { UMGqlRange } from '../../../common/domain_types'; import { UMResolver } from '../../../common/graphql/resolver_types'; import { - FilterBar, GetFilterBarQueryArgs, - GetLatestMonitorsQueryArgs, GetMonitorChartsDataQueryArgs, - GetMonitorPageTitleQueryArgs, MonitorChart, - MonitorPageTitle, - Ping, GetSnapshotHistogramQueryArgs, } from '../../../common/graphql/types'; import { UMServerLibs } from '../../lib/lib'; @@ -23,13 +18,6 @@ import { HistogramResult } from '../../../common/domain_types'; export type UMMonitorsResolver = UMResolver, any, UMGqlRange, UMContext>; -export type UMLatestMonitorsResolver = UMResolver< - Ping[] | Promise, - any, - GetLatestMonitorsQueryArgs, - UMContext ->; - export type UMGetMonitorChartsResolver = UMResolver< any | Promise, any, @@ -44,13 +32,6 @@ export type UMGetFilterBarResolver = UMResolver< UMContext >; -export type UMGetMontiorPageTitleResolver = UMResolver< - MonitorPageTitle | Promise | null, - any, - GetMonitorPageTitleQueryArgs, - UMContext ->; - export type UMGetSnapshotHistogram = UMResolver< HistogramResult | Promise, any, @@ -64,9 +45,6 @@ export const createMonitorsResolvers: CreateUMGraphQLResolvers = ( Query: { getSnapshotHistogram: UMGetSnapshotHistogram; getMonitorChartsData: UMGetMonitorChartsResolver; - getLatestMonitors: UMLatestMonitorsResolver; - getFilterBar: UMGetFilterBarResolver; - getMonitorPageTitle: UMGetMontiorPageTitleResolver; }; } => ({ Query: { @@ -97,36 +75,5 @@ export const createMonitorsResolvers: CreateUMGraphQLResolvers = ( location, }); }, - async getLatestMonitors( - _resolver, - { dateRangeStart, dateRangeEnd, monitorId, location }, - { APICaller } - ): Promise { - return await libs.pings.getLatestMonitorDocs({ - callES: APICaller, - dateRangeStart, - dateRangeEnd, - monitorId, - location, - }); - }, - async getFilterBar( - _resolver, - { dateRangeStart, dateRangeEnd }, - { APICaller } - ): Promise { - return await libs.monitors.getFilterBar({ - callES: APICaller, - dateRangeStart, - dateRangeEnd, - }); - }, - async getMonitorPageTitle( - _resolver: any, - { monitorId }, - { APICaller } - ): Promise { - return await libs.monitors.getMonitorPageTitle({ callES: APICaller, monitorId }); - }, }, }); diff --git a/x-pack/legacy/plugins/uptime/server/graphql/monitors/schema.gql.ts b/x-pack/legacy/plugins/uptime/server/graphql/monitors/schema.gql.ts index f9b14c63e70bb..8a86d97b4cd8e 100644 --- a/x-pack/legacy/plugins/uptime/server/graphql/monitors/schema.gql.ts +++ b/x-pack/legacy/plugins/uptime/server/graphql/monitors/schema.gql.ts @@ -7,22 +7,6 @@ import gql from 'graphql-tag'; export const monitorsSchema = gql` - "The data used to enrich the filter bar." - type FilterBar { - "A series of monitor IDs in the heartbeat indices." - ids: [String!] - "The location values users have configured for the agents." - locations: [String!] - "The ports of the monitored endpoints." - ports: [Int!] - "The schemes used by the monitors." - schemes: [String!] - "The possible status values contained in the indices." - statuses: [String!] - "The list of URLs" - urls: [String!] - } - type HistogramDataPoint { upCount: Int downCount: Int @@ -114,12 +98,6 @@ export const monitorsSchema = gql` interval: UnsignedInteger! } - type MonitorPageTitle { - id: String! - url: String - name: String - } - extend type Query { getMonitors( dateRangeStart: String! @@ -142,21 +120,5 @@ export const monitorsSchema = gql` dateRangeEnd: String! location: String ): MonitorChart - - "Fetch the most recent event data for a monitor ID, date range, location." - getLatestMonitors( - "The lower limit of the date range." - dateRangeStart: String! - "The upper limit of the date range." - dateRangeEnd: String! - "Optional: a specific monitor ID filter." - monitorId: String - "Optional: a specific instance location filter." - location: String - ): [Ping!]! - - getFilterBar(dateRangeStart: String!, dateRangeEnd: String!): FilterBar - - getMonitorPageTitle(monitorId: String!): MonitorPageTitle } `; diff --git a/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/__tests__/__snapshots__/get_snapshot_helper.test.ts.snap b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/__tests__/__snapshots__/get_snapshot_helper.test.ts.snap deleted file mode 100644 index 29c82ff455d36..0000000000000 --- a/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/__tests__/__snapshots__/get_snapshot_helper.test.ts.snap +++ /dev/null @@ -1,10 +0,0 @@ -// Jest Snapshot v1, https://goo.gl/fbAQLP - -exports[`get snapshot helper reduces check groups as expected 1`] = ` -Object { - "down": 1, - "mixed": 0, - "total": 3, - "up": 2, -} -`; diff --git a/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/__tests__/get_snapshot_helper.test.ts b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/__tests__/get_snapshot_helper.test.ts deleted file mode 100644 index 917e4a149de67..0000000000000 --- a/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/__tests__/get_snapshot_helper.test.ts +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { getSnapshotCountHelper } from '../get_snapshot_helper'; -import { MonitorGroups } from '../search'; - -describe('get snapshot helper', () => { - let mockIterator: any; - beforeAll(() => { - mockIterator = jest.fn(); - const summaryTimestamp = new Date('2019-01-01'); - const firstResult: MonitorGroups = { - id: 'firstGroup', - groups: [ - { - monitorId: 'first-monitor', - location: 'us-east-1', - checkGroup: 'abc', - status: 'down', - summaryTimestamp, - }, - { - monitorId: 'first-monitor', - location: 'us-west-1', - checkGroup: 'abc', - status: 'up', - summaryTimestamp, - }, - { - monitorId: 'first-monitor', - location: 'amsterdam', - checkGroup: 'abc', - status: 'down', - summaryTimestamp, - }, - ], - }; - const secondResult: MonitorGroups = { - id: 'secondGroup', - groups: [ - { - monitorId: 'second-monitor', - location: 'us-east-1', - checkGroup: 'yyz', - status: 'up', - summaryTimestamp, - }, - { - monitorId: 'second-monitor', - location: 'us-west-1', - checkGroup: 'yyz', - status: 'up', - summaryTimestamp, - }, - { - monitorId: 'second-monitor', - location: 'amsterdam', - checkGroup: 'yyz', - status: 'up', - summaryTimestamp, - }, - ], - }; - const thirdResult: MonitorGroups = { - id: 'thirdGroup', - groups: [ - { - monitorId: 'third-monitor', - location: 'us-east-1', - checkGroup: 'dt', - status: 'up', - summaryTimestamp, - }, - { - monitorId: 'third-monitor', - location: 'us-west-1', - checkGroup: 'dt', - status: 'up', - summaryTimestamp, - }, - { - monitorId: 'third-monitor', - location: 'amsterdam', - checkGroup: 'dt', - status: 'up', - summaryTimestamp, - }, - ], - }; - - const mockNext = jest - .fn() - .mockReturnValueOnce(firstResult) - .mockReturnValueOnce(secondResult) - .mockReturnValueOnce(thirdResult) - .mockReturnValueOnce(null); - mockIterator.next = mockNext; - }); - - it('reduces check groups as expected', async () => { - expect(await getSnapshotCountHelper(mockIterator)).toMatchSnapshot(); - }); -}); diff --git a/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/elasticsearch_monitor_states_adapter.ts b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/elasticsearch_monitor_states_adapter.ts index d264da2e7ec0c..eaaa8087e57cd 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/elasticsearch_monitor_states_adapter.ts +++ b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/elasticsearch_monitor_states_adapter.ts @@ -4,22 +4,12 @@ * you may not use this file except in compliance with the Elastic License. */ -import { UMMonitorStatesAdapter, CursorPagination } from './adapter_types'; +import { UMMonitorStatesAdapter } from './adapter_types'; import { INDEX_NAMES, CONTEXT_DEFAULTS } from '../../../../common/constants'; import { fetchPage } from './search'; import { MonitorGroupIterator } from './search/monitor_group_iterator'; -import { getSnapshotCountHelper } from './get_snapshot_helper'; - -export interface QueryContext { - count: (query: Record) => Promise; - search: (query: Record) => Promise; - dateRangeStart: string; - dateRangeEnd: string; - pagination: CursorPagination; - filterClause: any | null; - size: number; - statusFilter?: string; -} +import { Snapshot } from '../../../../common/runtime_types'; +import { QueryContext } from './search/query_context'; export const elasticsearchMonitorStatesAdapter: UMMonitorStatesAdapter = { // Gets a page of monitor states. @@ -35,16 +25,15 @@ export const elasticsearchMonitorStatesAdapter: UMMonitorStatesAdapter = { statusFilter = statusFilter === null ? undefined : statusFilter; const size = 10; - const queryContext: QueryContext = { - count: (query: Record): Promise => callES('count', query), - search: (query: Record): Promise => callES('search', query), + const queryContext = new QueryContext( + callES, dateRangeStart, dateRangeEnd, pagination, - filterClause: filters && filters !== '' ? JSON.parse(filters) : null, + filters && filters !== '' ? JSON.parse(filters) : null, size, - statusFilter, - }; + statusFilter + ); const page = await fetchPage(queryContext); @@ -55,18 +44,46 @@ export const elasticsearchMonitorStatesAdapter: UMMonitorStatesAdapter = { }; }, - getSnapshotCount: async ({ callES, dateRangeStart, dateRangeEnd, filters, statusFilter }) => { - const context: QueryContext = { - count: query => callES('count', query), - search: query => callES('search', query), + getSnapshotCount: async ({ + callES, + dateRangeStart, + dateRangeEnd, + filters, + statusFilter, + }): Promise => { + if (!(statusFilter === 'up' || statusFilter === 'down' || statusFilter === undefined)) { + throw new Error(`Invalid status filter value '${statusFilter}'`); + } + + const context = new QueryContext( + callES, dateRangeStart, dateRangeEnd, - pagination: CONTEXT_DEFAULTS.CURSOR_PAGINATION, - filterClause: filters && filters !== '' ? JSON.parse(filters) : null, - size: CONTEXT_DEFAULTS.MAX_MONITORS_FOR_SNAPSHOT_COUNT, - statusFilter, + CONTEXT_DEFAULTS.CURSOR_PAGINATION, + filters && filters !== '' ? JSON.parse(filters) : null, + CONTEXT_DEFAULTS.MAX_MONITORS_FOR_SNAPSHOT_COUNT, + statusFilter + ); + + // Calculate the total, up, and down counts. + const counts = await fastStatusCount(context); + + // Check if the last count was accurate, if not, we need to perform a slower count with the + // MonitorGroupsIterator. + if (!(await context.hasTimespan())) { + // Figure out whether 'up' or 'down' is more common. It's faster to count the lower cardinality + // one then use subtraction to figure out its opposite. + const [leastCommonStatus, mostCommonStatus]: Array<'up' | 'down'> = + counts.up > counts.down ? ['down', 'up'] : ['up', 'down']; + counts[leastCommonStatus] = await slowStatusCount(context, leastCommonStatus); + counts[mostCommonStatus] = counts.total - counts[leastCommonStatus]; + } + + return { + total: statusFilter ? counts[statusFilter] : counts.total, + up: statusFilter === 'down' ? 0 : counts.up, + down: statusFilter === 'up' ? 0 : counts.down, }; - return getSnapshotCountHelper(new MonitorGroupIterator(context)); }, statesIndexExists: async ({ callES }) => { @@ -92,3 +109,46 @@ const jsonifyPagination = (p: any): string | null => { return JSON.stringify(p); }; + +const fastStatusCount = async (context: QueryContext): Promise => { + const params = { + index: INDEX_NAMES.HEARTBEAT, + body: { + size: 0, + query: { bool: { filter: await context.dateAndCustomFilters() } }, + aggs: { + unique: { + // We set the precision threshold to 40k which is the max precision supported by cardinality + cardinality: { field: 'monitor.id', precision_threshold: 40000 }, + }, + down: { + filter: { range: { 'summary.down': { gt: 0 } } }, + aggs: { + unique: { cardinality: { field: 'monitor.id', precision_threshold: 40000 } }, + }, + }, + }, + }, + }; + + const statistics = await context.search(params); + const total = statistics.aggregations.unique.value; + const down = statistics.aggregations.down.unique.value; + + return { + total, + down, + up: total - down, + }; +}; + +const slowStatusCount = async (context: QueryContext, status: string): Promise => { + const downContext = context.clone(); + downContext.statusFilter = status; + const iterator = new MonitorGroupIterator(downContext); + let count = 0; + while (await iterator.next()) { + count++; + } + return count; +}; diff --git a/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/get_snapshot_helper.ts b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/get_snapshot_helper.ts deleted file mode 100644 index 8bd21b77406df..0000000000000 --- a/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/get_snapshot_helper.ts +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { MonitorGroups, MonitorGroupIterator } from './search'; -import { Snapshot } from '../../../../common/runtime_types'; - -const reduceItemsToCounts = (items: MonitorGroups[]) => { - let down = 0; - let up = 0; - items.forEach(item => { - if (item.groups.some(group => group.status === 'down')) { - down++; - } else { - up++; - } - }); - return { - down, - mixed: 0, - total: down + up, - up, - }; -}; - -export const getSnapshotCountHelper = async (iterator: MonitorGroupIterator): Promise => { - const items: MonitorGroups[] = []; - let res: MonitorGroups | null; - // query the index to find the most recent check group for each monitor/location - do { - res = await iterator.next(); - if (res) { - items.push(res); - } - } while (res !== null); - - return reduceItemsToCounts(items); -}; diff --git a/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/search/__tests__/fetch_page.test.ts b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/search/__tests__/fetch_page.test.ts index d571a5a902539..0bbdaa87a5e66 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/search/__tests__/fetch_page.test.ts +++ b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/search/__tests__/fetch_page.test.ts @@ -11,7 +11,7 @@ import { MonitorGroupsFetcher, MonitorGroupsPage, } from '../fetch_page'; -import { QueryContext } from '../../elasticsearch_monitor_states_adapter'; +import { QueryContext } from '../query_context'; import { MonitorSummary } from '../../../../../../common/graphql/types'; import { nextPagination, prevPagination, simpleQueryContext } from './test_helpers'; diff --git a/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/search/__tests__/monitor_group_iterator.test.ts b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/search/__tests__/monitor_group_iterator.test.ts index b8df3b635dc6b..0ce5e75195475 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/search/__tests__/monitor_group_iterator.test.ts +++ b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/search/__tests__/monitor_group_iterator.test.ts @@ -11,8 +11,8 @@ import { MonitorGroupIterator, } from '../monitor_group_iterator'; import { simpleQueryContext } from './test_helpers'; -import { QueryContext } from '../../elasticsearch_monitor_states_adapter'; import { MonitorGroups } from '../fetch_page'; +import { QueryContext } from '../query_context'; describe('iteration', () => { let iterator: MonitorGroupIterator | null = null; diff --git a/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/search/__tests__/test_helpers.ts b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/search/__tests__/test_helpers.ts index d6fe5f82e735d..bb3f3da3e289d 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/search/__tests__/test_helpers.ts +++ b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/search/__tests__/test_helpers.ts @@ -6,7 +6,7 @@ import { CursorPagination } from '../../adapter_types'; import { CursorDirection, SortOrder } from '../../../../../../common/graphql/types'; -import { QueryContext } from '../../elasticsearch_monitor_states_adapter'; +import { QueryContext } from '../query_context'; export const prevPagination = (key: any): CursorPagination => { return { @@ -23,14 +23,5 @@ export const nextPagination = (key: any): CursorPagination => { }; }; export const simpleQueryContext = (): QueryContext => { - return { - count: _query => new Promise(r => ({})), - search: _query => new Promise(r => ({})), - dateRangeEnd: '', - dateRangeStart: '', - filterClause: undefined, - pagination: nextPagination('something'), - size: 0, - statusFilter: '', - }; + return new QueryContext(undefined, '', '', nextPagination('something'), undefined, 0, ''); }; diff --git a/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/search/enrich_monitor_groups.ts b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/search/enrich_monitor_groups.ts index 093e105635c2c..b64015424ff40 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/search/enrich_monitor_groups.ts +++ b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/search/enrich_monitor_groups.ts @@ -5,7 +5,7 @@ */ import { get, sortBy } from 'lodash'; -import { QueryContext } from '../elasticsearch_monitor_states_adapter'; +import { QueryContext } from './query_context'; import { getHistogramIntervalFormatted } from '../../../helper'; import { INDEX_NAMES, STATES } from '../../../../../common/constants'; import { @@ -77,19 +77,19 @@ export const enrichMonitorGroups: MonitorEnricher = async ( } String agentIdIP = agentId + "-" + (ip == null ? "" : ip.toString()); def ts = doc["@timestamp"][0].toInstant().toEpochMilli(); - + def lastCheck = state.checksByAgentIdIP[agentId]; Instant lastTs = lastCheck != null ? lastCheck["@timestamp"] : null; if (lastTs != null && lastTs > ts) { return; } - + curCheck.put("@timestamp", ts); - + Map agent = new HashMap(); agent.id = agentId; curCheck.put("agent", agent); - + if (state.globals.url == null) { Map url = new HashMap(); Collection fields = ["full", "original", "scheme", "username", "password", "domain", "port", "path", "query", "fragment"]; @@ -102,7 +102,7 @@ export const enrichMonitorGroups: MonitorEnricher = async ( } state.globals.url = url; } - + Map monitor = new HashMap(); monitor.status = doc["monitor.status"][0]; monitor.ip = ip; @@ -113,7 +113,7 @@ export const enrichMonitorGroups: MonitorEnricher = async ( } } curCheck.monitor = monitor; - + if (curCheck.observer == null) { curCheck.observer = new HashMap(); } @@ -144,14 +144,14 @@ export const enrichMonitorGroups: MonitorEnricher = async ( if (!doc["tls.certificate_not_valid_before"].isEmpty()) { curCheck.tls.certificate_not_valid_before = doc["tls.certificate_not_valid_before"][0]; } - + state.checksByAgentIdIP[agentIdIP] = curCheck; `, combine_script: 'return state;', reduce_script: ` // The final document Map result = new HashMap(); - + Map checks = new HashMap(); Instant maxTs = Instant.ofEpochMilli(0); Collection ips = new HashSet(); @@ -159,7 +159,7 @@ export const enrichMonitorGroups: MonitorEnricher = async ( Collection podUids = new HashSet(); Collection containerIds = new HashSet(); Collection tls = new HashSet(); - String name = null; + String name = null; for (state in states) { result.putAll(state.globals); for (entry in state.checksByAgentIdIP.entrySet()) { @@ -167,18 +167,18 @@ export const enrichMonitorGroups: MonitorEnricher = async ( def check = entry.getValue(); def lastBestCheck = checks.get(agentIdIP); def checkTs = Instant.ofEpochMilli(check.get("@timestamp")); - + if (maxTs.isBefore(checkTs)) { maxTs = checkTs} - + if (lastBestCheck == null || lastBestCheck.get("@timestamp") < checkTs) { check["@timestamp"] = check["@timestamp"]; checks[agentIdIP] = check } - + if (check.monitor.name != null && check.monitor.name != "") { name = check.monitor.name; } - + ips.add(check.monitor.ip); if (check.observer != null && check.observer.geo != null && check.observer.geo.name != null) { geoNames.add(check.observer.geo.name); @@ -194,45 +194,45 @@ export const enrichMonitorGroups: MonitorEnricher = async ( } } } - + // We just use the values so we can store these as nested docs result.checks = checks.values(); result.put("@timestamp", maxTs); - - + + Map summary = new HashMap(); summary.up = checks.entrySet().stream().filter(c -> c.getValue().monitor.status == "up").count(); summary.down = checks.size() - summary.up; result.summary = summary; - + Map monitor = new HashMap(); monitor.ip = ips; monitor.name = name; - monitor.status = summary.down > 0 ? (summary.up > 0 ? "mixed": "down") : "up"; + monitor.status = summary.down > 0 ? "down" : "up"; result.monitor = monitor; - + Map observer = new HashMap(); Map geo = new HashMap(); observer.geo = geo; geo.name = geoNames; result.observer = observer; - + if (!podUids.isEmpty()) { result.kubernetes = new HashMap(); result.kubernetes.pod = new HashMap(); result.kubernetes.pod.uid = podUids; } - + if (!containerIds.isEmpty()) { result.container = new HashMap(); result.container.id = containerIds; } - + if (!tls.isEmpty()) { result.tls = new HashMap(); result.tls = tls; } - + return result; `, }, diff --git a/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/search/fetch_chunk.ts b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/search/fetch_chunk.ts index e395df0d1d08d..77676ac9a6373 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/search/fetch_chunk.ts +++ b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/search/fetch_chunk.ts @@ -6,8 +6,8 @@ import { refinePotentialMatches } from './refine_potential_matches'; import { findPotentialMatches } from './find_potential_matches'; -import { QueryContext } from '../elasticsearch_monitor_states_adapter'; import { ChunkFetcher, ChunkResult } from './monitor_group_iterator'; +import { QueryContext } from './query_context'; /** * Fetches a single 'chunk' of data with a single query, then uses a secondary query to filter out erroneous matches. diff --git a/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/search/fetch_page.ts b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/search/fetch_page.ts index 085c11f78b8f5..046bdc8a8d07d 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/search/fetch_page.ts +++ b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/search/fetch_page.ts @@ -6,7 +6,7 @@ import { flatten } from 'lodash'; import { CursorPagination } from '../adapter_types'; -import { QueryContext } from '../elasticsearch_monitor_states_adapter'; +import { QueryContext } from './query_context'; import { QUERY } from '../../../../../common/constants'; import { CursorDirection, MonitorSummary, SortOrder } from '../../../../../common/graphql/types'; import { enrichMonitorGroups } from './enrich_monitor_groups'; @@ -51,6 +51,7 @@ const fetchPageMonitorGroups: MonitorGroupsFetcher = async ( size: number ): Promise => { const monitorGroups: MonitorGroups[] = []; + const iterator = new MonitorGroupIterator(queryContext); let paginationBefore: CursorPagination | null = null; diff --git a/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/search/find_potential_matches.ts b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/search/find_potential_matches.ts index 8f5e26b75f56c..e34bc6ab805c0 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/search/find_potential_matches.ts +++ b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/search/find_potential_matches.ts @@ -5,10 +5,9 @@ */ import { get, set } from 'lodash'; -import { QueryContext } from '../elasticsearch_monitor_states_adapter'; import { CursorDirection } from '../../../../../common/graphql/types'; import { INDEX_NAMES } from '../../../../../common/constants'; -import { makeDateRangeFilter } from '../../../helper/make_date_rate_filter'; +import { QueryContext } from './query_context'; // This is the first phase of the query. In it, we find the most recent check groups that matched the given query. // Note that these check groups may not be the most recent groups for the matching monitor ID! We'll filter those @@ -55,7 +54,7 @@ export const findPotentialMatches = async ( }; const query = async (queryContext: QueryContext, searchAfter: any, size: number) => { - const body = queryBody(queryContext, searchAfter, size); + const body = await queryBody(queryContext, searchAfter, size); const params = { index: INDEX_NAMES.HEARTBEAT, @@ -65,15 +64,11 @@ const query = async (queryContext: QueryContext, searchAfter: any, size: number) return await queryContext.search(params); }; -const queryBody = (queryContext: QueryContext, searchAfter: any, size: number) => { +const queryBody = async (queryContext: QueryContext, searchAfter: any, size: number) => { const compositeOrder = cursorDirectionToOrder(queryContext.pagination.cursorDirection); - const filters: any[] = [ - makeDateRangeFilter(queryContext.dateRangeStart, queryContext.dateRangeEnd), - ]; - if (queryContext.filterClause) { - filters.push(queryContext.filterClause); - } + const filters = await queryContext.dateAndCustomFilters(); + if (queryContext.statusFilter) { filters.push({ match: { 'monitor.status': queryContext.statusFilter } }); } @@ -82,6 +77,11 @@ const queryBody = (queryContext: QueryContext, searchAfter: any, size: number) = size: 0, query: { bool: { filter: filters } }, aggs: { + has_timespan: { + filter: { + exists: { field: 'monitor.timespan' }, + }, + }, monitors: { composite: { size, diff --git a/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/search/monitor_group_iterator.ts b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/search/monitor_group_iterator.ts index 1de2dbb0e364d..27c16863a37ab 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/search/monitor_group_iterator.ts +++ b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/search/monitor_group_iterator.ts @@ -4,7 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { QueryContext } from '../elasticsearch_monitor_states_adapter'; +import { QueryContext } from './query_context'; import { CursorPagination } from '../adapter_types'; import { fetchChunk } from './fetch_chunk'; import { CursorDirection } from '../../../../../common/graphql/types'; @@ -155,7 +155,7 @@ export class MonitorGroupIterator { // Returns a copy of this fetcher that goes backwards from the current position reverse(): MonitorGroupIterator | null { - const reverseContext = Object.assign({}, this.queryContext); + const reverseContext = this.queryContext.clone(); const current = this.getCurrent(); reverseContext.pagination = { diff --git a/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/search/query_context.ts b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/search/query_context.ts new file mode 100644 index 0000000000000..03e228952f0e7 --- /dev/null +++ b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/search/query_context.ts @@ -0,0 +1,135 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import DateMath from '@elastic/datemath'; +import { APICaller } from 'kibana/server'; +import { CursorPagination } from '../adapter_types'; +import { INDEX_NAMES } from '../../../../../common/constants'; + +export class QueryContext { + callES: APICaller; + dateRangeStart: string; + dateRangeEnd: string; + pagination: CursorPagination; + filterClause: any | null; + size: number; + statusFilter?: string; + hasTimespanCache?: boolean; + + constructor( + database: any, + dateRangeStart: string, + dateRangeEnd: string, + pagination: CursorPagination, + filterClause: any | null, + size: number, + statusFilter?: string + ) { + this.callES = database; + this.dateRangeStart = dateRangeStart; + this.dateRangeEnd = dateRangeEnd; + this.pagination = pagination; + this.filterClause = filterClause; + this.size = size; + this.statusFilter = statusFilter; + } + + async search(params: any): Promise { + params.index = INDEX_NAMES.HEARTBEAT; + return this.callES('search', params); + } + + async count(params: any): Promise { + params.index = INDEX_NAMES.HEARTBEAT; + return this.callES('count', params); + } + + async dateAndCustomFilters(): Promise { + const clauses = [await this.dateRangeFilter()]; + if (this.filterClause) { + clauses.push(this.filterClause); + } + return clauses; + } + + async dateRangeFilter(forceNoTimespan?: boolean): Promise { + const timestampClause = { + range: { '@timestamp': { gte: this.dateRangeStart, lte: this.dateRangeEnd } }, + }; + + if (forceNoTimespan === true || !(await this.hasTimespan())) { + return timestampClause; + } + + // @ts-ignore + const tsStart = DateMath.parse(this.dateRangeEnd).subtract(10, 'seconds'); + const tsEnd = DateMath.parse(this.dateRangeEnd)!; + + return { + bool: { + filter: [ + timestampClause, + { + bool: { + should: [ + { + range: { + 'monitor.timespan': { + gte: tsStart.toISOString(), + lte: tsEnd.toISOString(), + }, + }, + }, + { + bool: { + must_not: { exists: { field: 'monitor.timespan' } }, + }, + }, + ], + }, + }, + ], + }, + }; + } + + async hasTimespan(): Promise { + if (this.hasTimespanCache) { + return this.hasTimespanCache; + } + + this.hasTimespanCache = + ( + await this.count({ + body: { + query: { + bool: { + filter: [ + await this.dateRangeFilter(true), + { exists: { field: 'monitor.timespan' } }, + ], + }, + }, + }, + terminate_after: 1, + }) + ).count > 0; + + return this.hasTimespanCache; + } + + clone(): QueryContext { + return new QueryContext( + this.callES, + this.dateRangeStart, + this.dateRangeEnd, + this.pagination, + this.filterClause, + this.size, + this.statusFilter + ); + } +} diff --git a/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/search/refine_potential_matches.ts b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/search/refine_potential_matches.ts index b0060cbee17bb..f8347d0737521 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/search/refine_potential_matches.ts +++ b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitor_states/search/refine_potential_matches.ts @@ -5,10 +5,9 @@ */ import { INDEX_NAMES } from '../../../../../common/constants'; -import { QueryContext } from '../elasticsearch_monitor_states_adapter'; +import { QueryContext } from './query_context'; import { CursorDirection } from '../../../../../common/graphql/types'; import { MonitorGroups, MonitorLocCheckGroup } from './fetch_page'; -import { makeDateRangeFilter } from '../../../helper/make_date_rate_filter'; /** * Determines whether the provided check groups are the latest complete check groups for their associated monitor ID's. @@ -103,7 +102,7 @@ export const mostRecentCheckGroups = async ( query: { bool: { filter: [ - makeDateRangeFilter(queryContext.dateRangeStart, queryContext.dateRangeEnd), + await queryContext.dateRangeFilter(), { terms: { 'monitor.id': potentialMatchMonitorIDs } }, // only match summary docs because we only want the latest *complete* check group. { exists: { field: 'summary' } }, diff --git a/x-pack/legacy/plugins/uptime/server/lib/adapters/monitors/__tests__/__snapshots__/extract_filter_aggs_results.test.ts.snap b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitors/__tests__/__snapshots__/extract_filter_aggs_results.test.ts.snap new file mode 100644 index 0000000000000..2f6d6e06f93e1 --- /dev/null +++ b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitors/__tests__/__snapshots__/extract_filter_aggs_results.test.ts.snap @@ -0,0 +1,27 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`extractFilterAggsResults extracts the bucket values of the expected filter fields 1`] = ` +Object { + "locations": Array [ + "us-east-2", + "fairbanks", + ], + "ports": Array [ + 12349, + 80, + 5601, + 8200, + 9200, + 9292, + ], + "schemes": Array [ + "http", + "tcp", + "icmp", + ], + "tags": Array [ + "api", + "dev", + ], +} +`; diff --git a/x-pack/legacy/plugins/uptime/server/lib/adapters/monitors/__tests__/__snapshots__/generate_filter_aggs.test.ts.snap b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitors/__tests__/__snapshots__/generate_filter_aggs.test.ts.snap new file mode 100644 index 0000000000000..0f7abf5050bca --- /dev/null +++ b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitors/__tests__/__snapshots__/generate_filter_aggs.test.ts.snap @@ -0,0 +1,171 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`generateFilterAggs generates expected aggregations object 1`] = ` +Object { + "locations": Object { + "aggs": Object { + "term": Object { + "terms": Object { + "field": "observer.geo.name", + }, + }, + }, + "filter": Object { + "bool": Object { + "should": Array [ + Object { + "term": Object { + "url.port": "80", + }, + }, + Object { + "term": Object { + "url.port": "5601", + }, + }, + Object { + "term": Object { + "tags": "api", + }, + }, + Object { + "term": Object { + "monitor.type": "http", + }, + }, + Object { + "term": Object { + "monitor.type": "tcp", + }, + }, + ], + }, + }, + }, + "ports": Object { + "aggs": Object { + "term": Object { + "terms": Object { + "field": "url.port", + }, + }, + }, + "filter": Object { + "bool": Object { + "should": Array [ + Object { + "term": Object { + "observer.geo.name": "fairbanks", + }, + }, + Object { + "term": Object { + "observer.geo.name": "us-east-2", + }, + }, + Object { + "term": Object { + "tags": "api", + }, + }, + Object { + "term": Object { + "monitor.type": "http", + }, + }, + Object { + "term": Object { + "monitor.type": "tcp", + }, + }, + ], + }, + }, + }, + "schemes": Object { + "aggs": Object { + "term": Object { + "terms": Object { + "field": "monitor.type", + }, + }, + }, + "filter": Object { + "bool": Object { + "should": Array [ + Object { + "term": Object { + "observer.geo.name": "fairbanks", + }, + }, + Object { + "term": Object { + "observer.geo.name": "us-east-2", + }, + }, + Object { + "term": Object { + "url.port": "80", + }, + }, + Object { + "term": Object { + "url.port": "5601", + }, + }, + Object { + "term": Object { + "tags": "api", + }, + }, + ], + }, + }, + }, + "tags": Object { + "aggs": Object { + "term": Object { + "terms": Object { + "field": "tags", + }, + }, + }, + "filter": Object { + "bool": Object { + "should": Array [ + Object { + "term": Object { + "observer.geo.name": "fairbanks", + }, + }, + Object { + "term": Object { + "observer.geo.name": "us-east-2", + }, + }, + Object { + "term": Object { + "url.port": "80", + }, + }, + Object { + "term": Object { + "url.port": "5601", + }, + }, + Object { + "term": Object { + "monitor.type": "http", + }, + }, + Object { + "term": Object { + "monitor.type": "tcp", + }, + }, + ], + }, + }, + }, +} +`; diff --git a/x-pack/legacy/plugins/uptime/server/lib/adapters/monitors/__tests__/combine_range_with_filters.test.ts b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitors/__tests__/combine_range_with_filters.test.ts new file mode 100644 index 0000000000000..2075b3a8fbe0f --- /dev/null +++ b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitors/__tests__/combine_range_with_filters.test.ts @@ -0,0 +1,110 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { combineRangeWithFilters } from '../elasticsearch_monitors_adapter'; + +describe('combineRangeWithFilters', () => { + it('combines filters that have no filter clause', () => { + expect( + combineRangeWithFilters('now-15m', 'now', { + bool: { should: [{ match: { 'url.port': 80 } }], minimum_should_match: 1 }, + }) + ).toEqual({ + bool: { + should: [ + { + match: { + 'url.port': 80, + }, + }, + ], + minimum_should_match: 1, + filter: [ + { + range: { + '@timestamp': { + gte: 'now-15m', + lte: 'now', + }, + }, + }, + ], + }, + }); + }); + + it('combines query with filter object', () => { + expect( + combineRangeWithFilters('now-15m', 'now', { + bool: { + filter: { term: { field: 'monitor.id' } }, + should: [{ match: { 'url.port': 80 } }], + minimum_should_match: 1, + }, + }) + ).toEqual({ + bool: { + filter: [ + { + field: 'monitor.id', + }, + { + range: { + '@timestamp': { + gte: 'now-15m', + lte: 'now', + }, + }, + }, + ], + should: [ + { + match: { + 'url.port': 80, + }, + }, + ], + minimum_should_match: 1, + }, + }); + }); + + it('combines query with filter list', () => { + expect( + combineRangeWithFilters('now-15m', 'now', { + bool: { + filter: [{ field: 'monitor.id' }], + should: [{ match: { 'url.port': 80 } }], + minimum_should_match: 1, + }, + }) + ).toEqual({ + bool: { + filter: [ + { + field: 'monitor.id', + }, + { + range: { + '@timestamp': { + gte: 'now-15m', + lte: 'now', + }, + }, + }, + ], + should: [ + { + match: { + 'url.port': 80, + }, + }, + ], + minimum_should_match: 1, + }, + }); + }); +}); diff --git a/x-pack/legacy/plugins/uptime/server/lib/adapters/monitors/__tests__/extract_filter_aggs_results.test.ts b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitors/__tests__/extract_filter_aggs_results.test.ts new file mode 100644 index 0000000000000..954cffd4c9522 --- /dev/null +++ b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitors/__tests__/extract_filter_aggs_results.test.ts @@ -0,0 +1,68 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { extractFilterAggsResults } from '../elasticsearch_monitors_adapter'; + +describe('extractFilterAggsResults', () => { + it('extracts the bucket values of the expected filter fields', () => { + expect( + extractFilterAggsResults( + { + locations: { + doc_count: 8098, + term: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { key: 'us-east-2', doc_count: 4050 }, + { key: 'fairbanks', doc_count: 4048 }, + ], + }, + }, + schemes: { + doc_count: 8098, + term: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { key: 'http', doc_count: 5055 }, + { key: 'tcp', doc_count: 2685 }, + { key: 'icmp', doc_count: 358 }, + ], + }, + }, + ports: { + doc_count: 8098, + term: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { key: 12349, doc_count: 3571 }, + { key: 80, doc_count: 2985 }, + { key: 5601, doc_count: 358 }, + { key: 8200, doc_count: 358 }, + { key: 9200, doc_count: 358 }, + { key: 9292, doc_count: 110 }, + ], + }, + }, + tags: { + doc_count: 8098, + term: { + doc_count_error_upper_bound: 0, + sum_other_doc_count: 0, + buckets: [ + { key: 'api', doc_count: 8098 }, + { key: 'dev', doc_count: 8098 }, + ], + }, + }, + }, + ['locations', 'ports', 'schemes', 'tags'] + ) + ).toMatchSnapshot(); + }); +}); diff --git a/x-pack/legacy/plugins/uptime/server/lib/adapters/monitors/__tests__/generate_filter_aggs.test.ts b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitors/__tests__/generate_filter_aggs.test.ts new file mode 100644 index 0000000000000..4e285ec25a492 --- /dev/null +++ b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitors/__tests__/generate_filter_aggs.test.ts @@ -0,0 +1,28 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { generateFilterAggs } from '../generate_filter_aggs'; + +describe('generateFilterAggs', () => { + it('generates expected aggregations object', () => { + expect( + generateFilterAggs( + [ + { aggName: 'locations', filterName: 'locations', field: 'observer.geo.name' }, + { aggName: 'ports', filterName: 'ports', field: 'url.port' }, + { aggName: 'schemes', filterName: 'schemes', field: 'monitor.type' }, + { aggName: 'tags', filterName: 'tags', field: 'tags' }, + ], + { + locations: ['fairbanks', 'us-east-2'], + ports: ['80', '5601'], + tags: ['api'], + schemes: ['http', 'tcp'], + } + ) + ).toMatchSnapshot(); + }); +}); diff --git a/x-pack/legacy/plugins/uptime/server/lib/adapters/monitors/adapter_types.ts b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitors/adapter_types.ts index b3d8cb855d55a..8523d9c75f51f 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/adapters/monitors/adapter_types.ts +++ b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitors/adapter_types.ts @@ -4,9 +4,13 @@ * you may not use this file except in compliance with the Elastic License. */ -import { MonitorChart, MonitorPageTitle } from '../../../../common/graphql/types'; +import { MonitorChart } from '../../../../common/graphql/types'; import { UMElasticsearchQueryFn } from '../framework'; -import { MonitorDetails, MonitorLocations } from '../../../../common/runtime_types'; +import { + MonitorDetails, + MonitorLocations, + OverviewFilters, +} from '../../../../common/runtime_types'; export interface GetMonitorChartsDataParams { /** @member monitorId ID value for the selected monitor */ @@ -20,18 +24,21 @@ export interface GetMonitorChartsDataParams { } export interface GetFilterBarParams { + /** @param dateRangeStart timestamp bounds */ dateRangeStart: string; /** @member dateRangeEnd timestamp bounds */ dateRangeEnd: string; + /** @member search this value should correspond to Elasticsearch DSL + * generated from KQL text the user provided. + */ + search?: Record; + filterOptions: Record; } export interface GetMonitorDetailsParams { monitorId: string; -} - -export interface GetMonitorPageTitleParams { - /** @member monitorId the ID to query */ - monitorId: string; + dateStart: string; + dateEnd: string; } /** @@ -51,11 +58,13 @@ export interface UMMonitorsAdapter { * Fetches data used to populate monitor charts */ getMonitorChartsData: UMElasticsearchQueryFn; - getFilterBar: UMElasticsearchQueryFn; + /** - * Fetch data for the monitor page title. + * Fetch options for the filter bar. */ - getMonitorPageTitle: UMElasticsearchQueryFn<{ monitorId: string }, MonitorPageTitle | null>; + getFilterBar: UMElasticsearchQueryFn; + getMonitorDetails: UMElasticsearchQueryFn; + getMonitorLocations: UMElasticsearchQueryFn; } diff --git a/x-pack/legacy/plugins/uptime/server/lib/adapters/monitors/elasticsearch_monitors_adapter.ts b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitors/elasticsearch_monitors_adapter.ts index b335205458965..b237fd8771f58 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/adapters/monitors/elasticsearch_monitors_adapter.ts +++ b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitors/elasticsearch_monitors_adapter.ts @@ -6,10 +6,56 @@ import { get } from 'lodash'; import { INDEX_NAMES } from '../../../../common/constants'; -import { MonitorChart, Ping, LocationDurationLine } from '../../../../common/graphql/types'; +import { MonitorChart, LocationDurationLine } from '../../../../common/graphql/types'; import { getHistogramIntervalFormatted } from '../../helper'; import { MonitorError, MonitorLocation } from '../../../../common/runtime_types'; import { UMMonitorsAdapter } from './adapter_types'; +import { generateFilterAggs } from './generate_filter_aggs'; +import { OverviewFilters } from '../../../../common/runtime_types'; + +export const combineRangeWithFilters = ( + dateRangeStart: string, + dateRangeEnd: string, + filters?: Record +) => { + const range = { + range: { + '@timestamp': { + gte: dateRangeStart, + lte: dateRangeEnd, + }, + }, + }; + if (!filters) return range; + const clientFiltersList = Array.isArray(filters?.bool?.filter ?? {}) + ? // i.e. {"bool":{"filter":{ ...some nested filter objects }}} + filters.bool.filter + : // i.e. {"bool":{"filter":[ ...some listed filter objects ]}} + Object.keys(filters?.bool?.filter ?? {}).map(key => ({ + ...filters?.bool?.filter?.[key], + })); + filters.bool.filter = [...clientFiltersList, range]; + return filters; +}; + +type SupportedFields = 'locations' | 'ports' | 'schemes' | 'tags'; + +export const extractFilterAggsResults = ( + responseAggregations: Record, + keys: SupportedFields[] +): OverviewFilters => { + const values: OverviewFilters = { + locations: [], + ports: [], + schemes: [], + tags: [], + }; + keys.forEach(key => { + const buckets = responseAggregations[key]?.term?.buckets ?? []; + values[key] = buckets.map((item: { key: string | number }) => item.key); + }); + return values; +}; const formatStatusBuckets = (time: any, buckets: any, docCount: any) => { let up = null; @@ -160,78 +206,49 @@ export const elasticsearchMonitorsAdapter: UMMonitorsAdapter = { return monitorChartsData; }, - getFilterBar: async ({ callES, dateRangeStart, dateRangeEnd }) => { - const fields: { [key: string]: string } = { - ids: 'monitor.id', - schemes: 'monitor.type', - urls: 'url.full', - ports: 'url.port', - locations: 'observer.geo.name', - }; + getFilterBar: async ({ callES, dateRangeStart, dateRangeEnd, search, filterOptions }) => { + const aggs = generateFilterAggs( + [ + { aggName: 'locations', filterName: 'locations', field: 'observer.geo.name' }, + { aggName: 'ports', filterName: 'ports', field: 'url.port' }, + { aggName: 'schemes', filterName: 'schemes', field: 'monitor.type' }, + { aggName: 'tags', filterName: 'tags', field: 'tags' }, + ], + filterOptions + ); + const filters = combineRangeWithFilters(dateRangeStart, dateRangeEnd, search); const params = { index: INDEX_NAMES.HEARTBEAT, body: { size: 0, query: { - range: { - '@timestamp': { - gte: dateRangeStart, - lte: dateRangeEnd, - }, - }, + ...filters, }, - aggs: Object.values(fields).reduce((acc: { [key: string]: any }, field) => { - acc[field] = { terms: { field, size: 20 } }; - return acc; - }, {}), + aggs, }, }; - const { aggregations } = await callES('search', params); - return Object.keys(fields).reduce((acc: { [key: string]: any[] }, field) => { - const bucketName = fields[field]; - acc[field] = aggregations[bucketName].buckets.map((b: { key: string | number }) => b.key); - return acc; - }, {}); + const { aggregations } = await callES('search', params); + return extractFilterAggsResults(aggregations, ['tags', 'locations', 'ports', 'schemes']); }, - getMonitorPageTitle: async ({ callES, monitorId }) => { - const params = { - index: INDEX_NAMES.HEARTBEAT, - body: { - query: { - bool: { - filter: { - term: { - 'monitor.id': monitorId, - }, - }, + getMonitorDetails: async ({ callES, monitorId, dateStart, dateEnd }) => { + const queryFilters: any = [ + { + range: { + '@timestamp': { + gte: dateStart, + lte: dateEnd, }, }, - sort: [ - { - '@timestamp': { - order: 'desc', - }, - }, - ], - size: 1, }, - }; - - const result = await callES('search', params); - const pageTitle: Ping | null = get(result, 'hits.hits[0]._source', null); - if (pageTitle === null) { - return null; - } - return { - id: get(pageTitle, 'monitor.id', null) || monitorId, - url: get(pageTitle, 'url.full', null), - name: get(pageTitle, 'monitor.name', null), - }; - }, + { + term: { + 'monitor.id': monitorId, + }, + }, + ]; - getMonitorDetails: async ({ callES, monitorId }) => { const params = { index: INDEX_NAMES.HEARTBEAT, body: { @@ -246,13 +263,7 @@ export const elasticsearchMonitorsAdapter: UMMonitorsAdapter = { }, }, ], - filter: [ - { - term: { - 'monitor.id': monitorId, - }, - }, - ], + filter: queryFilters, }, }, sort: [ @@ -279,11 +290,6 @@ export const elasticsearchMonitorsAdapter: UMMonitorsAdapter = { }; }, - /** - * Fetch data for the monitor page title. - * @param request Kibana server request - * - */ getMonitorLocations: async ({ callES, monitorId, dateStart, dateEnd }) => { const params = { index: INDEX_NAMES.HEARTBEAT, @@ -328,7 +334,7 @@ export const elasticsearchMonitorsAdapter: UMMonitorsAdapter = { order: 'desc', }, }, - _source: ['monitor', 'summary', 'observer'], + _source: ['monitor', 'summary', 'observer', '@timestamp'], }, }, }, @@ -359,6 +365,7 @@ export const elasticsearchMonitorsAdapter: UMMonitorsAdapter = { const location: MonitorLocation = { summary: mostRecentLocation?.summary, geo: getGeo(mostRecentLocation?.observer?.geo), + timestamp: mostRecentLocation['@timestamp'], }; monLocs.push(location); } diff --git a/x-pack/legacy/plugins/uptime/server/lib/adapters/monitors/generate_filter_aggs.ts b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitors/generate_filter_aggs.ts new file mode 100644 index 0000000000000..26d412e33c868 --- /dev/null +++ b/x-pack/legacy/plugins/uptime/server/lib/adapters/monitors/generate_filter_aggs.ts @@ -0,0 +1,58 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +interface AggDefinition { + aggName: string; + filterName: string; + field: string; +} + +export const FIELD_MAPPINGS: Record = { + schemes: 'monitor.type', + ports: 'url.port', + locations: 'observer.geo.name', + tags: 'tags', +}; + +const getFilterAggConditions = (filterTerms: Record, except: string) => { + const filters: any[] = []; + + Object.keys(filterTerms).forEach((key: string) => { + if (key === except && FIELD_MAPPINGS[key]) return; + filters.push( + ...filterTerms[key].map(value => ({ + term: { + [FIELD_MAPPINGS[key]]: value, + }, + })) + ); + }); + + return filters; +}; + +export const generateFilterAggs = ( + aggDefinitions: AggDefinition[], + filterOptions: Record +) => + aggDefinitions + .map(({ aggName, filterName, field }) => ({ + [aggName]: { + filter: { + bool: { + should: [...getFilterAggConditions(filterOptions, filterName)], + }, + }, + aggs: { + term: { + terms: { + field, + }, + }, + }, + }, + })) + .reduce((parent: Record, agg: any) => ({ ...parent, ...agg }), {}); diff --git a/x-pack/legacy/plugins/uptime/server/lib/adapters/pings/__tests__/elasticsearch_pings_adapter.test.ts b/x-pack/legacy/plugins/uptime/server/lib/adapters/pings/__tests__/elasticsearch_pings_adapter.test.ts index bd1c182e938a3..e1e39ac9b2637 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/adapters/pings/__tests__/elasticsearch_pings_adapter.test.ts +++ b/x-pack/legacy/plugins/uptime/server/lib/adapters/pings/__tests__/elasticsearch_pings_adapter.test.ts @@ -411,7 +411,7 @@ describe('ElasticsearchPingsAdapter class', () => { }); }); - describe('getLatestMonitorDocs', () => { + describe('getLatestMonitorStatus', () => { let expectedGetLatestSearchParams: any; beforeEach(() => { expectedGetLatestSearchParams = { @@ -429,7 +429,7 @@ describe('ElasticsearchPingsAdapter class', () => { }, }, { - term: { 'monitor.id': 'testmonitor' }, + term: { 'monitor.id': 'testMonitor' }, }, ], }, @@ -467,7 +467,7 @@ describe('ElasticsearchPingsAdapter class', () => { _source: { '@timestamp': 123456, monitor: { - id: 'testmonitor', + id: 'testMonitor', }, }, }, @@ -483,17 +483,16 @@ describe('ElasticsearchPingsAdapter class', () => { it('returns data in expected shape', async () => { const mockEsClient = jest.fn(async (_request: any, _params: any) => mockEsSearchResult); - const result = await adapter.getLatestMonitorDocs({ + const result = await adapter.getLatestMonitorStatus({ callES: mockEsClient, - dateRangeStart: 'now-1h', - dateRangeEnd: 'now', - monitorId: 'testmonitor', + dateStart: 'now-1h', + dateEnd: 'now', + monitorId: 'testMonitor', }); - expect(result).toHaveLength(1); - expect(result[0].timestamp).toBe(123456); - expect(result[0].monitor).not.toBeFalsy(); + expect(result.timestamp).toBe(123456); + expect(result.monitor).not.toBeFalsy(); // @ts-ignore monitor will be defined - expect(result[0].monitor.id).toBe('testmonitor'); + expect(result.monitor.id).toBe('testMonitor'); expect(mockEsClient).toHaveBeenCalledWith('search', expectedGetLatestSearchParams); }); }); diff --git a/x-pack/legacy/plugins/uptime/server/lib/adapters/pings/adapter_types.ts b/x-pack/legacy/plugins/uptime/server/lib/adapters/pings/adapter_types.ts index 81df1c7c0f631..8b2a49c0c9ffe 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/adapters/pings/adapter_types.ts +++ b/x-pack/legacy/plugins/uptime/server/lib/adapters/pings/adapter_types.ts @@ -33,16 +33,13 @@ export interface GetAllParams { export interface GetLatestMonitorDocsParams { /** @member dateRangeStart timestamp bounds */ - dateRangeStart: string; + dateStart?: string; /** @member dateRangeEnd timestamp bounds */ - dateRangeEnd: string; + dateEnd?: string; /** @member monitorId optional limit to monitorId */ monitorId?: string | null; - - /** @member location optional location value for use in filtering*/ - location?: string | null; } export interface GetPingHistogramParams { @@ -64,7 +61,10 @@ export interface GetPingHistogramParams { export interface UMPingsAdapter { getAll: UMElasticsearchQueryFn; - getLatestMonitorDocs: UMElasticsearchQueryFn; + // Get the monitor meta info regardless of timestamp + getMonitor: UMElasticsearchQueryFn; + + getLatestMonitorStatus: UMElasticsearchQueryFn; getPingHistogram: UMElasticsearchQueryFn; diff --git a/x-pack/legacy/plugins/uptime/server/lib/adapters/pings/elasticsearch_pings_adapter.ts b/x-pack/legacy/plugins/uptime/server/lib/adapters/pings/elasticsearch_pings_adapter.ts index 6862bed8d2bdd..adabffcb1ea4a 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/adapters/pings/elasticsearch_pings_adapter.ts +++ b/x-pack/legacy/plugins/uptime/server/lib/adapters/pings/elasticsearch_pings_adapter.ts @@ -88,7 +88,10 @@ export const elasticsearchPingsAdapter: UMPingsAdapter = { return results; }, - getLatestMonitorDocs: async ({ callES, dateRangeStart, dateRangeEnd, monitorId, location }) => { + // Get The monitor latest state sorted by timestamp with date range + getLatestMonitorStatus: async ({ callES, dateStart, dateEnd, monitorId }) => { + // TODO: Write tests for this function + const params = { index: INDEX_NAMES.HEARTBEAT, body: { @@ -98,13 +101,12 @@ export const elasticsearchPingsAdapter: UMPingsAdapter = { { range: { '@timestamp': { - gte: dateRangeStart, - lte: dateRangeEnd, + gte: dateStart, + lte: dateEnd, }, }, }, ...(monitorId ? [{ term: { 'monitor.id': monitorId } }] : []), - ...(location ? [{ term: { 'observer.geo.name': location } }] : []), ], }, }, @@ -131,21 +133,45 @@ export const elasticsearchPingsAdapter: UMPingsAdapter = { }; const result = await callES('search', params); - const buckets: any[] = get(result, 'aggregations.by_id.buckets', []); + const ping: any = result.aggregations.by_id.buckets?.[0]?.latest.hits?.hits?.[0] ?? {}; + + return { + ...ping?._source, + timestamp: ping?._source?.['@timestamp'], + }; + }, - return buckets.map( - ({ - latest: { - hits: { hits }, + // Get the monitor meta info regardless of timestamp + getMonitor: async ({ callES, monitorId }) => { + const params = { + index: INDEX_NAMES.HEARTBEAT, + body: { + size: 1, + _source: ['url', 'monitor', 'observer'], + query: { + bool: { + filter: [ + { + term: { + 'monitor.id': monitorId, + }, + }, + ], + }, }, - }) => { - const timestamp = hits[0]._source[`@timestamp`]; - return { - ...hits[0]._source, - timestamp, - }; - } - ); + sort: [ + { + '@timestamp': { + order: 'desc', + }, + }, + ], + }, + }; + + const result = await callES('search', params); + + return result.hits.hits[0]?._source; }, getPingHistogram: async ({ @@ -157,14 +183,14 @@ export const elasticsearchPingsAdapter: UMPingsAdapter = { statusFilter, }) => { const boolFilters = parseFilterQuery(filters); - const additionaFilters = []; + const additionalFilters = []; if (monitorId) { - additionaFilters.push({ match: { 'monitor.id': monitorId } }); + additionalFilters.push({ match: { 'monitor.id': monitorId } }); } if (boolFilters) { - additionaFilters.push(boolFilters); + additionalFilters.push(boolFilters); } - const filter = getFilterClause(dateRangeStart, dateRangeEnd, additionaFilters); + const filter = getFilterClause(dateRangeStart, dateRangeEnd, additionalFilters); const interval = getHistogramInterval(dateRangeStart, dateRangeEnd); const intervalFormatted = getHistogramIntervalFormatted(dateRangeStart, dateRangeEnd); diff --git a/x-pack/legacy/plugins/uptime/server/lib/helper/get_filter_clause.ts b/x-pack/legacy/plugins/uptime/server/lib/helper/get_filter_clause.ts index 5259aa1d61711..c81fec933cb22 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/helper/get_filter_clause.ts +++ b/x-pack/legacy/plugins/uptime/server/lib/helper/get_filter_clause.ts @@ -4,14 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -const getRange = (dateRangeStart: string, dateRangeEnd: string) => ({ - range: { - '@timestamp': { - gte: dateRangeStart, - lte: dateRangeEnd, - }, - }, -}); +import { makeDateRangeFilter } from './make_date_rate_filter'; export const getFilterClause = ( dateRangeStart: string, @@ -19,5 +12,5 @@ export const getFilterClause = ( additionalKeys?: Array<{ [key: string]: any }> ) => additionalKeys && additionalKeys.length > 0 - ? [getRange(dateRangeStart, dateRangeEnd), ...additionalKeys] - : [getRange(dateRangeStart, dateRangeEnd)]; + ? [makeDateRangeFilter(dateRangeStart, dateRangeEnd), ...additionalKeys] + : [makeDateRangeFilter(dateRangeStart, dateRangeEnd)]; diff --git a/x-pack/legacy/plugins/uptime/server/lib/helper/index.ts b/x-pack/legacy/plugins/uptime/server/lib/helper/index.ts index 4c88da7eca85a..f9a8de81332d5 100644 --- a/x-pack/legacy/plugins/uptime/server/lib/helper/index.ts +++ b/x-pack/legacy/plugins/uptime/server/lib/helper/index.ts @@ -9,3 +9,4 @@ export { getHistogramInterval } from './get_histogram_interval'; export { getHistogramIntervalFormatted } from './get_histogram_interval_formatted'; export { parseFilterQuery } from './parse_filter_query'; export { assertCloseTo } from './assert_close_to'; +export { objectValuesToArrays } from './object_to_array'; diff --git a/x-pack/legacy/plugins/uptime/server/lib/helper/object_to_array.ts b/x-pack/legacy/plugins/uptime/server/lib/helper/object_to_array.ts new file mode 100644 index 0000000000000..334c31c822eaa --- /dev/null +++ b/x-pack/legacy/plugins/uptime/server/lib/helper/object_to_array.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/** + * Converts the top-level fields of an object from an object to an array. + * @param record the obect to map + * @type T the type of the objects/arrays that will be mapped + */ +export const objectValuesToArrays = (record: Record): Record => { + const obj: Record = {}; + Object.keys(record).forEach((key: string) => { + const value = record[key]; + obj[key] = value ? (Array.isArray(value) ? value : [value]) : []; + }); + return obj; +}; diff --git a/x-pack/legacy/plugins/uptime/server/rest_api/index.ts b/x-pack/legacy/plugins/uptime/server/rest_api/index.ts index 4ab225076eff2..e64b317e67f98 100644 --- a/x-pack/legacy/plugins/uptime/server/rest_api/index.ts +++ b/x-pack/legacy/plugins/uptime/server/rest_api/index.ts @@ -4,21 +4,30 @@ * you may not use this file except in compliance with the Elastic License. */ +import { createGetOverviewFilters } from './overview_filters'; import { createGetAllRoute } from './pings'; import { createGetIndexPatternRoute } from './index_pattern'; import { createLogMonitorPageRoute, createLogOverviewPageRoute } from './telemetry'; import { createGetSnapshotCount } from './snapshot'; import { UMRestApiRouteFactory } from './types'; -import { createGetMonitorDetailsRoute, createGetMonitorLocationsRoute } from './monitors'; +import { + createGetMonitorRoute, + createGetMonitorDetailsRoute, + createGetMonitorLocationsRoute, + createGetStatusBarRoute, +} from './monitors'; export * from './types'; export { createRouteWithAuth } from './create_route_with_auth'; export { uptimeRouteWrapper } from './uptime_route_wrapper'; export const restApiRoutes: UMRestApiRouteFactory[] = [ + createGetOverviewFilters, createGetAllRoute, createGetIndexPatternRoute, + createGetMonitorRoute, createGetMonitorDetailsRoute, createGetMonitorLocationsRoute, + createGetStatusBarRoute, createGetSnapshotCount, createLogMonitorPageRoute, createLogOverviewPageRoute, diff --git a/x-pack/legacy/plugins/uptime/server/rest_api/monitors/index.ts b/x-pack/legacy/plugins/uptime/server/rest_api/monitors/index.ts index 2279233d49a09..7f1f10081dc4e 100644 --- a/x-pack/legacy/plugins/uptime/server/rest_api/monitors/index.ts +++ b/x-pack/legacy/plugins/uptime/server/rest_api/monitors/index.ts @@ -6,3 +6,4 @@ export { createGetMonitorDetailsRoute } from './monitors_details'; export { createGetMonitorLocationsRoute } from './monitor_locations'; +export { createGetMonitorRoute, createGetStatusBarRoute } from './status'; diff --git a/x-pack/legacy/plugins/uptime/server/rest_api/monitors/monitors_details.ts b/x-pack/legacy/plugins/uptime/server/rest_api/monitors/monitors_details.ts index a57e5ec469c59..9e1bc6f0d6a96 100644 --- a/x-pack/legacy/plugins/uptime/server/rest_api/monitors/monitors_details.ts +++ b/x-pack/legacy/plugins/uptime/server/rest_api/monitors/monitors_details.ts @@ -13,18 +13,24 @@ export const createGetMonitorDetailsRoute: UMRestApiRouteFactory = (libs: UMServ path: '/api/uptime/monitor/details', validate: { query: schema.object({ - monitorId: schema.maybe(schema.string()), + monitorId: schema.string(), + dateStart: schema.maybe(schema.string()), + dateEnd: schema.maybe(schema.string()), }), }, options: { tags: ['access:uptime'], }, handler: async ({ callES }, _context, request, response): Promise => { - const { monitorId } = request.query; - + const { monitorId, dateStart, dateEnd } = request.query; return response.ok({ body: { - ...(await libs.monitors.getMonitorDetails({ callES, monitorId })), + ...(await libs.monitors.getMonitorDetails({ + callES, + monitorId, + dateStart, + dateEnd, + })), }, }); }, diff --git a/x-pack/legacy/plugins/uptime/server/rest_api/monitors/status.ts b/x-pack/legacy/plugins/uptime/server/rest_api/monitors/status.ts new file mode 100644 index 0000000000000..8b1bc04b45110 --- /dev/null +++ b/x-pack/legacy/plugins/uptime/server/rest_api/monitors/status.ts @@ -0,0 +1,60 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { schema } from '@kbn/config-schema'; +import { UMServerLibs } from '../../lib/lib'; +import { UMRestApiRouteFactory } from '../types'; + +export const createGetMonitorRoute: UMRestApiRouteFactory = (libs: UMServerLibs) => ({ + method: 'GET', + path: '/api/uptime/monitor/selected', + validate: { + query: schema.object({ + monitorId: schema.string(), + }), + }, + options: { + tags: ['access:uptime'], + }, + handler: async ({ callES }, _context, request, response): Promise => { + const { monitorId } = request.query; + + return response.ok({ + body: { + ...(await libs.pings.getMonitor({ callES, monitorId })), + }, + }); + }, +}); + +export const createGetStatusBarRoute: UMRestApiRouteFactory = (libs: UMServerLibs) => ({ + method: 'GET', + path: '/api/uptime/monitor/status', + validate: { + query: schema.object({ + monitorId: schema.string(), + dateStart: schema.string(), + dateEnd: schema.string(), + }), + }, + options: { + tags: ['access:uptime'], + }, + handler: async ({ callES }, _context, request, response): Promise => { + const { monitorId, dateStart, dateEnd } = request.query; + const result = await libs.pings.getLatestMonitorStatus({ + callES, + monitorId, + dateStart, + dateEnd, + }); + return response.ok({ + body: { + ...result, + }, + }); + }, +}); diff --git a/x-pack/legacy/plugins/uptime/server/rest_api/overview_filters/get_overview_filters.ts b/x-pack/legacy/plugins/uptime/server/rest_api/overview_filters/get_overview_filters.ts new file mode 100644 index 0000000000000..ef93253bb5b70 --- /dev/null +++ b/x-pack/legacy/plugins/uptime/server/rest_api/overview_filters/get_overview_filters.ts @@ -0,0 +1,61 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { schema } from '@kbn/config-schema'; +import { UMServerLibs } from '../../lib/lib'; +import { UMRestApiRouteFactory } from '../types'; +import { objectValuesToArrays } from '../../lib/helper'; + +const arrayOrStringType = schema.maybe( + schema.oneOf([schema.string(), schema.arrayOf(schema.string())]) +); + +export const createGetOverviewFilters: UMRestApiRouteFactory = (libs: UMServerLibs) => ({ + method: 'GET', + path: '/api/uptime/filters', + validate: { + query: schema.object({ + dateRangeStart: schema.string(), + dateRangeEnd: schema.string(), + search: schema.maybe(schema.string()), + locations: arrayOrStringType, + schemes: arrayOrStringType, + ports: arrayOrStringType, + tags: arrayOrStringType, + }), + }, + + options: { + tags: ['access:uptime'], + }, + handler: async ({ callES }, _context, request, response) => { + const { dateRangeStart, dateRangeEnd, locations, schemes, search, ports, tags } = request.query; + + let parsedSearch: Record | undefined; + if (search) { + try { + parsedSearch = JSON.parse(search); + } catch (e) { + return response.badRequest({ body: { message: e.message } }); + } + } + + const filtersResponse = await libs.monitors.getFilterBar({ + callES, + dateRangeStart, + dateRangeEnd, + search: parsedSearch, + filterOptions: objectValuesToArrays({ + locations, + ports, + schemes, + tags, + }), + }); + + return response.ok({ body: { ...filtersResponse } }); + }, +}); diff --git a/x-pack/legacy/plugins/uptime/server/rest_api/overview_filters/index.ts b/x-pack/legacy/plugins/uptime/server/rest_api/overview_filters/index.ts new file mode 100644 index 0000000000000..dc4e0c66a8183 --- /dev/null +++ b/x-pack/legacy/plugins/uptime/server/rest_api/overview_filters/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { createGetOverviewFilters } from './get_overview_filters'; diff --git a/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/watch_create_threshold.helpers.ts b/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/watch_create_threshold.helpers.ts index 8cebe8ce26229..65648ae5f5a95 100644 --- a/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/watch_create_threshold.helpers.ts +++ b/x-pack/legacy/plugins/watcher/__jest__/client_integration/helpers/watch_create_threshold.helpers.ts @@ -104,4 +104,5 @@ export type TestSubjects = | 'webhookPathInput' | 'webhookPortInput' | 'webhookMethodSelect' + | 'webhookSchemeSelect' | 'webhookUsernameInput'; diff --git a/x-pack/legacy/plugins/watcher/__jest__/client_integration/watch_create_threshold.test.tsx b/x-pack/legacy/plugins/watcher/__jest__/client_integration/watch_create_threshold.test.tsx index 36a5c150eead7..2800b0107da24 100644 --- a/x-pack/legacy/plugins/watcher/__jest__/client_integration/watch_create_threshold.test.tsx +++ b/x-pack/legacy/plugins/watcher/__jest__/client_integration/watch_create_threshold.test.tsx @@ -257,9 +257,11 @@ describe(' create route', () => { triggerIntervalUnit: 'm', aggType: 'count', termSize: 5, + termOrder: 'desc', thresholdComparator: '>', timeWindowSize: 5, timeWindowUnit: 'm', + hasTermsAgg: false, threshold: 1000, }; @@ -324,9 +326,11 @@ describe(' create route', () => { triggerIntervalUnit: 'm', aggType: 'count', termSize: 5, + termOrder: 'desc', thresholdComparator: '>', timeWindowSize: 5, timeWindowUnit: 'm', + hasTermsAgg: false, threshold: 1000, }; @@ -387,9 +391,11 @@ describe(' create route', () => { triggerIntervalUnit: 'm', aggType: 'count', termSize: 5, + termOrder: 'desc', thresholdComparator: '>', timeWindowSize: 5, timeWindowUnit: 'm', + hasTermsAgg: false, threshold: 1000, }; @@ -461,9 +467,11 @@ describe(' create route', () => { triggerIntervalUnit: 'm', aggType: 'count', termSize: 5, + termOrder: 'desc', thresholdComparator: '>', timeWindowSize: 5, timeWindowUnit: 'm', + hasTermsAgg: false, threshold: 1000, }; @@ -487,6 +495,7 @@ describe(' create route', () => { const METHOD = 'put'; const HOST = 'localhost'; const PORT = '9200'; + const SCHEME = 'http'; const PATH = '/test'; const USERNAME = 'test_user'; const PASSWORD = 'test_password'; @@ -510,6 +519,7 @@ describe(' create route', () => { form.setInputValue('webhookMethodSelect', METHOD); form.setInputValue('webhookHostInput', HOST); form.setInputValue('webhookPortInput', PORT); + form.setInputValue('webhookSchemeSelect', SCHEME); form.setInputValue('webhookPathInput', PATH); form.setInputValue('webhookUsernameInput', USERNAME); form.setInputValue('webhookPasswordInput', PASSWORD); @@ -534,6 +544,7 @@ describe(' create route', () => { method: METHOD, host: HOST, port: Number(PORT), + scheme: SCHEME, path: PATH, body: '{\n "message": "Watch [{{ctx.metadata.name}}] has exceeded the threshold"\n}', // Default @@ -551,9 +562,11 @@ describe(' create route', () => { triggerIntervalUnit: 'm', aggType: 'count', termSize: 5, + termOrder: 'desc', thresholdComparator: '>', timeWindowSize: 5, timeWindowUnit: 'm', + hasTermsAgg: false, threshold: 1000, }; @@ -639,9 +652,11 @@ describe(' create route', () => { triggerIntervalUnit: 'm', aggType: 'count', termSize: 5, + termOrder: 'desc', thresholdComparator: '>', timeWindowSize: 5, timeWindowUnit: 'm', + hasTermsAgg: false, threshold: 1000, }; @@ -707,9 +722,11 @@ describe(' create route', () => { triggerIntervalUnit: 'm', aggType: 'count', termSize: 5, + termOrder: 'desc', thresholdComparator: '>', timeWindowSize: 5, timeWindowUnit: 'm', + hasTermsAgg: false, threshold: 1000, }; @@ -759,9 +776,11 @@ describe(' create route', () => { triggerIntervalUnit: 'm', aggType: 'count', termSize: 5, + termOrder: 'desc', thresholdComparator: '>', timeWindowSize: 5, timeWindowUnit: 'm', + hasTermsAgg: false, threshold: 1000, }; diff --git a/x-pack/legacy/plugins/watcher/__jest__/client_integration/watch_edit.test.ts b/x-pack/legacy/plugins/watcher/__jest__/client_integration/watch_edit.test.ts index 1eee3d3b7e6ee..131400a8702c4 100644 --- a/x-pack/legacy/plugins/watcher/__jest__/client_integration/watch_edit.test.ts +++ b/x-pack/legacy/plugins/watcher/__jest__/client_integration/watch_edit.test.ts @@ -168,6 +168,7 @@ describe('', () => { }); const latestRequest = server.requests[server.requests.length - 1]; + const { id, type, @@ -194,9 +195,11 @@ describe('', () => { triggerIntervalUnit, aggType, termSize, + termOrder: 'desc', thresholdComparator, timeWindowSize, timeWindowUnit, + hasTermsAgg: false, threshold: threshold && threshold[0], }) ); diff --git a/x-pack/legacy/plugins/watcher/common/models/action/webhook_action.js b/x-pack/legacy/plugins/watcher/common/models/action/webhook_action.js index 54be8407f207d..d6f921a75a9ea 100644 --- a/x-pack/legacy/plugins/watcher/common/models/action/webhook_action.js +++ b/x-pack/legacy/plugins/watcher/common/models/action/webhook_action.js @@ -16,6 +16,7 @@ export class WebhookAction extends BaseAction { this.method = props.method; this.host = props.host; this.port = props.port; + this.scheme = props.scheme; this.path = props.path; this.body = props.body; this.contentType = props.contentType; @@ -30,6 +31,7 @@ export class WebhookAction extends BaseAction { method: this.method, host: this.host, port: this.port, + scheme: this.scheme, path: this.path, body: this.body, contentType: this.contentType, @@ -47,6 +49,7 @@ export class WebhookAction extends BaseAction { method: json.method, host: json.host, port: json.port, + scheme: json.scheme, path: json.path, body: json.body, contentType: json.contentType, @@ -72,6 +75,10 @@ export class WebhookAction extends BaseAction { optionalFields.method = this.method; } + if (this.scheme) { + optionalFields.scheme = this.scheme; + } + if (this.body) { optionalFields.body = this.body; } @@ -108,7 +115,7 @@ export class WebhookAction extends BaseAction { const webhookJson = json && json.actionJson && json.actionJson.webhook; const { errors } = this.validateJson(json.actionJson); - const { path, method, body, auth, headers } = webhookJson; + const { path, method, scheme, body, auth, headers } = webhookJson; const optionalFields = {}; @@ -120,6 +127,10 @@ export class WebhookAction extends BaseAction { optionalFields.method = method; } + if (scheme) { + optionalFields.scheme = scheme; + } + if (body) { optionalFields.body = body; } diff --git a/x-pack/legacy/plugins/watcher/common/types/action_types.ts b/x-pack/legacy/plugins/watcher/common/types/action_types.ts index 123bf0f58db9d..918e9a933611b 100644 --- a/x-pack/legacy/plugins/watcher/common/types/action_types.ts +++ b/x-pack/legacy/plugins/watcher/common/types/action_types.ts @@ -56,6 +56,7 @@ export interface WebhookAction extends BaseAction { method?: 'head' | 'get' | 'post' | 'put' | 'delete'; host: string; port: number; + scheme?: 'http' | 'https'; path?: string; body?: string; username?: string; diff --git a/x-pack/legacy/plugins/watcher/public/legacy.ts b/x-pack/legacy/plugins/watcher/public/legacy.ts index d7b85ccfeb7b4..21fcd718ea1b7 100644 --- a/x-pack/legacy/plugins/watcher/public/legacy.ts +++ b/x-pack/legacy/plugins/watcher/public/legacy.ts @@ -43,6 +43,7 @@ routes.when('/management/elasticsearch/watcher/:param1?/:param2?/:param3?/:param app.mount(npStart as any, { element: elem, appBasePath: '/management/elasticsearch/watcher/', + onAppLeave: () => undefined, }); }, }, diff --git a/x-pack/legacy/plugins/watcher/public/np_ready/application/models/action/webhook_action.js b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/action/webhook_action.js index d46d9aacb035b..3225653acbb3d 100644 --- a/x-pack/legacy/plugins/watcher/public/np_ready/application/models/action/webhook_action.js +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/action/webhook_action.js @@ -11,23 +11,22 @@ import { i18n } from '@kbn/i18n'; export class WebhookAction extends BaseAction { constructor(props = {}) { super(props); - const defaultJson = JSON.stringify( { message: 'Watch [{{ctx.metadata.name}}] has exceeded the threshold' }, null, 2 ); this.body = get(props, 'body', props.ignoreDefaults ? null : defaultJson); - this.method = get(props, 'method'); this.host = get(props, 'host'); this.port = get(props, 'port'); + this.scheme = get(props, 'scheme', 'http'); this.path = get(props, 'path'); this.username = get(props, 'username'); this.password = get(props, 'password'); this.contentType = get(props, 'contentType'); - this.fullPath = `${this.host}:${this.port}${this.path}`; + this.fullPath = `${this.host}:${this.port}${this.path ? '/' + this.path : ''}`; } validate() { @@ -112,6 +111,7 @@ export class WebhookAction extends BaseAction { method: this.method, host: this.host, port: this.port, + scheme: this.scheme, path: this.path, body: this.body, username: this.username, diff --git a/x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch/threshold_watch.js b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch/threshold_watch.js index 7611f158fb962..2383388dd89bf 100644 --- a/x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch/threshold_watch.js +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/models/watch/threshold_watch.js @@ -256,9 +256,11 @@ export class ThresholdWatch extends BaseWatch { aggField: this.aggField, termSize: this.termSize, termField: this.termField, + termOrder: this.termOrder, thresholdComparator: this.thresholdComparator, timeWindowSize: this.timeWindowSize, timeWindowUnit: this.timeWindowUnit, + hasTermsAgg: this.hasTermsAgg, threshold: comparators[this.thresholdComparator].requiredValues > 1 ? this.threshold diff --git a/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/action_fields/webhook_action_fields.tsx b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/action_fields/webhook_action_fields.tsx index bdc6f0bcbb717..be0b551f4a39c 100644 --- a/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/action_fields/webhook_action_fields.tsx +++ b/x-pack/legacy/plugins/watcher/public/np_ready/application/sections/watch_edit/components/threshold_watch_edit/action_fields/webhook_action_fields.tsx @@ -29,13 +29,15 @@ interface Props { const HTTP_VERBS = ['head', 'get', 'post', 'put', 'delete']; +const SCHEME = ['http', 'https']; + export const WebhookActionFields: React.FunctionComponent = ({ action, editAction, errors, hasErrors, }) => { - const { method, host, port, path, body, username, password } = action; + const { method, host, port, scheme, path, body, username, password } = action; useEffect(() => { editAction({ key: 'contentType', value: 'application/json' }); // set content-type for threshold watch to json by default @@ -65,6 +67,27 @@ export const WebhookActionFields: React.FunctionComponent = ({
+ + + ({ text: verb, value: verb }))} + onChange={e => { + editAction({ key: 'scheme', value: e.target.value }); + }} + /> + + + { legendPosition={Position.Bottom} /> - + {watchVisualizationDataKeys.map((key: string) => { return ( { return ( diff --git a/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/base_watch.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/base_watch.js index d93535f9fb8af..d77e6d6caa2fa 100644 --- a/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/base_watch.js +++ b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/base_watch.js @@ -66,16 +66,6 @@ export class BaseWatch { return json; } - // to Elasticsearch - get upstreamJson() { - const watch = this.watchJson; - - return { - id: this.id, - watch, - }; - } - // from Kibana static getPropsFromDownstreamJson(json) { const actions = map(json.actions, action => { diff --git a/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/base_watch.test.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/base_watch.test.js index bf7889473b60d..c83fbc0b4564c 100644 --- a/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/base_watch.test.js +++ b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/base_watch.test.js @@ -188,50 +188,6 @@ describe('BaseWatch', () => { }); }); - describe('upstreamJson getter method', () => { - let props; - beforeEach(() => { - props = { - id: 'foo', - name: 'bar', - type: 'json', - watchStatus: { - downstreamJson: { - prop1: 'prop1', - prop2: 'prop2', - }, - }, - actions: [ - { - downstreamJson: { - prop1: 'prop3', - prop2: 'prop4', - }, - }, - ], - }; - }); - - it('should return a valid object', () => { - const watch = new BaseWatch(props); - - const actual = watch.upstreamJson; - const expected = { - id: props.id, - watch: { - metadata: { - name: props.name, - xpack: { - type: props.type, - }, - }, - }, - }; - - expect(actual).toEqual(expected); - }); - }); - describe('getPropsFromDownstreamJson method', () => { let downstreamJson; beforeEach(() => { diff --git a/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/json_watch.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/json_watch.js index e6d49f7adc19e..2440d4ef33881 100644 --- a/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/json_watch.js +++ b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/json_watch.js @@ -23,12 +23,6 @@ export class JsonWatch extends BaseWatch { return serializeJsonWatch(this.name, this.watch); } - // To Elasticsearch - get upstreamJson() { - const result = super.upstreamJson; - return result; - } - // To Kibana get downstreamJson() { const result = merge({}, super.downstreamJson, { diff --git a/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/json_watch.test.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/json_watch.test.js index 56150667b609e..0301c4c95be94 100644 --- a/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/json_watch.test.js +++ b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/json_watch.test.js @@ -52,26 +52,6 @@ describe('JsonWatch', () => { }); }); - describe('upstreamJson getter method', () => { - it('should return the correct result', () => { - const watch = new JsonWatch({ watch: { foo: 'bar' } }); - const actual = watch.upstreamJson; - const expected = { - id: undefined, - watch: { - foo: 'bar', - metadata: { - xpack: { - type: 'json', - }, - }, - }, - }; - - expect(actual).toEqual(expected); - }); - }); - describe('downstreamJson getter method', () => { let props; beforeEach(() => { diff --git a/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/threshold_watch.js b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/threshold_watch.js index 21909c488431f..e5410588ab566 100644 --- a/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/threshold_watch.js +++ b/x-pack/legacy/plugins/watcher/server/np_ready/models/watch/threshold_watch/threshold_watch.js @@ -55,12 +55,6 @@ export class ThresholdWatch extends BaseWatch { return formatVisualizeData(this, results); } - // To Elasticsearch - get upstreamJson() { - const result = super.upstreamJson; - return result; - } - // To Kibana get downstreamJson() { const result = merge({}, super.downstreamJson, { diff --git a/x-pack/legacy/plugins/watcher/server/np_ready/plugin.ts b/x-pack/legacy/plugins/watcher/server/np_ready/plugin.ts index 2e8c81efa19c0..a311c31082183 100644 --- a/x-pack/legacy/plugins/watcher/server/np_ready/plugin.ts +++ b/x-pack/legacy/plugins/watcher/server/np_ready/plugin.ts @@ -3,7 +3,6 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { first } from 'rxjs/operators'; import { Plugin, CoreSetup } from 'src/core/server'; import { i18n } from '@kbn/i18n'; import { PLUGIN } from '../../common/constants'; @@ -23,7 +22,7 @@ export class WatcherServerPlugin implements Plugin { { http, elasticsearch: elasticsearchService }: CoreSetup, { __LEGACY: serverShim }: { __LEGACY: ServerShim } ) { - const elasticsearch = await elasticsearchService.adminClient$.pipe(first()).toPromise(); + const elasticsearch = await elasticsearchService.adminClient; const router = http.createRouter(); const routeDependencies: RouteDependencies = { elasticsearch, diff --git a/x-pack/package.json b/x-pack/package.json index a90c1ed7f5b53..3f826030ac16b 100644 --- a/x-pack/package.json +++ b/x-pack/package.json @@ -31,7 +31,6 @@ "@kbn/es": "1.0.0", "@kbn/expect": "1.0.0", "@kbn/plugin-helpers": "9.0.2", - "@kbn/pm": "1.0.0", "@kbn/test": "1.0.0", "@kbn/utility-types": "1.0.0", "@mattapperson/slapshot": "1.4.0", @@ -43,6 +42,7 @@ "@storybook/react": "^5.2.6", "@storybook/theming": "^5.2.6", "@testing-library/react": "^9.3.2", + "@testing-library/react-hooks": "^3.2.1", "@testing-library/jest-dom": "4.2.0", "@types/angular": "^1.6.56", "@types/archiver": "^3.0.0", @@ -97,7 +97,7 @@ "@types/react-test-renderer": "^16.9.1", "@types/recompose": "^0.30.6", "@types/reduce-reducers": "^0.3.0", - "@types/redux-actions": "^2.2.1", + "@types/redux-actions": "^2.6.1", "@types/sinon": "^7.0.13", "@types/styled-components": "^4.4.1", "@types/supertest": "^2.0.5", @@ -193,6 +193,7 @@ "@scant/router": "^0.1.0", "@slack/webhook": "^5.0.0", "@turf/boolean-contains": "6.0.1", + "angular": "^1.7.9", "angular-resource": "1.7.8", "angular-sanitize": "1.7.8", "angular-ui-ace": "0.2.3", @@ -231,7 +232,7 @@ "file-type": "^10.9.0", "font-awesome": "4.7.0", "formsy-react": "^1.1.5", - "fp-ts": "^2.0.5", + "fp-ts": "^2.3.1", "geojson-rewind": "^0.3.1", "get-port": "4.2.0", "getos": "^3.1.0", @@ -309,12 +310,13 @@ "react-shortcuts": "^2.0.0", "react-sticky": "^6.0.3", "react-syntax-highlighter": "^5.7.0", + "react-use": "^13.13.0", "react-vis": "^1.8.1", "react-visibility-sensor": "^5.1.1", "recompose": "^0.26.0", "reduce-reducers": "^0.4.3", "redux": "4.0.0", - "redux-actions": "2.2.1", + "redux-actions": "2.6.5", "redux-observable": "^1.0.0", "redux-saga": "^0.16.0", "redux-thunk": "2.3.0", @@ -322,6 +324,7 @@ "request": "^2.88.0", "reselect": "3.0.1", "resize-observer-polyfill": "^1.5.0", + "re-resizable": "^6.1.1", "rison-node": "0.3.1", "rxjs": "^6.5.3", "semver": "5.7.0", @@ -342,7 +345,7 @@ "uuid": "3.3.2", "venn.js": "0.2.20", "vscode-languageserver": "^5.2.1", - "webpack": "4.33.0", + "webpack": "4.41.0", "wellknown": "^0.5.0", "xml2js": "^0.4.22", "xregexp": "4.2.4" diff --git a/x-pack/plugins/advanced_ui_actions/public/customize_time_range_modal.tsx b/x-pack/plugins/advanced_ui_actions/public/customize_time_range_modal.tsx index 90393f9f4ff6f..9880a2b811f8b 100644 --- a/x-pack/plugins/advanced_ui_actions/public/customize_time_range_modal.tsx +++ b/x-pack/plugins/advanced_ui_actions/public/customize_time_range_modal.tsx @@ -137,7 +137,7 @@ export class CustomizeTimeRangeModal extends Component {i18n.translate( diff --git a/x-pack/plugins/apm/server/index.ts b/x-pack/plugins/apm/server/index.ts index b0e10d245e0b9..e301d157d2c7c 100644 --- a/x-pack/plugins/apm/server/index.ts +++ b/x-pack/plugins/apm/server/index.ts @@ -16,6 +16,7 @@ export const config = { }, schema: schema.object({ serviceMapEnabled: schema.boolean({ defaultValue: false }), + serviceMapInitialTimeRange: schema.number({ defaultValue: 60 * 1000 * 60 }), // last 1 hour autocreateApmIndexPattern: schema.boolean({ defaultValue: true }), ui: schema.object({ enabled: schema.boolean({ defaultValue: true }), @@ -37,6 +38,7 @@ export function mergeConfigs(apmOssConfig: APMOSSConfig, apmConfig: APMXPackConf 'apm_oss.onboardingIndices': apmOssConfig.onboardingIndices, 'apm_oss.indexPattern': apmOssConfig.indexPattern, 'xpack.apm.serviceMapEnabled': apmConfig.serviceMapEnabled, + 'xpack.apm.serviceMapInitialTimeRange': apmConfig.serviceMapInitialTimeRange, 'xpack.apm.ui.enabled': apmConfig.ui.enabled, 'xpack.apm.ui.maxTraceItems': apmConfig.ui.maxTraceItems, 'xpack.apm.ui.transactionGroupBucketSize': apmConfig.ui.transactionGroupBucketSize, diff --git a/x-pack/plugins/apm/server/plugin.ts b/x-pack/plugins/apm/server/plugin.ts index a1cf2ae4e8ead..11d91efdf6b01 100644 --- a/x-pack/plugins/apm/server/plugin.ts +++ b/x-pack/plugins/apm/server/plugin.ts @@ -58,16 +58,14 @@ export class APMPlugin implements Plugin { }); await new Promise(resolve => { - combineLatest(mergedConfig$, core.elasticsearch.dataClient$).subscribe( - async ([config, dataClient]) => { - this.currentConfig = config; - await createApmAgentConfigurationIndex({ - esClient: dataClient, - config, - }); - resolve(); - } - ); + mergedConfig$.subscribe(async config => { + this.currentConfig = config; + await createApmAgentConfigurationIndex({ + esClient: core.elasticsearch.dataClient, + config, + }); + resolve(); + }); }); return { diff --git a/x-pack/plugins/canvas/server/routes/es_fields/es_fields.test.ts b/x-pack/plugins/canvas/server/routes/es_fields/es_fields.test.ts new file mode 100644 index 0000000000000..c96856d09256b --- /dev/null +++ b/x-pack/plugins/canvas/server/routes/es_fields/es_fields.test.ts @@ -0,0 +1,164 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { initializeESFieldsRoute } from './es_fields'; +import { + IRouter, + kibanaResponseFactory, + RequestHandlerContext, + RequestHandler, +} from 'src/core/server'; +import { + httpServiceMock, + httpServerMock, + loggingServiceMock, + elasticsearchServiceMock, +} from 'src/core/server/mocks'; + +const mockRouteContext = ({ + core: { + elasticsearch: { dataClient: elasticsearchServiceMock.createScopedClusterClient() }, + }, +} as unknown) as RequestHandlerContext; + +const path = `api/canvas/workpad/find`; + +describe('Retrieve ES Fields', () => { + let routeHandler: RequestHandler; + + beforeEach(() => { + const httpService = httpServiceMock.createSetupContract(); + const router = httpService.createRouter('') as jest.Mocked; + initializeESFieldsRoute({ + router, + logger: loggingServiceMock.create().get(), + }); + + routeHandler = router.get.mock.calls[0][1]; + }); + + it(`returns 200 with fields from existing index/index pattern`, async () => { + const index = 'test'; + const mockResults = { + indices: ['test'], + fields: { + '@timestamp': { + date: { + type: 'date', + searchable: true, + aggregatable: true, + }, + }, + name: { + text: { + type: 'text', + searchable: true, + aggregatable: false, + }, + }, + products: { + object: { + type: 'object', + searchable: false, + aggregatable: false, + }, + }, + }, + }; + const request = httpServerMock.createKibanaRequest({ + method: 'get', + path, + query: { + index, + }, + }); + + const callAsCurrentUserMock = mockRouteContext.core.elasticsearch.dataClient + .callAsCurrentUser as jest.Mock; + + callAsCurrentUserMock.mockResolvedValueOnce(mockResults); + + const response = await routeHandler(mockRouteContext, request, kibanaResponseFactory); + + expect(response.status).toBe(200); + expect(response.payload).toMatchInlineSnapshot(` + Object { + "@timestamp": "date", + "name": "string", + "products": "unsupported", + } + `); + }); + + it(`returns 200 with empty object when index/index pattern has no fields`, async () => { + const index = 'test'; + const mockResults = { indices: [index], fields: {} }; + const request = httpServerMock.createKibanaRequest({ + method: 'get', + path, + query: { + index, + }, + }); + + const callAsCurrentUserMock = mockRouteContext.core.elasticsearch.dataClient + .callAsCurrentUser as jest.Mock; + + callAsCurrentUserMock.mockResolvedValueOnce(mockResults); + + const response = await routeHandler(mockRouteContext, request, kibanaResponseFactory); + + expect(response.status).toBe(200); + expect(response.payload).toMatchInlineSnapshot('Object {}'); + }); + + it(`returns 200 with empty object when index/index pattern does not have specified field(s)`, async () => { + const index = 'test'; + + const mockResults = { + indices: [index], + fields: {}, + }; + + const request = httpServerMock.createKibanaRequest({ + method: 'get', + path, + query: { + index, + fields: ['foo', 'bar'], + }, + }); + + const callAsCurrentUserMock = mockRouteContext.core.elasticsearch.dataClient + .callAsCurrentUser as jest.Mock; + + callAsCurrentUserMock.mockResolvedValueOnce(mockResults); + + const response = await routeHandler(mockRouteContext, request, kibanaResponseFactory); + + expect(response.status).toBe(200); + expect(response.payload).toMatchInlineSnapshot(`Object {}`); + }); + + it(`returns 500 when index does not exist`, async () => { + const request = httpServerMock.createKibanaRequest({ + method: 'get', + path, + query: { + index: 'foo', + }, + }); + + const callAsCurrentUserMock = mockRouteContext.core.elasticsearch.dataClient + .callAsCurrentUser as jest.Mock; + + callAsCurrentUserMock.mockRejectedValueOnce(new Error('Index not found')); + + const response = await routeHandler(mockRouteContext, request, kibanaResponseFactory); + + expect(response.status).toBe(500); + }); +}); diff --git a/x-pack/plugins/canvas/server/routes/es_fields/es_fields.ts b/x-pack/plugins/canvas/server/routes/es_fields/es_fields.ts new file mode 100644 index 0000000000000..b82f84b931d73 --- /dev/null +++ b/x-pack/plugins/canvas/server/routes/es_fields/es_fields.ts @@ -0,0 +1,58 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { mapValues, keys } from 'lodash'; +import { schema } from '@kbn/config-schema'; +import { API_ROUTE } from '../../../../../legacy/plugins/canvas/common/lib'; +import { catchErrorHandler } from '../catch_error_handler'; +// @ts-ignore unconverted lib +import { normalizeType } from '../../../../../legacy/plugins/canvas/server/lib/normalize_type'; +import { RouteInitializerDeps } from '..'; + +const ESFieldsRequestSchema = schema.object({ + index: schema.string(), + fields: schema.maybe(schema.arrayOf(schema.string())), +}); + +export function initializeESFieldsRoute(deps: RouteInitializerDeps) { + const { router } = deps; + + router.get( + { + path: `${API_ROUTE}/es_fields`, + validate: { + query: ESFieldsRequestSchema, + }, + }, + catchErrorHandler(async (context, request, response) => { + const { callAsCurrentUser } = context.core.elasticsearch.dataClient; + const { index, fields } = request.query; + + const config = { + index, + fields: fields || '*', + }; + + const esFields = await callAsCurrentUser('fieldCaps', config).then(resp => { + return mapValues(resp.fields, types => { + if (keys(types).length > 1) { + return 'conflict'; + } + + try { + return normalizeType(keys(types)[0]); + } catch (e) { + return 'unsupported'; + } + }); + }); + + return response.ok({ + body: esFields, + }); + }) + ); +} diff --git a/x-pack/plugins/canvas/server/routes/es_fields/index.ts b/x-pack/plugins/canvas/server/routes/es_fields/index.ts new file mode 100644 index 0000000000000..fa44f09747d6c --- /dev/null +++ b/x-pack/plugins/canvas/server/routes/es_fields/index.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { initializeESFieldsRoute } from './es_fields'; +import { RouteInitializerDeps } from '..'; + +export function initESFieldsRoutes(deps: RouteInitializerDeps) { + initializeESFieldsRoute(deps); +} diff --git a/x-pack/plugins/canvas/server/routes/index.ts b/x-pack/plugins/canvas/server/routes/index.ts index 8b2d77d634760..e9afab5680332 100644 --- a/x-pack/plugins/canvas/server/routes/index.ts +++ b/x-pack/plugins/canvas/server/routes/index.ts @@ -7,6 +7,7 @@ import { IRouter, Logger } from 'src/core/server'; import { initWorkpadRoutes } from './workpad'; import { initCustomElementsRoutes } from './custom_elements'; +import { initESFieldsRoutes } from './es_fields'; export interface RouteInitializerDeps { router: IRouter; @@ -16,4 +17,5 @@ export interface RouteInitializerDeps { export function initRoutes(deps: RouteInitializerDeps) { initWorkpadRoutes(deps); initCustomElementsRoutes(deps); + initESFieldsRoutes(deps); } diff --git a/x-pack/plugins/canvas/server/routes/workpad/workpad_schema.ts b/x-pack/plugins/canvas/server/routes/workpad/workpad_schema.ts index 0bcb161575901..0c31f517a74b3 100644 --- a/x-pack/plugins/canvas/server/routes/workpad/workpad_schema.ts +++ b/x-pack/plugins/canvas/server/routes/workpad/workpad_schema.ts @@ -24,11 +24,13 @@ export const WorkpadElementSchema = schema.object({ export const WorkpadPageSchema = schema.object({ elements: schema.arrayOf(WorkpadElementSchema), - groups: schema.arrayOf( - schema.object({ - id: schema.string(), - position: PositionSchema, - }) + groups: schema.maybe( + schema.arrayOf( + schema.object({ + id: schema.string(), + position: PositionSchema, + }) + ) ), id: schema.string(), style: schema.recordOf(schema.string(), schema.string()), diff --git a/x-pack/plugins/case/README.md b/x-pack/plugins/case/README.md new file mode 100644 index 0000000000000..c0acb87835207 --- /dev/null +++ b/x-pack/plugins/case/README.md @@ -0,0 +1,9 @@ +# Case Workflow + +*Experimental Feature* + +Elastic is developing a Case Management Workflow. Follow our progress: + +- [Case API Documentation](https://documenter.getpostman.com/view/172706/SW7c2SuF?version=latest) +- [Github Meta](https://github.com/elastic/kibana/issues/50103) + diff --git a/x-pack/plugins/case/kibana.json b/x-pack/plugins/case/kibana.json new file mode 100644 index 0000000000000..23e3cc789ad3b --- /dev/null +++ b/x-pack/plugins/case/kibana.json @@ -0,0 +1,9 @@ +{ + "configPath": ["xpack", "case"], + "id": "case", + "kibanaVersion": "kibana", + "requiredPlugins": ["security"], + "server": true, + "ui": false, + "version": "8.0.0" +} diff --git a/x-pack/plugins/case/server/config.ts b/x-pack/plugins/case/server/config.ts new file mode 100644 index 0000000000000..a7cb117198f9b --- /dev/null +++ b/x-pack/plugins/case/server/config.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { schema, TypeOf } from '@kbn/config-schema'; + +export const ConfigSchema = schema.object({ + enabled: schema.boolean({ defaultValue: false }), + indexPattern: schema.string({ defaultValue: '.case-test-2' }), + secret: schema.string({ defaultValue: 'Cool secret huh?' }), +}); + +export type ConfigType = TypeOf; diff --git a/x-pack/plugins/case/server/constants.ts b/x-pack/plugins/case/server/constants.ts new file mode 100644 index 0000000000000..276dcd135254a --- /dev/null +++ b/x-pack/plugins/case/server/constants.ts @@ -0,0 +1,8 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const CASE_SAVED_OBJECT = 'case-workflow'; +export const CASE_COMMENT_SAVED_OBJECT = 'case-workflow-comment'; diff --git a/x-pack/plugins/case/server/index.ts b/x-pack/plugins/case/server/index.ts new file mode 100644 index 0000000000000..3963debea9795 --- /dev/null +++ b/x-pack/plugins/case/server/index.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { PluginInitializerContext } from '../../../../src/core/server'; +import { ConfigSchema } from './config'; +import { CasePlugin } from './plugin'; +export { NewCaseFormatted, NewCommentFormatted } from './routes/api/types'; + +export const config = { schema: ConfigSchema }; +export const plugin = (initializerContext: PluginInitializerContext) => + new CasePlugin(initializerContext); diff --git a/x-pack/plugins/case/server/plugin.ts b/x-pack/plugins/case/server/plugin.ts new file mode 100644 index 0000000000000..c52461cade058 --- /dev/null +++ b/x-pack/plugins/case/server/plugin.ts @@ -0,0 +1,63 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { first, map } from 'rxjs/operators'; +import { CoreSetup, Logger, PluginInitializerContext } from 'kibana/server'; +import { ConfigType } from './config'; +import { initCaseApi } from './routes/api'; +import { CaseService } from './services'; +import { PluginSetupContract as SecurityPluginSetup } from '../../security/server'; + +function createConfig$(context: PluginInitializerContext) { + return context.config.create().pipe(map(config => config)); +} + +export interface PluginsSetup { + security: SecurityPluginSetup; +} + +export class CasePlugin { + private readonly log: Logger; + + constructor(private readonly initializerContext: PluginInitializerContext) { + this.log = this.initializerContext.logger.get(); + } + + public async setup(core: CoreSetup, plugins: PluginsSetup) { + const config = await createConfig$(this.initializerContext) + .pipe(first()) + .toPromise(); + + if (!config.enabled) { + return; + } + const service = new CaseService(this.log); + + this.log.debug( + `Setting up Case Workflow with core contract [${Object.keys( + core + )}] and plugins [${Object.keys(plugins)}]` + ); + + const caseService = await service.setup({ + authentication: plugins.security.authc, + }); + + const router = core.http.createRouter(); + initCaseApi({ + caseService, + router, + }); + } + + public start() { + this.log.debug(`Starting Case Workflow`); + } + + public stop() { + this.log.debug(`Stopping Case Workflow`); + } +} diff --git a/x-pack/plugins/case/server/routes/api/__fixtures__/authc_mock.ts b/x-pack/plugins/case/server/routes/api/__fixtures__/authc_mock.ts new file mode 100644 index 0000000000000..94ce9627b9ac6 --- /dev/null +++ b/x-pack/plugins/case/server/routes/api/__fixtures__/authc_mock.ts @@ -0,0 +1,35 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { Authentication } from '../../../../../security/server'; + +const getCurrentUser = jest.fn().mockReturnValue({ + username: 'awesome', + full_name: 'Awesome D00d', +}); +const getCurrentUserThrow = jest.fn().mockImplementation(() => { + throw new Error('Bad User - the user is not authenticated'); +}); + +export const authenticationMock = { + create: (): jest.Mocked => ({ + login: jest.fn(), + createAPIKey: jest.fn(), + getCurrentUser, + invalidateAPIKey: jest.fn(), + isAuthenticated: jest.fn(), + logout: jest.fn(), + getSessionInfo: jest.fn(), + }), + createInvalid: (): jest.Mocked => ({ + login: jest.fn(), + createAPIKey: jest.fn(), + getCurrentUser: getCurrentUserThrow, + invalidateAPIKey: jest.fn(), + isAuthenticated: jest.fn(), + logout: jest.fn(), + getSessionInfo: jest.fn(), + }), +}; diff --git a/x-pack/plugins/case/server/routes/api/__fixtures__/create_mock_so_repository.ts b/x-pack/plugins/case/server/routes/api/__fixtures__/create_mock_so_repository.ts new file mode 100644 index 0000000000000..360c6de67b2a8 --- /dev/null +++ b/x-pack/plugins/case/server/routes/api/__fixtures__/create_mock_so_repository.ts @@ -0,0 +1,77 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { SavedObjectsClientContract, SavedObjectsErrorHelpers } from 'src/core/server'; +import { CASE_COMMENT_SAVED_OBJECT } from '../../../constants'; + +export const createMockSavedObjectsRepository = (savedObject: any[] = []) => { + const mockSavedObjectsClientContract = ({ + get: jest.fn((type, id) => { + const result = savedObject.filter(s => s.id === id); + if (!result.length) { + throw SavedObjectsErrorHelpers.createGenericNotFoundError(type, id); + } + return result[0]; + }), + find: jest.fn(findArgs => { + if (findArgs.hasReference && findArgs.hasReference.id === 'bad-guy') { + throw SavedObjectsErrorHelpers.createBadRequestError('Error thrown for testing'); + } + return { + total: savedObject.length, + saved_objects: savedObject, + }; + }), + create: jest.fn((type, attributes, references) => { + if (attributes.description === 'Throw an error' || attributes.comment === 'Throw an error') { + throw SavedObjectsErrorHelpers.createBadRequestError('Error thrown for testing'); + } + if (type === CASE_COMMENT_SAVED_OBJECT) { + return { + type, + id: 'mock-comment', + attributes, + ...references, + updated_at: '2019-12-02T22:48:08.327Z', + version: 'WzksMV0=', + }; + } + return { + type, + id: 'mock-it', + attributes, + references: [], + updated_at: '2019-12-02T22:48:08.327Z', + version: 'WzksMV0=', + }; + }), + update: jest.fn((type, id, attributes) => { + if (!savedObject.find(s => s.id === id)) { + throw SavedObjectsErrorHelpers.createGenericNotFoundError(type, id); + } + return { + id, + type, + updated_at: '2019-11-22T22:50:55.191Z', + version: 'WzE3LDFd', + attributes, + }; + }), + delete: jest.fn((type: string, id: string) => { + const result = savedObject.filter(s => s.id === id); + if (!result.length) { + throw SavedObjectsErrorHelpers.createGenericNotFoundError(type, id); + } + if (type === 'case-workflow-comment' && id === 'bad-guy') { + throw SavedObjectsErrorHelpers.createBadRequestError('Error thrown for testing'); + } + return {}; + }), + deleteByNamespace: jest.fn(), + } as unknown) as jest.Mocked; + + return mockSavedObjectsClientContract; +}; diff --git a/x-pack/plugins/case/server/routes/api/__fixtures__/index.ts b/x-pack/plugins/case/server/routes/api/__fixtures__/index.ts new file mode 100644 index 0000000000000..e1fec2d6b229c --- /dev/null +++ b/x-pack/plugins/case/server/routes/api/__fixtures__/index.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { mockCases, mockCasesErrorTriggerData, mockCaseComments } from './mock_saved_objects'; +export { createMockSavedObjectsRepository } from './create_mock_so_repository'; +export { createRouteContext } from './route_contexts'; +export { authenticationMock } from './authc_mock'; +export { createRoute } from './mock_router'; diff --git a/x-pack/plugins/case/server/routes/api/__fixtures__/mock_router.ts b/x-pack/plugins/case/server/routes/api/__fixtures__/mock_router.ts new file mode 100644 index 0000000000000..84889c3ac49be --- /dev/null +++ b/x-pack/plugins/case/server/routes/api/__fixtures__/mock_router.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IRouter } from 'kibana/server'; +import { loggingServiceMock, httpServiceMock } from '../../../../../../../src/core/server/mocks'; +import { CaseService } from '../../../services'; +import { authenticationMock } from '../__fixtures__'; +import { RouteDeps } from '../index'; + +export const createRoute = async ( + api: (deps: RouteDeps) => void, + method: 'get' | 'post' | 'delete', + badAuth = false +) => { + const httpService = httpServiceMock.createSetupContract(); + const router = httpService.createRouter('') as jest.Mocked; + + const log = loggingServiceMock.create().get('case'); + + const service = new CaseService(log); + const caseService = await service.setup({ + authentication: badAuth ? authenticationMock.createInvalid() : authenticationMock.create(), + }); + + api({ + router, + caseService, + }); + + return router[method].mock.calls[0][1]; +}; diff --git a/x-pack/plugins/case/server/routes/api/__fixtures__/mock_saved_objects.ts b/x-pack/plugins/case/server/routes/api/__fixtures__/mock_saved_objects.ts new file mode 100644 index 0000000000000..d59f0977e6993 --- /dev/null +++ b/x-pack/plugins/case/server/routes/api/__fixtures__/mock_saved_objects.ts @@ -0,0 +1,143 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export const mockCases = [ + { + type: 'case-workflow', + id: 'mock-id-1', + attributes: { + created_at: 1574718888885, + created_by: { + full_name: null, + username: 'elastic', + }, + description: 'This is a brand new case of a bad meanie defacing data', + title: 'Super Bad Security Issue', + state: 'open', + tags: ['defacement'], + case_type: 'security', + assignees: [], + }, + references: [], + updated_at: '2019-11-25T21:54:48.952Z', + version: 'WzAsMV0=', + }, + { + type: 'case-workflow', + id: 'mock-id-2', + attributes: { + created_at: 1574721120834, + created_by: { + full_name: null, + username: 'elastic', + }, + description: 'Oh no, a bad meanie destroying data!', + title: 'Damaging Data Destruction Detected', + state: 'open', + tags: ['Data Destruction'], + case_type: 'security', + assignees: [], + }, + references: [], + updated_at: '2019-11-25T22:32:00.900Z', + version: 'WzQsMV0=', + }, + { + type: 'case-workflow', + id: 'mock-id-3', + attributes: { + created_at: 1574721137881, + created_by: { + full_name: null, + username: 'elastic', + }, + description: 'Oh no, a bad meanie going LOLBins all over the place!', + title: 'Another bad one', + state: 'open', + tags: ['LOLBins'], + case_type: 'security', + assignees: [], + }, + references: [], + updated_at: '2019-11-25T22:32:17.947Z', + version: 'WzUsMV0=', + }, +]; + +export const mockCasesErrorTriggerData = [ + { + id: 'valid-id', + }, + { + id: 'bad-guy', + }, +]; + +export const mockCaseComments = [ + { + type: 'case-workflow-comment', + id: 'mock-comment-1', + attributes: { + comment: 'Wow, good luck catching that bad meanie!', + created_at: 1574718900112, + created_by: { + full_name: null, + username: 'elastic', + }, + }, + references: [ + { + type: 'case-workflow', + name: 'associated-case-workflow', + id: 'mock-id-1', + }, + ], + updated_at: '2019-11-25T21:55:00.177Z', + version: 'WzEsMV0=', + }, + { + type: 'case-workflow-comment', + id: 'mock-comment-2', + attributes: { + comment: 'Well I decided to update my comment. So what? Deal with it.', + created_at: 1574718902724, + created_by: { + full_name: null, + username: 'elastic', + }, + }, + references: [ + { + type: 'case-workflow', + name: 'associated-case-workflow', + id: 'mock-id-1', + }, + ], + updated_at: '2019-11-25T21:55:14.633Z', + version: 'WzMsMV0=', + }, + { + type: 'case-workflow-comment', + id: 'mock-comment-3', + attributes: { + comment: 'Wow, good luck catching that bad meanie!', + created_at: 1574721150542, + created_by: { + full_name: null, + username: 'elastic', + }, + }, + references: [ + { + type: 'case-workflow', + name: 'associated-case-workflow', + id: 'mock-id-3', + }, + ], + updated_at: '2019-11-25T22:32:30.608Z', + version: 'WzYsMV0=', + }, +]; diff --git a/x-pack/plugins/case/server/routes/api/__fixtures__/route_contexts.ts b/x-pack/plugins/case/server/routes/api/__fixtures__/route_contexts.ts new file mode 100644 index 0000000000000..b1881e394e796 --- /dev/null +++ b/x-pack/plugins/case/server/routes/api/__fixtures__/route_contexts.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { RequestHandlerContext } from 'src/core/server'; + +export const createRouteContext = (client: any) => { + return ({ + core: { + savedObjects: { + client, + }, + }, + } as unknown) as RequestHandlerContext; +}; diff --git a/x-pack/plugins/case/server/routes/api/__tests__/delete_case.test.ts b/x-pack/plugins/case/server/routes/api/__tests__/delete_case.test.ts new file mode 100644 index 0000000000000..9ea42ba42406b --- /dev/null +++ b/x-pack/plugins/case/server/routes/api/__tests__/delete_case.test.ts @@ -0,0 +1,83 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + createMockSavedObjectsRepository, + createRoute, + createRouteContext, + mockCases, + mockCasesErrorTriggerData, +} from '../__fixtures__'; +import { initDeleteCaseApi } from '../delete_case'; +import { kibanaResponseFactory, RequestHandler } from 'src/core/server'; +import { httpServerMock } from 'src/core/server/mocks'; + +describe('DELETE case', () => { + let routeHandler: RequestHandler; + beforeAll(async () => { + routeHandler = await createRoute(initDeleteCaseApi, 'delete'); + }); + it(`deletes the case. responds with 204`, async () => { + const request = httpServerMock.createKibanaRequest({ + path: '/api/cases/{id}', + method: 'delete', + params: { + id: 'mock-id-1', + }, + }); + + const theContext = createRouteContext(createMockSavedObjectsRepository(mockCases)); + + const response = await routeHandler(theContext, request, kibanaResponseFactory); + expect(response.status).toEqual(204); + }); + it(`returns an error when thrown from deleteCase service`, async () => { + const request = httpServerMock.createKibanaRequest({ + path: '/api/cases/{id}', + method: 'delete', + params: { + id: 'not-real', + }, + }); + + const theContext = createRouteContext(createMockSavedObjectsRepository(mockCases)); + + const response = await routeHandler(theContext, request, kibanaResponseFactory); + expect(response.status).toEqual(404); + }); + it(`returns an error when thrown from getAllCaseComments service`, async () => { + const request = httpServerMock.createKibanaRequest({ + path: '/api/cases/{id}', + method: 'delete', + params: { + id: 'bad-guy', + }, + }); + + const theContext = createRouteContext( + createMockSavedObjectsRepository(mockCasesErrorTriggerData) + ); + + const response = await routeHandler(theContext, request, kibanaResponseFactory); + expect(response.status).toEqual(400); + }); + it(`returns an error when thrown from deleteComment service`, async () => { + const request = httpServerMock.createKibanaRequest({ + path: '/api/cases/{id}', + method: 'delete', + params: { + id: 'valid-id', + }, + }); + + const theContext = createRouteContext( + createMockSavedObjectsRepository(mockCasesErrorTriggerData) + ); + + const response = await routeHandler(theContext, request, kibanaResponseFactory); + expect(response.status).toEqual(400); + }); +}); diff --git a/x-pack/plugins/case/server/routes/api/__tests__/delete_comment.test.ts b/x-pack/plugins/case/server/routes/api/__tests__/delete_comment.test.ts new file mode 100644 index 0000000000000..e50b3cbaa9c9a --- /dev/null +++ b/x-pack/plugins/case/server/routes/api/__tests__/delete_comment.test.ts @@ -0,0 +1,53 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + createMockSavedObjectsRepository, + createRoute, + createRouteContext, + mockCases, + mockCasesErrorTriggerData, +} from '../__fixtures__'; +import { initDeleteCommentApi } from '../delete_comment'; +import { kibanaResponseFactory, RequestHandler } from 'src/core/server'; +import { httpServerMock } from 'src/core/server/mocks'; + +describe('DELETE comment', () => { + let routeHandler: RequestHandler; + beforeAll(async () => { + routeHandler = await createRoute(initDeleteCommentApi, 'delete'); + }); + it(`deletes the comment. responds with 204`, async () => { + const request = httpServerMock.createKibanaRequest({ + path: '/api/cases/comments/{comment_id}', + method: 'delete', + params: { + comment_id: 'mock-id-1', + }, + }); + + const theContext = createRouteContext(createMockSavedObjectsRepository(mockCases)); + + const response = await routeHandler(theContext, request, kibanaResponseFactory); + expect(response.status).toEqual(204); + }); + it(`returns an error when thrown from deleteComment service`, async () => { + const request = httpServerMock.createKibanaRequest({ + path: '/api/cases/comments/{comment_id}', + method: 'delete', + params: { + comment_id: 'bad-guy', + }, + }); + + const theContext = createRouteContext( + createMockSavedObjectsRepository(mockCasesErrorTriggerData) + ); + + const response = await routeHandler(theContext, request, kibanaResponseFactory); + expect(response.status).toEqual(400); + }); +}); diff --git a/x-pack/plugins/case/server/routes/api/__tests__/get_all_cases.test.ts b/x-pack/plugins/case/server/routes/api/__tests__/get_all_cases.test.ts new file mode 100644 index 0000000000000..2f8a229c08f29 --- /dev/null +++ b/x-pack/plugins/case/server/routes/api/__tests__/get_all_cases.test.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + createMockSavedObjectsRepository, + createRoute, + createRouteContext, + mockCases, +} from '../__fixtures__'; +import { initGetAllCasesApi } from '../get_all_cases'; +import { kibanaResponseFactory, RequestHandler } from 'src/core/server'; +import { httpServerMock } from 'src/core/server/mocks'; + +describe('GET all cases', () => { + let routeHandler: RequestHandler; + beforeAll(async () => { + routeHandler = await createRoute(initGetAllCasesApi, 'get'); + }); + it(`returns the case without case comments when includeComments is false`, async () => { + const request = httpServerMock.createKibanaRequest({ + path: '/api/cases', + method: 'get', + }); + + const theContext = createRouteContext(createMockSavedObjectsRepository(mockCases)); + + const response = await routeHandler(theContext, request, kibanaResponseFactory); + expect(response.status).toEqual(200); + expect(response.payload.saved_objects).toHaveLength(3); + }); +}); diff --git a/x-pack/plugins/case/server/routes/api/__tests__/get_case.test.ts b/x-pack/plugins/case/server/routes/api/__tests__/get_case.test.ts new file mode 100644 index 0000000000000..3c5f8e52d1946 --- /dev/null +++ b/x-pack/plugins/case/server/routes/api/__tests__/get_case.test.ts @@ -0,0 +1,101 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + createMockSavedObjectsRepository, + createRoute, + createRouteContext, + mockCases, + mockCasesErrorTriggerData, +} from '../__fixtures__'; +import { initGetCaseApi } from '../get_case'; +import { kibanaResponseFactory, RequestHandler } from 'src/core/server'; +import { httpServerMock } from 'src/core/server/mocks'; + +describe('GET case', () => { + let routeHandler: RequestHandler; + beforeAll(async () => { + routeHandler = await createRoute(initGetCaseApi, 'get'); + }); + it(`returns the case without case comments when includeComments is false`, async () => { + const request = httpServerMock.createKibanaRequest({ + path: '/api/cases/{id}', + params: { + id: 'mock-id-1', + }, + method: 'get', + query: { + includeComments: false, + }, + }); + + const theContext = createRouteContext(createMockSavedObjectsRepository(mockCases)); + + const response = await routeHandler(theContext, request, kibanaResponseFactory); + + expect(response.status).toEqual(200); + expect(response.payload).toEqual(mockCases.find(s => s.id === 'mock-id-1')); + expect(response.payload.comments).toBeUndefined(); + }); + it(`returns an error when thrown from getCase`, async () => { + const request = httpServerMock.createKibanaRequest({ + path: '/api/cases/{id}', + params: { + id: 'abcdefg', + }, + method: 'get', + query: { + includeComments: false, + }, + }); + + const theContext = createRouteContext(createMockSavedObjectsRepository(mockCases)); + + const response = await routeHandler(theContext, request, kibanaResponseFactory); + + expect(response.status).toEqual(404); + expect(response.payload.isBoom).toEqual(true); + }); + it(`returns the case with case comments when includeComments is true`, async () => { + const request = httpServerMock.createKibanaRequest({ + path: '/api/cases/{id}', + params: { + id: 'mock-id-1', + }, + method: 'get', + query: { + includeComments: true, + }, + }); + + const theContext = createRouteContext(createMockSavedObjectsRepository(mockCases)); + + const response = await routeHandler(theContext, request, kibanaResponseFactory); + + expect(response.status).toEqual(200); + expect(response.payload.comments.saved_objects).toHaveLength(3); + }); + it(`returns an error when thrown from getAllCaseComments`, async () => { + const request = httpServerMock.createKibanaRequest({ + path: '/api/cases/{id}', + params: { + id: 'bad-guy', + }, + method: 'get', + query: { + includeComments: true, + }, + }); + + const theContext = createRouteContext( + createMockSavedObjectsRepository(mockCasesErrorTriggerData) + ); + + const response = await routeHandler(theContext, request, kibanaResponseFactory); + + expect(response.status).toEqual(400); + }); +}); diff --git a/x-pack/plugins/case/server/routes/api/__tests__/get_comment.test.ts b/x-pack/plugins/case/server/routes/api/__tests__/get_comment.test.ts new file mode 100644 index 0000000000000..9b6a1e435838b --- /dev/null +++ b/x-pack/plugins/case/server/routes/api/__tests__/get_comment.test.ts @@ -0,0 +1,51 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + createMockSavedObjectsRepository, + createRoute, + createRouteContext, + mockCaseComments, +} from '../__fixtures__'; +import { initGetCommentApi } from '../get_comment'; +import { kibanaResponseFactory, RequestHandler } from 'src/core/server'; +import { httpServerMock } from 'src/core/server/mocks'; + +describe('GET comment', () => { + let routeHandler: RequestHandler; + beforeAll(async () => { + routeHandler = await createRoute(initGetCommentApi, 'get'); + }); + it(`returns the comment`, async () => { + const request = httpServerMock.createKibanaRequest({ + path: '/api/cases/comments/{id}', + method: 'get', + params: { + id: 'mock-comment-1', + }, + }); + + const theContext = createRouteContext(createMockSavedObjectsRepository(mockCaseComments)); + + const response = await routeHandler(theContext, request, kibanaResponseFactory); + expect(response.status).toEqual(200); + expect(response.payload).toEqual(mockCaseComments.find(s => s.id === 'mock-comment-1')); + }); + it(`returns an error when getComment throws`, async () => { + const request = httpServerMock.createKibanaRequest({ + path: '/api/cases/comments/{id}', + method: 'get', + params: { + id: 'not-real', + }, + }); + + const theContext = createRouteContext(createMockSavedObjectsRepository(mockCaseComments)); + + const response = await routeHandler(theContext, request, kibanaResponseFactory); + expect(response.status).toEqual(404); + }); +}); diff --git a/x-pack/plugins/case/server/routes/api/__tests__/post_case.test.ts b/x-pack/plugins/case/server/routes/api/__tests__/post_case.test.ts new file mode 100644 index 0000000000000..bb688dde4c58f --- /dev/null +++ b/x-pack/plugins/case/server/routes/api/__tests__/post_case.test.ts @@ -0,0 +1,82 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + createMockSavedObjectsRepository, + createRoute, + createRouteContext, + mockCases, +} from '../__fixtures__'; +import { initPostCaseApi } from '../post_case'; +import { kibanaResponseFactory, RequestHandler } from 'src/core/server'; +import { httpServerMock } from 'src/core/server/mocks'; + +describe('POST cases', () => { + let routeHandler: RequestHandler; + beforeAll(async () => { + routeHandler = await createRoute(initPostCaseApi, 'post'); + }); + it(`Posts a new case`, async () => { + const request = httpServerMock.createKibanaRequest({ + path: '/api/cases', + method: 'post', + body: { + description: 'This is a brand new case of a bad meanie defacing data', + title: 'Super Bad Security Issue', + state: 'open', + tags: ['defacement'], + case_type: 'security', + }, + }); + + const theContext = createRouteContext(createMockSavedObjectsRepository(mockCases)); + + const response = await routeHandler(theContext, request, kibanaResponseFactory); + expect(response.status).toEqual(200); + expect(response.payload.id).toEqual('mock-it'); + expect(response.payload.attributes.created_by.username).toEqual('awesome'); + }); + it(`Returns an error if postNewCase throws`, async () => { + const request = httpServerMock.createKibanaRequest({ + path: '/api/cases', + method: 'post', + body: { + description: 'Throw an error', + title: 'Super Bad Security Issue', + state: 'open', + tags: ['error'], + case_type: 'security', + }, + }); + + const theContext = createRouteContext(createMockSavedObjectsRepository(mockCases)); + + const response = await routeHandler(theContext, request, kibanaResponseFactory); + expect(response.status).toEqual(400); + expect(response.payload.isBoom).toEqual(true); + }); + it(`Returns an error if user authentication throws`, async () => { + routeHandler = await createRoute(initPostCaseApi, 'post', true); + + const request = httpServerMock.createKibanaRequest({ + path: '/api/cases', + method: 'post', + body: { + description: 'This is a brand new case of a bad meanie defacing data', + title: 'Super Bad Security Issue', + state: 'open', + tags: ['defacement'], + case_type: 'security', + }, + }); + + const theContext = createRouteContext(createMockSavedObjectsRepository(mockCases)); + + const response = await routeHandler(theContext, request, kibanaResponseFactory); + expect(response.status).toEqual(500); + expect(response.payload.isBoom).toEqual(true); + }); +}); diff --git a/x-pack/plugins/case/server/routes/api/__tests__/post_comment.test.ts b/x-pack/plugins/case/server/routes/api/__tests__/post_comment.test.ts new file mode 100644 index 0000000000000..0c059b7f15ea4 --- /dev/null +++ b/x-pack/plugins/case/server/routes/api/__tests__/post_comment.test.ts @@ -0,0 +1,97 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + createMockSavedObjectsRepository, + createRoute, + createRouteContext, + mockCases, +} from '../__fixtures__'; +import { initPostCommentApi } from '../post_comment'; +import { kibanaResponseFactory, RequestHandler } from 'src/core/server'; +import { httpServerMock } from 'src/core/server/mocks'; + +describe('POST comment', () => { + let routeHandler: RequestHandler; + beforeAll(async () => { + routeHandler = await createRoute(initPostCommentApi, 'post'); + }); + it(`Posts a new comment`, async () => { + const request = httpServerMock.createKibanaRequest({ + path: '/api/cases/{id}/comment', + method: 'post', + params: { + id: 'mock-id-1', + }, + body: { + comment: 'Wow, good luck catching that bad meanie!', + }, + }); + + const theContext = createRouteContext(createMockSavedObjectsRepository(mockCases)); + + const response = await routeHandler(theContext, request, kibanaResponseFactory); + expect(response.status).toEqual(200); + expect(response.payload.id).toEqual('mock-comment'); + expect(response.payload.references[0].id).toEqual('mock-id-1'); + }); + it(`Returns an error if the case does not exist`, async () => { + const request = httpServerMock.createKibanaRequest({ + path: '/api/cases/{id}/comment', + method: 'post', + params: { + id: 'this-is-not-real', + }, + body: { + comment: 'Wow, good luck catching that bad meanie!', + }, + }); + + const theContext = createRouteContext(createMockSavedObjectsRepository(mockCases)); + + const response = await routeHandler(theContext, request, kibanaResponseFactory); + expect(response.status).toEqual(404); + expect(response.payload.isBoom).toEqual(true); + }); + it(`Returns an error if postNewCase throws`, async () => { + const request = httpServerMock.createKibanaRequest({ + path: '/api/cases/{id}/comment', + method: 'post', + params: { + id: 'mock-id-1', + }, + body: { + comment: 'Throw an error', + }, + }); + + const theContext = createRouteContext(createMockSavedObjectsRepository(mockCases)); + + const response = await routeHandler(theContext, request, kibanaResponseFactory); + expect(response.status).toEqual(400); + expect(response.payload.isBoom).toEqual(true); + }); + it(`Returns an error if user authentication throws`, async () => { + routeHandler = await createRoute(initPostCommentApi, 'post', true); + + const request = httpServerMock.createKibanaRequest({ + path: '/api/cases/{id}/comment', + method: 'post', + params: { + id: 'mock-id-1', + }, + body: { + comment: 'Wow, good luck catching that bad meanie!', + }, + }); + + const theContext = createRouteContext(createMockSavedObjectsRepository(mockCases)); + + const response = await routeHandler(theContext, request, kibanaResponseFactory); + expect(response.status).toEqual(500); + expect(response.payload.isBoom).toEqual(true); + }); +}); diff --git a/x-pack/plugins/case/server/routes/api/__tests__/update_case.test.ts b/x-pack/plugins/case/server/routes/api/__tests__/update_case.test.ts new file mode 100644 index 0000000000000..7ed478d2e7c01 --- /dev/null +++ b/x-pack/plugins/case/server/routes/api/__tests__/update_case.test.ts @@ -0,0 +1,59 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + createMockSavedObjectsRepository, + createRoute, + createRouteContext, + mockCases, +} from '../__fixtures__'; +import { initUpdateCaseApi } from '../update_case'; +import { kibanaResponseFactory, RequestHandler } from 'src/core/server'; +import { httpServerMock } from 'src/core/server/mocks'; + +describe('UPDATE case', () => { + let routeHandler: RequestHandler; + beforeAll(async () => { + routeHandler = await createRoute(initUpdateCaseApi, 'post'); + }); + it(`Updates a case`, async () => { + const request = httpServerMock.createKibanaRequest({ + path: '/api/cases/{id}', + method: 'post', + params: { + id: 'mock-id-1', + }, + body: { + state: 'closed', + }, + }); + + const theContext = createRouteContext(createMockSavedObjectsRepository(mockCases)); + + const response = await routeHandler(theContext, request, kibanaResponseFactory); + expect(response.status).toEqual(200); + expect(response.payload.id).toEqual('mock-id-1'); + expect(response.payload.attributes.state).toEqual('closed'); + }); + it(`Returns an error if updateCase throws`, async () => { + const request = httpServerMock.createKibanaRequest({ + path: '/api/cases/{id}', + method: 'post', + params: { + id: 'mock-id-does-not-exist', + }, + body: { + state: 'closed', + }, + }); + + const theContext = createRouteContext(createMockSavedObjectsRepository(mockCases)); + + const response = await routeHandler(theContext, request, kibanaResponseFactory); + expect(response.status).toEqual(404); + expect(response.payload.isBoom).toEqual(true); + }); +}); diff --git a/x-pack/plugins/case/server/routes/api/__tests__/update_comment.test.ts b/x-pack/plugins/case/server/routes/api/__tests__/update_comment.test.ts new file mode 100644 index 0000000000000..8aa84b45b7dbb --- /dev/null +++ b/x-pack/plugins/case/server/routes/api/__tests__/update_comment.test.ts @@ -0,0 +1,59 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + createMockSavedObjectsRepository, + createRoute, + createRouteContext, + mockCaseComments, +} from '../__fixtures__'; +import { initUpdateCommentApi } from '../update_comment'; +import { kibanaResponseFactory, RequestHandler } from 'src/core/server'; +import { httpServerMock } from 'src/core/server/mocks'; + +describe('UPDATE comment', () => { + let routeHandler: RequestHandler; + beforeAll(async () => { + routeHandler = await createRoute(initUpdateCommentApi, 'post'); + }); + it(`Updates a comment`, async () => { + const request = httpServerMock.createKibanaRequest({ + path: '/api/cases/comment/{id}', + method: 'post', + params: { + id: 'mock-comment-1', + }, + body: { + comment: 'Update my comment', + }, + }); + + const theContext = createRouteContext(createMockSavedObjectsRepository(mockCaseComments)); + + const response = await routeHandler(theContext, request, kibanaResponseFactory); + expect(response.status).toEqual(200); + expect(response.payload.id).toEqual('mock-comment-1'); + expect(response.payload.attributes.comment).toEqual('Update my comment'); + }); + it(`Returns an error if updateComment throws`, async () => { + const request = httpServerMock.createKibanaRequest({ + path: '/api/cases/comment/{id}', + method: 'post', + params: { + id: 'mock-comment-does-not-exist', + }, + body: { + comment: 'Update my comment', + }, + }); + + const theContext = createRouteContext(createMockSavedObjectsRepository(mockCaseComments)); + + const response = await routeHandler(theContext, request, kibanaResponseFactory); + expect(response.status).toEqual(404); + expect(response.payload.isBoom).toEqual(true); + }); +}); diff --git a/x-pack/plugins/case/server/routes/api/delete_case.ts b/x-pack/plugins/case/server/routes/api/delete_case.ts new file mode 100644 index 0000000000000..a5ae72b8b46ff --- /dev/null +++ b/x-pack/plugins/case/server/routes/api/delete_case.ts @@ -0,0 +1,56 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { schema } from '@kbn/config-schema'; +import { RouteDeps } from '.'; +import { wrapError } from './utils'; + +export function initDeleteCaseApi({ caseService, router }: RouteDeps) { + router.delete( + { + path: '/api/cases/{id}', + validate: { + params: schema.object({ + id: schema.string(), + }), + }, + }, + async (context, request, response) => { + let allCaseComments; + try { + await caseService.deleteCase({ + client: context.core.savedObjects.client, + caseId: request.params.id, + }); + } catch (error) { + return response.customError(wrapError(error)); + } + try { + allCaseComments = await caseService.getAllCaseComments({ + client: context.core.savedObjects.client, + caseId: request.params.id, + }); + } catch (error) { + return response.customError(wrapError(error)); + } + try { + if (allCaseComments.saved_objects.length > 0) { + await Promise.all( + allCaseComments.saved_objects.map(({ id }) => + caseService.deleteComment({ + client: context.core.savedObjects.client, + commentId: id, + }) + ) + ); + } + return response.noContent(); + } catch (error) { + return response.customError(wrapError(error)); + } + } + ); +} diff --git a/x-pack/plugins/case/server/routes/api/delete_comment.ts b/x-pack/plugins/case/server/routes/api/delete_comment.ts new file mode 100644 index 0000000000000..4a540dd9fd69f --- /dev/null +++ b/x-pack/plugins/case/server/routes/api/delete_comment.ts @@ -0,0 +1,34 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { schema } from '@kbn/config-schema'; +import { RouteDeps } from '.'; +import { wrapError } from './utils'; + +export function initDeleteCommentApi({ caseService, router }: RouteDeps) { + router.delete( + { + path: '/api/cases/comments/{comment_id}', + validate: { + params: schema.object({ + comment_id: schema.string(), + }), + }, + }, + async (context, request, response) => { + const client = context.core.savedObjects.client; + try { + await caseService.deleteComment({ + client, + commentId: request.params.comment_id, + }); + return response.noContent(); + } catch (error) { + return response.customError(wrapError(error)); + } + } + ); +} diff --git a/x-pack/plugins/case/server/routes/api/get_all_case_comments.ts b/x-pack/plugins/case/server/routes/api/get_all_case_comments.ts new file mode 100644 index 0000000000000..cc4956ead1bd7 --- /dev/null +++ b/x-pack/plugins/case/server/routes/api/get_all_case_comments.ts @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { schema } from '@kbn/config-schema'; +import { RouteDeps } from '.'; +import { wrapError } from './utils'; + +export function initGetAllCaseCommentsApi({ caseService, router }: RouteDeps) { + router.get( + { + path: '/api/cases/{id}/comments', + validate: { + params: schema.object({ + id: schema.string(), + }), + }, + }, + async (context, request, response) => { + try { + const theComments = await caseService.getAllCaseComments({ + client: context.core.savedObjects.client, + caseId: request.params.id, + }); + return response.ok({ body: theComments }); + } catch (error) { + return response.customError(wrapError(error)); + } + } + ); +} diff --git a/x-pack/plugins/case/server/routes/api/get_all_cases.ts b/x-pack/plugins/case/server/routes/api/get_all_cases.ts new file mode 100644 index 0000000000000..749a183dfe980 --- /dev/null +++ b/x-pack/plugins/case/server/routes/api/get_all_cases.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { RouteDeps } from '.'; +import { wrapError } from './utils'; + +export function initGetAllCasesApi({ caseService, router }: RouteDeps) { + router.get( + { + path: '/api/cases', + validate: false, + }, + async (context, request, response) => { + try { + const cases = await caseService.getAllCases({ + client: context.core.savedObjects.client, + }); + return response.ok({ body: cases }); + } catch (error) { + return response.customError(wrapError(error)); + } + } + ); +} diff --git a/x-pack/plugins/case/server/routes/api/get_case.ts b/x-pack/plugins/case/server/routes/api/get_case.ts new file mode 100644 index 0000000000000..6aad22a1ebf1b --- /dev/null +++ b/x-pack/plugins/case/server/routes/api/get_case.ts @@ -0,0 +1,49 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { schema } from '@kbn/config-schema'; +import { RouteDeps } from '.'; +import { wrapError } from './utils'; + +export function initGetCaseApi({ caseService, router }: RouteDeps) { + router.get( + { + path: '/api/cases/{id}', + validate: { + params: schema.object({ + id: schema.string(), + }), + query: schema.object({ + includeComments: schema.string({ defaultValue: 'true' }), + }), + }, + }, + async (context, request, response) => { + let theCase; + const includeComments = JSON.parse(request.query.includeComments); + try { + theCase = await caseService.getCase({ + client: context.core.savedObjects.client, + caseId: request.params.id, + }); + } catch (error) { + return response.customError(wrapError(error)); + } + if (!includeComments) { + return response.ok({ body: theCase }); + } + try { + const theComments = await caseService.getAllCaseComments({ + client: context.core.savedObjects.client, + caseId: request.params.id, + }); + return response.ok({ body: { ...theCase, comments: theComments } }); + } catch (error) { + return response.customError(wrapError(error)); + } + } + ); +} diff --git a/x-pack/plugins/case/server/routes/api/get_comment.ts b/x-pack/plugins/case/server/routes/api/get_comment.ts new file mode 100644 index 0000000000000..6fd507d89738d --- /dev/null +++ b/x-pack/plugins/case/server/routes/api/get_comment.ts @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { schema } from '@kbn/config-schema'; +import { RouteDeps } from '.'; +import { wrapError } from './utils'; + +export function initGetCommentApi({ caseService, router }: RouteDeps) { + router.get( + { + path: '/api/cases/comments/{id}', + validate: { + params: schema.object({ + id: schema.string(), + }), + }, + }, + async (context, request, response) => { + try { + const theComment = await caseService.getComment({ + client: context.core.savedObjects.client, + commentId: request.params.id, + }); + return response.ok({ body: theComment }); + } catch (error) { + return response.customError(wrapError(error)); + } + } + ); +} diff --git a/x-pack/plugins/case/server/routes/api/index.ts b/x-pack/plugins/case/server/routes/api/index.ts new file mode 100644 index 0000000000000..11ef91d539e87 --- /dev/null +++ b/x-pack/plugins/case/server/routes/api/index.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IRouter } from 'src/core/server'; +import { initDeleteCommentApi } from './delete_comment'; +import { initDeleteCaseApi } from './delete_case'; +import { initGetAllCaseCommentsApi } from './get_all_case_comments'; +import { initGetAllCasesApi } from './get_all_cases'; +import { initGetCaseApi } from './get_case'; +import { initGetCommentApi } from './get_comment'; +import { initPostCaseApi } from './post_case'; +import { initPostCommentApi } from './post_comment'; +import { initUpdateCaseApi } from './update_case'; +import { initUpdateCommentApi } from './update_comment'; +import { CaseServiceSetup } from '../../services'; + +export interface RouteDeps { + caseService: CaseServiceSetup; + router: IRouter; +} + +export function initCaseApi(deps: RouteDeps) { + initGetAllCaseCommentsApi(deps); + initGetAllCasesApi(deps); + initGetCaseApi(deps); + initGetCommentApi(deps); + initDeleteCaseApi(deps); + initDeleteCommentApi(deps); + initPostCaseApi(deps); + initPostCommentApi(deps); + initUpdateCaseApi(deps); + initUpdateCommentApi(deps); +} diff --git a/x-pack/plugins/case/server/routes/api/post_case.ts b/x-pack/plugins/case/server/routes/api/post_case.ts new file mode 100644 index 0000000000000..e5aa0a3548b48 --- /dev/null +++ b/x-pack/plugins/case/server/routes/api/post_case.ts @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { formatNewCase, wrapError } from './utils'; +import { NewCaseSchema } from './schema'; +import { RouteDeps } from '.'; + +export function initPostCaseApi({ caseService, router }: RouteDeps) { + router.post( + { + path: '/api/cases', + validate: { + body: NewCaseSchema, + }, + }, + async (context, request, response) => { + let createdBy; + try { + createdBy = await caseService.getUser({ request, response }); + } catch (error) { + return response.customError(wrapError(error)); + } + + try { + const newCase = await caseService.postNewCase({ + client: context.core.savedObjects.client, + attributes: formatNewCase(request.body, { + ...createdBy, + }), + }); + return response.ok({ body: newCase }); + } catch (error) { + return response.customError(wrapError(error)); + } + } + ); +} diff --git a/x-pack/plugins/case/server/routes/api/post_comment.ts b/x-pack/plugins/case/server/routes/api/post_comment.ts new file mode 100644 index 0000000000000..3f4592f5bb11f --- /dev/null +++ b/x-pack/plugins/case/server/routes/api/post_comment.ts @@ -0,0 +1,62 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { schema } from '@kbn/config-schema'; +import { formatNewComment, wrapError } from './utils'; +import { NewCommentSchema } from './schema'; +import { RouteDeps } from '.'; +import { CASE_SAVED_OBJECT } from '../../constants'; + +export function initPostCommentApi({ caseService, router }: RouteDeps) { + router.post( + { + path: '/api/cases/{id}/comment', + validate: { + params: schema.object({ + id: schema.string(), + }), + body: NewCommentSchema, + }, + }, + async (context, request, response) => { + let createdBy; + let newComment; + try { + await caseService.getCase({ + client: context.core.savedObjects.client, + caseId: request.params.id, + }); + } catch (error) { + return response.customError(wrapError(error)); + } + try { + createdBy = await caseService.getUser({ request, response }); + } catch (error) { + return response.customError(wrapError(error)); + } + try { + newComment = await caseService.postNewComment({ + client: context.core.savedObjects.client, + attributes: formatNewComment({ + newComment: request.body, + ...createdBy, + }), + references: [ + { + type: CASE_SAVED_OBJECT, + name: `associated-${CASE_SAVED_OBJECT}`, + id: request.params.id, + }, + ], + }); + + return response.ok({ body: newComment }); + } catch (error) { + return response.customError(wrapError(error)); + } + } + ); +} diff --git a/x-pack/plugins/case/server/routes/api/schema.ts b/x-pack/plugins/case/server/routes/api/schema.ts new file mode 100644 index 0000000000000..4a4a0c3a11e36 --- /dev/null +++ b/x-pack/plugins/case/server/routes/api/schema.ts @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { schema } from '@kbn/config-schema'; + +export const UserSchema = schema.object({ + username: schema.string(), + full_name: schema.maybe(schema.string()), +}); + +export const NewCommentSchema = schema.object({ + comment: schema.string(), +}); + +export const CommentSchema = schema.object({ + comment: schema.string(), + created_at: schema.number(), + created_by: UserSchema, +}); + +export const UpdatedCommentSchema = schema.object({ + comment: schema.string(), +}); + +export const NewCaseSchema = schema.object({ + assignees: schema.arrayOf(UserSchema, { defaultValue: [] }), + description: schema.string(), + title: schema.string(), + state: schema.oneOf([schema.literal('open'), schema.literal('closed')], { defaultValue: 'open' }), + tags: schema.arrayOf(schema.string(), { defaultValue: [] }), + case_type: schema.string(), +}); + +export const UpdatedCaseSchema = schema.object({ + assignees: schema.maybe(schema.arrayOf(UserSchema)), + description: schema.maybe(schema.string()), + title: schema.maybe(schema.string()), + state: schema.maybe(schema.oneOf([schema.literal('open'), schema.literal('closed')])), + tags: schema.maybe(schema.arrayOf(schema.string())), + case_type: schema.maybe(schema.string()), +}); diff --git a/x-pack/plugins/case/server/routes/api/types.ts b/x-pack/plugins/case/server/routes/api/types.ts new file mode 100644 index 0000000000000..d943e4e5fd7dd --- /dev/null +++ b/x-pack/plugins/case/server/routes/api/types.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { TypeOf } from '@kbn/config-schema'; +import { + CommentSchema, + NewCaseSchema, + NewCommentSchema, + UpdatedCaseSchema, + UpdatedCommentSchema, + UserSchema, +} from './schema'; + +export type NewCaseType = TypeOf; +export type NewCommentFormatted = TypeOf; +export type NewCommentType = TypeOf; +export type UpdatedCaseTyped = TypeOf; +export type UpdatedCommentType = TypeOf; +export type UserType = TypeOf; + +export interface NewCaseFormatted extends NewCaseType { + created_at: number; + created_by: UserType; +} + +export interface UpdatedCaseType { + assignees?: UpdatedCaseTyped['assignees']; + description?: UpdatedCaseTyped['description']; + title?: UpdatedCaseTyped['title']; + state?: UpdatedCaseTyped['state']; + tags?: UpdatedCaseTyped['tags']; + case_type?: UpdatedCaseTyped['case_type']; +} diff --git a/x-pack/plugins/case/server/routes/api/update_case.ts b/x-pack/plugins/case/server/routes/api/update_case.ts new file mode 100644 index 0000000000000..52c8cab0022dd --- /dev/null +++ b/x-pack/plugins/case/server/routes/api/update_case.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { schema } from '@kbn/config-schema'; +import { wrapError } from './utils'; +import { RouteDeps } from '.'; +import { UpdatedCaseSchema } from './schema'; + +export function initUpdateCaseApi({ caseService, router }: RouteDeps) { + router.post( + { + path: '/api/cases/{id}', + validate: { + params: schema.object({ + id: schema.string(), + }), + body: UpdatedCaseSchema, + }, + }, + async (context, request, response) => { + try { + const updatedCase = await caseService.updateCase({ + client: context.core.savedObjects.client, + caseId: request.params.id, + updatedAttributes: request.body, + }); + return response.ok({ body: updatedCase }); + } catch (error) { + return response.customError(wrapError(error)); + } + } + ); +} diff --git a/x-pack/plugins/case/server/routes/api/update_comment.ts b/x-pack/plugins/case/server/routes/api/update_comment.ts new file mode 100644 index 0000000000000..e1ee6029e8e4f --- /dev/null +++ b/x-pack/plugins/case/server/routes/api/update_comment.ts @@ -0,0 +1,36 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { schema } from '@kbn/config-schema'; +import { wrapError } from './utils'; +import { NewCommentSchema } from './schema'; +import { RouteDeps } from '.'; + +export function initUpdateCommentApi({ caseService, router }: RouteDeps) { + router.post( + { + path: '/api/cases/comment/{id}', + validate: { + params: schema.object({ + id: schema.string(), + }), + body: NewCommentSchema, + }, + }, + async (context, request, response) => { + try { + const updatedComment = await caseService.updateComment({ + client: context.core.savedObjects.client, + commentId: request.params.id, + updatedAttributes: request.body, + }); + return response.ok({ body: updatedComment }); + } catch (error) { + return response.customError(wrapError(error)); + } + } + ); +} diff --git a/x-pack/plugins/case/server/routes/api/utils.ts b/x-pack/plugins/case/server/routes/api/utils.ts new file mode 100644 index 0000000000000..c6e33dbb8433b --- /dev/null +++ b/x-pack/plugins/case/server/routes/api/utils.ts @@ -0,0 +1,48 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { boomify, isBoom } from 'boom'; +import { CustomHttpResponseOptions, ResponseError } from 'kibana/server'; +import { + NewCaseType, + NewCaseFormatted, + NewCommentType, + NewCommentFormatted, + UserType, +} from './types'; + +export const formatNewCase = ( + newCase: NewCaseType, + { full_name, username }: { full_name?: string; username: string } +): NewCaseFormatted => ({ + created_at: new Date().valueOf(), + created_by: { full_name, username }, + ...newCase, +}); + +interface NewCommentArgs { + newComment: NewCommentType; + full_name?: UserType['full_name']; + username: UserType['username']; +} +export const formatNewComment = ({ + newComment, + full_name, + username, +}: NewCommentArgs): NewCommentFormatted => ({ + ...newComment, + created_at: new Date().valueOf(), + created_by: { full_name, username }, +}); + +export function wrapError(error: any): CustomHttpResponseOptions { + const boom = isBoom(error) ? error : boomify(error); + return { + body: boom, + headers: boom.output.headers, + statusCode: boom.output.statusCode, + }; +} diff --git a/x-pack/plugins/case/server/services/index.ts b/x-pack/plugins/case/server/services/index.ts new file mode 100644 index 0000000000000..684d905a5c71f --- /dev/null +++ b/x-pack/plugins/case/server/services/index.ts @@ -0,0 +1,192 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + KibanaRequest, + KibanaResponseFactory, + Logger, + SavedObject, + SavedObjectsClientContract, + SavedObjectsFindResponse, + SavedObjectsUpdateResponse, + SavedObjectReference, +} from 'kibana/server'; +import { CASE_COMMENT_SAVED_OBJECT, CASE_SAVED_OBJECT } from '../constants'; +import { + NewCaseFormatted, + NewCommentFormatted, + UpdatedCaseType, + UpdatedCommentType, +} from '../routes/api/types'; +import { + AuthenticatedUser, + PluginSetupContract as SecurityPluginSetup, +} from '../../../security/server'; + +interface ClientArgs { + client: SavedObjectsClientContract; +} + +interface GetCaseArgs extends ClientArgs { + caseId: string; +} +interface GetCommentArgs extends ClientArgs { + commentId: string; +} +interface PostCaseArgs extends ClientArgs { + attributes: NewCaseFormatted; +} + +interface PostCommentArgs extends ClientArgs { + attributes: NewCommentFormatted; + references: SavedObjectReference[]; +} +interface UpdateCaseArgs extends ClientArgs { + caseId: string; + updatedAttributes: UpdatedCaseType; +} +interface UpdateCommentArgs extends ClientArgs { + commentId: string; + updatedAttributes: UpdatedCommentType; +} + +interface GetUserArgs { + request: KibanaRequest; + response: KibanaResponseFactory; +} + +interface CaseServiceDeps { + authentication: SecurityPluginSetup['authc']; +} +export interface CaseServiceSetup { + deleteCase(args: GetCaseArgs): Promise<{}>; + deleteComment(args: GetCommentArgs): Promise<{}>; + getAllCases(args: ClientArgs): Promise; + getAllCaseComments(args: GetCaseArgs): Promise; + getCase(args: GetCaseArgs): Promise; + getComment(args: GetCommentArgs): Promise; + getUser(args: GetUserArgs): Promise; + postNewCase(args: PostCaseArgs): Promise; + postNewComment(args: PostCommentArgs): Promise; + updateCase(args: UpdateCaseArgs): Promise; + updateComment(args: UpdateCommentArgs): Promise; +} + +export class CaseService { + constructor(private readonly log: Logger) {} + public setup = async ({ authentication }: CaseServiceDeps): Promise => ({ + deleteCase: async ({ client, caseId }: GetCaseArgs) => { + try { + this.log.debug(`Attempting to GET case ${caseId}`); + return await client.delete(CASE_SAVED_OBJECT, caseId); + } catch (error) { + this.log.debug(`Error on GET case ${caseId}: ${error}`); + throw error; + } + }, + deleteComment: async ({ client, commentId }: GetCommentArgs) => { + try { + this.log.debug(`Attempting to GET comment ${commentId}`); + return await client.delete(CASE_COMMENT_SAVED_OBJECT, commentId); + } catch (error) { + this.log.debug(`Error on GET comment ${commentId}: ${error}`); + throw error; + } + }, + getCase: async ({ client, caseId }: GetCaseArgs) => { + try { + this.log.debug(`Attempting to GET case ${caseId}`); + return await client.get(CASE_SAVED_OBJECT, caseId); + } catch (error) { + this.log.debug(`Error on GET case ${caseId}: ${error}`); + throw error; + } + }, + getComment: async ({ client, commentId }: GetCommentArgs) => { + try { + this.log.debug(`Attempting to GET comment ${commentId}`); + return await client.get(CASE_COMMENT_SAVED_OBJECT, commentId); + } catch (error) { + this.log.debug(`Error on GET comment ${commentId}: ${error}`); + throw error; + } + }, + getAllCases: async ({ client }: ClientArgs) => { + try { + this.log.debug(`Attempting to GET all cases`); + return await client.find({ type: CASE_SAVED_OBJECT }); + } catch (error) { + this.log.debug(`Error on GET cases: ${error}`); + throw error; + } + }, + getAllCaseComments: async ({ client, caseId }: GetCaseArgs) => { + try { + this.log.debug(`Attempting to GET all comments for case ${caseId}`); + return await client.find({ + type: CASE_COMMENT_SAVED_OBJECT, + hasReference: { type: CASE_SAVED_OBJECT, id: caseId }, + }); + } catch (error) { + this.log.debug(`Error on GET all comments for case ${caseId}: ${error}`); + throw error; + } + }, + getUser: async ({ request, response }: GetUserArgs) => { + let user; + try { + this.log.debug(`Attempting to authenticate a user`); + user = await authentication!.getCurrentUser(request); + } catch (error) { + this.log.debug(`Error on GET user: ${error}`); + throw error; + } + if (!user) { + this.log.debug(`Error on GET user: Bad User`); + throw new Error('Bad User - the user is not authenticated'); + } + return user; + }, + postNewCase: async ({ client, attributes }: PostCaseArgs) => { + try { + this.log.debug(`Attempting to POST a new case`); + return await client.create(CASE_SAVED_OBJECT, { ...attributes }); + } catch (error) { + this.log.debug(`Error on POST a new case: ${error}`); + throw error; + } + }, + postNewComment: async ({ client, attributes, references }: PostCommentArgs) => { + try { + this.log.debug(`Attempting to POST a new comment`); + return await client.create(CASE_COMMENT_SAVED_OBJECT, attributes, { references }); + } catch (error) { + this.log.debug(`Error on POST a new comment: ${error}`); + throw error; + } + }, + updateCase: async ({ client, caseId, updatedAttributes }: UpdateCaseArgs) => { + try { + this.log.debug(`Attempting to UPDATE case ${caseId}`); + return await client.update(CASE_SAVED_OBJECT, caseId, { ...updatedAttributes }); + } catch (error) { + this.log.debug(`Error on UPDATE case ${caseId}: ${error}`); + throw error; + } + }, + updateComment: async ({ client, commentId, updatedAttributes }: UpdateCommentArgs) => { + try { + this.log.debug(`Attempting to UPDATE comment ${commentId}`); + return await client.update(CASE_COMMENT_SAVED_OBJECT, commentId, { + ...updatedAttributes, + }); + } catch (error) { + this.log.debug(`Error on UPDATE comment ${commentId}: ${error}`); + throw error; + } + }, + }); +} diff --git a/x-pack/plugins/endpoint/server/config.test.ts b/x-pack/plugins/endpoint/server/config.test.ts new file mode 100644 index 0000000000000..39f6bca2d43ca --- /dev/null +++ b/x-pack/plugins/endpoint/server/config.test.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { EndpointConfigSchema, EndpointConfigType } from './config'; + +describe('test config schema', () => { + it('test config defaults', () => { + const config: EndpointConfigType = EndpointConfigSchema.validate({}); + expect(config.enabled).toEqual(false); + expect(config.endpointResultListDefaultPageSize).toEqual(10); + expect(config.endpointResultListDefaultFirstPageIndex).toEqual(0); + }); +}); diff --git a/x-pack/plugins/endpoint/server/config.ts b/x-pack/plugins/endpoint/server/config.ts new file mode 100644 index 0000000000000..3f9a8a5508dd8 --- /dev/null +++ b/x-pack/plugins/endpoint/server/config.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { schema, TypeOf } from '@kbn/config-schema'; +import { Observable } from 'rxjs'; +import { PluginInitializerContext } from 'kibana/server'; + +export type EndpointConfigType = ReturnType extends Observable + ? P + : ReturnType; + +export const EndpointConfigSchema = schema.object({ + enabled: schema.boolean({ defaultValue: false }), + endpointResultListDefaultFirstPageIndex: schema.number({ defaultValue: 0 }), + endpointResultListDefaultPageSize: schema.number({ defaultValue: 10 }), +}); + +export function createConfig$(context: PluginInitializerContext) { + return context.config.create>(); +} diff --git a/x-pack/plugins/endpoint/server/index.ts b/x-pack/plugins/endpoint/server/index.ts index eec836141ea5e..ae603b7e44449 100644 --- a/x-pack/plugins/endpoint/server/index.ts +++ b/x-pack/plugins/endpoint/server/index.ts @@ -4,8 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ -import { schema } from '@kbn/config-schema'; -import { PluginInitializer } from 'src/core/server'; +import { PluginInitializer, PluginInitializerContext } from 'src/core/server'; import { EndpointPlugin, EndpointPluginStart, @@ -13,9 +12,10 @@ import { EndpointPluginStartDependencies, EndpointPluginSetupDependencies, } from './plugin'; +import { EndpointConfigSchema } from './config'; export const config = { - schema: schema.object({ enabled: schema.boolean({ defaultValue: false }) }), + schema: EndpointConfigSchema, }; export const plugin: PluginInitializer< @@ -23,4 +23,4 @@ export const plugin: PluginInitializer< EndpointPluginStart, EndpointPluginSetupDependencies, EndpointPluginStartDependencies -> = () => new EndpointPlugin(); +> = (initializerContext: PluginInitializerContext) => new EndpointPlugin(initializerContext); diff --git a/x-pack/plugins/endpoint/server/plugin.test.ts b/x-pack/plugins/endpoint/server/plugin.test.ts new file mode 100644 index 0000000000000..87d373d3a4f34 --- /dev/null +++ b/x-pack/plugins/endpoint/server/plugin.test.ts @@ -0,0 +1,39 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { CoreSetup } from 'kibana/server'; +import { EndpointPlugin, EndpointPluginSetupDependencies } from './plugin'; +import { coreMock } from '../../../../src/core/server/mocks'; +import { PluginSetupContract } from '../../features/server'; + +describe('test endpoint plugin', () => { + let plugin: EndpointPlugin; + let mockCoreSetup: MockedKeys; + let mockedEndpointPluginSetupDependencies: jest.Mocked; + let mockedPluginSetupContract: jest.Mocked; + beforeEach(() => { + plugin = new EndpointPlugin( + coreMock.createPluginInitializerContext({ + cookieName: 'sid', + sessionTimeout: 1500, + }) + ); + + mockCoreSetup = coreMock.createSetup(); + mockedPluginSetupContract = { + registerFeature: jest.fn(), + getFeatures: jest.fn(), + getFeaturesUICapabilities: jest.fn(), + registerLegacyAPI: jest.fn(), + }; + mockedEndpointPluginSetupDependencies = { features: mockedPluginSetupContract }; + }); + + it('test properly setup plugin', async () => { + await plugin.setup(mockCoreSetup, mockedEndpointPluginSetupDependencies); + expect(mockedPluginSetupContract.registerFeature).toBeCalledTimes(1); + expect(mockCoreSetup.http.createRouter).toBeCalledTimes(1); + }); +}); diff --git a/x-pack/plugins/endpoint/server/plugin.ts b/x-pack/plugins/endpoint/server/plugin.ts index b41dfee1f78fd..7ed116ba21140 100644 --- a/x-pack/plugins/endpoint/server/plugin.ts +++ b/x-pack/plugins/endpoint/server/plugin.ts @@ -3,9 +3,13 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ -import { Plugin, CoreSetup } from 'kibana/server'; +import { Plugin, CoreSetup, PluginInitializerContext, Logger } from 'kibana/server'; +import { first } from 'rxjs/operators'; import { addRoutes } from './routes'; import { PluginSetupContract as FeaturesPluginSetupContract } from '../../features/server'; +import { createConfig$, EndpointConfigType } from './config'; +import { EndpointAppContext } from './types'; +import { registerEndpointRoutes } from './routes/endpoints'; export type EndpointPluginStart = void; export type EndpointPluginSetup = void; @@ -23,6 +27,10 @@ export class EndpointPlugin EndpointPluginSetupDependencies, EndpointPluginStartDependencies > { + private readonly logger: Logger; + constructor(private readonly initializerContext: PluginInitializerContext) { + this.logger = this.initializerContext.logger.get('endpoint'); + } public setup(core: CoreSetup, plugins: EndpointPluginSetupDependencies) { plugins.features.registerFeature({ id: 'endpoint', @@ -49,10 +57,23 @@ export class EndpointPlugin }, }, }); + const endpointContext = { + logFactory: this.initializerContext.logger, + config: (): Promise => { + return createConfig$(this.initializerContext) + .pipe(first()) + .toPromise(); + }, + } as EndpointAppContext; const router = core.http.createRouter(); addRoutes(router); + registerEndpointRoutes(router, endpointContext); } - public start() {} - public stop() {} + public start() { + this.logger.debug('Starting plugin'); + } + public stop() { + this.logger.debug('Stopping plugin'); + } } diff --git a/x-pack/plugins/endpoint/server/routes/endpoints.test.ts b/x-pack/plugins/endpoint/server/routes/endpoints.test.ts new file mode 100644 index 0000000000000..60433f86b6f7e --- /dev/null +++ b/x-pack/plugins/endpoint/server/routes/endpoints.test.ts @@ -0,0 +1,123 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { + IClusterClient, + IRouter, + IScopedClusterClient, + KibanaResponseFactory, + RequestHandler, + RequestHandlerContext, + RouteConfig, +} from 'kibana/server'; +import { + elasticsearchServiceMock, + httpServerMock, + httpServiceMock, + loggingServiceMock, +} from '../../../../../src/core/server/mocks'; +import { EndpointData } from '../types'; +import { SearchResponse } from 'elasticsearch'; +import { EndpointResultList, registerEndpointRoutes } from './endpoints'; +import { EndpointConfigSchema } from '../config'; +import * as data from '../test_data/all_endpoints_data.json'; + +describe('test endpoint route', () => { + let routerMock: jest.Mocked; + let mockResponse: jest.Mocked; + let mockClusterClient: jest.Mocked; + let mockScopedClient: jest.Mocked; + let routeHandler: RequestHandler; + let routeConfig: RouteConfig; + + beforeEach(() => { + mockClusterClient = elasticsearchServiceMock.createClusterClient() as jest.Mocked< + IClusterClient + >; + mockScopedClient = elasticsearchServiceMock.createScopedClusterClient(); + mockClusterClient.asScoped.mockReturnValue(mockScopedClient); + routerMock = httpServiceMock.createRouter(); + mockResponse = httpServerMock.createResponseFactory(); + registerEndpointRoutes(routerMock, { + logFactory: loggingServiceMock.create(), + config: () => Promise.resolve(EndpointConfigSchema.validate({})), + }); + }); + + it('test find the latest of all endpoints', async () => { + const mockRequest = httpServerMock.createKibanaRequest({}); + + const response: SearchResponse = (data as unknown) as SearchResponse< + EndpointData + >; + mockScopedClient.callAsCurrentUser.mockImplementationOnce(() => Promise.resolve(response)); + [routeConfig, routeHandler] = routerMock.post.mock.calls.find(([{ path }]) => + path.startsWith('/api/endpoint/endpoints') + )!; + + await routeHandler( + ({ + core: { + elasticsearch: { + dataClient: mockScopedClient, + }, + }, + } as unknown) as RequestHandlerContext, + mockRequest, + mockResponse + ); + + expect(mockScopedClient.callAsCurrentUser).toBeCalled(); + expect(routeConfig.options).toEqual({ authRequired: true }); + expect(mockResponse.ok).toBeCalled(); + const endpointResultList = mockResponse.ok.mock.calls[0][0]?.body as EndpointResultList; + expect(endpointResultList.endpoints.length).toEqual(3); + expect(endpointResultList.total).toEqual(3); + expect(endpointResultList.request_index).toEqual(0); + expect(endpointResultList.request_page_size).toEqual(10); + }); + + it('test find the latest of all endpoints with params', async () => { + const mockRequest = httpServerMock.createKibanaRequest({ + body: { + paging_properties: [ + { + page_size: 10, + }, + { + page_index: 1, + }, + ], + }, + }); + mockScopedClient.callAsCurrentUser.mockImplementationOnce(() => + Promise.resolve((data as unknown) as SearchResponse) + ); + [routeConfig, routeHandler] = routerMock.post.mock.calls.find(([{ path }]) => + path.startsWith('/api/endpoint/endpoints') + )!; + + await routeHandler( + ({ + core: { + elasticsearch: { + dataClient: mockScopedClient, + }, + }, + } as unknown) as RequestHandlerContext, + mockRequest, + mockResponse + ); + + expect(mockScopedClient.callAsCurrentUser).toBeCalled(); + expect(routeConfig.options).toEqual({ authRequired: true }); + expect(mockResponse.ok).toBeCalled(); + const endpointResultList = mockResponse.ok.mock.calls[0][0]?.body as EndpointResultList; + expect(endpointResultList.endpoints.length).toEqual(3); + expect(endpointResultList.total).toEqual(3); + expect(endpointResultList.request_index).toEqual(10); + expect(endpointResultList.request_page_size).toEqual(10); + }); +}); diff --git a/x-pack/plugins/endpoint/server/routes/endpoints.ts b/x-pack/plugins/endpoint/server/routes/endpoints.ts new file mode 100644 index 0000000000000..9d2babc61f11f --- /dev/null +++ b/x-pack/plugins/endpoint/server/routes/endpoints.ts @@ -0,0 +1,88 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { IRouter } from 'kibana/server'; +import { SearchResponse } from 'elasticsearch'; +import { schema } from '@kbn/config-schema'; +import { EndpointAppContext, EndpointData } from '../types'; +import { kibanaRequestToEndpointListQuery } from '../services/endpoint/endpoint_query_builders'; + +interface HitSource { + _source: EndpointData; +} + +export interface EndpointResultList { + // the endpoint restricted by the page size + endpoints: EndpointData[]; + // the total number of unique endpoints in the index + total: number; + // the page size requested + request_page_size: number; + // the index requested + request_index: number; +} + +export function registerEndpointRoutes(router: IRouter, endpointAppContext: EndpointAppContext) { + router.post( + { + path: '/api/endpoint/endpoints', + validate: { + body: schema.nullable( + schema.object({ + paging_properties: schema.arrayOf( + schema.oneOf([ + // the number of results to return for this request per page + schema.object({ + page_size: schema.number({ defaultValue: 10, min: 1, max: 10000 }), + }), + // the index of the page to return + schema.object({ page_index: schema.number({ defaultValue: 0, min: 0 }) }), + ]) + ), + }) + ), + }, + options: { authRequired: true }, + }, + async (context, req, res) => { + try { + const queryParams = await kibanaRequestToEndpointListQuery(req, endpointAppContext); + const response = (await context.core.elasticsearch.dataClient.callAsCurrentUser( + 'search', + queryParams + )) as SearchResponse; + return res.ok({ body: mapToEndpointResultList(queryParams, response) }); + } catch (err) { + return res.internalError({ body: err }); + } + } + ); +} + +function mapToEndpointResultList( + queryParams: Record, + searchResponse: SearchResponse +): EndpointResultList { + const totalNumberOfEndpoints = searchResponse?.aggregations?.total?.value || 0; + if (searchResponse.hits.hits.length > 0) { + return { + request_page_size: queryParams.size, + request_index: queryParams.from, + endpoints: searchResponse.hits.hits + .map(response => response.inner_hits.most_recent.hits.hits) + .flatMap(data => data as HitSource) + .map(entry => entry._source), + total: totalNumberOfEndpoints, + }; + } else { + return { + request_page_size: queryParams.size, + request_index: queryParams.from, + total: totalNumberOfEndpoints, + endpoints: [], + }; + } +} diff --git a/x-pack/plugins/endpoint/server/services/endpoint/endpoint_query_builders.test.ts b/x-pack/plugins/endpoint/server/services/endpoint/endpoint_query_builders.test.ts new file mode 100644 index 0000000000000..2a8cecec16526 --- /dev/null +++ b/x-pack/plugins/endpoint/server/services/endpoint/endpoint_query_builders.test.ts @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { httpServerMock, loggingServiceMock } from '../../../../../../src/core/server/mocks'; +import { EndpointConfigSchema } from '../../config'; +import { kibanaRequestToEndpointListQuery } from './endpoint_query_builders'; + +describe('test query builder', () => { + describe('test query builder request processing', () => { + it('test default query params for all endpoints when no params or body is provided', async () => { + const mockRequest = httpServerMock.createKibanaRequest({ + body: {}, + }); + const query = await kibanaRequestToEndpointListQuery(mockRequest, { + logFactory: loggingServiceMock.create(), + config: () => Promise.resolve(EndpointConfigSchema.validate({})), + }); + expect(query).toEqual({ + body: { + query: { + match_all: {}, + }, + collapse: { + field: 'machine_id', + inner_hits: { + name: 'most_recent', + size: 1, + sort: [{ created_at: 'desc' }], + }, + }, + aggs: { + total: { + cardinality: { + field: 'machine_id', + }, + }, + }, + sort: [ + { + created_at: { + order: 'desc', + }, + }, + ], + }, + from: 0, + size: 10, + index: 'endpoint-agent*', + } as Record); + }); + }); +}); diff --git a/x-pack/plugins/endpoint/server/services/endpoint/endpoint_query_builders.ts b/x-pack/plugins/endpoint/server/services/endpoint/endpoint_query_builders.ts new file mode 100644 index 0000000000000..7430ba9721608 --- /dev/null +++ b/x-pack/plugins/endpoint/server/services/endpoint/endpoint_query_builders.ts @@ -0,0 +1,66 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { KibanaRequest } from 'kibana/server'; +import { EndpointAppConstants, EndpointAppContext } from '../../types'; + +export const kibanaRequestToEndpointListQuery = async ( + request: KibanaRequest, + endpointAppContext: EndpointAppContext +): Promise> => { + const pagingProperties = await getPagingProperties(request, endpointAppContext); + return { + body: { + query: { + match_all: {}, + }, + collapse: { + field: 'machine_id', + inner_hits: { + name: 'most_recent', + size: 1, + sort: [{ created_at: 'desc' }], + }, + }, + aggs: { + total: { + cardinality: { + field: 'machine_id', + }, + }, + }, + sort: [ + { + created_at: { + order: 'desc', + }, + }, + ], + }, + from: pagingProperties.pageIndex * pagingProperties.pageSize, + size: pagingProperties.pageSize, + index: EndpointAppConstants.ENDPOINT_INDEX_NAME, + }; +}; + +async function getPagingProperties( + request: KibanaRequest, + endpointAppContext: EndpointAppContext +) { + const config = await endpointAppContext.config(); + const pagingProperties: { page_size?: number; page_index?: number } = {}; + if (request?.body?.paging_properties) { + for (const property of request.body.paging_properties) { + Object.assign( + pagingProperties, + ...Object.keys(property).map(key => ({ [key]: property[key] })) + ); + } + } + return { + pageSize: pagingProperties.page_size || config.endpointResultListDefaultPageSize, + pageIndex: pagingProperties.page_index || config.endpointResultListDefaultFirstPageIndex, + }; +} diff --git a/x-pack/plugins/endpoint/server/test_data/all_endpoints_data.json b/x-pack/plugins/endpoint/server/test_data/all_endpoints_data.json new file mode 100644 index 0000000000000..d505b2c929828 --- /dev/null +++ b/x-pack/plugins/endpoint/server/test_data/all_endpoints_data.json @@ -0,0 +1,348 @@ +{ + "took": 3, + "timed_out": false, + "_shards": { + "total": 1, + "successful": 1, + "skipped": 0, + "failed": 0 + }, + "hits": { + "total": { + "value": 9, + "relation": "eq" + }, + "max_score": null, + "hits": [ + { + "_index": "endpoint-agent", + "_id": "UV_6SG8B9c_DH2QsbOZd", + "_score": null, + "_source": { + "machine_id": "606267a9-2e51-42b4-956e-6cc7812e3447", + "created_at": "2019-12-27T20:09:28.377Z", + "host": { + "name": "natalee-2", + "hostname": "natalee-2.example.com", + "ip": "10.5.220.127", + "mac_address": "17-5f-c9-f8-ca-d6", + "os": { + "name": "windows 6.3", + "full": "Windows Server 2012R2" + } + }, + "endpoint": { + "domain": "example.com", + "is_base_image": false, + "active_directory_distinguished_name": "CN=natalee-2,DC=example,DC=com", + "active_directory_hostname": "natalee-2.example.com", + "upgrade": { + "status": null, + "updated_at": null + }, + "isolation": { + "status": false, + "request_status": null, + "updated_at": null + }, + "policy": { + "name": "With Eventing", + "id": "C2A9093E-E289-4C0A-AA44-8C32A414FA7A" + }, + "sensor": { + "persistence": true, + "status": {} + } + } + }, + "fields": { + "machine_id": [ + "606267a9-2e51-42b4-956e-6cc7812e3447" + ] + }, + "sort": [ + 1577477368377 + ], + "inner_hits": { + "most_recent": { + "hits": { + "total": { + "value": 3, + "relation": "eq" + }, + "max_score": null, + "hits": [ + { + "_index": "endpoint-agent", + "_id": "UV_6SG8B9c_DH2QsbOZd", + "_score": null, + "_source": { + "machine_id": "606267a9-2e51-42b4-956e-6cc7812e3447", + "created_at": "2019-12-27T20:09:28.377Z", + "host": { + "name": "natalee-2", + "hostname": "natalee-2.example.com", + "ip": "10.5.220.127", + "mac_address": "17-5f-c9-f8-ca-d6", + "os": { + "name": "windows 6.3", + "full": "Windows Server 2012R2" + } + }, + "endpoint": { + "domain": "example.com", + "is_base_image": false, + "active_directory_distinguished_name": "CN=natalee-2,DC=example,DC=com", + "active_directory_hostname": "natalee-2.example.com", + "upgrade": { + "status": null, + "updated_at": null + }, + "isolation": { + "status": false, + "request_status": null, + "updated_at": null + }, + "policy": { + "name": "With Eventing", + "id": "C2A9093E-E289-4C0A-AA44-8C32A414FA7A" + }, + "sensor": { + "persistence": true, + "status": {} + } + } + }, + "sort": [ + 1577477368377 + ] + } + ] + } + } + } + }, + { + "_index": "endpoint-agent", + "_id": "Ul_6SG8B9c_DH2QsbOZd", + "_score": null, + "_source": { + "machine_id": "8ec625e1-a80c-4c9f-bdfd-496060aa6310", + "created_at": "2019-12-27T20:09:28.377Z", + "host": { + "name": "luttrell-2", + "hostname": "luttrell-2.example.com", + "ip": "10.246.84.193", + "mac_address": "dc-d-88-14-c3-c6", + "os": { + "name": "windows 6.3", + "full": "Windows Server 2012R2" + } + }, + "endpoint": { + "domain": "example.com", + "is_base_image": false, + "active_directory_distinguished_name": "CN=luttrell-2,DC=example,DC=com", + "active_directory_hostname": "luttrell-2.example.com", + "upgrade": { + "status": null, + "updated_at": null + }, + "isolation": { + "status": false, + "request_status": null, + "updated_at": null + }, + "policy": { + "name": "Default", + "id": "00000000-0000-0000-0000-000000000000" + }, + "sensor": { + "persistence": true, + "status": {} + } + } + }, + "fields": { + "machine_id": [ + "8ec625e1-a80c-4c9f-bdfd-496060aa6310" + ] + }, + "sort": [ + 1577477368377 + ], + "inner_hits": { + "most_recent": { + "hits": { + "total": { + "value": 3, + "relation": "eq" + }, + "max_score": null, + "hits": [ + { + "_index": "endpoint-agent", + "_id": "Ul_6SG8B9c_DH2QsbOZd", + "_score": null, + "_source": { + "machine_id": "8ec625e1-a80c-4c9f-bdfd-496060aa6310", + "created_at": "2019-12-27T20:09:28.377Z", + "host": { + "name": "luttrell-2", + "hostname": "luttrell-2.example.com", + "ip": "10.246.84.193", + "mac_address": "dc-d-88-14-c3-c6", + "os": { + "name": "windows 6.3", + "full": "Windows Server 2012R2" + } + }, + "endpoint": { + "domain": "example.com", + "is_base_image": false, + "active_directory_distinguished_name": "CN=luttrell-2,DC=example,DC=com", + "active_directory_hostname": "luttrell-2.example.com", + "upgrade": { + "status": null, + "updated_at": null + }, + "isolation": { + "status": false, + "request_status": null, + "updated_at": null + }, + "policy": { + "name": "Default", + "id": "00000000-0000-0000-0000-000000000000" + }, + "sensor": { + "persistence": true, + "status": {} + } + } + }, + "sort": [ + 1577477368377 + ] + } + ] + } + } + } + }, + { + "_index": "endpoint-agent", + "_id": "U1_6SG8B9c_DH2QsbOZd", + "_score": null, + "_source": { + "machine_id": "853a308c-6e6d-4b92-a32b-2f623b6c8cf4", + "created_at": "2019-12-27T20:09:28.377Z", + "host": { + "name": "akeylah-7", + "hostname": "akeylah-7.example.com", + "ip": "10.252.242.44", + "mac_address": "27-b9-51-21-31-a", + "os": { + "name": "windows 6.3", + "full": "Windows Server 2012R2" + } + }, + "endpoint": { + "domain": "example.com", + "is_base_image": false, + "active_directory_distinguished_name": "CN=akeylah-7,DC=example,DC=com", + "active_directory_hostname": "akeylah-7.example.com", + "upgrade": { + "status": null, + "updated_at": null + }, + "isolation": { + "status": false, + "request_status": null, + "updated_at": null + }, + "policy": { + "name": "With Eventing", + "id": "C2A9093E-E289-4C0A-AA44-8C32A414FA7A" + }, + "sensor": { + "persistence": true, + "status": {} + } + } + }, + "fields": { + "machine_id": [ + "853a308c-6e6d-4b92-a32b-2f623b6c8cf4" + ] + }, + "sort": [ + 1577477368377 + ], + "inner_hits": { + "most_recent": { + "hits": { + "total": { + "value": 3, + "relation": "eq" + }, + "max_score": null, + "hits": [ + { + "_index": "endpoint-agent", + "_id": "U1_6SG8B9c_DH2QsbOZd", + "_score": null, + "_source": { + "machine_id": "853a308c-6e6d-4b92-a32b-2f623b6c8cf4", + "created_at": "2019-12-27T20:09:28.377Z", + "host": { + "name": "akeylah-7", + "hostname": "akeylah-7.example.com", + "ip": "10.252.242.44", + "mac_address": "27-b9-51-21-31-a", + "os": { + "name": "windows 6.3", + "full": "Windows Server 2012R2" + } + }, + "endpoint": { + "domain": "example.com", + "is_base_image": false, + "active_directory_distinguished_name": "CN=akeylah-7,DC=example,DC=com", + "active_directory_hostname": "akeylah-7.example.com", + "upgrade": { + "status": null, + "updated_at": null + }, + "isolation": { + "status": false, + "request_status": null, + "updated_at": null + }, + "policy": { + "name": "With Eventing", + "id": "C2A9093E-E289-4C0A-AA44-8C32A414FA7A" + }, + "sensor": { + "persistence": true, + "status": {} + } + } + }, + "sort": [ + 1577477368377 + ] + } + ] + } + } + } + } + ] + }, + "aggregations": { + "total": { + "value": 3 + } + } +} diff --git a/x-pack/plugins/endpoint/server/types.ts b/x-pack/plugins/endpoint/server/types.ts new file mode 100644 index 0000000000000..c6d0e3dea70cf --- /dev/null +++ b/x-pack/plugins/endpoint/server/types.ts @@ -0,0 +1,54 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { LoggerFactory } from 'kibana/server'; +import { EndpointConfigType } from './config'; + +export interface EndpointAppContext { + logFactory: LoggerFactory; + config(): Promise; +} + +export class EndpointAppConstants { + static ENDPOINT_INDEX_NAME = 'endpoint-agent*'; +} + +export interface EndpointData { + machine_id: string; + created_at: Date; + host: { + name: string; + hostname: string; + ip: string; + mac_address: string; + os: { + name: string; + full: string; + }; + }; + endpoint: { + domain: string; + is_base_image: boolean; + active_directory_distinguished_name: string; + active_directory_hostname: string; + upgrade: { + status?: string; + updated_at?: Date; + }; + isolation: { + status: boolean; + request_status?: string | boolean; + updated_at?: Date; + }; + policy: { + name: string; + id: string; + }; + sensor: { + persistence: boolean; + status: object; + }; + }; +} diff --git a/x-pack/plugins/graph/server/routes/explore.ts b/x-pack/plugins/graph/server/routes/explore.ts index 0a5b9f62f12a1..125378891151b 100644 --- a/x-pack/plugins/graph/server/routes/explore.ts +++ b/x-pack/plugins/graph/server/routes/explore.ts @@ -71,7 +71,11 @@ export function registerExploreRoute({ throw Boom.badRequest(relevantCause.reason); } - throw Boom.boomify(error); + return response.internalError({ + body: { + message: error.message, + }, + }); } } ) diff --git a/x-pack/plugins/graph/server/routes/search.ts b/x-pack/plugins/graph/server/routes/search.ts index 400cdc4e82b6e..91b404dc7cb91 100644 --- a/x-pack/plugins/graph/server/routes/search.ts +++ b/x-pack/plugins/graph/server/routes/search.ts @@ -6,7 +6,6 @@ import { IRouter } from 'kibana/server'; import { schema } from '@kbn/config-schema'; -import Boom from 'boom'; import { LicenseState, verifyApiAccess } from '../lib/license_state'; export function registerSearchRoute({ @@ -53,7 +52,12 @@ export function registerSearchRoute({ }, }); } catch (error) { - throw Boom.boomify(error, { statusCode: error.statusCode || 500 }); + return response.customError({ + statusCode: error.statusCode || 500, + body: { + message: error.message, + }, + }); } } ) diff --git a/x-pack/plugins/licensing/server/plugin.test.ts b/x-pack/plugins/licensing/server/plugin.test.ts index 0b5a3533bd3b6..9547a2dc52966 100644 --- a/x-pack/plugins/licensing/server/plugin.test.ts +++ b/x-pack/plugins/licensing/server/plugin.test.ts @@ -4,7 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -import { BehaviorSubject } from 'rxjs'; import { take, toArray } from 'rxjs/operators'; import moment from 'moment'; import { LicenseType } from '../common/types'; @@ -53,7 +52,7 @@ describe('licensing plugin', () => { features: {}, }); const coreSetup = coreMock.createSetup(); - coreSetup.elasticsearch.dataClient$ = new BehaviorSubject(dataClient); + coreSetup.elasticsearch.dataClient = dataClient; const { license$ } = await plugin.setup(coreSetup); const license = await license$.pipe(take(1)).toPromise(); @@ -71,7 +70,7 @@ describe('licensing plugin', () => { }) ); const coreSetup = coreMock.createSetup(); - coreSetup.elasticsearch.dataClient$ = new BehaviorSubject(dataClient); + coreSetup.elasticsearch.dataClient = dataClient; const { license$ } = await plugin.setup(coreSetup); const [first, second, third] = await license$.pipe(take(3), toArray()).toPromise(); @@ -85,7 +84,7 @@ describe('licensing plugin', () => { const dataClient = elasticsearchServiceMock.createClusterClient(); dataClient.callAsInternalUser.mockRejectedValue(new Error('test')); const coreSetup = coreMock.createSetup(); - coreSetup.elasticsearch.dataClient$ = new BehaviorSubject(dataClient); + coreSetup.elasticsearch.dataClient = dataClient; const { license$ } = await plugin.setup(coreSetup); const license = await license$.pipe(take(1)).toPromise(); @@ -99,7 +98,7 @@ describe('licensing plugin', () => { error.status = 400; dataClient.callAsInternalUser.mockRejectedValue(error); const coreSetup = coreMock.createSetup(); - coreSetup.elasticsearch.dataClient$ = new BehaviorSubject(dataClient); + coreSetup.elasticsearch.dataClient = dataClient; const { license$ } = await plugin.setup(coreSetup); const license = await license$.pipe(take(1)).toPromise(); @@ -119,7 +118,7 @@ describe('licensing plugin', () => { .mockResolvedValue({ license: buildRawLicense(), features: {} }); const coreSetup = coreMock.createSetup(); - coreSetup.elasticsearch.dataClient$ = new BehaviorSubject(dataClient); + coreSetup.elasticsearch.dataClient = dataClient; const { license$ } = await plugin.setup(coreSetup); const [first, second, third] = await license$.pipe(take(3), toArray()).toPromise(); @@ -137,7 +136,7 @@ describe('licensing plugin', () => { }); const coreSetup = coreMock.createSetup(); - coreSetup.elasticsearch.dataClient$ = new BehaviorSubject(dataClient); + coreSetup.elasticsearch.dataClient = dataClient; await plugin.setup(coreSetup); await flushPromises(); @@ -152,7 +151,7 @@ describe('licensing plugin', () => { }); const coreSetup = coreMock.createSetup(); - coreSetup.elasticsearch.dataClient$ = new BehaviorSubject(dataClient); + coreSetup.elasticsearch.dataClient = dataClient; await plugin.setup(coreSetup); await flushPromises(); @@ -180,7 +179,7 @@ describe('licensing plugin', () => { ); const coreSetup = coreMock.createSetup(); - coreSetup.elasticsearch.dataClient$ = new BehaviorSubject(dataClient); + coreSetup.elasticsearch.dataClient = dataClient; const { license$ } = await plugin.setup(coreSetup); const [first, second, third] = await license$.pipe(take(3), toArray()).toPromise(); @@ -209,7 +208,7 @@ describe('licensing plugin', () => { features: {}, }); const coreSetup = coreMock.createSetup(); - coreSetup.elasticsearch.dataClient$ = new BehaviorSubject(dataClient); + coreSetup.elasticsearch.dataClient = dataClient; const { refresh } = await plugin.setup(coreSetup); expect(dataClient.callAsInternalUser).toHaveBeenCalledTimes(0); @@ -242,7 +241,7 @@ describe('licensing plugin', () => { features: {}, }); const coreSetup = coreMock.createSetup(); - coreSetup.elasticsearch.dataClient$ = new BehaviorSubject(dataClient); + coreSetup.elasticsearch.dataClient = dataClient; const { createLicensePoller, license$ } = await plugin.setup(coreSetup); const customClient = elasticsearchServiceMock.createClusterClient(); diff --git a/x-pack/plugins/licensing/server/plugin.ts b/x-pack/plugins/licensing/server/plugin.ts index 2eabd534a997c..383245e6f4ee8 100644 --- a/x-pack/plugins/licensing/server/plugin.ts +++ b/x-pack/plugins/licensing/server/plugin.ts @@ -92,7 +92,7 @@ export class LicensingPlugin implements Plugin { this.logger.debug('Setting up Licensing plugin'); const config = await this.config$.pipe(take(1)).toPromise(); const pollingFrequency = config.api_polling_frequency; - const dataClient = await core.elasticsearch.dataClient$.pipe(take(1)).toPromise(); + const dataClient = await core.elasticsearch.dataClient; const { refresh, license$ } = this.createLicensePoller( dataClient, diff --git a/x-pack/plugins/security/common/licensing/license_features.ts b/x-pack/plugins/security/common/licensing/license_features.ts index 6b6c86d48c21e..33f8370a1b43e 100644 --- a/x-pack/plugins/security/common/licensing/license_features.ts +++ b/x-pack/plugins/security/common/licensing/license_features.ts @@ -23,6 +23,11 @@ export interface SecurityLicenseFeatures { */ readonly showLinks: boolean; + /** + * Indicates whether we show the Role Mappings UI. + */ + readonly showRoleMappingsManagement: boolean; + /** * Indicates whether we allow users to define document level security in roles. */ diff --git a/x-pack/plugins/security/common/licensing/license_service.test.ts b/x-pack/plugins/security/common/licensing/license_service.test.ts index f4fa5e00e2387..df2d66a036039 100644 --- a/x-pack/plugins/security/common/licensing/license_service.test.ts +++ b/x-pack/plugins/security/common/licensing/license_service.test.ts @@ -17,6 +17,7 @@ describe('license features', function() { showLogin: true, allowLogin: false, showLinks: false, + showRoleMappingsManagement: false, allowRoleDocumentLevelSecurity: false, allowRoleFieldLevelSecurity: false, layout: 'error-es-unavailable', @@ -34,6 +35,7 @@ describe('license features', function() { showLogin: true, allowLogin: false, showLinks: false, + showRoleMappingsManagement: false, allowRoleDocumentLevelSecurity: false, allowRoleFieldLevelSecurity: false, layout: 'error-xpack-unavailable', @@ -63,6 +65,7 @@ describe('license features', function() { "layout": "error-xpack-unavailable", "showLinks": false, "showLogin": true, + "showRoleMappingsManagement": false, }, ] `); @@ -79,6 +82,7 @@ describe('license features', function() { "linksMessage": "Access is denied because Security is disabled in Elasticsearch.", "showLinks": false, "showLogin": false, + "showRoleMappingsManagement": false, }, ] `); @@ -87,10 +91,12 @@ describe('license features', function() { } }); - it('should show login page and other security elements, allow RBAC but forbid document level security if license is not platinum or trial.', () => { - const mockRawLicense = licensingMock.createLicenseMock(); - mockRawLicense.hasAtLeast.mockReturnValue(false); - mockRawLicense.getFeature.mockReturnValue({ isEnabled: true, isAvailable: true }); + it('should show login page and other security elements, allow RBAC but forbid role mappings and document level security if license is basic.', () => { + const mockRawLicense = licensingMock.createLicense({ + features: { security: { isEnabled: true, isAvailable: true } }, + }); + + const getFeatureSpy = jest.spyOn(mockRawLicense, 'getFeature'); const serviceSetup = new SecurityLicenseService().setup({ license$: of(mockRawLicense), @@ -99,18 +105,19 @@ describe('license features', function() { showLogin: true, allowLogin: true, showLinks: true, + showRoleMappingsManagement: false, allowRoleDocumentLevelSecurity: false, allowRoleFieldLevelSecurity: false, allowRbac: true, }); - expect(mockRawLicense.getFeature).toHaveBeenCalledTimes(1); - expect(mockRawLicense.getFeature).toHaveBeenCalledWith('security'); + expect(getFeatureSpy).toHaveBeenCalledTimes(1); + expect(getFeatureSpy).toHaveBeenCalledWith('security'); }); it('should not show login page or other security elements if security is disabled in Elasticsearch.', () => { - const mockRawLicense = licensingMock.createLicenseMock(); - mockRawLicense.hasAtLeast.mockReturnValue(false); - mockRawLicense.getFeature.mockReturnValue({ isEnabled: false, isAvailable: true }); + const mockRawLicense = licensingMock.createLicense({ + features: { security: { isEnabled: false, isAvailable: true } }, + }); const serviceSetup = new SecurityLicenseService().setup({ license$: of(mockRawLicense), @@ -119,6 +126,7 @@ describe('license features', function() { showLogin: false, allowLogin: false, showLinks: false, + showRoleMappingsManagement: false, allowRoleDocumentLevelSecurity: false, allowRoleFieldLevelSecurity: false, allowRbac: false, @@ -126,12 +134,31 @@ describe('license features', function() { }); }); - it('should allow to login, allow RBAC and document level security if license >= platinum', () => { - const mockRawLicense = licensingMock.createLicenseMock(); - mockRawLicense.hasAtLeast.mockImplementation(license => { - return license === 'trial' || license === 'platinum' || license === 'enterprise'; + it('should allow role mappings, but not DLS/FLS if license = gold', () => { + const mockRawLicense = licensingMock.createLicense({ + license: { mode: 'gold', type: 'gold' }, + features: { security: { isEnabled: true, isAvailable: true } }, + }); + + const serviceSetup = new SecurityLicenseService().setup({ + license$: of(mockRawLicense), + }); + expect(serviceSetup.license.getFeatures()).toEqual({ + showLogin: true, + allowLogin: true, + showLinks: true, + showRoleMappingsManagement: true, + allowRoleDocumentLevelSecurity: false, + allowRoleFieldLevelSecurity: false, + allowRbac: true, + }); + }); + + it('should allow to login, allow RBAC, allow role mappings, and document level security if license >= platinum', () => { + const mockRawLicense = licensingMock.createLicense({ + license: { mode: 'platinum', type: 'platinum' }, + features: { security: { isEnabled: true, isAvailable: true } }, }); - mockRawLicense.getFeature.mockReturnValue({ isEnabled: true, isAvailable: true }); const serviceSetup = new SecurityLicenseService().setup({ license$: of(mockRawLicense), @@ -140,6 +167,7 @@ describe('license features', function() { showLogin: true, allowLogin: true, showLinks: true, + showRoleMappingsManagement: true, allowRoleDocumentLevelSecurity: true, allowRoleFieldLevelSecurity: true, allowRbac: true, diff --git a/x-pack/plugins/security/common/licensing/license_service.ts b/x-pack/plugins/security/common/licensing/license_service.ts index 0f9da03f9f6ec..e6d2eff49ed0d 100644 --- a/x-pack/plugins/security/common/licensing/license_service.ts +++ b/x-pack/plugins/security/common/licensing/license_service.ts @@ -70,6 +70,7 @@ export class SecurityLicenseService { showLogin: true, allowLogin: false, showLinks: false, + showRoleMappingsManagement: false, allowRoleDocumentLevelSecurity: false, allowRoleFieldLevelSecurity: false, allowRbac: false, @@ -85,6 +86,7 @@ export class SecurityLicenseService { showLogin: false, allowLogin: false, showLinks: false, + showRoleMappingsManagement: false, allowRoleDocumentLevelSecurity: false, allowRoleFieldLevelSecurity: false, allowRbac: false, @@ -92,11 +94,13 @@ export class SecurityLicenseService { }; } + const showRoleMappingsManagement = rawLicense.hasAtLeast('gold'); const isLicensePlatinumOrBetter = rawLicense.hasAtLeast('platinum'); return { showLogin: true, allowLogin: true, showLinks: true, + showRoleMappingsManagement, // Only platinum and trial licenses are compliant with field- and document-level security. allowRoleDocumentLevelSecurity: isLicensePlatinumOrBetter, allowRoleFieldLevelSecurity: isLicensePlatinumOrBetter, diff --git a/x-pack/plugins/security/common/model/index.ts b/x-pack/plugins/security/common/model/index.ts index 226ea3b70afe2..f3c65ed7e3cf1 100644 --- a/x-pack/plugins/security/common/model/index.ts +++ b/x-pack/plugins/security/common/model/index.ts @@ -12,3 +12,10 @@ export { FeaturesPrivileges } from './features_privileges'; export { RawKibanaPrivileges, RawKibanaFeaturePrivileges } from './raw_kibana_privileges'; export { Role, RoleIndexPrivilege, RoleKibanaPrivilege } from './role'; export { KibanaPrivileges } from './kibana_privileges'; +export { + InlineRoleTemplate, + StoredRoleTemplate, + InvalidRoleTemplate, + RoleTemplate, + RoleMapping, +} from './role_mapping'; diff --git a/x-pack/plugins/security/common/model/role_mapping.ts b/x-pack/plugins/security/common/model/role_mapping.ts new file mode 100644 index 0000000000000..99de183f648f7 --- /dev/null +++ b/x-pack/plugins/security/common/model/role_mapping.ts @@ -0,0 +1,55 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +interface RoleMappingAnyRule { + any: RoleMappingRule[]; +} + +interface RoleMappingAllRule { + all: RoleMappingRule[]; +} + +interface RoleMappingFieldRule { + field: Record; +} + +interface RoleMappingExceptRule { + except: RoleMappingRule; +} + +type RoleMappingRule = + | RoleMappingAnyRule + | RoleMappingAllRule + | RoleMappingFieldRule + | RoleMappingExceptRule; + +type RoleTemplateFormat = 'string' | 'json'; + +export interface InlineRoleTemplate { + template: { source: string }; + format?: RoleTemplateFormat; +} + +export interface StoredRoleTemplate { + template: { id: string }; + format?: RoleTemplateFormat; +} + +export interface InvalidRoleTemplate { + template: string; + format?: RoleTemplateFormat; +} + +export type RoleTemplate = InlineRoleTemplate | StoredRoleTemplate | InvalidRoleTemplate; + +export interface RoleMapping { + name: string; + enabled: boolean; + roles?: string[]; + role_templates?: RoleTemplate[]; + rules: RoleMappingRule | {}; + metadata: Record; +} diff --git a/x-pack/plugins/security/public/authentication/authentication_service.ts b/x-pack/plugins/security/public/authentication/authentication_service.ts new file mode 100644 index 0000000000000..23c45c88e563a --- /dev/null +++ b/x-pack/plugins/security/public/authentication/authentication_service.ts @@ -0,0 +1,31 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { HttpSetup } from 'src/core/public'; +import { AuthenticatedUser } from '../../common/model'; + +interface SetupParams { + http: HttpSetup; +} + +export interface AuthenticationServiceSetup { + /** + * Returns currently authenticated user and throws if current user isn't authenticated. + */ + getCurrentUser: () => Promise; +} + +export class AuthenticationService { + public setup({ http }: SetupParams): AuthenticationServiceSetup { + return { + async getCurrentUser() { + return (await http.get('/internal/security/me', { + headers: { 'kbn-system-api': true }, + })) as AuthenticatedUser; + }, + }; + } +} diff --git a/x-pack/plugins/security/public/authentication/index.mock.ts b/x-pack/plugins/security/public/authentication/index.mock.ts new file mode 100644 index 0000000000000..c8d77a5b62c6f --- /dev/null +++ b/x-pack/plugins/security/public/authentication/index.mock.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { AuthenticationServiceSetup } from './authentication_service'; + +export const authenticationMock = { + createSetup: (): jest.Mocked => ({ + getCurrentUser: jest.fn(), + }), +}; diff --git a/x-pack/plugins/security/public/authentication/index.ts b/x-pack/plugins/security/public/authentication/index.ts new file mode 100644 index 0000000000000..a55f4d7bb95b3 --- /dev/null +++ b/x-pack/plugins/security/public/authentication/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export { AuthenticationService, AuthenticationServiceSetup } from './authentication_service'; diff --git a/x-pack/plugins/security/public/index.ts b/x-pack/plugins/security/public/index.ts index dc34fcbbe7d1e..336ec37d76a1b 100644 --- a/x-pack/plugins/security/public/index.ts +++ b/x-pack/plugins/security/public/index.ts @@ -6,7 +6,10 @@ import { PluginInitializer } from 'src/core/public'; import { SecurityPlugin, SecurityPluginSetup, SecurityPluginStart } from './plugin'; + +export { SecurityPluginSetup, SecurityPluginStart }; export { SessionInfo } from './types'; +export { AuthenticatedUser } from '../common/model'; export const plugin: PluginInitializer = () => new SecurityPlugin(); diff --git a/x-pack/plugins/security/public/mocks.ts b/x-pack/plugins/security/public/mocks.ts new file mode 100644 index 0000000000000..3c0c59d10abd1 --- /dev/null +++ b/x-pack/plugins/security/public/mocks.ts @@ -0,0 +1,19 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { authenticationMock } from './authentication/index.mock'; +import { createSessionTimeoutMock } from './session/session_timeout.mock'; + +function createSetupMock() { + return { + authc: authenticationMock.createSetup(), + sessionTimeout: createSessionTimeoutMock(), + }; +} + +export const securityMock = { + createSetup: createSetupMock, +}; diff --git a/x-pack/plugins/security/public/nav_control/nav_control_service.test.ts b/x-pack/plugins/security/public/nav_control/nav_control_service.test.ts index 3879d611d46eb..a9a89ee05f561 100644 --- a/x-pack/plugins/security/public/nav_control/nav_control_service.test.ts +++ b/x-pack/plugins/security/public/nav_control/nav_control_service.test.ts @@ -10,6 +10,8 @@ import { ILicense } from '../../../licensing/public'; import { SecurityNavControlService } from '.'; import { SecurityLicenseService } from '../../common/licensing'; import { nextTick } from 'test_utils/enzyme_helpers'; +import { securityMock } from '../mocks'; +import { mockAuthenticatedUser } from '../../common/model/authenticated_user.mock'; const validLicense = { isAvailable: true, @@ -29,13 +31,17 @@ describe('SecurityNavControlService', () => { const license$ = new BehaviorSubject(validLicense); const navControlService = new SecurityNavControlService(); + const mockSecuritySetup = securityMock.createSetup(); + mockSecuritySetup.authc.getCurrentUser.mockResolvedValue( + mockAuthenticatedUser({ username: 'some-user', full_name: undefined }) + ); navControlService.setup({ securityLicense: new SecurityLicenseService().setup({ license$ }).license, + authc: mockSecuritySetup.authc, }); const coreStart = coreMock.createStart(); coreStart.chrome.navControls.registerRight = jest.fn(); - coreStart.http.get.mockResolvedValue({ username: 'some-user' }); navControlService.start({ core: coreStart }); expect(coreStart.chrome.navControls.registerRight).toHaveBeenCalledTimes(1); @@ -93,6 +99,7 @@ describe('SecurityNavControlService', () => { const navControlService = new SecurityNavControlService(); navControlService.setup({ securityLicense: new SecurityLicenseService().setup({ license$ }).license, + authc: securityMock.createSetup().authc, }); const coreStart = coreMock.createStart(); @@ -111,6 +118,7 @@ describe('SecurityNavControlService', () => { const navControlService = new SecurityNavControlService(); navControlService.setup({ securityLicense: new SecurityLicenseService().setup({ license$ }).license, + authc: securityMock.createSetup().authc, }); const coreStart = coreMock.createStart(); @@ -126,6 +134,7 @@ describe('SecurityNavControlService', () => { const navControlService = new SecurityNavControlService(); navControlService.setup({ securityLicense: new SecurityLicenseService().setup({ license$ }).license, + authc: securityMock.createSetup().authc, }); const coreStart = coreMock.createStart(); @@ -146,6 +155,7 @@ describe('SecurityNavControlService', () => { const navControlService = new SecurityNavControlService(); navControlService.setup({ securityLicense: new SecurityLicenseService().setup({ license$ }).license, + authc: securityMock.createSetup().authc, }); const coreStart = coreMock.createStart(); diff --git a/x-pack/plugins/security/public/nav_control/nav_control_service.tsx b/x-pack/plugins/security/public/nav_control/nav_control_service.tsx index aeeb84219c937..153e7112dc95b 100644 --- a/x-pack/plugins/security/public/nav_control/nav_control_service.tsx +++ b/x-pack/plugins/security/public/nav_control/nav_control_service.tsx @@ -9,11 +9,12 @@ import { CoreStart } from 'src/core/public'; import ReactDOM from 'react-dom'; import React from 'react'; import { SecurityLicense } from '../../common/licensing'; -import { AuthenticatedUser } from '../../common/model'; import { SecurityNavControl } from './nav_control_component'; +import { AuthenticationServiceSetup } from '../authentication'; interface SetupDeps { securityLicense: SecurityLicense; + authc: AuthenticationServiceSetup; } interface StartDeps { @@ -22,13 +23,15 @@ interface StartDeps { export class SecurityNavControlService { private securityLicense!: SecurityLicense; + private authc!: AuthenticationServiceSetup; private navControlRegistered!: boolean; private securityFeaturesSubscription?: Subscription; - public setup({ securityLicense }: SetupDeps) { + public setup({ securityLicense, authc }: SetupDeps) { this.securityLicense = securityLicense; + this.authc = authc; } public start({ core }: StartDeps) { @@ -38,14 +41,8 @@ export class SecurityNavControlService { const shouldRegisterNavControl = !isAnonymousPath && showLinks && !this.navControlRegistered; - if (shouldRegisterNavControl) { - const user = core.http.get('/internal/security/me', { - headers: { - 'kbn-system-api': true, - }, - }) as Promise; - this.registerSecurityNavControl(core, user); + this.registerSecurityNavControl(core); } } ); @@ -60,16 +57,16 @@ export class SecurityNavControlService { } private registerSecurityNavControl( - core: Pick, - user: Promise + core: Pick ) { + const currentUserPromise = this.authc.getCurrentUser(); core.chrome.navControls.registerRight({ order: 2000, mount: (el: HTMLElement) => { const I18nContext = core.i18n.Context; const props = { - user, + user: currentUserPromise, editProfileUrl: core.http.basePath.prepend('/app/kibana#/account'), logoutUrl: core.http.basePath.prepend(`/logout`), }; diff --git a/x-pack/plugins/security/public/plugin.ts b/x-pack/plugins/security/public/plugin.ts index 0f10f9d89f25a..50e0b838c750f 100644 --- a/x-pack/plugins/security/public/plugin.ts +++ b/x-pack/plugins/security/public/plugin.ts @@ -9,18 +9,20 @@ import { LicensingPluginSetup } from '../../licensing/public'; import { SessionExpired, SessionTimeout, + ISessionTimeout, SessionTimeoutHttpInterceptor, UnauthorizedResponseHttpInterceptor, } from './session'; import { SecurityLicenseService } from '../common/licensing'; import { SecurityNavControlService } from './nav_control'; +import { AuthenticationService } from './authentication'; export interface PluginSetupDependencies { licensing: LicensingPluginSetup; } export class SecurityPlugin implements Plugin { - private sessionTimeout!: SessionTimeout; + private sessionTimeout!: ISessionTimeout; private navControlService!: SecurityNavControlService; @@ -43,12 +45,15 @@ export class SecurityPlugin implements Plugin; private sessionInfo?: SessionInfo; private fetchTimer?: number; diff --git a/x-pack/plugins/security/server/authentication/providers/oidc.test.ts b/x-pack/plugins/security/server/authentication/providers/oidc.test.ts index ad7eab76db088..dd4f6820f89f3 100644 --- a/x-pack/plugins/security/server/authentication/providers/oidc.test.ts +++ b/x-pack/plugins/security/server/authentication/providers/oidc.test.ts @@ -42,7 +42,7 @@ describe('OIDCAuthenticationProvider', () => { describe('`login` method', () => { it('redirects third party initiated login attempts to the OpenId Connect Provider.', async () => { - const request = httpServerMock.createKibanaRequest({ path: '/api/security/oidc' }); + const request = httpServerMock.createKibanaRequest({ path: '/api/security/oidc/callback' }); mockOptions.client.callAsInternalUser.withArgs('shield.oidcPrepare').resolves({ state: 'statevalue', @@ -205,13 +205,14 @@ describe('OIDCAuthenticationProvider', () => { describe('authorization code flow', () => { defineAuthenticationFlowTests(() => ({ request: httpServerMock.createKibanaRequest({ - path: '/api/security/oidc?code=somecodehere&state=somestatehere', + path: '/api/security/oidc/callback?code=somecodehere&state=somestatehere', }), attempt: { flow: OIDCAuthenticationFlow.AuthorizationCode, - authenticationResponseURI: '/api/security/oidc?code=somecodehere&state=somestatehere', + authenticationResponseURI: + '/api/security/oidc/callback?code=somecodehere&state=somestatehere', }, - expectedRedirectURI: '/api/security/oidc?code=somecodehere&state=somestatehere', + expectedRedirectURI: '/api/security/oidc/callback?code=somecodehere&state=somestatehere', })); }); @@ -219,7 +220,7 @@ describe('OIDCAuthenticationProvider', () => { defineAuthenticationFlowTests(() => ({ request: httpServerMock.createKibanaRequest({ path: - '/api/security/oidc?authenticationResponseURI=http://kibana/api/security/oidc/implicit#id_token=sometoken', + '/api/security/oidc/callback?authenticationResponseURI=http://kibana/api/security/oidc/implicit#id_token=sometoken', }), attempt: { flow: OIDCAuthenticationFlow.Implicit, diff --git a/x-pack/plugins/security/server/config.ts b/x-pack/plugins/security/server/config.ts index b3f96497b0538..4f1c25702ae97 100644 --- a/x-pack/plugins/security/server/config.ts +++ b/x-pack/plugins/security/server/config.ts @@ -8,7 +8,6 @@ import crypto from 'crypto'; import { Observable } from 'rxjs'; import { map } from 'rxjs/operators'; import { schema, Type, TypeOf } from '@kbn/config-schema'; -import { duration } from 'moment'; import { PluginInitializerContext } from '../../../../src/core/server'; export type ConfigType = ReturnType extends Observable @@ -35,7 +34,6 @@ export const ConfigSchema = schema.object( schema.maybe(schema.string({ minLength: 32 })), schema.string({ minLength: 32, defaultValue: 'a'.repeat(32) }) ), - sessionTimeout: schema.maybe(schema.nullable(schema.number())), // DEPRECATED session: schema.object({ idleTimeout: schema.nullable(schema.duration()), lifespan: schema.nullable(schema.duration()), @@ -88,22 +86,11 @@ export function createConfig$(context: PluginInitializerContext, isTLSEnabled: b secureCookies = true; } - // "sessionTimeout" is deprecated and replaced with "session.idleTimeout" - // however, NP does not yet have a mechanism to automatically rename deprecated keys - // for the time being, we'll do it manually: - const deprecatedSessionTimeout = - typeof config.sessionTimeout === 'number' ? duration(config.sessionTimeout) : null; - const val = { + return { ...config, encryptionKey, secureCookies, - session: { - ...config.session, - idleTimeout: config.session.idleTimeout || deprecatedSessionTimeout, - }, }; - delete val.sessionTimeout; // DEPRECATED - return val; }) ); } diff --git a/x-pack/plugins/security/server/elasticsearch_client_plugin.ts b/x-pack/plugins/security/server/elasticsearch_client_plugin.ts index 60d947bd65863..996dcb685f29b 100644 --- a/x-pack/plugins/security/server/elasticsearch_client_plugin.ts +++ b/x-pack/plugins/security/server/elasticsearch_client_plugin.ts @@ -573,4 +573,64 @@ export function elasticsearchClientPlugin(Client: any, config: unknown, componen fmt: '/_security/delegate_pki', }, }); + + /** + * Retrieves all configured role mappings. + * + * @returns {{ [roleMappingName]: { enabled: boolean; roles: string[]; rules: Record} }} + */ + shield.getRoleMappings = ca({ + method: 'GET', + urls: [ + { + fmt: '/_security/role_mapping', + }, + { + fmt: '/_security/role_mapping/<%=name%>', + req: { + name: { + type: 'string', + required: true, + }, + }, + }, + ], + }); + + /** + * Saves the specified role mapping. + */ + shield.saveRoleMapping = ca({ + method: 'POST', + needBody: true, + urls: [ + { + fmt: '/_security/role_mapping/<%=name%>', + req: { + name: { + type: 'string', + required: true, + }, + }, + }, + ], + }); + + /** + * Deletes the specified role mapping. + */ + shield.deleteRoleMapping = ca({ + method: 'DELETE', + urls: [ + { + fmt: '/_security/role_mapping/<%=name%>', + req: { + name: { + type: 'string', + required: true, + }, + }, + }, + ], + }); } diff --git a/x-pack/plugins/security/server/index.ts b/x-pack/plugins/security/server/index.ts index e72e94e9cd94b..17e49b8cf40d3 100644 --- a/x-pack/plugins/security/server/index.ts +++ b/x-pack/plugins/security/server/index.ts @@ -4,15 +4,38 @@ * you may not use this file except in compliance with the Elastic License. */ -import { PluginInitializerContext } from '../../../../src/core/server'; +import { TypeOf } from '@kbn/config-schema'; +import { + PluginConfigDescriptor, + PluginInitializer, + PluginInitializerContext, + RecursiveReadonly, +} from '../../../../src/core/server'; import { ConfigSchema } from './config'; -import { Plugin } from './plugin'; +import { Plugin, PluginSetupContract, PluginSetupDependencies } from './plugin'; // These exports are part of public Security plugin contract, any change in signature of exported // functions or removal of exports should be considered as a breaking change. -export { AuthenticationResult, DeauthenticationResult, CreateAPIKeyResult } from './authentication'; -export { PluginSetupContract } from './plugin'; +export { + Authentication, + AuthenticationResult, + DeauthenticationResult, + CreateAPIKeyResult, + InvalidateAPIKeyParams, + InvalidateAPIKeyResult, +} from './authentication'; +export { PluginSetupContract }; +export { AuthenticatedUser } from '../common/model'; -export const config = { schema: ConfigSchema }; -export const plugin = (initializerContext: PluginInitializerContext) => - new Plugin(initializerContext); +export const config: PluginConfigDescriptor> = { + schema: ConfigSchema, + deprecations: ({ rename, unused }) => [ + rename('sessionTimeout', 'session.idleTimeout'), + unused('authorization.legacyFallback.enabled'), + ], +}; +export const plugin: PluginInitializer< + RecursiveReadonly, + void, + PluginSetupDependencies +> = (initializerContext: PluginInitializerContext) => new Plugin(initializerContext); diff --git a/x-pack/plugins/security/server/plugin.test.ts b/x-pack/plugins/security/server/plugin.test.ts index 05d67b112bad8..5e32a0e90198a 100644 --- a/x-pack/plugins/security/server/plugin.test.ts +++ b/x-pack/plugins/security/server/plugin.test.ts @@ -6,7 +6,7 @@ import { of } from 'rxjs'; import { ByteSizeValue } from '@kbn/config-schema'; -import { IClusterClient, CoreSetup } from '../../../../src/core/server'; +import { ICustomClusterClient, CoreSetup } from '../../../../src/core/server'; import { elasticsearchClientPlugin } from './elasticsearch_client_plugin'; import { Plugin, PluginSetupDependencies } from './plugin'; @@ -15,7 +15,7 @@ import { coreMock, elasticsearchServiceMock } from '../../../../src/core/server/ describe('Security Plugin', () => { let plugin: Plugin; let mockCoreSetup: MockedKeys; - let mockClusterClient: jest.Mocked; + let mockClusterClient: jest.Mocked; let mockDependencies: PluginSetupDependencies; beforeEach(() => { plugin = new Plugin( @@ -35,9 +35,9 @@ describe('Security Plugin', () => { mockCoreSetup = coreMock.createSetup(); mockCoreSetup.http.isTlsEnabled = true; - mockClusterClient = elasticsearchServiceMock.createClusterClient(); + mockClusterClient = elasticsearchServiceMock.createCustomClusterClient(); mockCoreSetup.elasticsearch.createClient.mockReturnValue( - (mockClusterClient as unknown) as jest.Mocked + (mockClusterClient as unknown) as jest.Mocked ); mockDependencies = { licensing: { license$: of({}) } } as PluginSetupDependencies; diff --git a/x-pack/plugins/security/server/plugin.ts b/x-pack/plugins/security/server/plugin.ts index cdd2a024310bb..ce682d8b30eb7 100644 --- a/x-pack/plugins/security/server/plugin.ts +++ b/x-pack/plugins/security/server/plugin.ts @@ -7,7 +7,7 @@ import { combineLatest } from 'rxjs'; import { first } from 'rxjs/operators'; import { - IClusterClient, + ICustomClusterClient, CoreSetup, KibanaRequest, Logger, @@ -85,7 +85,7 @@ export interface PluginSetupDependencies { */ export class Plugin { private readonly logger: Logger; - private clusterClient?: IClusterClient; + private clusterClient?: ICustomClusterClient; private spacesService?: SpacesService | symbol = Symbol('not accessed'); private securityLicenseService?: SecurityLicenseService; @@ -110,10 +110,7 @@ export class Plugin { this.logger = this.initializerContext.logger.get(); } - public async setup( - core: CoreSetup, - { features, licensing }: PluginSetupDependencies - ): Promise> { + public async setup(core: CoreSetup, { features, licensing }: PluginSetupDependencies) { const [config, legacyConfig] = await combineLatest([ createConfig$(this.initializerContext, core.http.isTlsEnabled), this.initializerContext.config.legacy.globalConfig$, @@ -169,7 +166,7 @@ export class Plugin { csp: core.http.csp, }); - return deepFreeze({ + return deepFreeze({ authc, authz: { diff --git a/x-pack/plugins/security/server/routes/authentication/oidc.ts b/x-pack/plugins/security/server/routes/authentication/oidc.ts index ee9c2b46ac878..232fdd26f7838 100644 --- a/x-pack/plugins/security/server/routes/authentication/oidc.ts +++ b/x-pack/plugins/security/server/routes/authentication/oidc.ts @@ -23,7 +23,7 @@ export function defineOIDCRoutes({ router, logger, authc, csp, basePath }: Route /** * The route should be configured as a redirect URI in OP when OpenID Connect implicit flow * is used, so that we can extract authentication response from URL fragment and send it to - * the `/api/security/oidc` route. + * the `/api/security/oidc/callback` route. */ router.get( { @@ -57,7 +57,7 @@ export function defineOIDCRoutes({ router, logger, authc, csp, basePath }: Route /** * The route that accompanies `/api/security/oidc/implicit` and renders a JavaScript snippet - * that extracts fragment part from the URL and send it to the `/api/security/oidc` route. + * that extracts fragment part from the URL and send it to the `/api/security/oidc/callback` route. * We need this separate endpoint because of default CSP policy that forbids inline scripts. */ router.get( @@ -72,7 +72,7 @@ export function defineOIDCRoutes({ router, logger, authc, csp, basePath }: Route createCustomResourceResponse( ` window.location.replace( - '${serverBasePath}/api/security/oidc?authenticationResponseURI=' + encodeURIComponent(window.location.href) + '${serverBasePath}/api/security/oidc/callback?authenticationResponseURI=' + encodeURIComponent(window.location.href) ); `, 'text/javascript', @@ -83,7 +83,7 @@ export function defineOIDCRoutes({ router, logger, authc, csp, basePath }: Route ); // Generate two identical routes with new and deprecated URL and issue a warning if route with deprecated URL is ever used. - for (const path of ['/api/security/oidc', '/api/security/v1/oidc']) { + for (const path of ['/api/security/oidc/callback', '/api/security/v1/oidc']) { router.get( { path, @@ -124,7 +124,7 @@ export function defineOIDCRoutes({ router, logger, authc, csp, basePath }: Route } else if (request.query.code || request.query.error) { if (path === '/api/security/v1/oidc') { logger.warn( - `The "${serverBasePath}${path}" URL is deprecated and will stop working in the next major version, please use "${serverBasePath}/api/security/oidc" URL instead.`, + `The "${serverBasePath}${path}" URL is deprecated and will stop working in the next major version, please use "${serverBasePath}/api/security/oidc/callback" URL instead.`, { tags: ['deprecation'] } ); } diff --git a/x-pack/plugins/security/server/routes/index.ts b/x-pack/plugins/security/server/routes/index.ts index ade840e7ca495..01df67cacb800 100644 --- a/x-pack/plugins/security/server/routes/index.ts +++ b/x-pack/plugins/security/server/routes/index.ts @@ -14,6 +14,7 @@ import { defineAuthorizationRoutes } from './authorization'; import { defineApiKeysRoutes } from './api_keys'; import { defineIndicesRoutes } from './indices'; import { defineUsersRoutes } from './users'; +import { defineRoleMappingRoutes } from './role_mapping'; /** * Describes parameters used to define HTTP routes. @@ -35,4 +36,5 @@ export function defineRoutes(params: RouteDefinitionParams) { defineApiKeysRoutes(params); defineIndicesRoutes(params); defineUsersRoutes(params); + defineRoleMappingRoutes(params); } diff --git a/x-pack/plugins/security/server/routes/role_mapping/delete.test.ts b/x-pack/plugins/security/server/routes/role_mapping/delete.test.ts new file mode 100644 index 0000000000000..e8a8a7216330b --- /dev/null +++ b/x-pack/plugins/security/server/routes/role_mapping/delete.test.ts @@ -0,0 +1,83 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { routeDefinitionParamsMock } from '../index.mock'; +import { elasticsearchServiceMock, httpServerMock } from 'src/core/server/mocks'; +import { kibanaResponseFactory, RequestHandlerContext } from '../../../../../../src/core/server'; +import { LICENSE_CHECK_STATE } from '../../../../licensing/server'; +import { defineRoleMappingDeleteRoutes } from './delete'; + +describe('DELETE role mappings', () => { + it('allows a role mapping to be deleted', async () => { + const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); + + const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); + mockRouteDefinitionParams.clusterClient.asScoped.mockReturnValue(mockScopedClusterClient); + mockScopedClusterClient.callAsCurrentUser.mockResolvedValue({ acknowledged: true }); + + defineRoleMappingDeleteRoutes(mockRouteDefinitionParams); + + const [[, handler]] = mockRouteDefinitionParams.router.delete.mock.calls; + + const name = 'mapping1'; + + const headers = { authorization: 'foo' }; + const mockRequest = httpServerMock.createKibanaRequest({ + method: 'delete', + path: `/internal/security/role_mapping/${name}`, + params: { name }, + headers, + }); + const mockContext = ({ + licensing: { + license: { check: jest.fn().mockReturnValue({ state: LICENSE_CHECK_STATE.Valid }) }, + }, + } as unknown) as RequestHandlerContext; + + const response = await handler(mockContext, mockRequest, kibanaResponseFactory); + expect(response.status).toBe(200); + expect(response.payload).toEqual({ acknowledged: true }); + expect(mockRouteDefinitionParams.clusterClient.asScoped).toHaveBeenCalledWith(mockRequest); + expect( + mockScopedClusterClient.callAsCurrentUser + ).toHaveBeenCalledWith('shield.deleteRoleMapping', { name }); + }); + + describe('failure', () => { + it('returns result of license check', async () => { + const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); + + defineRoleMappingDeleteRoutes(mockRouteDefinitionParams); + + const [[, handler]] = mockRouteDefinitionParams.router.delete.mock.calls; + + const name = 'mapping1'; + + const headers = { authorization: 'foo' }; + const mockRequest = httpServerMock.createKibanaRequest({ + method: 'delete', + path: `/internal/security/role_mapping/${name}`, + params: { name }, + headers, + }); + const mockContext = ({ + licensing: { + license: { + check: jest.fn().mockReturnValue({ + state: LICENSE_CHECK_STATE.Invalid, + message: 'test forbidden message', + }), + }, + }, + } as unknown) as RequestHandlerContext; + + const response = await handler(mockContext, mockRequest, kibanaResponseFactory); + expect(response.status).toBe(403); + expect(response.payload).toEqual({ message: 'test forbidden message' }); + expect(mockRouteDefinitionParams.clusterClient.asScoped).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/x-pack/plugins/security/server/routes/role_mapping/delete.ts b/x-pack/plugins/security/server/routes/role_mapping/delete.ts new file mode 100644 index 0000000000000..dc11bcd914b35 --- /dev/null +++ b/x-pack/plugins/security/server/routes/role_mapping/delete.ts @@ -0,0 +1,40 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { schema } from '@kbn/config-schema'; +import { createLicensedRouteHandler } from '../licensed_route_handler'; +import { wrapError } from '../../errors'; +import { RouteDefinitionParams } from '..'; + +export function defineRoleMappingDeleteRoutes(params: RouteDefinitionParams) { + const { clusterClient, router } = params; + + router.delete( + { + path: '/internal/security/role_mapping/{name}', + validate: { + params: schema.object({ + name: schema.string(), + }), + }, + }, + createLicensedRouteHandler(async (context, request, response) => { + try { + const deleteResponse = await clusterClient + .asScoped(request) + .callAsCurrentUser('shield.deleteRoleMapping', { + name: request.params.name, + }); + return response.ok({ body: deleteResponse }); + } catch (error) { + const wrappedError = wrapError(error); + return response.customError({ + body: wrappedError, + statusCode: wrappedError.output.statusCode, + }); + } + }) + ); +} diff --git a/x-pack/plugins/security/server/routes/role_mapping/feature_check.test.ts b/x-pack/plugins/security/server/routes/role_mapping/feature_check.test.ts new file mode 100644 index 0000000000000..f2c48fd370434 --- /dev/null +++ b/x-pack/plugins/security/server/routes/role_mapping/feature_check.test.ts @@ -0,0 +1,248 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { routeDefinitionParamsMock } from '../index.mock'; +import { elasticsearchServiceMock, httpServerMock } from 'src/core/server/mocks'; +import { + kibanaResponseFactory, + RequestHandlerContext, + IClusterClient, +} from '../../../../../../src/core/server'; +import { LICENSE_CHECK_STATE, LicenseCheck } from '../../../../licensing/server'; +import { defineRoleMappingFeatureCheckRoute } from './feature_check'; + +interface TestOptions { + licenseCheckResult?: LicenseCheck; + canManageRoleMappings?: boolean; + nodeSettingsResponse?: Record; + xpackUsageResponse?: Record; + internalUserClusterClientImpl?: IClusterClient['callAsInternalUser']; + asserts: { statusCode: number; result?: Record }; +} + +const defaultXpackUsageResponse = { + security: { + realms: { + native: { + available: true, + enabled: true, + }, + pki: { + available: true, + enabled: true, + }, + }, + }, +}; + +const getDefaultInternalUserClusterClientImpl = ( + nodeSettingsResponse: TestOptions['nodeSettingsResponse'], + xpackUsageResponse: TestOptions['xpackUsageResponse'] +) => + ((async (endpoint: string, clientParams: Record) => { + if (!clientParams) throw new TypeError('expected clientParams'); + + if (endpoint === 'nodes.info') { + return nodeSettingsResponse; + } + + if (endpoint === 'transport.request') { + if (clientParams.path === '/_xpack/usage') { + return xpackUsageResponse; + } + } + + throw new Error(`unexpected endpoint: ${endpoint}`); + }) as unknown) as TestOptions['internalUserClusterClientImpl']; + +describe('GET role mappings feature check', () => { + const getFeatureCheckTest = ( + description: string, + { + licenseCheckResult = { state: LICENSE_CHECK_STATE.Valid }, + canManageRoleMappings = true, + nodeSettingsResponse = {}, + xpackUsageResponse = defaultXpackUsageResponse, + internalUserClusterClientImpl = getDefaultInternalUserClusterClientImpl( + nodeSettingsResponse, + xpackUsageResponse + ), + asserts, + }: TestOptions + ) => { + test(description, async () => { + const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); + + const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); + mockRouteDefinitionParams.clusterClient.asScoped.mockReturnValue(mockScopedClusterClient); + mockRouteDefinitionParams.clusterClient.callAsInternalUser.mockImplementation( + internalUserClusterClientImpl + ); + + mockScopedClusterClient.callAsCurrentUser.mockImplementation(async (method, payload) => { + if (method === 'shield.hasPrivileges') { + return { + has_all_requested: canManageRoleMappings, + }; + } + }); + + defineRoleMappingFeatureCheckRoute(mockRouteDefinitionParams); + const [[, handler]] = mockRouteDefinitionParams.router.get.mock.calls; + + const headers = { authorization: 'foo' }; + const mockRequest = httpServerMock.createKibanaRequest({ + method: 'get', + path: `/internal/security/_check_role_mapping_features`, + headers, + }); + const mockContext = ({ + licensing: { license: { check: jest.fn().mockReturnValue(licenseCheckResult) } }, + } as unknown) as RequestHandlerContext; + + const response = await handler(mockContext, mockRequest, kibanaResponseFactory); + expect(response.status).toBe(asserts.statusCode); + expect(response.payload).toEqual(asserts.result); + + expect(mockContext.licensing.license.check).toHaveBeenCalledWith('security', 'basic'); + }); + }; + + getFeatureCheckTest('allows both script types with the default settings', { + asserts: { + statusCode: 200, + result: { + canManageRoleMappings: true, + canUseInlineScripts: true, + canUseStoredScripts: true, + hasCompatibleRealms: true, + }, + }, + }); + + getFeatureCheckTest('allows both script types when explicitly enabled', { + nodeSettingsResponse: { + nodes: { + someNodeId: { + settings: { + script: { + allowed_types: ['stored', 'inline'], + }, + }, + }, + }, + }, + asserts: { + statusCode: 200, + result: { + canManageRoleMappings: true, + canUseInlineScripts: true, + canUseStoredScripts: true, + hasCompatibleRealms: true, + }, + }, + }); + + getFeatureCheckTest('disallows stored scripts when disabled', { + nodeSettingsResponse: { + nodes: { + someNodeId: { + settings: { + script: { + allowed_types: ['inline'], + }, + }, + }, + }, + }, + asserts: { + statusCode: 200, + result: { + canManageRoleMappings: true, + canUseInlineScripts: true, + canUseStoredScripts: false, + hasCompatibleRealms: true, + }, + }, + }); + + getFeatureCheckTest('disallows inline scripts when disabled', { + nodeSettingsResponse: { + nodes: { + someNodeId: { + settings: { + script: { + allowed_types: ['stored'], + }, + }, + }, + }, + }, + asserts: { + statusCode: 200, + result: { + canManageRoleMappings: true, + canUseInlineScripts: false, + canUseStoredScripts: true, + hasCompatibleRealms: true, + }, + }, + }); + + getFeatureCheckTest('indicates incompatible realms when only native and file are enabled', { + xpackUsageResponse: { + security: { + realms: { + native: { + available: true, + enabled: true, + }, + file: { + available: true, + enabled: true, + }, + }, + }, + }, + asserts: { + statusCode: 200, + result: { + canManageRoleMappings: true, + canUseInlineScripts: true, + canUseStoredScripts: true, + hasCompatibleRealms: false, + }, + }, + }); + + getFeatureCheckTest('indicates canManageRoleMappings=false for users without `manage_security`', { + canManageRoleMappings: false, + asserts: { + statusCode: 200, + result: { + canManageRoleMappings: false, + }, + }, + }); + + getFeatureCheckTest( + 'falls back to allowing both script types if there is an error retrieving node settings', + { + internalUserClusterClientImpl: (() => { + return Promise.reject(new Error('something bad happened')); + }) as TestOptions['internalUserClusterClientImpl'], + asserts: { + statusCode: 200, + result: { + canManageRoleMappings: true, + canUseInlineScripts: true, + canUseStoredScripts: true, + hasCompatibleRealms: false, + }, + }, + } + ); +}); diff --git a/x-pack/plugins/security/server/routes/role_mapping/feature_check.ts b/x-pack/plugins/security/server/routes/role_mapping/feature_check.ts new file mode 100644 index 0000000000000..2be4f4cd89177 --- /dev/null +++ b/x-pack/plugins/security/server/routes/role_mapping/feature_check.ts @@ -0,0 +1,146 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { Logger, IClusterClient } from 'src/core/server'; +import { createLicensedRouteHandler } from '../licensed_route_handler'; +import { RouteDefinitionParams } from '..'; + +interface NodeSettingsResponse { + nodes: { + [nodeId: string]: { + settings: { + script: { + allowed_types?: string[]; + allowed_contexts?: string[]; + }; + }; + }; + }; +} + +interface XPackUsageResponse { + security: { + realms: { + [realmName: string]: { + available: boolean; + enabled: boolean; + }; + }; + }; +} + +const INCOMPATIBLE_REALMS = ['file', 'native']; + +export function defineRoleMappingFeatureCheckRoute({ + router, + clusterClient, + logger, +}: RouteDefinitionParams) { + router.get( + { + path: '/internal/security/_check_role_mapping_features', + validate: false, + }, + createLicensedRouteHandler(async (context, request, response) => { + const { has_all_requested: canManageRoleMappings } = await clusterClient + .asScoped(request) + .callAsCurrentUser('shield.hasPrivileges', { + body: { + cluster: ['manage_security'], + }, + }); + + if (!canManageRoleMappings) { + return response.ok({ + body: { + canManageRoleMappings, + }, + }); + } + + const enabledFeatures = await getEnabledRoleMappingsFeatures(clusterClient, logger); + + return response.ok({ + body: { + ...enabledFeatures, + canManageRoleMappings, + }, + }); + }) + ); +} + +async function getEnabledRoleMappingsFeatures(clusterClient: IClusterClient, logger: Logger) { + logger.debug(`Retrieving role mappings features`); + + const nodeScriptSettingsPromise: Promise = clusterClient + .callAsInternalUser('nodes.info', { + filterPath: 'nodes.*.settings.script', + }) + .catch(error => { + // fall back to assuming that node settings are unset/at their default values. + // this will allow the role mappings UI to permit both role template script types, + // even if ES will disallow it at mapping evaluation time. + logger.error(`Error retrieving node settings for role mappings: ${error}`); + return {}; + }); + + const xpackUsagePromise: Promise = clusterClient + // `transport.request` is potentially unsafe when combined with untrusted user input. + // Do not augment with such input. + .callAsInternalUser('transport.request', { + method: 'GET', + path: '/_xpack/usage', + }) + .catch(error => { + // fall back to no external realms configured. + // this will cause a warning in the UI about no compatible realms being enabled, but will otherwise allow + // the mappings screen to function correctly. + logger.error(`Error retrieving XPack usage info for role mappings: ${error}`); + return { + security: { + realms: {}, + }, + } as XPackUsageResponse; + }); + + const [nodeScriptSettings, xpackUsage] = await Promise.all([ + nodeScriptSettingsPromise, + xpackUsagePromise, + ]); + + let canUseStoredScripts = true; + let canUseInlineScripts = true; + if (usesCustomScriptSettings(nodeScriptSettings)) { + canUseStoredScripts = Object.values(nodeScriptSettings.nodes).some(node => { + const allowedTypes = node.settings.script.allowed_types; + return !allowedTypes || allowedTypes.includes('stored'); + }); + + canUseInlineScripts = Object.values(nodeScriptSettings.nodes).some(node => { + const allowedTypes = node.settings.script.allowed_types; + return !allowedTypes || allowedTypes.includes('inline'); + }); + } + + const hasCompatibleRealms = Object.entries(xpackUsage.security.realms).some( + ([realmName, realm]) => { + return !INCOMPATIBLE_REALMS.includes(realmName) && realm.available && realm.enabled; + } + ); + + return { + hasCompatibleRealms, + canUseStoredScripts, + canUseInlineScripts, + }; +} + +function usesCustomScriptSettings( + nodeResponse: NodeSettingsResponse | {} +): nodeResponse is NodeSettingsResponse { + return nodeResponse.hasOwnProperty('nodes'); +} diff --git a/x-pack/plugins/security/server/routes/role_mapping/get.test.ts b/x-pack/plugins/security/server/routes/role_mapping/get.test.ts new file mode 100644 index 0000000000000..c60d5518097ba --- /dev/null +++ b/x-pack/plugins/security/server/routes/role_mapping/get.test.ts @@ -0,0 +1,253 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import Boom from 'boom'; +import { routeDefinitionParamsMock } from '../index.mock'; +import { elasticsearchServiceMock, httpServerMock } from 'src/core/server/mocks'; +import { defineRoleMappingGetRoutes } from './get'; +import { kibanaResponseFactory, RequestHandlerContext } from '../../../../../../src/core/server'; +import { LICENSE_CHECK_STATE } from '../../../../licensing/server'; + +const mockRoleMappingResponse = { + mapping1: { + enabled: true, + roles: ['foo', 'bar'], + rules: { + field: { + dn: 'CN=bob,OU=example,O=com', + }, + }, + }, + mapping2: { + enabled: true, + role_templates: [{ template: JSON.stringify({ source: 'foo_{{username}}' }) }], + rules: { + any: [ + { + field: { + dn: 'CN=admin,OU=example,O=com', + }, + }, + { + field: { + username: 'admin_*', + }, + }, + ], + }, + }, + mapping3: { + enabled: true, + role_templates: [{ template: 'template with invalid json' }], + rules: { + field: { + dn: 'CN=bob,OU=example,O=com', + }, + }, + }, +}; + +describe('GET role mappings', () => { + it('returns all role mappings', async () => { + const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); + + const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); + mockRouteDefinitionParams.clusterClient.asScoped.mockReturnValue(mockScopedClusterClient); + mockScopedClusterClient.callAsCurrentUser.mockResolvedValue(mockRoleMappingResponse); + + defineRoleMappingGetRoutes(mockRouteDefinitionParams); + + const [[, handler]] = mockRouteDefinitionParams.router.get.mock.calls; + + const headers = { authorization: 'foo' }; + const mockRequest = httpServerMock.createKibanaRequest({ + method: 'get', + path: `/internal/security/role_mapping`, + headers, + }); + const mockContext = ({ + licensing: { + license: { check: jest.fn().mockReturnValue({ state: LICENSE_CHECK_STATE.Valid }) }, + }, + } as unknown) as RequestHandlerContext; + + const response = await handler(mockContext, mockRequest, kibanaResponseFactory); + expect(response.status).toBe(200); + expect(response.payload).toEqual([ + { + name: 'mapping1', + enabled: true, + roles: ['foo', 'bar'], + role_templates: [], + rules: { + field: { + dn: 'CN=bob,OU=example,O=com', + }, + }, + }, + { + name: 'mapping2', + enabled: true, + role_templates: [{ template: { source: 'foo_{{username}}' } }], + rules: { + any: [ + { + field: { + dn: 'CN=admin,OU=example,O=com', + }, + }, + { + field: { + username: 'admin_*', + }, + }, + ], + }, + }, + { + name: 'mapping3', + enabled: true, + role_templates: [{ template: 'template with invalid json' }], + rules: { + field: { + dn: 'CN=bob,OU=example,O=com', + }, + }, + }, + ]); + + expect(mockRouteDefinitionParams.clusterClient.asScoped).toHaveBeenCalledWith(mockRequest); + expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledWith( + 'shield.getRoleMappings', + { name: undefined } + ); + }); + + it('returns role mapping by name', async () => { + const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); + + const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); + mockRouteDefinitionParams.clusterClient.asScoped.mockReturnValue(mockScopedClusterClient); + mockScopedClusterClient.callAsCurrentUser.mockResolvedValue({ + mapping1: { + enabled: true, + roles: ['foo', 'bar'], + rules: { + field: { + dn: 'CN=bob,OU=example,O=com', + }, + }, + }, + }); + + defineRoleMappingGetRoutes(mockRouteDefinitionParams); + + const [[, handler]] = mockRouteDefinitionParams.router.get.mock.calls; + + const name = 'mapping1'; + + const headers = { authorization: 'foo' }; + const mockRequest = httpServerMock.createKibanaRequest({ + method: 'get', + path: `/internal/security/role_mapping/${name}`, + params: { name }, + headers, + }); + const mockContext = ({ + licensing: { + license: { check: jest.fn().mockReturnValue({ state: LICENSE_CHECK_STATE.Valid }) }, + }, + } as unknown) as RequestHandlerContext; + + const response = await handler(mockContext, mockRequest, kibanaResponseFactory); + expect(response.status).toBe(200); + expect(response.payload).toEqual({ + name: 'mapping1', + enabled: true, + roles: ['foo', 'bar'], + role_templates: [], + rules: { + field: { + dn: 'CN=bob,OU=example,O=com', + }, + }, + }); + + expect(mockRouteDefinitionParams.clusterClient.asScoped).toHaveBeenCalledWith(mockRequest); + expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledWith( + 'shield.getRoleMappings', + { name } + ); + }); + + describe('failure', () => { + it('returns result of license check', async () => { + const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); + + defineRoleMappingGetRoutes(mockRouteDefinitionParams); + + const [[, handler]] = mockRouteDefinitionParams.router.get.mock.calls; + + const headers = { authorization: 'foo' }; + const mockRequest = httpServerMock.createKibanaRequest({ + method: 'get', + path: `/internal/security/role_mapping`, + headers, + }); + const mockContext = ({ + licensing: { + license: { + check: jest.fn().mockReturnValue({ + state: LICENSE_CHECK_STATE.Invalid, + message: 'test forbidden message', + }), + }, + }, + } as unknown) as RequestHandlerContext; + + const response = await handler(mockContext, mockRequest, kibanaResponseFactory); + expect(response.status).toBe(403); + expect(response.payload).toEqual({ message: 'test forbidden message' }); + expect(mockRouteDefinitionParams.clusterClient.asScoped).not.toHaveBeenCalled(); + }); + + it('returns a 404 when the role mapping is not found', async () => { + const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); + + const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); + mockRouteDefinitionParams.clusterClient.asScoped.mockReturnValue(mockScopedClusterClient); + mockScopedClusterClient.callAsCurrentUser.mockRejectedValue( + Boom.notFound('role mapping not found!') + ); + + defineRoleMappingGetRoutes(mockRouteDefinitionParams); + + const [[, handler]] = mockRouteDefinitionParams.router.get.mock.calls; + + const name = 'mapping1'; + + const headers = { authorization: 'foo' }; + const mockRequest = httpServerMock.createKibanaRequest({ + method: 'get', + path: `/internal/security/role_mapping/${name}`, + params: { name }, + headers, + }); + const mockContext = ({ + licensing: { + license: { check: jest.fn().mockReturnValue({ state: LICENSE_CHECK_STATE.Valid }) }, + }, + } as unknown) as RequestHandlerContext; + + const response = await handler(mockContext, mockRequest, kibanaResponseFactory); + expect(response.status).toBe(404); + expect(mockRouteDefinitionParams.clusterClient.asScoped).toHaveBeenCalledWith(mockRequest); + expect( + mockScopedClusterClient.callAsCurrentUser + ).toHaveBeenCalledWith('shield.getRoleMappings', { name }); + }); + }); +}); diff --git a/x-pack/plugins/security/server/routes/role_mapping/get.ts b/x-pack/plugins/security/server/routes/role_mapping/get.ts new file mode 100644 index 0000000000000..9cd5cf83092e1 --- /dev/null +++ b/x-pack/plugins/security/server/routes/role_mapping/get.ts @@ -0,0 +1,80 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { schema } from '@kbn/config-schema'; +import { RoleMapping } from '../../../../../legacy/plugins/security/common/model'; +import { createLicensedRouteHandler } from '../licensed_route_handler'; +import { wrapError } from '../../errors'; +import { RouteDefinitionParams } from '..'; + +interface RoleMappingsResponse { + [roleMappingName: string]: Omit; +} + +export function defineRoleMappingGetRoutes(params: RouteDefinitionParams) { + const { clusterClient, logger, router } = params; + + router.get( + { + path: '/internal/security/role_mapping/{name?}', + validate: { + params: schema.object({ + name: schema.maybe(schema.string()), + }), + }, + }, + createLicensedRouteHandler(async (context, request, response) => { + const expectSingleEntity = typeof request.params.name === 'string'; + + try { + const roleMappingsResponse: RoleMappingsResponse = await clusterClient + .asScoped(request) + .callAsCurrentUser('shield.getRoleMappings', { + name: request.params.name, + }); + + const mappings = Object.entries(roleMappingsResponse).map(([name, mapping]) => { + return { + name, + ...mapping, + role_templates: (mapping.role_templates || []).map(entry => { + return { + ...entry, + template: tryParseRoleTemplate(entry.template as string), + }; + }), + } as RoleMapping; + }); + + if (expectSingleEntity) { + return response.ok({ body: mappings[0] }); + } + return response.ok({ body: mappings }); + } catch (error) { + const wrappedError = wrapError(error); + return response.customError({ + body: wrappedError, + statusCode: wrappedError.output.statusCode, + }); + } + }) + ); + + /** + * While role templates are normally persisted as objects via the create API, they are stored internally as strings. + * As a result, the ES APIs to retrieve role mappings represent the templates as strings, so we have to attempt + * to parse them back out. ES allows for invalid JSON to be stored, so we have to account for that as well. + * + * @param roleTemplate the string-based template to parse + */ + function tryParseRoleTemplate(roleTemplate: string) { + try { + return JSON.parse(roleTemplate); + } catch (e) { + logger.debug(`Role template is not valid JSON: ${e}`); + return roleTemplate; + } + } +} diff --git a/x-pack/plugins/security/server/routes/role_mapping/index.ts b/x-pack/plugins/security/server/routes/role_mapping/index.ts new file mode 100644 index 0000000000000..1bd90e8c1fae3 --- /dev/null +++ b/x-pack/plugins/security/server/routes/role_mapping/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { defineRoleMappingFeatureCheckRoute } from './feature_check'; +import { defineRoleMappingGetRoutes } from './get'; +import { defineRoleMappingPostRoutes } from './post'; +import { defineRoleMappingDeleteRoutes } from './delete'; +import { RouteDefinitionParams } from '..'; + +export function defineRoleMappingRoutes(params: RouteDefinitionParams) { + defineRoleMappingFeatureCheckRoute(params); + defineRoleMappingGetRoutes(params); + defineRoleMappingPostRoutes(params); + defineRoleMappingDeleteRoutes(params); +} diff --git a/x-pack/plugins/security/server/routes/role_mapping/post.test.ts b/x-pack/plugins/security/server/routes/role_mapping/post.test.ts new file mode 100644 index 0000000000000..7d820d668a6da --- /dev/null +++ b/x-pack/plugins/security/server/routes/role_mapping/post.test.ts @@ -0,0 +1,103 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { routeDefinitionParamsMock } from '../index.mock'; +import { elasticsearchServiceMock, httpServerMock } from 'src/core/server/mocks'; +import { kibanaResponseFactory, RequestHandlerContext } from '../../../../../../src/core/server'; +import { LICENSE_CHECK_STATE } from '../../../../licensing/server'; +import { defineRoleMappingPostRoutes } from './post'; + +describe('POST role mappings', () => { + it('allows a role mapping to be created', async () => { + const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); + + const mockScopedClusterClient = elasticsearchServiceMock.createScopedClusterClient(); + mockRouteDefinitionParams.clusterClient.asScoped.mockReturnValue(mockScopedClusterClient); + mockScopedClusterClient.callAsCurrentUser.mockResolvedValue({ created: true }); + + defineRoleMappingPostRoutes(mockRouteDefinitionParams); + + const [[, handler]] = mockRouteDefinitionParams.router.post.mock.calls; + + const name = 'mapping1'; + + const headers = { authorization: 'foo' }; + const mockRequest = httpServerMock.createKibanaRequest({ + method: 'post', + path: `/internal/security/role_mapping/${name}`, + params: { name }, + body: { + enabled: true, + roles: ['foo', 'bar'], + rules: { + field: { + dn: 'CN=bob,OU=example,O=com', + }, + }, + }, + headers, + }); + const mockContext = ({ + licensing: { + license: { check: jest.fn().mockReturnValue({ state: LICENSE_CHECK_STATE.Valid }) }, + }, + } as unknown) as RequestHandlerContext; + + const response = await handler(mockContext, mockRequest, kibanaResponseFactory); + expect(response.status).toBe(200); + expect(response.payload).toEqual({ created: true }); + + expect(mockRouteDefinitionParams.clusterClient.asScoped).toHaveBeenCalledWith(mockRequest); + expect(mockScopedClusterClient.callAsCurrentUser).toHaveBeenCalledWith( + 'shield.saveRoleMapping', + { + name, + body: { + enabled: true, + roles: ['foo', 'bar'], + rules: { + field: { + dn: 'CN=bob,OU=example,O=com', + }, + }, + }, + } + ); + }); + + describe('failure', () => { + it('returns result of license check', async () => { + const mockRouteDefinitionParams = routeDefinitionParamsMock.create(); + + defineRoleMappingPostRoutes(mockRouteDefinitionParams); + + const [[, handler]] = mockRouteDefinitionParams.router.post.mock.calls; + + const headers = { authorization: 'foo' }; + const mockRequest = httpServerMock.createKibanaRequest({ + method: 'post', + path: `/internal/security/role_mapping`, + headers, + }); + const mockContext = ({ + licensing: { + license: { + check: jest.fn().mockReturnValue({ + state: LICENSE_CHECK_STATE.Invalid, + message: 'test forbidden message', + }), + }, + }, + } as unknown) as RequestHandlerContext; + + const response = await handler(mockContext, mockRequest, kibanaResponseFactory); + expect(response.status).toBe(403); + expect(response.payload).toEqual({ message: 'test forbidden message' }); + + expect(mockRouteDefinitionParams.clusterClient.asScoped).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/x-pack/plugins/security/server/routes/role_mapping/post.ts b/x-pack/plugins/security/server/routes/role_mapping/post.ts new file mode 100644 index 0000000000000..bf9112be4ad3f --- /dev/null +++ b/x-pack/plugins/security/server/routes/role_mapping/post.ts @@ -0,0 +1,62 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { schema } from '@kbn/config-schema'; +import { createLicensedRouteHandler } from '../licensed_route_handler'; +import { wrapError } from '../../errors'; +import { RouteDefinitionParams } from '..'; + +export function defineRoleMappingPostRoutes(params: RouteDefinitionParams) { + const { clusterClient, router } = params; + + router.post( + { + path: '/internal/security/role_mapping/{name}', + validate: { + params: schema.object({ + name: schema.string(), + }), + body: schema.object({ + roles: schema.arrayOf(schema.string(), { defaultValue: [] }), + role_templates: schema.arrayOf( + schema.object({ + // Not validating `template` because the ES API currently accepts invalid payloads here. + // We allow this as well so that existing mappings can be updated via our Role Management UI + template: schema.any(), + format: schema.maybe( + schema.oneOf([schema.literal('string'), schema.literal('json')]) + ), + }), + { defaultValue: [] } + ), + enabled: schema.boolean(), + // Also lax on validation here because the real rules get quite complex, + // and keeping this in sync (and testable!) with ES could prove problematic. + // We do not interpret any of these rules within this route handler; + // they are simply passed to ES for processing. + rules: schema.object({}, { allowUnknowns: true }), + metadata: schema.object({}, { allowUnknowns: true }), + }), + }, + }, + createLicensedRouteHandler(async (context, request, response) => { + try { + const saveResponse = await clusterClient + .asScoped(request) + .callAsCurrentUser('shield.saveRoleMapping', { + name: request.params.name, + body: request.body, + }); + return response.ok({ body: saveResponse }); + } catch (error) { + const wrappedError = wrapError(error); + return response.customError({ + body: wrappedError, + statusCode: wrappedError.output.statusCode, + }); + } + }) + ); +} diff --git a/x-pack/plugins/spaces/server/lib/create_default_space.ts b/x-pack/plugins/spaces/server/lib/create_default_space.ts index 2301fa26dab28..0d1a4ddab91bb 100644 --- a/x-pack/plugins/spaces/server/lib/create_default_space.ts +++ b/x-pack/plugins/spaces/server/lib/create_default_space.ts @@ -9,7 +9,7 @@ import { SavedObjectsLegacyService, IClusterClient } from 'src/core/server'; import { DEFAULT_SPACE_ID } from '../../common/constants'; interface Deps { - esClient: Pick; + esClient: IClusterClient; savedObjects: SavedObjectsLegacyService; } diff --git a/x-pack/plugins/spaces/server/lib/request_interceptors/on_post_auth_interceptor.test.ts b/x-pack/plugins/spaces/server/lib/request_interceptors/on_post_auth_interceptor.test.ts index 2b0cfd3687a24..c1f557f164ad6 100644 --- a/x-pack/plugins/spaces/server/lib/request_interceptors/on_post_auth_interceptor.test.ts +++ b/x-pack/plugins/spaces/server/lib/request_interceptors/on_post_auth_interceptor.test.ts @@ -168,7 +168,7 @@ describe('onPostAuthInterceptor', () => { const spacesService = await service.setup({ http: (http as unknown) as CoreSetup['http'], - elasticsearch: elasticsearchServiceMock.createSetupContract(), + elasticsearch: elasticsearchServiceMock.createSetup(), authorization: securityMock.createSetup().authz, getSpacesAuditLogger: () => ({} as SpacesAuditLogger), config$: Rx.of(spacesConfig), diff --git a/x-pack/plugins/spaces/server/lib/spaces_tutorial_context_factory.test.ts b/x-pack/plugins/spaces/server/lib/spaces_tutorial_context_factory.test.ts index 9e1bb5cf9f7d6..a3396e98c3512 100644 --- a/x-pack/plugins/spaces/server/lib/spaces_tutorial_context_factory.test.ts +++ b/x-pack/plugins/spaces/server/lib/spaces_tutorial_context_factory.test.ts @@ -50,7 +50,7 @@ describe('createSpacesTutorialContextFactory', () => { it('should create context with the current space id for the default space', async () => { const spacesService = await service.setup({ http: coreMock.createSetup().http, - elasticsearch: elasticsearchServiceMock.createSetupContract(), + elasticsearch: elasticsearchServiceMock.createSetup(), authorization: securityMock.createSetup().authz, getSpacesAuditLogger: () => ({} as SpacesAuditLogger), config$: Rx.of(spacesConfig), diff --git a/x-pack/plugins/spaces/server/plugin.ts b/x-pack/plugins/spaces/server/plugin.ts index dc4bc839c0e29..6b7699100032d 100644 --- a/x-pack/plugins/spaces/server/plugin.ts +++ b/x-pack/plugins/spaces/server/plugin.ts @@ -5,7 +5,6 @@ */ import { Observable } from 'rxjs'; -import { take } from 'rxjs/operators'; import { UsageCollectionSetup } from 'src/plugins/usage_collection/server'; import { HomeServerPluginSetup } from 'src/plugins/home/server'; import { @@ -168,9 +167,8 @@ export class Plugin { ); }, createDefaultSpace: async () => { - const esClient = await core.elasticsearch.adminClient$.pipe(take(1)).toPromise(); - return createDefaultSpace({ - esClient, + return await createDefaultSpace({ + esClient: core.elasticsearch.adminClient, savedObjects: this.getLegacyAPI().savedObjects, }); }, diff --git a/x-pack/plugins/spaces/server/routes/api/external/copy_to_space.test.ts b/x-pack/plugins/spaces/server/routes/api/external/copy_to_space.test.ts index 4d8d08a487e9a..74197e6ca7556 100644 --- a/x-pack/plugins/spaces/server/routes/api/external/copy_to_space.test.ts +++ b/x-pack/plugins/spaces/server/routes/api/external/copy_to_space.test.ts @@ -43,7 +43,7 @@ describe('copy to space', () => { const service = new SpacesService(log, () => legacyAPI); const spacesService = await service.setup({ http: (httpService as unknown) as CoreSetup['http'], - elasticsearch: elasticsearchServiceMock.createSetupContract(), + elasticsearch: elasticsearchServiceMock.createSetup(), authorization: securityMock.createSetup().authz, getSpacesAuditLogger: () => ({} as SpacesAuditLogger), config$: Rx.of(spacesConfig), diff --git a/x-pack/plugins/spaces/server/routes/api/external/delete.test.ts b/x-pack/plugins/spaces/server/routes/api/external/delete.test.ts index 28d5708a3873c..35f18cf66a57e 100644 --- a/x-pack/plugins/spaces/server/routes/api/external/delete.test.ts +++ b/x-pack/plugins/spaces/server/routes/api/external/delete.test.ts @@ -44,7 +44,7 @@ describe('Spaces Public API', () => { const service = new SpacesService(log, () => legacyAPI); const spacesService = await service.setup({ http: (httpService as unknown) as CoreSetup['http'], - elasticsearch: elasticsearchServiceMock.createSetupContract(), + elasticsearch: elasticsearchServiceMock.createSetup(), authorization: securityMock.createSetup().authz, getSpacesAuditLogger: () => ({} as SpacesAuditLogger), config$: Rx.of(spacesConfig), diff --git a/x-pack/plugins/spaces/server/routes/api/external/get.test.ts b/x-pack/plugins/spaces/server/routes/api/external/get.test.ts index f9bd4494791f1..3300e30825283 100644 --- a/x-pack/plugins/spaces/server/routes/api/external/get.test.ts +++ b/x-pack/plugins/spaces/server/routes/api/external/get.test.ts @@ -42,7 +42,7 @@ describe('GET space', () => { const service = new SpacesService(log, () => legacyAPI); const spacesService = await service.setup({ http: (httpService as unknown) as CoreSetup['http'], - elasticsearch: elasticsearchServiceMock.createSetupContract(), + elasticsearch: elasticsearchServiceMock.createSetup(), authorization: securityMock.createSetup().authz, getSpacesAuditLogger: () => ({} as SpacesAuditLogger), config$: Rx.of(spacesConfig), diff --git a/x-pack/plugins/spaces/server/routes/api/external/get_all.test.ts b/x-pack/plugins/spaces/server/routes/api/external/get_all.test.ts index 02219db88a04c..ca89731f35946 100644 --- a/x-pack/plugins/spaces/server/routes/api/external/get_all.test.ts +++ b/x-pack/plugins/spaces/server/routes/api/external/get_all.test.ts @@ -42,7 +42,7 @@ describe('GET /spaces/space', () => { const service = new SpacesService(log, () => legacyAPI); const spacesService = await service.setup({ http: (httpService as unknown) as CoreSetup['http'], - elasticsearch: elasticsearchServiceMock.createSetupContract(), + elasticsearch: elasticsearchServiceMock.createSetup(), authorization: securityMock.createSetup().authz, getSpacesAuditLogger: () => ({} as SpacesAuditLogger), config$: Rx.of(spacesConfig), diff --git a/x-pack/plugins/spaces/server/routes/api/external/post.test.ts b/x-pack/plugins/spaces/server/routes/api/external/post.test.ts index d82ccaa8ff380..26ecbf2247e0f 100644 --- a/x-pack/plugins/spaces/server/routes/api/external/post.test.ts +++ b/x-pack/plugins/spaces/server/routes/api/external/post.test.ts @@ -43,7 +43,7 @@ describe('Spaces Public API', () => { const service = new SpacesService(log, () => legacyAPI); const spacesService = await service.setup({ http: (httpService as unknown) as CoreSetup['http'], - elasticsearch: elasticsearchServiceMock.createSetupContract(), + elasticsearch: elasticsearchServiceMock.createSetup(), authorization: securityMock.createSetup().authz, getSpacesAuditLogger: () => ({} as SpacesAuditLogger), config$: Rx.of(spacesConfig), diff --git a/x-pack/plugins/spaces/server/routes/api/external/put.test.ts b/x-pack/plugins/spaces/server/routes/api/external/put.test.ts index 15837110f4d92..e6182e027b854 100644 --- a/x-pack/plugins/spaces/server/routes/api/external/put.test.ts +++ b/x-pack/plugins/spaces/server/routes/api/external/put.test.ts @@ -44,7 +44,7 @@ describe('PUT /api/spaces/space', () => { const service = new SpacesService(log, () => legacyAPI); const spacesService = await service.setup({ http: (httpService as unknown) as CoreSetup['http'], - elasticsearch: elasticsearchServiceMock.createSetupContract(), + elasticsearch: elasticsearchServiceMock.createSetup(), authorization: securityMock.createSetup().authz, getSpacesAuditLogger: () => ({} as SpacesAuditLogger), config$: Rx.of(spacesConfig), diff --git a/x-pack/plugins/spaces/server/routes/api/internal/get_active_space.test.ts b/x-pack/plugins/spaces/server/routes/api/internal/get_active_space.test.ts index 8f97df2aa0a50..461f816ff5019 100644 --- a/x-pack/plugins/spaces/server/routes/api/internal/get_active_space.test.ts +++ b/x-pack/plugins/spaces/server/routes/api/internal/get_active_space.test.ts @@ -22,7 +22,7 @@ describe('GET /internal/spaces/_active_space', () => { const service = new SpacesService(null as any, () => legacyAPI); const spacesService = await service.setup({ http: (httpService as unknown) as CoreSetup['http'], - elasticsearch: elasticsearchServiceMock.createSetupContract(), + elasticsearch: elasticsearchServiceMock.createSetup(), authorization: null, getSpacesAuditLogger: () => ({} as SpacesAuditLogger), config$: Rx.of(spacesConfig), diff --git a/x-pack/plugins/spaces/server/spaces_service/spaces_service.test.ts b/x-pack/plugins/spaces/server/spaces_service/spaces_service.test.ts index 30ad3f399916b..68d096e046ed4 100644 --- a/x-pack/plugins/spaces/server/spaces_service/spaces_service.test.ts +++ b/x-pack/plugins/spaces/server/spaces_service/spaces_service.test.ts @@ -74,7 +74,7 @@ const createService = async (serverBasePath: string = '') => { const spacesServiceSetup = await spacesService.setup({ http: httpSetup, - elasticsearch: elasticsearchServiceMock.createSetupContract(), + elasticsearch: elasticsearchServiceMock.createSetup(), config$: Rx.of(spacesConfig), authorization: securityMock.createSetup().authz, getSpacesAuditLogger: () => new SpacesAuditLogger({}), diff --git a/x-pack/plugins/spaces/server/spaces_service/spaces_service.ts b/x-pack/plugins/spaces/server/spaces_service/spaces_service.ts index b8d0f910a42ea..f8ed58fa57551 100644 --- a/x-pack/plugins/spaces/server/spaces_service/spaces_service.ts +++ b/x-pack/plugins/spaces/server/spaces_service/spaces_service.ts @@ -5,7 +5,7 @@ */ import { map, take } from 'rxjs/operators'; -import { Observable, Subscription, combineLatest } from 'rxjs'; +import { Observable, Subscription } from 'rxjs'; import { Legacy } from 'kibana'; import { Logger, KibanaRequest, CoreSetup } from '../../../../../src/core/server'; import { PluginSetupContract as SecurityPluginSetup } from '../../../security/server'; @@ -69,15 +69,15 @@ export class SpacesService { }; const getScopedClient = async (request: KibanaRequest) => { - return combineLatest(elasticsearch.adminClient$, config$) + return config$ .pipe( - map(([clusterClient, config]) => { + map(config => { const internalRepository = this.getLegacyAPI().savedObjects.getSavedObjectsRepository( - clusterClient.callAsInternalUser, + elasticsearch.adminClient.callAsInternalUser, ['space'] ); - const callCluster = clusterClient.asScoped(request).callAsCurrentUser; + const callCluster = elasticsearch.adminClient.asScoped(request).callAsCurrentUser; const callWithRequestRepository = this.getLegacyAPI().savedObjects.getSavedObjectsRepository( callCluster, diff --git a/x-pack/plugins/task_manager/kibana.json b/x-pack/plugins/task_manager/kibana.json new file mode 100644 index 0000000000000..ad2d5d00ae0be --- /dev/null +++ b/x-pack/plugins/task_manager/kibana.json @@ -0,0 +1,8 @@ +{ + "id": "taskManager", + "server": true, + "version": "8.0.0", + "kibanaVersion": "kibana", + "configPath": ["xpack", "task_manager"], + "ui": false +} diff --git a/x-pack/plugins/task_manager/server/README.md b/x-pack/plugins/task_manager/server/README.md new file mode 100644 index 0000000000000..a067358dc8841 --- /dev/null +++ b/x-pack/plugins/task_manager/server/README.md @@ -0,0 +1,458 @@ +# Kibana task manager + +The task manager is a generic system for running background tasks. It supports: + +- Single-run and recurring tasks +- Scheduling tasks to run after a specified datetime +- Basic retry logic +- Recovery of stalled tasks / timeouts +- Tracking task state across multiple runs +- Configuring the run-parameters for specific tasks +- Basic coordination to prevent the same task instance from running on more than one Kibana system at a time + +## Implementation details + +At a high-level, the task manager works like this: + +- Every `{poll_interval}` milliseconds, check the `{index}` for any tasks that need to be run: + - `runAt` is past + - `attempts` is less than the configured threshold +- Attempt to claim the task by using optimistic concurrency to set: + - status to `running` + - `startedAt` to now + - `retryAt` to next time task should retry if it times out and is still in `running` status +- Execute the task, if the previous claim succeeded +- If the task fails, increment the `attempts` count and reschedule it +- If the task succeeds: + - If it is recurring, store the result of the run, and reschedule + - If it is not recurring, remove it from the index + +## Pooling + +Each task manager instance runs tasks in a pool which ensures that at most N tasks are run at a time, where N is configurable. This prevents the system from running too many tasks at once in resource constrained environments. In addition to this, each individual task type definition can have `numWorkers` specified, which tells the system how many workers are consumed by a single running instance of a task. This effectively limits how many tasks of a given type can be run at once. + +For example, we may have a system with a `max_workers` of 10, but a super expensive task (such as `reporting`) which specifies a `numWorkers` of 10. In this case, `reporting` tasks will run one at a time. + +If a task specifies a higher `numWorkers` than the system supports, the system's `max_workers` setting will be substituted for it. + +## Config options + +The task_manager can be configured via `taskManager` config options (e.g. `taskManager.maxAttempts`): + +- `max_attempts` - The maximum number of times a task will be attempted before being abandoned as failed +- `poll_interval` - How often the background worker should check the task_manager index for more work +- `index` - The name of the index that the task_manager +- `max_workers` - The maximum number of tasks a Kibana will run concurrently (defaults to 10) +- `credentials` - Encrypted user credentials. All tasks will run in the security context of this user. See [this issue](https://github.com/elastic/dev/issues/1045) for a discussion on task scheduler security. +- `override_num_workers`: An object of `taskType: number` that overrides the `num_workers` for tasks + - For example: `task_manager.override_num_workers.reporting: 2` would override the number of workers occupied by tasks of type `reporting` + - This allows sysadmins to tweak the operational performance of Kibana, allowing more or fewer tasks of a specific type to run simultaneously + +## Task definitions + +Plugins define tasks by calling the `registerTaskDefinitions` method on the `server.plugins.task_manager` object. + +A sample task can be found in the [x-pack/test/plugin_api_integration/plugins/task_manager](../../test/plugin_api_integration/plugins/task_manager/index.js) folder. + +```js +export class Plugin { + constructor() { + } + + public setup(core: CoreSetup, plugins: { taskManager }) { + taskManager.registerTaskDefinitions({ + // clusterMonitoring is the task type, and must be unique across the entire system + clusterMonitoring: { + // Human friendly name, used to represent this task in logs, UI, etc + title: 'Human friendly name', + + // Optional, human-friendly, more detailed description + description: 'Amazing!!', + + // Optional, how long, in minutes or seconds, the system should wait before + // a running instance of this task is considered to be timed out. + // This defaults to 5 minutes. + timeout: '5m', + + // Optional, how many attempts before marking task as failed. + // This defaults to what is configured at the task manager level. + maxAttempts: 5, + + // The clusterMonitoring task occupies 2 workers, so if the system has 10 worker slots, + // 5 clusterMonitoring tasks could run concurrently per Kibana instance. This value is + // overridden by the `override_num_workers` config value, if specified. + numWorkers: 2, + + // The createTaskRunner function / method returns an object that is responsible for + // performing the work of the task. context: { taskInstance }, is documented below. + createTaskRunner(context) { + return { + // Perform the work of the task. The return value should fit the TaskResult interface, documented + // below. Invalid return values will result in a logged warning. + async run() { + // Do some work + // Conditionally send some alerts + // Return some result or other... + }, + + // Optional, will be called if a running instance of this task times out, allowing the task + // to attempt to clean itself up. + async cancel() { + // Do whatever is required to cancel this task, such as killing any spawned processes + }, + }; + }, + }, + }); + } + + public start(core: CoreStart, plugins: { taskManager }) { + + } +} +``` + +When Kibana attempts to claim and run a task instance, it looks its definition up, and executes its createTaskRunner's method, passing it a run context which looks like this: + +```js +{ + // The data associated with this instance of the task, with two properties being most notable: + // + // params: + // An object, specific to this task instance, used by the + // task to determine exactly what work should be performed. + // e.g. a cluster-monitoring task might have a `clusterName` + // property in here, but a movie-monitoring task might have + // a `directorName` property. + // + // state: + // The state returned from the previous run of this task instance. + // If this task instance has never succesfully run, this will + // be an empty object: {} + taskInstance, +} +``` + +## Task result + +The task runner's `run` method is expected to return a promise that resolves to undefined or to an object that looks like the following: +```js +{ + // Optional, if specified, this is used as the tasks' nextRun, overriding + // the default system scheduler. + runAt: "2020-07-24T17:34:35.272Z", + + // Optional, an error object, logged out as a warning. The pressence of this + // property indicates that the task did not succeed. + error: { message: 'Hrumph!' }, + + // Optional, this will be passed into the next run of the task, if + // this is a recurring task. + state: { + anything: 'goes here', + }, +} +``` + +Other return values will result in a warning, but the system should continue to work. + +## Task instances + +The task_manager module will store scheduled task instances in an index. This allows for recovery of failed tasks, coordination across Kibana clusters, persistence across Kibana reboots, etc. + +The data stored for a task instance looks something like this: + +```js +{ + // The type of task that will run this instance. + taskType: 'clusterMonitoring', + + // The next time this task instance should run. It is not guaranteed + // to run at this time, but it is guaranteed not to run earlier than + // this. + runAt: "2020-07-24T17:34:35.272Z", + + // Indicates that this is a recurring task. We currently only support + // interval syntax with either minutes such as `5m` or seconds `10s`. + schedule: { interval: '5m' }, + + // How many times this task has been unsuccesfully attempted, + // this will be reset to 0 if the task ever succesfully completes. + // This is incremented if a task fails or times out. + attempts: 0, + + // Currently, this is either idle | claiming | running | failed. It is used to + // coordinate which Kibana instance owns / is running a specific + // task instance. + // idle: Task Instance isn't being worked on + // claiming: A Kibana instance has claimed ownership but hasn't started running + // the Task Instance yet + // running: A Kibana instance has began working on the Task Instance + // failed: The last run of the Task Instance failed, waiting to retry + status: 'idle', + + // The params specific to this task instance, which will be + // passed to the task when it runs, and will be used by the + // task to determine exactly what work should be performed. + // This is a JSON blob, and will be different per task type. + // e.g. a cluster-monitoring task might have a `clusterName` + // property in here, but a movie-monitoring task might have + // a `directorName` property. + params: '{ "task": "specific stuff here" }', + + // The result of the previous run of this task instance. This + // will be passed to the next run of the task, along with the + // params, and could be used by a task to do special logic If + // the task state changes (e.g. from green to red, or foo to bar) + // If there was no previous run (e.g. the instance has never succesfully + // completed, this will be an empty object.). This is a JSON blob, + // and will be different per task type. + state: '{ "status": "green" }', + + // An extension point for 3rd parties to build in security features on + // top of the task manager. For example, this might be the token of the user + // who scheduled this task. + userContext: 'the token of the user who scheduled this task', + + // An extension point for 3rd parties to build in security features on + // top of the task manager, and is expected to be the id of the user, if any, + // that scheduled this task. + user: '23lk3l42', + + // An application-specific designation, allowing different Kibana + // plugins / apps to query for only those tasks they care about. + scope: ['alerting'], + + // The Kibana UUID of the Kibana instance who last claimed ownership for running this task. + ownerId: '123e4567-e89b-12d3-a456-426655440000' +} +``` + +## Programmatic access + +The task manager mixin exposes a taskManager object on the Kibana server which plugins can use to manage scheduled tasks. Each method takes an optional `scope` argument and ensures that only tasks with the specified scope(s) will be affected. + +### Overview +Interaction with the TaskManager Plugin is done via the Kibana Platform Plugin system. +When developing your Plugin, you're asked to define a `setup` method and a `start` method. +These methods are handed Kibana's Plugin APIs for these two stages, which means you'll have access to the following apis in these two stages: + +#### Setup +The _Setup_ Plugin api includes methods which configure Task Manager to support your Plugin's requirements, such as defining custom Middleware and Task Definitions. +```js +{ + addMiddleware: (middleware: Middleware) => { + // ... + }, + registerTaskDefinitions: (taskDefinitions: TaskDictionary) => { + // ... + }, +} +``` + +#### Start +The _Start_ Plugin api allow you to use Task Manager to facilitate your Plugin's behaviour, such as scheduling tasks. + +```js +{ + fetch: (opts: FetchOpts) => { + // ... + }, + remove: (id: string) => { + // ... + }, + schedule: (taskInstance: TaskInstanceWithDeprecatedFields, options?: any) => { + // ... + }, + runNow: (taskId: string) => { + // ... + }, + ensureScheduled: (taskInstance: TaskInstanceWithId, options?: any) => { + // ... + }, +} +``` + +### Detailed APIs + +#### schedule +Using `schedule` you can instruct TaskManger to schedule an instance of a TaskType at some point in the future. + + +```js +export class Plugin { + constructor() { + } + + public setup(core: CoreSetup, plugins: { taskManager }) { + } + + public start(core: CoreStart, plugins: { taskManager }) { + // Schedules a task. All properties are as documented in the previous + // storage section, except that here, params is an object, not a JSON + // string. + const task = await taskManager.schedule({ + taskType, + runAt, + schedule, + params, + scope: ['my-fanci-app'], + }); + + // Removes the specified task + await taskManager.remove(task.id); + + // Fetches tasks, supports pagination, via the search-after API: + // https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-search-after.html + // If scope is not specified, all tasks are returned, otherwise only tasks + // with the given scope are returned. + const results = await taskManager.find({ scope: 'my-fanci-app', searchAfter: ['ids'] }); + } +} +``` +*results* then look something like this: + +```json + { + "searchAfter": ["233322"], + // Tasks is an array of task instances + "tasks": [{ + "id": "3242342", + "taskType": "reporting", + // etc + }] + } +``` + +#### ensureScheduling +When using the `schedule` api to schedule a Task you can provide a hard coded `id` on the Task. This tells TaskManager to use this `id` to identify the Task Instance rather than generate an `id` on its own. +The danger is that in such a situation, a Task with that same `id` might already have been scheduled at some earlier point, and this would result in an error. In some cases, this is the expected behavior, but often you only care about ensuring the task has been _scheduled_ and don't need it to be scheduled a fresh. + +To achieve this you should use the `ensureScheduling` api which has the exact same behavior as `schedule`, except it allows the scheduling of a Task with an `id` that's already in assigned to another Task and it will assume that the existing Task is the one you wished to `schedule`, treating this as a successful operation. + +#### runNow +Using `runNow` you can instruct TaskManger to run an existing task on-demand, without waiting for its scheduled time to be reached. + +```js +export class Plugin { + constructor() { + } + + public setup(core: CoreSetup, plugins: { taskManager }) { + } + + public start(core: CoreStart, plugins: { taskManager }) { + try { + const taskRunResult = await taskManager.runNow('91760f10-ba42-de9799'); + // If no error is thrown, the task has completed successfully. + } catch(err: Error) { + // If running the task has failed, we throw an error with an appropriate message. + // For example, if the requested task doesnt exist: `Error: failed to run task "91760f10-ba42-de9799" as it does not exist` + // Or if, for example, the task is already running: `Error: failed to run task "91760f10-ba42-de9799" as it is currently running` + } + } +} +``` + +#### more options + +More custom access to the tasks can be done directly via Elasticsearch, though that won't be officially supported, as we can change the document structure at any time. + +## Middleware + +The task manager exposes a middleware layer that allows modifying tasks before they are scheduled / persisted to the task manager index, and modifying tasks / the run context before a task is run. + +For example: +```js +export class Plugin { + constructor() { + } + + public setup(core: CoreSetup, plugins: { taskManager }) { + taskManager.addMiddleware({ + async beforeSave({ taskInstance, ...opts }) { + console.log(`About to save a task of type ${taskInstance.taskType}`); + + return { + ...opts, + taskInstance: { + ...taskInstance, + params: { + ...taskInstance.params, + example: 'Added to params!', + }, + }, + }; + }, + + async beforeRun({ taskInstance, ...opts }) { + console.log(`About to run ${taskInstance.taskType} ${taskInstance.id}`); + const { example, ...taskWithoutExampleProp } = taskInstance; + + return { + ...opts, + taskInstance: taskWithoutExampleProp, + }; + }, + }); + } + + public start(core: CoreStart, plugins: { taskManager }) { + + } +} +``` + +## Task Poller: polling for work +TaskManager used to work in a `pull` model, but it now needs to support both `push` and `pull`, so it has been remodeled internally to support a single `push` model. + +Task Manager's _push_ mechanism is driven by the following operations: + +1. A polling interval has been reached. +2. A new Task is scheduled. +3. A Task is run using `runNow`. + +The polling interval is straight forward: TaskPoller is configured to emit an event at a fixed interval. +That said, if there are no workers available, we want to ignore these events, so we'll throttle the interval on worker availability. + +Whenever a user uses the `schedule` api to schedule a new Task, we want to trigger an early polling in order to respond to the newly scheduled task as soon as possible, but this too we only wish to do if there are available workers, so we can throttle this too. + +When a `runNow` call is made we need to force a poll as the user will now be waiting on the result of the `runNow` call, but +there is a complexity here- we don't want to force polling (as there might not be any worker capacity and it's possible that a polling cycle is already running), but we also can't throttle, as we can't afford to "drop" these requests, so we'll have to buffer these. + +We now want to respond to all three of these push events, but we still need to balance against our worker capacity, so if there are too many requests buffered, we only want to `take` as many requests as we have capacity to handle. +Luckily, `Polling Interval` and `Task Scheduled` simply denote a request to "poll for work as soon as possible", unlike `Run Task Now` which also means "poll for these specific tasks", so our worker capacity only needs to be applied to `Run Task Now`. + +We achieve this model by buffering requests into a queue using a Set (which removes duplicated). As we don't want an unbounded queue in our system, we have limited the size of this queue (configurable by the `xpack.task_manager.request_capacity` config, defaulting to 1,000 requests) which forces us to throw an error once this cap is reachedand to all subsequent calls to `runNow` until the queue drain bellow the cap. + +Our current model, then, is this: +``` + Polling Interval --> filter(availableWorkers > 0) - mapTo([]) -------\\ + Task Scheduled --> filter(availableWorkers > 0) - mapTo([]) --------||==>Set([]+[]+[`1`,`2`]) ==> work([`1`,`2`]) + Run Task `1` Now --\ // + ----> buffer(availableWorkers > 0) -- [`1`,`2`] -// + Run Task `2` Now --/ +``` + +## Limitations in v1.0 + +In v1, the system only understands 1 minute increments (e.g. '1m', '7m'). Tasks which need something more robust will need to specify their own "runAt" in their run method's return value. + +There is only a rudimentary mechanism for coordinating tasks and handling expired tasks. Tasks are considered expired if their runAt has arrived, and their status is still 'running'. + +There is no task history. Each run overwrites the previous run's state. One-time tasks are removed from the index upon completion regardless of success / failure. + +The task manager's public API is create / delete / list. Updates aren't directly supported, and listing should be scoped so that users only see their own tasks. + +## Testing + +- Unit tests: + ``` + cd x-pack + node scripts/jest --testPathPattern=task_manager --watch + ``` +- Integration tests: + ``` + node scripts/functional_tests_server.js --config x-pack/test/plugin_api_integration/config.js + node scripts/functional_test_runner --config x-pack/test/plugin_api_integration/config.js + ``` diff --git a/x-pack/plugins/task_manager/server/config.test.ts b/x-pack/plugins/task_manager/server/config.test.ts new file mode 100644 index 0000000000000..f7962f7011f34 --- /dev/null +++ b/x-pack/plugins/task_manager/server/config.test.ts @@ -0,0 +1,33 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { configSchema } from './config'; + +describe('config validation', () => { + test('task manager defaults', () => { + const config: Record = {}; + expect(configSchema.validate(config)).toMatchInlineSnapshot(` + Object { + "enabled": true, + "index": ".kibana_task_manager", + "max_attempts": 3, + "max_workers": 10, + "poll_interval": 3000, + "request_capacity": 1000, + } + `); + }); + + test('the ElastiSearch Tasks index cannot be used for task manager', () => { + const config: Record = { + index: '.tasks', + }; + expect(() => { + configSchema.validate(config); + }).toThrowErrorMatchingInlineSnapshot( + `"[index]: \\".tasks\\" is an invalid Kibana Task Manager index, as it is already in use by the ElasticSearch Tasks Manager"` + ); + }); +}); diff --git a/x-pack/plugins/task_manager/server/config.ts b/x-pack/plugins/task_manager/server/config.ts new file mode 100644 index 0000000000000..06e6ad3e62282 --- /dev/null +++ b/x-pack/plugins/task_manager/server/config.ts @@ -0,0 +1,44 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { schema, TypeOf } from '@kbn/config-schema'; + +export const configSchema = schema.object({ + enabled: schema.boolean({ defaultValue: true }), + /* The maximum number of times a task will be attempted before being abandoned as failed */ + max_attempts: schema.number({ + defaultValue: 3, + min: 1, + }), + /* How often, in milliseconds, the task manager will look for more work. */ + poll_interval: schema.number({ + defaultValue: 3000, + min: 100, + }), + /* How many requests can Task Manager buffer before it rejects new requests. */ + request_capacity: schema.number({ + // a nice round contrived number, feel free to change as we learn how it behaves + defaultValue: 1000, + min: 1, + }), + /* The name of the index used to store task information. */ + index: schema.string({ + defaultValue: '.kibana_task_manager', + validate: val => { + if (val.toLowerCase() === '.tasks') { + return `"${val}" is an invalid Kibana Task Manager index, as it is already in use by the ElasticSearch Tasks Manager`; + } + }, + }), + /* The maximum number of tasks that this Kibana instance will run simultaneously. */ + max_workers: schema.number({ + defaultValue: 10, + // disable the task manager rather than trying to specify it with 0 workers + min: 1, + }), +}); + +export type TaskManagerConfig = TypeOf; diff --git a/x-pack/plugins/task_manager/server/create_task_manager.test.ts b/x-pack/plugins/task_manager/server/create_task_manager.test.ts new file mode 100644 index 0000000000000..f4deeb1ea02ed --- /dev/null +++ b/x-pack/plugins/task_manager/server/create_task_manager.test.ts @@ -0,0 +1,58 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { createTaskManager, LegacyDeps } from './create_task_manager'; +import { mockLogger } from './test_utils'; +import { CoreSetup, UuidServiceSetup } from 'kibana/server'; +import { savedObjectsRepositoryMock } from '../../../../src/core/server/mocks'; + +jest.mock('./task_manager'); + +describe('createTaskManager', () => { + const uuid: UuidServiceSetup = { + getInstanceUuid() { + return 'some-uuid'; + }, + }; + const mockCoreSetup = { + uuid, + } as CoreSetup; + + const getMockLegacyDeps = (): LegacyDeps => ({ + config: {}, + savedObjectSchemas: {}, + elasticsearch: { + callAsInternalUser: jest.fn(), + }, + savedObjectsRepository: savedObjectsRepositoryMock.create(), + logger: mockLogger(), + }); + + beforeEach(() => { + jest.resetAllMocks(); + }); + + test('exposes the underlying TaskManager', async () => { + const mockLegacyDeps = getMockLegacyDeps(); + const setupResult = createTaskManager(mockCoreSetup, mockLegacyDeps); + expect(setupResult).toMatchInlineSnapshot(` + TaskManager { + "addMiddleware": [MockFunction], + "assertUninitialized": [MockFunction], + "attemptToRun": [MockFunction], + "ensureScheduled": [MockFunction], + "fetch": [MockFunction], + "registerTaskDefinitions": [MockFunction], + "remove": [MockFunction], + "runNow": [MockFunction], + "schedule": [MockFunction], + "start": [MockFunction], + "stop": [MockFunction], + "waitUntilStarted": [MockFunction], + } + `); + }); +}); diff --git a/x-pack/plugins/task_manager/server/create_task_manager.ts b/x-pack/plugins/task_manager/server/create_task_manager.ts new file mode 100644 index 0000000000000..5c66b8ba5bd58 --- /dev/null +++ b/x-pack/plugins/task_manager/server/create_task_manager.ts @@ -0,0 +1,46 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { + IClusterClient, + SavedObjectsSerializer, + SavedObjectsSchema, + CoreSetup, + ISavedObjectsRepository, +} from '../../../../src/core/server'; +import { TaskManager } from './task_manager'; +import { Logger } from './types'; + +export interface LegacyDeps { + config: any; + savedObjectSchemas: any; + elasticsearch: Pick; + savedObjectsRepository: ISavedObjectsRepository; + logger: Logger; +} + +export function createTaskManager( + core: CoreSetup, + { + logger, + config, + savedObjectSchemas, + elasticsearch: { callAsInternalUser }, + savedObjectsRepository, + }: LegacyDeps +) { + // as we use this Schema solely to interact with Tasks, we + // can initialise it with solely the Tasks schema + const serializer = new SavedObjectsSerializer(new SavedObjectsSchema(savedObjectSchemas)); + return new TaskManager({ + taskManagerId: core.uuid.getInstanceUuid(), + config, + savedObjectsRepository, + serializer, + callAsInternalUser, + logger, + }); +} diff --git a/x-pack/plugins/task_manager/server/index.ts b/x-pack/plugins/task_manager/server/index.ts new file mode 100644 index 0000000000000..7eba218e16fed --- /dev/null +++ b/x-pack/plugins/task_manager/server/index.ts @@ -0,0 +1,29 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { PluginInitializerContext } from 'src/core/server'; +import { TaskManagerPlugin } from './plugin'; +import { configSchema } from './config'; + +export const plugin = (initContext: PluginInitializerContext) => new TaskManagerPlugin(initContext); + +export { + TaskInstance, + ConcreteTaskInstance, + TaskRunCreatorFunction, + TaskStatus, + RunContext, +} from './task'; + +export { + TaskManagerPlugin as TaskManager, + TaskManagerSetupContract, + TaskManagerStartContract, +} from './plugin'; + +export const config = { + schema: configSchema, +}; diff --git a/x-pack/legacy/plugins/task_manager/lib/correct_deprecated_fields.test.ts b/x-pack/plugins/task_manager/server/lib/correct_deprecated_fields.test.ts similarity index 100% rename from x-pack/legacy/plugins/task_manager/lib/correct_deprecated_fields.test.ts rename to x-pack/plugins/task_manager/server/lib/correct_deprecated_fields.test.ts diff --git a/x-pack/legacy/plugins/task_manager/lib/correct_deprecated_fields.ts b/x-pack/plugins/task_manager/server/lib/correct_deprecated_fields.ts similarity index 100% rename from x-pack/legacy/plugins/task_manager/lib/correct_deprecated_fields.ts rename to x-pack/plugins/task_manager/server/lib/correct_deprecated_fields.ts diff --git a/x-pack/legacy/plugins/task_manager/lib/fill_pool.test.ts b/x-pack/plugins/task_manager/server/lib/fill_pool.test.ts similarity index 100% rename from x-pack/legacy/plugins/task_manager/lib/fill_pool.test.ts rename to x-pack/plugins/task_manager/server/lib/fill_pool.test.ts diff --git a/x-pack/legacy/plugins/task_manager/lib/fill_pool.ts b/x-pack/plugins/task_manager/server/lib/fill_pool.ts similarity index 100% rename from x-pack/legacy/plugins/task_manager/lib/fill_pool.ts rename to x-pack/plugins/task_manager/server/lib/fill_pool.ts diff --git a/x-pack/legacy/plugins/task_manager/lib/get_template_version.test.ts b/x-pack/plugins/task_manager/server/lib/get_template_version.test.ts similarity index 100% rename from x-pack/legacy/plugins/task_manager/lib/get_template_version.test.ts rename to x-pack/plugins/task_manager/server/lib/get_template_version.test.ts diff --git a/x-pack/legacy/plugins/task_manager/lib/get_template_version.ts b/x-pack/plugins/task_manager/server/lib/get_template_version.ts similarity index 100% rename from x-pack/legacy/plugins/task_manager/lib/get_template_version.ts rename to x-pack/plugins/task_manager/server/lib/get_template_version.ts diff --git a/x-pack/legacy/plugins/task_manager/lib/identify_es_error.test.ts b/x-pack/plugins/task_manager/server/lib/identify_es_error.test.ts similarity index 100% rename from x-pack/legacy/plugins/task_manager/lib/identify_es_error.test.ts rename to x-pack/plugins/task_manager/server/lib/identify_es_error.test.ts diff --git a/x-pack/legacy/plugins/task_manager/lib/identify_es_error.ts b/x-pack/plugins/task_manager/server/lib/identify_es_error.ts similarity index 100% rename from x-pack/legacy/plugins/task_manager/lib/identify_es_error.ts rename to x-pack/plugins/task_manager/server/lib/identify_es_error.ts diff --git a/x-pack/legacy/plugins/task_manager/lib/intervals.test.ts b/x-pack/plugins/task_manager/server/lib/intervals.test.ts similarity index 100% rename from x-pack/legacy/plugins/task_manager/lib/intervals.test.ts rename to x-pack/plugins/task_manager/server/lib/intervals.test.ts diff --git a/x-pack/legacy/plugins/task_manager/lib/intervals.ts b/x-pack/plugins/task_manager/server/lib/intervals.ts similarity index 100% rename from x-pack/legacy/plugins/task_manager/lib/intervals.ts rename to x-pack/plugins/task_manager/server/lib/intervals.ts diff --git a/x-pack/legacy/plugins/task_manager/lib/middleware.test.ts b/x-pack/plugins/task_manager/server/lib/middleware.test.ts similarity index 100% rename from x-pack/legacy/plugins/task_manager/lib/middleware.test.ts rename to x-pack/plugins/task_manager/server/lib/middleware.test.ts diff --git a/x-pack/legacy/plugins/task_manager/lib/middleware.ts b/x-pack/plugins/task_manager/server/lib/middleware.ts similarity index 100% rename from x-pack/legacy/plugins/task_manager/lib/middleware.ts rename to x-pack/plugins/task_manager/server/lib/middleware.ts diff --git a/x-pack/legacy/plugins/task_manager/lib/pull_from_set.test.ts b/x-pack/plugins/task_manager/server/lib/pull_from_set.test.ts similarity index 100% rename from x-pack/legacy/plugins/task_manager/lib/pull_from_set.test.ts rename to x-pack/plugins/task_manager/server/lib/pull_from_set.test.ts diff --git a/x-pack/legacy/plugins/task_manager/lib/pull_from_set.ts b/x-pack/plugins/task_manager/server/lib/pull_from_set.ts similarity index 100% rename from x-pack/legacy/plugins/task_manager/lib/pull_from_set.ts rename to x-pack/plugins/task_manager/server/lib/pull_from_set.ts diff --git a/x-pack/legacy/plugins/task_manager/lib/result_type.ts b/x-pack/plugins/task_manager/server/lib/result_type.ts similarity index 100% rename from x-pack/legacy/plugins/task_manager/lib/result_type.ts rename to x-pack/plugins/task_manager/server/lib/result_type.ts diff --git a/x-pack/legacy/plugins/task_manager/lib/sanitize_task_definitions.test.ts b/x-pack/plugins/task_manager/server/lib/sanitize_task_definitions.test.ts similarity index 100% rename from x-pack/legacy/plugins/task_manager/lib/sanitize_task_definitions.test.ts rename to x-pack/plugins/task_manager/server/lib/sanitize_task_definitions.test.ts diff --git a/x-pack/legacy/plugins/task_manager/lib/sanitize_task_definitions.ts b/x-pack/plugins/task_manager/server/lib/sanitize_task_definitions.ts similarity index 100% rename from x-pack/legacy/plugins/task_manager/lib/sanitize_task_definitions.ts rename to x-pack/plugins/task_manager/server/lib/sanitize_task_definitions.ts diff --git a/x-pack/plugins/task_manager/server/plugin.ts b/x-pack/plugins/task_manager/server/plugin.ts new file mode 100644 index 0000000000000..9bdd1ce6d8748 --- /dev/null +++ b/x-pack/plugins/task_manager/server/plugin.ts @@ -0,0 +1,83 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import { PluginInitializerContext, Plugin, CoreSetup } from 'src/core/server'; +import { Observable, Subject } from 'rxjs'; +import { first } from 'rxjs/operators'; +import { once } from 'lodash'; +import { TaskDictionary, TaskDefinition } from './task'; +import { TaskManager } from './task_manager'; +import { createTaskManager, LegacyDeps } from './create_task_manager'; +import { TaskManagerConfig } from './config'; +import { Middleware } from './lib/middleware'; + +export type PluginLegacyDependencies = Pick; +export type TaskManagerSetupContract = { + config$: Observable; + registerLegacyAPI: (legacyDependencies: PluginLegacyDependencies) => Promise; +} & Pick; + +export type TaskManagerStartContract = Pick< + TaskManager, + 'fetch' | 'remove' | 'schedule' | 'runNow' | 'ensureScheduled' +>; + +export class TaskManagerPlugin + implements Plugin { + legacyTaskManager$: Subject = new Subject(); + taskManager: Promise = this.legacyTaskManager$.pipe(first()).toPromise(); + currentConfig: TaskManagerConfig; + + constructor(private readonly initContext: PluginInitializerContext) { + this.initContext = initContext; + this.currentConfig = {} as TaskManagerConfig; + } + + public setup(core: CoreSetup, plugins: any): TaskManagerSetupContract { + const logger = this.initContext.logger.get('taskManager'); + const config$ = this.initContext.config.create(); + const savedObjectsRepository = core.savedObjects.createInternalRepository(['task']); + const elasticsearch = core.elasticsearch.adminClient; + return { + config$, + registerLegacyAPI: once((__LEGACY: PluginLegacyDependencies) => { + config$.subscribe(async config => { + this.legacyTaskManager$.next( + createTaskManager(core, { + logger, + config, + elasticsearch, + savedObjectsRepository, + ...__LEGACY, + }) + ); + this.legacyTaskManager$.complete(); + }); + return this.taskManager; + }), + addMiddleware: (middleware: Middleware) => { + this.taskManager.then(tm => tm.addMiddleware(middleware)); + }, + registerTaskDefinitions: (taskDefinition: TaskDictionary) => { + this.taskManager.then(tm => tm.registerTaskDefinitions(taskDefinition)); + }, + }; + } + + public start(): TaskManagerStartContract { + return { + fetch: (...args) => this.taskManager.then(tm => tm.fetch(...args)), + remove: (...args) => this.taskManager.then(tm => tm.remove(...args)), + schedule: (...args) => this.taskManager.then(tm => tm.schedule(...args)), + runNow: (...args) => this.taskManager.then(tm => tm.runNow(...args)), + ensureScheduled: (...args) => this.taskManager.then(tm => tm.ensureScheduled(...args)), + }; + } + public stop() { + this.taskManager.then(tm => { + tm.stop(); + }); + } +} diff --git a/x-pack/legacy/plugins/task_manager/queries/mark_available_tasks_as_claimed.test.ts b/x-pack/plugins/task_manager/server/queries/mark_available_tasks_as_claimed.test.ts similarity index 100% rename from x-pack/legacy/plugins/task_manager/queries/mark_available_tasks_as_claimed.test.ts rename to x-pack/plugins/task_manager/server/queries/mark_available_tasks_as_claimed.test.ts diff --git a/x-pack/legacy/plugins/task_manager/queries/mark_available_tasks_as_claimed.ts b/x-pack/plugins/task_manager/server/queries/mark_available_tasks_as_claimed.ts similarity index 100% rename from x-pack/legacy/plugins/task_manager/queries/mark_available_tasks_as_claimed.ts rename to x-pack/plugins/task_manager/server/queries/mark_available_tasks_as_claimed.ts diff --git a/x-pack/legacy/plugins/task_manager/queries/query_clauses.ts b/x-pack/plugins/task_manager/server/queries/query_clauses.ts similarity index 100% rename from x-pack/legacy/plugins/task_manager/queries/query_clauses.ts rename to x-pack/plugins/task_manager/server/queries/query_clauses.ts diff --git a/x-pack/legacy/plugins/task_manager/task.ts b/x-pack/plugins/task_manager/server/task.ts similarity index 100% rename from x-pack/legacy/plugins/task_manager/task.ts rename to x-pack/plugins/task_manager/server/task.ts diff --git a/x-pack/legacy/plugins/task_manager/task_events.ts b/x-pack/plugins/task_manager/server/task_events.ts similarity index 100% rename from x-pack/legacy/plugins/task_manager/task_events.ts rename to x-pack/plugins/task_manager/server/task_events.ts diff --git a/x-pack/plugins/task_manager/server/task_manager.mock.ts b/x-pack/plugins/task_manager/server/task_manager.mock.ts new file mode 100644 index 0000000000000..9750dd14100d9 --- /dev/null +++ b/x-pack/plugins/task_manager/server/task_manager.mock.ts @@ -0,0 +1,32 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { TaskManagerSetupContract, TaskManagerStartContract } from './plugin'; +import { Subject } from 'rxjs'; + +export const taskManagerMock = { + setup(overrides: Partial> = {}) { + const mocked: jest.Mocked = { + registerTaskDefinitions: jest.fn(), + addMiddleware: jest.fn(), + config$: new Subject(), + registerLegacyAPI: jest.fn(), + ...overrides, + }; + return mocked; + }, + start(overrides: Partial> = {}) { + const mocked: jest.Mocked = { + ensureScheduled: jest.fn(), + schedule: jest.fn(), + fetch: jest.fn(), + runNow: jest.fn(), + remove: jest.fn(), + ...overrides, + }; + return mocked; + }, +}; diff --git a/x-pack/legacy/plugins/task_manager/task_manager.test.ts b/x-pack/plugins/task_manager/server/task_manager.test.ts similarity index 92% rename from x-pack/legacy/plugins/task_manager/task_manager.test.ts rename to x-pack/plugins/task_manager/server/task_manager.test.ts index e70ee108f275c..a65723b2e8de7 100644 --- a/x-pack/legacy/plugins/task_manager/task_manager.test.ts +++ b/x-pack/plugins/task_manager/server/task_manager.test.ts @@ -20,43 +20,33 @@ import { awaitTaskRunResult, TaskLifecycleEvent, } from './task_manager'; -// Task manager uses an unconventional directory structure so the linter marks this as a violation, server files should -// be moved under task_manager/server/ -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { savedObjectsClientMock } from 'src/core/server/mocks'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { SavedObjectsSerializer, SavedObjectsSchema } from 'src/core/server'; +import { savedObjectsRepositoryMock } from '../../../../src/core/server/mocks'; +import { SavedObjectsSerializer, SavedObjectsSchema } from '../../../../src/core/server'; import { mockLogger } from './test_utils'; import { asErr, asOk } from './lib/result_type'; import { ConcreteTaskInstance, TaskLifecycleResult, TaskStatus } from './task'; -const savedObjectsClient = savedObjectsClientMock.create(); +const savedObjectsClient = savedObjectsRepositoryMock.create(); const serializer = new SavedObjectsSerializer(new SavedObjectsSchema()); describe('TaskManager', () => { let clock: sinon.SinonFakeTimers; - const defaultConfig = { - xpack: { - task_manager: { - max_workers: 10, - index: 'foo', - max_attempts: 9, - poll_interval: 6000000, - }, - }, - server: { - uuid: 'some-uuid', - }, - }; + const config = { - get: (path: string) => _.get(defaultConfig, path), + enabled: true, + max_workers: 10, + index: 'foo', + max_attempts: 9, + poll_interval: 6000000, + request_capacity: 1000, }; const taskManagerOpts = { config, savedObjectsRepository: savedObjectsClient, serializer, - callWithInternalUser: jest.fn(), + callAsInternalUser: jest.fn(), logger: mockLogger(), + taskManagerId: 'some-uuid', }; beforeEach(() => { @@ -67,21 +57,9 @@ describe('TaskManager', () => { test('throws if no valid UUID is available', async () => { expect(() => { - const configWithoutServerUUID = { - xpack: { - task_manager: { - max_workers: 10, - index: 'foo', - max_attempts: 9, - poll_interval: 6000000, - }, - }, - }; new TaskManager({ ...taskManagerOpts, - config: { - get: (path: string) => _.get(configWithoutServerUUID, path), - }, + taskManagerId: '', }); }).toThrowErrorMatchingInlineSnapshot( `"TaskManager is unable to start as Kibana has no valid UUID assigned to it."` @@ -238,7 +216,7 @@ describe('TaskManager', () => { test('allows and queues fetching tasks before starting', async () => { const client = new TaskManager(taskManagerOpts); - taskManagerOpts.callWithInternalUser.mockResolvedValue({ + taskManagerOpts.callAsInternalUser.mockResolvedValue({ hits: { total: { value: 0, @@ -249,13 +227,13 @@ describe('TaskManager', () => { const promise = client.fetch({}); client.start(); await promise; - expect(taskManagerOpts.callWithInternalUser).toHaveBeenCalled(); + expect(taskManagerOpts.callAsInternalUser).toHaveBeenCalled(); }); test('allows fetching tasks after starting', async () => { const client = new TaskManager(taskManagerOpts); client.start(); - taskManagerOpts.callWithInternalUser.mockResolvedValue({ + taskManagerOpts.callAsInternalUser.mockResolvedValue({ hits: { total: { value: 0, @@ -264,7 +242,7 @@ describe('TaskManager', () => { }, }); await client.fetch({}); - expect(taskManagerOpts.callWithInternalUser).toHaveBeenCalled(); + expect(taskManagerOpts.callAsInternalUser).toHaveBeenCalled(); }); test('allows middleware registration before starting', () => { @@ -286,7 +264,6 @@ describe('TaskManager', () => { }; client.start(); - expect(() => client.addMiddleware(middleware)).toThrow( /Cannot add middleware after the task manager is initialized/i ); diff --git a/x-pack/legacy/plugins/task_manager/task_manager.ts b/x-pack/plugins/task_manager/server/task_manager.ts similarity index 94% rename from x-pack/legacy/plugins/task_manager/task_manager.ts rename to x-pack/plugins/task_manager/server/task_manager.ts index b7abeb248d143..c0baed3708a0a 100644 --- a/x-pack/legacy/plugins/task_manager/task_manager.ts +++ b/x-pack/plugins/task_manager/server/task_manager.ts @@ -7,14 +7,16 @@ import { Subject, Observable, Subscription } from 'rxjs'; import { filter } from 'rxjs/operators'; import { performance } from 'perf_hooks'; -// Task manager uses an unconventional directory structure so the linter marks this as a violation, server files should -// be moved under task_manager/server/ -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { SavedObjectsClientContract, SavedObjectsSerializer } from 'src/core/server'; import { pipe } from 'fp-ts/lib/pipeable'; import { Option, none, some, map as mapOptional } from 'fp-ts/lib/Option'; +import { + SavedObjectsSerializer, + IScopedClusterClient, + ISavedObjectsRepository, +} from '../../../../src/core/server'; import { Result, asErr, either, map, mapErr, promiseResult } from './lib/result_type'; +import { TaskManagerConfig } from './config'; import { Logger } from './types'; import { @@ -59,10 +61,11 @@ const VERSION_CONFLICT_STATUS = 409; export interface TaskManagerOpts { logger: Logger; - config: any; - callWithInternalUser: any; - savedObjectsRepository: SavedObjectsClientContract; + config: TaskManagerConfig; + callAsInternalUser: IScopedClusterClient['callAsInternalUser']; + savedObjectsRepository: ISavedObjectsRepository; serializer: SavedObjectsSerializer; + taskManagerId: string; } interface RunNowResult { @@ -113,7 +116,7 @@ export class TaskManager { constructor(opts: TaskManagerOpts) { this.logger = opts.logger; - const taskManagerId = opts.config.get('server.uuid'); + const { taskManagerId } = opts; if (!taskManagerId) { this.logger.error( `TaskManager is unable to start as there the Kibana UUID is invalid (value of the "server.uuid" configuration is ${taskManagerId})` @@ -126,9 +129,9 @@ export class TaskManager { this.store = new TaskStore({ serializer: opts.serializer, savedObjectsRepository: opts.savedObjectsRepository, - callCluster: opts.callWithInternalUser, - index: opts.config.get('xpack.task_manager.index'), - maxAttempts: opts.config.get('xpack.task_manager.max_attempts'), + callCluster: opts.callAsInternalUser, + index: opts.config.index, + maxAttempts: opts.config.max_attempts, definitions: this.definitions, taskManagerId: `kibana:${taskManagerId}`, }); @@ -137,12 +140,12 @@ export class TaskManager { this.pool = new TaskPool({ logger: this.logger, - maxWorkers: opts.config.get('xpack.task_manager.max_workers'), + maxWorkers: opts.config.max_workers, }); this.poller$ = createTaskPoller({ - pollInterval: opts.config.get('xpack.task_manager.poll_interval'), - bufferCapacity: opts.config.get('xpack.task_manager.request_capacity'), + pollInterval: opts.config.poll_interval, + bufferCapacity: opts.config.request_capacity, getCapacity: () => this.pool.availableWorkers, pollRequests$: this.claimRequests$, work: this.pollForWork, diff --git a/x-pack/legacy/plugins/task_manager/task_poller.test.ts b/x-pack/plugins/task_manager/server/task_poller.test.ts similarity index 100% rename from x-pack/legacy/plugins/task_manager/task_poller.test.ts rename to x-pack/plugins/task_manager/server/task_poller.test.ts diff --git a/x-pack/legacy/plugins/task_manager/task_poller.ts b/x-pack/plugins/task_manager/server/task_poller.ts similarity index 100% rename from x-pack/legacy/plugins/task_manager/task_poller.ts rename to x-pack/plugins/task_manager/server/task_poller.ts diff --git a/x-pack/legacy/plugins/task_manager/task_pool.test.ts b/x-pack/plugins/task_manager/server/task_pool.test.ts similarity index 100% rename from x-pack/legacy/plugins/task_manager/task_pool.test.ts rename to x-pack/plugins/task_manager/server/task_pool.test.ts diff --git a/x-pack/legacy/plugins/task_manager/task_pool.ts b/x-pack/plugins/task_manager/server/task_pool.ts similarity index 100% rename from x-pack/legacy/plugins/task_manager/task_pool.ts rename to x-pack/plugins/task_manager/server/task_pool.ts diff --git a/x-pack/plugins/task_manager/server/task_runner.test.ts b/x-pack/plugins/task_manager/server/task_runner.test.ts new file mode 100644 index 0000000000000..3f0132105347e --- /dev/null +++ b/x-pack/plugins/task_manager/server/task_runner.test.ts @@ -0,0 +1,949 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import _ from 'lodash'; +import sinon from 'sinon'; +import { minutesFromNow } from './lib/intervals'; +import { asOk, asErr } from './lib/result_type'; +import { TaskEvent, asTaskRunEvent, asTaskMarkRunningEvent } from './task_events'; +import { ConcreteTaskInstance, TaskStatus } from './task'; +import { TaskManagerRunner } from './task_runner'; +import { mockLogger } from './test_utils'; +import { SavedObjectsErrorHelpers } from '../../../../src/core/server'; + +let fakeTimer: sinon.SinonFakeTimers; + +beforeAll(() => { + fakeTimer = sinon.useFakeTimers(); +}); + +afterAll(() => fakeTimer.restore()); + +describe('TaskManagerRunner', () => { + test('provides details about the task that is running', () => { + const { runner } = testOpts({ + instance: { + id: 'foo', + taskType: 'bar', + }, + }); + + expect(runner.id).toEqual('foo'); + expect(runner.taskType).toEqual('bar'); + expect(runner.toString()).toEqual('bar "foo"'); + }); + + test('warns if the task returns an unexpected result', async () => { + await allowsReturnType(undefined); + await allowsReturnType({}); + await allowsReturnType({ + runAt: new Date(), + }); + await allowsReturnType({ + error: new Error('Dang it!'), + }); + await allowsReturnType({ + state: { shazm: true }, + }); + await disallowsReturnType('hm....'); + await disallowsReturnType({ + whatIsThis: '?!!?', + }); + }); + + test('queues a reattempt if the task fails', async () => { + const initialAttempts = _.random(0, 2); + const id = Date.now().toString(); + const { runner, store } = testOpts({ + instance: { + id, + attempts: initialAttempts, + params: { a: 'b' }, + state: { hey: 'there' }, + }, + definitions: { + bar: { + createTaskRunner: () => ({ + async run() { + throw new Error('Dangit!'); + }, + }), + }, + }, + }); + + await runner.run(); + + sinon.assert.calledOnce(store.update); + const instance = store.update.args[0][0]; + + expect(instance.id).toEqual(id); + expect(instance.runAt.getTime()).toEqual(minutesFromNow(initialAttempts * 5).getTime()); + expect(instance.params).toEqual({ a: 'b' }); + expect(instance.state).toEqual({ hey: 'there' }); + }); + + test('reschedules tasks that have an schedule', async () => { + const { runner, store } = testOpts({ + instance: { + schedule: { interval: '10m' }, + status: TaskStatus.Running, + startedAt: new Date(), + }, + definitions: { + bar: { + createTaskRunner: () => ({ + async run() { + return; + }, + }), + }, + }, + }); + + await runner.run(); + + sinon.assert.calledOnce(store.update); + const instance = store.update.args[0][0]; + + expect(instance.runAt.getTime()).toBeGreaterThan(minutesFromNow(9).getTime()); + expect(instance.runAt.getTime()).toBeLessThanOrEqual(minutesFromNow(10).getTime()); + }); + + test('reschedules tasks that return a runAt', async () => { + const runAt = minutesFromNow(_.random(1, 10)); + const { runner, store } = testOpts({ + definitions: { + bar: { + createTaskRunner: () => ({ + async run() { + return { runAt }; + }, + }), + }, + }, + }); + + await runner.run(); + + sinon.assert.calledOnce(store.update); + sinon.assert.calledWithMatch(store.update, { runAt }); + }); + + test('tasks that return runAt override the schedule', async () => { + const runAt = minutesFromNow(_.random(5)); + const { runner, store } = testOpts({ + instance: { + schedule: { interval: '20m' }, + }, + definitions: { + bar: { + createTaskRunner: () => ({ + async run() { + return { runAt }; + }, + }), + }, + }, + }); + + await runner.run(); + + sinon.assert.calledOnce(store.update); + sinon.assert.calledWithMatch(store.update, { runAt }); + }); + + test('removes non-recurring tasks after they complete', async () => { + const id = _.random(1, 20).toString(); + const { runner, store } = testOpts({ + instance: { + id, + schedule: undefined, + }, + definitions: { + bar: { + createTaskRunner: () => ({ + async run() { + return undefined; + }, + }), + }, + }, + }); + + await runner.run(); + + sinon.assert.calledOnce(store.remove); + sinon.assert.calledWith(store.remove, id); + }); + + test('cancel cancels the task runner, if it is cancellable', async () => { + let wasCancelled = false; + const { runner, logger } = testOpts({ + definitions: { + bar: { + createTaskRunner: () => ({ + async run() { + const promise = new Promise(r => setTimeout(r, 1000)); + fakeTimer.tick(1000); + await promise; + }, + async cancel() { + wasCancelled = true; + }, + }), + }, + }, + }); + + const promise = runner.run(); + await Promise.resolve(); + await runner.cancel(); + await promise; + + expect(wasCancelled).toBeTruthy(); + expect(logger.warn).not.toHaveBeenCalled(); + }); + + test('warns if cancel is called on a non-cancellable task', async () => { + const { runner, logger } = testOpts({ + definitions: { + bar: { + createTaskRunner: () => ({ + run: async () => undefined, + }), + }, + }, + }); + + const promise = runner.run(); + await runner.cancel(); + await promise; + + expect(logger.warn).toHaveBeenCalledTimes(1); + expect(logger.warn.mock.calls[0][0]).toMatchInlineSnapshot( + `"The task bar \\"foo\\" is not cancellable."` + ); + }); + + test('sets startedAt, status, attempts and retryAt when claiming a task', async () => { + const timeoutMinutes = 1; + const id = _.random(1, 20).toString(); + const initialAttempts = _.random(0, 2); + const { runner, store } = testOpts({ + instance: { + id, + attempts: initialAttempts, + schedule: undefined, + }, + definitions: { + bar: { + timeout: `${timeoutMinutes}m`, + createTaskRunner: () => ({ + run: async () => undefined, + }), + }, + }, + }); + + await runner.markTaskAsRunning(); + + sinon.assert.calledOnce(store.update); + const instance = store.update.args[0][0]; + + expect(instance.attempts).toEqual(initialAttempts + 1); + expect(instance.status).toBe('running'); + expect(instance.startedAt.getTime()).toEqual(Date.now()); + expect(instance.retryAt.getTime()).toEqual( + minutesFromNow((initialAttempts + 1) * 5).getTime() + timeoutMinutes * 60 * 1000 + ); + }); + + test('uses getRetry function (returning date) on error when defined', async () => { + const initialAttempts = _.random(1, 3); + const nextRetry = new Date(Date.now() + _.random(15, 100) * 1000); + const id = Date.now().toString(); + const getRetryStub = sinon.stub().returns(nextRetry); + const error = new Error('Dangit!'); + const { runner, store } = testOpts({ + instance: { + id, + attempts: initialAttempts, + }, + definitions: { + bar: { + getRetry: getRetryStub, + createTaskRunner: () => ({ + async run() { + throw error; + }, + }), + }, + }, + }); + + await runner.run(); + + sinon.assert.calledOnce(store.update); + sinon.assert.calledWith(getRetryStub, initialAttempts, error); + const instance = store.update.args[0][0]; + + expect(instance.runAt.getTime()).toEqual(nextRetry.getTime()); + }); + + test('uses getRetry function (returning true) on error when defined', async () => { + const initialAttempts = _.random(1, 3); + const id = Date.now().toString(); + const getRetryStub = sinon.stub().returns(true); + const error = new Error('Dangit!'); + const { runner, store } = testOpts({ + instance: { + id, + attempts: initialAttempts, + }, + definitions: { + bar: { + getRetry: getRetryStub, + createTaskRunner: () => ({ + async run() { + throw error; + }, + }), + }, + }, + }); + + await runner.run(); + + sinon.assert.calledOnce(store.update); + sinon.assert.calledWith(getRetryStub, initialAttempts, error); + const instance = store.update.args[0][0]; + + const expectedRunAt = new Date(Date.now() + initialAttempts * 5 * 60 * 1000); + expect(instance.runAt.getTime()).toEqual(expectedRunAt.getTime()); + }); + + test('uses getRetry function (returning false) on error when defined', async () => { + const initialAttempts = _.random(1, 3); + const id = Date.now().toString(); + const getRetryStub = sinon.stub().returns(false); + const error = new Error('Dangit!'); + const { runner, store } = testOpts({ + instance: { + id, + attempts: initialAttempts, + }, + definitions: { + bar: { + getRetry: getRetryStub, + createTaskRunner: () => ({ + async run() { + throw error; + }, + }), + }, + }, + }); + + await runner.run(); + + sinon.assert.calledOnce(store.update); + sinon.assert.calledWith(getRetryStub, initialAttempts, error); + const instance = store.update.args[0][0]; + + expect(instance.status).toBe('failed'); + }); + + test('bypasses getRetry function (returning false) on error of a recurring task', async () => { + const initialAttempts = _.random(1, 3); + const id = Date.now().toString(); + const getRetryStub = sinon.stub().returns(false); + const error = new Error('Dangit!'); + const { runner, store } = testOpts({ + instance: { + id, + attempts: initialAttempts, + schedule: { interval: '1m' }, + startedAt: new Date(), + }, + definitions: { + bar: { + getRetry: getRetryStub, + createTaskRunner: () => ({ + async run() { + throw error; + }, + }), + }, + }, + }); + + await runner.run(); + + sinon.assert.calledOnce(store.update); + sinon.assert.notCalled(getRetryStub); + const instance = store.update.args[0][0]; + + const nextIntervalDelay = 60000; // 1m + const expectedRunAt = new Date(Date.now() + nextIntervalDelay); + expect(instance.runAt.getTime()).toEqual(expectedRunAt.getTime()); + }); + + test('uses getRetry (returning date) to set retryAt when defined', async () => { + const id = _.random(1, 20).toString(); + const initialAttempts = _.random(1, 3); + const nextRetry = new Date(Date.now() + _.random(15, 100) * 1000); + const timeoutMinutes = 1; + const getRetryStub = sinon.stub().returns(nextRetry); + const { runner, store } = testOpts({ + instance: { + id, + attempts: initialAttempts, + schedule: undefined, + }, + definitions: { + bar: { + timeout: `${timeoutMinutes}m`, + getRetry: getRetryStub, + createTaskRunner: () => ({ + run: async () => undefined, + }), + }, + }, + }); + + await runner.markTaskAsRunning(); + + sinon.assert.calledOnce(store.update); + sinon.assert.calledWith(getRetryStub, initialAttempts + 1); + const instance = store.update.args[0][0]; + + expect(instance.retryAt.getTime()).toEqual( + new Date(nextRetry.getTime() + timeoutMinutes * 60 * 1000).getTime() + ); + }); + + test('it returns false when markTaskAsRunning fails due to VERSION_CONFLICT_STATUS', async () => { + const id = _.random(1, 20).toString(); + const initialAttempts = _.random(1, 3); + const nextRetry = new Date(Date.now() + _.random(15, 100) * 1000); + const timeoutMinutes = 1; + const getRetryStub = sinon.stub().returns(nextRetry); + const { runner, store } = testOpts({ + instance: { + id, + attempts: initialAttempts, + schedule: undefined, + }, + definitions: { + bar: { + timeout: `${timeoutMinutes}m`, + getRetry: getRetryStub, + createTaskRunner: () => ({ + run: async () => undefined, + }), + }, + }, + }); + + store.update = sinon + .stub() + .throws( + SavedObjectsErrorHelpers.decorateConflictError(new Error('repo error')).output.payload + ); + + expect(await runner.markTaskAsRunning()).toEqual(false); + }); + + test('it throw when markTaskAsRunning fails for unexpected reasons', async () => { + const id = _.random(1, 20).toString(); + const initialAttempts = _.random(1, 3); + const nextRetry = new Date(Date.now() + _.random(15, 100) * 1000); + const timeoutMinutes = 1; + const getRetryStub = sinon.stub().returns(nextRetry); + const { runner, store } = testOpts({ + instance: { + id, + attempts: initialAttempts, + schedule: undefined, + }, + definitions: { + bar: { + timeout: `${timeoutMinutes}m`, + getRetry: getRetryStub, + createTaskRunner: () => ({ + run: async () => undefined, + }), + }, + }, + }); + + store.update = sinon + .stub() + .throws(SavedObjectsErrorHelpers.createGenericNotFoundError('type', 'id').output.payload); + + return expect(runner.markTaskAsRunning()).rejects.toMatchInlineSnapshot(` + Object { + "error": "Not Found", + "message": "Saved object [type/id] not found", + "statusCode": 404, + } + `); + }); + + test('uses getRetry (returning true) to set retryAt when defined', async () => { + const id = _.random(1, 20).toString(); + const initialAttempts = _.random(1, 3); + const timeoutMinutes = 1; + const getRetryStub = sinon.stub().returns(true); + const { runner, store } = testOpts({ + instance: { + id, + attempts: initialAttempts, + schedule: undefined, + }, + definitions: { + bar: { + timeout: `${timeoutMinutes}m`, + getRetry: getRetryStub, + createTaskRunner: () => ({ + run: async () => undefined, + }), + }, + }, + }); + + await runner.markTaskAsRunning(); + + sinon.assert.calledOnce(store.update); + sinon.assert.calledWith(getRetryStub, initialAttempts + 1); + const instance = store.update.args[0][0]; + + const attemptDelay = (initialAttempts + 1) * 5 * 60 * 1000; + const timeoutDelay = timeoutMinutes * 60 * 1000; + expect(instance.retryAt.getTime()).toEqual( + new Date(Date.now() + attemptDelay + timeoutDelay).getTime() + ); + }); + + test('uses getRetry (returning false) to set retryAt when defined', async () => { + const id = _.random(1, 20).toString(); + const initialAttempts = _.random(1, 3); + const timeoutMinutes = 1; + const getRetryStub = sinon.stub().returns(false); + const { runner, store } = testOpts({ + instance: { + id, + attempts: initialAttempts, + schedule: undefined, + }, + definitions: { + bar: { + timeout: `${timeoutMinutes}m`, + getRetry: getRetryStub, + createTaskRunner: () => ({ + run: async () => undefined, + }), + }, + }, + }); + + await runner.markTaskAsRunning(); + + sinon.assert.calledOnce(store.update); + sinon.assert.calledWith(getRetryStub, initialAttempts + 1); + const instance = store.update.args[0][0]; + + expect(instance.retryAt).toBeNull(); + expect(instance.status).toBe('running'); + }); + + test('bypasses getRetry (returning false) of a recurring task to set retryAt when defined', async () => { + const id = _.random(1, 20).toString(); + const initialAttempts = _.random(1, 3); + const timeoutMinutes = 1; + const getRetryStub = sinon.stub().returns(false); + const { runner, store } = testOpts({ + instance: { + id, + attempts: initialAttempts, + schedule: { interval: '1m' }, + startedAt: new Date(), + }, + definitions: { + bar: { + timeout: `${timeoutMinutes}m`, + getRetry: getRetryStub, + createTaskRunner: () => ({ + run: async () => undefined, + }), + }, + }, + }); + + await runner.markTaskAsRunning(); + + sinon.assert.calledOnce(store.update); + sinon.assert.notCalled(getRetryStub); + const instance = store.update.args[0][0]; + + const timeoutDelay = timeoutMinutes * 60 * 1000; + expect(instance.retryAt.getTime()).toEqual(new Date(Date.now() + timeoutDelay).getTime()); + }); + + test('Fails non-recurring task when maxAttempts reached', async () => { + const id = _.random(1, 20).toString(); + const initialAttempts = 3; + const { runner, store } = testOpts({ + instance: { + id, + attempts: initialAttempts, + schedule: undefined, + }, + definitions: { + bar: { + maxAttempts: 3, + createTaskRunner: () => ({ + run: async () => { + throw new Error(); + }, + }), + }, + }, + }); + + await runner.run(); + + sinon.assert.calledOnce(store.update); + const instance = store.update.args[0][0]; + expect(instance.attempts).toEqual(3); + expect(instance.status).toEqual('failed'); + expect(instance.retryAt).toBeNull(); + expect(instance.runAt.getTime()).toBeLessThanOrEqual(Date.now()); + }); + + test(`Doesn't fail recurring tasks when maxAttempts reached`, async () => { + const id = _.random(1, 20).toString(); + const initialAttempts = 3; + const intervalSeconds = 10; + const { runner, store } = testOpts({ + instance: { + id, + attempts: initialAttempts, + schedule: { interval: `${intervalSeconds}s` }, + startedAt: new Date(), + }, + definitions: { + bar: { + maxAttempts: 3, + createTaskRunner: () => ({ + run: async () => { + throw new Error(); + }, + }), + }, + }, + }); + + await runner.run(); + + sinon.assert.calledOnce(store.update); + const instance = store.update.args[0][0]; + expect(instance.attempts).toEqual(3); + expect(instance.status).toEqual('idle'); + expect(instance.runAt.getTime()).toEqual( + new Date(Date.now() + intervalSeconds * 1000).getTime() + ); + }); + + describe('TaskEvents', () => { + test('emits TaskEvent when a task is marked as running', async () => { + const id = _.random(1, 20).toString(); + const onTaskEvent = jest.fn(); + const { runner, instance, store } = testOpts({ + onTaskEvent, + instance: { + id, + }, + definitions: { + bar: { + timeout: `1m`, + getRetry: () => {}, + createTaskRunner: () => ({ + run: async () => undefined, + }), + }, + }, + }); + + store.update.returns(instance); + + await runner.markTaskAsRunning(); + + expect(onTaskEvent).toHaveBeenCalledWith(asTaskMarkRunningEvent(id, asOk(instance))); + }); + + test('emits TaskEvent when a task fails to be marked as running', async () => { + expect.assertions(2); + + const id = _.random(1, 20).toString(); + const onTaskEvent = jest.fn(); + const { runner, store } = testOpts({ + onTaskEvent, + instance: { + id, + }, + definitions: { + bar: { + timeout: `1m`, + getRetry: () => {}, + createTaskRunner: () => ({ + run: async () => undefined, + }), + }, + }, + }); + + store.update.throws(new Error('cant mark as running')); + + try { + await runner.markTaskAsRunning(); + } catch (err) { + expect(onTaskEvent).toHaveBeenCalledWith(asTaskMarkRunningEvent(id, asErr(err))); + } + expect(onTaskEvent).toHaveBeenCalledTimes(1); + }); + + test('emits TaskEvent when a task is run successfully', async () => { + const id = _.random(1, 20).toString(); + const onTaskEvent = jest.fn(); + const { runner, instance } = testOpts({ + onTaskEvent, + instance: { + id, + }, + definitions: { + bar: { + createTaskRunner: () => ({ + async run() { + return {}; + }, + }), + }, + }, + }); + + await runner.run(); + + expect(onTaskEvent).toHaveBeenCalledWith(asTaskRunEvent(id, asOk(instance))); + }); + + test('emits TaskEvent when a recurring task is run successfully', async () => { + const id = _.random(1, 20).toString(); + const runAt = minutesFromNow(_.random(5)); + const onTaskEvent = jest.fn(); + const { runner, instance } = testOpts({ + onTaskEvent, + instance: { + id, + schedule: { interval: '1m' }, + }, + definitions: { + bar: { + createTaskRunner: () => ({ + async run() { + return { runAt }; + }, + }), + }, + }, + }); + + await runner.run(); + + expect(onTaskEvent).toHaveBeenCalledWith(asTaskRunEvent(id, asOk(instance))); + }); + + test('emits TaskEvent when a task run throws an error', async () => { + const id = _.random(1, 20).toString(); + const error = new Error('Dangit!'); + const onTaskEvent = jest.fn(); + const { runner } = testOpts({ + onTaskEvent, + instance: { + id, + }, + definitions: { + bar: { + createTaskRunner: () => ({ + async run() { + throw error; + }, + }), + }, + }, + }); + await runner.run(); + + expect(onTaskEvent).toHaveBeenCalledWith(asTaskRunEvent(id, asErr(error))); + expect(onTaskEvent).toHaveBeenCalledTimes(1); + }); + + test('emits TaskEvent when a task run returns an error', async () => { + const id = _.random(1, 20).toString(); + const error = new Error('Dangit!'); + const onTaskEvent = jest.fn(); + const { runner } = testOpts({ + onTaskEvent, + instance: { + id, + schedule: { interval: '1m' }, + startedAt: new Date(), + }, + definitions: { + bar: { + createTaskRunner: () => ({ + async run() { + return { error }; + }, + }), + }, + }, + }); + + await runner.run(); + + expect(onTaskEvent).toHaveBeenCalledWith(asTaskRunEvent(id, asErr(error))); + expect(onTaskEvent).toHaveBeenCalledTimes(1); + }); + + test('emits TaskEvent when a task returns an error and is marked as failed', async () => { + const id = _.random(1, 20).toString(); + const error = new Error('Dangit!'); + const onTaskEvent = jest.fn(); + const { runner, store } = testOpts({ + onTaskEvent, + instance: { + id, + startedAt: new Date(), + }, + definitions: { + bar: { + getRetry: () => false, + createTaskRunner: () => ({ + async run() { + return { error }; + }, + }), + }, + }, + }); + + await runner.run(); + + const instance = store.update.args[0][0]; + expect(instance.status).toBe('failed'); + + expect(onTaskEvent).toHaveBeenCalledWith(asTaskRunEvent(id, asErr(error))); + expect(onTaskEvent).toHaveBeenCalledTimes(1); + }); + }); + + interface TestOpts { + instance?: Partial; + definitions?: any; + onTaskEvent?: (event: TaskEvent) => void; + } + + function testOpts(opts: TestOpts) { + const callCluster = sinon.stub(); + const createTaskRunner = sinon.stub(); + const logger = mockLogger(); + + const instance = Object.assign( + { + id: 'foo', + taskType: 'bar', + sequenceNumber: 32, + primaryTerm: 32, + runAt: new Date(), + scheduledAt: new Date(), + startedAt: null, + retryAt: null, + attempts: 0, + params: {}, + scope: ['reporting'], + state: {}, + status: 'idle', + user: 'example', + ownerId: null, + }, + opts.instance || {} + ); + + const store = { + update: sinon.stub(), + remove: sinon.stub(), + maxAttempts: 5, + }; + + store.update.returns(instance); + + const runner = new TaskManagerRunner({ + beforeRun: context => Promise.resolve(context), + beforeMarkRunning: context => Promise.resolve(context), + logger, + store, + instance, + definitions: Object.assign(opts.definitions || {}, { + testbar: { + type: 'bar', + title: 'Bar!', + createTaskRunner, + }, + }), + onTaskEvent: opts.onTaskEvent, + }); + + return { + callCluster, + createTaskRunner, + runner, + logger, + store, + instance, + }; + } + + async function testReturn(result: any, shouldBeValid: boolean) { + const { runner, logger } = testOpts({ + definitions: { + bar: { + createTaskRunner: () => ({ + run: async () => result, + }), + }, + }, + }); + + await runner.run(); + + if (shouldBeValid) { + expect(logger.warn).not.toHaveBeenCalled(); + } else { + expect(logger.warn).toHaveBeenCalledTimes(1); + expect(logger.warn.mock.calls[0][0]).toMatch(/invalid task result/i); + } + } + + function allowsReturnType(result: any) { + return testReturn(result, true); + } + + function disallowsReturnType(result: any) { + return testReturn(result, false); + } +}); diff --git a/x-pack/legacy/plugins/task_manager/task_runner.ts b/x-pack/plugins/task_manager/server/task_runner.ts similarity index 100% rename from x-pack/legacy/plugins/task_manager/task_runner.ts rename to x-pack/plugins/task_manager/server/task_runner.ts diff --git a/x-pack/legacy/plugins/task_manager/task_store.test.ts b/x-pack/plugins/task_manager/server/task_store.test.ts similarity index 98% rename from x-pack/legacy/plugins/task_manager/task_store.test.ts rename to x-pack/plugins/task_manager/server/task_store.test.ts index bbce3143e8011..f47cc41c2d045 100644 --- a/x-pack/legacy/plugins/task_manager/task_store.test.ts +++ b/x-pack/plugins/task_manager/server/task_store.test.ts @@ -17,13 +17,12 @@ import { TaskLifecycleResult, } from './task'; import { FetchOpts, StoreOpts, OwnershipClaimingOpts, TaskStore } from './task_store'; -// Task manager uses an unconventional directory structure so the linter marks this as a violation, server files should -// be moved under task_manager/server/ -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { savedObjectsClientMock } from 'src/core/server/mocks'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths -import { SavedObjectsSerializer, SavedObjectsSchema, SavedObjectAttributes } from 'src/core/server'; -// eslint-disable-next-line @kbn/eslint/no-restricted-paths +import { savedObjectsRepositoryMock } from '../../../../src/core/server/mocks'; +import { + SavedObjectsSerializer, + SavedObjectsSchema, + SavedObjectAttributes, +} from '../../../../src/core/server'; import { SavedObjectsErrorHelpers } from '../../../../src/core/server/saved_objects/service/lib/errors'; import { asTaskClaimEvent, TaskEvent } from './task_events'; import { asOk, asErr } from './lib/result_type'; @@ -46,7 +45,7 @@ const taskDefinitions: TaskDictionary = { }, }; -const savedObjectsClient = savedObjectsClientMock.create(); +const savedObjectsClient = savedObjectsRepositoryMock.create(); const serializer = new SavedObjectsSerializer(new SavedObjectsSchema()); beforeEach(() => jest.resetAllMocks()); diff --git a/x-pack/legacy/plugins/task_manager/task_store.ts b/x-pack/plugins/task_manager/server/task_store.ts similarity index 97% rename from x-pack/legacy/plugins/task_manager/task_store.ts rename to x-pack/plugins/task_manager/server/task_store.ts index 096a8774c8488..f4695b152237a 100644 --- a/x-pack/legacy/plugins/task_manager/task_store.ts +++ b/x-pack/plugins/task_manager/server/task_store.ts @@ -11,15 +11,12 @@ import { Subject, Observable } from 'rxjs'; import { omit, difference } from 'lodash'; import { - SavedObjectsClientContract, SavedObject, SavedObjectAttributes, SavedObjectsSerializer, SavedObjectsRawDoc, - // Task manager uses an unconventional directory structure so the linter marks this as a violation, server files should - // be moved under task_manager/server/ - // eslint-disable-next-line @kbn/eslint/no-restricted-paths -} from 'src/core/server'; + ISavedObjectsRepository, +} from '../../../../src/core/server'; import { asOk, asErr } from './lib/result_type'; @@ -63,7 +60,7 @@ export interface StoreOpts { taskManagerId: string; maxAttempts: number; definitions: TaskDictionary; - savedObjectsRepository: SavedObjectsClientContract; + savedObjectsRepository: ISavedObjectsRepository; serializer: SavedObjectsSerializer; } @@ -126,7 +123,7 @@ export class TaskStore { private callCluster: ElasticJs; private definitions: TaskDictionary; - private savedObjectsRepository: SavedObjectsClientContract; + private savedObjectsRepository: ISavedObjectsRepository; private serializer: SavedObjectsSerializer; private events$: Subject; diff --git a/x-pack/legacy/plugins/task_manager/test_utils/index.ts b/x-pack/plugins/task_manager/server/test_utils/index.ts similarity index 100% rename from x-pack/legacy/plugins/task_manager/test_utils/index.ts rename to x-pack/plugins/task_manager/server/test_utils/index.ts diff --git a/x-pack/legacy/plugins/task_manager/types.ts b/x-pack/plugins/task_manager/server/types.ts similarity index 100% rename from x-pack/legacy/plugins/task_manager/types.ts rename to x-pack/plugins/task_manager/server/types.ts diff --git a/x-pack/plugins/translations/translations/ja-JP.json b/x-pack/plugins/translations/translations/ja-JP.json index d957e451fdb74..5661020ba6fa6 100644 --- a/x-pack/plugins/translations/translations/ja-JP.json +++ b/x-pack/plugins/translations/translations/ja-JP.json @@ -441,7 +441,6 @@ "common.ui.flotCharts.tueLabel": "火", "common.ui.flotCharts.wedLabel": "水", "common.ui.management.breadcrumb": "管理", - "common.ui.management.nav.menu": "管理メニュー", "common.ui.modals.cancelButtonLabel": "キャンセル", "common.ui.notify.fatalError.errorStatusMessage": "エラー {errStatus} {errStatusText}: {errMessage}", "common.ui.notify.fatalError.unavailableServerErrorMessage": "HTTP リクエストが接続に失敗しました。Kibana サーバーが実行されていて、ご使用のブラウザの接続が正常に動作していることを確認するか、システム管理者にお問い合わせください。", @@ -519,6 +518,7 @@ "common.ui.aggTypes.buckets.ranges.rangesFormatMessage": "{gte} {from} と {lt} {to}", "management.connectDataDisplayName": "データに接続", "management.displayName": "管理", + "management.nav.menu": "管理メニュー", "management.editIndexPattern.createIndex.defaultButtonDescription": "すべてのデータに完全集約を実行", "management.editIndexPattern.createIndex.defaultButtonText": "標準インデックスパターン", "management.editIndexPattern.createIndex.defaultTypeName": "インデックスパターン", @@ -970,8 +970,8 @@ "kibana-react.savedObjects.saveModal.saveButtonLabel": "保存", "kibana-react.savedObjects.saveModal.saveTitle": "{objectType} を保存", "kibana-react.savedObjects.saveModal.titleLabel": "タイトル", - "kibana_utils.stateManagement.url.unableToRestoreUrlErrorMessage": "URL を完全に復元できません。共有機能を使用していることを確認してください。", - "kibana_utils.stateManagement.url.unableToStoreHistoryInSessionErrorMessage": "セッションがいっぱいで安全に削除できるアイテムが見つからないため、Kibana は履歴アイテムを保存できません。\n\nこれは大抵新規タブに移動することで解決されますが、より大きな問題が原因である可能性もあります。このメッセージが定期的に表示される場合は、{gitHubIssuesUrl} で問題を報告してください。", + "kibana_utils.stateManagement.stateHash.unableToRestoreUrlErrorMessage": "URL を完全に復元できません。共有機能を使用していることを確認してください。", + "kibana_utils.stateManagement.stateHash.unableToStoreHistoryInSessionErrorMessage": "セッションがいっぱいで安全に削除できるアイテムが見つからないため、Kibana は履歴アイテムを保存できません。\n\nこれは大抵新規タブに移動することで解決されますが、より大きな問題が原因である可能性もあります。このメッセージが定期的に表示される場合は、{gitHubIssuesUrl} で問題を報告してください。", "inspector.closeButton": "インスペクターを閉じる", "inspector.reqTimestampDescription": "リクエストの開始が記録された時刻です", "inspector.reqTimestampKey": "リクエストのタイムスタンプ", @@ -1773,10 +1773,7 @@ "kbn.context.reloadPageDescription.selectValidAnchorDocumentTextMessage": "にアクセスして有効な別のドキュメントを選択してください。", "kbn.context.unableToLoadAnchorDocumentDescription": "別のドキュメントが読み込めません", "kbn.context.unableToLoadDocumentDescription": "ドキュメントが読み込めません", - "kbn.dashboard.addVisualizationDescription1": "上のメニューバーの ", - "kbn.dashboard.addVisualizationDescription2": " ボタンをクリックして、ダッシュボードにビジュアライゼーションを追加します。", "kbn.dashboard.addVisualizationLinkAriaLabel": "ビジュアライゼーションを追加", - "kbn.dashboard.addVisualizationLinkText": "追加", "kbn.dashboard.badge.readOnly.text": "読み込み専用", "kbn.dashboard.badge.readOnly.tooltip": "ダッシュボードを保存できません", "kbn.dashboard.changeViewModeConfirmModal.cancelButtonLabel": "編集を続行", @@ -1853,6 +1850,25 @@ "kbn.discover.discoverDescription": "ドキュメントにクエリをかけたりフィルターを適用することで、データをインタラクティブに閲覧できます。", "kbn.discover.discoverTitle": "ディスカバー", "kbn.discover.documentsAriaLabel": "ドキュメント", + "kbn.discover.docViews.table.fieldNamesBeginningWithUnderscoreUnsupportedTooltip": "{underscoreSign} で始まるフィールド名はサポートされていません", + "kbn.discover.docViews.table.filterForFieldPresentButtonAriaLabel": "現在のフィールドのフィルター", + "kbn.discover.docViews.table.filterForFieldPresentButtonTooltip": "現在のフィールドのフィルター", + "kbn.discover.docViews.table.filterForValueButtonAriaLabel": "値でフィルタリング", + "kbn.discover.docViews.table.filterForValueButtonTooltip": "値でフィルタリング", + "kbn.discover.docViews.table.filterOutValueButtonAriaLabel": "値を除外", + "kbn.discover.docViews.table.filterOutValueButtonTooltip": "値を除外します", + "kbn.discover.docViews.table.noCachedMappingForThisFieldTooltip": "このフィールドのキャッシュされたマッピングがありません。管理 > インデックスパターンページからフィールドリストを更新してください", + "kbn.discover.docViews.table.tableTitle": "表", + "kbn.discover.docViews.table.toggleColumnInTableButtonAriaLabel": "表の列を切り替える", + "kbn.discover.docViews.table.toggleColumnInTableButtonTooltip": "表の列を切り替える", + "kbn.discover.docViews.table.unableToFilterForPresenceOfMetaFieldsTooltip": "メタフィールドの有無でフィルタリングできません", + "kbn.discover.docViews.table.unableToFilterForPresenceOfScriptedFieldsTooltip": "スクリプトフィールドの有無でフィルタリングできません", + "kbn.discover.docViews.table.unindexedFieldsCanNotBeSearchedTooltip": "インデックスされていないフィールドは検索できません", + "kbn.discover.docViews.json.codeEditorAriaLabel": "Elasticsearch ドキュメントの JSON ビューのみを読み込む", + "kbn.discover.docViews.json.jsonTitle": "JSON", + "kbn.discover.docViews.table.fieldNamesBeginningWithUnderscoreUnsupportedAriaLabel": "警告", + "kbn.discover.docViews.table.noCachedMappingForThisFieldAriaLabel": "警告", + "kbn.discover.docViews.table.toggleFieldDetails": "フィールド詳細を切り替える", "kbn.discover.errorLoadingData": "データの読み込み中にエラーが発生", "kbn.discover.fetchError.howToAddressErrorDescription": "このエラーは、{scriptedFields} タブにある {managementLink} の {fetchErrorScript} フィールドを編集することで解決できます。", "kbn.discover.fetchError.managmentLinkText": "管理 > インデックスパターン", @@ -2837,25 +2853,6 @@ "kbn.advancedSettings.courier.batchSearchesText": "無効の場合、ダッシュボードパネルは個々に読み込まれ、検索リクエストはユーザーが移動するか\n クエリを更新すると停止します。有効の場合、ダッシュボードパネルはすべてのデータが読み込まれると同時に読み込まれ、\n 検索は停止しません。", "kbn.doc.couldNotFindDocumentsDescription": "その ID に一致するドキュメントがありません。", "kbn.doc.somethingWentWrongDescription": "{indexName} が欠けています。", - "kbnDocViews.table.fieldNamesBeginningWithUnderscoreUnsupportedTooltip": "{underscoreSign} で始まるフィールド名はサポートされていません", - "kbnDocViews.table.filterForFieldPresentButtonAriaLabel": "現在のフィールドのフィルター", - "kbnDocViews.table.filterForFieldPresentButtonTooltip": "現在のフィールドのフィルター", - "kbnDocViews.table.filterForValueButtonAriaLabel": "値でフィルタリング", - "kbnDocViews.table.filterForValueButtonTooltip": "値でフィルタリング", - "kbnDocViews.table.filterOutValueButtonAriaLabel": "値を除外", - "kbnDocViews.table.filterOutValueButtonTooltip": "値を除外します", - "kbnDocViews.table.noCachedMappingForThisFieldTooltip": "このフィールドのキャッシュされたマッピングがありません。管理 > インデックスパターンページからフィールドリストを更新してください", - "kbnDocViews.table.tableTitle": "表", - "kbnDocViews.table.toggleColumnInTableButtonAriaLabel": "表の列を切り替える", - "kbnDocViews.table.toggleColumnInTableButtonTooltip": "表の列を切り替える", - "kbnDocViews.table.unableToFilterForPresenceOfMetaFieldsTooltip": "メタフィールドの有無でフィルタリングできません", - "kbnDocViews.table.unableToFilterForPresenceOfScriptedFieldsTooltip": "スクリプトフィールドの有無でフィルタリングできません", - "kbnDocViews.table.unindexedFieldsCanNotBeSearchedTooltip": "インデックスされていないフィールドは検索できません", - "kbnDocViews.json.codeEditorAriaLabel": "Elasticsearch ドキュメントの JSON ビューのみを読み込む", - "kbnDocViews.json.jsonTitle": "JSON", - "kbnDocViews.table.fieldNamesBeginningWithUnderscoreUnsupportedAriaLabel": "警告", - "kbnDocViews.table.noCachedMappingForThisFieldAriaLabel": "警告", - "kbnDocViews.table.toggleFieldDetails": "フィールド詳細を切り替える", "kbnVislibVisTypes.area.areaDescription": "折れ線グラフの下の数量を強調します。", "kbnVislibVisTypes.area.areaTitle": "エリア", "kbnVislibVisTypes.area.groupTitle": "系列を分割", @@ -6078,8 +6075,6 @@ "xpack.infra.logs.highlights.goToPreviousHighlightButtonLabel": "前のハイライトにスキップ", "xpack.infra.logs.index.settingsTabTitle": "設定", "xpack.infra.logs.index.streamTabTitle": "ストリーム", - "xpack.infra.logs.logsAnalysisResults.onboardingSuccessContent": "機械学習ロボットがデータの収集を開始するまでしばらくお待ちください。", - "xpack.infra.logs.logsAnalysisResults.onboardingSuccessTitle": "成功!", "xpack.infra.logs.streamPage.documentTitle": "{previousTitle} | ストリーム", "xpack.infra.logsPage.toolbar.kqlSearchFieldAriaLabel": "ログエントリーを検索", "xpack.infra.metricDetailPage.awsMetricsLayout.cpuUtilSection.percentSeriesLabel": "パーセント", @@ -6591,8 +6586,6 @@ "xpack.maps.source.wmsTitle": "ウェブマップサービス", "xpack.maps.style.heatmap.displayNameLabel": "ヒートマップスタイル", "xpack.maps.style.heatmap.resolutionStyleErrorMessage": "解像度パラメーターが認識されません: {resolution}", - "xpack.maps.styles.staticDynamic.dynamicDescription": "プロパティ値で特徴をシンボル化します。", - "xpack.maps.styles.staticDynamic.staticDescription": "静的スタイルプロパティで特徴をシンボル化します。", "xpack.maps.styles.vector.borderColorLabel": "境界線の色", "xpack.maps.styles.vector.borderWidthLabel": "境界線の幅", "xpack.maps.styles.vector.fillColorLabel": "塗りつぶす色", @@ -8053,7 +8046,6 @@ "xpack.ml.timeSeriesExplorer.timeSeriesChart.anomalyScoreLabel": "異常スコア", "xpack.ml.timeSeriesExplorer.timeSeriesChart.modelPlotEnabled.lowerBoundsLabel": "下の境界", "xpack.ml.timeSeriesExplorer.timeSeriesChart.modelPlotEnabled.upperBoundsLabel": "上の境界", - "xpack.ml.timeSeriesExplorer.timeSeriesChart.modelPlotEnabled.valueLabel": "値", "xpack.ml.timeSeriesExplorer.timeSeriesChart.moreThanOneUnusualByFieldValuesLabel": "{numberOfCauses} 個の {plusSign}異常な{byFieldName}値", "xpack.ml.timeSeriesExplorer.timeSeriesChart.multiBucketImpactLabel": "複数バケットの影響", "xpack.ml.timeSeriesExplorer.timeSeriesChart.scheduledEventsLabel": "予定イベント {counter}", @@ -10971,7 +10963,6 @@ "xpack.snapshotRestore.repositoryDetails.typeS3.serverSideEncryptionLabel": "サーバー側エコシステム", "xpack.snapshotRestore.repositoryDetails.typeS3.storageClassLabel": "ストレージクラス", "xpack.snapshotRestore.repositoryDetails.typeTitle": "レポジトリタイプ", - "xpack.snapshotRestore.repositoryDetails.verificationDetails": "認証情報レポジトリ「{name}」", "xpack.snapshotRestore.repositoryDetails.verificationDetailsTitle": "詳細", "xpack.snapshotRestore.repositoryDetails.verificationTitle": "認証ステータス", "xpack.snapshotRestore.repositoryDetails.verifyButtonLabel": "レポジトリを検証", @@ -11744,7 +11735,6 @@ "xpack.uptime.emptyState.loadingMessage": "読み込み中…", "xpack.uptime.emptyState.noDataTitle": "利用可能なアップタイムデータがありません", "xpack.uptime.emptyStateError.title": "エラー", - "xpack.uptime.emptyStatusBar.defaultMessage": "監視 ID {monitorId} のデータが見つかりません", "xpack.uptime.errorMessage": "エラー: {message}", "xpack.uptime.featureCatalogueDescription": "エンドポイントヘルスチェックとアップタイム監視を行います。", "xpack.uptime.featureRegistry.uptimeFeatureName": "アップタイム", @@ -11784,7 +11774,6 @@ "xpack.uptime.monitorList.statusColumn.upLabel": "アップ", "xpack.uptime.monitorList.statusColumnLabel": "ステータス", "xpack.uptime.monitorStatusBar.durationTextAriaLabel": "ミリ秒単位の監視時間", - "xpack.uptime.monitorStatusBar.healthStatus.durationInMillisecondsMessage": "{duration}ms", "xpack.uptime.monitorStatusBar.healthStatusMessage.downLabel": "ダウン", "xpack.uptime.monitorStatusBar.healthStatusMessage.upLabel": "アップ", "xpack.uptime.monitorStatusBar.healthStatusMessageAriaLabel": "監視ステータス", @@ -11821,9 +11810,9 @@ "xpack.uptime.pingList.expandRow": "拡張", "xpack.uptime.snapshot.pingsOverTimeTitle": "一定時間のピング", "xpack.uptime.snapshotHistogram.yAxis.title": "ピング", - "xpack.uptime.donutChart.ariaLabel": "現在のステータスを表す円グラフ、{total} 個中 {down} 個のモニターがダウンしています。", - "xpack.uptime.donutChart.legend.downRowLabel": "ダウン", - "xpack.uptime.donutChart.legend.upRowLabel": "アップ", + "xpack.uptime.snapshot.donutChart.ariaLabel": "現在のステータスを表す円グラフ、{total} 個中 {down} 個のモニターがダウンしています。", + "xpack.uptime.snapshot.donutChart.legend.downRowLabel": "ダウン", + "xpack.uptime.snapshot.donutChart.legend.upRowLabel": "アップ", "xpack.uptime.durationChart.emptyPrompt.description": "このモニターは選択された時間範囲で一度も {emphasizedText} していません。", "xpack.uptime.durationChart.emptyPrompt.title": "利用可能な期間データがありません", "xpack.uptime.emptyStateError.notAuthorized": "アップタイムデータの表示が承認されていません。システム管理者にお問い合わせください。", @@ -11834,8 +11823,6 @@ "xpack.uptime.kueryBar.searchPlaceholder": "モニター ID、名前、プロトコルタイプなどを検索…", "xpack.uptime.monitorList.noItemForSelectedFiltersMessage": "選択されたフィルター条件でモニターが見つかりませんでした", "xpack.uptime.monitorList.table.description": "列にステータス、名前、URL、IP、ダウンタイム履歴、統合が入力されたモニターステータス表です。この表は現在 {length} 項目を表示しています。", - "xpack.uptime.monitorStatusBar.sslCertificateExpiry.ariaLabel": "SSL 証明書の有効期限:", - "xpack.uptime.monitorStatusBar.sslCertificateExpiry.content": "SSL 証明書の有効期限: {certificateValidity}", "xpack.uptime.notFountPage.homeLinkText": "ホームへ戻る", "xpack.uptime.overviewPageLink.disabled.ariaLabel": "無効になったページ付けボタンです。モニターリストがこれ以上ナビゲーションできないことを示しています。", "xpack.uptime.overviewPageLink.next.ariaLabel": "次の結果ページ", diff --git a/x-pack/plugins/translations/translations/zh-CN.json b/x-pack/plugins/translations/translations/zh-CN.json index 2e47c7a615e36..1bcfab4240aed 100644 --- a/x-pack/plugins/translations/translations/zh-CN.json +++ b/x-pack/plugins/translations/translations/zh-CN.json @@ -441,7 +441,6 @@ "common.ui.flotCharts.tueLabel": "周二", "common.ui.flotCharts.wedLabel": "周三", "common.ui.management.breadcrumb": "管理", - "common.ui.management.nav.menu": "管理菜单", "common.ui.modals.cancelButtonLabel": "取消", "common.ui.notify.fatalError.errorStatusMessage": "错误 {errStatus} {errStatusText}:{errMessage}", "common.ui.notify.fatalError.unavailableServerErrorMessage": "HTTP 请求无法连接。请检查 Kibana 服务器是否正在运行以及您的浏览器是否具有有效的连接,或请联系您的系统管理员。", @@ -520,6 +519,7 @@ "common.ui.aggTypes.buckets.ranges.rangesFormatMessage": "{gte} {from} 且 {lt} {to}", "management.connectDataDisplayName": "连接数据", "management.displayName": "管理", + "management.nav.menu": "管理菜单", "management.editIndexPattern.createIndex.defaultButtonDescription": "对任何数据执行完全聚合", "management.editIndexPattern.createIndex.defaultButtonText": "标准索引模式", "management.editIndexPattern.createIndex.defaultTypeName": "索引模式", @@ -971,8 +971,8 @@ "kibana-react.savedObjects.saveModal.saveButtonLabel": "保存", "kibana-react.savedObjects.saveModal.saveTitle": "保存 {objectType}", "kibana-react.savedObjects.saveModal.titleLabel": "标题", - "kibana_utils.stateManagement.url.unableToRestoreUrlErrorMessage": "无法完整还原 URL,确保使用共享功能。", - "kibana_utils.stateManagement.url.unableToStoreHistoryInSessionErrorMessage": "Kibana 无法将历史记录项存储在您的会话中,因为其已满,并且似乎没有任何可安全删除的项。\n\n通常可通过移至新的标签页来解决此问题,但这会导致更大的问题。如果您有规律地看到此消息,请在 {gitHubIssuesUrl} 提交问题。", + "kibana_utils.stateManagement.stateHash.unableToRestoreUrlErrorMessage": "无法完整还原 URL,确保使用共享功能。", + "kibana_utils.stateManagement.stateHash.unableToStoreHistoryInSessionErrorMessage": "Kibana 无法将历史记录项存储在您的会话中,因为其已满,并且似乎没有任何可安全删除的项。\n\n通常可通过移至新的标签页来解决此问题,但这会导致更大的问题。如果您有规律地看到此消息,请在 {gitHubIssuesUrl} 提交问题。", "inspector.closeButton": "关闭检查器", "inspector.reqTimestampDescription": "记录请求启动的时间", "inspector.reqTimestampKey": "请求时间戳", @@ -1774,10 +1774,7 @@ "kbn.context.reloadPageDescription.selectValidAnchorDocumentTextMessage": "以选择有效地定位点文档。", "kbn.context.unableToLoadAnchorDocumentDescription": "无法加载该定位点文档", "kbn.context.unableToLoadDocumentDescription": "无法加载文档", - "kbn.dashboard.addVisualizationDescription1": "单击上述菜单栏中的 ", - "kbn.dashboard.addVisualizationDescription2": " 按钮,以将可视化添加到仪表板。", "kbn.dashboard.addVisualizationLinkAriaLabel": "添加可视化", - "kbn.dashboard.addVisualizationLinkText": "添加", "kbn.dashboard.badge.readOnly.text": "只读", "kbn.dashboard.badge.readOnly.tooltip": "无法保存仪表板", "kbn.dashboard.changeViewModeConfirmModal.cancelButtonLabel": "继续编辑", @@ -1854,6 +1851,25 @@ "kbn.discover.discoverDescription": "通过查询和筛选原始文档来以交互方式浏览您的数据。", "kbn.discover.discoverTitle": "Discover", "kbn.discover.documentsAriaLabel": "文档", + "kbn.discover.docViews.table.fieldNamesBeginningWithUnderscoreUnsupportedTooltip": "不支持以 {underscoreSign} 开头的字段名称", + "kbn.discover.docViews.table.filterForFieldPresentButtonAriaLabel": "筛留存在的字段", + "kbn.discover.docViews.table.filterForFieldPresentButtonTooltip": "筛留存在的字段", + "kbn.discover.docViews.table.filterForValueButtonAriaLabel": "筛留值", + "kbn.discover.docViews.table.filterForValueButtonTooltip": "筛留值", + "kbn.discover.docViews.table.filterOutValueButtonAriaLabel": "筛除值", + "kbn.discover.docViews.table.filterOutValueButtonTooltip": "筛除值", + "kbn.discover.docViews.table.noCachedMappingForThisFieldTooltip": "此字段没有任何已缓存映射。从“管理”>“索引模式”页面刷新字段列表", + "kbn.discover.docViews.table.tableTitle": "表", + "kbn.discover.docViews.table.toggleColumnInTableButtonAriaLabel": "在表中切换列", + "kbn.discover.docViews.table.toggleColumnInTableButtonTooltip": "在表中切换列", + "kbn.discover.docViews.table.unableToFilterForPresenceOfMetaFieldsTooltip": "无法筛留元数据字段", + "kbn.discover.docViews.table.unableToFilterForPresenceOfScriptedFieldsTooltip": "无法筛留脚本字段", + "kbn.discover.docViews.table.unindexedFieldsCanNotBeSearchedTooltip": "无法搜索未索引字段", + "kbn.discover.docViews.json.codeEditorAriaLabel": "Elasticsearch 文档的只读 JSON 视图", + "kbn.discover.docViews.json.jsonTitle": "JSON", + "kbn.discover.docViews.table.fieldNamesBeginningWithUnderscoreUnsupportedAriaLabel": "警告", + "kbn.discover.docViews.table.noCachedMappingForThisFieldAriaLabel": "警告", + "kbn.discover.docViews.table.toggleFieldDetails": "切换字段详细信息", "kbn.discover.errorLoadingData": "加载数据时出错", "kbn.discover.fetchError.howToAddressErrorDescription": "您可以通过编辑 “{scriptedFields}” 选项卡下 “{managementLink}” 中的 “{fetchErrorScript}” 字段来解决此错误。", "kbn.discover.fetchError.managmentLinkText": "管理 > 索引模式", @@ -2838,25 +2854,6 @@ "kbn.advancedSettings.courier.batchSearchesText": "禁用时,仪表板面板将分别加载,用户离开时或更新查询时,\n 搜索请求将终止。启用时,仪表板面板将一起加载并加载所有数据,\n 搜索将不会终止。", "kbn.doc.couldNotFindDocumentsDescription": "无文档匹配该 ID。", "kbn.doc.somethingWentWrongDescription": "{indexName} 缺失。", - "kbnDocViews.table.fieldNamesBeginningWithUnderscoreUnsupportedTooltip": "不支持以 {underscoreSign} 开头的字段名称", - "kbnDocViews.table.filterForFieldPresentButtonAriaLabel": "筛留存在的字段", - "kbnDocViews.table.filterForFieldPresentButtonTooltip": "筛留存在的字段", - "kbnDocViews.table.filterForValueButtonAriaLabel": "筛留值", - "kbnDocViews.table.filterForValueButtonTooltip": "筛留值", - "kbnDocViews.table.filterOutValueButtonAriaLabel": "筛除值", - "kbnDocViews.table.filterOutValueButtonTooltip": "筛除值", - "kbnDocViews.table.noCachedMappingForThisFieldTooltip": "此字段没有任何已缓存映射。从“管理”>“索引模式”页面刷新字段列表", - "kbnDocViews.table.tableTitle": "表", - "kbnDocViews.table.toggleColumnInTableButtonAriaLabel": "在表中切换列", - "kbnDocViews.table.toggleColumnInTableButtonTooltip": "在表中切换列", - "kbnDocViews.table.unableToFilterForPresenceOfMetaFieldsTooltip": "无法筛留元数据字段", - "kbnDocViews.table.unableToFilterForPresenceOfScriptedFieldsTooltip": "无法筛留脚本字段", - "kbnDocViews.table.unindexedFieldsCanNotBeSearchedTooltip": "无法搜索未索引字段", - "kbnDocViews.json.codeEditorAriaLabel": "Elasticsearch 文档的只读 JSON 视图", - "kbnDocViews.json.jsonTitle": "JSON", - "kbnDocViews.table.fieldNamesBeginningWithUnderscoreUnsupportedAriaLabel": "警告", - "kbnDocViews.table.noCachedMappingForThisFieldAriaLabel": "警告", - "kbnDocViews.table.toggleFieldDetails": "切换字段详细信息", "kbnVislibVisTypes.area.areaDescription": "突出折线图下方的数量", "kbnVislibVisTypes.area.areaTitle": "面积图", "kbnVislibVisTypes.area.groupTitle": "拆分序列", @@ -6080,8 +6077,6 @@ "xpack.infra.logs.highlights.goToPreviousHighlightButtonLabel": "跳转到上一高亮条目", "xpack.infra.logs.index.settingsTabTitle": "设置", "xpack.infra.logs.index.streamTabTitle": "流式传输", - "xpack.infra.logs.logsAnalysisResults.onboardingSuccessContent": "请注意,我们的 Machine Learning 机器人若干分钟后才会开始收集数据。", - "xpack.infra.logs.logsAnalysisResults.onboardingSuccessTitle": "成功!", "xpack.infra.logs.streamPage.documentTitle": "{previousTitle} | 流式传输", "xpack.infra.logsPage.toolbar.kqlSearchFieldAriaLabel": "搜索日志条目", "xpack.infra.metricDetailPage.awsMetricsLayout.cpuUtilSection.percentSeriesLabel": "百分比", @@ -6593,8 +6588,6 @@ "xpack.maps.source.wmsTitle": "Web 地图服务", "xpack.maps.style.heatmap.displayNameLabel": "热图样式", "xpack.maps.style.heatmap.resolutionStyleErrorMessage": "无法识别分辨率参数:{resolution}", - "xpack.maps.styles.staticDynamic.dynamicDescription": "使用属性值代表功能。", - "xpack.maps.styles.staticDynamic.staticDescription": "使用静态样式属性代表功能。", "xpack.maps.styles.vector.borderColorLabel": "边框颜色", "xpack.maps.styles.vector.borderWidthLabel": "边框宽度", "xpack.maps.styles.vector.fillColorLabel": "填充颜色", @@ -8142,7 +8135,6 @@ "xpack.ml.timeSeriesExplorer.timeSeriesChart.anomalyScoreLabel": "异常分数", "xpack.ml.timeSeriesExplorer.timeSeriesChart.modelPlotEnabled.lowerBoundsLabel": "下边界", "xpack.ml.timeSeriesExplorer.timeSeriesChart.modelPlotEnabled.upperBoundsLabel": "上边界", - "xpack.ml.timeSeriesExplorer.timeSeriesChart.modelPlotEnabled.valueLabel": "值", "xpack.ml.timeSeriesExplorer.timeSeriesChart.moreThanOneUnusualByFieldValuesLabel": "{numberOfCauses}{plusSign} 异常 {byFieldName} 值", "xpack.ml.timeSeriesExplorer.timeSeriesChart.multiBucketImpactLabel": "多存储桶影响", "xpack.ml.timeSeriesExplorer.timeSeriesChart.scheduledEventsLabel": "已计划事件{counter}", @@ -11060,7 +11052,6 @@ "xpack.snapshotRestore.repositoryDetails.typeS3.serverSideEncryptionLabel": "服务器端加密", "xpack.snapshotRestore.repositoryDetails.typeS3.storageClassLabel": "存储类", "xpack.snapshotRestore.repositoryDetails.typeTitle": "存储库类型", - "xpack.snapshotRestore.repositoryDetails.verificationDetails": "验证详情存储库“{name}”", "xpack.snapshotRestore.repositoryDetails.verificationDetailsTitle": "详情", "xpack.snapshotRestore.repositoryDetails.verificationTitle": "验证状态", "xpack.snapshotRestore.repositoryDetails.verifyButtonLabel": "验证存储库", @@ -11833,7 +11824,6 @@ "xpack.uptime.emptyState.loadingMessage": "正在加载……", "xpack.uptime.emptyState.noDataTitle": "没有可用的运行时间数据", "xpack.uptime.emptyStateError.title": "错误", - "xpack.uptime.emptyStatusBar.defaultMessage": "未找到监测 ID {monitorId} 的数据", "xpack.uptime.errorMessage": "错误:{message}", "xpack.uptime.featureCatalogueDescription": "执行终端节点运行状况检查和运行时间监测。", "xpack.uptime.featureRegistry.uptimeFeatureName": "运行时间", @@ -11873,7 +11863,6 @@ "xpack.uptime.monitorList.statusColumn.upLabel": "运行", "xpack.uptime.monitorList.statusColumnLabel": "状态", "xpack.uptime.monitorStatusBar.durationTextAriaLabel": "监测持续时间(毫秒)", - "xpack.uptime.monitorStatusBar.healthStatus.durationInMillisecondsMessage": "{duration}ms", "xpack.uptime.monitorStatusBar.healthStatusMessage.downLabel": "关闭", "xpack.uptime.monitorStatusBar.healthStatusMessage.upLabel": "运行", "xpack.uptime.monitorStatusBar.healthStatusMessageAriaLabel": "检测状态", @@ -11910,9 +11899,9 @@ "xpack.uptime.pingList.expandRow": "展开", "xpack.uptime.snapshot.pingsOverTimeTitle": "时移 Ping 数", "xpack.uptime.snapshotHistogram.yAxis.title": "Ping", - "xpack.uptime.donutChart.ariaLabel": "显示当前状态的饼图。{down} 个监测已关闭,共 {total} 个。", - "xpack.uptime.donutChart.legend.downRowLabel": "关闭", - "xpack.uptime.donutChart.legend.upRowLabel": "运行", + "xpack.uptime.snapshot.donutChart.ariaLabel": "显示当前状态的饼图。{down} 个监测已关闭,共 {total} 个。", + "xpack.uptime.snapshot.donutChart.legend.downRowLabel": "关闭", + "xpack.uptime.snapshot.donutChart.legend.upRowLabel": "运行", "xpack.uptime.durationChart.emptyPrompt.description": "在选定时间范围内此监测从未{emphasizedText}。", "xpack.uptime.durationChart.emptyPrompt.title": "没有持续时间数据", "xpack.uptime.emptyStateError.notAuthorized": "您无权查看 Uptime 数据,请联系系统管理员。", @@ -11923,8 +11912,6 @@ "xpack.uptime.kueryBar.searchPlaceholder": "搜索监测 ID、名称和协议类型......", "xpack.uptime.monitorList.noItemForSelectedFiltersMessage": "未找到匹配选定筛选条件的监测", "xpack.uptime.monitorList.table.description": "具有“状态”、“名称”、“URL”、“IP”、“中断历史记录”和“集成”列的“监测状态”表。该表当前显示 {length} 个项目。", - "xpack.uptime.monitorStatusBar.sslCertificateExpiry.ariaLabel": "SSL 证书过期", - "xpack.uptime.monitorStatusBar.sslCertificateExpiry.content": "SSL 证书于 {certificateValidity} 过期", "xpack.uptime.notFountPage.homeLinkText": "返回主页", "xpack.uptime.overviewPageLink.disabled.ariaLabel": "禁用的分页按钮表示在监测列表中无法进行进一步导航。", "xpack.uptime.overviewPageLink.next.ariaLabel": "下页结果", diff --git a/x-pack/scripts/functional_tests.js b/x-pack/scripts/functional_tests.js index 2b92e70fb30af..11ee038cf39f0 100644 --- a/x-pack/scripts/functional_tests.js +++ b/x-pack/scripts/functional_tests.js @@ -4,38 +4,45 @@ * you may not use this file except in compliance with the Elastic License. */ -require('@kbn/plugin-helpers').babelRegister(); -require('@kbn/test').runTestsCli([ +const alwaysImportedTests = [require.resolve('../test/functional/config.js')]; +const onlyNotInCoverageTests = [ require.resolve('../test/reporting/configs/chromium_api.js'), require.resolve('../test/reporting/configs/chromium_functional.js'), - require.resolve('../test/reporting/configs/generate_api'), - require.resolve('../test/functional/config.js'), + require.resolve('../test/reporting/configs/generate_api.js'), + require.resolve('../test/functional_with_es_ssl/config.ts'), + require.resolve('../test/functional/config_security_basic.js'), require.resolve('../test/api_integration/config_security_basic.js'), require.resolve('../test/api_integration/config.js'), require.resolve('../test/alerting_api_integration/spaces_only/config.ts'), require.resolve('../test/alerting_api_integration/security_and_spaces/config.ts'), require.resolve('../test/plugin_api_integration/config.js'), - require.resolve('../test/plugin_functional/config'), - require.resolve('../test/kerberos_api_integration/config'), - require.resolve('../test/kerberos_api_integration/anonymous_access.config'), - require.resolve('../test/saml_api_integration/config'), - require.resolve('../test/token_api_integration/config'), - require.resolve('../test/oidc_api_integration/config'), - require.resolve('../test/oidc_api_integration/implicit_flow.config'), - require.resolve('../test/pki_api_integration/config'), - require.resolve('../test/spaces_api_integration/spaces_only/config'), - require.resolve('../test/spaces_api_integration/security_and_spaces/config_trial'), - require.resolve('../test/spaces_api_integration/security_and_spaces/config_basic'), - require.resolve('../test/saved_object_api_integration/security_and_spaces/config_trial'), - require.resolve('../test/saved_object_api_integration/security_and_spaces/config_basic'), - require.resolve('../test/saved_object_api_integration/security_only/config_trial'), - require.resolve('../test/saved_object_api_integration/security_only/config_basic'), - require.resolve('../test/saved_object_api_integration/spaces_only/config'), - require.resolve('../test/ui_capabilities/security_and_spaces/config'), - require.resolve('../test/ui_capabilities/security_only/config'), - require.resolve('../test/ui_capabilities/spaces_only/config'), - require.resolve('../test/upgrade_assistant_integration/config'), - require.resolve('../test/licensing_plugin/config'), - require.resolve('../test/licensing_plugin/config.public'), - require.resolve('../test/licensing_plugin/config.legacy'), + require.resolve('../test/plugin_functional/config.ts'), + require.resolve('../test/kerberos_api_integration/config.ts'), + require.resolve('../test/kerberos_api_integration/anonymous_access.config.ts'), + require.resolve('../test/saml_api_integration/config.ts'), + require.resolve('../test/token_api_integration/config.js'), + require.resolve('../test/oidc_api_integration/config.ts'), + require.resolve('../test/oidc_api_integration/implicit_flow.config.ts'), + require.resolve('../test/pki_api_integration/config.ts'), + require.resolve('../test/spaces_api_integration/spaces_only/config.ts'), + require.resolve('../test/spaces_api_integration/security_and_spaces/config_trial.ts'), + require.resolve('../test/spaces_api_integration/security_and_spaces/config_basic.ts'), + require.resolve('../test/saved_object_api_integration/security_and_spaces/config_trial.ts'), + require.resolve('../test/saved_object_api_integration/security_and_spaces/config_basic.ts'), + require.resolve('../test/saved_object_api_integration/security_only/config_trial.ts'), + require.resolve('../test/saved_object_api_integration/security_only/config_basic.ts'), + require.resolve('../test/saved_object_api_integration/spaces_only/config.ts'), + require.resolve('../test/ui_capabilities/security_and_spaces/config.ts'), + require.resolve('../test/ui_capabilities/security_only/config.ts'), + require.resolve('../test/ui_capabilities/spaces_only/config.ts'), + require.resolve('../test/upgrade_assistant_integration/config.js'), + require.resolve('../test/licensing_plugin/config.ts'), + require.resolve('../test/licensing_plugin/config.public.ts'), + require.resolve('../test/licensing_plugin/config.legacy.ts'), +]; + +require('@kbn/plugin-helpers').babelRegister(); +require('@kbn/test').runTestsCli([ + ...alwaysImportedTests, + ...(!!process.env.CODE_COVERAGE ? [] : onlyNotInCoverageTests), ]); diff --git a/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/index.ts b/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/index.ts index ebe741df71d79..b5d201c1682bd 100644 --- a/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/index.ts +++ b/x-pack/test/alerting_api_integration/common/fixtures/plugins/alerts/index.ts @@ -202,8 +202,21 @@ export default function(kibana: any) { id: 'test.always-firing', name: 'Test: Always Firing', actionGroups: ['default', 'other'], - async executor({ services, params, state }: AlertExecutorOptions) { + async executor(alertExecutorOptions: AlertExecutorOptions) { + const { + services, + params, + state, + alertId, + spaceId, + namespace, + name, + tags, + createdBy, + updatedBy, + } = alertExecutorOptions; let group = 'default'; + const alertInfo = { alertId, spaceId, namespace, name, tags, createdBy, updatedBy }; if (params.groupsToScheduleActionsInSeries) { const index = state.groupInSeriesIndex || 0; @@ -226,6 +239,7 @@ export default function(kibana: any) { params, reference: params.reference, source: 'alert:test.always-firing', + alertInfo, }, }); return { diff --git a/x-pack/test/alerting_api_integration/common/fixtures/plugins/task_manager/index.ts b/x-pack/test/alerting_api_integration/common/fixtures/plugins/task_manager/index.ts index 3bfad59b71166..29708f86b0a9b 100644 --- a/x-pack/test/alerting_api_integration/common/fixtures/plugins/task_manager/index.ts +++ b/x-pack/test/alerting_api_integration/common/fixtures/plugins/task_manager/index.ts @@ -4,6 +4,8 @@ * you may not use this file except in compliance with the Elastic License. */ +import { TaskManagerStartContract } from '../../../../../../plugins/task_manager/server'; + const taskManagerQuery = (...filters: any[]) => ({ bool: { filter: { @@ -38,7 +40,7 @@ export default function(kibana: any) { }, init(server: any) { - const taskManager = server.plugins.task_manager; + const taskManager = server.newPlatform.start.plugins.taskManager as TaskManagerStartContract; server.route({ path: '/api/alerting_tasks/{taskId}', diff --git a/x-pack/test/alerting_api_integration/common/lib/alert_utils.ts b/x-pack/test/alerting_api_integration/common/lib/alert_utils.ts index 487f396d7a3dc..c793af359489a 100644 --- a/x-pack/test/alerting_api_integration/common/lib/alert_utils.ts +++ b/x-pack/test/alerting_api_integration/common/lib/alert_utils.ts @@ -17,13 +17,21 @@ export interface AlertUtilsOpts { objectRemover?: ObjectRemover; } -export interface CreateAlwaysFiringActionOpts { +export interface CreateAlertWithActionOpts { indexRecordActionId?: string; objectRemover?: ObjectRemover; overwrites?: Record; reference: string; } +interface UpdateAlwaysFiringAction { + alertId: string; + actionId: string | undefined; + reference: string; + user: User; + overwrites: Record; +} + export class AlertUtils { private referenceCounter = 1; private readonly user?: User; @@ -159,7 +167,64 @@ export class AlertUtils { overwrites = {}, indexRecordActionId, reference, - }: CreateAlwaysFiringActionOpts) { + }: CreateAlertWithActionOpts) { + const objRemover = objectRemover || this.objectRemover; + const actionId = indexRecordActionId || this.indexRecordActionId; + + if (!objRemover) { + throw new Error('objectRemover is required'); + } + if (!actionId) { + throw new Error('indexRecordActionId is required '); + } + + let request = this.supertestWithoutAuth + .post(`${getUrlPrefix(this.space.id)}/api/alert`) + .set('kbn-xsrf', 'foo'); + if (this.user) { + request = request.auth(this.user.username, this.user.password); + } + const alertBody = getDefaultAlwaysFiringAlertData(reference, actionId); + const response = await request.send({ ...alertBody, ...overwrites }); + if (response.statusCode === 200) { + objRemover.add(this.space.id, response.body.id, 'alert'); + } + return response; + } + + public async updateAlwaysFiringAction({ + alertId, + actionId, + reference, + user, + overwrites = {}, + }: UpdateAlwaysFiringAction) { + actionId = actionId || this.indexRecordActionId; + + if (!actionId) { + throw new Error('actionId is required '); + } + + const request = this.supertestWithoutAuth + .put(`${getUrlPrefix(this.space.id)}/api/alert/${alertId}`) + .set('kbn-xsrf', 'foo') + .auth(user.username, user.password); + + const alertBody = getDefaultAlwaysFiringAlertData(reference, actionId); + delete alertBody.alertTypeId; + delete alertBody.enabled; + delete alertBody.consumer; + + const response = await request.send({ ...alertBody, ...overwrites }); + return response; + } + + public async createAlwaysFailingAction({ + objectRemover, + overwrites = {}, + indexRecordActionId, + reference, + }: CreateAlertWithActionOpts) { const objRemover = objectRemover || this.objectRemover; const actionId = indexRecordActionId || this.indexRecordActionId; @@ -178,28 +243,17 @@ export class AlertUtils { } const response = await request.send({ enabled: true, - name: 'abc', - schedule: { interval: '1m' }, - throttle: '1m', + name: 'fail', + schedule: { interval: '30s' }, + throttle: '30s', tags: [], - alertTypeId: 'test.always-firing', + alertTypeId: 'test.failing', consumer: 'bar', params: { index: ES_TEST_INDEX_NAME, reference, }, - actions: [ - { - group: 'default', - id: this.indexRecordActionId, - params: { - index: ES_TEST_INDEX_NAME, - reference, - message: - 'instanceContextValue: {{context.instanceContextValue}}, instanceStateValue: {{state.instanceStateValue}}', - }, - }, - ], + actions: [], ...overwrites, }); if (response.statusCode === 200) { @@ -208,3 +262,31 @@ export class AlertUtils { return response; } } + +function getDefaultAlwaysFiringAlertData(reference: string, actionId: string) { + return { + enabled: true, + name: 'abc', + schedule: { interval: '1m' }, + throttle: '1m', + tags: [], + alertTypeId: 'test.always-firing', + consumer: 'bar', + params: { + index: ES_TEST_INDEX_NAME, + reference, + }, + actions: [ + { + group: 'default', + id: actionId, + params: { + index: ES_TEST_INDEX_NAME, + reference, + message: + 'instanceContextValue: {{context.instanceContextValue}}, instanceStateValue: {{state.instanceStateValue}}', + }, + }, + ], + }; +} diff --git a/x-pack/test/alerting_api_integration/common/lib/index.ts b/x-pack/test/alerting_api_integration/common/lib/index.ts index a2f21264634f8..c1e59664f9ce2 100644 --- a/x-pack/test/alerting_api_integration/common/lib/index.ts +++ b/x-pack/test/alerting_api_integration/common/lib/index.ts @@ -10,4 +10,5 @@ export { ES_TEST_INDEX_NAME, ESTestIndexTool } from './es_test_index_tool'; export { getTestAlertData } from './get_test_alert_data'; export { AlertUtils } from './alert_utils'; export { TaskManagerUtils } from './task_manager_utils'; +export * from './test_assertions'; export { checkAAD } from './check_aad'; diff --git a/x-pack/test/alerting_api_integration/common/lib/test_assertions.ts b/x-pack/test/alerting_api_integration/common/lib/test_assertions.ts new file mode 100644 index 0000000000000..9495dd4cfae82 --- /dev/null +++ b/x-pack/test/alerting_api_integration/common/lib/test_assertions.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; + +export function ensureDatetimeIsWithinRange( + date: number, + expectedDiff: number, + buffer: number = 10000 +) { + const diff = date - Date.now(); + expect(diff).to.be.greaterThan(expectedDiff - buffer); + expect(diff).to.be.lessThan(expectedDiff + buffer); +} diff --git a/x-pack/test/alerting_api_integration/common/types.ts b/x-pack/test/alerting_api_integration/common/types.ts index e94add5bbcd28..c4a341435aaaa 100644 --- a/x-pack/test/alerting_api_integration/common/types.ts +++ b/x-pack/test/alerting_api_integration/common/types.ts @@ -52,6 +52,7 @@ export interface User { export interface Space { id: string; + namespace?: string; name: string; disabledFeatures: string[]; } diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/scenarios.ts b/x-pack/test/alerting_api_integration/security_and_spaces/scenarios.ts index 354d87bd11bb2..d58fcd29e29fc 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/scenarios.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/scenarios.ts @@ -29,7 +29,7 @@ const NoKibanaPrivileges: User = { }, }; -const Superuser: User = { +export const Superuser: User = { username: 'superuser', fullName: 'superuser', password: 'superuser-password', diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/alerts.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/alerts.ts index 69bc547e3bfc1..d20450f8ec47e 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/alerts.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/alerts.ts @@ -5,7 +5,7 @@ */ import expect from '@kbn/expect'; -import { UserAtSpaceScenarios } from '../../scenarios'; +import { UserAtSpaceScenarios, Superuser } from '../../scenarios'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; import { ESTestIndexTool, @@ -96,7 +96,9 @@ export default function alertTests({ getService }: FtrProviderContext) { // Wait for the action to index a document before disabling the alert and waiting for tasks to finish await esTestIndexTool.waitForDocs('action:test.index-record', reference); - await alertUtils.disable(response.body.id); + + const alertId = response.body.id; + await alertUtils.disable(alertId); await taskManagerUtils.waitForIdle(testStart); // Ensure only 1 alert executed with proper params @@ -113,6 +115,15 @@ export default function alertTests({ getService }: FtrProviderContext) { index: ES_TEST_INDEX_NAME, reference, }, + alertInfo: { + alertId, + spaceId: space.id, + namespace: space.id, + name: 'abc', + tags: [], + createdBy: user.fullName, + updatedBy: user.fullName, + }, }); // Ensure only 1 action executed with proper params @@ -142,6 +153,56 @@ export default function alertTests({ getService }: FtrProviderContext) { } }); + it('should pass updated alert params to executor', async () => { + // create an alert + const reference = alertUtils.generateReference(); + const overwrites = { + throttle: '1s', + schedule: { interval: '1s' }, + }; + const response = await alertUtils.createAlwaysFiringAction({ reference, overwrites }); + + // only need to test creation success paths + if (response.statusCode !== 200) return; + + // update the alert with super user + const alertId = response.body.id; + const reference2 = alertUtils.generateReference(); + const response2 = await alertUtils.updateAlwaysFiringAction({ + alertId, + actionId: indexRecordActionId, + user: Superuser, + reference: reference2, + overwrites: { + name: 'def', + tags: ['fee', 'fi', 'fo'], + throttle: '1s', + schedule: { interval: '1s' }, + }, + }); + + expect(response2.statusCode).to.eql(200); + + // make sure alert info passed to executor is correct + await esTestIndexTool.waitForDocs('alert:test.always-firing', reference2); + await alertUtils.disable(alertId); + const alertSearchResult = await esTestIndexTool.search( + 'alert:test.always-firing', + reference2 + ); + + expect(alertSearchResult.hits.total.value).to.be.greaterThan(0); + expect(alertSearchResult.hits.hits[0]._source.alertInfo).to.eql({ + alertId, + spaceId: space.id, + namespace: space.id, + name: 'def', + tags: ['fee', 'fi', 'fo'], + createdBy: user.fullName, + updatedBy: Superuser.fullName, + }); + }); + it('should handle custom retry logic when appropriate', async () => { const testStart = new Date(); // We have to provide the test.rate-limit the next runAt, for testing purposes @@ -700,7 +761,8 @@ export default function alertTests({ getService }: FtrProviderContext) { } }); - it(`should unmute all instances when unmuting an alert`, async () => { + // Flaky: https://github.com/elastic/kibana/issues/54125 + it.skip(`should unmute all instances when unmuting an alert`, async () => { const testStart = new Date(); const reference = alertUtils.generateReference(); const response = await alertUtils.createAlwaysFiringAction({ diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/create.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/create.ts index 772f85c4dac8c..003bb10c0adae 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/create.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/create.ts @@ -92,6 +92,8 @@ export default function createAlertTests({ getService }: FtrProviderContext) { createdBy: user.username, schedule: { interval: '1m' }, scheduledTaskId: response.body.scheduledTaskId, + createdAt: response.body.createdAt, + updatedAt: response.body.updatedAt, throttle: '1m', updatedBy: user.username, apiKeyOwner: user.username, @@ -99,6 +101,9 @@ export default function createAlertTests({ getService }: FtrProviderContext) { mutedInstanceIds: [], }); expect(typeof response.body.scheduledTaskId).to.be('string'); + expect(Date.parse(response.body.createdAt)).to.be.greaterThan(0); + expect(Date.parse(response.body.updatedAt)).to.be.greaterThan(0); + const { _source: taskRecord } = await getScheduledTask(response.body.scheduledTaskId); expect(taskRecord.type).to.eql('task'); expect(taskRecord.task.taskType).to.eql('alerting:test.noop'); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/find.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/find.ts index 92d8447e8f7d5..d99ab794cd28f 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/find.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/find.ts @@ -66,12 +66,16 @@ export default function createFindTests({ getService }: FtrProviderContext) { params: {}, createdBy: 'elastic', scheduledTaskId: match.scheduledTaskId, + createdAt: match.createdAt, + updatedAt: match.updatedAt, throttle: '1m', updatedBy: 'elastic', apiKeyOwner: 'elastic', muteAll: false, mutedInstanceIds: [], }); + expect(Date.parse(match.createdAt)).to.be.greaterThan(0); + expect(Date.parse(match.updatedAt)).to.be.greaterThan(0); break; default: throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); @@ -157,7 +161,11 @@ export default function createFindTests({ getService }: FtrProviderContext) { apiKeyOwner: 'elastic', muteAll: false, mutedInstanceIds: [], + createdAt: match.createdAt, + updatedAt: match.updatedAt, }); + expect(Date.parse(match.createdAt)).to.be.greaterThan(0); + expect(Date.parse(match.updatedAt)).to.be.greaterThan(0); break; default: throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/get.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/get.ts index eaa361155b61f..20eed4013d7dd 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/get.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/get.ts @@ -60,12 +60,16 @@ export default function createGetTests({ getService }: FtrProviderContext) { params: {}, createdBy: 'elastic', scheduledTaskId: response.body.scheduledTaskId, + updatedAt: response.body.updatedAt, + createdAt: response.body.createdAt, throttle: '1m', updatedBy: 'elastic', apiKeyOwner: 'elastic', muteAll: false, mutedInstanceIds: [], }); + expect(Date.parse(response.body.createdAt)).to.be.greaterThan(0); + expect(Date.parse(response.body.updatedAt)).to.be.greaterThan(0); break; default: throw new Error(`Scenario untested: ${JSON.stringify(scenario)}`); diff --git a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/update.ts b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/update.ts index ec162a75ee0a5..2a7e0b2203824 100644 --- a/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/update.ts +++ b/x-pack/test/alerting_api_integration/security_and_spaces/tests/alerting/update.ts @@ -7,7 +7,13 @@ import expect from '@kbn/expect'; import { Response as SupertestResponse } from 'supertest'; import { UserAtSpaceScenarios } from '../../scenarios'; -import { checkAAD, getUrlPrefix, getTestAlertData, ObjectRemover } from '../../../common/lib'; +import { + checkAAD, + getUrlPrefix, + getTestAlertData, + ObjectRemover, + ensureDatetimeIsWithinRange, +} from '../../../common/lib'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; // eslint-disable-next-line import/no-default-export @@ -81,7 +87,14 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { muteAll: false, mutedInstanceIds: [], scheduledTaskId: createdAlert.scheduledTaskId, + createdAt: response.body.createdAt, + updatedAt: response.body.updatedAt, }); + expect(Date.parse(response.body.createdAt)).to.be.greaterThan(0); + expect(Date.parse(response.body.updatedAt)).to.be.greaterThan(0); + expect(Date.parse(response.body.updatedAt)).to.be.greaterThan( + Date.parse(response.body.createdAt) + ); // Ensure AAD isn't broken await checkAAD({ supertest, @@ -399,10 +412,3 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { } }); } - -function ensureDatetimeIsWithinRange(scheduledRunTime: number, expectedDiff: number) { - const buffer = 10000; - const diff = scheduledRunTime - Date.now(); - expect(diff).to.be.greaterThan(expectedDiff - buffer); - expect(diff).to.be.lessThan(expectedDiff + buffer); -} diff --git a/x-pack/test/alerting_api_integration/spaces_only/scenarios.ts b/x-pack/test/alerting_api_integration/spaces_only/scenarios.ts index 2b91c408d3de9..c2b3ec6148036 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/scenarios.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/scenarios.ts @@ -8,17 +8,27 @@ import { Space } from '../common/types'; const Space1: Space = { id: 'space1', + namespace: 'space1', name: 'Space 1', disabledFeatures: [], }; const Other: Space = { id: 'other', + namespace: 'other', name: 'Other', disabledFeatures: [], }; +const Default: Space = { + id: 'default', + namespace: undefined, + name: 'Default', + disabledFeatures: [], +}; + export const Spaces = { space1: Space1, other: Other, + default: Default, }; diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/alerts.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/alerts.ts deleted file mode 100644 index 03e973194b4e2..0000000000000 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/alerts.ts +++ /dev/null @@ -1,279 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import expect from '@kbn/expect'; -import { Spaces } from '../../scenarios'; -import { FtrProviderContext } from '../../../common/ftr_provider_context'; -import { - ESTestIndexTool, - ES_TEST_INDEX_NAME, - getUrlPrefix, - getTestAlertData, - ObjectRemover, - AlertUtils, -} from '../../../common/lib'; - -// eslint-disable-next-line import/no-default-export -export default function alertTests({ getService }: FtrProviderContext) { - const supertestWithoutAuth = getService('supertestWithoutAuth'); - const es = getService('legacyEs'); - const retry = getService('retry'); - const esTestIndexTool = new ESTestIndexTool(es, retry); - - describe('alerts', () => { - let alertUtils: AlertUtils; - let indexRecordActionId: string; - const authorizationIndex = '.kibana-test-authorization'; - const objectRemover = new ObjectRemover(supertestWithoutAuth); - - before(async () => { - await esTestIndexTool.destroy(); - await esTestIndexTool.setup(); - await es.indices.create({ index: authorizationIndex }); - const { body: createdAction } = await supertestWithoutAuth - .post(`${getUrlPrefix(Spaces.space1.id)}/api/action`) - .set('kbn-xsrf', 'foo') - .send({ - name: 'My action', - actionTypeId: 'test.index-record', - config: { - unencrypted: `This value shouldn't get encrypted`, - }, - secrets: { - encrypted: 'This value should be encrypted', - }, - }) - .expect(200); - indexRecordActionId = createdAction.id; - alertUtils = new AlertUtils({ - space: Spaces.space1, - supertestWithoutAuth, - indexRecordActionId, - objectRemover, - }); - }); - afterEach(() => objectRemover.removeAll()); - after(async () => { - await esTestIndexTool.destroy(); - await es.indices.delete({ index: authorizationIndex }); - objectRemover.add(Spaces.space1.id, indexRecordActionId, 'action'); - await objectRemover.removeAll(); - }); - - it('should schedule task, run alert and schedule actions', async () => { - const reference = alertUtils.generateReference(); - const response = await alertUtils.createAlwaysFiringAction({ reference }); - - expect(response.statusCode).to.eql(200); - const alertTestRecord = ( - await esTestIndexTool.waitForDocs('alert:test.always-firing', reference) - )[0]; - expect(alertTestRecord._source).to.eql({ - source: 'alert:test.always-firing', - reference, - state: {}, - params: { - index: ES_TEST_INDEX_NAME, - reference, - }, - }); - const actionTestRecord = ( - await esTestIndexTool.waitForDocs('action:test.index-record', reference) - )[0]; - expect(actionTestRecord._source).to.eql({ - config: { - unencrypted: `This value shouldn't get encrypted`, - }, - secrets: { - encrypted: 'This value should be encrypted', - }, - params: { - index: ES_TEST_INDEX_NAME, - reference, - message: 'instanceContextValue: true, instanceStateValue: true', - }, - reference, - source: 'action:test.index-record', - }); - }); - - it('should handle custom retry logic', async () => { - // We'll use this start time to query tasks created after this point - const testStart = new Date(); - // We have to provide the test.rate-limit the next runAt, for testing purposes - const retryDate = new Date(Date.now() + 60000); - - const { body: createdAction } = await supertestWithoutAuth - .post(`${getUrlPrefix(Spaces.space1.id)}/api/action`) - .set('kbn-xsrf', 'foo') - .send({ - name: 'Test rate limit', - actionTypeId: 'test.rate-limit', - config: {}, - }) - .expect(200); - objectRemover.add(Spaces.space1.id, createdAction.id, 'action'); - - const reference = alertUtils.generateReference(); - const response = await supertestWithoutAuth - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alert`) - .set('kbn-xsrf', 'foo') - .send( - getTestAlertData({ - schedule: { interval: '1m' }, - alertTypeId: 'test.always-firing', - params: { - index: ES_TEST_INDEX_NAME, - reference: 'create-test-2', - }, - actions: [ - { - group: 'default', - id: createdAction.id, - params: { - reference, - index: ES_TEST_INDEX_NAME, - retryAt: retryDate.getTime(), - }, - }, - ], - }) - ); - - expect(response.statusCode).to.eql(200); - objectRemover.add(Spaces.space1.id, response.body.id, 'alert'); - const scheduledActionTask = await retry.try(async () => { - const searchResult = await es.search({ - index: '.kibana_task_manager', - body: { - query: { - bool: { - must: [ - { - term: { - 'task.status': 'idle', - }, - }, - { - term: { - 'task.attempts': 1, - }, - }, - { - term: { - 'task.taskType': 'actions:test.rate-limit', - }, - }, - { - range: { - 'task.scheduledAt': { - gte: testStart, - }, - }, - }, - ], - }, - }, - }, - }); - expect(searchResult.hits.total.value).to.eql(1); - return searchResult.hits.hits[0]; - }); - expect(scheduledActionTask._source.task.runAt).to.eql(retryDate.toISOString()); - }); - - it('should have proper callCluster and savedObjectsClient authorization for alert type executor', async () => { - const reference = alertUtils.generateReference(); - const response = await supertestWithoutAuth - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alert`) - .set('kbn-xsrf', 'foo') - .send( - getTestAlertData({ - alertTypeId: 'test.authorization', - params: { - callClusterAuthorizationIndex: authorizationIndex, - savedObjectsClientType: 'dashboard', - savedObjectsClientId: '1', - index: ES_TEST_INDEX_NAME, - reference, - }, - }) - ); - - expect(response.statusCode).to.eql(200); - objectRemover.add(Spaces.space1.id, response.body.id, 'alert'); - const alertTestRecord = ( - await esTestIndexTool.waitForDocs('alert:test.authorization', reference) - )[0]; - expect(alertTestRecord._source.state).to.eql({ - callClusterSuccess: true, - savedObjectsClientSuccess: false, - savedObjectsClientError: { - ...alertTestRecord._source.state.savedObjectsClientError, - output: { - ...alertTestRecord._source.state.savedObjectsClientError.output, - statusCode: 404, - }, - }, - }); - }); - - it('should have proper callCluster and savedObjectsClient authorization for action type executor', async () => { - const reference = alertUtils.generateReference(); - const { body: createdAction } = await supertestWithoutAuth - .post(`${getUrlPrefix(Spaces.space1.id)}/api/action`) - .set('kbn-xsrf', 'foo') - .send({ - name: 'My action', - actionTypeId: 'test.authorization', - }) - .expect(200); - objectRemover.add(Spaces.space1.id, createdAction.id, 'action'); - const response = await supertestWithoutAuth - .post(`${getUrlPrefix(Spaces.space1.id)}/api/alert`) - .set('kbn-xsrf', 'foo') - .send( - getTestAlertData({ - alertTypeId: 'test.always-firing', - params: { - index: ES_TEST_INDEX_NAME, - reference, - }, - actions: [ - { - group: 'default', - id: createdAction.id, - params: { - callClusterAuthorizationIndex: authorizationIndex, - savedObjectsClientType: 'dashboard', - savedObjectsClientId: '1', - index: ES_TEST_INDEX_NAME, - reference, - }, - }, - ], - }) - ); - - expect(response.statusCode).to.eql(200); - objectRemover.add(Spaces.space1.id, response.body.id, 'alert'); - const actionTestRecord = ( - await esTestIndexTool.waitForDocs('action:test.authorization', reference) - )[0]; - expect(actionTestRecord._source.state).to.eql({ - callClusterSuccess: true, - savedObjectsClientSuccess: false, - savedObjectsClientError: { - ...actionTestRecord._source.state.savedObjectsClientError, - output: { - ...actionTestRecord._source.state.savedObjectsClientError.output, - statusCode: 404, - }, - }, - }); - }); - }); -} diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/alerts_base.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/alerts_base.ts new file mode 100644 index 0000000000000..d9a58851afb31 --- /dev/null +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/alerts_base.ts @@ -0,0 +1,333 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; +import { Response as SupertestResponse } from 'supertest'; +import { Space } from '../../../common/types'; +import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { + ESTestIndexTool, + ES_TEST_INDEX_NAME, + getUrlPrefix, + getTestAlertData, + ObjectRemover, + AlertUtils, + ensureDatetimeIsWithinRange, +} from '../../../common/lib'; + +// eslint-disable-next-line import/no-default-export +export function alertTests({ getService }: FtrProviderContext, space: Space) { + const supertestWithoutAuth = getService('supertestWithoutAuth'); + const es = getService('legacyEs'); + const retry = getService('retry'); + const esTestIndexTool = new ESTestIndexTool(es, retry); + + function getAlertingTaskById(taskId: string) { + return supertestWithoutAuth + .get(`/api/alerting_tasks/${taskId}`) + .expect(200) + .then((response: SupertestResponse) => response.body); + } + + describe('alerts', () => { + let alertUtils: AlertUtils; + let indexRecordActionId: string; + const authorizationIndex = '.kibana-test-authorization'; + const objectRemover = new ObjectRemover(supertestWithoutAuth); + + before(async () => { + await esTestIndexTool.destroy(); + await esTestIndexTool.setup(); + await es.indices.create({ index: authorizationIndex }); + const { body: createdAction } = await supertestWithoutAuth + .post(`${getUrlPrefix(space.id)}/api/action`) + .set('kbn-xsrf', 'foo') + .send({ + name: 'My action', + actionTypeId: 'test.index-record', + config: { + unencrypted: `This value shouldn't get encrypted`, + }, + secrets: { + encrypted: 'This value should be encrypted', + }, + }) + .expect(200); + indexRecordActionId = createdAction.id; + alertUtils = new AlertUtils({ + space, + supertestWithoutAuth, + indexRecordActionId, + objectRemover, + }); + }); + afterEach(() => objectRemover.removeAll()); + after(async () => { + await esTestIndexTool.destroy(); + await es.indices.delete({ index: authorizationIndex }); + objectRemover.add(space.id, indexRecordActionId, 'action'); + await objectRemover.removeAll(); + }); + + it('should schedule task, run alert and schedule actions', async () => { + const reference = alertUtils.generateReference(); + const response = await alertUtils.createAlwaysFiringAction({ reference }); + const alertId = response.body.id; + + expect(response.statusCode).to.eql(200); + const alertTestRecord = ( + await esTestIndexTool.waitForDocs('alert:test.always-firing', reference) + )[0]; + const expected = { + source: 'alert:test.always-firing', + reference, + state: {}, + params: { + index: ES_TEST_INDEX_NAME, + reference, + }, + alertInfo: { + alertId, + spaceId: space.id, + namespace: space.namespace, + name: 'abc', + tags: [], + createdBy: null, + updatedBy: null, + }, + }; + if (expected.alertInfo.namespace === undefined) { + delete expected.alertInfo.namespace; + } + expect(alertTestRecord._source).to.eql(expected); + const actionTestRecord = ( + await esTestIndexTool.waitForDocs('action:test.index-record', reference) + )[0]; + expect(actionTestRecord._source).to.eql({ + config: { + unencrypted: `This value shouldn't get encrypted`, + }, + secrets: { + encrypted: 'This value should be encrypted', + }, + params: { + index: ES_TEST_INDEX_NAME, + reference, + message: 'instanceContextValue: true, instanceStateValue: true', + }, + reference, + source: 'action:test.index-record', + }); + }); + + it('should reschedule failing alerts using the alerting interval and not the Task Manager retry logic', async () => { + /* + Alerting does not use the Task Manager schedule and instead implements its own due to a current limitation + in TaskManager's ability to update an existing Task. + For this reason we need to handle the retry when Alert executors fail, as TaskManager doesn't understand that + alerting tasks are recurring tasks. + */ + const alertIntervalInSeconds = 30; + const reference = alertUtils.generateReference(); + const response = await alertUtils.createAlwaysFailingAction({ + reference, + overwrites: { schedule: { interval: `${alertIntervalInSeconds}s` } }, + }); + + expect(response.statusCode).to.eql(200); + + // wait for executor Alert Executor to be run, which means the underlying task is running + await esTestIndexTool.waitForDocs('alert:test.failing', reference); + + await retry.try(async () => { + const alertTask = (await getAlertingTaskById(response.body.scheduledTaskId)).docs[0]; + expect(alertTask.status).to.eql('idle'); + // ensure the alert is rescheduled to a minute from now + ensureDatetimeIsWithinRange( + Date.parse(alertTask.runAt), + alertIntervalInSeconds * 1000, + 5000 + ); + }); + }); + + it('should handle custom retry logic', async () => { + // We'll use this start time to query tasks created after this point + const testStart = new Date(); + // We have to provide the test.rate-limit the next runAt, for testing purposes + const retryDate = new Date(Date.now() + 60000); + + const { body: createdAction } = await supertestWithoutAuth + .post(`${getUrlPrefix(space.id)}/api/action`) + .set('kbn-xsrf', 'foo') + .send({ + name: 'Test rate limit', + actionTypeId: 'test.rate-limit', + config: {}, + }) + .expect(200); + objectRemover.add(space.id, createdAction.id, 'action'); + + const reference = alertUtils.generateReference(); + const response = await supertestWithoutAuth + .post(`${getUrlPrefix(space.id)}/api/alert`) + .set('kbn-xsrf', 'foo') + .send( + getTestAlertData({ + schedule: { interval: '1m' }, + alertTypeId: 'test.always-firing', + params: { + index: ES_TEST_INDEX_NAME, + reference: 'create-test-2', + }, + actions: [ + { + group: 'default', + id: createdAction.id, + params: { + reference, + index: ES_TEST_INDEX_NAME, + retryAt: retryDate.getTime(), + }, + }, + ], + }) + ); + + expect(response.statusCode).to.eql(200); + objectRemover.add(space.id, response.body.id, 'alert'); + const scheduledActionTask = await retry.try(async () => { + const searchResult = await es.search({ + index: '.kibana_task_manager', + body: { + query: { + bool: { + must: [ + { + term: { + 'task.status': 'idle', + }, + }, + { + term: { + 'task.attempts': 1, + }, + }, + { + term: { + 'task.taskType': 'actions:test.rate-limit', + }, + }, + { + range: { + 'task.scheduledAt': { + gte: testStart, + }, + }, + }, + ], + }, + }, + }, + }); + expect(searchResult.hits.total.value).to.eql(1); + return searchResult.hits.hits[0]; + }); + expect(scheduledActionTask._source.task.runAt).to.eql(retryDate.toISOString()); + }); + + it('should have proper callCluster and savedObjectsClient authorization for alert type executor', async () => { + const reference = alertUtils.generateReference(); + const response = await supertestWithoutAuth + .post(`${getUrlPrefix(space.id)}/api/alert`) + .set('kbn-xsrf', 'foo') + .send( + getTestAlertData({ + alertTypeId: 'test.authorization', + params: { + callClusterAuthorizationIndex: authorizationIndex, + savedObjectsClientType: 'dashboard', + savedObjectsClientId: '1', + index: ES_TEST_INDEX_NAME, + reference, + }, + }) + ); + + expect(response.statusCode).to.eql(200); + objectRemover.add(space.id, response.body.id, 'alert'); + const alertTestRecord = ( + await esTestIndexTool.waitForDocs('alert:test.authorization', reference) + )[0]; + expect(alertTestRecord._source.state).to.eql({ + callClusterSuccess: true, + savedObjectsClientSuccess: false, + savedObjectsClientError: { + ...alertTestRecord._source.state.savedObjectsClientError, + output: { + ...alertTestRecord._source.state.savedObjectsClientError.output, + statusCode: 404, + }, + }, + }); + }); + + it('should have proper callCluster and savedObjectsClient authorization for action type executor', async () => { + const reference = alertUtils.generateReference(); + const { body: createdAction } = await supertestWithoutAuth + .post(`${getUrlPrefix(space.id)}/api/action`) + .set('kbn-xsrf', 'foo') + .send({ + name: 'My action', + actionTypeId: 'test.authorization', + }) + .expect(200); + objectRemover.add(space.id, createdAction.id, 'action'); + const response = await supertestWithoutAuth + .post(`${getUrlPrefix(space.id)}/api/alert`) + .set('kbn-xsrf', 'foo') + .send( + getTestAlertData({ + alertTypeId: 'test.always-firing', + params: { + index: ES_TEST_INDEX_NAME, + reference, + }, + actions: [ + { + group: 'default', + id: createdAction.id, + params: { + callClusterAuthorizationIndex: authorizationIndex, + savedObjectsClientType: 'dashboard', + savedObjectsClientId: '1', + index: ES_TEST_INDEX_NAME, + reference, + }, + }, + ], + }) + ); + + expect(response.statusCode).to.eql(200); + objectRemover.add(space.id, response.body.id, 'alert'); + const actionTestRecord = ( + await esTestIndexTool.waitForDocs('action:test.authorization', reference) + )[0]; + expect(actionTestRecord._source.state).to.eql({ + callClusterSuccess: true, + savedObjectsClientSuccess: false, + savedObjectsClientError: { + ...actionTestRecord._source.state.savedObjectsClientError, + output: { + ...actionTestRecord._source.state.savedObjectsClientError.output, + statusCode: 404, + }, + }, + }); + }); + }); +} diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/alerts_default_space.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/alerts_default_space.ts new file mode 100644 index 0000000000000..3e677952d8700 --- /dev/null +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/alerts_default_space.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { Spaces } from '../../scenarios'; +import { alertTests } from './alerts_base'; + +// eslint-disable-next-line import/no-default-export +export default function alertSpace1Tests(context: FtrProviderContext) { + alertTests(context, Spaces.default); +} diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/alerts_space1.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/alerts_space1.ts new file mode 100644 index 0000000000000..07ad4cd294ab3 --- /dev/null +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/alerts_space1.ts @@ -0,0 +1,14 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { FtrProviderContext } from '../../../common/ftr_provider_context'; +import { Spaces } from '../../scenarios'; +import { alertTests } from './alerts_base'; + +// eslint-disable-next-line import/no-default-export +export default function alertSpace1Tests(context: FtrProviderContext) { + alertTests(context, Spaces.space1); +} diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts index d64065b596498..50e01c65b6a86 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/create.ts @@ -79,7 +79,12 @@ export default function createAlertTests({ getService }: FtrProviderContext) { throttle: '1m', muteAll: false, mutedInstanceIds: [], + createdAt: response.body.createdAt, + updatedAt: response.body.updatedAt, }); + expect(Date.parse(response.body.createdAt)).to.be.greaterThan(0); + expect(Date.parse(response.body.updatedAt)).to.be.greaterThan(0); + expect(typeof response.body.scheduledTaskId).to.be('string'); const { _source: taskRecord } = await getScheduledTask(response.body.scheduledTaskId); expect(taskRecord.type).to.eql('task'); diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/find.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/find.ts index 1ee814aace797..70935a462d03e 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/find.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/find.ts @@ -54,7 +54,11 @@ export default function createFindTests({ getService }: FtrProviderContext) { throttle: '1m', muteAll: false, mutedInstanceIds: [], + createdAt: match.createdAt, + updatedAt: match.updatedAt, }); + expect(Date.parse(match.createdAt)).to.be.greaterThan(0); + expect(Date.parse(match.updatedAt)).to.be.greaterThan(0); }); it(`shouldn't find alert from another space`, async () => { diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get.ts index 328b0a01d5cbd..30b5e43aee585 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/get.ts @@ -48,7 +48,11 @@ export default function createGetTests({ getService }: FtrProviderContext) { throttle: '1m', muteAll: false, mutedInstanceIds: [], + createdAt: response.body.createdAt, + updatedAt: response.body.updatedAt, }); + expect(Date.parse(response.body.createdAt)).to.be.greaterThan(0); + expect(Date.parse(response.body.updatedAt)).to.be.greaterThan(0); }); it(`shouldn't find alert from another space`, async () => { diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/index.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/index.ts index 1aa084356cfa4..569c0d538d473 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/index.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/index.ts @@ -22,6 +22,7 @@ export default function alertingTests({ loadTestFile }: FtrProviderContext) { loadTestFile(require.resolve('./unmute_instance')); loadTestFile(require.resolve('./update')); loadTestFile(require.resolve('./update_api_key')); - loadTestFile(require.resolve('./alerts')); + loadTestFile(require.resolve('./alerts_space1')); + loadTestFile(require.resolve('./alerts_default_space')); }); } diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/update.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/update.ts index ecc614ad3807b..5a35d4bf83865 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/update.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/alerting/update.ts @@ -4,6 +4,7 @@ * you may not use this file except in compliance with the Elastic License. */ +import expect from '@kbn/expect/expect.js'; import { Spaces } from '../../scenarios'; import { checkAAD, getUrlPrefix, getTestAlertData, ObjectRemover } from '../../../common/lib'; import { FtrProviderContext } from '../../../common/ftr_provider_context'; @@ -35,24 +36,33 @@ export default function createUpdateTests({ getService }: FtrProviderContext) { actions: [], throttle: '1m', }; - await supertest + const response = await supertest .put(`${getUrlPrefix(Spaces.space1.id)}/api/alert/${createdAlert.id}`) .set('kbn-xsrf', 'foo') .send(updatedData) - .expect(200, { - ...updatedData, - id: createdAlert.id, - tags: ['bar'], - alertTypeId: 'test.noop', - consumer: 'bar', - createdBy: null, - enabled: true, - updatedBy: null, - apiKeyOwner: null, - muteAll: false, - mutedInstanceIds: [], - scheduledTaskId: createdAlert.scheduledTaskId, - }); + .expect(200); + + expect(response.body).to.eql({ + ...updatedData, + id: createdAlert.id, + tags: ['bar'], + alertTypeId: 'test.noop', + consumer: 'bar', + createdBy: null, + enabled: true, + updatedBy: null, + apiKeyOwner: null, + muteAll: false, + mutedInstanceIds: [], + scheduledTaskId: createdAlert.scheduledTaskId, + createdAt: response.body.createdAt, + updatedAt: response.body.updatedAt, + }); + expect(Date.parse(response.body.createdAt)).to.be.greaterThan(0); + expect(Date.parse(response.body.updatedAt)).to.be.greaterThan(0); + expect(Date.parse(response.body.updatedAt)).to.be.greaterThan( + Date.parse(response.body.createdAt) + ); // Ensure AAD isn't broken await checkAAD({ diff --git a/x-pack/test/alerting_api_integration/spaces_only/tests/index.ts b/x-pack/test/alerting_api_integration/spaces_only/tests/index.ts index f18aebaf4e689..cb2b17980d37a 100644 --- a/x-pack/test/alerting_api_integration/spaces_only/tests/index.ts +++ b/x-pack/test/alerting_api_integration/spaces_only/tests/index.ts @@ -20,7 +20,10 @@ export default function alertingApiIntegrationTests({ before(async () => { for (const space of Object.values(Spaces)) { - await spacesService.create(space); + if (space.id === 'default') continue; + + const { id, name, disabledFeatures } = space; + await spacesService.create({ id, name, disabledFeatures }); } }); diff --git a/x-pack/test/api_integration/apis/endpoint/endpoints.ts b/x-pack/test/api_integration/apis/endpoint/endpoints.ts new file mode 100644 index 0000000000000..32864489d3786 --- /dev/null +++ b/x-pack/test/api_integration/apis/endpoint/endpoints.ts @@ -0,0 +1,107 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ +import expect from '@kbn/expect/expect.js'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function({ getService }: FtrProviderContext) { + const esArchiver = getService('esArchiver'); + const supertest = getService('supertest'); + describe('test endpoints api', () => { + describe('POST /api/endpoint/endpoints when index is empty', () => { + it('endpoints api should return empty result when index is empty', async () => { + await esArchiver.unload('endpoint/endpoints'); + const { body } = await supertest + .post('/api/endpoint/endpoints') + .set('kbn-xsrf', 'xxx') + .send() + .expect(200); + expect(body.total).to.eql(0); + expect(body.endpoints.length).to.eql(0); + expect(body.request_page_size).to.eql(10); + expect(body.request_index).to.eql(0); + }); + }); + + describe('POST /api/endpoint/endpoints when index is not empty', () => { + before(() => esArchiver.load('endpoint/endpoints')); + after(() => esArchiver.unload('endpoint/endpoints')); + it('endpoints api should return one entry for each endpoint with default paging', async () => { + const { body } = await supertest + .post('/api/endpoint/endpoints') + .set('kbn-xsrf', 'xxx') + .send() + .expect(200); + expect(body.total).to.eql(3); + expect(body.endpoints.length).to.eql(3); + expect(body.request_page_size).to.eql(10); + expect(body.request_index).to.eql(0); + }); + + it('endpoints api should return page based on params passed.', async () => { + const { body } = await supertest + .post('/api/endpoint/endpoints') + .set('kbn-xsrf', 'xxx') + .send({ + paging_properties: [ + { + page_size: 1, + }, + { + page_index: 1, + }, + ], + }) + .expect(200); + expect(body.total).to.eql(3); + expect(body.endpoints.length).to.eql(1); + expect(body.request_page_size).to.eql(1); + expect(body.request_index).to.eql(1); + }); + + /* test that when paging properties produces no result, the total should reflect the actual number of endpoints + in the index. + */ + it('endpoints api should return accurate total endpoints if page index produces no result', async () => { + const { body } = await supertest + .post('/api/endpoint/endpoints') + .set('kbn-xsrf', 'xxx') + .send({ + paging_properties: [ + { + page_size: 10, + }, + { + page_index: 3, + }, + ], + }) + .expect(200); + expect(body.total).to.eql(3); + expect(body.endpoints.length).to.eql(0); + expect(body.request_page_size).to.eql(10); + expect(body.request_index).to.eql(30); + }); + + it('endpoints api should return 400 when pagingProperties is below boundaries.', async () => { + const { body } = await supertest + .post('/api/endpoint/endpoints') + .set('kbn-xsrf', 'xxx') + .send({ + paging_properties: [ + { + page_size: 0, + }, + { + page_index: 1, + }, + ], + }) + .expect(400); + expect(body.message).to.contain('Value is [0] but it must be equal to or greater than [1]'); + }); + }); + }); +} diff --git a/x-pack/test/api_integration/apis/endpoint/index.ts b/x-pack/test/api_integration/apis/endpoint/index.ts index e0ffbb13e5978..a3f0e828d7240 100644 --- a/x-pack/test/api_integration/apis/endpoint/index.ts +++ b/x-pack/test/api_integration/apis/endpoint/index.ts @@ -9,5 +9,6 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function endpointAPIIntegrationTests({ loadTestFile }: FtrProviderContext) { describe('Endpoint plugin', function() { loadTestFile(require.resolve('./resolver')); + loadTestFile(require.resolve('./endpoints')); }); } diff --git a/x-pack/test/api_integration/apis/infra/log_entries.ts b/x-pack/test/api_integration/apis/infra/log_entries.ts index 4020acc00c618..8db1426a219d4 100644 --- a/x-pack/test/api_integration/apis/infra/log_entries.ts +++ b/x-pack/test/api_integration/apis/infra/log_entries.ts @@ -9,6 +9,21 @@ import { ascending, pairs } from 'd3-array'; import gql from 'graphql-tag'; import { v4 as uuidv4 } from 'uuid'; +import { pipe } from 'fp-ts/lib/pipeable'; +import { identity } from 'fp-ts/lib/function'; +import { fold } from 'fp-ts/lib/Either'; + +import { + createPlainError, + throwErrors, +} from '../../../../legacy/plugins/infra/common/runtime_types'; + +import { + LOG_ENTRIES_PATH, + logEntriesRequestRT, + logEntriesResponseRT, +} from '../../../../legacy/plugins/infra/common/http_api'; + import { sharedFragments } from '../../../../legacy/plugins/infra/common/graphql/shared'; import { InfraTimeKey } from '../../../../legacy/plugins/infra/public/graphql/types'; import { FtrProviderContext } from '../../ftr_provider_context'; @@ -88,15 +103,209 @@ const logEntriesBetweenQuery = gql` ${sharedFragments.InfraLogEntryFields} `; +const COMMON_HEADERS = { + 'kbn-xsrf': 'some-xsrf-token', +}; + export default function({ getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); const client = getService('infraOpsGraphQLClient'); + const supertest = getService('supertest'); const sourceConfigurationService = getService('infraOpsSourceConfiguration'); describe('log entry apis', () => { before(() => esArchiver.load('infra/metrics_and_logs')); after(() => esArchiver.unload('infra/metrics_and_logs')); + describe('/log_entries/entries', () => { + describe('with the default source', () => { + before(() => esArchiver.load('empty_kibana')); + after(() => esArchiver.unload('empty_kibana')); + + it('works', async () => { + const { body } = await supertest + .post(LOG_ENTRIES_PATH) + .set(COMMON_HEADERS) + .send( + logEntriesRequestRT.encode({ + sourceId: 'default', + startDate: EARLIEST_KEY_WITH_DATA.time, + endDate: KEY_WITHIN_DATA_RANGE.time, + }) + ) + .expect(200); + + const logEntriesResponse = pipe( + logEntriesResponseRT.decode(body), + fold(throwErrors(createPlainError), identity) + ); + + const entries = logEntriesResponse.data.entries; + const firstEntry = entries[0]; + const lastEntry = entries[entries.length - 1]; + + // Has the default page size + expect(entries).to.have.length(200); + + // Cursors are set correctly + expect(firstEntry.cursor).to.eql(logEntriesResponse.data.topCursor); + expect(lastEntry.cursor).to.eql(logEntriesResponse.data.bottomCursor); + + // Entries fall within range + // @kbn/expect doesn't have a `lessOrEqualThan` or `moreOrEqualThan` comparators + expect(firstEntry.cursor.time >= EARLIEST_KEY_WITH_DATA.time).to.be(true); + expect(lastEntry.cursor.time <= KEY_WITHIN_DATA_RANGE.time).to.be(true); + }); + + it('Paginates correctly with `after`', async () => { + const { body: firstPageBody } = await supertest + .post(LOG_ENTRIES_PATH) + .set(COMMON_HEADERS) + .send( + logEntriesRequestRT.encode({ + sourceId: 'default', + startDate: EARLIEST_KEY_WITH_DATA.time, + endDate: KEY_WITHIN_DATA_RANGE.time, + size: 10, + }) + ); + const firstPage = pipe( + logEntriesResponseRT.decode(firstPageBody), + fold(throwErrors(createPlainError), identity) + ); + + const { body: secondPageBody } = await supertest + .post(LOG_ENTRIES_PATH) + .set(COMMON_HEADERS) + .send( + logEntriesRequestRT.encode({ + sourceId: 'default', + startDate: EARLIEST_KEY_WITH_DATA.time, + endDate: KEY_WITHIN_DATA_RANGE.time, + after: firstPage.data.bottomCursor, + size: 10, + }) + ); + const secondPage = pipe( + logEntriesResponseRT.decode(secondPageBody), + fold(throwErrors(createPlainError), identity) + ); + + const { body: bothPagesBody } = await supertest + .post(LOG_ENTRIES_PATH) + .set(COMMON_HEADERS) + .send( + logEntriesRequestRT.encode({ + sourceId: 'default', + startDate: EARLIEST_KEY_WITH_DATA.time, + endDate: KEY_WITHIN_DATA_RANGE.time, + size: 20, + }) + ); + const bothPages = pipe( + logEntriesResponseRT.decode(bothPagesBody), + fold(throwErrors(createPlainError), identity) + ); + + expect(bothPages.data.entries).to.eql([ + ...firstPage.data.entries, + ...secondPage.data.entries, + ]); + + expect(bothPages.data.topCursor).to.eql(firstPage.data.topCursor); + expect(bothPages.data.bottomCursor).to.eql(secondPage.data.bottomCursor); + }); + + it('Paginates correctly with `before`', async () => { + const { body: lastPageBody } = await supertest + .post(LOG_ENTRIES_PATH) + .set(COMMON_HEADERS) + .send( + logEntriesRequestRT.encode({ + sourceId: 'default', + startDate: KEY_WITHIN_DATA_RANGE.time, + endDate: LATEST_KEY_WITH_DATA.time, + before: 'last', + size: 10, + }) + ); + const lastPage = pipe( + logEntriesResponseRT.decode(lastPageBody), + fold(throwErrors(createPlainError), identity) + ); + + const { body: secondToLastPageBody } = await supertest + .post(LOG_ENTRIES_PATH) + .set(COMMON_HEADERS) + .send( + logEntriesRequestRT.encode({ + sourceId: 'default', + startDate: KEY_WITHIN_DATA_RANGE.time, + endDate: LATEST_KEY_WITH_DATA.time, + before: lastPage.data.topCursor, + size: 10, + }) + ); + const secondToLastPage = pipe( + logEntriesResponseRT.decode(secondToLastPageBody), + fold(throwErrors(createPlainError), identity) + ); + + const { body: bothPagesBody } = await supertest + .post(LOG_ENTRIES_PATH) + .set(COMMON_HEADERS) + .send( + logEntriesRequestRT.encode({ + sourceId: 'default', + startDate: KEY_WITHIN_DATA_RANGE.time, + endDate: LATEST_KEY_WITH_DATA.time, + before: 'last', + size: 20, + }) + ); + const bothPages = pipe( + logEntriesResponseRT.decode(bothPagesBody), + fold(throwErrors(createPlainError), identity) + ); + + expect(bothPages.data.entries).to.eql([ + ...secondToLastPage.data.entries, + ...lastPage.data.entries, + ]); + + expect(bothPages.data.topCursor).to.eql(secondToLastPage.data.topCursor); + expect(bothPages.data.bottomCursor).to.eql(lastPage.data.bottomCursor); + }); + + it('centers entries around a point', async () => { + const { body } = await supertest + .post(LOG_ENTRIES_PATH) + .set(COMMON_HEADERS) + .send( + logEntriesRequestRT.encode({ + sourceId: 'default', + startDate: EARLIEST_KEY_WITH_DATA.time, + endDate: LATEST_KEY_WITH_DATA.time, + center: KEY_WITHIN_DATA_RANGE, + }) + ) + .expect(200); + const logEntriesResponse = pipe( + logEntriesResponseRT.decode(body), + fold(throwErrors(createPlainError), identity) + ); + + const entries = logEntriesResponse.data.entries; + const firstEntry = entries[0]; + const lastEntry = entries[entries.length - 1]; + + expect(entries).to.have.length(200); + expect(firstEntry.cursor.time >= EARLIEST_KEY_WITH_DATA.time).to.be(true); + expect(lastEntry.cursor.time <= LATEST_KEY_WITH_DATA.time).to.be(true); + }); + }); + }); + describe('logEntriesAround', () => { describe('with the default source', () => { before(() => esArchiver.load('empty_kibana')); diff --git a/x-pack/test/api_integration/apis/infra/log_entry_highlights.ts b/x-pack/test/api_integration/apis/infra/log_entry_highlights.ts index 88d7d8716d0f5..66701de2704a6 100644 --- a/x-pack/test/api_integration/apis/infra/log_entry_highlights.ts +++ b/x-pack/test/api_integration/apis/infra/log_entry_highlights.ts @@ -8,6 +8,21 @@ import expect from '@kbn/expect'; import { ascending, pairs } from 'd3-array'; import gql from 'graphql-tag'; +import { pipe } from 'fp-ts/lib/pipeable'; +import { identity } from 'fp-ts/lib/function'; +import { fold } from 'fp-ts/lib/Either'; + +import { + createPlainError, + throwErrors, +} from '../../../../legacy/plugins/infra/common/runtime_types'; + +import { + LOG_ENTRIES_HIGHLIGHTS_PATH, + logEntriesHighlightsRequestRT, + logEntriesHighlightsResponseRT, +} from '../../../../legacy/plugins/infra/common/http_api'; + import { FtrProviderContext } from '../../ftr_provider_context'; import { sharedFragments } from '../../../../legacy/plugins/infra/common/graphql/shared'; import { InfraTimeKey } from '../../../../legacy/plugins/infra/public/graphql/types'; @@ -29,14 +44,151 @@ const KEY_AFTER_END = { tiebreaker: 0, }; +const COMMON_HEADERS = { + 'kbn-xsrf': 'some-xsrf-token', +}; + export default function({ getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); + const supertest = getService('supertest'); const client = getService('infraOpsGraphQLClient'); describe('log highlight apis', () => { before(() => esArchiver.load('infra/simple_logs')); after(() => esArchiver.unload('infra/simple_logs')); + describe('/log_entries/highlights', () => { + describe('with the default source', () => { + before(() => esArchiver.load('empty_kibana')); + after(() => esArchiver.unload('empty_kibana')); + + it('highlights built-in message column', async () => { + const { body } = await supertest + .post(LOG_ENTRIES_HIGHLIGHTS_PATH) + .set(COMMON_HEADERS) + .send( + logEntriesHighlightsRequestRT.encode({ + sourceId: 'default', + startDate: KEY_BEFORE_START.time, + endDate: KEY_AFTER_END.time, + highlightTerms: ['message of document 0'], + }) + ) + .expect(200); + + const logEntriesHighlightsResponse = pipe( + logEntriesHighlightsResponseRT.decode(body), + fold(throwErrors(createPlainError), identity) + ); + + expect(logEntriesHighlightsResponse.data).to.have.length(1); + + const data = logEntriesHighlightsResponse.data[0]; + const entries = data.entries; + const firstEntry = entries[0]; + const lastEntry = entries[entries.length - 1]; + + // Finds expected entries + expect(entries).to.have.length(10); + + // Cursors are set correctly + expect(firstEntry.cursor).to.eql(data.topCursor); + expect(lastEntry.cursor).to.eql(data.bottomCursor); + + // Entries fall within range + // @kbn/expect doesn't have a `lessOrEqualThan` or `moreOrEqualThan` comparators + expect(firstEntry.cursor.time >= KEY_BEFORE_START.time).to.be(true); + expect(lastEntry.cursor.time <= KEY_AFTER_END.time).to.be(true); + + // All entries contain the highlights + entries.forEach(entry => { + entry.columns.forEach(column => { + if ('message' in column && 'highlights' in column.message[0]) { + expect(column.message[0].highlights).to.eql(['message', 'of', 'document', '0']); + } + }); + }); + }); + + // Skipped since it behaves differently in master and in the 7.X branch + // See https://github.com/elastic/kibana/issues/49959 + it.skip('highlights field columns', async () => { + const { body } = await supertest + .post(LOG_ENTRIES_HIGHLIGHTS_PATH) + .set(COMMON_HEADERS) + .send( + logEntriesHighlightsRequestRT.encode({ + sourceId: 'default', + startDate: KEY_BEFORE_START.time, + endDate: KEY_AFTER_END.time, + highlightTerms: ['generate_test_data/simple_logs'], + }) + ) + .expect(200); + + const logEntriesHighlightsResponse = pipe( + logEntriesHighlightsResponseRT.decode(body), + fold(throwErrors(createPlainError), identity) + ); + + expect(logEntriesHighlightsResponse.data).to.have.length(1); + + const entries = logEntriesHighlightsResponse.data[0].entries; + + // Finds expected entries + expect(entries).to.have.length(50); + + // All entries contain the highlights + entries.forEach(entry => { + entry.columns.forEach(column => { + if ('field' in column && 'highlights' in column && column.highlights.length > 0) { + expect(column.highlights).to.eql(['generate_test_data/simple_logs']); + } + }); + }); + }); + + it('applies the query as well as the highlight', async () => { + const { body } = await supertest + .post(LOG_ENTRIES_HIGHLIGHTS_PATH) + .set(COMMON_HEADERS) + .send( + logEntriesHighlightsRequestRT.encode({ + sourceId: 'default', + startDate: KEY_BEFORE_START.time, + endDate: KEY_AFTER_END.time, + query: JSON.stringify({ + multi_match: { query: 'host-a', type: 'phrase', lenient: true }, + }), + highlightTerms: ['message'], + }) + ) + .expect(200); + + const logEntriesHighlightsResponse = pipe( + logEntriesHighlightsResponseRT.decode(body), + fold(throwErrors(createPlainError), identity) + ); + + expect(logEntriesHighlightsResponse.data).to.have.length(1); + + const entries = logEntriesHighlightsResponse.data[0].entries; + + // Finds expected entries + expect(entries).to.have.length(25); + + // All entries contain the highlights + entries.forEach(entry => { + entry.columns.forEach(column => { + if ('message' in column && 'highlights' in column.message[0]) { + expect(column.message[0].highlights).to.eql(['message', 'message']); + } + }); + }); + }); + }); + }); + describe('logEntryHighlights', () => { describe('with the default source', () => { before(() => esArchiver.load('empty_kibana')); diff --git a/x-pack/test/api_integration/apis/monitoring/elasticsearch/fixtures/index_detail.json b/x-pack/test/api_integration/apis/monitoring/elasticsearch/fixtures/index_detail.json index 04d56d5949d2c..65094144d6ff0 100644 --- a/x-pack/test/api_integration/apis/monitoring/elasticsearch/fixtures/index_detail.json +++ b/x-pack/test/api_integration/apis/monitoring/elasticsearch/fixtures/index_detail.json @@ -9,21 +9,6 @@ "totalShards": 10, "status": "green" }, - "logs": { - "enabled": false, - "limit": 10, - "reason": { - "clusterExists": false, - "indexPatternExists": false, - "indexPatternInTimeRangeExists": false, - "typeExistsAtAnyTime": false, - "usingStructuredLogs": false, - "nodeExists": null, - "indexExists": false, - "typeExists": false - }, - "logs": [] - }, "metrics": { "index_search_request_rate": [ { @@ -1104,93 +1089,108 @@ } ] }, + "logs": { + "enabled": false, + "logs": [], + "reason": { + "indexPatternExists": false, + "indexPatternInTimeRangeExists": false, + "typeExistsAtAnyTime": false, + "typeExists": false, + "usingStructuredLogs": false, + "clusterExists": false, + "nodeExists": null, + "indexExists": false + }, + "limit": 10 + }, "shards": [ { - "state": "STARTED", - "primary": false, + "index": "avocado-tweets-2017.10.02", "node": "xcP6ue7eRCieNNitFTT0EA", + "primary": false, "relocating_node": null, "shard": 4, - "index": "avocado-tweets-2017.10.02" + "state": "STARTED" }, { - "state": "STARTED", - "primary": true, + "index": "avocado-tweets-2017.10.02", "node": "jUT5KdxfRbORSCWkb5zjmA", + "primary": true, "relocating_node": null, "shard": 4, - "index": "avocado-tweets-2017.10.02" + "state": "STARTED" }, { - "state": "STARTED", - "primary": true, + "index": "avocado-tweets-2017.10.02", "node": "xcP6ue7eRCieNNitFTT0EA", + "primary": true, "relocating_node": null, "shard": 1, - "index": "avocado-tweets-2017.10.02" + "state": "STARTED" }, { - "state": "STARTED", - "primary": false, + "index": "avocado-tweets-2017.10.02", "node": "jUT5KdxfRbORSCWkb5zjmA", + "primary": false, "relocating_node": null, "shard": 1, - "index": "avocado-tweets-2017.10.02" + "state": "STARTED" }, { - "state": "STARTED", - "primary": true, + "index": "avocado-tweets-2017.10.02", "node": "xcP6ue7eRCieNNitFTT0EA", + "primary": true, "relocating_node": null, "shard": 2, - "index": "avocado-tweets-2017.10.02" + "state": "STARTED" }, { - "state": "STARTED", - "primary": false, + "index": "avocado-tweets-2017.10.02", "node": "jUT5KdxfRbORSCWkb5zjmA", + "primary": false, "relocating_node": null, "shard": 2, - "index": "avocado-tweets-2017.10.02" + "state": "STARTED" }, { - "state": "STARTED", - "primary": false, + "index": "avocado-tweets-2017.10.02", "node": "xcP6ue7eRCieNNitFTT0EA", + "primary": false, "relocating_node": null, "shard": 3, - "index": "avocado-tweets-2017.10.02" + "state": "STARTED" }, { - "state": "STARTED", - "primary": true, + "index": "avocado-tweets-2017.10.02", "node": "jUT5KdxfRbORSCWkb5zjmA", + "primary": true, "relocating_node": null, "shard": 3, - "index": "avocado-tweets-2017.10.02" + "state": "STARTED" }, { - "state": "STARTED", - "primary": false, + "index": "avocado-tweets-2017.10.02", "node": "xcP6ue7eRCieNNitFTT0EA", + "primary": false, "relocating_node": null, "shard": 0, - "index": "avocado-tweets-2017.10.02" + "state": "STARTED" }, { - "state": "STARTED", - "primary": true, + "index": "avocado-tweets-2017.10.02", "node": "jUT5KdxfRbORSCWkb5zjmA", + "primary": true, "relocating_node": null, "shard": 0, - "index": "avocado-tweets-2017.10.02" + "state": "STARTED" } ], "shardStats": { "nodes": { "jUT5KdxfRbORSCWkb5zjmA": { - "shardCount": 38, - "indexCount": 20, + "shardCount": 5, + "indexCount": 1, "name": "whatever-01", "node_ids": [ "jUT5KdxfRbORSCWkb5zjmA" @@ -1198,29 +1198,20 @@ "type": "master" }, "xcP6ue7eRCieNNitFTT0EA": { - "shardCount": 36, - "indexCount": 19, + "shardCount": 5, + "indexCount": 1, "name": "whatever-02", "node_ids": [ "xcP6ue7eRCieNNitFTT0EA" ], "type": "node" - }, - "bwQWH-7IQY-mFPpfoaoFXQ": { - "shardCount": 4, - "indexCount": 4, - "name": "whatever-03", - "node_ids": [ - "bwQWH-7IQY-mFPpfoaoFXQ" - ], - "type": "node" } } }, "nodes": { "jUT5KdxfRbORSCWkb5zjmA": { - "shardCount": 38, - "indexCount": 20, + "shardCount": 5, + "indexCount": 1, "name": "whatever-01", "node_ids": [ "jUT5KdxfRbORSCWkb5zjmA" @@ -1228,22 +1219,13 @@ "type": "master" }, "xcP6ue7eRCieNNitFTT0EA": { - "shardCount": 36, - "indexCount": 19, + "shardCount": 5, + "indexCount": 1, "name": "whatever-02", "node_ids": [ "xcP6ue7eRCieNNitFTT0EA" ], "type": "node" - }, - "bwQWH-7IQY-mFPpfoaoFXQ": { - "shardCount": 4, - "indexCount": 4, - "name": "whatever-03", - "node_ids": [ - "bwQWH-7IQY-mFPpfoaoFXQ" - ], - "type": "node" } }, "stateUuid": "6wwwErXyTfaa4uHBHG5Pbg" diff --git a/x-pack/test/api_integration/apis/monitoring/elasticsearch/fixtures/node_detail.json b/x-pack/test/api_integration/apis/monitoring/elasticsearch/fixtures/node_detail.json index 0b8d26558e7fc..32096b0b97067 100644 --- a/x-pack/test/api_integration/apis/monitoring/elasticsearch/fixtures/node_detail.json +++ b/x-pack/test/api_integration/apis/monitoring/elasticsearch/fixtures/node_detail.json @@ -1,293 +1,1518 @@ { "nodeSummary": { - "resolver": "jxcP6ue7eRCieNNitFTT0EA", - "node_ids": [], - "attributes": {}, - "transport_address": "", - "name": "jxcP6ue7eRCieNNitFTT0EA", - "type": "node", - "nodeTypeLabel": "Offline Node", - "status": "Offline", - "isOnline": false + "resolver": "jUT5KdxfRbORSCWkb5zjmA", + "node_ids": [ + "jUT5KdxfRbORSCWkb5zjmA" + ], + "attributes": { + "ml.enabled": "true", + "ml.max_open_jobs": "10" + }, + "transport_address": "127.0.0.1:9300", + "name": "whatever-01", + "type": "master", + "nodeTypeLabel": "Master Node", + "nodeTypeClass": "starFilled", + "totalShards": 38, + "indexCount": 20, + "documents": 24830, + "dataSize": 52847579, + "freeSpace": 186755088384, + "totalSpace": 499065712640, + "usedHeap": 29, + "status": "Online", + "isOnline": true + }, + "metrics": { + "node_total_io": [ + { + "bucket_size": "10 seconds", + "timeRange": { + "min": 1507235508000, + "max": 1507235712000 + }, + "metric": { + "app": "elasticsearch", + "field": "node_stats.fs.io_stats.total.operations", + "metricAgg": "max", + "label": "Total I/O", + "title": "I/O Operations Rate", + "description": "Total I/O. (This metric is not supported on all platforms and may display N/A if I/O data is unavailable.)", + "units": "/s", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": true + }, + "data": [ + [ + 1507235520000, + null + ], + [ + 1507235530000, + null + ], + [ + 1507235540000, + null + ], + [ + 1507235550000, + null + ], + [ + 1507235560000, + null + ], + [ + 1507235570000, + null + ], + [ + 1507235580000, + null + ], + [ + 1507235590000, + null + ], + [ + 1507235600000, + null + ], + [ + 1507235610000, + null + ], + [ + 1507235620000, + null + ], + [ + 1507235630000, + null + ], + [ + 1507235640000, + null + ], + [ + 1507235650000, + null + ], + [ + 1507235660000, + null + ], + [ + 1507235670000, + null + ], + [ + 1507235680000, + null + ], + [ + 1507235690000, + null + ], + [ + 1507235700000, + null + ] + ] + }, + { + "bucket_size": "10 seconds", + "timeRange": { + "min": 1507235508000, + "max": 1507235712000 + }, + "metric": { + "app": "elasticsearch", + "field": "node_stats.fs.io_stats.total.read_operations", + "metricAgg": "max", + "label": "Total Read I/O", + "title": "I/O Operations Rate", + "description": "Total Read I/O. (This metric is not supported on all platforms and may display N/A if I/O data is unavailable.)", + "units": "/s", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": true + }, + "data": [ + [ + 1507235520000, + null + ], + [ + 1507235530000, + null + ], + [ + 1507235540000, + null + ], + [ + 1507235550000, + null + ], + [ + 1507235560000, + null + ], + [ + 1507235570000, + null + ], + [ + 1507235580000, + null + ], + [ + 1507235590000, + null + ], + [ + 1507235600000, + null + ], + [ + 1507235610000, + null + ], + [ + 1507235620000, + null + ], + [ + 1507235630000, + null + ], + [ + 1507235640000, + null + ], + [ + 1507235650000, + null + ], + [ + 1507235660000, + null + ], + [ + 1507235670000, + null + ], + [ + 1507235680000, + null + ], + [ + 1507235690000, + null + ], + [ + 1507235700000, + null + ] + ] + }, + { + "bucket_size": "10 seconds", + "timeRange": { + "min": 1507235508000, + "max": 1507235712000 + }, + "metric": { + "app": "elasticsearch", + "field": "node_stats.fs.io_stats.total.write_operations", + "metricAgg": "max", + "label": "Total Write I/O", + "title": "I/O Operations Rate", + "description": "Total Write I/O. (This metric is not supported on all platforms and may display N/A if I/O data is unavailable.)", + "units": "/s", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": true + }, + "data": [ + [ + 1507235520000, + null + ], + [ + 1507235530000, + null + ], + [ + 1507235540000, + null + ], + [ + 1507235550000, + null + ], + [ + 1507235560000, + null + ], + [ + 1507235570000, + null + ], + [ + 1507235580000, + null + ], + [ + 1507235590000, + null + ], + [ + 1507235600000, + null + ], + [ + 1507235610000, + null + ], + [ + 1507235620000, + null + ], + [ + 1507235630000, + null + ], + [ + 1507235640000, + null + ], + [ + 1507235650000, + null + ], + [ + 1507235660000, + null + ], + [ + 1507235670000, + null + ], + [ + 1507235680000, + null + ], + [ + 1507235690000, + null + ], + [ + 1507235700000, + null + ] + ] + } + ], + "node_latency": [ + { + "bucket_size": "10 seconds", + "timeRange": { + "min": 1507235508000, + "max": 1507235712000 + }, + "metric": { + "app": "elasticsearch", + "field": "node_stats.indices.search.query_total", + "metricAgg": "sum", + "label": "Search", + "title": "Latency", + "description": "Average latency for searching, which is time it takes to execute searches divided by number of searches submitted. This considers primary and replica shards.", + "units": "ms", + "format": "0,0.[00]", + "hasCalculation": true, + "isDerivative": false + }, + "data": [ + [ + 1507235520000, + null + ], + [ + 1507235530000, + 0.33333333333333337 + ], + [ + 1507235540000, + 0 + ], + [ + 1507235550000, + 0.33333333333333337 + ], + [ + 1507235560000, + 0 + ], + [ + 1507235570000, + 0.33333333333333337 + ], + [ + 1507235580000, + 0 + ], + [ + 1507235590000, + 0 + ], + [ + 1507235600000, + 0.33333333333333337 + ], + [ + 1507235610000, + 0 + ], + [ + 1507235620000, + 0 + ], + [ + 1507235630000, + 0.33333333333333337 + ], + [ + 1507235640000, + 0 + ], + [ + 1507235650000, + 0 + ], + [ + 1507235660000, + 0 + ], + [ + 1507235670000, + 0.2 + ], + [ + 1507235680000, + 0 + ], + [ + 1507235690000, + 0 + ], + [ + 1507235700000, + 0 + ] + ] + }, + { + "bucket_size": "10 seconds", + "timeRange": { + "min": 1507235508000, + "max": 1507235712000 + }, + "metric": { + "app": "elasticsearch", + "field": "node_stats.indices.indexing.index_total", + "metricAgg": "sum", + "label": "Indexing", + "title": "Latency", + "description": "Average latency for indexing documents, which is time it takes to index documents divided by number that were indexed. This considers any shard located on this node, including replicas.", + "units": "ms", + "format": "0,0.[00]", + "hasCalculation": true, + "isDerivative": false + }, + "data": [ + [ + 1507235520000, + null + ], + [ + 1507235530000, + 0 + ], + [ + 1507235540000, + 0 + ], + [ + 1507235550000, + 0.888888888888889 + ], + [ + 1507235560000, + 1.1666666666666667 + ], + [ + 1507235570000, + 0 + ], + [ + 1507235580000, + 0 + ], + [ + 1507235590000, + 0 + ], + [ + 1507235600000, + 0 + ], + [ + 1507235610000, + 1.3333333333333333 + ], + [ + 1507235620000, + 1.1666666666666667 + ], + [ + 1507235630000, + 0 + ], + [ + 1507235640000, + 0 + ], + [ + 1507235650000, + 0 + ], + [ + 1507235660000, + 0 + ], + [ + 1507235670000, + 2.3333333333333335 + ], + [ + 1507235680000, + 2.8749999999999996 + ], + [ + 1507235690000, + 0 + ], + [ + 1507235700000, + 0 + ] + ] + } + ], + "node_jvm_mem": [ + { + "bucket_size": "10 seconds", + "timeRange": { + "min": 1507235508000, + "max": 1507235712000 + }, + "metric": { + "app": "elasticsearch", + "field": "node_stats.jvm.mem.heap_max_in_bytes", + "metricAgg": "max", + "label": "Max Heap", + "title": "JVM Heap", + "description": "Total heap available to Elasticsearch running in the JVM.", + "units": "B", + "format": "0.0 b", + "hasCalculation": false, + "isDerivative": false + }, + "data": [ + [ + 1507235520000, + 709623808 + ], + [ + 1507235530000, + 709623808 + ], + [ + 1507235540000, + 709623808 + ], + [ + 1507235550000, + 709623808 + ], + [ + 1507235560000, + 709623808 + ], + [ + 1507235570000, + 709623808 + ], + [ + 1507235580000, + 709623808 + ], + [ + 1507235590000, + 709623808 + ], + [ + 1507235600000, + 709623808 + ], + [ + 1507235610000, + 709623808 + ], + [ + 1507235620000, + 709623808 + ], + [ + 1507235630000, + 709623808 + ], + [ + 1507235640000, + 709623808 + ], + [ + 1507235650000, + 709623808 + ], + [ + 1507235660000, + 709623808 + ], + [ + 1507235670000, + 709623808 + ], + [ + 1507235680000, + 709623808 + ], + [ + 1507235690000, + 709623808 + ], + [ + 1507235700000, + 709623808 + ] + ] + }, + { + "bucket_size": "10 seconds", + "timeRange": { + "min": 1507235508000, + "max": 1507235712000 + }, + "metric": { + "app": "elasticsearch", + "field": "node_stats.jvm.mem.heap_used_in_bytes", + "metricAgg": "max", + "label": "Used Heap", + "title": "JVM Heap", + "description": "Total heap used by Elasticsearch running in the JVM.", + "units": "B", + "format": "0.0 b", + "hasCalculation": false, + "isDerivative": false + }, + "data": [ + [ + 1507235520000, + 317052776 + ], + [ + 1507235530000, + 344014976 + ], + [ + 1507235540000, + 368593248 + ], + [ + 1507235550000, + 253850400 + ], + [ + 1507235560000, + 348095032 + ], + [ + 1507235570000, + 182919712 + ], + [ + 1507235580000, + 212395016 + ], + [ + 1507235590000, + 244004144 + ], + [ + 1507235600000, + 270412240 + ], + [ + 1507235610000, + 245052864 + ], + [ + 1507235620000, + 370270616 + ], + [ + 1507235630000, + 196944168 + ], + [ + 1507235640000, + 223491760 + ], + [ + 1507235650000, + 253878472 + ], + [ + 1507235660000, + 280811736 + ], + [ + 1507235670000, + 371931976 + ], + [ + 1507235680000, + 329874616 + ], + [ + 1507235690000, + 363869776 + ], + [ + 1507235700000, + 211045968 + ] + ] + } + ], + "node_mem": [ + { + "bucket_size": "10 seconds", + "timeRange": { + "min": 1507235508000, + "max": 1507235712000 + }, + "metric": { + "app": "elasticsearch", + "field": "node_stats.indices.segments.memory_in_bytes", + "metricAgg": "max", + "label": "Lucene Total", + "title": "Index Memory", + "description": "Total heap memory used by Lucene for current index. This is the sum of other fields for primary and replica shards on this node.", + "units": "B", + "format": "0.0 b", + "hasCalculation": false, + "isDerivative": false + }, + "data": [ + [ + 1507235520000, + 4797457 + ], + [ + 1507235530000, + 4797457 + ], + [ + 1507235540000, + 4797457 + ], + [ + 1507235550000, + 4797457 + ], + [ + 1507235560000, + 4823580 + ], + [ + 1507235570000, + 4823580 + ], + [ + 1507235580000, + 4823580 + ], + [ + 1507235590000, + 4823580 + ], + [ + 1507235600000, + 4823580 + ], + [ + 1507235610000, + 4838368 + ], + [ + 1507235620000, + 4741420 + ], + [ + 1507235630000, + 4741420 + ], + [ + 1507235640000, + 4741420 + ], + [ + 1507235650000, + 4741420 + ], + [ + 1507235660000, + 4741420 + ], + [ + 1507235670000, + 4757998 + ], + [ + 1507235680000, + 4787542 + ], + [ + 1507235690000, + 4787542 + ], + [ + 1507235700000, + 4787542 + ] + ] + }, + { + "bucket_size": "10 seconds", + "timeRange": { + "min": 1507235508000, + "max": 1507235712000 + }, + "metric": { + "app": "elasticsearch", + "field": "node_stats.indices.segments.terms_memory_in_bytes", + "metricAgg": "max", + "label": "Terms", + "title": "Index Memory", + "description": "Heap memory used by Terms (e.g., text). This is a part of Lucene Total.", + "units": "B", + "format": "0.0 b", + "hasCalculation": false, + "isDerivative": false + }, + "data": [ + [ + 1507235520000, + 3764438 + ], + [ + 1507235530000, + 3764438 + ], + [ + 1507235540000, + 3764438 + ], + [ + 1507235550000, + 3764438 + ], + [ + 1507235560000, + 3786762 + ], + [ + 1507235570000, + 3786762 + ], + [ + 1507235580000, + 3786762 + ], + [ + 1507235590000, + 3786762 + ], + [ + 1507235600000, + 3786762 + ], + [ + 1507235610000, + 3799306 + ], + [ + 1507235620000, + 3715996 + ], + [ + 1507235630000, + 3715996 + ], + [ + 1507235640000, + 3715996 + ], + [ + 1507235650000, + 3715996 + ], + [ + 1507235660000, + 3715996 + ], + [ + 1507235670000, + 3729890 + ], + [ + 1507235680000, + 3755528 + ], + [ + 1507235690000, + 3755528 + ], + [ + 1507235700000, + 3755528 + ] + ] + }, + { + "bucket_size": "10 seconds", + "timeRange": { + "min": 1507235508000, + "max": 1507235712000 + }, + "metric": { + "app": "elasticsearch", + "field": "node_stats.indices.segments.points_memory_in_bytes", + "metricAgg": "max", + "label": "Points", + "title": "Index Memory", + "description": "Heap memory used by Points (e.g., numbers, IPs, and geo data). This is a part of Lucene Total.", + "units": "B", + "format": "0.0 b", + "hasCalculation": false, + "isDerivative": false + }, + "data": [ + [ + 1507235520000, + 12171 + ], + [ + 1507235530000, + 12171 + ], + [ + 1507235540000, + 12171 + ], + [ + 1507235550000, + 12171 + ], + [ + 1507235560000, + 12198 + ], + [ + 1507235570000, + 12198 + ], + [ + 1507235580000, + 12198 + ], + [ + 1507235590000, + 12198 + ], + [ + 1507235600000, + 12198 + ], + [ + 1507235610000, + 12218 + ], + [ + 1507235620000, + 12120 + ], + [ + 1507235630000, + 12120 + ], + [ + 1507235640000, + 12120 + ], + [ + 1507235650000, + 12120 + ], + [ + 1507235660000, + 12120 + ], + [ + 1507235670000, + 12140 + ], + [ + 1507235680000, + 12166 + ], + [ + 1507235690000, + 12166 + ], + [ + 1507235700000, + 12166 + ] + ] + } + ], + "node_cpu_metric": [ + { + "bucket_size": "10 seconds", + "timeRange": { + "min": 1507235508000, + "max": 1507235712000 + }, + "metric": { + "app": "elasticsearch", + "field": "node_stats.process.cpu.percent", + "metricAgg": "max", + "label": "CPU Utilization", + "description": "Percentage of CPU usage for the Elasticsearch process.", + "units": "%", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": false + }, + "data": [ + [ + 1507235520000, + 1 + ], + [ + 1507235530000, + 0 + ], + [ + 1507235540000, + 0 + ], + [ + 1507235550000, + 1 + ], + [ + 1507235560000, + 2 + ], + [ + 1507235570000, + 0 + ], + [ + 1507235580000, + 2 + ], + [ + 1507235590000, + 0 + ], + [ + 1507235600000, + 0 + ], + [ + 1507235610000, + 3 + ], + [ + 1507235620000, + 2 + ], + [ + 1507235630000, + 2 + ], + [ + 1507235640000, + 0 + ], + [ + 1507235650000, + 1 + ], + [ + 1507235660000, + 0 + ], + [ + 1507235670000, + 2 + ], + [ + 1507235680000, + 2 + ], + [ + 1507235690000, + 1 + ], + [ + 1507235700000, + 0 + ] + ] + } + ], + "node_load_average": [ + { + "bucket_size": "10 seconds", + "timeRange": { + "min": 1507235508000, + "max": 1507235712000 + }, + "metric": { + "app": "elasticsearch", + "field": "node_stats.os.cpu.load_average.1m", + "metricAgg": "max", + "label": "1m", + "title": "System Load", + "description": "Load average over the last minute.", + "units": "", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": false + }, + "data": [ + [ + 1507235520000, + 2.876953125 + ], + [ + 1507235530000, + 2.66015625 + ], + [ + 1507235540000, + 2.40625 + ], + [ + 1507235550000, + 2.189453125 + ], + [ + 1507235560000, + 2.626953125 + ], + [ + 1507235570000, + 2.451171875 + ], + [ + 1507235580000, + 2.81640625 + ], + [ + 1507235590000, + 3.70703125 + ], + [ + 1507235600000, + 3.51171875 + ], + [ + 1507235610000, + 3.359375 + ], + [ + 1507235620000, + 3.076171875 + ], + [ + 1507235630000, + 2.990234375 + ], + [ + 1507235640000, + 2.904296875 + ], + [ + 1507235650000, + 2.84375 + ], + [ + 1507235660000, + 3.28125 + ], + [ + 1507235670000, + 5.30859375 + ], + [ + 1507235680000, + 7.63671875 + ], + [ + 1507235690000, + 9.4375 + ], + [ + 1507235700000, + 11.421875 + ] + ] + } + ], + "node_segment_count": [ + { + "bucket_size": "10 seconds", + "timeRange": { + "min": 1507235508000, + "max": 1507235712000 + }, + "metric": { + "app": "elasticsearch", + "field": "node_stats.indices.segments.count", + "metricAgg": "max", + "label": "Segment Count", + "description": "Maximum segment count for primary and replica shards on this node.", + "units": "", + "format": "0,0.[00]", + "hasCalculation": false, + "isDerivative": false + }, + "data": [ + [ + 1507235520000, + 128 + ], + [ + 1507235530000, + 128 + ], + [ + 1507235540000, + 128 + ], + [ + 1507235550000, + 128 + ], + [ + 1507235560000, + 131 + ], + [ + 1507235570000, + 131 + ], + [ + 1507235580000, + 131 + ], + [ + 1507235590000, + 131 + ], + [ + 1507235600000, + 131 + ], + [ + 1507235610000, + 133 + ], + [ + 1507235620000, + 126 + ], + [ + 1507235630000, + 126 + ], + [ + 1507235640000, + 126 + ], + [ + 1507235650000, + 126 + ], + [ + 1507235660000, + 126 + ], + [ + 1507235670000, + 128 + ], + [ + 1507235680000, + 130 + ], + [ + 1507235690000, + 130 + ], + [ + 1507235700000, + 130 + ] + ] + } + ] }, "logs": { "enabled": false, - "limit": 10, + "logs": [], "reason": { - "clusterExists": false, "indexPatternExists": false, "indexPatternInTimeRangeExists": false, "typeExistsAtAnyTime": false, + "typeExists": false, "usingStructuredLogs": false, + "clusterExists": false, "nodeExists": false, - "indexExists": null, - "typeExists": false + "indexExists": null }, - "logs": [] + "limit": 10 }, - "metrics": { - "node_latency": [{ - "bucket_size": "10 seconds", - "timeRange": { - "min": 1507235508000, - "max": 1507235712000 - }, - "metric": { - "app": "elasticsearch", - "field": "node_stats.indices.search.query_total", - "metricAgg": "sum", - "label": "Search", - "title": "Latency", - "description": "Average latency for searching, which is time it takes to execute searches divided by number of searches submitted. This considers primary and replica shards.", - "units": "ms", - "format": "0,0.[00]", - "hasCalculation": true, - "isDerivative": false - }, - "data": [] - }, { - "bucket_size": "10 seconds", - "timeRange": { - "min": 1507235508000, - "max": 1507235712000 - }, - "metric": { - "app": "elasticsearch", - "field": "node_stats.indices.indexing.index_total", - "metricAgg": "sum", - "label": "Indexing", - "title": "Latency", - "description": "Average latency for indexing documents, which is time it takes to index documents divided by number that were indexed. This considers any shard located on this node, including replicas.", - "units": "ms", - "format": "0,0.[00]", - "hasCalculation": true, - "isDerivative": false - }, - "data": [] - }], - "node_jvm_mem": [{ - "bucket_size": "10 seconds", - "timeRange": { - "min": 1507235508000, - "max": 1507235712000 - }, - "metric": { - "app": "elasticsearch", - "field": "node_stats.jvm.mem.heap_max_in_bytes", - "metricAgg": "max", - "label": "Max Heap", - "title": "JVM Heap", - "description": "Total heap available to Elasticsearch running in the JVM.", - "units": "B", - "format": "0.0 b", - "hasCalculation": false, - "isDerivative": false - }, - "data": [] - }, { - "bucket_size": "10 seconds", - "timeRange": { - "min": 1507235508000, - "max": 1507235712000 - }, - "metric": { - "app": "elasticsearch", - "field": "node_stats.jvm.mem.heap_used_in_bytes", - "metricAgg": "max", - "label": "Used Heap", - "title": "JVM Heap", - "description": "Total heap used by Elasticsearch running in the JVM.", - "units": "B", - "format": "0.0 b", - "hasCalculation": false, - "isDerivative": false - }, - "data": [] - }], - "node_mem": [{ - "bucket_size": "10 seconds", - "timeRange": { - "min": 1507235508000, - "max": 1507235712000 - }, - "metric": { - "app": "elasticsearch", - "field": "node_stats.indices.segments.memory_in_bytes", - "metricAgg": "max", - "label": "Lucene Total", - "title": "Index Memory", - "description": "Total heap memory used by Lucene for current index. This is the sum of other fields for primary and replica shards on this node.", - "units": "B", - "format": "0.0 b", - "hasCalculation": false, - "isDerivative": false - }, - "data": [] - }, { - "bucket_size": "10 seconds", - "timeRange": { - "min": 1507235508000, - "max": 1507235712000 - }, - "metric": { - "app": "elasticsearch", - "field": "node_stats.indices.segments.terms_memory_in_bytes", - "metricAgg": "max", - "label": "Terms", - "title": "Index Memory", - "description": "Heap memory used by Terms (e.g., text). This is a part of Lucene Total.", - "units": "B", - "format": "0.0 b", - "hasCalculation": false, - "isDerivative": false - }, - "data": [] - }, { - "bucket_size": "10 seconds", - "timeRange": { - "min": 1507235508000, - "max": 1507235712000 - }, - "metric": { - "app": "elasticsearch", - "field": "node_stats.indices.segments.points_memory_in_bytes", - "metricAgg": "max", - "label": "Points", - "title": "Index Memory", - "description": "Heap memory used by Points (e.g., numbers, IPs, and geo data). This is a part of Lucene Total.", - "units": "B", - "format": "0.0 b", - "hasCalculation": false, - "isDerivative": false - }, - "data": [] - }], - "node_cpu_metric": [{ - "bucket_size": "10 seconds", - "timeRange": { - "min": 1507235508000, - "max": 1507235712000 - }, - "metric": { - "app": "elasticsearch", - "field": "node_stats.process.cpu.percent", - "metricAgg": "max", - "label": "CPU Utilization", - "description": "Percentage of CPU usage for the Elasticsearch process.", - "units": "%", - "format": "0,0.[00]", - "hasCalculation": false, - "isDerivative": false - }, - "data": [] - }], - "node_total_io": [{ - "bucket_size": "10 seconds", - "data": [], - "metric": { - "app": "elasticsearch", - "description": "Total I/O. (This metric is not supported on all platforms and may display N/A if I/O data is unavailable.)", - "field": "node_stats.fs.io_stats.total.operations", - "format": "0,0.[00]", - "hasCalculation": false, - "isDerivative": true, - "label": "Total I/O", - "metricAgg": "max", - "title": "I/O Operations Rate", - "units": "/s" - }, - "timeRange": { - "max": 1507235712000, - "min": 1507235508000 - } - }, - { - "bucket_size": "10 seconds", - "data": [], - "metric": { - "app": "elasticsearch", - "description": "Total Read I/O. (This metric is not supported on all platforms and may display N/A if I/O data is unavailable.)", - "field": "node_stats.fs.io_stats.total.read_operations", - "format": "0,0.[00]", - "hasCalculation": false, - "isDerivative": true, - "label": "Total Read I/O", - "metricAgg": "max", - "title": "I/O Operations Rate", - "units": "/s" - }, - "timeRange": { - "max": 1507235712000, - "min": 1507235508000 - } + "shards": [ + { + "index": "watermelon-tweets-2017.10.05", + "node": "jUT5KdxfRbORSCWkb5zjmA", + "primary": true, + "relocating_node": null, + "shard": 4, + "state": "STARTED" }, { - "bucket_size": "10 seconds", - "data": [], - "metric": { - "app": "elasticsearch", - "description": "Total Write I/O. (This metric is not supported on all platforms and may display N/A if I/O data is unavailable.)", - "field": "node_stats.fs.io_stats.total.write_operations", - "format": "0,0.[00]", - "hasCalculation": false, - "isDerivative": true, - "label": "Total Write I/O", - "metricAgg": "max", - "title": "I/O Operations Rate", - "units": "/s" - }, - "timeRange": { - "max": 1507235712000, - "min": 1507235508000 - } - }], - "node_load_average": [{ - "bucket_size": "10 seconds", - "timeRange": { - "min": 1507235508000, - "max": 1507235712000 - }, - "metric": { - "app": "elasticsearch", - "field": "node_stats.os.cpu.load_average.1m", - "metricAgg": "max", - "label": "1m", - "title": "System Load", - "description": "Load average over the last minute.", - "units": "", - "format": "0,0.[00]", - "hasCalculation": false, - "isDerivative": false - }, - "data": [] - }], - "node_segment_count": [{ - "bucket_size": "10 seconds", - "timeRange": { - "min": 1507235508000, - "max": 1507235712000 - }, - "metric": { - "app": "elasticsearch", - "field": "node_stats.indices.segments.count", - "metricAgg": "max", - "label": "Segment Count", - "description": "Maximum segment count for primary and replica shards on this node.", - "units": "", - "format": "0,0.[00]", - "hasCalculation": false, - "isDerivative": false - }, - "data": [] - }] - }, - "shards": [], + "index": "watermelon-tweets-2017.10.05", + "node": "jUT5KdxfRbORSCWkb5zjmA", + "primary": true, + "relocating_node": null, + "shard": 1, + "state": "STARTED" + }, + { + "index": "watermelon-tweets-2017.10.05", + "node": "jUT5KdxfRbORSCWkb5zjmA", + "primary": true, + "relocating_node": null, + "shard": 2, + "state": "STARTED" + }, + { + "index": "watermelon-tweets-2017.10.05", + "node": "jUT5KdxfRbORSCWkb5zjmA", + "primary": true, + "relocating_node": null, + "shard": 3, + "state": "STARTED" + }, + { + "index": "avocado-tweets-2017.09.30", + "node": "jUT5KdxfRbORSCWkb5zjmA", + "primary": true, + "relocating_node": null, + "shard": 4, + "state": "STARTED" + }, + { + "index": "avocado-tweets-2017.09.30", + "node": "jUT5KdxfRbORSCWkb5zjmA", + "primary": true, + "relocating_node": null, + "shard": 1, + "state": "STARTED" + }, + { + "index": "avocado-tweets-2017.09.30", + "node": "jUT5KdxfRbORSCWkb5zjmA", + "primary": false, + "relocating_node": null, + "shard": 2, + "state": "STARTED" + }, + { + "index": "avocado-tweets-2017.09.30", + "node": "jUT5KdxfRbORSCWkb5zjmA", + "primary": true, + "relocating_node": null, + "shard": 3, + "state": "STARTED" + }, + { + "index": "avocado-tweets-2017.09.30", + "node": "jUT5KdxfRbORSCWkb5zjmA", + "primary": false, + "relocating_node": null, + "shard": 0, + "state": "STARTED" + }, + { + "index": "avocado-tweets-2017.10.03", + "node": "jUT5KdxfRbORSCWkb5zjmA", + "primary": true, + "relocating_node": null, + "shard": 4, + "state": "STARTED" + }, + { + "index": "avocado-tweets-2017.10.03", + "node": "jUT5KdxfRbORSCWkb5zjmA", + "primary": true, + "relocating_node": null, + "shard": 1, + "state": "STARTED" + }, + { + "index": "avocado-tweets-2017.10.03", + "node": "jUT5KdxfRbORSCWkb5zjmA", + "primary": true, + "relocating_node": null, + "shard": 2, + "state": "STARTED" + }, + { + "index": "avocado-tweets-2017.10.03", + "node": "jUT5KdxfRbORSCWkb5zjmA", + "primary": true, + "relocating_node": null, + "shard": 3, + "state": "STARTED" + }, + { + "index": "avocado-tweets-2017.10.03", + "node": "jUT5KdxfRbORSCWkb5zjmA", + "primary": true, + "relocating_node": null, + "shard": 0, + "state": "STARTED" + }, + { + "index": "phone-home", + "node": "jUT5KdxfRbORSCWkb5zjmA", + "primary": true, + "relocating_node": null, + "shard": 4, + "state": "STARTED" + }, + { + "index": "phone-home", + "node": "jUT5KdxfRbORSCWkb5zjmA", + "primary": true, + "relocating_node": null, + "shard": 2, + "state": "STARTED" + }, + { + "index": "phone-home", + "node": "jUT5KdxfRbORSCWkb5zjmA", + "primary": true, + "relocating_node": null, + "shard": 3, + "state": "STARTED" + }, + { + "index": "phone-home", + "node": "jUT5KdxfRbORSCWkb5zjmA", + "primary": true, + "relocating_node": null, + "shard": 0, + "state": "STARTED" + }, + { + "index": "avocado-tweets-2017.10.02", + "node": "jUT5KdxfRbORSCWkb5zjmA", + "primary": true, + "relocating_node": null, + "shard": 4, + "state": "STARTED" + }, + { + "index": "avocado-tweets-2017.10.02", + "node": "jUT5KdxfRbORSCWkb5zjmA", + "primary": false, + "relocating_node": null, + "shard": 1, + "state": "STARTED" + }, + { + "index": "avocado-tweets-2017.10.02", + "node": "jUT5KdxfRbORSCWkb5zjmA", + "primary": false, + "relocating_node": null, + "shard": 2, + "state": "STARTED" + }, + { + "index": "avocado-tweets-2017.10.02", + "node": "jUT5KdxfRbORSCWkb5zjmA", + "primary": true, + "relocating_node": null, + "shard": 3, + "state": "STARTED" + }, + { + "index": "avocado-tweets-2017.10.02", + "node": "jUT5KdxfRbORSCWkb5zjmA", + "primary": true, + "relocating_node": null, + "shard": 0, + "state": "STARTED" + }, + { + "index": "relocation_test", + "node": "jUT5KdxfRbORSCWkb5zjmA", + "primary": true, + "relocating_node": "bwQWH-7IQY-mFPpfoaoFXQ", + "shard": 0, + "state": "RELOCATING" + } + ], "shardStats": { "indices": { "avocado-tweets-2017.09.30": { "status": "green", - "primary": 5, - "replica": 5, + "primary": 3, + "replica": 2, "unassigned": { "primary": 0, "replica": 0 @@ -295,8 +1520,8 @@ }, "avocado-tweets-2017.10.02": { "status": "green", - "primary": 5, - "replica": 5, + "primary": 3, + "replica": 2, "unassigned": { "primary": 0, "replica": 0 @@ -305,43 +1530,34 @@ "avocado-tweets-2017.10.03": { "status": "green", "primary": 5, - "replica": 5, + "replica": 0, "unassigned": { "primary": 0, "replica": 0 } }, "phone-home": { - "status": "yellow", - "primary": 5, - "replica": 4, + "status": "green", + "primary": 4, + "replica": 0, "unassigned": { "primary": 0, - "replica": 1 + "replica": 0 } }, "watermelon-tweets-2017.10.05": { - "status": "yellow", - "primary": 5, - "replica": 4, - "unassigned": { - "primary": 0, - "replica": 1 - } - }, - ".security-v6": { - "status": "yellow", - "primary": 1, - "replica": 1, + "status": "green", + "primary": 4, + "replica": 0, "unassigned": { "primary": 0, - "replica": 1 + "replica": 0 } }, ".kibana": { "status": "green", "primary": 1, - "replica": 1, + "replica": 0, "unassigned": { "primary": 0, "replica": 0 @@ -349,7 +1565,7 @@ }, ".monitoring-alerts-6": { "status": "green", - "primary": 1, + "primary": 0, "replica": 1, "unassigned": { "primary": 0, @@ -358,7 +1574,7 @@ }, ".monitoring-es-6-2017.10.05": { "status": "yellow", - "primary": 1, + "primary": 0, "replica": 0, "unassigned": { "primary": 0, @@ -367,17 +1583,26 @@ }, ".monitoring-kibana-6-2017.10.05": { "status": "yellow", - "primary": 1, + "primary": 0, "replica": 0, "unassigned": { "primary": 0, "replica": 1 } }, + ".security-v6": { + "status": "green", + "primary": 1, + "replica": 0, + "unassigned": { + "primary": 0, + "replica": 0 + } + }, ".triggered_watches": { "status": "green", "primary": 1, - "replica": 1, + "replica": 0, "unassigned": { "primary": 0, "replica": 0 @@ -386,7 +1611,7 @@ ".watcher-history-7-2017.09.29": { "status": "green", "primary": 1, - "replica": 1, + "replica": 0, "unassigned": { "primary": 0, "replica": 0 @@ -395,7 +1620,7 @@ ".watcher-history-7-2017.09.30": { "status": "green", "primary": 1, - "replica": 1, + "replica": 0, "unassigned": { "primary": 0, "replica": 0 @@ -404,7 +1629,7 @@ ".watcher-history-7-2017.10.01": { "status": "green", "primary": 1, - "replica": 1, + "replica": 0, "unassigned": { "primary": 0, "replica": 0 @@ -412,7 +1637,7 @@ }, ".watcher-history-7-2017.10.02": { "status": "green", - "primary": 1, + "primary": 0, "replica": 1, "unassigned": { "primary": 0, @@ -422,7 +1647,7 @@ ".watcher-history-7-2017.10.03": { "status": "green", "primary": 1, - "replica": 1, + "replica": 0, "unassigned": { "primary": 0, "replica": 0 @@ -430,7 +1655,7 @@ }, ".watcher-history-7-2017.10.04": { "status": "green", - "primary": 1, + "primary": 0, "replica": 1, "unassigned": { "primary": 0, @@ -440,7 +1665,7 @@ ".watcher-history-7-2017.10.05": { "status": "green", "primary": 1, - "replica": 1, + "replica": 0, "unassigned": { "primary": 0, "replica": 0 @@ -449,7 +1674,7 @@ ".watches": { "status": "green", "primary": 1, - "replica": 1, + "replica": 0, "unassigned": { "primary": 0, "replica": 0 @@ -471,22 +1696,10 @@ "shardCount": 38, "indexCount": 20, "name": "whatever-01", - "node_ids": ["jUT5KdxfRbORSCWkb5zjmA"], + "node_ids": [ + "jUT5KdxfRbORSCWkb5zjmA" + ], "type": "master" - }, - "xcP6ue7eRCieNNitFTT0EA": { - "shardCount": 36, - "indexCount": 19, - "name": "whatever-02", - "node_ids": ["xcP6ue7eRCieNNitFTT0EA"], - "type": "node" - }, - "bwQWH-7IQY-mFPpfoaoFXQ": { - "shardCount": 4, - "indexCount": 4, - "name": "whatever-03", - "node_ids": ["bwQWH-7IQY-mFPpfoaoFXQ"], - "type": "node" } }, "stateUuid": "6wwwErXyTfaa4uHBHG5Pbg" diff --git a/x-pack/test/api_integration/apis/monitoring/elasticsearch/node_detail.js b/x-pack/test/api_integration/apis/monitoring/elasticsearch/node_detail.js index 43b5f6d119d6b..9fbf1e02c9c2e 100644 --- a/x-pack/test/api_integration/apis/monitoring/elasticsearch/node_detail.js +++ b/x-pack/test/api_integration/apis/monitoring/elasticsearch/node_detail.js @@ -32,7 +32,7 @@ export default function({ getService }) { it('should summarize node with metrics', async () => { const { body } = await supertest .post( - '/api/monitoring/v1/clusters/YCxj-RAgSZCP6GuOQ8M1EQ/elasticsearch/nodes/jxcP6ue7eRCieNNitFTT0EA' + '/api/monitoring/v1/clusters/YCxj-RAgSZCP6GuOQ8M1EQ/elasticsearch/nodes/jUT5KdxfRbORSCWkb5zjmA' ) .set('kbn-xsrf', 'xxx') .send({ diff --git a/x-pack/test/api_integration/apis/monitoring/setup/collection/ccs.js b/x-pack/test/api_integration/apis/monitoring/setup/collection/ccs.js new file mode 100644 index 0000000000000..4c32e311c6cf3 --- /dev/null +++ b/x-pack/test/api_integration/apis/monitoring/setup/collection/ccs.js @@ -0,0 +1,42 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export default function({ getService }) { + const supertest = getService('supertest'); + const esArchiver = getService('esArchiver'); + + describe('ccs', () => { + const archive = 'monitoring/setup/collection/detect_apm'; + const timeRange = { + min: '2019-04-16T00:00:00.741Z', + max: '2019-04-16T23:59:59.741Z', + }; + + before('load archive', () => { + return esArchiver.load(archive); + }); + + after('unload archive', () => { + return esArchiver.unload(archive); + }); + + it('should not fail with a ccs parameter for cluster', async () => { + await supertest + .post('/api/monitoring/v1/setup/collection/cluster?skipLiveData=true') + .set('kbn-xsrf', 'xxx') + .send({ timeRange, ccs: '*' }) + .expect(200); + }); + + it('should not fail with a ccs parameter for node', async () => { + await supertest + .post('/api/monitoring/v1/setup/collection/node/123?skipLiveData=true') + .set('kbn-xsrf', 'xxx') + .send({ timeRange, ccs: '*' }) + .expect(200); + }); + }); +} diff --git a/x-pack/test/api_integration/apis/monitoring/setup/collection/index.js b/x-pack/test/api_integration/apis/monitoring/setup/collection/index.js index 48d8b15ecbcad..01594babbc2f4 100644 --- a/x-pack/test/api_integration/apis/monitoring/setup/collection/index.js +++ b/x-pack/test/api_integration/apis/monitoring/setup/collection/index.js @@ -16,5 +16,6 @@ export default function({ loadTestFile }) { loadTestFile(require.resolve('./detect_logstash_management')); loadTestFile(require.resolve('./detect_apm')); loadTestFile(require.resolve('./security')); + loadTestFile(require.resolve('./ccs')); }); } diff --git a/x-pack/test/api_integration/apis/telemetry/telemetry_local.js b/x-pack/test/api_integration/apis/telemetry/telemetry_local.js index 0bf24be0fa0b0..db705b301a71e 100644 --- a/x-pack/test/api_integration/apis/telemetry/telemetry_local.js +++ b/x-pack/test/api_integration/apis/telemetry/telemetry_local.js @@ -79,9 +79,16 @@ export default function({ getService }) { expect(stats.stack_stats.kibana.plugins.apm.services_per_agent).to.be.an('object'); expect(stats.stack_stats.kibana.plugins.infraops.last_24_hours).to.be.an('object'); expect(stats.stack_stats.kibana.plugins.kql.defaultQueryLanguage).to.be.a('string'); + expect(stats.stack_stats.kibana.plugins['maps-telemetry'].attributes.timeCaptured).to.be.a( + 'string' + ); + expect(stats.stack_stats.kibana.plugins.reporting.enabled).to.be(true); expect(stats.stack_stats.kibana.plugins.rollups.index_patterns).to.be.an('object'); expect(stats.stack_stats.kibana.plugins.spaces.available).to.be(true); + expect(stats.stack_stats.kibana.plugins.fileUploadTelemetry.filesUploadedTotalCount).to.be.a( + 'number' + ); expect(stats.stack_stats.kibana.os.platforms[0].platform).to.be.a('string'); expect(stats.stack_stats.kibana.os.platforms[0].count).to.be(1); diff --git a/x-pack/test/api_integration/apis/uptime/graphql/filter_bar.js b/x-pack/test/api_integration/apis/uptime/graphql/filter_bar.js deleted file mode 100644 index c21a602d7ba1a..0000000000000 --- a/x-pack/test/api_integration/apis/uptime/graphql/filter_bar.js +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { expectFixtureEql } from './helpers/expect_fixture_eql'; -import { filterBarQueryString } from '../../../../../legacy/plugins/uptime/public/queries'; - -export default function({ getService }) { - describe('filterBar query', () => { - before('load heartbeat data', () => getService('esArchiver').load('uptime/full_heartbeat')); - after('unload heartbeat index', () => getService('esArchiver').unload('uptime/full_heartbeat')); - - const supertest = getService('supertest'); - - it('returns the expected filters', async () => { - const getFilterBarQuery = { - operationName: 'FilterBar', - query: filterBarQueryString, - variables: { - dateRangeStart: '2019-01-28T17:40:08.078Z', - dateRangeEnd: '2025-01-28T19:00:16.078Z', - }, - }; - const { - body: { data }, - } = await supertest - .post('/api/uptime/graphql') - .set('kbn-xsrf', 'foo') - .send({ ...getFilterBarQuery }); - expectFixtureEql(data, 'filter_list'); - }); - }); -} diff --git a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/filter_list.json b/x-pack/test/api_integration/apis/uptime/graphql/fixtures/filter_list.json deleted file mode 100644 index f07e416322105..0000000000000 --- a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/filter_list.json +++ /dev/null @@ -1,40 +0,0 @@ -{ - "filterBar": { - "ids": [ - "0000-intermittent", - "0001-up", - "0002-up", - "0003-up", - "0004-up", - "0005-up", - "0006-up", - "0007-up", - "0008-up", - "0009-up", - "0010-down", - "0011-up", - "0012-up", - "0013-up", - "0014-up", - "0015-intermittent", - "0016-up", - "0017-up", - "0018-up", - "0019-up" - ], - "locations": [ - "mpls" - ], - "ports": [ - 5678 - ], - "schemes": [ - "http" - ], - "urls": [ - "http://localhost:5678/pattern?r=200x1", - "http://localhost:5678/pattern?r=200x5,500x1", - "http://localhost:5678/pattern?r=400x1" - ] - } -} \ No newline at end of file diff --git a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/filters.json b/x-pack/test/api_integration/apis/uptime/graphql/fixtures/filters.json new file mode 100644 index 0000000000000..76e307f27d841 --- /dev/null +++ b/x-pack/test/api_integration/apis/uptime/graphql/fixtures/filters.json @@ -0,0 +1,12 @@ +{ + "schemes": [ + "http" + ], + "ports": [ + 5678 + ], + "locations": [ + "mpls" + ], + "tags": [] +} \ No newline at end of file diff --git a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_page_title.json b/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_page_title.json deleted file mode 100644 index c0dac6c3711bc..0000000000000 --- a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_page_title.json +++ /dev/null @@ -1,7 +0,0 @@ -{ - "monitorPageTitle": { - "id": "0002-up", - "url": "http://localhost:5678/pattern?r=200x1", - "name": "" - } -} \ No newline at end of file diff --git a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_status_by_id.json b/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_status_by_id.json deleted file mode 100644 index 8899803365595..0000000000000 --- a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/monitor_status_by_id.json +++ /dev/null @@ -1,20 +0,0 @@ -[ - { - "timestamp": "2019-09-11T03:40:34.371Z", - "monitor": { - "status": "up", - "duration": { - "us": 24627 - } - }, - "observer": { - "geo": { - "name": "mpls" - } - }, - "tls": null, - "url": { - "full": "http://localhost:5678/pattern?r=200x1" - } - } -] \ No newline at end of file diff --git a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/snapshot.json b/x-pack/test/api_integration/apis/uptime/graphql/fixtures/snapshot.json index 93d63bad66e30..12d8f514a3a30 100644 --- a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/snapshot.json +++ b/x-pack/test/api_integration/apis/uptime/graphql/fixtures/snapshot.json @@ -1,6 +1,5 @@ { - "up": 93, + "up": 10, "down": 7, - "mixed": 0, - "total": 100 + "total": 17 } \ No newline at end of file diff --git a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/snapshot_empty.json b/x-pack/test/api_integration/apis/uptime/graphql/fixtures/snapshot_empty.json index 94c1ffbc74290..c1e7f0ba247fb 100644 --- a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/snapshot_empty.json +++ b/x-pack/test/api_integration/apis/uptime/graphql/fixtures/snapshot_empty.json @@ -1,6 +1,5 @@ { "up": 0, - "down": 7, - "mixed": 0, - "total": 7 + "down": 0, + "total": 0 } \ No newline at end of file diff --git a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/snapshot_filtered_by_down.json b/x-pack/test/api_integration/apis/uptime/graphql/fixtures/snapshot_filtered_by_down.json index 94c1ffbc74290..94777570dd6f0 100644 --- a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/snapshot_filtered_by_down.json +++ b/x-pack/test/api_integration/apis/uptime/graphql/fixtures/snapshot_filtered_by_down.json @@ -1,6 +1,5 @@ { "up": 0, "down": 7, - "mixed": 0, "total": 7 } \ No newline at end of file diff --git a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/snapshot_filtered_by_up.json b/x-pack/test/api_integration/apis/uptime/graphql/fixtures/snapshot_filtered_by_up.json index 2d79880e7c0ee..42a1581707360 100644 --- a/x-pack/test/api_integration/apis/uptime/graphql/fixtures/snapshot_filtered_by_up.json +++ b/x-pack/test/api_integration/apis/uptime/graphql/fixtures/snapshot_filtered_by_up.json @@ -1,6 +1,5 @@ { - "up": 93, + "up": 10, "down": 0, - "mixed": 0, - "total": 93 + "total": 10 } \ No newline at end of file diff --git a/x-pack/test/api_integration/apis/uptime/graphql/helpers/expect_fixture_eql.ts b/x-pack/test/api_integration/apis/uptime/graphql/helpers/expect_fixture_eql.ts index 362803d2b8550..45cc9011773a9 100644 --- a/x-pack/test/api_integration/apis/uptime/graphql/helpers/expect_fixture_eql.ts +++ b/x-pack/test/api_integration/apis/uptime/graphql/helpers/expect_fixture_eql.ts @@ -10,6 +10,7 @@ import { join } from 'path'; import { cloneDeep } from 'lodash'; const fixturesDir = join(__dirname, '..', 'fixtures'); +const restFixturesDir = join(__dirname, '../../rest/', 'fixtures'); const excludeFieldsFrom = (from: any, excluder?: (d: any) => any): any => { const clone = cloneDeep(from); @@ -20,7 +21,11 @@ const excludeFieldsFrom = (from: any, excluder?: (d: any) => any): any => { }; export const expectFixtureEql = (data: T, fixtureName: string, excluder?: (d: T) => void) => { - const fixturePath = join(fixturesDir, `${fixtureName}.json`); + let fixturePath = join(fixturesDir, `${fixtureName}.json`); + if (!fs.existsSync(fixturePath)) { + fixturePath = join(restFixturesDir, `${fixtureName}.json`); + } + const dataExcluded = excludeFieldsFrom(data, excluder); expect(dataExcluded).not.to.be(undefined); if (process.env.UPDATE_UPTIME_FIXTURES) { diff --git a/x-pack/test/api_integration/apis/uptime/graphql/helpers/make_checks.ts b/x-pack/test/api_integration/apis/uptime/graphql/helpers/make_checks.ts index 9bfdf04c8dbe3..f89905f0da04f 100644 --- a/x-pack/test/api_integration/apis/uptime/graphql/helpers/make_checks.ts +++ b/x-pack/test/api_integration/apis/uptime/graphql/helpers/make_checks.ts @@ -5,11 +5,12 @@ */ import uuid from 'uuid'; -import { merge } from 'lodash'; +import { merge, flattenDeep } from 'lodash'; + +const INDEX_NAME = 'heartbeat-8.0.0'; export const makePing = async ( es: any, - index: string, monitorId: string, fields: { [key: string]: any }, mogrify: (doc: any) => any @@ -101,7 +102,7 @@ export const makePing = async ( const doc = mogrify(merge(baseDoc, fields)); await es.index({ - index, + index: INDEX_NAME, refresh: true, body: doc, }); @@ -111,7 +112,6 @@ export const makePing = async ( export const makeCheck = async ( es: any, - index: string, monitorId: string, numIps: number, fields: { [key: string]: any }, @@ -137,7 +137,7 @@ export const makeCheck = async ( if (i === numIps - 1) { pingFields.summary = summary; } - const doc = await makePing(es, index, monitorId, pingFields, mogrify); + const doc = await makePing(es, monitorId, pingFields, mogrify); docs.push(doc); // @ts-ignore summary[doc.monitor.status]++; @@ -147,16 +147,78 @@ export const makeCheck = async ( export const makeChecks = async ( es: any, - index: string, monitorId: string, numChecks: number, numIps: number, + every: number, // number of millis between checks fields: { [key: string]: any } = {}, mogrify: (doc: any) => any = d => d ) => { const checks = []; + const oldestTime = new Date().getTime() - numChecks * every; + let newestTime = oldestTime; for (let li = 0; li < numChecks; li++) { - checks.push(await makeCheck(es, index, monitorId, numIps, fields, mogrify)); + const checkDate = new Date(newestTime + every); + newestTime = checkDate.getTime() + every; + fields = merge(fields, { + '@timestamp': checkDate.toISOString(), + monitor: { + timespan: { + gte: checkDate.toISOString(), + lt: new Date(newestTime).toISOString(), + }, + }, + }); + checks.push(await makeCheck(es, monitorId, numIps, fields, mogrify)); } + return checks; }; + +export const makeChecksWithStatus = async ( + es: any, + monitorId: string, + numChecks: number, + numIps: number, + every: number, + fields: { [key: string]: any } = {}, + status: 'up' | 'down', + mogrify: (doc: any) => any = d => d +) => { + const oppositeStatus = status === 'up' ? 'down' : 'up'; + + return await makeChecks(es, monitorId, numChecks, numIps, every, fields, d => { + d.monitor.status = status; + if (d.summary) { + d.summary[status] += d.summary[oppositeStatus]; + d.summary[oppositeStatus] = 0; + } + + return mogrify(d); + }); +}; + +// Helper for processing a list of checks to find the time picker bounds. +export const getChecksDateRange = (checks: any[]) => { + // Flatten 2d arrays + const flattened = flattenDeep(checks); + + let startTime = 1 / 0; + let endTime = -1 / 0; + flattened.forEach(c => { + const ts = Date.parse(c['@timestamp']); + + if (ts < startTime) { + startTime = ts; + } + + if (ts > endTime) { + endTime = ts; + } + }); + + return { + start: new Date(startTime).toISOString(), + end: new Date(endTime).toISOString(), + }; +}; diff --git a/x-pack/test/api_integration/apis/uptime/graphql/index.js b/x-pack/test/api_integration/apis/uptime/graphql/index.js index 3a696244a5eba..64999761fde4e 100644 --- a/x-pack/test/api_integration/apis/uptime/graphql/index.js +++ b/x-pack/test/api_integration/apis/uptime/graphql/index.js @@ -11,11 +11,8 @@ export default function({ loadTestFile }) { // verifying the pre-loaded documents are returned in a way that // matches the snapshots contained in './fixtures' loadTestFile(require.resolve('./doc_count')); - loadTestFile(require.resolve('./filter_bar')); loadTestFile(require.resolve('./monitor_charts')); - loadTestFile(require.resolve('./monitor_page_title')); loadTestFile(require.resolve('./monitor_states')); - loadTestFile(require.resolve('./monitor_status_bar')); loadTestFile(require.resolve('./ping_list')); loadTestFile(require.resolve('./snapshot_histogram')); }); diff --git a/x-pack/test/api_integration/apis/uptime/graphql/monitor_page_title.ts b/x-pack/test/api_integration/apis/uptime/graphql/monitor_page_title.ts deleted file mode 100644 index 56d22610ffe49..0000000000000 --- a/x-pack/test/api_integration/apis/uptime/graphql/monitor_page_title.ts +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { monitorPageTitleQueryString } from '../../../../../legacy/plugins/uptime/public/queries/monitor_page_title_query'; -import { FtrProviderContext } from '../../../ftr_provider_context'; -import { expectFixtureEql } from './helpers/expect_fixture_eql'; - -export default function({ getService }: FtrProviderContext) { - describe('monitor_page_title', () => { - before('load heartbeat data', () => getService('esArchiver').load('uptime/full_heartbeat')); - after('unload heartbeat index', () => getService('esArchiver').unload('uptime/full_heartbeat')); - - const supertest = getService('supertest'); - - it('will fetch a title for a given monitorId', async () => { - const getMonitorTitleQuery = { - operationName: 'MonitorPageTitle', - query: monitorPageTitleQueryString, - variables: { - monitorId: '0002-up', - }, - }; - - const { - body: { data }, - } = await supertest - .post('/api/uptime/graphql') - .set('kbn-xsrf', 'foo') - .send({ ...getMonitorTitleQuery }); - - expectFixtureEql(data, 'monitor_page_title'); - }); - }); -} diff --git a/x-pack/test/api_integration/apis/uptime/graphql/monitor_states.ts b/x-pack/test/api_integration/apis/uptime/graphql/monitor_states.ts index c305bb99c28f7..511cdb6d004fa 100644 --- a/x-pack/test/api_integration/apis/uptime/graphql/monitor_states.ts +++ b/x-pack/test/api_integration/apis/uptime/graphql/monitor_states.ts @@ -8,7 +8,7 @@ import expect from '@kbn/expect'; import { monitorStatesQueryString } from '../../../../../legacy/plugins/uptime/public/queries/monitor_states_query'; import { expectFixtureEql } from './helpers/expect_fixture_eql'; import { FtrProviderContext } from '../../../ftr_provider_context'; -import { makeChecks } from './helpers/make_checks'; +import { makeChecksWithStatus } from './helpers/make_checks'; export default function({ getService }: FtrProviderContext) { const supertest = getService('supertest'); @@ -104,11 +104,10 @@ export default function({ getService }: FtrProviderContext) { }; before(async () => { - const index = 'heartbeat-8.0.0'; - const es = getService('legacyEs'); dateRangeStart = new Date().toISOString(); - checks = await makeChecks(es, index, testMonitorId, 1, numIps, {}, d => { + checks = await makeChecksWithStatus(es, testMonitorId, 1, numIps, 1, {}, 'up', d => { + // turn an all up status into having at least one down if (d.summary) { d.monitor.status = 'down'; d.summary.up--; diff --git a/x-pack/test/api_integration/apis/uptime/graphql/monitor_status_bar.js b/x-pack/test/api_integration/apis/uptime/graphql/monitor_status_bar.js deleted file mode 100644 index 5d03c90bb2032..0000000000000 --- a/x-pack/test/api_integration/apis/uptime/graphql/monitor_status_bar.js +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import { monitorStatusBarQueryString } from '../../../../../legacy/plugins/uptime/public/queries'; -import { expectFixtureEql } from './helpers/expect_fixture_eql'; - -export default function({ getService }) { - describe('monitorStatusBar query', () => { - before('load heartbeat data', () => getService('esArchiver').load('uptime/full_heartbeat')); - after('unload heartbeat index', () => getService('esArchiver').unload('uptime/full_heartbeat')); - - const supertest = getService('supertest'); - - it('returns the status for all monitors with no ID filtering', async () => { - const getMonitorStatusBarQuery = { - operationName: 'MonitorStatus', - query: monitorStatusBarQueryString, - variables: { - dateRangeStart: '2019-01-28T17:40:08.078Z', - dateRangeEnd: '2025-01-28T19:00:16.078Z', - }, - }; - const { - body: { - data: { monitorStatus: responseData }, - }, - } = await supertest - .post('/api/uptime/graphql') - .set('kbn-xsrf', 'foo') - .send({ ...getMonitorStatusBarQuery }); - - expectFixtureEql(responseData, 'monitor_status_all', res => - res.forEach(i => delete i.millisFromNow) - ); - }); - - it('returns the status for only the given monitor', async () => { - const getMonitorStatusBarQuery = { - operationName: 'MonitorStatus', - query: monitorStatusBarQueryString, - variables: { - dateRangeStart: '2019-01-28T17:40:08.078Z', - dateRangeEnd: '2025-01-28T19:00:16.078Z', - monitorId: '0002-up', - }, - }; - const res = await supertest - .post('/api/uptime/graphql') - .set('kbn-xsrf', 'foo') - .send({ ...getMonitorStatusBarQuery }); - - expectFixtureEql(res.body.data.monitorStatus, 'monitor_status_by_id'); - }); - }); -} diff --git a/x-pack/test/api_integration/apis/uptime/rest/filters.ts b/x-pack/test/api_integration/apis/uptime/rest/filters.ts new file mode 100644 index 0000000000000..6cec6143a6d7c --- /dev/null +++ b/x-pack/test/api_integration/apis/uptime/rest/filters.ts @@ -0,0 +1,27 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { expectFixtureEql } from '../graphql/helpers/expect_fixture_eql'; +import { FtrProviderContext } from '../../../ftr_provider_context'; + +const getApiPath = (dateRangeStart: string, dateRangeEnd: string, filters?: string) => + `/api/uptime/filters?dateRangeStart=${dateRangeStart}&dateRangeEnd=${dateRangeEnd}${ + filters ? `&filters=${filters}` : '' + }`; + +export default function({ getService }: FtrProviderContext) { + const supertest = getService('supertest'); + + describe('filter group endpoint', () => { + const dateRangeStart = '2019-01-28T17:40:08.078Z'; + const dateRangeEnd = '2025-01-28T19:00:16.078Z'; + + it('returns expected filters', async () => { + const resp = await supertest.get(getApiPath(dateRangeStart, dateRangeEnd)); + expectFixtureEql(resp.body, 'filters'); + }); + }); +} diff --git a/x-pack/test/api_integration/apis/uptime/rest/fixtures/monitor_latest_status.json b/x-pack/test/api_integration/apis/uptime/rest/fixtures/monitor_latest_status.json new file mode 100644 index 0000000000000..1702cb2c21007 --- /dev/null +++ b/x-pack/test/api_integration/apis/uptime/rest/fixtures/monitor_latest_status.json @@ -0,0 +1,89 @@ +{ + "@timestamp": "2019-09-11T03:40:34.371Z", + "agent": { + "ephemeral_id": "412a92a8-2142-4b1a-a7a2-1afd32e12f85", + "hostname": "avc-x1x", + "id": "04e1d082-65bc-4929-8d65-d0768a2621c4", + "type": "heartbeat", + "version": "8.0.0" + }, + "ecs": { + "version": "1.1.0" + }, + "event": { + "dataset": "uptime" + }, + "host": { + "name": "avc-x1x" + }, + "http": { + "response": { + "body": { + "bytes": 3, + "hash": "27badc983df1780b60c2b3fa9d3a19a00e46aac798451f0febdca52920faaddf" + }, + "status_code": 200 + }, + "rtt": { + "content": { + "us": 57 + }, + "response_header": { + "us": 262 + }, + "total": { + "us": 20331 + }, + "validate": { + "us": 319 + }, + "write_request": { + "us": 82 + } + } + }, + "monitor": { + "check_group": "d76f0762-d445-11e9-88e3-3e80641b9c71", + "duration": { + "us": 24627 + }, + "id": "0002-up", + "ip": "127.0.0.1", + "name": "", + "status": "up", + "type": "http" + }, + "observer": { + "geo": { + "location": "37.926868, -78.024902", + "name": "mpls" + }, + "hostname": "avc-x1x" + }, + "resolve": { + "ip": "127.0.0.1", + "rtt": { + "us": 4218 + } + }, + "summary": { + "down": 0, + "up": 1 + }, + "tcp": { + "rtt": { + "connect": { + "us": 103 + } + } + }, + "timestamp": "2019-09-11T03:40:34.371Z", + "url": { + "domain": "localhost", + "full": "http://localhost:5678/pattern?r=200x1", + "path": "/pattern", + "port": 5678, + "query": "r=200x1", + "scheme": "http" + } + } diff --git a/x-pack/test/api_integration/apis/uptime/rest/fixtures/selected_monitor.json b/x-pack/test/api_integration/apis/uptime/rest/fixtures/selected_monitor.json new file mode 100644 index 0000000000000..d8367ea67052f --- /dev/null +++ b/x-pack/test/api_integration/apis/uptime/rest/fixtures/selected_monitor.json @@ -0,0 +1,28 @@ +{ + "monitor": { + "check_group": "d76f0762-d445-11e9-88e3-3e80641b9c71", + "duration": { + "us": 24627 + }, + "id": "0002-up", + "ip": "127.0.0.1", + "name": "", + "status": "up", + "type": "http" + }, + "observer": { + "geo": { + "location": "37.926868, -78.024902", + "name": "mpls" + }, + "hostname": "avc-x1x" + }, + "url": { + "domain": "localhost", + "full": "http://localhost:5678/pattern?r=200x1", + "path": "/pattern", + "port": 5678, + "query": "r=200x1", + "scheme": "http" + } +} diff --git a/x-pack/test/api_integration/apis/uptime/rest/index.ts b/x-pack/test/api_integration/apis/uptime/rest/index.ts index b76d3f7c2e44a..a86411f7c49ec 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/index.ts +++ b/x-pack/test/api_integration/apis/uptime/rest/index.ts @@ -9,8 +9,16 @@ import { FtrProviderContext } from '../../../ftr_provider_context'; export default function({ getService, loadTestFile }: FtrProviderContext) { const esArchiver = getService('esArchiver'); describe('uptime REST endpoints', () => { - before('load heartbeat data', () => esArchiver.load('uptime/full_heartbeat')); - after('unload', () => esArchiver.unload('uptime/full_heartbeat')); - loadTestFile(require.resolve('./snapshot')); + describe('with generated data', () => { + before('load heartbeat data', () => esArchiver.load('uptime/blank')); + after('unload', () => esArchiver.unload('uptime/blank')); + loadTestFile(require.resolve('./snapshot')); + }); + describe('with real-world data', () => { + before('load heartbeat data', () => esArchiver.load('uptime/full_heartbeat')); + after('unload', () => esArchiver.unload('uptime/full_heartbeat')); + loadTestFile(require.resolve('./monitor_latest_status')); + loadTestFile(require.resolve('./selected_monitor')); + }); }); } diff --git a/x-pack/test/api_integration/apis/uptime/rest/monitor_latest_status.ts b/x-pack/test/api_integration/apis/uptime/rest/monitor_latest_status.ts new file mode 100644 index 0000000000000..749b304c87ee3 --- /dev/null +++ b/x-pack/test/api_integration/apis/uptime/rest/monitor_latest_status.ts @@ -0,0 +1,25 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { expectFixtureEql } from '../graphql/helpers/expect_fixture_eql'; +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function({ getService }: FtrProviderContext) { + describe('get monitor latest status API', () => { + const dateStart = '2018-01-28T17:40:08.078Z'; + const dateEnd = '2025-01-28T19:00:16.078Z'; + const monitorId = '0002-up'; + + const supertest = getService('supertest'); + + it('returns the status for only the given monitor', async () => { + const apiResponse = await supertest.get( + `/api/uptime/monitor/status?monitorId=${monitorId}&dateStart=${dateStart}&dateEnd=${dateEnd}` + ); + expectFixtureEql(apiResponse.body, 'monitor_latest_status'); + }); + }); +} diff --git a/x-pack/test/api_integration/apis/uptime/rest/selected_monitor.ts b/x-pack/test/api_integration/apis/uptime/rest/selected_monitor.ts new file mode 100644 index 0000000000000..ed034f58a5f59 --- /dev/null +++ b/x-pack/test/api_integration/apis/uptime/rest/selected_monitor.ts @@ -0,0 +1,23 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { expectFixtureEql } from '../graphql/helpers/expect_fixture_eql'; +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function({ getService }: FtrProviderContext) { + describe('get selected monitor by ID', () => { + const monitorId = '0002-up'; + + const supertest = getService('supertest'); + + it('returns the monitor for give ID', async () => { + const apiResponse = await supertest.get( + `/api/uptime/monitor/selected?monitorId=${monitorId}` + ); + expectFixtureEql(apiResponse.body, 'selected_monitor'); + }); + }); +} diff --git a/x-pack/test/api_integration/apis/uptime/rest/snapshot.ts b/x-pack/test/api_integration/apis/uptime/rest/snapshot.ts index 0175dc649b495..b0d97837c770f 100644 --- a/x-pack/test/api_integration/apis/uptime/rest/snapshot.ts +++ b/x-pack/test/api_integration/apis/uptime/rest/snapshot.ts @@ -6,47 +6,97 @@ import { expectFixtureEql } from '../graphql/helpers/expect_fixture_eql'; import { FtrProviderContext } from '../../../ftr_provider_context'; +import { makeChecksWithStatus, getChecksDateRange } from '../graphql/helpers/make_checks'; export default function({ getService }: FtrProviderContext) { const supertest = getService('supertest'); describe('snapshot count', () => { - let dateRangeStart = '2019-01-28T17:40:08.078Z'; - let dateRangeEnd = '2025-01-28T19:00:16.078Z'; - - it('will fetch the full set of snapshot counts', async () => { - const apiResponse = await supertest.get( - `/api/uptime/snapshot/count?dateRangeStart=${dateRangeStart}&dateRangeEnd=${dateRangeEnd}` - ); - expectFixtureEql(apiResponse.body, 'snapshot'); - }); + const dateRangeStart = new Date().toISOString(); + const dateRangeEnd = new Date().toISOString(); - it('will fetch a monitor snapshot filtered by down status', async () => { - const filters = `{"bool":{"must":[{"match":{"monitor.status":{"query":"down","operator":"and"}}}]}}`; - const statusFilter = 'down'; - const apiResponse = await supertest.get( - `/api/uptime/snapshot/count?dateRangeStart=${dateRangeStart}&dateRangeEnd=${dateRangeEnd}&filters=${filters}&statusFilter=${statusFilter}` - ); - expectFixtureEql(apiResponse.body, 'snapshot_filtered_by_down'); - }); + describe('when no data is present', async () => { + it('returns a null snapshot', async () => { + const apiResponse = await supertest.get( + `/api/uptime/snapshot/count?dateRangeStart=${dateRangeStart}&dateRangeEnd=${dateRangeEnd}` + ); - it('will fetch a monitor snapshot filtered by up status', async () => { - const filters = `{"bool":{"must":[{"match":{"monitor.status":{"query":"up","operator":"and"}}}]}}`; - const statusFilter = 'up'; - const apiResponse = await supertest.get( - `/api/uptime/snapshot/count?dateRangeStart=${dateRangeStart}&dateRangeEnd=${dateRangeEnd}&filters=${filters}&statusFilter=${statusFilter}` - ); - expectFixtureEql(apiResponse.body, 'snapshot_filtered_by_up'); + expectFixtureEql(apiResponse.body, 'snapshot_empty'); + }); }); - it('returns a null snapshot when no data is present', async () => { - dateRangeStart = '2019-01-25T04:30:54.740Z'; - dateRangeEnd = '2025-01-28T04:50:54.740Z'; - const filters = `{"bool":{"must":[{"match":{"monitor.status":{"query":"down","operator":"and"}}}]}}`; - const apiResponse = await supertest.get( - `/api/uptime/snapshot/count?dateRangeStart=${dateRangeStart}&dateRangeEnd=${dateRangeEnd}&filters=${filters}` - ); - expectFixtureEql(apiResponse.body, 'snapshot_empty'); + describe('when data is present', async () => { + const numUpMonitors = 10; + const numDownMonitors = 7; + const numIps = 2; + const checksPerMonitor = 5; + const scheduleEvery = 10000; // fake monitor checks every 10s + let dateRange: { start: string; end: string }; + + [true, false].forEach(async (includeTimespan: boolean) => { + describe(`with timespans ${includeTimespan ? 'included' : 'missing'}`, async () => { + before(async () => { + const promises: Array> = []; + + // When includeTimespan is false we have to remove the values there. + let mogrify = (d: any) => d; + if ((includeTimespan = false)) { + mogrify = (d: any): any => { + d.monitor.delete('timespan'); + return d; + }; + } + + const makeMonitorChecks = async (monitorId: string, status: 'up' | 'down') => { + return makeChecksWithStatus( + getService('legacyEs'), + monitorId, + checksPerMonitor, + numIps, + scheduleEvery, + {}, + status, + mogrify + ); + }; + + for (let i = 0; i < numUpMonitors; i++) { + promises.push(makeMonitorChecks(`up-${i}`, 'up')); + } + for (let i = 0; i < numDownMonitors; i++) { + promises.push(makeMonitorChecks(`down-${i}`, 'down')); + } + + const allResults = await Promise.all(promises); + dateRange = getChecksDateRange(allResults); + }); + + it('will count all statuses correctly', async () => { + const apiResponse = await supertest.get( + `/api/uptime/snapshot/count?dateRangeStart=${dateRange.start}&dateRangeEnd=${dateRange.end}` + ); + + expectFixtureEql(apiResponse.body, 'snapshot'); + }); + + it('will fetch a monitor snapshot filtered by down status', async () => { + const statusFilter = 'down'; + const apiResponse = await supertest.get( + `/api/uptime/snapshot/count?dateRangeStart=${dateRange.start}&dateRangeEnd=${dateRange.end}&statusFilter=${statusFilter}` + ); + + expectFixtureEql(apiResponse.body, 'snapshot_filtered_by_down'); + }); + + it('will fetch a monitor snapshot filtered by up status', async () => { + const statusFilter = 'up'; + const apiResponse = await supertest.get( + `/api/uptime/snapshot/count?dateRangeStart=${dateRange.start}&dateRangeEnd=${dateRange.end}&statusFilter=${statusFilter}` + ); + expectFixtureEql(apiResponse.body, 'snapshot_filtered_by_up'); + }); + }); + }); }); }); } diff --git a/x-pack/test/functional/apps/advanced_settings/feature_controls/advanced_settings_security.ts b/x-pack/test/functional/apps/advanced_settings/feature_controls/advanced_settings_security.ts index f148d62421ff8..ad4f81777e780 100644 --- a/x-pack/test/functional/apps/advanced_settings/feature_controls/advanced_settings_security.ts +++ b/x-pack/test/functional/apps/advanced_settings/feature_controls/advanced_settings_security.ts @@ -67,9 +67,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { }); it('shows management navlink', async () => { - const navLinks = (await appsMenu.readLinks()).map( - (link: Record) => link.text - ); + const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).to.eql(['Management']); }); @@ -125,9 +123,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { }); it('shows Management navlink', async () => { - const navLinks = (await appsMenu.readLinks()).map( - (link: Record) => link.text - ); + const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).to.eql(['Management']); }); @@ -178,9 +174,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { }); it('shows Management navlink', async () => { - const navLinks = (await appsMenu.readLinks()).map( - (link: Record) => link.text - ); + const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).to.eql(['Discover', 'Management']); }); diff --git a/x-pack/test/functional/apps/advanced_settings/feature_controls/advanced_settings_spaces.ts b/x-pack/test/functional/apps/advanced_settings/feature_controls/advanced_settings_spaces.ts index 9ac6d4fdef19f..ee58be76928b3 100644 --- a/x-pack/test/functional/apps/advanced_settings/feature_controls/advanced_settings_spaces.ts +++ b/x-pack/test/functional/apps/advanced_settings/feature_controls/advanced_settings_spaces.ts @@ -40,9 +40,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { await PageObjects.common.navigateToApp('home', { basePath: '/s/custom_space', }); - const navLinks = (await appsMenu.readLinks()).map( - (link: Record) => link.text - ); + const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).to.contain('Management'); }); diff --git a/x-pack/test/functional/apps/apm/feature_controls/apm_security.ts b/x-pack/test/functional/apps/apm/feature_controls/apm_security.ts index 9190e0b4886ce..e2d5efac4644c 100644 --- a/x-pack/test/functional/apps/apm/feature_controls/apm_security.ts +++ b/x-pack/test/functional/apps/apm/feature_controls/apm_security.ts @@ -60,10 +60,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { it('shows apm navlink', async () => { const navLinks = await appsMenu.readLinks(); - expect(navLinks.map((link: Record) => link.text)).to.eql([ - 'APM', - 'Management', - ]); + expect(navLinks.map(link => link.text)).to.eql(['APM', 'Management']); }); it('can navigate to APM app', async () => { @@ -111,9 +108,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { }); it('shows apm navlink', async () => { - const navLinks = (await appsMenu.readLinks()).map( - (link: Record) => link.text - ); + const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).to.eql(['APM', 'Management']); }); @@ -166,9 +161,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { }); it(`doesn't show APM navlink`, async () => { - const navLinks = (await appsMenu.readLinks()).map( - (link: Record) => link.text - ); + const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).not.to.contain('APM'); }); diff --git a/x-pack/test/functional/apps/apm/feature_controls/apm_spaces.ts b/x-pack/test/functional/apps/apm/feature_controls/apm_spaces.ts index 191ba5c4d1e25..1ac1784e0e05d 100644 --- a/x-pack/test/functional/apps/apm/feature_controls/apm_spaces.ts +++ b/x-pack/test/functional/apps/apm/feature_controls/apm_spaces.ts @@ -30,9 +30,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { await PageObjects.common.navigateToApp('home', { basePath: '/s/custom_space', }); - const navLinks = (await appsMenu.readLinks()).map( - (link: Record) => link.text - ); + const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).to.contain('APM'); }); @@ -61,9 +59,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { await PageObjects.common.navigateToApp('home', { basePath: '/s/custom_space', }); - const navLinks = (await appsMenu.readLinks()).map( - (link: Record) => link.text - ); + const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).not.to.contain('APM'); }); diff --git a/x-pack/test/functional/apps/canvas/expression.ts b/x-pack/test/functional/apps/canvas/expression.ts new file mode 100644 index 0000000000000..fc6b80468b9f2 --- /dev/null +++ b/x-pack/test/functional/apps/canvas/expression.ts @@ -0,0 +1,70 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; + +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default function canvasExpressionTest({ getService, getPageObjects }: FtrProviderContext) { + const esArchiver = getService('esArchiver'); + const testSubjects = getService('testSubjects'); + // const browser = getService('browser'); + const retry = getService('retry'); + const PageObjects = getPageObjects(['canvas', 'common']); + const find = getService('find'); + + describe('expression editor', function() { + // there is an issue with FF not properly clicking on workpad elements + this.tags('skipFirefox'); + + before(async () => { + // init data + await esArchiver.loadIfNeeded('logstash_functional'); + await esArchiver.load('canvas/default'); + + // load test workpad + await PageObjects.common.navigateToApp('canvas', { + hash: '/workpad/workpad-1705f884-6224-47de-ba49-ca224fe6ec31/page/1', + }); + }); + + it('updates when element is changed via side bar', async () => { + // wait for all our elements to load up + await retry.try(async () => { + const elements = await testSubjects.findAll( + 'canvasWorkpadPage > canvasWorkpadPageElementContent' + ); + expect(elements).to.have.length(4); + }); + + // find the first workpad element (a markdown element) and click it to select it + await testSubjects.click('canvasWorkpadPage > canvasWorkpadPageElementContent', 20000); + + // open the expression editor + await PageObjects.canvas.openExpressionEditor(); + + // select markdown content and clear it + const mdBox = await find.byCssSelector('.canvasSidebar__panel .canvasTextArea__code'); + const oldMd = await mdBox.getVisibleText(); + await mdBox.clearValueWithKeyboard(); + + // type the new text + const newMd = `${oldMd} and this is a test`; + await mdBox.type(newMd); + await find.clickByCssSelector('.canvasArg--controls .euiButton'); + + // make sure the open expression editor also has the changes + const editor = await find.byCssSelector('.monaco-editor .view-lines'); + const editorText = await editor.getVisibleText(); + expect(editorText).to.contain('Orange: Timelion, Server function and this is a test'); + + // reset the markdown + await mdBox.clearValueWithKeyboard(); + await mdBox.type(oldMd); + await find.clickByCssSelector('.canvasArg--controls .euiButton'); + }); + }); +} diff --git a/x-pack/test/functional/apps/canvas/feature_controls/canvas_security.ts b/x-pack/test/functional/apps/canvas/feature_controls/canvas_security.ts index a58eb61ec4ca2..d0e37ec8e3f35 100644 --- a/x-pack/test/functional/apps/canvas/feature_controls/canvas_security.ts +++ b/x-pack/test/functional/apps/canvas/feature_controls/canvas_security.ts @@ -65,9 +65,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { }); it('shows canvas navlink', async () => { - const navLinks = (await appsMenu.readLinks()).map( - (link: Record) => link.text - ); + const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).to.eql(['Canvas', 'Management']); }); @@ -143,9 +141,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { }); it('shows canvas navlink', async () => { - const navLinks = (await appsMenu.readLinks()).map( - (link: Record) => link.text - ); + const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).to.eql(['Canvas', 'Management']); }); diff --git a/x-pack/test/functional/apps/canvas/feature_controls/canvas_spaces.ts b/x-pack/test/functional/apps/canvas/feature_controls/canvas_spaces.ts index 5a6857901536f..28b572401892b 100644 --- a/x-pack/test/functional/apps/canvas/feature_controls/canvas_spaces.ts +++ b/x-pack/test/functional/apps/canvas/feature_controls/canvas_spaces.ts @@ -40,9 +40,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { await PageObjects.common.navigateToApp('home', { basePath: '/s/custom_space', }); - const navLinks = (await appsMenu.readLinks()).map( - (link: Record) => link.text - ); + const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).to.contain('Canvas'); }); @@ -98,9 +96,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { await PageObjects.common.navigateToApp('home', { basePath: '/s/custom_space', }); - const navLinks = (await appsMenu.readLinks()).map( - (link: Record) => link.text - ); + const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).not.to.contain('Canvas'); }); diff --git a/x-pack/test/functional/apps/canvas/index.js b/x-pack/test/functional/apps/canvas/index.js index bc33161cc4e97..fa4e362b6bc59 100644 --- a/x-pack/test/functional/apps/canvas/index.js +++ b/x-pack/test/functional/apps/canvas/index.js @@ -8,6 +8,7 @@ export default function canvasApp({ loadTestFile }) { describe('Canvas app', function canvasAppTestSuite() { this.tags('ciGroup2'); // CI requires tags ヽ(゜Q。)ノ? loadTestFile(require.resolve('./smoke_test')); + loadTestFile(require.resolve('./expression')); loadTestFile(require.resolve('./feature_controls/canvas_security')); loadTestFile(require.resolve('./feature_controls/canvas_spaces')); }); diff --git a/x-pack/test/functional/apps/dashboard/feature_controls/dashboard_security.ts b/x-pack/test/functional/apps/dashboard/feature_controls/dashboard_security.ts index c7a9764c6fb58..d25fae3c4894c 100644 --- a/x-pack/test/functional/apps/dashboard/feature_controls/dashboard_security.ts +++ b/x-pack/test/functional/apps/dashboard/feature_controls/dashboard_security.ts @@ -75,10 +75,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { it('shows dashboard navlink', async () => { const navLinks = await appsMenu.readLinks(); - expect(navLinks.map((link: Record) => link.text)).to.eql([ - 'Dashboard', - 'Management', - ]); + expect(navLinks.map(link => link.text)).to.eql(['Dashboard', 'Management']); }); it(`landing page shows "Create new Dashboard" button`, async () => { @@ -107,7 +104,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { shouldLoginIfPrompted: false, } ); - await testSubjects.existOrFail('emptyDashboardAddPanelButton', { timeout: 10000 }); + await testSubjects.existOrFail('emptyDashboardWidget', { timeout: 10000 }); }); it(`can view existing Dashboard`, async () => { @@ -255,9 +252,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { }); it('shows dashboard navlink', async () => { - const navLinks = (await appsMenu.readLinks()).map( - (link: Record) => link.text - ); + const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).to.eql(['Dashboard', 'Management']); }); diff --git a/x-pack/test/functional/apps/dashboard/feature_controls/dashboard_spaces.ts b/x-pack/test/functional/apps/dashboard/feature_controls/dashboard_spaces.ts index 127141b156cd8..ebe08a60c2563 100644 --- a/x-pack/test/functional/apps/dashboard/feature_controls/dashboard_spaces.ts +++ b/x-pack/test/functional/apps/dashboard/feature_controls/dashboard_spaces.ts @@ -43,9 +43,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { await PageObjects.common.navigateToApp('home', { basePath: '/s/custom_space', }); - const navLinks = (await appsMenu.readLinks()).map( - (link: Record) => link.text - ); + const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).to.contain('Dashboard'); }); @@ -73,7 +71,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { shouldLoginIfPrompted: false, } ); - await testSubjects.existOrFail('emptyDashboardAddPanelButton', { timeout: 10000 }); + await testSubjects.existOrFail('emptyDashboardWidget', { timeout: 10000 }); }); it(`can view existing Dashboard`, async () => { @@ -107,9 +105,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { await PageObjects.common.navigateToApp('home', { basePath: '/s/custom_space', }); - const navLinks = (await appsMenu.readLinks()).map( - (link: Record) => link.text - ); + const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).not.to.contain('Dashboard'); }); diff --git a/x-pack/test/functional/apps/dashboard_mode/dashboard_empty_screen.js b/x-pack/test/functional/apps/dashboard_mode/dashboard_empty_screen.js new file mode 100644 index 0000000000000..c90a0ae6d19fc --- /dev/null +++ b/x-pack/test/functional/apps/dashboard_mode/dashboard_empty_screen.js @@ -0,0 +1,68 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +export default function({ getPageObjects, getService }) { + const log = getService('log'); + const testSubjects = getService('testSubjects'); + const esArchiver = getService('esArchiver'); + const dashboardVisualizations = getService('dashboardVisualizations'); + const PageObjects = getPageObjects(['common', 'dashboard', 'visualize', 'lens']); + + describe('empty dashboard', function() { + before(async () => { + await esArchiver.loadIfNeeded('logstash_functional'); + await esArchiver.loadIfNeeded('lens/basic'); + await PageObjects.common.navigateToApp('dashboard'); + await PageObjects.dashboard.preserveCrossAppState(); + await PageObjects.dashboard.clickNewDashboard(); + }); + + after(async () => { + await PageObjects.dashboard.gotoDashboardLandingPage(); + }); + + async function createAndAddLens(title) { + log.debug(`createAndAddLens(${title})`); + const inViewMode = await PageObjects.dashboard.getIsInViewMode(); + if (inViewMode) { + await PageObjects.dashboard.switchToEditMode(); + } + await PageObjects.visualize.clickLensWidget(); + await PageObjects.lens.goToTimeRange(); + await PageObjects.lens.configureDimension({ + dimension: + '[data-test-subj="lnsXY_xDimensionPanel"] [data-test-subj="indexPattern-configure-dimension"]', + operation: 'date_histogram', + field: '@timestamp', + }); + + await PageObjects.lens.configureDimension({ + dimension: + '[data-test-subj="lnsXY_yDimensionPanel"] [data-test-subj="indexPattern-configure-dimension"]', + operation: 'avg', + field: 'bytes', + }); + + await PageObjects.lens.configureDimension({ + dimension: + '[data-test-subj="lnsXY_splitDimensionPanel"] [data-test-subj="indexPattern-configure-dimension"]', + operation: 'terms', + field: 'ip', + }); + await PageObjects.lens.save(title); + } + + it('adds Lens visualization to empty dashboard', async () => { + const title = 'Dashboard Test Lens'; + await testSubjects.exists('addVisualizationButton'); + await testSubjects.click('addVisualizationButton'); + await dashboardVisualizations.ensureNewVisualizationDialogIsShowing(); + await createAndAddLens(title); + await PageObjects.dashboard.waitForRenderComplete(); + await testSubjects.exists(`embeddablePanelHeading-${title}`); + }); + }); +} diff --git a/x-pack/test/functional/apps/dashboard_mode/index.js b/x-pack/test/functional/apps/dashboard_mode/index.js index 2a98634ba40d5..09b9717ea9f02 100644 --- a/x-pack/test/functional/apps/dashboard_mode/index.js +++ b/x-pack/test/functional/apps/dashboard_mode/index.js @@ -9,5 +9,6 @@ export default function({ loadTestFile }) { this.tags('ciGroup7'); loadTestFile(require.resolve('./dashboard_view_mode')); + loadTestFile(require.resolve('./dashboard_empty_screen')); }); } diff --git a/x-pack/test/functional/apps/dev_tools/feature_controls/dev_tools_security.ts b/x-pack/test/functional/apps/dev_tools/feature_controls/dev_tools_security.ts index fd7739e6930d0..494fd71ea6f34 100644 --- a/x-pack/test/functional/apps/dev_tools/feature_controls/dev_tools_security.ts +++ b/x-pack/test/functional/apps/dev_tools/feature_controls/dev_tools_security.ts @@ -63,10 +63,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { it('shows Dev Tools navlink', async () => { const navLinks = await appsMenu.readLinks(); - expect(navLinks.map((link: Record) => link.text)).to.eql([ - 'Dev Tools', - 'Management', - ]); + expect(navLinks.map(link => link.text)).to.eql(['Dev Tools', 'Management']); }); describe('console', () => { @@ -146,9 +143,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { }); it(`shows 'Dev Tools' navlink`, async () => { - const navLinks = (await appsMenu.readLinks()).map( - (link: Record) => link.text - ); + const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).to.eql(['Dev Tools', 'Management']); }); diff --git a/x-pack/test/functional/apps/dev_tools/feature_controls/dev_tools_spaces.ts b/x-pack/test/functional/apps/dev_tools/feature_controls/dev_tools_spaces.ts index e3bc3a1c6ce11..4184d223a9686 100644 --- a/x-pack/test/functional/apps/dev_tools/feature_controls/dev_tools_spaces.ts +++ b/x-pack/test/functional/apps/dev_tools/feature_controls/dev_tools_spaces.ts @@ -40,9 +40,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { await PageObjects.common.navigateToApp('home', { basePath: '/s/custom_space', }); - const navLinks = (await appsMenu.readLinks()).map( - (link: Record) => link.text - ); + const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).to.contain('Dev Tools'); }); @@ -79,9 +77,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { await PageObjects.common.navigateToApp('home', { basePath: '/s/custom_space', }); - const navLinks = (await appsMenu.readLinks()).map( - (link: Record) => link.text - ); + const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).not.to.contain('Dev Tools'); }); diff --git a/x-pack/test/functional/apps/discover/feature_controls/discover_security.ts b/x-pack/test/functional/apps/discover/feature_controls/discover_security.ts index 553ce459ebb18..1912b16d96f36 100644 --- a/x-pack/test/functional/apps/discover/feature_controls/discover_security.ts +++ b/x-pack/test/functional/apps/discover/feature_controls/discover_security.ts @@ -81,10 +81,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { it('shows discover navlink', async () => { const navLinks = await appsMenu.readLinks(); - expect(navLinks.map((link: Record) => link.text)).to.eql([ - 'Discover', - 'Management', - ]); + expect(navLinks.map(link => link.text)).to.eql(['Discover', 'Management']); }); it('shows save button', async () => { @@ -170,9 +167,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { }); it('shows discover navlink', async () => { - const navLinks = (await appsMenu.readLinks()).map( - (link: Record) => link.text - ); + const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).to.eql(['Discover', 'Management']); }); diff --git a/x-pack/test/functional/apps/discover/feature_controls/discover_spaces.ts b/x-pack/test/functional/apps/discover/feature_controls/discover_spaces.ts index 3e5dcd7b0c987..e6b6f28f8b92f 100644 --- a/x-pack/test/functional/apps/discover/feature_controls/discover_spaces.ts +++ b/x-pack/test/functional/apps/discover/feature_controls/discover_spaces.ts @@ -49,9 +49,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { await PageObjects.common.navigateToApp('home', { basePath: '/s/custom_space', }); - const navLinks = (await appsMenu.readLinks()).map( - (link: Record) => link.text - ); + const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).to.contain('Discover'); }); @@ -93,9 +91,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { await PageObjects.common.navigateToApp('home', { basePath: '/s/custom_space', }); - const navLinks = (await appsMenu.readLinks()).map( - (link: Record) => link.text - ); + const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).not.to.contain('Discover'); }); diff --git a/x-pack/test/functional/apps/endpoint/feature_controls/endpoint_spaces.ts b/x-pack/test/functional/apps/endpoint/feature_controls/endpoint_spaces.ts index 1d1fb566eb075..d8eb969b99b3b 100644 --- a/x-pack/test/functional/apps/endpoint/feature_controls/endpoint_spaces.ts +++ b/x-pack/test/functional/apps/endpoint/feature_controls/endpoint_spaces.ts @@ -30,9 +30,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { await pageObjects.common.navigateToApp('home', { basePath: '/s/custom_space', }); - const navLinks = (await appsMenu.readLinks()).map( - (link: Record) => link.text - ); + const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).to.contain('EEndpoint'); }); @@ -70,9 +68,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { await pageObjects.common.navigateToApp('home', { basePath: '/s/custom_space', }); - const navLinks = (await appsMenu.readLinks()).map( - (link: Record) => link.text - ); + const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).not.to.contain('EEndpoint'); }); }); diff --git a/x-pack/test/functional/apps/graph/feature_controls/graph_security.ts b/x-pack/test/functional/apps/graph/feature_controls/graph_security.ts index acc8943033a1a..a2b062e6ef84f 100644 --- a/x-pack/test/functional/apps/graph/feature_controls/graph_security.ts +++ b/x-pack/test/functional/apps/graph/feature_controls/graph_security.ts @@ -64,10 +64,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { it('shows graph navlink', async () => { const navLinks = await appsMenu.readLinks(); - expect(navLinks.map((link: Record) => link.text)).to.eql([ - 'Graph', - 'Management', - ]); + expect(navLinks.map(link => link.text)).to.eql(['Graph', 'Management']); }); it('landing page shows "Create new graph" button', async () => { @@ -129,9 +126,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { }); it('shows graph navlink', async () => { - const navLinks = (await appsMenu.readLinks()).map( - (link: Record) => link.text - ); + const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).to.eql(['Graph', 'Management']); }); @@ -183,9 +178,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { }); it(`doesn't show graph navlink`, async () => { - const navLinks = (await appsMenu.readLinks()).map( - (link: Record) => link.text - ); + const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).not.to.contain('Graph'); }); diff --git a/x-pack/test/functional/apps/graph/feature_controls/graph_spaces.ts b/x-pack/test/functional/apps/graph/feature_controls/graph_spaces.ts index 0945b35ba0930..a0b0d5bef9668 100644 --- a/x-pack/test/functional/apps/graph/feature_controls/graph_spaces.ts +++ b/x-pack/test/functional/apps/graph/feature_controls/graph_spaces.ts @@ -34,9 +34,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { await PageObjects.common.navigateToApp('home', { basePath: '/s/custom_space', }); - const navLinks = (await appsMenu.readLinks()).map( - (link: Record) => link.text - ); + const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).to.contain('Graph'); }); @@ -75,9 +73,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { await PageObjects.common.navigateToApp('home', { basePath: '/s/custom_space', }); - const navLinks = (await appsMenu.readLinks()).map( - (link: Record) => link.text - ); + const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).not.to.contain('Graph'); }); diff --git a/x-pack/test/functional/apps/index_patterns/feature_controls/index_patterns_security.ts b/x-pack/test/functional/apps/index_patterns/feature_controls/index_patterns_security.ts index 4929bb52c170c..30cdc95b38e62 100644 --- a/x-pack/test/functional/apps/index_patterns/feature_controls/index_patterns_security.ts +++ b/x-pack/test/functional/apps/index_patterns/feature_controls/index_patterns_security.ts @@ -69,9 +69,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { }); it('shows management navlink', async () => { - const navLinks = (await appsMenu.readLinks()).map( - (link: Record) => link.text - ); + const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).to.eql(['Management']); }); @@ -125,9 +123,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { }); it('shows management navlink', async () => { - const navLinks = (await appsMenu.readLinks()).map( - (link: Record) => link.text - ); + const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).to.eql(['Management']); }); @@ -179,9 +175,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { }); it('shows Management navlink', async () => { - const navLinks = (await appsMenu.readLinks()).map( - (link: Record) => link.text - ); + const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).to.eql(['Discover', 'Management']); }); diff --git a/x-pack/test/functional/apps/index_patterns/feature_controls/index_patterns_spaces.ts b/x-pack/test/functional/apps/index_patterns/feature_controls/index_patterns_spaces.ts index bc8542288410c..6a2b77de17f45 100644 --- a/x-pack/test/functional/apps/index_patterns/feature_controls/index_patterns_spaces.ts +++ b/x-pack/test/functional/apps/index_patterns/feature_controls/index_patterns_spaces.ts @@ -40,9 +40,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { await PageObjects.common.navigateToApp('home', { basePath: '/s/custom_space', }); - const navLinks = (await appsMenu.readLinks()).map( - (link: Record) => link.text - ); + const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).to.contain('Management'); }); diff --git a/x-pack/test/functional/apps/infra/feature_controls/infrastructure_security.ts b/x-pack/test/functional/apps/infra/feature_controls/infrastructure_security.ts index 4d61e0996419c..5062f094061c0 100644 --- a/x-pack/test/functional/apps/infra/feature_controls/infrastructure_security.ts +++ b/x-pack/test/functional/apps/infra/feature_controls/infrastructure_security.ts @@ -60,9 +60,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { }); it('shows metrics navlink', async () => { - const navLinks = (await appsMenu.readLinks()).map( - (link: Record) => link.text - ); + const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).to.eql(['Metrics', 'Management']); }); @@ -175,9 +173,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { }); it('shows metrics navlink', async () => { - const navLinks = (await appsMenu.readLinks()).map( - (link: Record) => link.text - ); + const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).to.eql(['Metrics', 'Management']); }); @@ -417,9 +413,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { }); it(`doesn't show metrics navlink`, async () => { - const navLinks = (await appsMenu.readLinks()).map( - (link: Record) => link.text - ); + const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).to.not.contain(['Metrics']); }); diff --git a/x-pack/test/functional/apps/infra/feature_controls/infrastructure_spaces.ts b/x-pack/test/functional/apps/infra/feature_controls/infrastructure_spaces.ts index 300b22e5bcbc3..7c2a11a542d66 100644 --- a/x-pack/test/functional/apps/infra/feature_controls/infrastructure_spaces.ts +++ b/x-pack/test/functional/apps/infra/feature_controls/infrastructure_spaces.ts @@ -48,9 +48,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { await PageObjects.common.navigateToApp('home', { basePath: '/s/custom_space', }); - const navLinks = (await appsMenu.readLinks()).map( - (link: Record) => link.text - ); + const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).to.contain('Metrics'); }); @@ -101,9 +99,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { await PageObjects.common.navigateToApp('home', { basePath: '/s/custom_space', }); - const navLinks = (await appsMenu.readLinks()).map( - (link: Record) => link.text - ); + const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).not.to.contain('Metrics'); }); diff --git a/x-pack/test/functional/apps/infra/feature_controls/logs_security.ts b/x-pack/test/functional/apps/infra/feature_controls/logs_security.ts index d092e6736656e..b9634c29dac1c 100644 --- a/x-pack/test/functional/apps/infra/feature_controls/logs_security.ts +++ b/x-pack/test/functional/apps/infra/feature_controls/logs_security.ts @@ -57,9 +57,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { }); it('shows logs navlink', async () => { - const navLinks = (await appsMenu.readLinks()).map( - (link: Record) => link.text - ); + const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).to.eql(['Logs', 'Management']); }); @@ -122,9 +120,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { }); it('shows logs navlink', async () => { - const navLinks = (await appsMenu.readLinks()).map( - (link: Record) => link.text - ); + const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).to.eql(['Logs', 'Management']); }); @@ -187,9 +183,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { }); it(`doesn't show logs navlink`, async () => { - const navLinks = (await appsMenu.readLinks()).map( - (link: Record) => link.text - ); + const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).to.not.contain('Logs'); }); diff --git a/x-pack/test/functional/apps/infra/feature_controls/logs_spaces.ts b/x-pack/test/functional/apps/infra/feature_controls/logs_spaces.ts index 8230b25efbbf9..6b078d2cfa71a 100644 --- a/x-pack/test/functional/apps/infra/feature_controls/logs_spaces.ts +++ b/x-pack/test/functional/apps/infra/feature_controls/logs_spaces.ts @@ -36,9 +36,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { await PageObjects.common.navigateToApp('home', { basePath: '/s/custom_space', }); - const navLinks = (await appsMenu.readLinks()).map( - (link: Record) => link.text - ); + const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).to.contain('Logs'); }); @@ -77,9 +75,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { await PageObjects.common.navigateToApp('home', { basePath: '/s/custom_space', }); - const navLinks = (await appsMenu.readLinks()).map( - (link: Record) => link.text - ); + const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).to.not.contain('Logs'); }); diff --git a/x-pack/test/functional/apps/infra/link_to.ts b/x-pack/test/functional/apps/infra/link_to.ts index a68637600be8b..639b65ec5eca8 100644 --- a/x-pack/test/functional/apps/infra/link_to.ts +++ b/x-pack/test/functional/apps/infra/link_to.ts @@ -22,8 +22,8 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { state: undefined, }; const expectedSearchString = - "logFilter=(expression:'trace.id:433b4651687e18be2c6c8e3b11f53d09',kind:kuery)&logPosition=(position:(tiebreaker:0,time:1565707203194))&sourceId=default"; - const expectedRedirect = `/logs/stream?${expectedSearchString}`; + "logFilter=(expression:'trace.id:433b4651687e18be2c6c8e3b11f53d09',kind:kuery)&logPosition=(position:(tiebreaker:0,time:1565707203194),streamLive:!f)&sourceId=default"; + const expectedRedirectPath = '/logs/stream?'; await pageObjects.common.navigateToActualUrl( 'infraOps', @@ -32,7 +32,9 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { await retry.tryForTime(5000, async () => { const currentUrl = await browser.getCurrentUrl(); const [, currentHash] = decodeURIComponent(currentUrl).split('#'); - expect(currentHash).to.contain(expectedRedirect); + // Account for unpredictable location of the g parameter in the search string + expect(currentHash.slice(0, expectedRedirectPath.length)).to.be(expectedRedirectPath); + expect(currentHash.slice(expectedRedirectPath.length)).to.contain(expectedSearchString); }); }); }); diff --git a/x-pack/test/functional/apps/machine_learning/feature_controls/ml_security.ts b/x-pack/test/functional/apps/machine_learning/feature_controls/ml_security.ts index 8b2df502dc100..8fb6f21c778d3 100644 --- a/x-pack/test/functional/apps/machine_learning/feature_controls/ml_security.ts +++ b/x-pack/test/functional/apps/machine_learning/feature_controls/ml_security.ts @@ -80,9 +80,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { }); it(`doesn't show ml navlink`, async () => { - const navLinks = (await appsMenu.readLinks()).map( - (link: Record) => link.text - ); + const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).not.to.contain('Machine Learning'); }); }); @@ -103,9 +101,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { }); it('shows ML navlink', async () => { - const navLinks = (await appsMenu.readLinks()).map( - (link: Record) => link.text - ); + const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).to.contain('Machine Learning'); }); }); diff --git a/x-pack/test/functional/apps/machine_learning/feature_controls/ml_spaces.ts b/x-pack/test/functional/apps/machine_learning/feature_controls/ml_spaces.ts index 13036737218bc..fc94688e98811 100644 --- a/x-pack/test/functional/apps/machine_learning/feature_controls/ml_spaces.ts +++ b/x-pack/test/functional/apps/machine_learning/feature_controls/ml_spaces.ts @@ -39,9 +39,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { await PageObjects.common.navigateToApp('home', { basePath: '/s/custom_space', }); - const navLinks = (await appsMenu.readLinks()).map( - (link: Record) => link.text - ); + const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).to.contain('Machine Learning'); }); @@ -71,9 +69,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { await PageObjects.common.navigateToApp('home', { basePath: '/s/custom_space', }); - const navLinks = (await appsMenu.readLinks()).map( - (link: Record) => link.text - ); + const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).not.to.contain('Machine Learning'); }); diff --git a/x-pack/test/functional/apps/maps/feature_controls/maps_security.ts b/x-pack/test/functional/apps/maps/feature_controls/maps_security.ts index cf31f445a96f3..804ad5725edfd 100644 --- a/x-pack/test/functional/apps/maps/feature_controls/maps_security.ts +++ b/x-pack/test/functional/apps/maps/feature_controls/maps_security.ts @@ -65,9 +65,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { }); it('shows maps navlink', async () => { - const navLinks = (await appsMenu.readLinks()).map( - (link: Record) => link.text - ); + const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).to.eql(['Maps', 'Management']); }); @@ -154,9 +152,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { }); it('shows Maps navlink', async () => { - const navLinks = (await appsMenu.readLinks()).map( - (link: Record) => link.text - ); + const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).to.eql(['Maps', 'Management']); }); @@ -251,9 +247,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { }); it('does not show Maps navlink', async () => { - const navLinks = (await appsMenu.readLinks()).map( - (link: Record) => link.text - ); + const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).to.eql(['Discover', 'Management']); }); diff --git a/x-pack/test/functional/apps/maps/feature_controls/maps_spaces.ts b/x-pack/test/functional/apps/maps/feature_controls/maps_spaces.ts index 0c86b47b373e6..e157586aecead 100644 --- a/x-pack/test/functional/apps/maps/feature_controls/maps_spaces.ts +++ b/x-pack/test/functional/apps/maps/feature_controls/maps_spaces.ts @@ -42,9 +42,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { await PageObjects.common.navigateToApp('home', { basePath: '/s/custom_space', }); - const navLinks = (await appsMenu.readLinks()).map( - (link: Record) => link.text - ); + const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).to.contain('Maps'); }); diff --git a/x-pack/test/functional/apps/monitoring/feature_controls/monitoring_security.ts b/x-pack/test/functional/apps/monitoring/feature_controls/monitoring_security.ts index 8848df83d36d6..d985da42ab5ed 100644 --- a/x-pack/test/functional/apps/monitoring/feature_controls/monitoring_security.ts +++ b/x-pack/test/functional/apps/monitoring/feature_controls/monitoring_security.ts @@ -76,9 +76,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { }); it(`doesn't show monitoring navlink`, async () => { - const navLinks = (await appsMenu.readLinks()).map( - (link: Record) => link.text - ); + const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).not.to.contain('Stack Monitoring'); }); }); @@ -99,9 +97,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { }); it('shows monitoring navlink', async () => { - const navLinks = (await appsMenu.readLinks()).map( - (link: Record) => link.text - ); + const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).to.contain('Stack Monitoring'); }); }); diff --git a/x-pack/test/functional/apps/monitoring/feature_controls/monitoring_spaces.ts b/x-pack/test/functional/apps/monitoring/feature_controls/monitoring_spaces.ts index 80f33ff6175c5..7459b53ca4a32 100644 --- a/x-pack/test/functional/apps/monitoring/feature_controls/monitoring_spaces.ts +++ b/x-pack/test/functional/apps/monitoring/feature_controls/monitoring_spaces.ts @@ -41,9 +41,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { await PageObjects.common.navigateToApp('home', { basePath: '/s/custom_space', }); - const navLinks = (await appsMenu.readLinks()).map( - (link: Record) => link.text - ); + const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).to.contain('Stack Monitoring'); }); @@ -74,9 +72,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { await PageObjects.common.navigateToApp('home', { basePath: '/s/custom_space', }); - const navLinks = (await appsMenu.readLinks()).map( - (link: Record) => link.text - ); + const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).not.to.contain('Stack Monitoring'); }); diff --git a/x-pack/test/functional/apps/security/basic_license/index.ts b/x-pack/test/functional/apps/security/basic_license/index.ts new file mode 100644 index 0000000000000..0dbbd3988f8dd --- /dev/null +++ b/x-pack/test/functional/apps/security/basic_license/index.ts @@ -0,0 +1,15 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default function({ loadTestFile }: FtrProviderContext) { + describe('security app - basic license', function() { + this.tags('ciGroup4'); + + loadTestFile(require.resolve('./role_mappings')); + }); +} diff --git a/x-pack/test/functional/apps/security/basic_license/role_mappings.ts b/x-pack/test/functional/apps/security/basic_license/role_mappings.ts new file mode 100644 index 0000000000000..45b325d57bee0 --- /dev/null +++ b/x-pack/test/functional/apps/security/basic_license/role_mappings.ts @@ -0,0 +1,22 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { FtrProviderContext } from '../../../ftr_provider_context'; + +export default ({ getPageObjects, getService }: FtrProviderContext) => { + const pageObjects = getPageObjects(['common', 'roleMappings']); + const testSubjects = getService('testSubjects'); + + describe('Role Mappings', function() { + before(async () => { + await pageObjects.common.navigateToApp('settings'); + }); + + it('does not render the Role Mappings UI under the basic license', async () => { + await testSubjects.missingOrFail('roleMappings'); + }); + }); +}; diff --git a/x-pack/test/functional/apps/security/index.js b/x-pack/test/functional/apps/security/index.js index b5d9b5f14be97..827a702b92d85 100644 --- a/x-pack/test/functional/apps/security/index.js +++ b/x-pack/test/functional/apps/security/index.js @@ -16,5 +16,6 @@ export default function({ loadTestFile }) { loadTestFile(require.resolve('./field_level_security')); loadTestFile(require.resolve('./rbac_phase1')); loadTestFile(require.resolve('./user_email')); + loadTestFile(require.resolve('./role_mappings')); }); } diff --git a/x-pack/test/functional/apps/security/rbac_phase1.js b/x-pack/test/functional/apps/security/rbac_phase1.js index 361bb6dbbdae4..9309e23bdd762 100644 --- a/x-pack/test/functional/apps/security/rbac_phase1.js +++ b/x-pack/test/functional/apps/security/rbac_phase1.js @@ -7,7 +7,14 @@ import expect from '@kbn/expect'; import { indexBy } from 'lodash'; export default function({ getService, getPageObjects }) { - const PageObjects = getPageObjects(['security', 'settings', 'common', 'visualize', 'timePicker']); + const PageObjects = getPageObjects([ + 'security', + 'settings', + 'common', + 'visualize', + 'timePicker', + 'visChart', + ]); const log = getService('log'); const esArchiver = getService('esArchiver'); const browser = getService('browser'); @@ -110,7 +117,7 @@ export default function({ getService, getPageObjects }) { '"' ); await PageObjects.timePicker.setDefaultAbsoluteRange(); - await PageObjects.visualize.waitForVisualization(); + await PageObjects.visChart.waitForVisualization(); await PageObjects.visualize.saveVisualizationExpectSuccess(vizName1); await PageObjects.security.forceLogout(); }); diff --git a/x-pack/test/functional/apps/security/role_mappings.ts b/x-pack/test/functional/apps/security/role_mappings.ts new file mode 100644 index 0000000000000..5fed56ee79e3d --- /dev/null +++ b/x-pack/test/functional/apps/security/role_mappings.ts @@ -0,0 +1,168 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; +import { parse } from 'url'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default ({ getPageObjects, getService }: FtrProviderContext) => { + const pageObjects = getPageObjects(['common', 'roleMappings']); + const security = getService('security'); + const testSubjects = getService('testSubjects'); + const browser = getService('browser'); + const aceEditor = getService('aceEditor'); + + describe('Role Mappings', function() { + before(async () => { + await pageObjects.common.navigateToApp('roleMappings'); + }); + + it('displays a message when no role mappings exist', async () => { + await testSubjects.existOrFail('roleMappingsEmptyPrompt'); + await testSubjects.existOrFail('createRoleMappingButton'); + }); + + it('allows a role mapping to be created', async () => { + await testSubjects.click('createRoleMappingButton'); + await testSubjects.setValue('roleMappingFormNameInput', 'new_role_mapping'); + await testSubjects.setValue('roleMappingFormRoleComboBox', 'superuser'); + await browser.pressKeys(browser.keys.ENTER); + + await testSubjects.click('roleMappingsAddRuleButton'); + + await testSubjects.click('roleMappingsJSONRuleEditorButton'); + + await aceEditor.setValue( + 'roleMappingsJSONEditor', + JSON.stringify({ + all: [ + { + field: { + username: '*', + }, + }, + { + field: { + 'metadata.foo.bar': 'baz', + }, + }, + { + except: { + any: [ + { + field: { + dn: 'foo', + }, + }, + { + field: { + dn: 'bar', + }, + }, + ], + }, + }, + ], + }) + ); + + await testSubjects.click('roleMappingsVisualRuleEditorButton'); + + await testSubjects.click('saveRoleMappingButton'); + + await testSubjects.existOrFail('savedRoleMappingSuccessToast'); + }); + + it('allows a role mapping to be deleted', async () => { + await testSubjects.click(`deleteRoleMappingButton-new_role_mapping`); + await testSubjects.click('confirmModalConfirmButton'); + await testSubjects.existOrFail('deletedRoleMappingSuccessToast'); + }); + + it('displays an error and returns to the listing page when navigating to a role mapping which does not exist', async () => { + await pageObjects.common.navigateToActualUrl( + 'kibana', + '#/management/security/role_mappings/edit/i-do-not-exist', + { ensureCurrentUrl: false } + ); + + await testSubjects.existOrFail('errorLoadingRoleMappingEditorToast'); + + const url = parse(await browser.getCurrentUrl()); + + expect(url.hash).to.eql('#/management/security/role_mappings?_g=()'); + }); + + describe('with role mappings', () => { + const mappings = [ + { + name: 'a_enabled_role_mapping', + enabled: true, + roles: ['superuser'], + rules: { + field: { + username: '*', + }, + }, + metadata: {}, + }, + { + name: 'b_disabled_role_mapping', + enabled: false, + role_templates: [{ template: { source: 'superuser' } }], + rules: { + field: { + username: '*', + }, + }, + metadata: {}, + }, + ]; + + before(async () => { + await Promise.all( + mappings.map(mapping => { + const { name, ...payload } = mapping; + return security.roleMappings.create(name, payload); + }) + ); + + await pageObjects.common.navigateToApp('roleMappings'); + }); + + after(async () => { + await Promise.all(mappings.map(mapping => security.roleMappings.delete(mapping.name))); + }); + + it('displays a table of all role mappings', async () => { + const rows = await testSubjects.findAll('roleMappingRow'); + expect(rows.length).to.eql(mappings.length); + for (let i = 0; i < rows.length; i++) { + const row = rows[i]; + const mapping = mappings[i]; + + const name = await ( + await testSubjects.findDescendant('roleMappingName', row) + ).getVisibleText(); + + const enabled = + (await ( + await testSubjects.findDescendant('roleMappingEnabled', row) + ).getVisibleText()) === 'Enabled'; + + expect(name).to.eql(mapping.name); + expect(enabled).to.eql(mapping.enabled); + } + }); + + it('allows a role mapping to be edited', async () => { + await testSubjects.click('roleMappingName'); + await testSubjects.click('saveRoleMappingButton'); + await testSubjects.existOrFail('savedRoleMappingSuccessToast'); + }); + }); + }); +}; diff --git a/x-pack/test/functional/apps/snapshot_restore/home_page.ts b/x-pack/test/functional/apps/snapshot_restore/home_page.ts index 99d3ea7834e6b..608c7f321a08f 100644 --- a/x-pack/test/functional/apps/snapshot_restore/home_page.ts +++ b/x-pack/test/functional/apps/snapshot_restore/home_page.ts @@ -10,6 +10,7 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default ({ getPageObjects, getService }: FtrProviderContext) => { const pageObjects = getPageObjects(['common', 'snapshotRestore']); const log = getService('log'); + const es = getService('legacyEs'); describe('Home page', function() { this.tags('smoke'); @@ -26,5 +27,37 @@ export default ({ getPageObjects, getService }: FtrProviderContext) => { const repositoriesButton = await pageObjects.snapshotRestore.registerRepositoryButton(); expect(await repositoriesButton.isDisplayed()).to.be(true); }); + + describe('Repositories Tab', async () => { + before(async () => { + await es.snapshot.createRepository({ + repository: 'my-repository', + body: { + type: 'fs', + settings: { + location: '/tmp/es-backups/', + compress: true, + }, + }, + verify: true, + }); + await pageObjects.snapshotRestore.navToRepositories(); + }); + + it('cleanup repository', async () => { + await pageObjects.snapshotRestore.viewRepositoryDetails('my-repository'); + await pageObjects.common.sleep(25000); + const cleanupResponse = await pageObjects.snapshotRestore.performRepositoryCleanup(); + await pageObjects.common.sleep(25000); + expect(cleanupResponse).to.contain('results'); + expect(cleanupResponse).to.contain('deleted_bytes'); + expect(cleanupResponse).to.contain('deleted_blobs'); + }); + after(async () => { + await es.snapshot.deleteRepository({ + repository: 'my-repository', + }); + }); + }); }); }; diff --git a/x-pack/test/functional/apps/spaces/feature_controls/spaces_security.ts b/x-pack/test/functional/apps/spaces/feature_controls/spaces_security.ts index 46f0be1e6f6d6..1e79c76bf83e5 100644 --- a/x-pack/test/functional/apps/spaces/feature_controls/spaces_security.ts +++ b/x-pack/test/functional/apps/spaces/feature_controls/spaces_security.ts @@ -55,9 +55,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { }); it('shows management navlink', async () => { - const navLinks = (await appsMenu.readLinks()).map( - (link: Record) => link.text - ); + const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).to.contain('Management'); }); @@ -131,9 +129,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { }); it('shows management navlink', async () => { - const navLinks = (await appsMenu.readLinks()).map( - (link: Record) => link.text - ); + const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).to.contain('Management'); }); diff --git a/x-pack/test/functional/apps/timelion/feature_controls/timelion_security.ts b/x-pack/test/functional/apps/timelion/feature_controls/timelion_security.ts index 64fb218a62c80..dea45f161e451 100644 --- a/x-pack/test/functional/apps/timelion/feature_controls/timelion_security.ts +++ b/x-pack/test/functional/apps/timelion/feature_controls/timelion_security.ts @@ -59,9 +59,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { }); it('shows timelion navlink', async () => { - const navLinks = (await appsMenu.readLinks()).map( - (link: Record) => link.text - ); + const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).to.eql(['Timelion', 'Management']); }); @@ -113,9 +111,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { }); it('shows timelion navlink', async () => { - const navLinks = (await appsMenu.readLinks()).map( - (link: Record) => link.text - ); + const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).to.eql(['Timelion', 'Management']); }); diff --git a/x-pack/test/functional/apps/timelion/feature_controls/timelion_spaces.ts b/x-pack/test/functional/apps/timelion/feature_controls/timelion_spaces.ts index ea5e255071dad..fb203a23359bd 100644 --- a/x-pack/test/functional/apps/timelion/feature_controls/timelion_spaces.ts +++ b/x-pack/test/functional/apps/timelion/feature_controls/timelion_spaces.ts @@ -38,9 +38,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { await PageObjects.common.navigateToApp('home', { basePath: '/s/custom_space', }); - const navLinks = (await appsMenu.readLinks()).map( - (link: Record) => link.text - ); + const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).to.contain('Timelion'); }); @@ -71,9 +69,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { await PageObjects.common.navigateToApp('home', { basePath: '/s/custom_space', }); - const navLinks = (await appsMenu.readLinks()).map( - (link: Record) => link.text - ); + const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).not.to.contain('Timelion'); }); diff --git a/x-pack/test/functional/apps/uptime/feature_controls/uptime_security.ts b/x-pack/test/functional/apps/uptime/feature_controls/uptime_security.ts index c5a597cdaffb0..a004f8db66823 100644 --- a/x-pack/test/functional/apps/uptime/feature_controls/uptime_security.ts +++ b/x-pack/test/functional/apps/uptime/feature_controls/uptime_security.ts @@ -64,10 +64,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { it('shows uptime navlink', async () => { const navLinks = await appsMenu.readLinks(); - expect(navLinks.map((link: Record) => link.text)).to.eql([ - 'Uptime', - 'Management', - ]); + expect(navLinks.map(link => link.text)).to.eql(['Uptime', 'Management']); }); it('can navigate to Uptime app', async () => { @@ -117,9 +114,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { }); it('shows uptime navlink', async () => { - const navLinks = (await appsMenu.readLinks()).map( - (link: Record) => link.text - ); + const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).to.eql(['Uptime', 'Management']); }); @@ -170,9 +165,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { }); it(`doesn't show uptime navlink`, async () => { - const navLinks = (await appsMenu.readLinks()).map( - (link: Record) => link.text - ); + const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).not.to.contain('Uptime'); }); diff --git a/x-pack/test/functional/apps/uptime/feature_controls/uptime_spaces.ts b/x-pack/test/functional/apps/uptime/feature_controls/uptime_spaces.ts index 96bc3c5f74f59..77c5b323340bf 100644 --- a/x-pack/test/functional/apps/uptime/feature_controls/uptime_spaces.ts +++ b/x-pack/test/functional/apps/uptime/feature_controls/uptime_spaces.ts @@ -30,9 +30,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { await PageObjects.common.navigateToApp('home', { basePath: '/s/custom_space', }); - const navLinks = (await appsMenu.readLinks()).map( - (link: Record) => link.text - ); + const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).to.contain('Uptime'); }); @@ -59,9 +57,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { await PageObjects.common.navigateToApp('home', { basePath: '/s/custom_space', }); - const navLinks = (await appsMenu.readLinks()).map( - (link: Record) => link.text - ); + const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).not.to.contain('Uptime'); }); diff --git a/x-pack/test/functional/apps/uptime/overview.ts b/x-pack/test/functional/apps/uptime/overview.ts index 9a337c86185fe..bcfb72967b75a 100644 --- a/x-pack/test/functional/apps/uptime/overview.ts +++ b/x-pack/test/functional/apps/uptime/overview.ts @@ -29,6 +29,27 @@ export default ({ getPageObjects }: FtrProviderContext) => { await pageObjects.uptime.pageHasExpectedIds(['0000-intermittent']); }); + it('applies filters for multiple fields', async () => { + await pageObjects.uptime.goToUptimePageAndSetDateRange(DEFAULT_DATE_START, DEFAULT_DATE_END); + await pageObjects.uptime.selectFilterItems({ + location: ['mpls'], + port: ['5678'], + scheme: ['http'], + }); + await pageObjects.uptime.pageHasExpectedIds([ + '0000-intermittent', + '0001-up', + '0002-up', + '0003-up', + '0004-up', + '0005-up', + '0006-up', + '0007-up', + '0008-up', + '0009-up', + ]); + }); + it('pagination is cleared when filter criteria changes', async () => { await pageObjects.uptime.goToUptimePageAndSetDateRange(DEFAULT_DATE_START, DEFAULT_DATE_END); await pageObjects.uptime.changePage('next'); @@ -64,5 +85,27 @@ export default ({ getPageObjects }: FtrProviderContext) => { '0009-up', ]); }); + + describe('snapshot counts', () => { + it('updates the snapshot count when status filter is set to down', async () => { + await pageObjects.uptime.goToUptimePageAndSetDateRange( + DEFAULT_DATE_START, + DEFAULT_DATE_END + ); + await pageObjects.uptime.setStatusFilter('down'); + const counts = await pageObjects.uptime.getSnapshotCount(); + expect(counts).to.eql({ up: '0', down: '7' }); + }); + + it('updates the snapshot count when status filter is set to up', async () => { + await pageObjects.uptime.goToUptimePageAndSetDateRange( + DEFAULT_DATE_START, + DEFAULT_DATE_END + ); + await pageObjects.uptime.setStatusFilter('up'); + const counts = await pageObjects.uptime.getSnapshotCount(); + expect(counts).to.eql({ up: '93', down: '0' }); + }); + }); }); }; diff --git a/x-pack/test/functional/apps/visualize/feature_controls/visualize_security.ts b/x-pack/test/functional/apps/visualize/feature_controls/visualize_security.ts index 86fe606ecafad..d55076cb0ab43 100644 --- a/x-pack/test/functional/apps/visualize/feature_controls/visualize_security.ts +++ b/x-pack/test/functional/apps/visualize/feature_controls/visualize_security.ts @@ -74,9 +74,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { }); it('shows visualize navlink', async () => { - const navLinks = (await appsMenu.readLinks()).map( - (link: Record) => link.text - ); + const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).to.eql(['Visualize', 'Management']); }); @@ -190,9 +188,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { }); it('shows visualize navlink', async () => { - const navLinks = (await appsMenu.readLinks()).map( - (link: Record) => link.text - ); + const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).to.eql(['Visualize', 'Management']); }); diff --git a/x-pack/test/functional/apps/visualize/feature_controls/visualize_spaces.ts b/x-pack/test/functional/apps/visualize/feature_controls/visualize_spaces.ts index d0fdc7c95ea38..9193862d2ba9e 100644 --- a/x-pack/test/functional/apps/visualize/feature_controls/visualize_spaces.ts +++ b/x-pack/test/functional/apps/visualize/feature_controls/visualize_spaces.ts @@ -40,9 +40,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { await PageObjects.common.navigateToApp('home', { basePath: '/s/custom_space', }); - const navLinks = (await appsMenu.readLinks()).map( - (link: Record) => link.text - ); + const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).to.contain('Visualize'); }); @@ -81,9 +79,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { await PageObjects.common.navigateToApp('home', { basePath: '/s/custom_space', }); - const navLinks = (await appsMenu.readLinks()).map( - (link: Record) => link.text - ); + const navLinks = (await appsMenu.readLinks()).map(link => link.text); expect(navLinks).not.to.contain('Visualize'); }); diff --git a/x-pack/test/functional/apps/visualize/hybrid_visualization.ts b/x-pack/test/functional/apps/visualize/hybrid_visualization.ts index 03b6ed8e8e7c5..5ec6bd275133a 100644 --- a/x-pack/test/functional/apps/visualize/hybrid_visualization.ts +++ b/x-pack/test/functional/apps/visualize/hybrid_visualization.ts @@ -7,7 +7,7 @@ import { FtrProviderContext } from '../../ftr_provider_context'; export default function({ getPageObjects, getService }: FtrProviderContext) { const esArchiver = getService('esArchiver'); - const PageObjects = getPageObjects(['common', 'visualize', 'timePicker']); + const PageObjects = getPageObjects(['common', 'visualize', 'timePicker', 'visChart']); const inspector = getService('inspector'); describe('hybrid index pattern', () => { @@ -81,7 +81,7 @@ export default function({ getPageObjects, getService }: FtrProviderContext) { await PageObjects.common.navigateToApp('visualize'); await PageObjects.visualize.clickVisualizationByName('hybrid_histogram_line_chart'); await PageObjects.timePicker.setAbsoluteRange(fromTime, toTime); - await PageObjects.visualize.waitForVisualizationRenderingStabilized(); + await PageObjects.visChart.waitForVisualizationRenderingStabilized(); await inspector.open(); await inspector.setTablePageSize(50); await inspector.expectTableData(expectedData); diff --git a/x-pack/test/functional/config.js b/x-pack/test/functional/config.js index 17235c61c7d8c..664bfdf8d2a74 100644 --- a/x-pack/test/functional/config.js +++ b/x-pack/test/functional/config.js @@ -69,7 +69,7 @@ export default async function({ readConfigFile }) { esTestCluster: { license: 'trial', from: 'snapshot', - serverArgs: [], + serverArgs: ['path.repo=/tmp/'], }, kbnTestServer: { @@ -160,6 +160,10 @@ export default async function({ readConfigFile }) { ml: { pathname: '/app/ml', }, + roleMappings: { + pathname: '/app/kibana', + hash: '/management/security/role_mappings', + }, rollupJob: { pathname: '/app/kibana', hash: '/management/elasticsearch/rollup_jobs/', diff --git a/x-pack/test/functional/config_security_basic.js b/x-pack/test/functional/config_security_basic.js new file mode 100644 index 0000000000000..12d94e922a97c --- /dev/null +++ b/x-pack/test/functional/config_security_basic.js @@ -0,0 +1,76 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +/* eslint-disable import/no-default-export */ + +import { resolve } from 'path'; + +import { services } from './services'; +import { pageObjects } from './page_objects'; + +// the default export of config files must be a config provider +// that returns an object with the projects config values +export default async function({ readConfigFile }) { + const kibanaCommonConfig = await readConfigFile( + require.resolve('../../../test/common/config.js') + ); + const kibanaFunctionalConfig = await readConfigFile( + require.resolve('../../../test/functional/config.js') + ); + + return { + // list paths to the files that contain your plugins tests + testFiles: [resolve(__dirname, './apps/security/basic_license')], + + services, + pageObjects, + + servers: kibanaFunctionalConfig.get('servers'), + + esTestCluster: { + license: 'basic', + from: 'snapshot', + serverArgs: [], + }, + + kbnTestServer: { + ...kibanaCommonConfig.get('kbnTestServer'), + serverArgs: [ + ...kibanaCommonConfig.get('kbnTestServer.serverArgs'), + '--server.uuid=5b2de169-2785-441b-ae8c-186a1936b17d', + '--xpack.security.encryptionKey="wuGNaIhoMpk5sO4UBxgr3NyW1sFcLgIf"', // server restarts should not invalidate active sessions + '--telemetry.banner=false', + ], + }, + uiSettings: { + defaults: { + 'accessibility:disableAnimations': true, + 'dateFormat:tz': 'UTC', + }, + }, + // the apps section defines the urls that + // `PageObjects.common.navigateTo(appKey)` will use. + // Merge urls for your plugin with the urls defined in + // Kibana's config in order to use this helper + apps: { + ...kibanaFunctionalConfig.get('apps'), + }, + + // choose where esArchiver should load archives from + esArchiver: { + directory: resolve(__dirname, 'es_archives'), + }, + + // choose where screenshots should be saved + screenshots: { + directory: resolve(__dirname, 'screenshots'), + }, + + junit: { + reportName: 'Chrome X-Pack UI Functional Tests', + }, + }; +} diff --git a/x-pack/test/functional/es_archives/endpoint/endpoints/data.json.gz b/x-pack/test/functional/es_archives/endpoint/endpoints/data.json.gz new file mode 100644 index 0000000000000..fda46096e1ab2 Binary files /dev/null and b/x-pack/test/functional/es_archives/endpoint/endpoints/data.json.gz differ diff --git a/x-pack/test/functional/es_archives/endpoint/endpoints/mappings.json b/x-pack/test/functional/es_archives/endpoint/endpoints/mappings.json new file mode 100644 index 0000000000000..9544d05d70600 --- /dev/null +++ b/x-pack/test/functional/es_archives/endpoint/endpoints/mappings.json @@ -0,0 +1,104 @@ +{ + "type": "index", + "value": { + "aliases": { + }, + "index": "endpoint-agent", + "mappings": { + "properties": { + "created_at": { + "type": "date" + }, + "endpoint": { + "properties": { + "active_directory_distinguished_name": { + "type": "text" + }, + "active_directory_hostname": { + "type": "text" + }, + "domain": { + "type": "text" + }, + "is_base_image": { + "type": "boolean" + }, + "isolation": { + "properties": { + "status": { + "type": "boolean" + } + } + }, + "policy": { + "properties": { + "id": { + "ignore_above": 256, + "type": "keyword" + }, + "name": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "sensor": { + "properties": { + "persistence": { + "type": "boolean" + }, + "status": { + "type": "object" + } + } + }, + "upgrade": { + "type": "object" + } + } + }, + "host": { + "properties": { + "hostname": { + "type": "text" + }, + "ip": { + "ignore_above": 256, + "type": "keyword" + }, + "mac_address": { + "type": "text" + }, + "name": { + "type": "text" + }, + "os": { + "properties": { + "full": { + "type": "text" + }, + "name": { + "type": "text" + } + } + } + } + }, + "machine_id": { + "type": "keyword" + } + } + }, + "settings": { + "index": { + "number_of_replicas": "0", + "number_of_shards": "1" + } + } + } +} \ No newline at end of file diff --git a/x-pack/test/reporting/es_archives/bwc/6_2/data.json.gz b/x-pack/test/functional/es_archives/reporting/bwc/6_2/data.json.gz similarity index 100% rename from x-pack/test/reporting/es_archives/bwc/6_2/data.json.gz rename to x-pack/test/functional/es_archives/reporting/bwc/6_2/data.json.gz diff --git a/x-pack/test/reporting/es_archives/bwc/6_2/mappings.json b/x-pack/test/functional/es_archives/reporting/bwc/6_2/mappings.json similarity index 100% rename from x-pack/test/reporting/es_archives/bwc/6_2/mappings.json rename to x-pack/test/functional/es_archives/reporting/bwc/6_2/mappings.json diff --git a/x-pack/test/reporting/es_archives/bwc/6_3/data.json.gz b/x-pack/test/functional/es_archives/reporting/bwc/6_3/data.json.gz similarity index 100% rename from x-pack/test/reporting/es_archives/bwc/6_3/data.json.gz rename to x-pack/test/functional/es_archives/reporting/bwc/6_3/data.json.gz diff --git a/x-pack/test/reporting/es_archives/bwc/6_3/mappings.json b/x-pack/test/functional/es_archives/reporting/bwc/6_3/mappings.json similarity index 100% rename from x-pack/test/reporting/es_archives/bwc/6_3/mappings.json rename to x-pack/test/functional/es_archives/reporting/bwc/6_3/mappings.json diff --git a/x-pack/test/functional/es_archives/reporting/ecommerce/data.json.gz b/x-pack/test/functional/es_archives/reporting/ecommerce/data.json.gz new file mode 100644 index 0000000000000..b38981c03417e Binary files /dev/null and b/x-pack/test/functional/es_archives/reporting/ecommerce/data.json.gz differ diff --git a/x-pack/test/functional/es_archives/reporting/ecommerce/mappings.json b/x-pack/test/functional/es_archives/reporting/ecommerce/mappings.json new file mode 100644 index 0000000000000..9e3275bd40bfe --- /dev/null +++ b/x-pack/test/functional/es_archives/reporting/ecommerce/mappings.json @@ -0,0 +1,1226 @@ +{ + "type": "index", + "value": { + "aliases": { + }, + "index": "ecommerce", + "mappings": { + "properties": { + "category": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "currency": { + "type": "keyword" + }, + "customer_birth_date": { + "type": "date" + }, + "customer_first_name": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "customer_full_name": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "customer_gender": { + "type": "keyword" + }, + "customer_id": { + "type": "keyword" + }, + "customer_last_name": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "customer_phone": { + "type": "keyword" + }, + "day_of_week": { + "type": "keyword" + }, + "day_of_week_i": { + "type": "integer" + }, + "email": { + "type": "keyword" + }, + "geoip": { + "properties": { + "city_name": { + "type": "keyword" + }, + "continent_name": { + "type": "keyword" + }, + "country_iso_code": { + "type": "keyword" + }, + "location": { + "type": "geo_point" + }, + "region_name": { + "type": "keyword" + } + } + }, + "manufacturer": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "order_date": { + "type": "date" + }, + "order_id": { + "type": "keyword" + }, + "products": { + "properties": { + "_id": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "base_price": { + "type": "half_float" + }, + "base_unit_price": { + "type": "half_float" + }, + "category": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "created_on": { + "type": "date" + }, + "discount_amount": { + "type": "half_float" + }, + "discount_percentage": { + "type": "half_float" + }, + "manufacturer": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "min_price": { + "type": "half_float" + }, + "price": { + "type": "half_float" + }, + "product_id": { + "type": "long" + }, + "product_name": { + "analyzer": "english", + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + }, + "quantity": { + "type": "integer" + }, + "sku": { + "type": "keyword" + }, + "tax_amount": { + "type": "half_float" + }, + "taxful_price": { + "type": "half_float" + }, + "taxless_price": { + "type": "half_float" + }, + "unit_discount_amount": { + "type": "half_float" + } + } + }, + "sku": { + "type": "keyword" + }, + "taxful_total_price": { + "type": "half_float" + }, + "taxless_total_price": { + "type": "half_float" + }, + "total_quantity": { + "type": "integer" + }, + "total_unique_products": { + "type": "integer" + }, + "type": { + "type": "keyword" + }, + "user": { + "type": "keyword" + } + } + }, + "settings": { + "index": { + "number_of_replicas": "0", + "number_of_shards": "1" + } + } + } +} + +{ + "type": "index", + "value": { + "aliases": { + ".kibana": { + } + }, + "index": ".kibana_1", + "mappings": { + "_meta": { + "migrationMappingPropertyHashes": { + "apm-telemetry": "07ee1939fa4302c62ddc052ec03fed90", + "canvas-element": "7390014e1091044523666d97247392fc", + "canvas-workpad": "b0a1706d356228dbdcb4a17e6b9eb231", + "config": "87aca8fdb053154f11383fce3dbf3edf", + "dashboard": "d00f614b29a80360e1190193fd333bab", + "file-upload-telemetry": "0ed4d3e1983d1217a30982630897092e", + "graph-workspace": "cd7ba1330e6682e9cc00b78850874be1", + "index-pattern": "66eccb05066c5a89924f48a9e9736499", + "infrastructure-ui-source": "ddc0ecb18383f6b26101a2fadb2dab0c", + "kql-telemetry": "d12a98a6f19a2d273696597547e064ee", + "map": "23d7aa4a720d4938ccde3983f87bd58d", + "maps-telemetry": "a4229f8b16a6820c6d724b7e0c1f729d", + "migrationVersion": "4a1746014a75ade3a714e1db5763276f", + "ml-telemetry": "257fd1d4b4fdbb9cb4b8a3b27da201e9", + "namespace": "2f4316de49999235636386fe51dc06c1", + "query": "11aaeb7f5f7fa5bb43f25e18ce26e7d9", + "references": "7997cf5a56cc02bdc9c93361bde732b0", + "sample-data-telemetry": "7d3cfeb915303c9641c59681967ffeb4", + "search": "181661168bbadd1eff5902361e2a0d5c", + "server": "ec97f1c5da1a19609a60874e5af1100c", + "siem-ui-timeline": "1f6f0860ad7bc0dba3e42467ca40470d", + "siem-ui-timeline-note": "8874706eedc49059d4cf0f5094559084", + "siem-ui-timeline-pinned-event": "20638091112f0e14f0e443d512301c29", + "space": "25de8c2deec044392922989cfcf24c54", + "telemetry": "e1c8bc94e443aefd9458932cc0697a4d", + "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", + "type": "2f4316de49999235636386fe51dc06c1", + "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", + "updated_at": "00da57df13e94e9d98437d13ace4bfe0", + "upgrade-assistant-reindex-operation": "a53a20fe086b72c9a86da3cc12dad8a6", + "upgrade-assistant-telemetry": "56702cec857e0a9dacfb696655b4ff7b", + "url": "c7f66a0df8b1b52f17c28c4adb111105", + "visualization": "52d7a13ad68a150c4525b292d23e12cc" + } + }, + "dynamic": "strict", + "properties": { + "apm-telemetry": { + "properties": { + "has_any_services": { + "type": "boolean" + }, + "services_per_agent": { + "properties": { + "dotnet": { + "null_value": 0, + "type": "long" + }, + "go": { + "null_value": 0, + "type": "long" + }, + "java": { + "null_value": 0, + "type": "long" + }, + "js-base": { + "null_value": 0, + "type": "long" + }, + "nodejs": { + "null_value": 0, + "type": "long" + }, + "python": { + "null_value": 0, + "type": "long" + }, + "ruby": { + "null_value": 0, + "type": "long" + }, + "rum-js": { + "null_value": 0, + "type": "long" + } + } + } + } + }, + "canvas-element": { + "dynamic": "false", + "properties": { + "@created": { + "type": "date" + }, + "@timestamp": { + "type": "date" + }, + "content": { + "type": "text" + }, + "help": { + "type": "text" + }, + "image": { + "type": "text" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "canvas-workpad": { + "dynamic": "false", + "properties": { + "@created": { + "type": "date" + }, + "@timestamp": { + "type": "date" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "config": { + "dynamic": "true", + "properties": { + "buildNum": { + "type": "keyword" + }, + "dateFormat:tz": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "defaultIndex": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "dashboard": { + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "optionsJSON": { + "type": "text" + }, + "panelsJSON": { + "type": "text" + }, + "refreshInterval": { + "properties": { + "display": { + "type": "keyword" + }, + "pause": { + "type": "boolean" + }, + "section": { + "type": "integer" + }, + "value": { + "type": "integer" + } + } + }, + "timeFrom": { + "type": "keyword" + }, + "timeRestore": { + "type": "boolean" + }, + "timeTo": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "file-upload-telemetry": { + "properties": { + "filesUploadedTotalCount": { + "type": "long" + } + } + }, + "graph-workspace": { + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "numLinks": { + "type": "integer" + }, + "numVertices": { + "type": "integer" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + }, + "wsState": { + "type": "text" + } + } + }, + "index-pattern": { + "properties": { + "fieldFormatMap": { + "type": "text" + }, + "fields": { + "type": "text" + }, + "intervalName": { + "type": "keyword" + }, + "notExpandable": { + "type": "boolean" + }, + "sourceFilters": { + "type": "text" + }, + "timeFieldName": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "type": { + "type": "keyword" + }, + "typeMeta": { + "type": "keyword" + } + } + }, + "infrastructure-ui-source": { + "properties": { + "description": { + "type": "text" + }, + "fields": { + "properties": { + "container": { + "type": "keyword" + }, + "host": { + "type": "keyword" + }, + "pod": { + "type": "keyword" + }, + "tiebreaker": { + "type": "keyword" + }, + "timestamp": { + "type": "keyword" + } + } + }, + "logAlias": { + "type": "keyword" + }, + "logColumns": { + "properties": { + "fieldColumn": { + "properties": { + "field": { + "type": "keyword" + }, + "id": { + "type": "keyword" + } + } + }, + "messageColumn": { + "properties": { + "id": { + "type": "keyword" + } + } + }, + "timestampColumn": { + "properties": { + "id": { + "type": "keyword" + } + } + } + }, + "type": "nested" + }, + "metricAlias": { + "type": "keyword" + }, + "name": { + "type": "text" + } + } + }, + "kql-telemetry": { + "properties": { + "optInCount": { + "type": "long" + }, + "optOutCount": { + "type": "long" + } + } + }, + "map": { + "properties": { + "bounds": { + "type": "geo_shape" + }, + "description": { + "type": "text" + }, + "layerListJSON": { + "type": "text" + }, + "mapStateJSON": { + "type": "text" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "maps-telemetry": { + "properties": { + "attributesPerMap": { + "properties": { + "dataSourcesCount": { + "properties": { + "avg": { + "type": "long" + }, + "max": { + "type": "long" + }, + "min": { + "type": "long" + } + } + }, + "emsVectorLayersCount": { + "dynamic": "true", + "type": "object" + }, + "layerTypesCount": { + "dynamic": "true", + "type": "object" + }, + "layersCount": { + "properties": { + "avg": { + "type": "long" + }, + "max": { + "type": "long" + }, + "min": { + "type": "long" + } + } + } + } + }, + "mapsTotalCount": { + "type": "long" + }, + "timeCaptured": { + "type": "date" + } + } + }, + "migrationVersion": { + "dynamic": "true", + "properties": { + "index-pattern": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "space": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "ml-telemetry": { + "properties": { + "file_data_visualizer": { + "properties": { + "index_creation_count": { + "type": "long" + } + } + } + } + }, + "namespace": { + "type": "keyword" + }, + "query": { + "properties": { + "description": { + "type": "text" + }, + "filters": { + "enabled": false, + "type": "object" + }, + "query": { + "properties": { + "language": { + "type": "keyword" + }, + "query": { + "index": false, + "type": "keyword" + } + } + }, + "timefilter": { + "enabled": false, + "type": "object" + }, + "title": { + "type": "text" + } + } + }, + "references": { + "properties": { + "id": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "sample-data-telemetry": { + "properties": { + "installCount": { + "type": "long" + }, + "unInstallCount": { + "type": "long" + } + } + }, + "search": { + "properties": { + "columns": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "sort": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "server": { + "properties": { + "uuid": { + "type": "keyword" + } + } + }, + "siem-ui-timeline": { + "properties": { + "columns": { + "properties": { + "aggregatable": { + "type": "boolean" + }, + "category": { + "type": "keyword" + }, + "columnHeaderType": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "example": { + "type": "text" + }, + "id": { + "type": "keyword" + }, + "indexes": { + "type": "keyword" + }, + "name": { + "type": "text" + }, + "placeholder": { + "type": "text" + }, + "searchable": { + "type": "boolean" + }, + "type": { + "type": "keyword" + } + } + }, + "created": { + "type": "date" + }, + "createdBy": { + "type": "text" + }, + "dataProviders": { + "properties": { + "and": { + "properties": { + "enabled": { + "type": "boolean" + }, + "excluded": { + "type": "boolean" + }, + "id": { + "type": "keyword" + }, + "kqlQuery": { + "type": "text" + }, + "name": { + "type": "text" + }, + "queryMatch": { + "properties": { + "displayField": { + "type": "text" + }, + "displayValue": { + "type": "text" + }, + "field": { + "type": "text" + }, + "operator": { + "type": "text" + }, + "value": { + "type": "text" + } + } + } + } + }, + "enabled": { + "type": "boolean" + }, + "excluded": { + "type": "boolean" + }, + "id": { + "type": "keyword" + }, + "kqlQuery": { + "type": "text" + }, + "name": { + "type": "text" + }, + "queryMatch": { + "properties": { + "displayField": { + "type": "text" + }, + "displayValue": { + "type": "text" + }, + "field": { + "type": "text" + }, + "operator": { + "type": "text" + }, + "value": { + "type": "text" + } + } + } + } + }, + "dateRange": { + "properties": { + "end": { + "type": "date" + }, + "start": { + "type": "date" + } + } + }, + "description": { + "type": "text" + }, + "favorite": { + "properties": { + "favoriteDate": { + "type": "date" + }, + "fullName": { + "type": "text" + }, + "keySearch": { + "type": "text" + }, + "userName": { + "type": "text" + } + } + }, + "kqlMode": { + "type": "keyword" + }, + "kqlQuery": { + "properties": { + "filterQuery": { + "properties": { + "kuery": { + "properties": { + "expression": { + "type": "text" + }, + "kind": { + "type": "keyword" + } + } + }, + "serializedQuery": { + "type": "text" + } + } + } + } + }, + "sort": { + "properties": { + "columnId": { + "type": "keyword" + }, + "sortDirection": { + "type": "keyword" + } + } + }, + "title": { + "type": "text" + }, + "updated": { + "type": "date" + }, + "updatedBy": { + "type": "text" + } + } + }, + "siem-ui-timeline-note": { + "properties": { + "created": { + "type": "date" + }, + "createdBy": { + "type": "text" + }, + "eventId": { + "type": "keyword" + }, + "note": { + "type": "text" + }, + "timelineId": { + "type": "keyword" + }, + "updated": { + "type": "date" + }, + "updatedBy": { + "type": "text" + } + } + }, + "siem-ui-timeline-pinned-event": { + "properties": { + "created": { + "type": "date" + }, + "createdBy": { + "type": "text" + }, + "eventId": { + "type": "keyword" + }, + "timelineId": { + "type": "keyword" + }, + "updated": { + "type": "date" + }, + "updatedBy": { + "type": "text" + } + } + }, + "space": { + "properties": { + "_reserved": { + "type": "boolean" + }, + "color": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "disabledFeatures": { + "type": "keyword" + }, + "initials": { + "type": "keyword" + }, + "name": { + "fields": { + "keyword": { + "ignore_above": 2048, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "telemetry": { + "properties": { + "enabled": { + "type": "boolean" + } + } + }, + "timelion-sheet": { + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "timelion_chart_height": { + "type": "integer" + }, + "timelion_columns": { + "type": "integer" + }, + "timelion_interval": { + "type": "keyword" + }, + "timelion_other_interval": { + "type": "keyword" + }, + "timelion_rows": { + "type": "integer" + }, + "timelion_sheet": { + "type": "text" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "type": { + "type": "keyword" + }, + "ui-metric": { + "properties": { + "count": { + "type": "integer" + } + } + }, + "updated_at": { + "type": "date" + }, + "upgrade-assistant-reindex-operation": { + "dynamic": "true", + "properties": { + "indexName": { + "type": "keyword" + }, + "status": { + "type": "integer" + } + } + }, + "upgrade-assistant-telemetry": { + "properties": { + "features": { + "properties": { + "deprecation_logging": { + "properties": { + "enabled": { + "null_value": true, + "type": "boolean" + } + } + } + } + }, + "ui_open": { + "properties": { + "cluster": { + "null_value": 0, + "type": "long" + }, + "indices": { + "null_value": 0, + "type": "long" + }, + "overview": { + "null_value": 0, + "type": "long" + } + } + }, + "ui_reindex": { + "properties": { + "close": { + "null_value": 0, + "type": "long" + }, + "open": { + "null_value": 0, + "type": "long" + }, + "start": { + "null_value": 0, + "type": "long" + }, + "stop": { + "null_value": 0, + "type": "long" + } + } + } + } + }, + "url": { + "properties": { + "accessCount": { + "type": "long" + }, + "accessDate": { + "type": "date" + }, + "createDate": { + "type": "date" + }, + "url": { + "fields": { + "keyword": { + "ignore_above": 2048, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "visualization": { + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "savedSearchRefName": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + }, + "visState": { + "type": "text" + } + } + } + } + }, + "settings": { + "index": { + "auto_expand_replicas": "0-1", + "number_of_replicas": "0", + "number_of_shards": "1" + } + } + } +} \ No newline at end of file diff --git a/x-pack/test/functional/es_archives/reporting/ecommerce_kibana/data.json.gz b/x-pack/test/functional/es_archives/reporting/ecommerce_kibana/data.json.gz new file mode 100644 index 0000000000000..c2050b3399ab7 Binary files /dev/null and b/x-pack/test/functional/es_archives/reporting/ecommerce_kibana/data.json.gz differ diff --git a/x-pack/test/functional/es_archives/reporting/ecommerce_kibana/mappings.json b/x-pack/test/functional/es_archives/reporting/ecommerce_kibana/mappings.json new file mode 100644 index 0000000000000..cf647f5c53212 --- /dev/null +++ b/x-pack/test/functional/es_archives/reporting/ecommerce_kibana/mappings.json @@ -0,0 +1,1455 @@ +{ + "type": "index", + "value": { + "aliases": { + ".kibana": { + } + }, + "index": ".kibana_1", + "mappings": { + "_meta": { + "migrationMappingPropertyHashes": { + "action": "c0c235fba02ebd2a2412bcda79009b58", + "action_task_params": "a9d49f184ee89641044be0ca2950fa3a", + "alert": "d49f9b8d1277c6004506eec20dc0b108", + "apm-indices": "9bb9b2bf1fa636ed8619cbab5ce6a1dd", + "apm-services-telemetry": "07ee1939fa4302c62ddc052ec03fed90", + "canvas-element": "7390014e1091044523666d97247392fc", + "canvas-workpad": "b0a1706d356228dbdcb4a17e6b9eb231", + "config": "87aca8fdb053154f11383fce3dbf3edf", + "dashboard": "d00f614b29a80360e1190193fd333bab", + "file-upload-telemetry": "0ed4d3e1983d1217a30982630897092e", + "graph-workspace": "cd7ba1330e6682e9cc00b78850874be1", + "index-pattern": "66eccb05066c5a89924f48a9e9736499", + "infrastructure-ui-source": "ddc0ecb18383f6b26101a2fadb2dab0c", + "inventory-view": "84b320fd67209906333ffce261128462", + "kql-telemetry": "d12a98a6f19a2d273696597547e064ee", + "lens": "21c3ea0763beb1ecb0162529706b88c5", + "lens-ui-telemetry": "509bfa5978586998e05f9e303c07a327", + "map": "23d7aa4a720d4938ccde3983f87bd58d", + "maps-telemetry": "a4229f8b16a6820c6d724b7e0c1f729d", + "metrics-explorer-view": "53c5365793677328df0ccb6138bf3cdd", + "migrationVersion": "4a1746014a75ade3a714e1db5763276f", + "ml-telemetry": "257fd1d4b4fdbb9cb4b8a3b27da201e9", + "namespace": "2f4316de49999235636386fe51dc06c1", + "query": "11aaeb7f5f7fa5bb43f25e18ce26e7d9", + "references": "7997cf5a56cc02bdc9c93361bde732b0", + "sample-data-telemetry": "7d3cfeb915303c9641c59681967ffeb4", + "search": "181661168bbadd1eff5902361e2a0d5c", + "server": "ec97f1c5da1a19609a60874e5af1100c", + "siem-ui-timeline": "6485ab095be8d15246667b98a1a34295", + "siem-ui-timeline-note": "8874706eedc49059d4cf0f5094559084", + "siem-ui-timeline-pinned-event": "20638091112f0e14f0e443d512301c29", + "space": "c5ca8acafa0beaa4d08d014a97b6bc6b", + "telemetry": "358ffaa88ba34a97d55af0933a117de4", + "timelion-sheet": "9a2a2748877c7a7b582fef201ab1d4cf", + "type": "2f4316de49999235636386fe51dc06c1", + "ui-metric": "0d409297dc5ebe1e3a1da691c6ee32e3", + "updated_at": "00da57df13e94e9d98437d13ace4bfe0", + "upgrade-assistant-reindex-operation": "a53a20fe086b72c9a86da3cc12dad8a6", + "upgrade-assistant-telemetry": "56702cec857e0a9dacfb696655b4ff7b", + "url": "c7f66a0df8b1b52f17c28c4adb111105", + "visualization": "52d7a13ad68a150c4525b292d23e12cc" + } + }, + "dynamic": "strict", + "properties": { + "action": { + "properties": { + "actionTypeId": { + "type": "keyword" + }, + "config": { + "enabled": false, + "type": "object" + }, + "name": { + "type": "text" + }, + "secrets": { + "type": "binary" + } + } + }, + "action_task_params": { + "properties": { + "actionId": { + "type": "keyword" + }, + "apiKey": { + "type": "binary" + }, + "params": { + "enabled": false, + "type": "object" + } + } + }, + "alert": { + "properties": { + "actions": { + "properties": { + "actionRef": { + "type": "keyword" + }, + "actionTypeId": { + "type": "keyword" + }, + "group": { + "type": "keyword" + }, + "params": { + "enabled": false, + "type": "object" + } + }, + "type": "nested" + }, + "alertTypeId": { + "type": "keyword" + }, + "apiKey": { + "type": "binary" + }, + "apiKeyOwner": { + "type": "keyword" + }, + "createdBy": { + "type": "keyword" + }, + "enabled": { + "type": "boolean" + }, + "interval": { + "type": "keyword" + }, + "muteAll": { + "type": "boolean" + }, + "mutedInstanceIds": { + "type": "keyword" + }, + "name": { + "type": "text" + }, + "params": { + "enabled": false, + "type": "object" + }, + "scheduledTaskId": { + "type": "keyword" + }, + "tags": { + "type": "keyword" + }, + "throttle": { + "type": "keyword" + }, + "updatedBy": { + "type": "keyword" + } + } + }, + "apm-indices": { + "properties": { + "apm_oss": { + "properties": { + "errorIndices": { + "type": "keyword" + }, + "metricsIndices": { + "type": "keyword" + }, + "onboardingIndices": { + "type": "keyword" + }, + "sourcemapIndices": { + "type": "keyword" + }, + "spanIndices": { + "type": "keyword" + }, + "transactionIndices": { + "type": "keyword" + } + } + } + } + }, + "apm-services-telemetry": { + "properties": { + "has_any_services": { + "type": "boolean" + }, + "services_per_agent": { + "properties": { + "dotnet": { + "null_value": 0, + "type": "long" + }, + "go": { + "null_value": 0, + "type": "long" + }, + "java": { + "null_value": 0, + "type": "long" + }, + "js-base": { + "null_value": 0, + "type": "long" + }, + "nodejs": { + "null_value": 0, + "type": "long" + }, + "python": { + "null_value": 0, + "type": "long" + }, + "ruby": { + "null_value": 0, + "type": "long" + }, + "rum-js": { + "null_value": 0, + "type": "long" + } + } + } + } + }, + "apm-telemetry": { + "properties": { + "has_any_services": { + "type": "boolean" + }, + "services_per_agent": { + "properties": { + "dotnet": { + "null_value": 0, + "type": "long" + }, + "go": { + "null_value": 0, + "type": "long" + }, + "java": { + "null_value": 0, + "type": "long" + }, + "js-base": { + "null_value": 0, + "type": "long" + }, + "nodejs": { + "null_value": 0, + "type": "long" + }, + "python": { + "null_value": 0, + "type": "long" + }, + "ruby": { + "null_value": 0, + "type": "long" + }, + "rum-js": { + "null_value": 0, + "type": "long" + } + } + } + } + }, + "canvas-element": { + "dynamic": "false", + "properties": { + "@created": { + "type": "date" + }, + "@timestamp": { + "type": "date" + }, + "content": { + "type": "text" + }, + "help": { + "type": "text" + }, + "image": { + "type": "text" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "canvas-workpad": { + "dynamic": "false", + "properties": { + "@created": { + "type": "date" + }, + "@timestamp": { + "type": "date" + }, + "name": { + "fields": { + "keyword": { + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "config": { + "dynamic": "true", + "properties": { + "buildNum": { + "type": "keyword" + }, + "dateFormat:tz": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "defaultIndex": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "dashboard": { + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "optionsJSON": { + "type": "text" + }, + "panelsJSON": { + "type": "text" + }, + "refreshInterval": { + "properties": { + "display": { + "type": "keyword" + }, + "pause": { + "type": "boolean" + }, + "section": { + "type": "integer" + }, + "value": { + "type": "integer" + } + } + }, + "timeFrom": { + "type": "keyword" + }, + "timeRestore": { + "type": "boolean" + }, + "timeTo": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "file-upload-telemetry": { + "properties": { + "filesUploadedTotalCount": { + "type": "long" + } + } + }, + "graph-workspace": { + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "numLinks": { + "type": "integer" + }, + "numVertices": { + "type": "integer" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + }, + "wsState": { + "type": "text" + } + } + }, + "index-pattern": { + "properties": { + "fieldFormatMap": { + "type": "text" + }, + "fields": { + "type": "text" + }, + "intervalName": { + "type": "keyword" + }, + "notExpandable": { + "type": "boolean" + }, + "sourceFilters": { + "type": "text" + }, + "timeFieldName": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "type": { + "type": "keyword" + }, + "typeMeta": { + "type": "keyword" + } + } + }, + "infrastructure-ui-source": { + "properties": { + "description": { + "type": "text" + }, + "fields": { + "properties": { + "container": { + "type": "keyword" + }, + "host": { + "type": "keyword" + }, + "pod": { + "type": "keyword" + }, + "tiebreaker": { + "type": "keyword" + }, + "timestamp": { + "type": "keyword" + } + } + }, + "logAlias": { + "type": "keyword" + }, + "logColumns": { + "properties": { + "fieldColumn": { + "properties": { + "field": { + "type": "keyword" + }, + "id": { + "type": "keyword" + } + } + }, + "messageColumn": { + "properties": { + "id": { + "type": "keyword" + } + } + }, + "timestampColumn": { + "properties": { + "id": { + "type": "keyword" + } + } + } + }, + "type": "nested" + }, + "metricAlias": { + "type": "keyword" + }, + "name": { + "type": "text" + } + } + }, + "inventory-view": { + "properties": { + "autoBounds": { + "type": "boolean" + }, + "autoReload": { + "type": "boolean" + }, + "boundsOverride": { + "properties": { + "max": { + "type": "integer" + }, + "min": { + "type": "integer" + } + } + }, + "customOptions": { + "properties": { + "field": { + "type": "keyword" + }, + "text": { + "type": "keyword" + } + }, + "type": "nested" + }, + "filterQuery": { + "properties": { + "expression": { + "type": "keyword" + }, + "kind": { + "type": "keyword" + } + } + }, + "groupBy": { + "properties": { + "field": { + "type": "keyword" + }, + "label": { + "type": "keyword" + } + }, + "type": "nested" + }, + "metric": { + "properties": { + "type": { + "type": "keyword" + } + } + }, + "name": { + "type": "keyword" + }, + "nodeType": { + "type": "keyword" + }, + "time": { + "type": "integer" + }, + "view": { + "type": "keyword" + } + } + }, + "kql-telemetry": { + "properties": { + "optInCount": { + "type": "long" + }, + "optOutCount": { + "type": "long" + } + } + }, + "lens": { + "properties": { + "expression": { + "index": false, + "type": "keyword" + }, + "state": { + "type": "flattened" + }, + "title": { + "type": "text" + }, + "visualizationType": { + "type": "keyword" + } + } + }, + "lens-ui-telemetry": { + "properties": { + "count": { + "type": "integer" + }, + "date": { + "type": "date" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + } + }, + "map": { + "properties": { + "bounds": { + "type": "geo_shape" + }, + "description": { + "type": "text" + }, + "layerListJSON": { + "type": "text" + }, + "mapStateJSON": { + "type": "text" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "maps-telemetry": { + "properties": { + "attributesPerMap": { + "properties": { + "dataSourcesCount": { + "properties": { + "avg": { + "type": "long" + }, + "max": { + "type": "long" + }, + "min": { + "type": "long" + } + } + }, + "emsVectorLayersCount": { + "dynamic": "true", + "type": "object" + }, + "layerTypesCount": { + "dynamic": "true", + "type": "object" + }, + "layersCount": { + "properties": { + "avg": { + "type": "long" + }, + "max": { + "type": "long" + }, + "min": { + "type": "long" + } + } + } + } + }, + "mapsTotalCount": { + "type": "long" + }, + "timeCaptured": { + "type": "date" + } + } + }, + "metrics-explorer-view": { + "properties": { + "chartOptions": { + "properties": { + "stack": { + "type": "boolean" + }, + "type": { + "type": "keyword" + }, + "yAxisMode": { + "type": "keyword" + } + } + }, + "currentTimerange": { + "properties": { + "from": { + "type": "keyword" + }, + "interval": { + "type": "keyword" + }, + "to": { + "type": "keyword" + } + } + }, + "name": { + "type": "keyword" + }, + "options": { + "properties": { + "aggregation": { + "type": "keyword" + }, + "filterQuery": { + "type": "keyword" + }, + "groupBy": { + "type": "keyword" + }, + "limit": { + "type": "integer" + }, + "metrics": { + "properties": { + "aggregation": { + "type": "keyword" + }, + "color": { + "type": "keyword" + }, + "field": { + "type": "keyword" + }, + "label": { + "type": "keyword" + } + }, + "type": "nested" + } + } + } + } + }, + "migrationVersion": { + "dynamic": "true", + "properties": { + "dashboard": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "index-pattern": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "search": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + }, + "space": { + "fields": { + "keyword": { + "ignore_above": 256, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "ml-telemetry": { + "properties": { + "file_data_visualizer": { + "properties": { + "index_creation_count": { + "type": "long" + } + } + } + } + }, + "namespace": { + "type": "keyword" + }, + "query": { + "properties": { + "description": { + "type": "text" + }, + "filters": { + "enabled": false, + "type": "object" + }, + "query": { + "properties": { + "language": { + "type": "keyword" + }, + "query": { + "index": false, + "type": "keyword" + } + } + }, + "timefilter": { + "enabled": false, + "type": "object" + }, + "title": { + "type": "text" + } + } + }, + "references": { + "properties": { + "id": { + "type": "keyword" + }, + "name": { + "type": "keyword" + }, + "type": { + "type": "keyword" + } + }, + "type": "nested" + }, + "sample-data-telemetry": { + "properties": { + "installCount": { + "type": "long" + }, + "unInstallCount": { + "type": "long" + } + } + }, + "search": { + "properties": { + "columns": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "sort": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "server": { + "properties": { + "uuid": { + "type": "keyword" + } + } + }, + "siem-ui-timeline": { + "properties": { + "columns": { + "properties": { + "aggregatable": { + "type": "boolean" + }, + "category": { + "type": "keyword" + }, + "columnHeaderType": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "example": { + "type": "text" + }, + "id": { + "type": "keyword" + }, + "indexes": { + "type": "keyword" + }, + "name": { + "type": "text" + }, + "placeholder": { + "type": "text" + }, + "searchable": { + "type": "boolean" + }, + "type": { + "type": "keyword" + } + } + }, + "created": { + "type": "date" + }, + "createdBy": { + "type": "text" + }, + "dataProviders": { + "properties": { + "and": { + "properties": { + "enabled": { + "type": "boolean" + }, + "excluded": { + "type": "boolean" + }, + "id": { + "type": "keyword" + }, + "kqlQuery": { + "type": "text" + }, + "name": { + "type": "text" + }, + "queryMatch": { + "properties": { + "displayField": { + "type": "text" + }, + "displayValue": { + "type": "text" + }, + "field": { + "type": "text" + }, + "operator": { + "type": "text" + }, + "value": { + "type": "text" + } + } + } + } + }, + "enabled": { + "type": "boolean" + }, + "excluded": { + "type": "boolean" + }, + "id": { + "type": "keyword" + }, + "kqlQuery": { + "type": "text" + }, + "name": { + "type": "text" + }, + "queryMatch": { + "properties": { + "displayField": { + "type": "text" + }, + "displayValue": { + "type": "text" + }, + "field": { + "type": "text" + }, + "operator": { + "type": "text" + }, + "value": { + "type": "text" + } + } + } + } + }, + "dateRange": { + "properties": { + "end": { + "type": "date" + }, + "start": { + "type": "date" + } + } + }, + "description": { + "type": "text" + }, + "favorite": { + "properties": { + "favoriteDate": { + "type": "date" + }, + "fullName": { + "type": "text" + }, + "keySearch": { + "type": "text" + }, + "userName": { + "type": "text" + } + } + }, + "filters": { + "properties": { + "exists": { + "type": "text" + }, + "match_all": { + "type": "text" + }, + "meta": { + "properties": { + "alias": { + "type": "text" + }, + "controlledBy": { + "type": "text" + }, + "disabled": { + "type": "boolean" + }, + "field": { + "type": "text" + }, + "formattedValue": { + "type": "text" + }, + "index": { + "type": "keyword" + }, + "key": { + "type": "keyword" + }, + "negate": { + "type": "boolean" + }, + "params": { + "type": "text" + }, + "type": { + "type": "keyword" + }, + "value": { + "type": "text" + } + } + }, + "missing": { + "type": "text" + }, + "query": { + "type": "text" + }, + "range": { + "type": "text" + }, + "script": { + "type": "text" + } + } + }, + "kqlMode": { + "type": "keyword" + }, + "kqlQuery": { + "properties": { + "filterQuery": { + "properties": { + "kuery": { + "properties": { + "expression": { + "type": "text" + }, + "kind": { + "type": "keyword" + } + } + }, + "serializedQuery": { + "type": "text" + } + } + } + } + }, + "savedQueryId": { + "type": "keyword" + }, + "sort": { + "properties": { + "columnId": { + "type": "keyword" + }, + "sortDirection": { + "type": "keyword" + } + } + }, + "title": { + "type": "text" + }, + "updated": { + "type": "date" + }, + "updatedBy": { + "type": "text" + } + } + }, + "siem-ui-timeline-note": { + "properties": { + "created": { + "type": "date" + }, + "createdBy": { + "type": "text" + }, + "eventId": { + "type": "keyword" + }, + "note": { + "type": "text" + }, + "timelineId": { + "type": "keyword" + }, + "updated": { + "type": "date" + }, + "updatedBy": { + "type": "text" + } + } + }, + "siem-ui-timeline-pinned-event": { + "properties": { + "created": { + "type": "date" + }, + "createdBy": { + "type": "text" + }, + "eventId": { + "type": "keyword" + }, + "timelineId": { + "type": "keyword" + }, + "updated": { + "type": "date" + }, + "updatedBy": { + "type": "text" + } + } + }, + "space": { + "properties": { + "_reserved": { + "type": "boolean" + }, + "color": { + "type": "keyword" + }, + "description": { + "type": "text" + }, + "disabledFeatures": { + "type": "keyword" + }, + "imageUrl": { + "index": false, + "type": "text" + }, + "initials": { + "type": "keyword" + }, + "name": { + "fields": { + "keyword": { + "ignore_above": 2048, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "telemetry": { + "properties": { + "enabled": { + "type": "boolean" + }, + "lastReported": { + "type": "date" + }, + "lastVersionChecked": { + "ignore_above": 256, + "type": "keyword" + }, + "sendUsageFrom": { + "ignore_above": 256, + "type": "keyword" + }, + "userHasSeenNotice": { + "type": "boolean" + } + } + }, + "timelion-sheet": { + "properties": { + "description": { + "type": "text" + }, + "hits": { + "type": "integer" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "timelion_chart_height": { + "type": "integer" + }, + "timelion_columns": { + "type": "integer" + }, + "timelion_interval": { + "type": "keyword" + }, + "timelion_other_interval": { + "type": "keyword" + }, + "timelion_rows": { + "type": "integer" + }, + "timelion_sheet": { + "type": "text" + }, + "title": { + "type": "text" + }, + "version": { + "type": "integer" + } + } + }, + "type": { + "type": "keyword" + }, + "ui-metric": { + "properties": { + "count": { + "type": "integer" + } + } + }, + "updated_at": { + "type": "date" + }, + "upgrade-assistant-reindex-operation": { + "dynamic": "true", + "properties": { + "indexName": { + "type": "keyword" + }, + "status": { + "type": "integer" + } + } + }, + "upgrade-assistant-telemetry": { + "properties": { + "features": { + "properties": { + "deprecation_logging": { + "properties": { + "enabled": { + "null_value": true, + "type": "boolean" + } + } + } + } + }, + "ui_open": { + "properties": { + "cluster": { + "null_value": 0, + "type": "long" + }, + "indices": { + "null_value": 0, + "type": "long" + }, + "overview": { + "null_value": 0, + "type": "long" + } + } + }, + "ui_reindex": { + "properties": { + "close": { + "null_value": 0, + "type": "long" + }, + "open": { + "null_value": 0, + "type": "long" + }, + "start": { + "null_value": 0, + "type": "long" + }, + "stop": { + "null_value": 0, + "type": "long" + } + } + } + } + }, + "url": { + "properties": { + "accessCount": { + "type": "long" + }, + "accessDate": { + "type": "date" + }, + "createDate": { + "type": "date" + }, + "url": { + "fields": { + "keyword": { + "ignore_above": 2048, + "type": "keyword" + } + }, + "type": "text" + } + } + }, + "visualization": { + "properties": { + "description": { + "type": "text" + }, + "kibanaSavedObjectMeta": { + "properties": { + "searchSourceJSON": { + "type": "text" + } + } + }, + "savedSearchRefName": { + "type": "keyword" + }, + "title": { + "type": "text" + }, + "uiStateJSON": { + "type": "text" + }, + "version": { + "type": "integer" + }, + "visState": { + "type": "text" + } + } + } + } + }, + "settings": { + "index": { + "auto_expand_replicas": "0-1", + "number_of_replicas": "0", + "number_of_shards": "1" + } + } + } +} \ No newline at end of file diff --git a/x-pack/test/reporting/es_archives/historic/data.json.gz b/x-pack/test/functional/es_archives/reporting/historic/data.json.gz similarity index 100% rename from x-pack/test/reporting/es_archives/historic/data.json.gz rename to x-pack/test/functional/es_archives/reporting/historic/data.json.gz diff --git a/x-pack/test/reporting/es_archives/historic/mappings.json b/x-pack/test/functional/es_archives/reporting/historic/mappings.json similarity index 100% rename from x-pack/test/reporting/es_archives/historic/mappings.json rename to x-pack/test/functional/es_archives/reporting/historic/mappings.json diff --git a/x-pack/test/functional/es_archives/uptime/blank/mappings.json b/x-pack/test/functional/es_archives/uptime/blank/mappings.json index eb383cc87563d..a1b0696cdaadc 100644 --- a/x-pack/test/functional/es_archives/uptime/blank/mappings.json +++ b/x-pack/test/functional/es_archives/uptime/blank/mappings.json @@ -1267,6 +1267,9 @@ "type": { "ignore_above": 1024, "type": "keyword" + }, + "timespan": { + "type": "date_range" } } }, diff --git a/x-pack/test/functional/page_objects/canvas_page.ts b/x-pack/test/functional/page_objects/canvas_page.ts index a4b4f500b8832..fa117dbea393d 100644 --- a/x-pack/test/functional/page_objects/canvas_page.ts +++ b/x-pack/test/functional/page_objects/canvas_page.ts @@ -23,6 +23,10 @@ export function CanvasPageProvider({ getService }: FtrProviderContext) { await browser.pressKeys(browser.keys.ESCAPE); }, + async openExpressionEditor() { + await testSubjects.click('canvasExpressionEditorButton'); + }, + async waitForWorkpadElements() { await testSubjects.findAll('canvasWorkpadPage > canvasWorkpadPageElementContent'); }, diff --git a/x-pack/test/functional/page_objects/index.ts b/x-pack/test/functional/page_objects/index.ts index 82011c48d4460..18ea515a73147 100644 --- a/x-pack/test/functional/page_objects/index.ts +++ b/x-pack/test/functional/page_objects/index.ts @@ -46,6 +46,7 @@ import { RemoteClustersPageProvider } from './remote_clusters_page'; import { CopySavedObjectsToSpacePageProvider } from './copy_saved_objects_to_space_page'; import { LensPageProvider } from './lens_page'; import { InfraMetricExplorerProvider } from './infra_metric_explorer'; +import { RoleMappingsPageProvider } from './role_mappings_page'; // just like services, PageObjects are defined as a map of // names to Providers. Merge in Kibana's or pick specific ones @@ -78,4 +79,5 @@ export const pageObjects = { remoteClusters: RemoteClustersPageProvider, copySavedObjectsToSpace: CopySavedObjectsToSpacePageProvider, lens: LensPageProvider, + roleMappings: RoleMappingsPageProvider, }; diff --git a/x-pack/test/functional/page_objects/reporting_page.js b/x-pack/test/functional/page_objects/reporting_page.js index d28234803ca29..acbc1dd100e5d 100644 --- a/x-pack/test/functional/page_objects/reporting_page.js +++ b/x-pack/test/functional/page_objects/reporting_page.js @@ -7,6 +7,13 @@ import { parse } from 'url'; import http from 'http'; +/* + * NOTE: Reporting is a service, not an app. The page objects that are + * important for generating reports belong to the apps that integrate with the + * Reporting service. Eventually, this file should be dissolved across the + * apps that need it for testing their integration. + * Issue: https://github.com/elastic/kibana/issues/52927 + */ export function ReportingPageProvider({ getService, getPageObjects }) { const retry = getService('retry'); const log = getService('log'); @@ -22,7 +29,7 @@ export function ReportingPageProvider({ getService, getPageObjects }) { log.debug('ReportingPage:initTests'); await PageObjects.settings.navigateTo(); await esArchiver.loadIfNeeded('../../functional/es_archives/logstash_functional'); - await esArchiver.load('historic'); + await esArchiver.load('reporting/historic'); await kibanaServer.uiSettings.replace({ defaultIndex: 'logstash-*', }); diff --git a/x-pack/test/functional/page_objects/role_mappings_page.ts b/x-pack/test/functional/page_objects/role_mappings_page.ts new file mode 100644 index 0000000000000..b1adfb00af739 --- /dev/null +++ b/x-pack/test/functional/page_objects/role_mappings_page.ts @@ -0,0 +1,17 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { FtrProviderContext } from '../ftr_provider_context'; + +export function RoleMappingsPageProvider({ getService }: FtrProviderContext) { + const testSubjects = getService('testSubjects'); + + return { + async appTitleText() { + return await testSubjects.getVisibleText('appTitle'); + }, + }; +} diff --git a/x-pack/test/functional/page_objects/snapshot_restore_page.ts b/x-pack/test/functional/page_objects/snapshot_restore_page.ts index 25bdfc7075727..1c8ba9f633111 100644 --- a/x-pack/test/functional/page_objects/snapshot_restore_page.ts +++ b/x-pack/test/functional/page_objects/snapshot_restore_page.ts @@ -3,11 +3,11 @@ * or more contributor license agreements. Licensed under the Elastic License; * you may not use this file except in compliance with the Elastic License. */ - import { FtrProviderContext } from '../ftr_provider_context'; export function SnapshotRestorePageProvider({ getService }: FtrProviderContext) { const testSubjects = getService('testSubjects'); + const retry = getService('retry'); return { async appTitleText() { @@ -16,5 +16,50 @@ export function SnapshotRestorePageProvider({ getService }: FtrProviderContext) async registerRepositoryButton() { return await testSubjects.find('registerRepositoryButton'); }, + async navToRepositories() { + await testSubjects.click('repositories_tab'); + await retry.waitForWithTimeout( + 'Wait for register repository button to be on page', + 10000, + async () => { + return await testSubjects.isDisplayed('registerRepositoryButton'); + } + ); + }, + async getRepoList() { + const table = await testSubjects.find('repositoryTable'); + const rows = await table.findAllByCssSelector('[data-test-subj="row"]'); + return await Promise.all( + rows.map(async row => { + return { + repoName: await ( + await row.findByCssSelector('[data-test-subj="Name_cell"]') + ).getVisibleText(), + repoLink: await ( + await row.findByCssSelector('[data-test-subj="Name_cell"]') + ).findByCssSelector('a'), + repoType: await ( + await row.findByCssSelector('[data-test-subj="Type_cell"]') + ).getVisibleText(), + repoEdit: await row.findByCssSelector('[data-test-subj="editRepositoryButton"]'), + repoDelete: await row.findByCssSelector('[data-test-subj="deleteRepositoryButton"]'), + }; + }) + ); + }, + async viewRepositoryDetails(name: string) { + const repos = await this.getRepoList(); + if (repos.length === 1) { + const repoToView = repos.filter(r => (r.repoName = name))[0]; + await repoToView.repoLink.click(); + } + await retry.waitForWithTimeout(`Repo title should be ${name}`, 10000, async () => { + return (await testSubjects.getVisibleText('title')) === name; + }); + }, + async performRepositoryCleanup() { + await testSubjects.click('cleanupRepositoryButton'); + return await testSubjects.getVisibleText('cleanupCodeBlock'); + }, }; } diff --git a/x-pack/test/functional/page_objects/uptime_page.ts b/x-pack/test/functional/page_objects/uptime_page.ts index 26c95c3bf526d..f04f96148583f 100644 --- a/x-pack/test/functional/page_objects/uptime_page.ts +++ b/x-pack/test/functional/page_objects/uptime_page.ts @@ -70,5 +70,20 @@ export function UptimePageProvider({ getPageObjects, getService }: FtrProviderCo await uptimeService.setStatusFilterDown(); } } + + public async selectFilterItems(filters: Record) { + for (const key in filters) { + if (filters.hasOwnProperty(key)) { + const values = filters[key]; + for (let i = 0; i < values.length; i++) { + await uptimeService.selectFilterItem(key, values[i]); + } + } + } + } + + public async getSnapshotCount() { + return await uptimeService.getSnapshotCount(); + } })(); } diff --git a/x-pack/test/functional/services/index.ts b/x-pack/test/functional/services/index.ts index 5ab8933a4804a..84d5a792ae6ca 100644 --- a/x-pack/test/functional/services/index.ts +++ b/x-pack/test/functional/services/index.ts @@ -57,6 +57,7 @@ export const services = { ...kibanaFunctionalServices, ...commonServices, + supertest: kibanaApiIntegrationServices.supertest, esSupertest: kibanaApiIntegrationServices.esSupertest, monitoringNoData: MonitoringNoDataProvider, monitoringClusterList: MonitoringClusterListProvider, diff --git a/x-pack/test/functional/services/uptime.ts b/x-pack/test/functional/services/uptime.ts index 40d2e3dafc7f8..ca38c2e9dd897 100644 --- a/x-pack/test/functional/services/uptime.ts +++ b/x-pack/test/functional/services/uptime.ts @@ -49,5 +49,20 @@ export function UptimeProvider({ getService }: FtrProviderContext) { async setStatusFilterDown() { await testSubjects.click('xpack.uptime.filterBar.filterStatusDown'); }, + async selectFilterItem(filterType: string, option: string) { + const popoverId = `filter-popover_${filterType}`; + const optionId = `filter-popover-item_${option}`; + await testSubjects.existOrFail(popoverId); + await testSubjects.click(popoverId); + await testSubjects.existOrFail(optionId); + await testSubjects.click(optionId); + await testSubjects.click(popoverId); + }, + async getSnapshotCount() { + return { + up: await testSubjects.getVisibleText('xpack.uptime.snapshot.donutChart.up'), + down: await testSubjects.getVisibleText('xpack.uptime.snapshot.donutChart.down'), + }; + }, }; } diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts new file mode 100644 index 0000000000000..4fdfe0d32ace3 --- /dev/null +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/alerts.ts @@ -0,0 +1,344 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import uuid from 'uuid'; +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +function generateUniqueKey() { + return uuid.v4().replace(/-/g, ''); +} + +export default ({ getPageObjects, getService }: FtrProviderContext) => { + const testSubjects = getService('testSubjects'); + const pageObjects = getPageObjects(['common', 'triggersActionsUI', 'header']); + const supertest = getService('supertest'); + const retry = getService('retry'); + + async function createAlert() { + const { body: createdAlert } = await supertest + .post(`/api/alert`) + .set('kbn-xsrf', 'foo') + .send({ + enabled: true, + name: generateUniqueKey(), + tags: ['foo', 'bar'], + alertTypeId: 'test.noop', + consumer: 'test', + schedule: { interval: '1m' }, + throttle: '1m', + actions: [], + params: {}, + }) + .expect(200); + return createdAlert; + } + + describe('alerts', function() { + before(async () => { + await pageObjects.common.navigateToApp('triggersActions'); + const alertsTab = await testSubjects.find('alertsTab'); + await alertsTab.click(); + }); + + it('should search for alert', async () => { + const createdAlert = await createAlert(); + + await pageObjects.triggersActionsUI.searchAlerts(createdAlert.name); + + const searchResults = await pageObjects.triggersActionsUI.getAlertsList(); + expect(searchResults).to.eql([ + { + name: createdAlert.name, + tagsText: 'foo, bar', + alertType: 'Test: Noop', + interval: '1m', + }, + ]); + }); + + it('should search for tags', async () => { + const createdAlert = await createAlert(); + + await pageObjects.triggersActionsUI.searchAlerts(`${createdAlert.name} foo`); + + const searchResults = await pageObjects.triggersActionsUI.getAlertsList(); + expect(searchResults).to.eql([ + { + name: createdAlert.name, + tagsText: 'foo, bar', + alertType: 'Test: Noop', + interval: '1m', + }, + ]); + }); + + // Flaky until https://github.com/elastic/eui/issues/2612 fixed + it.skip('should disable single alert', async () => { + const createdAlert = await createAlert(); + + await pageObjects.triggersActionsUI.searchAlerts(createdAlert.name); + + const collapsedItemActions = await testSubjects.find('collapsedItemActions'); + await collapsedItemActions.click(); + + const enableSwitch = await testSubjects.find('enableSwitch'); + await enableSwitch.click(); + + await pageObjects.triggersActionsUI.searchAlerts(createdAlert.name); + + const collapsedItemActionsAfterDisable = await testSubjects.find('collapsedItemActions'); + await collapsedItemActionsAfterDisable.click(); + + const enableSwitchAfterDisable = await testSubjects.find('enableSwitch'); + const isChecked = await enableSwitchAfterDisable.getAttribute('aria-checked'); + expect(isChecked).to.eql('false'); + }); + + // Flaky until https://github.com/elastic/eui/issues/2612 fixed + it.skip('should re-enable single alert', async () => { + const createdAlert = await createAlert(); + + await pageObjects.triggersActionsUI.searchAlerts(createdAlert.name); + + const collapsedItemActions = await testSubjects.find('collapsedItemActions'); + await collapsedItemActions.click(); + + const enableSwitch = await testSubjects.find('enableSwitch'); + await enableSwitch.click(); + + await pageObjects.triggersActionsUI.searchAlerts(createdAlert.name); + + const collapsedItemActionsAfterDisable = await testSubjects.find('collapsedItemActions'); + await collapsedItemActionsAfterDisable.click(); + + const enableSwitchAfterDisable = await testSubjects.find('enableSwitch'); + await enableSwitchAfterDisable.click(); + + await pageObjects.triggersActionsUI.searchAlerts(createdAlert.name); + + const collapsedItemActionsAfterReEnable = await testSubjects.find('collapsedItemActions'); + await collapsedItemActionsAfterReEnable.click(); + + const enableSwitchAfterReEnable = await testSubjects.find('enableSwitch'); + const isChecked = await enableSwitchAfterReEnable.getAttribute('aria-checked'); + expect(isChecked).to.eql('true'); + }); + + // Flaky until https://github.com/elastic/eui/issues/2612 fixed + it.skip('should mute single alert', async () => { + const createdAlert = await createAlert(); + + await pageObjects.triggersActionsUI.searchAlerts(createdAlert.name); + + const collapsedItemActions = await testSubjects.find('collapsedItemActions'); + await collapsedItemActions.click(); + + const muteSwitch = await testSubjects.find('muteSwitch'); + await muteSwitch.click(); + + await pageObjects.triggersActionsUI.searchAlerts(createdAlert.name); + + const collapsedItemActionsAfterMute = await testSubjects.find('collapsedItemActions'); + await collapsedItemActionsAfterMute.click(); + + const muteSwitchAfterMute = await testSubjects.find('muteSwitch'); + const isChecked = await muteSwitchAfterMute.getAttribute('aria-checked'); + expect(isChecked).to.eql('true'); + }); + + // Flaky until https://github.com/elastic/eui/issues/2612 fixed + it.skip('should unmute single alert', async () => { + const createdAlert = await createAlert(); + + await pageObjects.triggersActionsUI.searchAlerts(createdAlert.name); + + const collapsedItemActions = await testSubjects.find('collapsedItemActions'); + await collapsedItemActions.click(); + + const muteSwitch = await testSubjects.find('muteSwitch'); + await muteSwitch.click(); + + await pageObjects.triggersActionsUI.searchAlerts(createdAlert.name); + + const collapsedItemActionsAfterMute = await testSubjects.find('collapsedItemActions'); + await collapsedItemActionsAfterMute.click(); + + const muteSwitchAfterMute = await testSubjects.find('muteSwitch'); + await muteSwitchAfterMute.click(); + + await pageObjects.triggersActionsUI.searchAlerts(createdAlert.name); + + const collapsedItemActionsAfterUnmute = await testSubjects.find('collapsedItemActions'); + await collapsedItemActionsAfterUnmute.click(); + + const muteSwitchAfterUnmute = await testSubjects.find('muteSwitch'); + const isChecked = await muteSwitchAfterUnmute.getAttribute('aria-checked'); + expect(isChecked).to.eql('false'); + }); + + // Flaky, will be fixed with https://github.com/elastic/kibana/issues/53956 + it.skip('should delete single alert', async () => { + const createdAlert = await createAlert(); + + await pageObjects.triggersActionsUI.searchAlerts(createdAlert.name); + + const collapsedItemActions = await testSubjects.find('collapsedItemActions'); + await collapsedItemActions.click(); + + const deleteBtn = await testSubjects.find('deleteAlert'); + await deleteBtn.click(); + + retry.try(async () => { + await pageObjects.triggersActionsUI.searchAlerts(createdAlert.name); + + const searchResults = await pageObjects.triggersActionsUI.getAlertsList(); + expect(searchResults.length).to.eql(0); + }); + }); + + // Flaky, will be fixed with https://github.com/elastic/kibana/issues/49830 + it.skip('should mute all selection', async () => { + const createdAlert = await createAlert(); + + await pageObjects.triggersActionsUI.searchAlerts(createdAlert.name); + + const checkbox = await testSubjects.find(`checkboxSelectRow-${createdAlert.id}`); + await checkbox.click(); + + const bulkActionBtn = await testSubjects.find('bulkAction'); + await bulkActionBtn.click(); + + const muteAllBtn = await testSubjects.find('muteAll'); + await muteAllBtn.click(); + + // Unmute all button shows after clicking mute all + await testSubjects.existOrFail('unmuteAll'); + + await pageObjects.triggersActionsUI.searchAlerts(createdAlert.name); + + const collapsedItemActions = await testSubjects.find('collapsedItemActions'); + await collapsedItemActions.click(); + + const muteSwitch = await testSubjects.find('muteSwitch'); + const isChecked = await muteSwitch.getAttribute('aria-checked'); + expect(isChecked).to.eql('true'); + }); + + // Flaky, will be fixed with https://github.com/elastic/kibana/issues/49830 + it.skip('should unmute all selection', async () => { + const createdAlert = await createAlert(); + + await pageObjects.triggersActionsUI.searchAlerts(createdAlert.name); + + const checkbox = await testSubjects.find(`checkboxSelectRow-${createdAlert.id}`); + await checkbox.click(); + + const bulkActionBtn = await testSubjects.find('bulkAction'); + await bulkActionBtn.click(); + + const muteAllBtn = await testSubjects.find('muteAll'); + await muteAllBtn.click(); + + const unmuteAllBtn = await testSubjects.find('unmuteAll'); + await unmuteAllBtn.click(); + + // Mute all button shows after clicking unmute all + await testSubjects.existOrFail('muteAll'); + + await pageObjects.triggersActionsUI.searchAlerts(createdAlert.name); + + const collapsedItemActions = await testSubjects.find('collapsedItemActions'); + await collapsedItemActions.click(); + + const muteSwitch = await testSubjects.find('muteSwitch'); + const isChecked = await muteSwitch.getAttribute('aria-checked'); + expect(isChecked).to.eql('false'); + }); + + // Flaky, will be fixed with https://github.com/elastic/kibana/issues/49830 + it.skip('should disable all selection', async () => { + const createdAlert = await createAlert(); + + await pageObjects.triggersActionsUI.searchAlerts(createdAlert.name); + + const checkbox = await testSubjects.find(`checkboxSelectRow-${createdAlert.id}`); + await checkbox.click(); + + const bulkActionBtn = await testSubjects.find('bulkAction'); + await bulkActionBtn.click(); + + const disableAllBtn = await testSubjects.find('disableAll'); + await disableAllBtn.click(); + + // Enable all button shows after clicking disable all + await testSubjects.existOrFail('enableAll'); + + await pageObjects.triggersActionsUI.searchAlerts(createdAlert.name); + + const collapsedItemActions = await testSubjects.find('collapsedItemActions'); + await collapsedItemActions.click(); + + const enableSwitch = await testSubjects.find('enableSwitch'); + const isChecked = await enableSwitch.getAttribute('aria-checked'); + expect(isChecked).to.eql('false'); + }); + + // Flaky, will be fixed with https://github.com/elastic/kibana/issues/49830 + it.skip('should enable all selection', async () => { + const createdAlert = await createAlert(); + + await pageObjects.triggersActionsUI.searchAlerts(createdAlert.name); + + const checkbox = await testSubjects.find(`checkboxSelectRow-${createdAlert.id}`); + await checkbox.click(); + + const bulkActionBtn = await testSubjects.find('bulkAction'); + await bulkActionBtn.click(); + + const disableAllBtn = await testSubjects.find('disableAll'); + await disableAllBtn.click(); + + const enableAllBtn = await testSubjects.find('enableAll'); + await enableAllBtn.click(); + + // Disable all button shows after clicking enable all + await testSubjects.existOrFail('disableAll'); + + await pageObjects.triggersActionsUI.searchAlerts(createdAlert.name); + + const collapsedItemActions = await testSubjects.find('collapsedItemActions'); + await collapsedItemActions.click(); + + const enableSwitch = await testSubjects.find('enableSwitch'); + const isChecked = await enableSwitch.getAttribute('aria-checked'); + expect(isChecked).to.eql('true'); + }); + + // Flaky, will be fixed with https://github.com/elastic/kibana/issues/53956 + it.skip('should delete all selection', async () => { + const createdAlert = await createAlert(); + + await pageObjects.triggersActionsUI.searchAlerts(createdAlert.name); + + const checkbox = await testSubjects.find(`checkboxSelectRow-${createdAlert.id}`); + await checkbox.click(); + + const bulkActionBtn = await testSubjects.find('bulkAction'); + await bulkActionBtn.click(); + + const deleteAllBtn = await testSubjects.find('deleteAll'); + await deleteAllBtn.click(); + + retry.try(async () => { + await pageObjects.triggersActionsUI.searchAlerts(createdAlert.name); + + const searchResults = await pageObjects.triggersActionsUI.getAlertsList(); + expect(searchResults.length).to.eql(0); + }); + }); + }); +}; diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors.ts new file mode 100644 index 0000000000000..7b60685225ac6 --- /dev/null +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/connectors.ts @@ -0,0 +1,202 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import uuid from 'uuid'; +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +function generateUniqueKey() { + return uuid.v4().replace(/-/g, ''); +} + +export default ({ getPageObjects, getService }: FtrProviderContext) => { + const testSubjects = getService('testSubjects'); + const pageObjects = getPageObjects(['common', 'triggersActionsUI', 'header']); + const find = getService('find'); + + describe('Connectors', function() { + before(async () => { + await pageObjects.common.navigateToApp('triggersActions'); + const alertsTab = await testSubjects.find('connectorsTab'); + await alertsTab.click(); + }); + + it('should create a connector', async () => { + const connectorName = generateUniqueKey(); + + await pageObjects.triggersActionsUI.clickCreateConnectorButton(); + + const serverLogCard = await testSubjects.find('.server-log-card'); + await serverLogCard.click(); + + const nameInput = await testSubjects.find('nameInput'); + await nameInput.click(); + await nameInput.clearValue(); + await nameInput.type(connectorName); + + const saveButton = await find.byCssSelector( + '[data-test-subj="saveActionButton"]:not(disabled)' + ); + await saveButton.click(); + + const toastTitle = await pageObjects.common.closeToast(); + expect(toastTitle).to.eql(`Created '${connectorName}'`); + + await pageObjects.triggersActionsUI.searchConnectors(connectorName); + + const searchResults = await pageObjects.triggersActionsUI.getConnectorsList(); + expect(searchResults).to.eql([ + { + name: connectorName, + actionType: 'Server log', + referencedByCount: '0', + }, + ]); + }); + + it('should edit a connector', async () => { + const connectorName = generateUniqueKey(); + const updatedConnectorName = `${connectorName}updated`; + + await pageObjects.triggersActionsUI.clickCreateConnectorButton(); + + const serverLogCard = await testSubjects.find('.server-log-card'); + await serverLogCard.click(); + + const nameInput = await testSubjects.find('nameInput'); + await nameInput.click(); + await nameInput.clearValue(); + await nameInput.type(connectorName); + + const saveButton = await find.byCssSelector( + '[data-test-subj="saveActionButton"]:not(disabled)' + ); + await saveButton.click(); + + await pageObjects.common.closeToast(); + + await pageObjects.triggersActionsUI.searchConnectors(connectorName); + + const searchResultsBeforeEdit = await pageObjects.triggersActionsUI.getConnectorsList(); + expect(searchResultsBeforeEdit.length).to.eql(1); + + const editConnectorBtn = await find.byCssSelector( + '[data-test-subj="connectorsTableCell-name"] button' + ); + await editConnectorBtn.click(); + + const nameInputToUpdate = await testSubjects.find('nameInput'); + await nameInputToUpdate.click(); + await nameInputToUpdate.clearValue(); + await nameInputToUpdate.type(updatedConnectorName); + + const saveEditButton = await find.byCssSelector( + '[data-test-subj="saveActionButton"]:not(disabled)' + ); + await saveEditButton.click(); + + const toastTitle = await pageObjects.common.closeToast(); + expect(toastTitle).to.eql(`Updated '${updatedConnectorName}'`); + + await pageObjects.triggersActionsUI.searchConnectors(updatedConnectorName); + + const searchResultsAfterEdit = await pageObjects.triggersActionsUI.getConnectorsList(); + expect(searchResultsAfterEdit).to.eql([ + { + name: updatedConnectorName, + actionType: 'Server log', + referencedByCount: '0', + }, + ]); + }); + + it('should delete a connector', async () => { + async function createConnector(connectorName: string) { + await pageObjects.triggersActionsUI.clickCreateConnectorButton(); + + const serverLogCard = await testSubjects.find('.server-log-card'); + await serverLogCard.click(); + + const nameInput = await testSubjects.find('nameInput'); + await nameInput.click(); + await nameInput.clearValue(); + await nameInput.type(connectorName); + + const saveButton = await find.byCssSelector( + '[data-test-subj="saveActionButton"]:not(disabled)' + ); + await saveButton.click(); + await pageObjects.common.closeToast(); + } + const connectorName = generateUniqueKey(); + await createConnector(connectorName); + + await createConnector(generateUniqueKey()); + + await pageObjects.triggersActionsUI.searchConnectors(connectorName); + + const searchResultsBeforeDelete = await pageObjects.triggersActionsUI.getConnectorsList(); + expect(searchResultsBeforeDelete.length).to.eql(1); + + const deleteConnectorBtn = await testSubjects.find('deleteConnector'); + await deleteConnectorBtn.click(); + await testSubjects.existOrFail('deleteConnectorsConfirmation'); + await testSubjects.click('deleteConnectorsConfirmation > confirmModalConfirmButton'); + await testSubjects.missingOrFail('deleteConnectorsConfirmation'); + + await pageObjects.triggersActionsUI.searchConnectors(connectorName); + + const searchResultsAfterDelete = await pageObjects.triggersActionsUI.getConnectorsList(); + expect(searchResultsAfterDelete.length).to.eql(0); + }); + + it('should bulk delete connectors', async () => { + async function createConnector(connectorName: string) { + await pageObjects.triggersActionsUI.clickCreateConnectorButton(); + + const serverLogCard = await testSubjects.find('.server-log-card'); + await serverLogCard.click(); + + const nameInput = await testSubjects.find('nameInput'); + await nameInput.click(); + await nameInput.clearValue(); + await nameInput.type(connectorName); + + const saveButton = await find.byCssSelector( + '[data-test-subj="saveActionButton"]:not(disabled)' + ); + await saveButton.click(); + await pageObjects.common.closeToast(); + } + + const connectorName = generateUniqueKey(); + await createConnector(connectorName); + + await createConnector(generateUniqueKey()); + + await pageObjects.triggersActionsUI.searchConnectors(connectorName); + + const searchResultsBeforeDelete = await pageObjects.triggersActionsUI.getConnectorsList(); + expect(searchResultsBeforeDelete.length).to.eql(1); + + const deleteCheckbox = await find.byCssSelector( + '.euiTableRowCellCheckbox .euiCheckbox__input' + ); + await deleteCheckbox.click(); + + const bulkDeleteBtn = await testSubjects.find('bulkDelete'); + await bulkDeleteBtn.click(); + await testSubjects.existOrFail('deleteConnectorsConfirmation'); + await testSubjects.click('deleteConnectorsConfirmation > confirmModalConfirmButton'); + await testSubjects.missingOrFail('deleteConnectorsConfirmation'); + + await pageObjects.triggersActionsUI.searchConnectors(connectorName); + + const searchResultsAfterDelete = await pageObjects.triggersActionsUI.getConnectorsList(); + expect(searchResultsAfterDelete.length).to.eql(0); + }); + }); +}; diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/home_page.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/home_page.ts new file mode 100644 index 0000000000000..13f50a505b0b6 --- /dev/null +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/home_page.ts @@ -0,0 +1,60 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import expect from '@kbn/expect'; +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default ({ getPageObjects, getService }: FtrProviderContext) => { + const testSubjects = getService('testSubjects'); + const pageObjects = getPageObjects(['common', 'triggersActionsUI', 'header']); + const log = getService('log'); + const browser = getService('browser'); + + describe('Home page', function() { + before(async () => { + await pageObjects.common.navigateToApp('triggersActions'); + }); + + it('Loads the app', async () => { + await log.debug('Checking for section heading to say Triggers and Actions.'); + + const headingText = await pageObjects.triggersActionsUI.getSectionHeadingText(); + expect(headingText).to.be('Alerts and Actions'); + }); + + describe('Connectors tab', () => { + it('renders the connectors tab', async () => { + // Navigate to the connectors tab + pageObjects.triggersActionsUI.changeTabs('connectorsTab'); + + await pageObjects.header.waitUntilLoadingHasFinished(); + + // Verify url + const url = await browser.getCurrentUrl(); + expect(url).to.contain(`/connectors`); + + // Verify content + await testSubjects.existOrFail('actionsList'); + }); + }); + + describe('Alerts tab', () => { + it('renders the alerts tab', async () => { + // Navigate to the alerts tab + pageObjects.triggersActionsUI.changeTabs('alertsTab'); + + await pageObjects.header.waitUntilLoadingHasFinished(); + + // Verify url + const url = await browser.getCurrentUrl(); + expect(url).to.contain(`/alerts`); + + // Verify content + await testSubjects.existOrFail('alertsList'); + }); + }); + }); +}; diff --git a/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/index.ts b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/index.ts new file mode 100644 index 0000000000000..c76f477c8cfbe --- /dev/null +++ b/x-pack/test/functional_with_es_ssl/apps/triggers_actions_ui/index.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { FtrProviderContext } from '../../ftr_provider_context'; + +export default ({ loadTestFile, getService }: FtrProviderContext) => { + describe('Actions and Triggers app', function() { + this.tags('ciGroup3'); + loadTestFile(require.resolve('./home_page')); + loadTestFile(require.resolve('./connectors')); + loadTestFile(require.resolve('./alerts')); + }); +}; diff --git a/x-pack/test/functional_with_es_ssl/config.ts b/x-pack/test/functional_with_es_ssl/config.ts new file mode 100644 index 0000000000000..1a9736b0b4773 --- /dev/null +++ b/x-pack/test/functional_with_es_ssl/config.ts @@ -0,0 +1,59 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { resolve, join } from 'path'; +import { CA_CERT_PATH } from '@kbn/dev-utils'; +import { FtrConfigProviderContext } from '@kbn/test/types/ftr'; +import { services } from './services'; +import { pageObjects } from './page_objects'; + +// eslint-disable-next-line import/no-default-export +export default async function({ readConfigFile }: FtrConfigProviderContext) { + const xpackFunctionalConfig = await readConfigFile(require.resolve('../functional/config.js')); + + const servers = { + ...xpackFunctionalConfig.get('servers'), + elasticsearch: { + ...xpackFunctionalConfig.get('servers.elasticsearch'), + protocol: 'https', + }, + }; + + const returnedObject = { + ...xpackFunctionalConfig.getAll(), + servers, + services, + pageObjects, + // list paths to the files that contain your plugins tests + testFiles: [resolve(__dirname, './apps/triggers_actions_ui')], + apps: { + ...xpackFunctionalConfig.get('apps'), + triggersActions: { + pathname: '/app/kibana', + hash: '/management/kibana/triggersActions', + }, + }, + esTestCluster: { + ...xpackFunctionalConfig.get('esTestCluster'), + ssl: true, + }, + kbnTestServer: { + ...xpackFunctionalConfig.get('kbnTestServer'), + serverArgs: [ + ...xpackFunctionalConfig.get('kbnTestServer.serverArgs'), + `--elasticsearch.hosts=https://${servers.elasticsearch.hostname}:${servers.elasticsearch.port}`, + `--elasticsearch.ssl.certificateAuthorities=${CA_CERT_PATH}`, + `--plugin-path=${join(__dirname, 'fixtures', 'plugins', 'alerts')}`, + '--xpack.actions.enabled=true', + '--xpack.alerting.enabled=true', + '--xpack.triggers_actions_ui.enabled=true', + '--xpack.triggers_actions_ui.createAlertUiEnabled=true', + ], + }, + }; + + return returnedObject; +} diff --git a/x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/index.ts b/x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/index.ts new file mode 100644 index 0000000000000..df651c67c2c28 --- /dev/null +++ b/x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/index.ts @@ -0,0 +1,24 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { AlertType } from '../../../../../legacy/plugins/alerting'; + +// eslint-disable-next-line import/no-default-export +export default function(kibana: any) { + return new kibana.Plugin({ + require: ['alerting'], + name: 'alerts', + init(server: any) { + const noopAlertType: AlertType = { + id: 'test.noop', + name: 'Test: Noop', + actionGroups: ['default'], + async executor() {}, + }; + server.plugins.alerting.setup.registerType(noopAlertType); + }, + }); +} diff --git a/x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/package.json b/x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/package.json new file mode 100644 index 0000000000000..836fa09855d8f --- /dev/null +++ b/x-pack/test/functional_with_es_ssl/fixtures/plugins/alerts/package.json @@ -0,0 +1,7 @@ +{ + "name": "alerts", + "version": "0.0.0", + "kibana": { + "version": "kibana" + } +} diff --git a/x-pack/test/functional_with_es_ssl/ftr_provider_context.d.ts b/x-pack/test/functional_with_es_ssl/ftr_provider_context.d.ts new file mode 100644 index 0000000000000..bb257cdcbfe1b --- /dev/null +++ b/x-pack/test/functional_with_es_ssl/ftr_provider_context.d.ts @@ -0,0 +1,12 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { GenericFtrProviderContext } from '@kbn/test/types/ftr'; + +import { pageObjects } from './page_objects'; +import { services } from './services'; + +export type FtrProviderContext = GenericFtrProviderContext; diff --git a/x-pack/test/functional_with_es_ssl/page_objects/index.ts b/x-pack/test/functional_with_es_ssl/page_objects/index.ts new file mode 100644 index 0000000000000..a068ba7dfe81d --- /dev/null +++ b/x-pack/test/functional_with_es_ssl/page_objects/index.ts @@ -0,0 +1,13 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { pageObjects as xpackFunctionalPageObjects } from '../../functional/page_objects'; +import { TriggersActionsPageProvider } from './triggers_actions_ui_page'; + +export const pageObjects = { + ...xpackFunctionalPageObjects, + triggersActionsUI: TriggersActionsPageProvider, +}; diff --git a/x-pack/test/functional_with_es_ssl/page_objects/triggers_actions_ui_page.ts b/x-pack/test/functional_with_es_ssl/page_objects/triggers_actions_ui_page.ts new file mode 100644 index 0000000000000..ce68109771487 --- /dev/null +++ b/x-pack/test/functional_with_es_ssl/page_objects/triggers_actions_ui_page.ts @@ -0,0 +1,97 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { FtrProviderContext } from '../ftr_provider_context'; + +const ENTER_KEY = '\uE007'; + +export function TriggersActionsPageProvider({ getService }: FtrProviderContext) { + const find = getService('find'); + const testSubjects = getService('testSubjects'); + + return { + async getSectionHeadingText() { + return await testSubjects.getVisibleText('appTitle'); + }, + async clickCreateConnectorButton() { + const createBtn = await find.byCssSelector( + '[data-test-subj="createActionButton"],[data-test-subj="createFirstActionButton"]' + ); + await createBtn.click(); + }, + async searchConnectors(searchText: string) { + const searchBox = await find.byCssSelector('[data-test-subj="actionsList"] .euiFieldSearch'); + await searchBox.click(); + await searchBox.clearValue(); + await searchBox.type(searchText); + await searchBox.pressKeys(ENTER_KEY); + await find.byCssSelector( + '.euiBasicTable[data-test-subj="actionsTable"]:not(.euiBasicTable-loading)' + ); + }, + async searchAlerts(searchText: string) { + const searchBox = await testSubjects.find('alertSearchField'); + await searchBox.click(); + await searchBox.clearValue(); + await searchBox.type(searchText); + await searchBox.pressKeys(ENTER_KEY); + await find.byCssSelector( + '.euiBasicTable[data-test-subj="alertsList"]:not(.euiBasicTable-loading)' + ); + }, + async getConnectorsList() { + const table = await find.byCssSelector('[data-test-subj="actionsList"] table'); + const $ = await table.parseDomContent(); + return $.findTestSubjects('connectors-row') + .toArray() + .map(row => { + return { + name: $(row) + .findTestSubject('connectorsTableCell-name') + .find('.euiTableCellContent') + .text(), + actionType: $(row) + .findTestSubject('connectorsTableCell-actionType') + .find('.euiTableCellContent') + .text(), + referencedByCount: $(row) + .findTestSubject('connectorsTableCell-referencedByCount') + .find('.euiTableCellContent') + .text(), + }; + }); + }, + async getAlertsList() { + const table = await find.byCssSelector('[data-test-subj="alertsList"] table'); + const $ = await table.parseDomContent(); + return $.findTestSubjects('alert-row') + .toArray() + .map(row => { + return { + name: $(row) + .findTestSubject('alertsTableCell-name') + .find('.euiTableCellContent') + .text(), + tagsText: $(row) + .findTestSubject('alertsTableCell-tagsText') + .find('.euiTableCellContent') + .text(), + alertType: $(row) + .findTestSubject('alertsTableCell-alertType') + .find('.euiTableCellContent') + .text(), + interval: $(row) + .findTestSubject('alertsTableCell-interval') + .find('.euiTableCellContent') + .text(), + }; + }); + }, + async changeTabs(tab: 'alertsTab' | 'connectorsTab') { + return await testSubjects.click(tab); + }, + }; +} diff --git a/x-pack/test/functional_with_es_ssl/services/index.ts b/x-pack/test/functional_with_es_ssl/services/index.ts new file mode 100644 index 0000000000000..6e96921c25a31 --- /dev/null +++ b/x-pack/test/functional_with_es_ssl/services/index.ts @@ -0,0 +1,11 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +import { services as xpackFunctionalServices } from '../../functional/services'; + +export const services = { + ...xpackFunctionalServices, +}; diff --git a/x-pack/test/licensing_plugin/public/updates.ts b/x-pack/test/licensing_plugin/public/updates.ts index 6d2253fe83868..2e750dcea1a59 100644 --- a/x-pack/test/licensing_plugin/public/updates.ts +++ b/x-pack/test/licensing_plugin/public/updates.ts @@ -32,7 +32,7 @@ export default function(ftrContext: FtrProviderContext) { // this call enforces signature check to detect license update // and causes license re-fetch await setup.core.http.get('/'); - await testUtils.delay(100); + await testUtils.delay(500); const licensing: LicensingPluginSetup = setup.plugins.licensing; licensing.license$.subscribe(license => cb(license.type)); @@ -48,7 +48,7 @@ export default function(ftrContext: FtrProviderContext) { // this call enforces signature check to detect license update // and causes license re-fetch await setup.core.http.get('/'); - await testUtils.delay(100); + await testUtils.delay(500); const licensing: LicensingPluginSetup = setup.plugins.licensing; licensing.license$.subscribe(license => cb(license.type)); @@ -64,7 +64,7 @@ export default function(ftrContext: FtrProviderContext) { // this call enforces signature check to detect license update // and causes license re-fetch await setup.core.http.get('/'); - await testUtils.delay(100); + await testUtils.delay(500); const licensing: LicensingPluginSetup = setup.plugins.licensing; licensing.license$.subscribe(license => cb(license.type)); @@ -80,7 +80,7 @@ export default function(ftrContext: FtrProviderContext) { // this call enforces signature check to detect license update // and causes license re-fetch await setup.core.http.get('/'); - await testUtils.delay(100); + await testUtils.delay(500); const licensing: LicensingPluginSetup = setup.plugins.licensing; licensing.license$.subscribe(license => cb(license.type)); diff --git a/x-pack/test/oidc_api_integration/apis/authorization_code_flow/oidc_auth.js b/x-pack/test/oidc_api_integration/apis/authorization_code_flow/oidc_auth.js index 9556693d2fd7c..57bcd1b041c49 100644 --- a/x-pack/test/oidc_api_integration/apis/authorization_code_flow/oidc_auth.js +++ b/x-pack/test/oidc_api_integration/apis/authorization_code_flow/oidc_auth.js @@ -125,14 +125,14 @@ export default function({ getService }) { it('should fail if OpenID Connect response is not complemented with handshake cookie', async () => { await supertest - .get(`/api/security/oidc?code=thisisthecode&state=${stateAndNonce.state}`) + .get(`/api/security/oidc/callback?code=thisisthecode&state=${stateAndNonce.state}`) .set('kbn-xsrf', 'xxx') .expect(401); }); it('should fail if state is not matching', async () => { await supertest - .get(`/api/security/oidc?code=thisisthecode&state=someothervalue`) + .get(`/api/security/oidc/callback?code=thisisthecode&state=someothervalue`) .set('kbn-xsrf', 'xxx') .set('Cookie', handshakeCookie.cookieString()) .expect(401); @@ -140,7 +140,7 @@ export default function({ getService }) { it('should succeed if both the OpenID Connect response and the cookie are provided', async () => { const oidcAuthenticationResponse = await supertest - .get(`/api/security/oidc?code=code1&state=${stateAndNonce.state}`) + .get(`/api/security/oidc/callback?code=code1&state=${stateAndNonce.state}`) .set('kbn-xsrf', 'xxx') .set('Cookie', handshakeCookie.cookieString()) .expect(302); @@ -195,7 +195,7 @@ export default function({ getService }) { .expect(200); const oidcAuthenticationResponse = await supertest - .get(`/api/security/oidc?code=code2&state=${stateAndNonce.state}`) + .get(`/api/security/oidc/callback?code=code2&state=${stateAndNonce.state}`) .set('kbn-xsrf', 'xxx') .set('Cookie', handshakeCookie.cookieString()) .expect(302); @@ -245,7 +245,7 @@ export default function({ getService }) { .expect(200); const oidcAuthenticationResponse = await supertest - .get(`/api/security/oidc?code=code1&state=${stateAndNonce.state}`) + .get(`/api/security/oidc/callback?code=code1&state=${stateAndNonce.state}`) .set('kbn-xsrf', 'xxx') .set('Cookie', sessionCookie.cookieString()) .expect(302); @@ -320,7 +320,7 @@ export default function({ getService }) { .expect(200); const oidcAuthenticationResponse = await supertest - .get(`/api/security/oidc?code=code1&state=${stateAndNonce.state}`) + .get(`/api/security/oidc/callback?code=code1&state=${stateAndNonce.state}`) .set('kbn-xsrf', 'xxx') .set('Cookie', handshakeCookie.cookieString()) .expect(302); @@ -409,7 +409,7 @@ export default function({ getService }) { .expect(200); const oidcAuthenticationResponse = await supertest - .get(`/api/security/oidc?code=code1&state=${stateAndNonce.state}`) + .get(`/api/security/oidc/callback?code=code1&state=${stateAndNonce.state}`) .set('kbn-xsrf', 'xxx') .set('Cookie', handshakeCookie.cookieString()) .expect(302); @@ -499,7 +499,7 @@ export default function({ getService }) { .expect(200); const oidcAuthenticationResponse = await supertest - .get(`/api/security/oidc?code=code1&state=${stateAndNonce.state}`) + .get(`/api/security/oidc/callback?code=code1&state=${stateAndNonce.state}`) .set('kbn-xsrf', 'xxx') .set('Cookie', handshakeCookie.cookieString()) .expect(302); diff --git a/x-pack/test/oidc_api_integration/apis/implicit_flow/oidc_auth.ts b/x-pack/test/oidc_api_integration/apis/implicit_flow/oidc_auth.ts index 0e07f01776713..1f5a64835416a 100644 --- a/x-pack/test/oidc_api_integration/apis/implicit_flow/oidc_auth.ts +++ b/x-pack/test/oidc_api_integration/apis/implicit_flow/oidc_auth.ts @@ -66,7 +66,7 @@ export default function({ getService }: FtrProviderContext) { // Check that script that forwards URL fragment worked correctly. expect(dom.window.location.href).to.be( - '/api/security/oidc?authenticationResponseURI=https%3A%2F%2Fkibana.com%2Fapi%2Fsecurity%2Foidc%2Fimplicit%23token%3Dsome_token%26access_token%3Dsome_access_token' + '/api/security/oidc/callback?authenticationResponseURI=https%3A%2F%2Fkibana.com%2Fapi%2Fsecurity%2Foidc%2Fimplicit%23token%3Dsome_token%26access_token%3Dsome_access_token' ); }); @@ -76,7 +76,7 @@ export default function({ getService }: FtrProviderContext) { await supertest .get( - `/api/security/oidc?authenticationResponseURI=${encodeURIComponent( + `/api/security/oidc/callback?authenticationResponseURI=${encodeURIComponent( authenticationResponse )}` ) @@ -90,7 +90,7 @@ export default function({ getService }: FtrProviderContext) { await supertest .get( - `/api/security/oidc?authenticationResponseURI=${encodeURIComponent( + `/api/security/oidc/callback?authenticationResponseURI=${encodeURIComponent( authenticationResponse )}` ) @@ -100,13 +100,13 @@ export default function({ getService }: FtrProviderContext) { }); // FLAKY: https://github.com/elastic/kibana/issues/43938 - it.skip('should succeed if both the OpenID Connect response and the cookie are provided', async () => { + it('should succeed if both the OpenID Connect response and the cookie are provided', async () => { const { idToken, accessToken } = createTokens('1', stateAndNonce.nonce); const authenticationResponse = `https://kibana.com/api/security/oidc/implicit#id_token=${idToken}&state=${stateAndNonce.state}&token_type=bearer&access_token=${accessToken}`; const oidcAuthenticationResponse = await supertest .get( - `/api/security/oidc?authenticationResponseURI=${encodeURIComponent( + `/api/security/oidc/callback?authenticationResponseURI=${encodeURIComponent( authenticationResponse )}` ) diff --git a/x-pack/test/oidc_api_integration/config.ts b/x-pack/test/oidc_api_integration/config.ts index 184ccbcdfa691..724ffd35cc9e3 100644 --- a/x-pack/test/oidc_api_integration/config.ts +++ b/x-pack/test/oidc_api_integration/config.ts @@ -32,7 +32,7 @@ export default async function({ readConfigFile }: FtrConfigProviderContext) { `xpack.security.authc.realms.oidc.oidc1.rp.client_id=0oa8sqpov3TxMWJOt356`, `xpack.security.authc.realms.oidc.oidc1.rp.client_secret=0oa8sqpov3TxMWJOt356`, `xpack.security.authc.realms.oidc.oidc1.rp.response_type=code`, - `xpack.security.authc.realms.oidc.oidc1.rp.redirect_uri=http://localhost:${kibanaPort}/api/security/oidc`, + `xpack.security.authc.realms.oidc.oidc1.rp.redirect_uri=http://localhost:${kibanaPort}/api/security/oidc/callback`, `xpack.security.authc.realms.oidc.oidc1.op.authorization_endpoint=https://test-op.elastic.co/oauth2/v1/authorize`, `xpack.security.authc.realms.oidc.oidc1.op.endsession_endpoint=https://test-op.elastic.co/oauth2/v1/endsession`, `xpack.security.authc.realms.oidc.oidc1.op.token_endpoint=http://localhost:${kibanaPort}/api/oidc_provider/token_endpoint`, diff --git a/x-pack/test/pki_api_integration/config.ts b/x-pack/test/pki_api_integration/config.ts index 50b41ad251827..a445b3d4943b0 100644 --- a/x-pack/test/pki_api_integration/config.ts +++ b/x-pack/test/pki_api_integration/config.ts @@ -7,7 +7,7 @@ import { resolve } from 'path'; import { FtrConfigProviderContext } from '@kbn/test/types/ftr'; // @ts-ignore -import { CA_CERT_PATH, ES_KEY_PATH, ES_CERT_PATH } from '@kbn/dev-utils'; +import { CA_CERT_PATH, KBN_CERT_PATH, KBN_KEY_PATH } from '@kbn/dev-utils'; import { services } from './services'; export default async function({ readConfigFile }: FtrConfigProviderContext) { @@ -54,8 +54,8 @@ export default async function({ readConfigFile }: FtrConfigProviderContext) { serverArgs: [ ...xPackAPITestsConfig.get('kbnTestServer.serverArgs'), '--server.ssl.enabled=true', - `--server.ssl.key=${ES_KEY_PATH}`, - `--server.ssl.certificate=${ES_CERT_PATH}`, + `--server.ssl.key=${KBN_KEY_PATH}`, + `--server.ssl.certificate=${KBN_CERT_PATH}`, `--server.ssl.certificateAuthorities=${JSON.stringify([ CA_CERT_PATH, resolve(__dirname, './fixtures/kibana_ca.crt'), diff --git a/x-pack/test/pki_api_integration/fixtures/README.md b/x-pack/test/pki_api_integration/fixtures/README.md index 0fcbc76183b48..ac2be482c6e33 100644 --- a/x-pack/test/pki_api_integration/fixtures/README.md +++ b/x-pack/test/pki_api_integration/fixtures/README.md @@ -1,7 +1,16 @@ # PKI Fixtures -* `es_ca.key` - the CA key used to sign certificates from @kbn/dev-utils that are used and trusted by test Elasticsearch server. -* `first_client.p12` and `second_client.p12` - the client certificate bundles signed by `es_ca.key` and hence trusted by -both test Kibana and Elasticsearch servers. +* `first_client.p12` and `second_client.p12` - the client certificate bundles signed by the Elastic Stack CA (in `kbn-dev-utils`) +and hence trusted by both test Kibana and Elasticsearch servers. * `untrusted_client.p12` - the client certificate bundle trusted by test Kibana server, but not test Elasticsearch test server. -* `kibana_ca.crt` and `kibana_ca.key` - the CA certificate and key trusted by test Kibana server only. \ No newline at end of file +* `kibana_ca.crt` and `kibana_ca.key` - the CA certificate and key trusted by test Kibana server only. + +The `first_client.p12` and `second_client.p12` files were generated the same time as the other certificates in `kbn-dev-utils`, using the +following commands: + +``` +bin/elasticsearch-certutil cert -days 18250 --ca elastic-stack-ca.p12 --ca-pass castorepass --name first_client --pass "" +bin/elasticsearch-certutil cert -days 18250 --ca elastic-stack-ca.p12 --ca-pass castorepass --name second_client --pass "" +``` + +If that CA is ever changed, these two files must be regenerated. diff --git a/x-pack/test/pki_api_integration/fixtures/es_ca.key b/x-pack/test/pki_api_integration/fixtures/es_ca.key deleted file mode 100644 index 5428f86851e5a..0000000000000 --- a/x-pack/test/pki_api_integration/fixtures/es_ca.key +++ /dev/null @@ -1,27 +0,0 @@ ------BEGIN RSA PRIVATE KEY----- -MIIEpAIBAAKCAQEAjSJiqfwPZfvgHO1OZbxzgPn2EW/KewIHXygTAdL926Pm6R45 -G5H972B46NcSUoOZbOhDyvg6OKMJAICiXa85yOf3nyTo4APspR+K4AH60SEJohRF -mZwL/OryfiKvN5n5DxC2+Hb1wouwBUJM6DP62C24ve8YWuWwNkhJqWKe1YQUzPc1 -svqvU5uaHTzvLtp++RqSDNkcIqWl5S9Ip5PtOv6MHkCaIr2g4KQzplFwhT5qVd1Q -nYVBsQ0D8htLqUJBfjW0KHouEZpbjxJlc+EuyExS1o1+y3mVT+t2yZHAoIquh5ve -5A7a/RGJTyoR5u1DFs4Tcx2378kjA86gCQtClwIDAQABAoIBAFTOGKMzxrztQJmh -Lr6LIoyZpnaLygtoCK3xEprCAbB9KD9j3cTnUMMKIR0oPuY+FW8Pkczgo3ts2/fl -U6sfo4VJfc2vDA+vy/7cmUJJbkFDrNorfDb1QW7UbqnEhazPZIzc6lUahkpETZyb -XkMZGN3Ve3EFvojAA8ZaYYjarb52HRddLPZJ7c8ZiHfJ1jHNIvx6dIQ6CJVuovBJ -OGbbSAK8MjUtOI2XzWNHgUqGHcjVDFysuAac3ckK14TaN4KVNRl+usAMkZwqSM5u -j/ATFL9hx7nkzh3KWPsuOLMoLX7JN81z0YtT52wTxJoSiZKk/u91JHZ3NcrsOSPS -oLvVkyECgYEA16qtXvtmboAbqeuXf0nF+7QD0b+MdaRFIacqTG0LpEgY9Tjgs9Pn -6z44tHABWPVkRLNQZiky99MAq4Ci354Bk9dmylCw9ADH78VGmKWklbQEr1rw4dqm -DHTj9NQ79SyTdiasQjnnxCilWkrO6ZUqD8og4DT5MhzfxO/ZND8arGsCgYEAp4df -oI5lwlc1n9X/G9RQAKwNM5un8RmReleUVemjkcvWwvZVEjV0Gcc1WtjB+77Y5B9B -CM3laURDGrAgX5VS/I2jb0xqBNUr8XccSkDQAP9UuVPZgxpS+8d0R3fxVzniHWwR -WC2dW/Is40i/6+7AkFXhkiFiqxkvSg4pWHPazYUCgYB/gP7C+urSRZcVXJ3SuXD9 -oK3pYc/O9XGRtd0CFi4d0CpBQIFIj+27XKv1sYp6Z4oCO+k6nPzvG6Z3vrOMdUQF -fgHddttHRvbtwLo+ISAvCaEDc0aaoMQu9SSYaKmSB+qenbqV5NorVMR9n2C5JGEb -uKq7I1Z41C1Pp2XIx84jRQKBgQCjKvfZsjesZDJnfg9dtJlDPlARXt7gte16gkiI -sOnOfAGtnCzZclSlMuBlnk65enVXIpW+FIQH1iOhn7+4OQE92FpBceSk1ldZdJCK -RbwR7J5Bb0igJ4iBkA9R+KGIOmlgDLyL7MmiHyrXKCk9iynkqrDsGjY2vW3QrCBa -9WQ73QKBgQDAYZzplO4TPoPK9AnxoW/HpSwGEO7Fb8fLyPg94CvHn4QBCFJUKuTn -hBp/TJgF6CjQWQMr2FKVFF33Ow7+Qa96YGvmYlEjR/71D4Rlprj5JJpuO154DI3I -YIMNTjvwEQEI+YamMarKsz0Kq+I1EYSAf6bQ4H2PgxDxwTXaLkl0RA== ------END RSA PRIVATE KEY----- diff --git a/x-pack/test/pki_api_integration/fixtures/first_client.p12 b/x-pack/test/pki_api_integration/fixtures/first_client.p12 index 62da80d9ab80e..9d838199e8392 100644 Binary files a/x-pack/test/pki_api_integration/fixtures/first_client.p12 and b/x-pack/test/pki_api_integration/fixtures/first_client.p12 differ diff --git a/x-pack/test/pki_api_integration/fixtures/second_client.p12 b/x-pack/test/pki_api_integration/fixtures/second_client.p12 index 1c85686cb7b68..f41c0e030ba79 100644 Binary files a/x-pack/test/pki_api_integration/fixtures/second_client.p12 and b/x-pack/test/pki_api_integration/fixtures/second_client.p12 differ diff --git a/x-pack/test/plugin_api_integration/plugins/task_manager/index.js b/x-pack/test/plugin_api_integration/plugins/task_manager/index.js index b0e46543b4e76..50fb9571c2687 100644 --- a/x-pack/test/plugin_api_integration/plugins/task_manager/index.js +++ b/x-pack/test/plugin_api_integration/plugins/task_manager/index.js @@ -28,7 +28,11 @@ export default function TaskTestingAPI(kibana) { }, init(server) { - const taskManager = server.plugins.task_manager; + const taskManager = { + ...server.newPlatform.setup.plugins.taskManager, + ...server.newPlatform.start.plugins.taskManager, + }; + const legacyTaskManager = server.plugins.task_manager; const defaultSampleTaskConfig = { timeout: '1m', @@ -128,7 +132,7 @@ export default function TaskTestingAPI(kibana) { }, }); - initRoutes(server, taskTestingEvents); + initRoutes(server, taskManager, legacyTaskManager, taskTestingEvents); }, }); } diff --git a/x-pack/test/plugin_api_integration/plugins/task_manager/init_routes.js b/x-pack/test/plugin_api_integration/plugins/task_manager/init_routes.js index 3330d08dfd0d2..c0dcd99525915 100644 --- a/x-pack/test/plugin_api_integration/plugins/task_manager/init_routes.js +++ b/x-pack/test/plugin_api_integration/plugins/task_manager/init_routes.js @@ -23,9 +23,7 @@ const taskManagerQuery = { }, }; -export function initRoutes(server, taskTestingEvents) { - const taskManager = server.plugins.task_manager; - +export function initRoutes(server, taskManager, legacyTaskManager, taskTestingEvents) { server.route({ path: '/api/sample_tasks/schedule', method: 'POST', @@ -62,6 +60,45 @@ export function initRoutes(server, taskTestingEvents) { }, }); + /* + Schedule using legacy Api + */ + server.route({ + path: '/api/sample_tasks/schedule_legacy', + method: 'POST', + config: { + validate: { + payload: Joi.object({ + task: Joi.object({ + taskType: Joi.string().required(), + schedule: Joi.object({ + interval: Joi.string(), + }).optional(), + interval: Joi.string().optional(), + params: Joi.object().required(), + state: Joi.object().optional(), + id: Joi.string().optional(), + }), + }), + }, + }, + async handler(request) { + try { + const { task: taskFields } = request.payload; + const task = { + ...taskFields, + scope: [scope], + }; + + const taskResult = await legacyTaskManager.schedule(task, { request }); + + return taskResult; + } catch (err) { + return err; + } + }, + }); + server.route({ path: '/api/sample_tasks/run_now', method: 'POST', diff --git a/x-pack/test/plugin_api_integration/test_suites/task_manager/task_manager_integration.js b/x-pack/test/plugin_api_integration/test_suites/task_manager/task_manager_integration.js index 486a93aba76df..0b1c1cbb5af29 100644 --- a/x-pack/test/plugin_api_integration/test_suites/task_manager/task_manager_integration.js +++ b/x-pack/test/plugin_api_integration/test_suites/task_manager/task_manager_integration.js @@ -11,7 +11,7 @@ import supertestAsPromised from 'supertest-as-promised'; const { task: { properties: taskManagerIndexMapping }, -} = require('../../../../legacy/plugins/task_manager/mappings.json'); +} = require('../../../../legacy/plugins/task_manager/server/mappings.json'); export default function({ getService }) { const es = getService('legacyEs'); @@ -74,6 +74,15 @@ export default function({ getService }) { .then(response => response.body); } + function scheduleTaskUsingLegacyApi(task) { + return supertest + .post('/api/sample_tasks/schedule_legacy') + .set('kbn-xsrf', 'xxx') + .send({ task }) + .expect(200) + .then(response => response.body); + } + function runTaskNow(task) { return supertest .post('/api/sample_tasks/run_now') @@ -494,5 +503,15 @@ export default function({ getService }) { expect(getTaskById(tasks, longRunningTask.id).state.count).to.eql(1); }); }); + + it('should retain the legacy api until v8.0.0', async () => { + const result = await scheduleTaskUsingLegacyApi({ + id: 'task-with-legacy-api', + taskType: 'sampleTask', + params: {}, + }); + + expect(result.id).to.be('task-with-legacy-api'); + }); }); } diff --git a/x-pack/test/plugin_api_perf/plugins/task_manager_performance/index.js b/x-pack/test/plugin_api_perf/plugins/task_manager_performance/index.js index c3cd582fd59c4..87e3b3b66a201 100644 --- a/x-pack/test/plugin_api_perf/plugins/task_manager_performance/index.js +++ b/x-pack/test/plugin_api_perf/plugins/task_manager_performance/index.js @@ -23,7 +23,10 @@ export default function TaskManagerPerformanceAPI(kibana) { }, init(server) { - const taskManager = server.plugins.task_manager; + const taskManager = { + ...server.newPlatform.setup.plugins.taskManager, + ...server.newPlatform.start.plugins.taskManager, + }; const performanceState = resetPerfState({}); let lastFlush = new Date(); diff --git a/x-pack/test/plugin_api_perf/plugins/task_manager_performance/init_routes.js b/x-pack/test/plugin_api_perf/plugins/task_manager_performance/init_routes.js index ca6d8707f5c58..6cd706a6ebecd 100644 --- a/x-pack/test/plugin_api_perf/plugins/task_manager_performance/init_routes.js +++ b/x-pack/test/plugin_api_perf/plugins/task_manager_performance/init_routes.js @@ -9,7 +9,10 @@ import { range, chunk } from 'lodash'; const scope = 'perf-testing'; export function initRoutes(server, performanceState) { - const taskManager = server.plugins.task_manager; + const taskManager = { + ...server.newPlatform.setup.plugins.taskManager, + ...server.newPlatform.start.plugins.taskManager, + }; server.route({ path: '/api/perf_tasks', diff --git a/x-pack/test/reporting/README.md b/x-pack/test/reporting/README.md index 30859fa96c015..d4a6c20a835e2 100644 --- a/x-pack/test/reporting/README.md +++ b/x-pack/test/reporting/README.md @@ -82,16 +82,6 @@ node scripts/functional_tests_server.js --config test/reporting/configs/chromium **Note:** Dashboard has some snapshot testing too, in `_dashboard_snapshots.js`. This test watches for a command line flag `--updateBaselines` which automates updating the baselines. Probably worthwhile to do some similar here in the long run. - ### Adding a new BWC test - - We have tests that ensure the latest version of Kibana will continue to generate reports from URLs generated in previous versions, to ensure backward compatibility. These tests are in `api/bwc_generation_urls.js`. It's important to update these every now and then and add new ones, especially if anything in the URL changed in a release. - - To add test coverage for a specific minor release,: -1. Checkout previous branch, e.g. `git checkout upstream/6.4` -2. Sync your environment via `yarn kbn bootstrap` (Note, if you run into problems you may want to first clean via `yarn kbn clean`) -3. Start up kibana and Elasticsearch (`yarn es snapshot --license trial` in one terminal, and `yarn start` in another) -4. Load the reporting test data that is used in the tests. Ensure you are in the `x-pack` directory and run: - ``` node ../scripts/es_archiver.js --es-url http://elastic:changeme@localhost:9200 load ../../../../test/functional/fixtures/es_archiver/dashboard/current/kibana ``` diff --git a/x-pack/test/reporting/api/bwc_existing_indexes.js b/x-pack/test/reporting/api/bwc_existing_indexes.js deleted file mode 100644 index da2ed87159209..0000000000000 --- a/x-pack/test/reporting/api/bwc_existing_indexes.js +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import * as GenerationUrls from './generation_urls'; - -/** - * This file tests the situation when a reporting index spans releases. By default reporting indexes are created - * on a weekly basis, but this is configurable so it is possible a user has this set to yearly. In that event, it - * is possible report data is getting posted to an index that was created by a very old version. We don't have a - * reporting index migration plan, so this test is important to ensure BWC, or that in the event we decide to make - * a major change in a major release, we handle it properly. - */ - -export default function({ getService }) { - const esArchiver = getService('esArchiver'); - const reportingAPI = getService('reportingAPI'); - const usageAPI = getService('usageAPI'); - - // FLAKY: https://github.com/elastic/kibana/issues/42725 - describe.skip('BWC report generation into existing indexes', () => { - let expectedCompletedReportCount; - let cleanupIndexAlias; - - describe('existing 6_2 index', () => { - before('load data and add index alias', async () => { - await reportingAPI.deleteAllReportingIndexes(); - await esArchiver.load('bwc/6_2'); - - // The index name in the 6_2 archive. - const ARCHIVED_REPORTING_INDEX = '.reporting-2018.03.11'; - cleanupIndexAlias = await reportingAPI.coerceReportsIntoExistingIndex( - ARCHIVED_REPORTING_INDEX - ); - - const stats = await usageAPI.getUsageStats(); - expectedCompletedReportCount = await reportingAPI.getCompletedReportCount(stats); - }); - - after('remove index alias', async () => { - await cleanupIndexAlias(); - }); - - // Might not be great test practice to lump all these jobs together but reporting takes awhile and it'll be - // more efficient to post them all up front, then sequentially. - it('multiple jobs posted', async () => { - const reportPaths = []; - reportPaths.push( - await reportingAPI.postJob(GenerationUrls.CSV_DISCOVER_KUERY_AND_FILTER_6_3) - ); - reportPaths.push( - await reportingAPI.postJob(GenerationUrls.PDF_PRESERVE_DASHBOARD_FILTER_6_3) - ); - reportPaths.push( - await reportingAPI.postJob(GenerationUrls.PDF_PRESERVE_PIE_VISUALIZATION_6_3) - ); - reportPaths.push(await reportingAPI.postJob(GenerationUrls.PDF_PRINT_DASHBOARD_6_3)); - reportPaths.push( - await reportingAPI.postJob( - GenerationUrls.PDF_PRINT_PIE_VISUALIZATION_FILTER_AND_SAVED_SEARCH_6_3 - ) - ); - - await reportingAPI.expectAllJobsToFinishSuccessfully(reportPaths); - }).timeout(1540000); - - it('jobs completed successfully', async () => { - const stats = await usageAPI.getUsageStats(); - expectedCompletedReportCount += 5; - reportingAPI.expectCompletedReportCount(stats, expectedCompletedReportCount); - }); - }); - }); -} diff --git a/x-pack/test/reporting/api/bwc_generation_urls.js b/x-pack/test/reporting/api/bwc_generation_urls.js deleted file mode 100644 index 25b40ff3f74a8..0000000000000 --- a/x-pack/test/reporting/api/bwc_generation_urls.js +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -import * as GenerationUrls from './generation_urls'; - -export default function({ getService }) { - const reportingAPI = getService('reportingAPI'); - const usageAPI = getService('usageAPI'); - - describe('BWC report generation urls', () => { - describe('Pre 6_2', () => { - before(async () => { - await reportingAPI.deleteAllReportingIndexes(); - }); - - // The URL being tested was captured from release 6.4 and then the layout section was removed to test structure before - // preserve_layout was introduced. See https://github.com/elastic/kibana/issues/23414 - it('job posted successfully', async () => { - const path = await reportingAPI.postJob(GenerationUrls.PDF_PRINT_DASHBOARD_PRE_6_2); - await reportingAPI.waitForJobToFinish(path); - const stats = await usageAPI.getUsageStats(); - reportingAPI.expectCompletedReportCount(stats, 1); - }).timeout(500000); - }); - - describe('6_2', () => { - before(async () => { - await reportingAPI.deleteAllReportingIndexes(); - }); - - // Might not be great test practice to lump all these jobs together but reporting takes awhile and it'll be - // more efficient to post them all up front, then sequentially. - it('multiple jobs posted', async () => { - const reportPaths = []; - reportPaths.push(await reportingAPI.postJob(GenerationUrls.PDF_PRINT_DASHBOARD_6_2)); - reportPaths.push(await reportingAPI.postJob(GenerationUrls.PDF_PRESERVE_VISUALIZATION_6_2)); - reportPaths.push(await reportingAPI.postJob(GenerationUrls.CSV_DISCOVER_FILTER_QUERY_6_2)); - - await reportingAPI.expectAllJobsToFinishSuccessfully(reportPaths); - }).timeout(1540000); - - it('jobs completed successfully', async () => { - const stats = await usageAPI.getUsageStats(); - reportingAPI.expectCompletedReportCount(stats, 3); - }); - }); - - // 6.3 urls currently being tested as part of the "bwc_existing_indexes" test suite. Reports are time consuming, - // don't replicate tests if we don't need to, so no specific 6_3 url tests here. - }); -} diff --git a/x-pack/test/reporting/api/chromium_tests.js b/x-pack/test/reporting/api/chromium_tests.js index 6594a80db491b..2d5a31bb40da3 100644 --- a/x-pack/test/reporting/api/chromium_tests.js +++ b/x-pack/test/reporting/api/chromium_tests.js @@ -27,8 +27,6 @@ export default function({ loadTestFile, getService }) { await esArchiver.unload(OSS_DATA_ARCHIVE_PATH); }); - loadTestFile(require.resolve('./bwc_existing_indexes')); - loadTestFile(require.resolve('./bwc_generation_urls')); loadTestFile(require.resolve('./usage')); }); } diff --git a/x-pack/test/reporting/api/generate/csv_saved_search.ts b/x-pack/test/reporting/api/generate/csv_saved_search.ts index e0cd19bf258ea..ed44ba8ea4a76 100644 --- a/x-pack/test/reporting/api/generate/csv_saved_search.ts +++ b/x-pack/test/reporting/api/generate/csv_saved_search.ts @@ -14,6 +14,7 @@ import { CSV_RESULT_TIMEBASED, CSV_RESULT_TIMELESS, CSV_RESULT_NANOS, + CSV_RESULT_DOCVALUE, } from './fixtures'; interface GenerateOpts { @@ -268,6 +269,68 @@ export default function({ getService }: { getService: any }) { await esArchiver.unload('reporting/hugedata'); }); + + it('for docvalue_fields', async () => { + // load test data that contains a saved search and documents + await esArchiver.load('reporting/ecommerce'); + await esArchiver.load('reporting/ecommerce_kibana'); + + const params = { + searchId: 'search:6091ead0-1c6d-11ea-a100-8589bb9d7c6b', + postPayload: { + timerange: { + min: '2019-06-26T06:20:28Z', + max: '2019-06-26T07:27:58Z', + timezone: 'UTC', + }, + state: { + sort: [{ order_date: { order: 'desc', unmapped_type: 'boolean' } }], + docvalue_fields: [ + { field: 'customer_birth_date', format: 'date_time' }, + { field: 'order_date', format: 'date_time' }, + { field: 'products.created_on', format: 'date_time' }, + ], + query: { + bool: { + must: [], + filter: [ + { match_all: {} }, + { match_all: {} }, + { + range: { + order_date: { + gte: '2019-06-26T06:20:28.066Z', + lte: '2019-06-26T07:27:58.573Z', + format: 'strict_date_optional_time', + }, + }, + }, + ], + should: [], + must_not: [], + }, + }, + }, + }, + isImmediate: true, + }; + const { + status: resStatus, + text: resText, + type: resType, + } = (await generateAPI.getCsvFromSavedSearch( + params.searchId, + params.postPayload, + params.isImmediate + )) as supertest.Response; + + expect(resStatus).to.eql(200); + expect(resType).to.eql('text/csv'); + expect(resText).to.eql(CSV_RESULT_DOCVALUE); + + await esArchiver.unload('reporting/ecommerce'); + await esArchiver.unload('reporting/ecommerce_kibana'); + }); }); // FLAKY: https://github.com/elastic/kibana/issues/37471 diff --git a/x-pack/test/reporting/api/generate/fixtures.ts b/x-pack/test/reporting/api/generate/fixtures.ts index a9fde9dfb9363..c68e417498917 100644 --- a/x-pack/test/reporting/api/generate/fixtures.ts +++ b/x-pack/test/reporting/api/generate/fixtures.ts @@ -174,3 +174,14 @@ format:strict_date_optional_time,gte:'2004-09-17T21:19:34.213Z',lte:'2019-09-17T 213Z')))),must:!(),must_not:!(),should:!())),script_fields:(),sort:!(('@timestamp':(order :desc,unmapped_type:boolean))),stored_fields:!('@timestamp',clientip,extension),version:! t),index:'logstash-*'),title:'A Saved Search With a DATE FILTER',type:search)`; + +export const CSV_RESULT_DOCVALUE = `"order_date",category,currency,"customer_id","order_id","day_of_week_i","order_date","products.created_on",sku +"[""2019-06-26T07:26:24.000Z"",""2019-06-26T07:26:24.000Z""]","[""Men\'s Shoes"",""Men\'s Clothing""]",EUR,49,569743,3,"[""2019-06-26T07:26:24.000Z"",""2019-06-26T07:26:24.000Z""]","[""2016-12-15T07:26:24.000Z"",""2016-12-15T07:26:24.000Z""]","[""ZO0403504035"",""ZO0610306103""]" +"[""2019-06-26T07:20:38.000Z"",""2019-06-26T07:20:38.000Z""]","[""Men\'s Shoes"",""Women\'s Accessories""]",EUR,29,569736,3,"[""2019-06-26T07:20:38.000Z"",""2019-06-26T07:20:38.000Z""]","[""2016-12-15T07:20:38.000Z"",""2016-12-15T07:20:38.000Z""]","[""ZO0517305173"",""ZO0319703197""]" +"[""2019-06-26T07:19:12.000Z"",""2019-06-26T07:19:12.000Z""]","[""Women\'s Clothing"",""Women\'s Shoes""]",EUR,20,569734,3,"[""2019-06-26T07:19:12.000Z"",""2019-06-26T07:19:12.000Z""]","[""2016-12-15T07:19:12.000Z"",""2016-12-15T07:19:12.000Z""]","[""ZO0348703487"",""ZO0141401414""]" +"[""2019-06-26T07:00:29.000Z"",""2019-06-26T07:00:29.000Z""]","[""Women\'s Clothing""]",EUR,17,569716,3,"[""2019-06-26T07:00:29.000Z"",""2019-06-26T07:00:29.000Z""]","[""2016-12-15T07:00:29.000Z"",""2016-12-15T07:00:29.000Z""]","[""ZO0146701467"",""ZO0212902129""]" +"[""2019-06-26T06:56:10.000Z"",""2019-06-26T06:56:10.000Z""]","[""Women\'s Clothing"",""Women\'s Shoes""]",EUR,6,569710,3,"[""2019-06-26T06:56:10.000Z"",""2019-06-26T06:56:10.000Z""]","[""2016-12-15T06:56:10.000Z"",""2016-12-15T06:56:10.000Z""]","[""ZO0053600536"",""ZO0239702397""]" +"[""2019-06-26T06:47:31.000Z"",""2019-06-26T06:47:31.000Z""]","[""Men\'s Shoes""]",EUR,52,569699,3,"[""2019-06-26T06:47:31.000Z"",""2019-06-26T06:47:31.000Z""]","[""2016-12-15T06:47:31.000Z"",""2016-12-15T06:47:31.000Z""]","[""ZO0398603986"",""ZO0521305213""]" +"[""2019-06-26T06:37:26.000Z"",""2019-06-26T06:37:26.000Z""]","[""Men\'s Shoes""]",EUR,50,569694,3,"[""2019-06-26T06:37:26.000Z"",""2019-06-26T06:37:26.000Z""]","[""2016-12-15T06:37:26.000Z"",""2016-12-15T06:37:26.000Z""]","[""ZO0398703987"",""ZO0687806878""]" +"[""2019-06-26T06:21:36.000Z"",""2019-06-26T06:21:36.000Z""]","[""Men\'s Clothing""]",EUR,52,569679,3,"[""2019-06-26T06:21:36.000Z"",""2019-06-26T06:21:36.000Z""]","[""2016-12-15T06:21:36.000Z"",""2016-12-15T06:21:36.000Z""]","[""ZO0433604336"",""ZO0275702757""]" +`; diff --git a/x-pack/test/reporting/api/generation_urls.js b/x-pack/test/reporting/api/generation_urls.js index 182d395704a25..b98b1a26651a1 100644 --- a/x-pack/test/reporting/api/generation_urls.js +++ b/x-pack/test/reporting/api/generation_urls.js @@ -4,13 +4,6 @@ * you may not use this file except in compliance with the Elastic License. */ -// These all have the domain name portion stripped out. The api infrastructure assumes it when we post to it anyhow. - -// The URL below was captured from release 6.4 and then the layout section was removed to test structure before -// preserve_layout was introduced. See https://github.com/elastic/kibana/issues/23414 -export const PDF_PRINT_DASHBOARD_PRE_6_2 = - '/api/reporting/generate/printablePdf?jobParams=(browserTimezone:America%2FNew_York,objectType:dashboard,relativeUrls:!(%27%2Fapp%2Fkibana%23%2Fdashboard%2F2ae34a60-3dd4-11e8-b2b9-5d5dc1715159%3F_g%3D(refreshInterval:(pause:!!t,value:0),time:(from:!%27Mon%2BApr%2B09%2B2018%2B17:56:08%2BGMT-0400!%27,mode:absolute,to:!%27Wed%2BApr%2B11%2B2018%2B17:56:08%2BGMT-0400!%27))%26_a%3D(description:!%27!%27,filters:!!(),fullScreenMode:!!f,options:(hidePanelTitles:!!f,useMargins:!!t),panels:!!((embeddableConfig:(),gridData:(h:15,i:!%271!%27,w:24,x:0,y:0),id:!%27145ced90-3dcb-11e8-8660-4d65aa086b3c!%27,panelIndex:!%271!%27,type:visualization,version:!%276.3.0!%27),(embeddableConfig:(),gridData:(h:15,i:!%272!%27,w:24,x:24,y:0),id:e2023110-3dcb-11e8-8660-4d65aa086b3c,panelIndex:!%272!%27,type:visualization,version:!%276.3.0!%27)),query:(language:lucene,query:!%27!%27),timeRestore:!!f,title:!%27couple%2Bpanels!%27,viewMode:view)%27),title:%27couple%20panels%27)'; - export const PDF_PRINT_DASHBOARD_6_3 = '/api/reporting/generate/printablePdf?jobParams=(browserTimezone:America%2FNew_York,layout:(id:print),objectType:dashboard,relativeUrls:!(%27%2Fapp%2Fkibana%23%2Fdashboard%2F2ae34a60-3dd4-11e8-b2b9-5d5dc1715159%3F_g%3D(refreshInterval:(display:Off,pause:!!f,value:0),time:(from:!%27Mon%2BApr%2B09%2B2018%2B17:56:08%2BGMT-0400!%27,mode:absolute,to:!%27Wed%2BApr%2B11%2B2018%2B17:56:08%2BGMT-0400!%27))%26_a%3D(description:!%27!%27,filters:!!(),fullScreenMode:!!f,options:(hidePanelTitles:!!f,useMargins:!!t),panels:!!((embeddableConfig:(),gridData:(h:15,i:!%271!%27,w:24,x:0,y:0),id:!%27145ced90-3dcb-11e8-8660-4d65aa086b3c!%27,panelIndex:!%271!%27,type:visualization,version:!%276.3.0!%27),(embeddableConfig:(),gridData:(h:15,i:!%272!%27,w:24,x:24,y:0),id:e2023110-3dcb-11e8-8660-4d65aa086b3c,panelIndex:!%272!%27,type:visualization,version:!%276.3.0!%27)),query:(language:lucene,query:!%27!%27),timeRestore:!!f,title:!%27couple%2Bpanels!%27,viewMode:view)%27),title:%27couple%20panels%27)'; export const PDF_PRESERVE_DASHBOARD_FILTER_6_3 = @@ -21,10 +14,3 @@ export const PDF_PRINT_PIE_VISUALIZATION_FILTER_AND_SAVED_SEARCH_6_3 = '/api/reporting/generate/printablePdf?jobParams=(browserTimezone:America%2FNew_York,layout:(id:print),objectType:visualization,relativeUrls:!(%27%2Fapp%2Fkibana%23%2Fvisualize%2Fedit%2Fbefdb6b0-3e59-11e8-9fc3-39e49624228e%3F_g%3D(refreshInterval:(display:Off,pause:!!f,value:0),time:(from:!%27Mon%2BApr%2B09%2B2018%2B17:56:08%2BGMT-0400!%27,mode:absolute,to:!%27Wed%2BApr%2B11%2B2018%2B17:56:08%2BGMT-0400!%27))%26_a%3D(filters:!!((!%27$state!%27:(store:appState),meta:(alias:!!n,disabled:!!f,index:a0f483a0-3dc9-11e8-8660-4d65aa086b3c,key:animal.keyword,negate:!!f,params:(query:dog,type:phrase),type:phrase,value:dog),query:(match:(animal.keyword:(query:dog,type:phrase))))),linked:!!t,query:(language:lucene,query:!%27!%27),uiState:(),vis:(aggs:!!((enabled:!!t,id:!%271!%27,params:(),schema:metric,type:count),(enabled:!!t,id:!%272!%27,params:(field:name.keyword,missingBucket:!!f,missingBucketLabel:Missing,order:desc,orderBy:!%271!%27,otherBucket:!!f,otherBucketLabel:Other,size:5),schema:segment,type:terms)),params:(addLegend:!!t,addTooltip:!!t,isDonut:!!t,labels:(last_level:!!t,show:!!f,truncate:100,values:!!t),legendPosition:right,type:pie),title:!%27Filter%2BTest:%2Banimals:%2Blinked%2Bto%2Bsearch%2Bwith%2Bfilter!%27,type:pie))%27),title:%27Filter%20Test:%20animals:%20linked%20to%20search%20with%20filter%27)'; export const CSV_DISCOVER_KUERY_AND_FILTER_6_3 = '/api/reporting/generate/csv?jobParams=(conflictedTypesFields:!(),fields:!(%27@timestamp%27,agent,bytes,clientip),indexPatternId:%270bf35f60-3dc9-11e8-8660-4d65aa086b3c%27,metaFields:!(_source,_id,_type,_index,_score),searchRequest:(body:(_source:(excludes:!(),includes:!(%27@timestamp%27,agent,bytes,clientip)),docvalue_fields:!(%27@timestamp%27),query:(bool:(filter:!((bool:(minimum_should_match:1,should:!((match:(clientip:%2773.14.212.83%27)))))),must:!((range:(bytes:(gte:100,lt:1000))),(range:(%27@timestamp%27:(format:epoch_millis,gte:1369165215770,lte:1526931615770)))),must_not:!(),should:!())),script_fields:(),sort:!((%27@timestamp%27:(order:desc,unmapped_type:boolean))),stored_fields:!(%27@timestamp%27,agent,bytes,clientip),version:!t),index:%27logstash-*%27),title:%27Bytes%20and%20kuery%20in%20saved%20search%20with%20filter%27,type:search)'; - -export const PDF_PRINT_DASHBOARD_6_2 = - '/api/reporting/generate/printablePdf?jobParams=(browserTimezone:America%2FNew_York,layout:(id:print),objectType:dashboard,queryString:%27_g%3D(refreshInterval:(display:Off,pause:!!f,value:0),time:(from:!%27Mon%2BApr%2B09%2B2018%2B17:56:08%2BGMT-0400!%27,mode:absolute,to:!%27Wed%2BApr%2B11%2B2018%2B17:56:08%2BGMT-0400!%27))%26_a%3D(description:!%27!%27,filters:!!((!%27$state!%27:(store:appState),meta:(alias:!!n,disabled:!!f,field:isDog,index:a0f483a0-3dc9-11e8-8660-4d65aa086b3c,key:isDog,negate:!!f,params:(value:!!t),type:phrase,value:true),script:(script:(inline:!%27boolean%2Bcompare(Supplier%2Bs,%2Bdef%2Bv)%2B%257Breturn%2Bs.get()%2B%253D%253D%2Bv%3B%257Dcompare(()%2B-%253E%2B%257B%2Breturn%2Bdoc%255B!!!%27animal.keyword!!!%27%255D.value%2B%253D%253D%2B!!!%27dog!!!%27%2B%257D,%2Bparams.value)%3B!%27,lang:painless,params:(value:!!t))))),fullScreenMode:!!f,options:(hidePanelTitles:!!f,useMargins:!!t),panels:!!((gridData:(h:3,i:!%274!%27,w:6,x:6,y:0),id:edb65990-53ca-11e8-b481-c9426d020fcd,panelIndex:!%274!%27,type:visualization,version:!%276.2.4!%27),(gridData:(h:3,i:!%275!%27,w:6,x:0,y:0),id:!%270644f890-53cb-11e8-b481-c9426d020fcd!%27,panelIndex:!%275!%27,type:visualization,version:!%276.2.4!%27)),query:(language:lucene,query:!%27weightLbs:%253E15!%27),timeRestore:!!t,title:!%27Animal%2BWeights%2B(created%2Bin%2B6.2)!%27,viewMode:view)%27,savedObjectId:%271b2f47b0-53cb-11e8-b481-c9426d020fcd%27)'; -export const PDF_PRESERVE_VISUALIZATION_6_2 = - '/api/reporting/generate/printablePdf?jobParams=(browserTimezone:America%2FNew_York,layout:(dimensions:(height:441,width:1002),id:preserve_layout),objectType:visualization,queryString:%27_g%3D(refreshInterval:(display:Off,pause:!!f,value:0),time:(from:!%27Mon%2BApr%2B09%2B2018%2B17:56:08%2BGMT-0400!%27,mode:absolute,to:!%27Wed%2BApr%2B11%2B2018%2B17:56:08%2BGMT-0400!%27))%26_a%3D(filters:!!(),linked:!!f,query:(language:lucene,query:!%27weightLbs:%253E10!%27),uiState:(),vis:(aggs:!!((enabled:!!t,id:!%271!%27,params:(),schema:metric,type:count),(enabled:!!t,id:!%272!%27,params:(field:weightLbs,missingBucket:!!f,missingBucketLabel:Missing,order:desc,orderBy:!%271!%27,otherBucket:!!f,otherBucketLabel:Other,size:5),schema:segment,type:terms)),params:(addLegend:!!t,addTooltip:!!t,isDonut:!!t,labels:(last_level:!!t,show:!!f,truncate:100,values:!!t),legendPosition:right,type:pie),title:!%27Weight%2Bin%2Blbs%2Bpie%2Bcreated%2Bin%2B6.2!%27,type:pie))%27,savedObjectId:%270644f890-53cb-11e8-b481-c9426d020fcd%27)'; -export const CSV_DISCOVER_FILTER_QUERY_6_2 = - '/api/reporting/generate/csv?jobParams=(conflictedTypesFields:!(),fields:!(%27@timestamp%27,animal,sound,weightLbs),indexPatternId:a0f483a0-3dc9-11e8-8660-4d65aa086b3c,metaFields:!(_source,_id,_type,_index,_score),searchRequest:(body:(_source:(excludes:!(),includes:!(%27@timestamp%27,animal,sound,weightLbs)),docvalue_fields:!(%27@timestamp%27),query:(bool:(filter:!(),must:!((query_string:(analyze_wildcard:!t,default_field:%27*%27,query:%27weightLbs:%3E10%27)),(match_phrase:(sound.keyword:(query:growl))),(range:(%27@timestamp%27:(format:epoch_millis,gte:1523310968000,lte:1523483768000)))),must_not:!(),should:!())),script_fields:(),sort:!((%27@timestamp%27:(order:desc,unmapped_type:boolean))),stored_fields:!(%27@timestamp%27,animal,sound,weightLbs),version:!t),index:%27animals-*%27),title:%27Search%20created%20in%206.2%27,type:search)'; diff --git a/x-pack/test/reporting/api/usage.js b/x-pack/test/reporting/api/usage.js index c5271255d483b..c1005c441f463 100644 --- a/x-pack/test/reporting/api/usage.js +++ b/x-pack/test/reporting/api/usage.js @@ -46,7 +46,7 @@ export default function({ getService }) { describe('from archive data', () => { it('generated from 6.2', async () => { - await esArchiver.load('bwc/6_2'); + await esArchiver.load('reporting/bwc/6_2'); const usage = await usageAPI.getUsageStats(); reportingAPI.expectRecentJobTypeTotalStats(usage, 'csv', 0); @@ -64,10 +64,12 @@ export default function({ getService }) { reportingAPI.expectAllTimePdfAppStats(usage, 'dashboard', 0); reportingAPI.expectAllTimePdfLayoutStats(usage, 'preserve_layout', 0); reportingAPI.expectAllTimePdfLayoutStats(usage, 'print', 0); + + await esArchiver.unload('reporting/bwc/6_2'); }); it('generated from 6.3', async () => { - await esArchiver.load('bwc/6_3'); + await esArchiver.load('reporting/bwc/6_3'); const usage = await usageAPI.getUsageStats(); reportingAPI.expectRecentJobTypeTotalStats(usage, 'csv', 0); @@ -83,6 +85,8 @@ export default function({ getService }) { reportingAPI.expectAllTimePdfAppStats(usage, 'dashboard', 3); reportingAPI.expectAllTimePdfLayoutStats(usage, 'preserve_layout', 3); reportingAPI.expectAllTimePdfLayoutStats(usage, 'print', 3); + + await esArchiver.unload('reporting/bwc/6_3'); }); }); diff --git a/x-pack/test/reporting/configs/api.js b/x-pack/test/reporting/configs/api.js deleted file mode 100644 index 523bd551f9ab6..0000000000000 --- a/x-pack/test/reporting/configs/api.js +++ /dev/null @@ -1,41 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -const path = require('path'); - -import { ReportingAPIProvider } from '../services'; - -export async function getReportingApiConfig({ readConfigFile }) { - const apiConfig = await readConfigFile(require.resolve('../../api_integration/config.js')); - - return { - servers: apiConfig.get('servers'), - services: { - ...apiConfig.get('services'), - reportingAPI: ReportingAPIProvider, - }, - esArchiver: { - directory: path.resolve(__dirname, '../es_archives'), - }, - junit: { - reportName: 'X-Pack Reporting API Tests', - }, - testFiles: [require.resolve('../api/generate')], - esTestCluster: apiConfig.get('esTestCluster'), - kbnTestServer: { - ...apiConfig.get('kbnTestServer'), - serverArgs: [ - ...apiConfig.get('kbnTestServer.serverArgs'), - '--xpack.reporting.csv.enablePanelActionDownload=true', - `--optimize.enabled=true`, - '--logging.events.log', - JSON.stringify(['info', 'warning', 'error', 'fatal', 'optimize', 'reporting']), - ], - }, - }; -} - -export default getReportingApiConfig; diff --git a/x-pack/test/reporting/configs/chromium_api.js b/x-pack/test/reporting/configs/chromium_api.js index feaabefcd7dfb..95649dfb5d7a3 100644 --- a/x-pack/test/reporting/configs/chromium_api.js +++ b/x-pack/test/reporting/configs/chromium_api.js @@ -4,25 +4,36 @@ * you may not use this file except in compliance with the Elastic License. */ -import { getReportingApiConfig } from './api'; +import { ReportingAPIProvider } from '../services'; export default async function({ readConfigFile }) { - const reportingApiConfig = await getReportingApiConfig({ readConfigFile }); + const apiConfig = await readConfigFile(require.resolve('../../api_integration/config.js')); + const functionalConfig = await readConfigFile(require.resolve('../../functional/config.js')); return { - ...reportingApiConfig, - junit: { - reportName: 'X-Pack Chromium API Reporting Tests', - }, + servers: apiConfig.get('servers'), + junit: { reportName: 'X-Pack Chromium API Reporting Tests' }, testFiles: [require.resolve('../api/chromium_tests')], + services: { + ...apiConfig.get('services'), + reportingAPI: ReportingAPIProvider, + }, kbnTestServer: { - ...reportingApiConfig.kbnTestServer, + ...apiConfig.get('kbnTestServer'), serverArgs: [ - ...reportingApiConfig.kbnTestServer.serverArgs, + // Reporting API tests use functionalConfig instead of apiConfig because they needs a fully working UI. By default, the API config + // does not have optimize setting enabled, and Kibana would not have a working UI. + ...functionalConfig.get('kbnTestServer.serverArgs'), + '--logging.events.log', + '["info","warning","error","fatal","optimize","reporting"]', + '--xpack.endpoint.enabled=true', '--xpack.reporting.csv.enablePanelActionDownload=true', - `--xpack.reporting.capture.browser.type=chromium`, - `--xpack.spaces.enabled=false`, + '--xpack.reporting.capture.maxAttempts=1', + '--xpack.security.session.idleTimeout=3600000', + '--xpack.spaces.enabled=false', ], }, + esArchiver: apiConfig.get('esArchiver'), + esTestCluster: apiConfig.get('esTestCluster'), }; } diff --git a/x-pack/test/reporting/configs/chromium_functional.js b/x-pack/test/reporting/configs/chromium_functional.js index 5cb14f048aeaf..22640e858cc6c 100644 --- a/x-pack/test/reporting/configs/chromium_functional.js +++ b/x-pack/test/reporting/configs/chromium_functional.js @@ -4,24 +4,32 @@ * you may not use this file except in compliance with the Elastic License. */ -import { getFunctionalConfig } from './functional'; - export default async function({ readConfigFile }) { - const functionalConfig = await getFunctionalConfig({ readConfigFile }); + const functionalConfig = await readConfigFile(require.resolve('../../functional/config.js')); return { - ...functionalConfig, + services: functionalConfig.get('services'), + pageObjects: functionalConfig.get('pageObjects'), + servers: functionalConfig.get('servers'), + apps: functionalConfig.get('apps'), + screenshots: functionalConfig.get('screenshots'), junit: { reportName: 'X-Pack Chromium Functional Reporting Tests', }, testFiles: [require.resolve('../functional')], kbnTestServer: { - ...functionalConfig.kbnTestServer, + ...functionalConfig.get('kbnTestServer'), serverArgs: [ - ...functionalConfig.kbnTestServer.serverArgs, + ...functionalConfig.get('kbnTestServer.serverArgs'), + '--logging.events.log', + '["info","warning","error","fatal","optimize","reporting"]', + '--xpack.endpoint.enabled=true', '--xpack.reporting.csv.enablePanelActionDownload=true', - `--xpack.reporting.capture.browser.type=chromium`, + '--xpack.security.session.idleTimeout=3600000', + '--xpack.spaces.enabled=false', ], }, + esArchiver: functionalConfig.get('esArchiver'), + esTestCluster: functionalConfig.get('esTestCluster'), }; } diff --git a/x-pack/test/reporting/configs/functional.js b/x-pack/test/reporting/configs/functional.js deleted file mode 100644 index 33481a1cab330..0000000000000 --- a/x-pack/test/reporting/configs/functional.js +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ -const path = require('path'); - -export async function getFunctionalConfig({ readConfigFile }) { - const xPackFunctionalTestsConfig = await readConfigFile( - require.resolve('../../functional/config.js') - ); - - return { - services: xPackFunctionalTestsConfig.get('services'), - pageObjects: xPackFunctionalTestsConfig.get('pageObjects'), - servers: xPackFunctionalTestsConfig.get('servers'), - esTestCluster: xPackFunctionalTestsConfig.get('esTestCluster'), - apps: xPackFunctionalTestsConfig.get('apps'), - esArchiver: { - directory: path.resolve(__dirname, '../es_archives'), - }, - screenshots: xPackFunctionalTestsConfig.get('screenshots'), - junit: { - reportName: 'X-Pack Reporting Functional Tests', - }, - kbnTestServer: { - ...xPackFunctionalTestsConfig.get('kbnTestServer'), - serverArgs: [ - ...xPackFunctionalTestsConfig.get('kbnTestServer.serverArgs'), - '--xpack.reporting.csv.enablePanelActionDownload=true', - '--logging.events.log', - JSON.stringify(['info', 'warning', 'error', 'fatal', 'optimize', 'reporting']), - ], - }, - testFiles: [require.resolve('../functional')], - }; -} - -export default getFunctionalConfig; diff --git a/x-pack/test/reporting/configs/generate_api.js b/x-pack/test/reporting/configs/generate_api.js index de63d1e3b6715..c2b5e6c84f05f 100644 --- a/x-pack/test/reporting/configs/generate_api.js +++ b/x-pack/test/reporting/configs/generate_api.js @@ -6,44 +6,38 @@ import { esTestConfig, kbnTestConfig, kibanaServerTestUser } from '@kbn/test'; import { format as formatUrl } from 'url'; -import { getApiIntegrationConfig } from '../../api_integration/config'; -import { getReportingApiConfig } from './api'; +import { ReportingAPIProvider } from '../services'; export default async function({ readConfigFile }) { - const servers = { - kibana: kbnTestConfig.getUrlParts(), - elasticsearch: esTestConfig.getUrlParts(), - }; - - const apiTestConfig = await getApiIntegrationConfig({ readConfigFile }); - const reportingApiConfig = await getReportingApiConfig({ readConfigFile }); - const xPackFunctionalTestsConfig = await readConfigFile( - require.resolve('../../functional/config.js') - ); + const apiConfig = await readConfigFile(require.resolve('../../api_integration/config.js')); return { - ...reportingApiConfig, + servers: apiConfig.get('servers'), junit: { reportName: 'X-Pack Reporting Generate API Integration Tests' }, testFiles: [require.resolve('../api/generate')], services: { - ...apiTestConfig.services, - ...reportingApiConfig.services, + ...apiConfig.get('services'), + reportingAPI: ReportingAPIProvider, }, kbnTestServer: { - ...xPackFunctionalTestsConfig.get('kbnTestServer'), + ...apiConfig.get('kbnTestServer'), serverArgs: [ - `--optimize.enabled=false`, + '--logging.events.log', + '["info","warning","error","fatal","optimize","reporting"]', + `--elasticsearch.hosts=${formatUrl(esTestConfig.getUrlParts())}`, + `--elasticsearch.password=${kibanaServerTestUser.password}`, + `--elasticsearch.username=${kibanaServerTestUser.username}`, `--logging.json=false`, + `--optimize.enabled=false`, `--server.maxPayloadBytes=1679958`, `--server.port=${kbnTestConfig.getPort()}`, - `--elasticsearch.hosts=${formatUrl(servers.elasticsearch)}`, - `--elasticsearch.username=${kibanaServerTestUser.username}`, - `--elasticsearch.password=${kibanaServerTestUser.password}`, `--xpack.reporting.csv.enablePanelActionDownload=true`, `--xpack.reporting.csv.maxSizeBytes=2850`, `--xpack.reporting.queue.pollInterval=3000`, + `--xpack.spaces.enabled=false`, ], }, - esArchiver: apiTestConfig.esArchiver, + esArchiver: apiConfig.get('esArchiver'), + esTestCluster: apiConfig.get('esTestCluster'), }; } diff --git a/x-pack/test/reporting/es_archives/current/reporting/data.json.gz b/x-pack/test/reporting/es_archives/current/reporting/data.json.gz deleted file mode 100644 index 9e6f6048ebc42..0000000000000 Binary files a/x-pack/test/reporting/es_archives/current/reporting/data.json.gz and /dev/null differ diff --git a/x-pack/test/reporting/es_archives/current/reporting/mappings.json b/x-pack/test/reporting/es_archives/current/reporting/mappings.json deleted file mode 100644 index e41a524d716a6..0000000000000 --- a/x-pack/test/reporting/es_archives/current/reporting/mappings.json +++ /dev/null @@ -1,90 +0,0 @@ -{ - "type": "index", - "value": { - "index": ".reporting-2018.05.06", - "mappings": { - "properties": { - "attempts": { - "type": "short" - }, - "completed_at": { - "type": "date" - }, - "created_at": { - "type": "date" - }, - "created_by": { - "type": "keyword" - }, - "jobtype": { - "type": "keyword" - }, - "max_attempts": { - "type": "short" - }, - "meta": { - "properties": { - "layout": { - "fields": { - "keyword": { - "ignore_above": 256, - "type": "keyword" - } - }, - "type": "text" - }, - "objectType": { - "fields": { - "keyword": { - "ignore_above": 256, - "type": "keyword" - } - }, - "type": "text" - } - } - }, - "output": { - "properties": { - "content": { - "enabled": false, - "type": "object" - }, - "content_type": { - "type": "keyword" - }, - "max_size_reached": { - "type": "boolean" - } - } - }, - "payload": { - "enabled": false, - "type": "object" - }, - "priority": { - "type": "byte" - }, - "process_expiration": { - "type": "date" - }, - "started_at": { - "type": "date" - }, - "status": { - "type": "keyword" - }, - "timeout": { - "type": "long" - } - } - }, - "settings": { - "index": { - "auto_expand_replicas": "0-1", - "number_of_replicas": "0", - "number_of_shards": "1" - } - } - } -} \ No newline at end of file diff --git a/x-pack/test/reporting/functional/reporting.js b/x-pack/test/reporting/functional/reporting.js index 7d72db8c08e22..8458f4e537b70 100644 --- a/x-pack/test/reporting/functional/reporting.js +++ b/x-pack/test/reporting/functional/reporting.js @@ -25,6 +25,7 @@ export default function({ getService, getPageObjects }) { 'header', 'discover', 'visualize', + 'visEditor', ]); const log = getService('log'); @@ -298,9 +299,9 @@ export default function({ getService, getPageObjects }) { it('becomes available when saved', async () => { await PageObjects.reporting.setTimepickerInDataRange(); - await PageObjects.visualize.clickBucket('X-axis'); - await PageObjects.visualize.selectAggregation('Date Histogram'); - await PageObjects.visualize.clickGo(); + await PageObjects.visEditor.clickBucket('X-axis'); + await PageObjects.visEditor.selectAggregation('Date Histogram'); + await PageObjects.visEditor.clickGo(); await PageObjects.visualize.saveVisualization('my viz'); await PageObjects.reporting.openPdfReportingPanel(); await expectEnabledGenerateReportButton(); diff --git a/x-pack/test/saml_api_integration/fixtures/idp_metadata.xml b/x-pack/test/saml_api_integration/fixtures/idp_metadata.xml index 132faffd41667..a890fe812987b 100644 --- a/x-pack/test/saml_api_integration/fixtures/idp_metadata.xml +++ b/x-pack/test/saml_api_integration/fixtures/idp_metadata.xml @@ -6,25 +6,25 @@ - - MIIDMDCCAhgCCQCkOD7fnHiQrTANBgkqhkiG9w0BAQUFADBZMQswCQYDVQQGEwJB - VTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50ZXJuZXQgV2lkZ2l0 - cyBQdHkgTHRkMRIwEAYDVQQDEwlsb2NhbGhvc3QwIBcNMTYwMTE5MjExNjQ5WhgP - MjA4NDAyMDYyMTE2NDlaMFkxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0 - YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGQxEjAQBgNVBAMT - CWxvY2FsaG9zdDCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBALR63SnR - LW/Dgl0Vuy8gB6KjWwvajRWeXNgvlf6LX56oNsUFREHBLQlC2uT5R26F3tOqCDbs - MFNoyDjMBinXRRFJJ2Sokue7GSsGvBv41LMTHnO/MeCCbEOqghJS/QI89cV+u+Aw - 9U+v426KAlCa1sGuE2+3/JvqdBQyheiukmGLiJ0OofpfgpYuFmKi2uYBKU3qzjUx - D01wQ4rCpq5nEnksGhgBeBDnheYmmDsj/wDvnz1exK/WprvTiHQ5MwuIQ4OybwgV - WDF+zv8PXrObrsZvD/ulrjh1cakvnCe2kDYEKMRiHUDesHS2jNJkBUe+FJo4/E3U - pFoYOtdoBe69BIUCAwEAATANBgkqhkiG9w0BAQUFAAOCAQEAQhjF91G0R662XJJ7 - jGMudA9VbRVCow8s68I/GWPZmpKxPAxwz0xiv1eFIoiP416LX9amnx3yAmUoN4Wr - Cq0jsgyT1AOiSCdxkvYsqQG3SFVVt5BDLjThH66Vxi7Bach6SyATa1NG588mg7n9 - pPJ4A1rcj+5kZuwnd52kfVLP+535lylwMyoyJa2AskieRPLNSzus2eUDTR6F+9Mb - eLOwp5rMl2nNYfLXUCSqEeC6uPu0yq6Tu0N0SjThfKndd2NU1fk3zyOjxyCIhGPe - G8VhrPY4lkJ9EE9Tuq095jwd1+q9fYzlKZWhOmg+IcOwUMgbgeWpeZTAhUIZAnia - 4UH6NA== + + MIIDOTCCAiGgAwIBAgIVANNWkg9lzNiLqNkMFhFKHcXyaZmqMA0GCSqGSIb3DQEB +CwUAMDQxMjAwBgNVBAMTKUVsYXN0aWMgQ2VydGlmaWNhdGUgVG9vbCBBdXRvZ2Vu +ZXJhdGVkIENBMCAXDTE5MTIyNzE3MDM0MloYDzIwNjkxMjE0MTcwMzQyWjARMQ8w +DQYDVQQDEwZraWJhbmEwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAwggEKAoIBAQCQ +wYYbQtbRBKJ4uNZc2+IgRU+7NNL21ZebQlEIMgK7jAqOMrsW2b5DATz41Fd+GQFU +FUYYjwo+PQj6sJHshOJo/gNb32HrydvMI7YPvevkszkuEGCfXxQ3Dw2RTACLgD0Q +OCkwHvn3TMf0loloV/ePGWaZDYZaXi3a5DdWi/HFFoJysgF0JV2f6XyKhJkGaEfJ +s9pWX269zH/XQvGNx4BEimJpYB8h4JnDYPFIiQdqj+sl2b+kS1hH9kL5gBAMXjFU +vcNnX+PmyTjyJrGo75k0ku+spBf1bMwuQt3uSmM+TQIXkvFDmS0DOVESrpA5EC1T +BUGRz6o/I88Xx4Mud771AgMBAAGjYzBhMB0GA1UdDgQWBBQLB1Eo23M3Ss8MsFaz +V+Twcb3PmDAfBgNVHSMEGDAWgBQa7SYOe8NGcF00EbwPHA91YCsHSTAUBgNVHREE +DTALgglsb2NhbGhvc3QwCQYDVR0TBAIwADANBgkqhkiG9w0BAQsFAAOCAQEAnEl/ +z5IElIjvkK4AgMPrNcRlvIGDt2orEik7b6Jsq6/RiJQ7cSsYTZf7xbqyxNsUOTxv ++frj47MEN448H2nRvUxH29YR3XygV5aEwADSAhwaQWn0QfWTCZbJTmSoNEDtDOzX +TGDlAoCD9s9Xz9S1JpxY4H+WWRZrBSDM6SC1c6CzuEeZRuScNAjYD5mh2v6fOlSy +b8xJWSg0AFlJPCa3ZsA2SKbNqI0uNfJTnkXRm88Z2NHcgtlADbOLKauWfCrpgsCk +cZgo6yAYkOM148h/8wGla1eX+iE1R72NUABGydu8MSQKvc0emWJkGsC1/KqPlf/O +eOUsdwn1yDKHRxDHyA== diff --git a/x-pack/test/saml_api_integration/fixtures/saml_tools.ts b/x-pack/test/saml_api_integration/fixtures/saml_tools.ts index f8862e6fb209d..b7b94b8eeb17a 100644 --- a/x-pack/test/saml_api_integration/fixtures/saml_tools.ts +++ b/x-pack/test/saml_api_integration/fixtures/saml_tools.ts @@ -12,6 +12,7 @@ import zlib from 'zlib'; import { promisify } from 'util'; import { parseString } from 'xml2js'; import { SignedXml } from 'xml-crypto'; +import { KBN_KEY_PATH } from '@kbn/dev-utils'; /** * @file Defines a set of tools that allow us to parse and generate various SAML XML messages. @@ -24,7 +25,7 @@ const inflateRawAsync = promisify(zlib.inflateRaw); const deflateRawAsync = promisify(zlib.deflateRaw); const parseStringAsync = promisify(parseString); -const signingKey = fs.readFileSync(require.resolve('../../../../test/dev_certs/server.key')); +const signingKey = fs.readFileSync(KBN_KEY_PATH); const signatureAlgorithm = 'http://www.w3.org/2001/04/xmldsig-more#rsa-sha256'; export async function getSAMLRequestId(urlWithSAMLRequestId: string) { diff --git a/x-pack/test/spaces_api_integration/common/suites/copy_to_space.ts b/x-pack/test/spaces_api_integration/common/suites/copy_to_space.ts index 7383bb3409f1a..00ab75702e90d 100644 --- a/x-pack/test/spaces_api_integration/common/suites/copy_to_space.ts +++ b/x-pack/test/spaces_api_integration/common/suites/copy_to_space.ts @@ -87,14 +87,8 @@ export function copyToSpaceTestSuiteFactory( body: { size: 0, query: { - bool: { - must_not: { - term: { - // exclude spaces from the result set. - // we don't assert on these. - type: 'space', - }, - }, + terms: { + type: ['visualization', 'dashboard', 'index-pattern'], }, }, aggs: { diff --git a/x-pack/test/spaces_api_integration/common/suites/delete.ts b/x-pack/test/spaces_api_integration/common/suites/delete.ts index e3994634be1d9..9036fcbf7a8dd 100644 --- a/x-pack/test/spaces_api_integration/common/suites/delete.ts +++ b/x-pack/test/spaces_api_integration/common/suites/delete.ts @@ -39,6 +39,11 @@ export function deleteTestSuiteFactory(es: any, esArchiver: any, supertest: Supe index: '.kibana', body: { size: 0, + query: { + terms: { + type: ['visualization', 'dashboard', 'space', 'config', 'index-pattern'], + }, + }, aggs: { count: { terms: { diff --git a/x-pack/test/typings/encode_uri_query.d.ts b/x-pack/test/typings/encode_uri_query.d.ts deleted file mode 100644 index e1ab5f4a70abf..0000000000000 --- a/x-pack/test/typings/encode_uri_query.d.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -declare module 'encode-uri-query' { - function encodeUriQuery(query: string, usePercentageSpace?: boolean): string; - // eslint-disable-next-line import/no-default-export - export default encodeUriQuery; -} diff --git a/x-pack/test/typings/hapi.d.ts b/x-pack/test/typings/hapi.d.ts index fa2712e69a5b0..fc5ce09e5e618 100644 --- a/x-pack/test/typings/hapi.d.ts +++ b/x-pack/test/typings/hapi.d.ts @@ -9,7 +9,6 @@ import 'hapi'; import { XPackMainPlugin } from '../../legacy/plugins/xpack_main/server/xpack_main'; import { SecurityPlugin } from '../../legacy/plugins/security'; import { ActionsPlugin, ActionsClient } from '../../legacy/plugins/actions'; -import { TaskManager } from '../../legacy/plugins/task_manager'; import { AlertingPlugin, AlertsClient } from '../../legacy/plugins/alerting'; declare module 'hapi' { @@ -22,6 +21,5 @@ declare module 'hapi' { security?: SecurityPlugin; actions?: ActionsPlugin; alerting?: AlertingPlugin; - task_manager?: TaskManager; } } diff --git a/x-pack/test_utils/stub_web_worker.ts b/x-pack/test_utils/stub_web_worker.ts new file mode 100644 index 0000000000000..2e7d5cf2098c8 --- /dev/null +++ b/x-pack/test_utils/stub_web_worker.ts @@ -0,0 +1,16 @@ +/* + * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one + * or more contributor license agreements. Licensed under the Elastic License; + * you may not use this file except in compliance with the Elastic License. + */ + +if (!window.Worker) { + // @ts-ignore we aren't honoring the real Worker spec here + window.Worker = function Worker() { + this.postMessage = jest.fn(); + + // @ts-ignore TypeScript doesn't think this exists on the Worker interface + // https://developer.mozilla.org/en-US/docs/Web/API/Worker/terminate + this.terminate = jest.fn(); + }; +} diff --git a/x-pack/typings/encode_uri_query.d.ts b/x-pack/typings/encode_uri_query.d.ts deleted file mode 100644 index e1ab5f4a70abf..0000000000000 --- a/x-pack/typings/encode_uri_query.d.ts +++ /dev/null @@ -1,11 +0,0 @@ -/* - * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one - * or more contributor license agreements. Licensed under the Elastic License; - * you may not use this file except in compliance with the Elastic License. - */ - -declare module 'encode-uri-query' { - function encodeUriQuery(query: string, usePercentageSpace?: boolean): string; - // eslint-disable-next-line import/no-default-export - export default encodeUriQuery; -} diff --git a/x-pack/typings/hapi.d.ts b/x-pack/typings/hapi.d.ts index 569508caf3f20..a739d5f884f6e 100644 --- a/x-pack/typings/hapi.d.ts +++ b/x-pack/typings/hapi.d.ts @@ -9,8 +9,8 @@ import 'hapi'; import { XPackMainPlugin } from '../legacy/plugins/xpack_main/server/xpack_main'; import { SecurityPlugin } from '../legacy/plugins/security'; import { ActionsPlugin, ActionsClient } from '../legacy/plugins/actions'; -import { TaskManager } from '../legacy/plugins/task_manager'; import { AlertingPlugin, AlertsClient } from '../legacy/plugins/alerting'; +import { LegacyTaskManagerApi } from '../legacy/plugins/task_manager/server'; declare module 'hapi' { interface Request { @@ -22,6 +22,6 @@ declare module 'hapi' { security?: SecurityPlugin; actions?: ActionsPlugin; alerting?: AlertingPlugin; - task_manager?: TaskManager; + task_manager?: LegacyTaskManagerApi; } } diff --git a/yarn.lock b/yarn.lock index 6b096306b78d4..ff098b7b9c891 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1361,10 +1361,10 @@ dependencies: "@elastic/apm-rum-core" "^4.7.0" -"@elastic/charts@^14.0.0": - version "14.0.0" - resolved "https://registry.yarnpkg.com/@elastic/charts/-/charts-14.0.0.tgz#410c87e9ae53df5848aae09a210fa7d08510b376" - integrity sha512-cskrD5Yq6yTTqGOKV2/7dw/eRON1GbWkIgSuWXPIBa/TQMUwiWqxFkxSMUJSbu9xXq07KMblDgXLf73yMc0AyQ== +"@elastic/charts@^16.1.0": + version "16.1.0" + resolved "https://registry.yarnpkg.com/@elastic/charts/-/charts-16.1.0.tgz#67cf11625dcd7e1c2cf16ef53349e6a68a73f5b1" + integrity sha512-0jZ7thhGmYC0ZdEVkxfg6M66epCD7k7BfYIi12FnrmIK+mUD2IPhR8b2TJXvaojPryN4YTNreGRncQ9R58fOoQ== dependencies: "@types/d3-shape" "^1.3.1" classnames "^2.2.6" @@ -1372,18 +1372,18 @@ d3-collection "^1.0.7" d3-scale "^1.0.7" d3-shape "^1.3.4" - fp-ts "^1.14.2" - konva "^2.6.0" - mobx "^4.9.2" - mobx-react "^5.4.3" + konva "^4.0.18" newtype-ts "^0.2.4" prop-types "^15.7.2" - react "^16.8.3" - react-dom "^16.8.3" - react-konva "16.8.3" + re-reselect "^3.4.0" + react-konva "16.10.1-0" + react-redux "^7.1.0" react-spring "^8.0.8" + redux "^4.0.4" + reselect "^4.0.0" resize-observer-polyfill "^1.5.1" ts-debounce "^1.0.0" + utility-types "^3.9.0" uuid "^3.3.2" "@elastic/elasticsearch@^7.4.0": @@ -2246,64 +2246,61 @@ utils-error-reviver "^1.0.0" utils-error-to-json "^1.0.0" -"@microsoft/api-documenter@7.4.3": - version "7.4.3" - resolved "https://registry.yarnpkg.com/@microsoft/api-documenter/-/api-documenter-7.4.3.tgz#af5e69891c4b62e963a697127b69a25396d0123d" - integrity sha512-+RdqNNt9ssUCBAsLvKVPzbRD5RYd0HRgmnhRuEr+eZg2tj2tqfP9AhLWK5SBVn68CX5hyeWHEnQJ4HikDTZN8A== +"@microsoft/api-documenter@7.7.2": + version "7.7.2" + resolved "https://registry.yarnpkg.com/@microsoft/api-documenter/-/api-documenter-7.7.2.tgz#b6897f052ad447d6bb74f806287e8846c64691da" + integrity sha512-4mWE5G3grYd4PX5D6awiKa3B3GOXumkyGspgeTwlOBxrmj0FuVFRNPVZxGU0NqYnaw/bW4cg4ftUnSDzycrW+A== dependencies: - "@microsoft/api-extractor-model" "7.4.1" - "@microsoft/node-core-library" "3.14.2" - "@microsoft/ts-command-line" "4.2.8" + "@microsoft/api-extractor-model" "7.7.0" + "@microsoft/node-core-library" "3.18.0" + "@microsoft/ts-command-line" "4.3.5" "@microsoft/tsdoc" "0.12.14" colors "~1.2.1" js-yaml "~3.13.1" resolve "1.8.1" -"@microsoft/api-extractor-model@7.4.1": - version "7.4.1" - resolved "https://registry.yarnpkg.com/@microsoft/api-extractor-model/-/api-extractor-model-7.4.1.tgz#3376f72570d336960c9b7b0dd44c8a0dbbe34604" - integrity sha512-rBO/QbrOMCdL8e9qwhIu1aH4C5sKOnUO1YhEh3+kVieFzTjiRnync7ghyQOtCaCVl2VXtp4LuOIv02e82oRqUg== +"@microsoft/api-extractor-model@7.7.0": + version "7.7.0" + resolved "https://registry.yarnpkg.com/@microsoft/api-extractor-model/-/api-extractor-model-7.7.0.tgz#a5e86a638fa3fea283aeebc4785d8150652f30c6" + integrity sha512-9yrSr9LpdNnx7X8bXVb/YbcQopizsr43McAG7Xno5CMNFzbSkmIr8FJL0L+WGfrSWSTms9Bngfz7d1ScP6zbWQ== dependencies: - "@microsoft/node-core-library" "3.14.2" + "@microsoft/node-core-library" "3.18.0" "@microsoft/tsdoc" "0.12.14" - "@types/node" "8.5.8" -"@microsoft/api-extractor@7.4.2": - version "7.4.2" - resolved "https://registry.yarnpkg.com/@microsoft/api-extractor/-/api-extractor-7.4.2.tgz#440023cf05c69840e054cdb5f85cab9680227a40" - integrity sha512-O8OEaFvsvWEuwkOcVyWegIAFDY6TBZBvSIoOKLsSQYiQZtryGf13e2ym83iewhbUN7RmuOJtyQUKlBvcJbpgQA== +"@microsoft/api-extractor@7.7.0": + version "7.7.0" + resolved "https://registry.yarnpkg.com/@microsoft/api-extractor/-/api-extractor-7.7.0.tgz#1550a5b88ca927d57e9c9698356a2f9375c5984c" + integrity sha512-1ngy95VA1s7GTE+bkS7QoYTg/TZs54CdJ46uAhl6HlyDJut4p/aH46W70g2XQs9VniIymW1Qe6fqNmcQUx5CVg== dependencies: - "@microsoft/api-extractor-model" "7.4.1" - "@microsoft/node-core-library" "3.14.2" - "@microsoft/ts-command-line" "4.2.8" + "@microsoft/api-extractor-model" "7.7.0" + "@microsoft/node-core-library" "3.18.0" + "@microsoft/ts-command-line" "4.3.5" "@microsoft/tsdoc" "0.12.14" colors "~1.2.1" lodash "~4.17.15" resolve "1.8.1" source-map "~0.6.1" - typescript "~3.5.3" + typescript "~3.7.2" -"@microsoft/node-core-library@3.14.2": - version "3.14.2" - resolved "https://registry.yarnpkg.com/@microsoft/node-core-library/-/node-core-library-3.14.2.tgz#255d421963f2d447a19f935e3c8eb3053e8e381b" - integrity sha512-bd8XhqhIvXsWg/SSNsZJdJxkN8Ucj7XKQkRe4cdYiKqpVdAREvW/shw8AoZIdgvjLI53029I/MO2Wn/AjGD3Jw== +"@microsoft/node-core-library@3.18.0": + version "3.18.0" + resolved "https://registry.yarnpkg.com/@microsoft/node-core-library/-/node-core-library-3.18.0.tgz#9a9123354b3e067bb8a975ba791959ffee1322ed" + integrity sha512-VzzSHtcwgHVW1xbHqpngfn+OS1trAZ1Tw3XXBlMsEKe7Wz7FF2gLr0hZa6x9Pemk5pkd4tu4+GTSOJjCKGjrgg== dependencies: - "@types/fs-extra" "5.0.4" - "@types/jju" "~1.4.0" - "@types/node" "8.5.8" - "@types/z-schema" "3.16.31" + "@types/node" "8.10.54" colors "~1.2.1" fs-extra "~7.0.1" jju "~1.4.0" + semver "~5.3.0" + timsort "~0.3.0" z-schema "~3.18.3" -"@microsoft/ts-command-line@4.2.8": - version "4.2.8" - resolved "https://registry.yarnpkg.com/@microsoft/ts-command-line/-/ts-command-line-4.2.8.tgz#92f4c85d0a4b893090fe6605f255e272b270495e" - integrity sha512-K4sc8/OJ/y5uQPWJFACMExS2UIqF+t3vdQ2A9Mhl9tMsp70CXf0sp6Y9ENYju1K7XWwR5Clh8dkP2jO1Ntlg1g== +"@microsoft/ts-command-line@4.3.5": + version "4.3.5" + resolved "https://registry.yarnpkg.com/@microsoft/ts-command-line/-/ts-command-line-4.3.5.tgz#78026d20244f39978d3397849ac8c40c0c2d4079" + integrity sha512-CN3j86apNOmllUmeJ0AyRfTYA2BP2xlnfgmnyp1HWLqcJmR/zLe/fk/+gohGnNt7o5/qHta3681LQhO2Yy3GFw== dependencies: "@types/argparse" "1.0.33" - "@types/node" "8.5.8" argparse "~1.0.9" colors "~1.2.1" @@ -3442,6 +3439,11 @@ resolved "https://registry.yarnpkg.com/@types/dedent/-/dedent-0.7.0.tgz#155f339ca404e6dd90b9ce46a3f78fd69ca9b050" integrity sha512-EGlKlgMhnLt/cM4DbUSafFdrkeJoC9Mvnj0PUCU7tFmTjMjNRT957kXCx0wYm3JuEq4o4ZsS5vG+NlkM2DMd2A== +"@types/deep-freeze-strict@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@types/deep-freeze-strict/-/deep-freeze-strict-1.1.0.tgz#447a6a2576191344aa42310131dd3df5c41492c4" + integrity sha1-RHpqJXYZE0SqQjEBMd099cQUksQ= + "@types/delete-empty@^2.0.0": version "2.0.0" resolved "https://registry.yarnpkg.com/@types/delete-empty/-/delete-empty-2.0.0.tgz#1647ae9e68f708a6ba778531af667ec55bc61964" @@ -3465,10 +3467,10 @@ resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d" integrity sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag== -"@types/eslint@^6.1.2": - version "6.1.2" - resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-6.1.2.tgz#297ece0f3815f93d699b18bdade5e6bee747284f" - integrity sha512-t+smTKg1e9SshiIOI94Zi+Lvo3bfHF20MuKP8w3VGWdrS1dYm33A7xrSoyy9FQv6oE2TwYqEXVJ50I0or8+FWQ== +"@types/eslint@^6.1.3": + version "6.1.3" + resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-6.1.3.tgz#ec2a66e445a48efaa234020eb3b6e8f06afc9c61" + integrity sha512-llYf1QNZaDweXtA7uY6JczcwHmFwJL9TpK3E6sY0B18l6ulDT6VWNMAdEjYccFHiDfxLPxffd8QmSDV4QUUspA== dependencies: "@types/estree" "*" "@types/json-schema" "*" @@ -3498,13 +3500,6 @@ resolved "https://registry.yarnpkg.com/@types/file-saver/-/file-saver-2.0.0.tgz#cbb49815a5e1129d5f23836a98d65d93822409af" integrity sha512-dxdRrUov2HVTbSRFX+7xwUPlbGYVEZK6PrSqClg2QPos3PNe0bCajkDDkDeeC1znjSH03KOEqVbXpnJuWa2wgQ== -"@types/fs-extra@5.0.4": - version "5.0.4" - resolved "https://registry.yarnpkg.com/@types/fs-extra/-/fs-extra-5.0.4.tgz#b971134d162cc0497d221adde3dbb67502225599" - integrity sha512-DsknoBvD8s+RFfSGjmERJ7ZOP1HI0UZRA3FSI+Zakhrc/Gy26YQsLI+m5V5DHxroHRJqCDLKJp7Hixn8zyaF7g== - dependencies: - "@types/node" "*" - "@types/geojson@*": version "7946.0.7" resolved "https://registry.yarnpkg.com/@types/geojson/-/geojson-7946.0.7.tgz#c8fa532b60a0042219cdf173ca21a975ef0666ad" @@ -3703,11 +3698,6 @@ dependencies: "@types/jest-diff" "*" -"@types/jju@~1.4.0": - version "1.4.1" - resolved "https://registry.yarnpkg.com/@types/jju/-/jju-1.4.1.tgz#0a39f5f8e84fec46150a7b9ca985c3f89ad98e9f" - integrity sha512-LFt+YA7Lv2IZROMwokZKiPNORAV5N3huMs3IKnzlE430HWhWYZ8b+78HiwJXJJP1V2IEjinyJURuRJfGoaFSIA== - "@types/joi@*", "@types/joi@^13.4.2": version "13.6.1" resolved "https://registry.yarnpkg.com/@types/joi/-/joi-13.6.1.tgz#325486a397504f8e22c8c551dc8b0e1d41d5d5ae" @@ -3897,6 +3887,13 @@ dependencies: "@types/node" "*" +"@types/node-forge@^0.9.0": + version "0.9.0" + resolved "https://registry.yarnpkg.com/@types/node-forge/-/node-forge-0.9.0.tgz#e9f678ec09283f9f35cb8de6c01f86be9278ac08" + integrity sha512-J00+BIHJOfagO1Qs67Jp5CZO3VkFxY8YKMt44oBhXr+3ZYNnl8wv/vtcJyPjuH0QZ+q7+5nnc6o/YH91ZJy2pQ== + dependencies: + "@types/node" "*" + "@types/node-jose@1.1.0": version "1.1.0" resolved "https://registry.yarnpkg.com/@types/node-jose/-/node-jose-1.1.0.tgz#26e1d234b41a39035482443ef35414bf34ba5d8b" @@ -3904,7 +3901,7 @@ dependencies: "@types/node" "*" -"@types/node@*", "@types/node@10.12.27", "@types/node@8.5.8", "@types/node@>=8.9.0", "@types/node@^10.12.27", "@types/node@^12.0.2": +"@types/node@*", "@types/node@10.12.27", "@types/node@8.10.54", "@types/node@>=8.9.0", "@types/node@^10.12.27", "@types/node@^12.0.2": version "10.12.27" resolved "https://registry.yarnpkg.com/@types/node/-/node-10.12.27.tgz#eb3843f15d0ba0986cc7e4d734d2ee8b50709ef8" integrity sha512-e9wgeY6gaY21on3ve0xAjgBVjGDWq/xUteK0ujsE53bUoxycMkqfnkUgMt6ffZtykZ5X12Mg3T7Pw4TRCObDKg== @@ -3959,6 +3956,11 @@ resolved "https://registry.yarnpkg.com/@types/parse-link-header/-/parse-link-header-1.0.0.tgz#69f059e40a0fa93dc2e095d4142395ae6adc5d7a" integrity sha512-fCA3btjE7QFeRLfcD0Sjg+6/CnmC66HpMBoRfRzd2raTaWMJV21CCZ0LO8MOqf8onl5n0EPfjq4zDhbyX8SVwA== +"@types/pegjs@^0.10.1": + version "0.10.1" + resolved "https://registry.yarnpkg.com/@types/pegjs/-/pegjs-0.10.1.tgz#9a2f3961dc62430fdb21061eb0ddbd890f9e3b94" + integrity sha512-ra8IchO9odGQmYKbm+94K58UyKCEKdZh9y0vxhG4pIpOJOBlC1C+ZtBVr6jLs+/oJ4pl+1p/4t3JtBA8J10Vvw== + "@types/pngjs@^3.3.2": version "3.3.2" resolved "https://registry.yarnpkg.com/@types/pngjs/-/pngjs-3.3.2.tgz#8ed3bd655ab3a92ea32ada7a21f618e63b93b1d4" @@ -4018,10 +4020,10 @@ dependencies: "@types/react" "*" -"@types/react-beautiful-dnd@^11.0.3": - version "11.0.3" - resolved "https://registry.yarnpkg.com/@types/react-beautiful-dnd/-/react-beautiful-dnd-11.0.3.tgz#51d9f37942dd18cc4aa10da98a5c883664e7ee46" - integrity sha512-7ZbT/7mNJu+uRrUGdTQ1hAINtqg909L4NHrXyspV42fvVgBgda6ysiBzoDUMENmQ/RlRJdpyrcp8Dtd/77bp9Q== +"@types/react-beautiful-dnd@^11.0.4": + version "11.0.4" + resolved "https://registry.yarnpkg.com/@types/react-beautiful-dnd/-/react-beautiful-dnd-11.0.4.tgz#25cdf16864df8fd1d82f9416c8c0fd957e793024" + integrity sha512-a1Nvt1AcSEA962OuXrk1gu5bJQhzu0B3qFNO999/0nmF+oAD7HIAY0DwraS3L3XM1cVuRO1+PtpTkD4CfRK2QA== dependencies: "@types/react" "*" @@ -4163,10 +4165,10 @@ dependencies: redux "^4.0.0" -"@types/redux-actions@^2.2.1": - version "2.3.0" - resolved "https://registry.yarnpkg.com/@types/redux-actions/-/redux-actions-2.3.0.tgz#d28d7913ec86ee9e20ecb33a1fed887ecb538149" - integrity sha512-N5gZT7Tg5HGRbQH56D6umLhv1R4koEFjfz5+2TFo/tjAz3Y3Aj+hjQBum3UUO4D53hYO439UlWP5Q+S63vujrQ== +"@types/redux-actions@^2.6.1": + version "2.6.1" + resolved "https://registry.yarnpkg.com/@types/redux-actions/-/redux-actions-2.6.1.tgz#0940e97fa35ad3004316bddb391d8e01d2efa605" + integrity sha512-zKgK+ATp3sswXs6sOYo1tk8xdXTy4CTaeeYrVQlClCjeOpag5vzPo0ASWiiBJ7vsiQRAdb3VkuFLnDoBimF67g== "@types/redux@^3.6.31": version "3.6.31" @@ -4444,67 +4446,45 @@ dependencies: "@types/yargs-parser" "*" -"@types/z-schema@3.16.31": - version "3.16.31" - resolved "https://registry.yarnpkg.com/@types/z-schema/-/z-schema-3.16.31.tgz#2eb1d00a5e4ec3fa58c76afde12e182b66dc5c1c" - integrity sha1-LrHQCl5Ow/pYx2r94S4YK2bcXBw= - "@types/zen-observable@^0.8.0": version "0.8.0" resolved "https://registry.yarnpkg.com/@types/zen-observable/-/zen-observable-0.8.0.tgz#8b63ab7f1aa5321248aad5ac890a485656dcea4d" integrity sha512-te5lMAWii1uEJ4FwLjzdlbw3+n0FZNOvFXHxQDKeT0dilh7HOzdMzV2TrJVUzq8ep7J4Na8OUYPRLSQkJHAlrg== -"@typescript-eslint/eslint-plugin@^2.12.0": - version "2.12.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.12.0.tgz#0da7cbca7b24f4c6919e9eb31c704bfb126f90ad" - integrity sha512-1t4r9rpLuEwl3hgt90jY18wJHSyb0E3orVL3DaqwmpiSDHmHiSspVsvsFF78BJ/3NNG3qmeso836jpuBWYziAA== +"@typescript-eslint/eslint-plugin@^2.15.0": + version "2.15.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-2.15.0.tgz#5442c30b687ffd576ff74cfea46a6d7bfb0ee893" + integrity sha512-XRJFznI5v4K1WvIrWmjFjBAdQWaUTz4xJEdqR7+wAFsv6Q9dP3mOlE6BMNT3pdlp9eF1+bC5m5LZTmLMqffCVw== dependencies: - "@typescript-eslint/experimental-utils" "2.12.0" + "@typescript-eslint/experimental-utils" "2.15.0" eslint-utils "^1.4.3" functional-red-black-tree "^1.0.1" regexpp "^3.0.0" tsutils "^3.17.1" -"@typescript-eslint/experimental-utils@2.12.0": - version "2.12.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.12.0.tgz#e0a76ffb6293e058748408a191921e453c31d40d" - integrity sha512-jv4gYpw5N5BrWF3ntROvCuLe1IjRenLy5+U57J24NbPGwZFAjhnM45qpq0nDH1y/AZMb3Br25YiNVwyPbz6RkA== +"@typescript-eslint/experimental-utils@2.15.0", "@typescript-eslint/experimental-utils@^2.5.0": + version "2.15.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-2.15.0.tgz#41e35313bfaef91650ddb5380846d1c78a780070" + integrity sha512-Qkxu5zndY5hqlcQkmA88gfLvqQulMpX/TN91XC7OuXsRf4XG5xLGie0sbpX97o/oeccjeZYRMipIsjKk/tjDHA== dependencies: "@types/json-schema" "^7.0.3" - "@typescript-eslint/typescript-estree" "2.12.0" + "@typescript-eslint/typescript-estree" "2.15.0" eslint-scope "^5.0.0" -"@typescript-eslint/experimental-utils@^1.13.0": - version "1.13.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/experimental-utils/-/experimental-utils-1.13.0.tgz#b08c60d780c0067de2fb44b04b432f540138301e" - integrity sha512-zmpS6SyqG4ZF64ffaJ6uah6tWWWgZ8m+c54XXgwFtUv0jNz8aJAVx8chMCvnk7yl6xwn8d+d96+tWp7fXzTuDg== - dependencies: - "@types/json-schema" "^7.0.3" - "@typescript-eslint/typescript-estree" "1.13.0" - eslint-scope "^4.0.0" - -"@typescript-eslint/parser@^2.12.0": - version "2.12.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-2.12.0.tgz#393f1604943a4ca570bb1a45bc8834e9b9158884" - integrity sha512-lPdkwpdzxEfjI8TyTzZqPatkrswLSVu4bqUgnB03fHSOwpC7KSerPgJRgIAf11UGNf7HKjJV6oaPZI4AghLU6g== +"@typescript-eslint/parser@^2.15.0": + version "2.15.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-2.15.0.tgz#379a71a51b0429bc3bc55c5f8aab831bf607e411" + integrity sha512-6iSgQsqAYTaHw59t0tdjzZJluRAjswdGltzKEdLtcJOxR2UVTPHYvZRqkAVGCkaMVb6Fpa60NnuozNCvsSpA9g== dependencies: "@types/eslint-visitor-keys" "^1.0.0" - "@typescript-eslint/experimental-utils" "2.12.0" - "@typescript-eslint/typescript-estree" "2.12.0" + "@typescript-eslint/experimental-utils" "2.15.0" + "@typescript-eslint/typescript-estree" "2.15.0" eslint-visitor-keys "^1.1.0" -"@typescript-eslint/typescript-estree@1.13.0": - version "1.13.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-1.13.0.tgz#8140f17d0f60c03619798f1d628b8434913dc32e" - integrity sha512-b5rCmd2e6DCC6tCTN9GSUAuxdYwCM/k/2wdjHGrIRGPSJotWMCe/dGpi66u42bhuh8q3QBzqM4TMA1GUUCJvdw== - dependencies: - lodash.unescape "4.0.1" - semver "5.5.0" - -"@typescript-eslint/typescript-estree@2.12.0": - version "2.12.0" - resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.12.0.tgz#bd9e547ccffd17dfab0c3ab0947c80c8e2eb914c" - integrity sha512-rGehVfjHEn8Frh9UW02ZZIfJs6SIIxIu/K1bbci8rFfDE/1lQ8krIJy5OXOV3DVnNdDPtoiPOdEANkLMrwXbiQ== +"@typescript-eslint/typescript-estree@2.15.0": + version "2.15.0" + resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-2.15.0.tgz#79ae52eed8701b164d91e968a65d85a9105e76d3" + integrity sha512-L6Pog+w3VZzXkAdyqA0VlwybF8WcwZX+mufso86CMxSdWmcizJ38lgBdpqTbc9bo92iyi0rOvmATKiwl+amjxg== dependencies: debug "^4.1.1" eslint-visitor-keys "^1.1.0" @@ -4710,6 +4690,11 @@ resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d" integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ== +"@yarnpkg/lockfile@^1.1.0": + version "1.1.0" + resolved "https://registry.yarnpkg.com/@yarnpkg/lockfile/-/lockfile-1.1.0.tgz#e77a97fbd345b76d83245edcd17d393b1b41fb31" + integrity sha512-GpSwvyXOcOOlV70vbnzjj4fW5xW/FdUF6nQEt1ENy7m4ZCczi1+/buVUPAqmGfqznsORNFzUMjctTIp8a9tuCQ== + JSONStream@1.3.5: version "1.3.5" resolved "https://registry.yarnpkg.com/JSONStream/-/JSONStream-1.3.5.tgz#3208c1f08d3a4d99261ab64f92302bc15e111ca0" @@ -4779,11 +4764,6 @@ accepts@~1.3.7: mime-types "~2.1.24" negotiator "0.6.2" -acorn-dynamic-import@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/acorn-dynamic-import/-/acorn-dynamic-import-4.0.0.tgz#482210140582a36b83c3e342e1cfebcaa9240948" - integrity sha512-d3OEjQV4ROpoflsnUA8HozoIR504TFxNivYEUi6uwz0IYhBkTDXGuWlNdMtybRt3nqVx/L6XqMt0FxkXuWKZhw== - acorn-globals@^3.0.0: version "3.1.0" resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-3.1.0.tgz#fd8270f71fbb4996b004fa880ee5d46573a731bf" @@ -4814,7 +4794,7 @@ acorn-jsx@^3.0.0: dependencies: acorn "^3.0.4" -acorn-jsx@^5.0.2: +acorn-jsx@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.1.0.tgz#294adb71b57398b0680015f0a38c563ee1db5384" integrity sha512-tMUqwBWfLFbJbizRmEcWSLw6HnFzfdJs2sOJEOwwtVPMoH/0Ay+E703oZz78VSXZiiDcZrQ5XKjPIUQixhmgVw== @@ -4838,7 +4818,7 @@ acorn-walk@^7.0.0: resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.0.0.tgz#c8ba6f0f1aac4b0a9e32d1f0af12be769528f36b" integrity sha512-7Bv1We7ZGuU79zZbb6rRqcpxo3OY+zrdtloZWoyD8fmGX+FeXRjE+iuGkZjSXLVovLzrsvMGMy0EkwA0E0umxg== -acorn@5.X, acorn@^5.0.0, acorn@^5.0.3, acorn@^5.1.2, acorn@^5.2.1, acorn@^5.5.0: +acorn@5.X, acorn@^5.0.0, acorn@^5.0.3, acorn@^5.1.2, acorn@^5.5.0: version "5.7.3" resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.7.3.tgz#67aa231bf8812974b85235a96771eb6bd07ea279" integrity sha512-T/zvzYRfbVojPWahDsE5evJdHb3oJoQfFbsrKM7w5Zcs++Tr257tia3BmMP8XYVjp1S9RZXQMh7gao96BlqZOw== @@ -4858,7 +4838,7 @@ acorn@^6.0.1: resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.1.1.tgz#7d25ae05bb8ad1f9b699108e1094ecd7884adc1f" integrity sha512-jPTiwtOxaHNaAPg/dmrJ/beuzLRnXtB0kQPQ8JpotKJgTB6rX6c8mlf315941pyjBSaPg8NHXS9fhP4u17DpGA== -acorn@^6.0.5, acorn@^6.2.1: +acorn@^6.2.1: version "6.3.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-6.3.0.tgz#0087509119ffa4fc0a0041d1e93a417e68cb856e" integrity sha512-/czfa8BwS88b9gWQVhc8eknunSA2DoJpJyTQkhheIf5E48u1N0R4q/YxxsAeqRrmK9TQ/uYfgLDfZo91UlANIA== @@ -5595,14 +5575,7 @@ are-we-there-yet@~1.1.2: delegates "^1.0.0" readable-stream "^2.0.6" -argparse@^1.0.7: - version "1.0.9" - resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.9.tgz#73d83bc263f86e97f8cc4f6bae1b0e90a7d22c86" - integrity sha1-c9g7wmP4bpf4zE9rrhsOkKfSLIY= - dependencies: - sprintf-js "~1.0.2" - -argparse@~1.0.9: +argparse@^1.0.7, argparse@~1.0.9: version "1.0.10" resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911" integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg== @@ -5993,7 +5966,7 @@ async@2.4.0: dependencies: lodash "^4.14.0" -async@2.6.1, async@^2.5.0, async@^2.6.0, async@^2.6.1: +async@2.6.1, async@^2.6.0, async@^2.6.1: version "2.6.1" resolved "https://registry.yarnpkg.com/async/-/async-2.6.1.tgz#b245a23ca71930044ec53fa46aa00a3e87c6a610" integrity sha512-fNEiL2+AZt6AlAw/29Cr0UDe4sRAHCpEHh54WMz+Bb7QfNcFw4h3loofyJpLeQs4Yx7yuqu/2dLgM5hKOs6HlQ== @@ -6685,11 +6658,6 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767" integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c= -base62@^1.1.0: - version "1.2.8" - resolved "https://registry.yarnpkg.com/base62/-/base62-1.2.8.tgz#1264cb0fb848d875792877479dbe8bae6bae3428" - integrity sha512-V6YHUbjLxN1ymqNLb1DPHoU1CpfdL7d2YTIp5W3U4hhoG4hhxNmsFDs66M9EXxBiSEke5Bt5dwdfMwwZF70iLA== - base64-arraybuffer@0.1.5: version "0.1.5" resolved "https://registry.yarnpkg.com/base64-arraybuffer/-/base64-arraybuffer-0.1.5.tgz#73926771923b5a19747ad666aa5cd4bf9c6e9ce8" @@ -8016,7 +7984,7 @@ chroma-js@^1.4.1: resolved "https://registry.yarnpkg.com/chroma-js/-/chroma-js-1.4.1.tgz#eb2d9c4d1ff24616be84b35119f4d26f8205f134" integrity sha512-jTwQiT859RTFN/vIf7s+Vl/Z2LcMrvMv3WUFmd/4u76AdlFC0NTNgqEEFPcRiHmAswPsMiQEDZLM8vX8qXpZNQ== -chrome-trace-event@^1.0.0, chrome-trace-event@^1.0.2: +chrome-trace-event@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz#234090ee97c7d4ad1a2c4beae27505deffc608a4" integrity sha512-9e/zx1jw7B4CO+c/RXoCsfg/x1AfUBioy4owYH0bJprEYAx5hRFLRhWBqHAG57D0ZM4H7vxbP7bPe0VwhQRYDQ== @@ -8548,12 +8516,12 @@ commander@3.0.2: resolved "https://registry.yarnpkg.com/commander/-/commander-3.0.2.tgz#6837c3fb677ad9933d1cfba42dd14d5117d6b39e" integrity sha512-Gar0ASD4BDyKC4hl4DwHqDrmvjoxWKZigVnAbn5H1owvm4CxCPdb0HQDehwNYMJpla5+M2tPmPARzhtYuwpHow== -commander@^2.13.0, commander@^2.15.1, commander@^2.16.0, commander@^2.19.0, commander@^2.20.0, commander@^2.7.1: +commander@^2.13.0, commander@^2.15.1, commander@^2.16.0, commander@^2.19.0, commander@^2.20.0: version "2.20.0" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.0.tgz#d58bb2b5c1ee8f87b0d340027e9e94e222c5a422" integrity sha512-7j2y+40w61zy6YC2iRNpUe/NwhNyoXrYpHMrSunaMG64nRnaf96zO/KMQR4OyN/UnE5KLyEBnKHd4aG3rskjpQ== -commander@^2.5.0: +commander@^2.7.1: version "2.20.3" resolved "https://registry.yarnpkg.com/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" integrity sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ== @@ -8585,21 +8553,6 @@ commondir@^1.0.1: resolved "https://registry.yarnpkg.com/commondir/-/commondir-1.0.1.tgz#ddd800da0c66127393cca5950ea968a3aaf1253b" integrity sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs= -commoner@^0.10.1: - version "0.10.8" - resolved "https://registry.yarnpkg.com/commoner/-/commoner-0.10.8.tgz#34fc3672cd24393e8bb47e70caa0293811f4f2c5" - integrity sha1-NPw2cs0kOT6LtH5wyqApOBH08sU= - dependencies: - commander "^2.5.0" - detective "^4.3.1" - glob "^5.0.15" - graceful-fs "^4.1.2" - iconv-lite "^0.4.5" - mkdirp "^0.5.0" - private "^0.1.6" - q "^1.1.2" - recast "^0.11.17" - compare-versions@3.5.1: version "3.5.1" resolved "https://registry.yarnpkg.com/compare-versions/-/compare-versions-3.5.1.tgz#26e1f5cf0d48a77eced5046b9f67b6b61075a393" @@ -10086,6 +10039,11 @@ deep-extend@^0.6.0: resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac" integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA== +deep-freeze-strict@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/deep-freeze-strict/-/deep-freeze-strict-1.1.1.tgz#77d0583ca24a69be4bbd9ac2fae415d55523e5b0" + integrity sha1-d9BYPKJKab5LvZrC+uQV1VUj5bA= + deep-is@^0.1.3, deep-is@~0.1.3: version "0.1.3" resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34" @@ -10169,7 +10127,7 @@ define-property@^2.0.2: is-descriptor "^1.0.2" isobject "^3.0.1" -defined@^1.0.0, defined@~1.0.0: +defined@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693" integrity sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM= @@ -10418,14 +10376,6 @@ detective-typescript@^5.1.1: node-source-walk "^4.2.0" typescript "^3.4.5" -detective@^4.3.1: - version "4.7.1" - resolved "https://registry.yarnpkg.com/detective/-/detective-4.7.1.tgz#0eca7314338442febb6d65da54c10bb1c82b246e" - integrity sha512-H6PmeeUcZloWtdt4DAkFyzFL94arpHr3NOwwmVILFiy+9Qd4JTxxXrzfyGk/lmct2qVGBwTSwSXagqu2BxmWig== - dependencies: - acorn "^5.2.1" - defined "^1.0.0" - dezalgo@^1.0.0: version "1.0.3" resolved "https://registry.yarnpkg.com/dezalgo/-/dezalgo-1.0.3.tgz#7f742de066fc748bc8db820569dddce49bf0d456" @@ -10975,9 +10925,9 @@ element-resize-detector@^1.1.15: batch-processor "^1.0.0" elliptic@^6.0.0: - version "6.4.0" - resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.4.0.tgz#cac9af8762c85836187003c8dfe193e5e2eae5df" - integrity sha1-ysmvh2LIWDYYcAPI3+GT5eLq5d8= + version "6.5.2" + resolved "https://registry.yarnpkg.com/elliptic/-/elliptic-6.5.2.tgz#05c5678d7173c049d8ca433552224a495d0e3762" + integrity sha512-f4x70okzZbIQl/NSRLkI/+tteV/9WqL98zx+SQ69KbXxmVrmjwsNUPn/gYJJ0sHvEak24cZgHIPegRePAtA/xw== dependencies: bn.js "^4.4.0" brorand "^1.0.1" @@ -11018,11 +10968,6 @@ enabled@1.0.x: dependencies: env-variable "0.0.x" -encode-uri-query@1.0.1: - version "1.0.1" - resolved "https://registry.yarnpkg.com/encode-uri-query/-/encode-uri-query-1.0.1.tgz#e9c70d3e1aab71b039e55b38a166013508803ba8" - integrity sha1-6ccNPhqrcbA55Vs4oWYBNQiAO6g= - encodeurl@^1.0.2, encodeurl@~1.0.1, encodeurl@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" @@ -11132,14 +11077,6 @@ env-variable@0.0.x: resolved "https://registry.yarnpkg.com/env-variable/-/env-variable-0.0.5.tgz#913dd830bef11e96a039c038d4130604eba37f88" integrity sha512-zoB603vQReOFvTg5xMl9I1P2PnHsHQQKTEowsKKD7nseUfJq6UWzK+4YtlWUO1nhiQUxe6XMkk+JleSZD1NZFA== -envify@^3.0.0: - version "3.4.1" - resolved "https://registry.yarnpkg.com/envify/-/envify-3.4.1.tgz#d7122329e8df1688ba771b12501917c9ce5cbce8" - integrity sha1-1xIjKejfFoi6dxsSUBkXyc5cvOg= - dependencies: - jstransform "^11.0.3" - through "~2.3.4" - enzyme-adapter-react-16@^1.15.1: version "1.15.1" resolved "https://registry.yarnpkg.com/enzyme-adapter-react-16/-/enzyme-adapter-react-16-1.15.1.tgz#8ad55332be7091dc53a25d7d38b3485fc2ba50d5" @@ -11490,10 +11427,10 @@ escope@^3.6.0: esrecurse "^4.1.0" estraverse "^4.1.1" -eslint-config-prettier@^6.4.0: - version "6.4.0" - resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-6.4.0.tgz#0a04f147e31d33c6c161b2dd0971418ac52d0477" - integrity sha512-YrKucoFdc7SEko5Sxe4r6ixqXPDP1tunGw91POeZTTRKItf/AMFYt/YLEQtZMkR2LVpAVhcAcZgcWpm1oGPW7w== +eslint-config-prettier@^6.9.0: + version "6.9.0" + resolved "https://registry.yarnpkg.com/eslint-config-prettier/-/eslint-config-prettier-6.9.0.tgz#430d24822e82f7deb1e22a435bfa3999fae4ad64" + integrity sha512-k4E14HBtcLv0uqThaI6I/n1LEqROp8XaPu6SO9Z32u5NlGRC07Enu1Bh2KEFw4FNHbekH8yzbIU9kUGxbiGmCA== dependencies: get-stdin "^6.0.0" @@ -11532,20 +11469,12 @@ eslint-import-resolver-webpack@0.11.1: resolve "^1.10.0" semver "^5.3.0" -eslint-module-utils@2.4.1: - version "2.4.1" - resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.4.1.tgz#7b4675875bf96b0dbf1b21977456e5bb1f5e018c" - integrity sha512-H6DOj+ejw7Tesdgbfs4jeS4YMFrT8uI8xwd1gtQqXssaR0EQ26L+2O/w6wkYFy2MymON0fTwHmXBvvfLNZVZEw== - dependencies: - debug "^2.6.8" - pkg-dir "^2.0.0" - -eslint-module-utils@^2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.4.0.tgz#8b93499e9b00eab80ccb6614e69f03678e84e09a" - integrity sha512-14tltLm38Eu3zS+mt0KvILC3q8jyIAH518MlG+HO0p+yK885Lb1UHTY/UgR91eOyGdmxAPb+OLoW4znqIT6Ndw== +eslint-module-utils@2.5.0, eslint-module-utils@^2.4.1: + version "2.5.0" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.5.0.tgz#cdf0b40d623032274ccd2abd7e64c4e524d6e19c" + integrity sha512-kCo8pZaNz2dsAW7nCUjuVoI11EBXXpIzfNxmaoLhXoRDOnqXLC4iSGVRdZPhOitfbdEfMEfKOiENaK6wDPZEGw== dependencies: - debug "^2.6.8" + debug "^2.6.9" pkg-dir "^2.0.0" eslint-plugin-babel@^5.3.0: @@ -11555,51 +11484,57 @@ eslint-plugin-babel@^5.3.0: dependencies: eslint-rule-composer "^0.3.0" -eslint-plugin-ban@^1.3.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-ban/-/eslint-plugin-ban-1.3.0.tgz#be9714cb9e01a1adec6c86cdb182e97636eafe44" - integrity sha512-A9A2z60UeVj7/BdKzeIjVEGAog/4QXAyOkZ98AUnZc7fsRp+J7YW7+U/YEVpBJqjSiU/FGUA5tGJoI34ul/TyA== +eslint-plugin-ban@^1.4.0: + version "1.4.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-ban/-/eslint-plugin-ban-1.4.0.tgz#b3a7b000412921336b1feeece5b8ce9a69dea605" + integrity sha512-wtrUOLg8WUiGDkVnmyMseLRtXYBM+bJTe2STvhqznHVj6RPAiNEVLbvDj2b0WWwY/2ldKqeaw3iHUHwfCJ8c8Q== dependencies: requireindex "~1.2.0" -eslint-plugin-cypress@^2.7.0: - version "2.7.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-cypress/-/eslint-plugin-cypress-2.7.0.tgz#117f14ce63698e4c4f3afea3d7e27025c8d504f0" - integrity sha512-52Lq5ePCD/8jc536e1RqtLfj33BAy1s7BlYgCjbG39J5kqUitcTlRY5i3NRoeAyPHueDwETsq0eASF44ugLosQ== +eslint-plugin-cypress@^2.8.1: + version "2.8.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-cypress/-/eslint-plugin-cypress-2.8.1.tgz#981a0f3658b40de430bcf05cabc96b396487c91f" + integrity sha512-jDpcP+MmjmqQO/x3bwIXgp4cl7Q66RYS5/IsuOQP4Qo2sEqE3DI8tTxBQ1EhnV5qEDd2Z2TYHR+5vYI6oCN4uw== dependencies: globals "^11.12.0" -eslint-plugin-es@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-es/-/eslint-plugin-es-2.0.0.tgz#0f5f5da5f18aa21989feebe8a73eadefb3432976" - integrity sha512-f6fceVtg27BR02EYnBhgWLFQfK6bN4Ll0nQFrBHOlCsAyxeZkn0NHns5O0YZOPrV1B3ramd6cgFwaoFLcSkwEQ== +eslint-plugin-es@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-es/-/eslint-plugin-es-3.0.0.tgz#98cb1bc8ab0aa807977855e11ad9d1c9422d014b" + integrity sha512-6/Jb/J/ZvSebydwbBJO1R9E5ky7YeElfK56Veh7e4QGFHCXoIXGH9HhVz+ibJLM3XJ1XjP+T7rKBLUa/Y7eIng== dependencies: - eslint-utils "^1.4.2" + eslint-utils "^2.0.0" regexpp "^3.0.0" -eslint-plugin-import@^2.18.2: - version "2.18.2" - resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.18.2.tgz#02f1180b90b077b33d447a17a2326ceb400aceb6" - integrity sha512-5ohpsHAiUBRNaBWAF08izwUGlbrJoJJ+W9/TBwsGoR1MnlgfwMIKrFeSjWbt6moabiXW9xNvtFz+97KHRfI4HQ== +eslint-plugin-eslint-plugin@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-eslint-plugin/-/eslint-plugin-eslint-plugin-2.1.0.tgz#a7a00f15a886957d855feacaafee264f039e62d5" + integrity sha512-kT3A/ZJftt28gbl/Cv04qezb/NQ1dwYIbi8lyf806XMxkus7DvOVCLIfTXMrorp322Pnoez7+zabXH29tADIDg== + +eslint-plugin-import@^2.19.1: + version "2.19.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.19.1.tgz#5654e10b7839d064dd0d46cd1b88ec2133a11448" + integrity sha512-x68131aKoCZlCae7rDXKSAQmbT5DQuManyXo2sK6fJJ0aK5CWAkv6A6HJZGgqC8IhjQxYPgo6/IY4Oz8AFsbBw== dependencies: array-includes "^3.0.3" + array.prototype.flat "^1.2.1" contains-path "^0.1.0" debug "^2.6.9" doctrine "1.5.0" eslint-import-resolver-node "^0.3.2" - eslint-module-utils "^2.4.0" + eslint-module-utils "^2.4.1" has "^1.0.3" minimatch "^3.0.4" object.values "^1.1.0" read-pkg-up "^2.0.0" - resolve "^1.11.0" + resolve "^1.12.0" -eslint-plugin-jest@^22.19.0: - version "22.19.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-22.19.0.tgz#0cf90946a8c927d40a2c64458c89bb635d0f2a0b" - integrity sha512-4zUc3rh36ds0SXdl2LywT4YWA3zRe8sfLhz8bPp8qQPIKvynTTkNGwmSCMpl5d9QiZE2JxSinGF+WD8yU+O0Lg== +eslint-plugin-jest@^23.3.0: + version "23.3.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-23.3.0.tgz#b1443d0c46d6a0de9ef3de78176dd6688c7d5326" + integrity sha512-GE6CR4ESJeu6Huw7vfZfaXHmX2R2kCFvf2X9OMcOxfP158yLKgLWz7PqLYTwRDACi84IhpmRxO8lK7GGwG05UQ== dependencies: - "@typescript-eslint/experimental-utils" "^1.13.0" + "@typescript-eslint/experimental-utils" "^2.5.0" eslint-plugin-jsx-a11y@^6.2.3: version "6.2.3" @@ -11616,10 +11551,10 @@ eslint-plugin-jsx-a11y@^6.2.3: has "^1.0.3" jsx-ast-utils "^2.2.1" -eslint-plugin-mocha@^6.2.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-mocha/-/eslint-plugin-mocha-6.2.0.tgz#16ff9ce4d5a6a35af522d5db0ce3c8946566e4c1" - integrity sha512-vE/+tHJVom2BkMOiwkOKcAM5YqGPk3C6gMvQ32DHihKkaXF6vmxtj3UEOg64wP3m8/Zk5V/UmQbFE5nqu1EXSg== +eslint-plugin-mocha@^6.2.2: + version "6.2.2" + resolved "https://registry.yarnpkg.com/eslint-plugin-mocha/-/eslint-plugin-mocha-6.2.2.tgz#6ef4b78bd12d744beb08a06e8209de330985100d" + integrity sha512-oNhPzfkT6Q6CJ0HMVJ2KLxEWG97VWGTmuHOoRcDLE0U88ugUyFNV9wrT2XIt5cGtqc5W9k38m4xTN34L09KhBA== dependencies: ramda "^0.26.1" @@ -11628,13 +11563,13 @@ eslint-plugin-no-unsanitized@^3.0.2: resolved "https://registry.yarnpkg.com/eslint-plugin-no-unsanitized/-/eslint-plugin-no-unsanitized-3.0.2.tgz#83c6fcf8e34715112757e03dd4ee436dce29ed45" integrity sha512-JnwpoH8Sv4QOjrTDutENBHzSnyYtspdjtglYtqUtAHe6f6LLKqykJle+UwFPg23GGwt5hI3amS9CRDezW8GAww== -eslint-plugin-node@^10.0.0: - version "10.0.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-node/-/eslint-plugin-node-10.0.0.tgz#fd1adbc7a300cf7eb6ac55cf4b0b6fc6e577f5a6" - integrity sha512-1CSyM/QCjs6PXaT18+zuAXsjXGIGo5Rw630rSKwokSs2jrYURQc4R5JZpoanNCqwNmepg+0eZ9L7YiRUJb8jiQ== +eslint-plugin-node@^11.0.0: + version "11.0.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-node/-/eslint-plugin-node-11.0.0.tgz#365944bb0804c5d1d501182a9bc41a0ffefed726" + integrity sha512-chUs/NVID+sknFiJzxoN9lM7uKSOEta8GC8365hw1nDfwIPIjjpRSwwPvQanWv8dt/pDe9EV4anmVSwdiSndNg== dependencies: - eslint-plugin-es "^2.0.0" - eslint-utils "^1.4.2" + eslint-plugin-es "^3.0.0" + eslint-utils "^2.0.0" ignore "^5.1.1" minimatch "^3.0.4" resolve "^1.10.1" @@ -11645,46 +11580,39 @@ eslint-plugin-prefer-object-spread@^1.2.1: resolved "https://registry.yarnpkg.com/eslint-plugin-prefer-object-spread/-/eslint-plugin-prefer-object-spread-1.2.1.tgz#27fb91853690cceb3ae6101d9c8aecc6a67a402c" integrity sha1-J/uRhTaQzOs65hAdnIrsxqZ6QCw= -eslint-plugin-prettier@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.1.tgz#507b8562410d02a03f0ddc949c616f877852f2ba" - integrity sha512-A+TZuHZ0KU0cnn56/9mfR7/KjUJ9QNVXUhwvRFSR7PGPe0zQR6PTkmyqg1AtUUEOzTqeRsUwyKFh0oVZKVCrtA== +eslint-plugin-prettier@^3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/eslint-plugin-prettier/-/eslint-plugin-prettier-3.1.2.tgz#432e5a667666ab84ce72f945c72f77d996a5c9ba" + integrity sha512-GlolCC9y3XZfv3RQfwGew7NnuFDKsfI4lbvRK+PIIo23SFH+LemGs4cKwzAaRa+Mdb+lQO/STaIayno8T5sJJA== dependencies: prettier-linter-helpers "^1.0.0" -eslint-plugin-react-hooks@^2.1.2: - version "2.1.2" - resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-2.1.2.tgz#1358d2acb2c5e02b7e90c37e611ac258a488e3a7" - integrity sha512-ZR+AyesAUGxJAyTFlF3MbzeVHAcQTFQt1fFVe5o0dzY/HFoj1dgQDMoIkiM+ltN/HhlHBYX4JpJwYonjxsyQMA== +eslint-plugin-react-hooks@^2.3.0: + version "2.3.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-2.3.0.tgz#53e073961f1f5ccf8dd19558036c1fac8c29d99a" + integrity sha512-gLKCa52G4ee7uXzdLiorca7JIQZPPXRAQDXV83J4bUEeUuc5pIEyZYAZ45Xnxe5IuupxEqHS+hUhSLIimK1EMw== -eslint-plugin-react@^7.16.0: - version "7.16.0" - resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.16.0.tgz#9928e4f3e2122ed3ba6a5b56d0303ba3e41d8c09" - integrity sha512-GacBAATewhhptbK3/vTP09CbFrgUJmBSaaRcWdbQLFvUZy9yVcQxigBNHGPU/KE2AyHpzj3AWXpxoMTsIDiHug== +eslint-plugin-react@^7.17.0: + version "7.17.0" + resolved "https://registry.yarnpkg.com/eslint-plugin-react/-/eslint-plugin-react-7.17.0.tgz#a31b3e134b76046abe3cd278e7482bd35a1d12d7" + integrity sha512-ODB7yg6lxhBVMeiH1c7E95FLD4E/TwmFjltiU+ethv7KPdCwgiFuOZg9zNRHyufStTDLl/dEFqI2Q1VPmCd78A== dependencies: array-includes "^3.0.3" doctrine "^2.1.0" + eslint-plugin-eslint-plugin "^2.1.0" has "^1.0.3" - jsx-ast-utils "^2.2.1" + jsx-ast-utils "^2.2.3" object.entries "^1.1.0" - object.fromentries "^2.0.0" + object.fromentries "^2.0.1" object.values "^1.1.0" prop-types "^15.7.2" - resolve "^1.12.0" + resolve "^1.13.1" eslint-rule-composer@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/eslint-rule-composer/-/eslint-rule-composer-0.3.0.tgz#79320c927b0c5c0d3d3d2b76c8b4a488f25bbaf9" integrity sha512-bt+Sh8CtDmn2OajxvNO+BX7Wn4CIWMpTRm3MaiKPCQcnnlm0CS2mhui6QaoeQugs+3Kj2ESKEEGJUdVafwhiCg== -eslint-scope@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.0.tgz#50bf3071e9338bcdc43331794a0cb533f0136172" - integrity sha512-1G6UTDi7Jc1ELFwnR58HV4fK9OQK4S6N985f166xqXxpjU6plxFISJa2Ba9KCQuFa8RCnj/lSFJbHo7UFDBnUA== - dependencies: - esrecurse "^4.1.0" - estraverse "^4.1.1" - eslint-scope@^4.0.3: version "4.0.3" resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-4.0.3.tgz#ca03833310f6889a3264781aa82e63eb9cfe7848" @@ -11701,13 +11629,20 @@ eslint-scope@^5.0.0: esrecurse "^4.1.0" estraverse "^4.1.1" -eslint-utils@^1.4.2, eslint-utils@^1.4.3: +eslint-utils@^1.4.3: version "1.4.3" resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-1.4.3.tgz#74fec7c54d0776b6f67e0251040b5806564e981f" integrity sha512-fbBN5W2xdY45KulGXmLHZ3c3FHfVYmKg0IrAKGOkT/464PQsx2UeIzfz1RmEci+KLm1bBaAzZAh8+/E+XAeZ8Q== dependencies: eslint-visitor-keys "^1.1.0" +eslint-utils@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/eslint-utils/-/eslint-utils-2.0.0.tgz#7be1cc70f27a72a76cd14aa698bcabed6890e1cd" + integrity sha512-0HCPuJv+7Wv1bACm8y5/ECVfYdfsAm9xmVb7saeFlxjPYALefjhbYoCkBjPdPzGH8wWyTpAez82Fh3VKYEZ8OA== + dependencies: + eslint-visitor-keys "^1.1.0" + eslint-visitor-keys@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#3f3180fb2e291017716acb4c9d6d5b5c34a6a81d" @@ -11757,10 +11692,10 @@ eslint@^2.7.0: text-table "~0.2.0" user-home "^2.0.0" -eslint@^6.5.1: - version "6.5.1" - resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.5.1.tgz#828e4c469697d43bb586144be152198b91e96ed6" - integrity sha512-32h99BoLYStT1iq1v2P9uwpyznQ4M2jRiFB6acitKz52Gqn+vPaMDUTB1bYi1WN4Nquj2w+t+bimYUG83DC55A== +eslint@^6.8.0: + version "6.8.0" + resolved "https://registry.yarnpkg.com/eslint/-/eslint-6.8.0.tgz#62262d6729739f9275723824302fb227c8c93ffb" + integrity sha512-K+Iayyo2LtyYhDSYwz5D5QdWw0hCacNzyq1Y821Xna2xSJj7cijoLLYmLxTQgcgZ9mC61nryMy9S7GRbYpI5Ig== dependencies: "@babel/code-frame" "^7.0.0" ajv "^6.10.0" @@ -11769,19 +11704,19 @@ eslint@^6.5.1: debug "^4.0.1" doctrine "^3.0.0" eslint-scope "^5.0.0" - eslint-utils "^1.4.2" + eslint-utils "^1.4.3" eslint-visitor-keys "^1.1.0" - espree "^6.1.1" + espree "^6.1.2" esquery "^1.0.1" esutils "^2.0.2" file-entry-cache "^5.0.1" functional-red-black-tree "^1.0.1" glob-parent "^5.0.0" - globals "^11.7.0" + globals "^12.1.0" ignore "^4.0.6" import-fresh "^3.0.0" imurmurhash "^0.1.4" - inquirer "^6.4.1" + inquirer "^7.0.0" is-glob "^4.0.0" js-yaml "^3.13.1" json-stable-stringify-without-jsonify "^1.0.1" @@ -11790,7 +11725,7 @@ eslint@^6.5.1: minimatch "^3.0.4" mkdirp "^0.5.1" natural-compare "^1.4.0" - optionator "^0.8.2" + optionator "^0.8.3" progress "^2.0.0" regexpp "^2.0.1" semver "^6.1.2" @@ -11808,20 +11743,15 @@ espree@^3.1.6: acorn "^5.5.0" acorn-jsx "^3.0.0" -espree@^6.1.1: - version "6.1.1" - resolved "https://registry.yarnpkg.com/espree/-/espree-6.1.1.tgz#7f80e5f7257fc47db450022d723e356daeb1e5de" - integrity sha512-EYbr8XZUhWbYCqQRW0duU5LxzL5bETN6AjKBGy1302qqzPaCH10QbRg3Wvco79Z8x9WbiE8HYB4e75xl6qUYvQ== +espree@^6.1.2: + version "6.1.2" + resolved "https://registry.yarnpkg.com/espree/-/espree-6.1.2.tgz#6c272650932b4f91c3714e5e7b5f5e2ecf47262d" + integrity sha512-2iUPuuPP+yW1PZaMSDM9eyVf8D5P0Hi8h83YtZ5bPc/zHYjII5khoixIUTMO794NOY8F/ThF1Bo8ncZILarUTA== dependencies: - acorn "^7.0.0" - acorn-jsx "^5.0.2" + acorn "^7.1.0" + acorn-jsx "^5.1.0" eslint-visitor-keys "^1.1.0" -esprima-fb@^15001.1.0-dev-harmony-fb: - version "15001.1.0-dev-harmony-fb" - resolved "https://registry.yarnpkg.com/esprima-fb/-/esprima-fb-15001.1.0-dev-harmony-fb.tgz#30a947303c6b8d5e955bee2b99b1d233206a6901" - integrity sha1-MKlHMDxrjV6VW+4rmbHSMyBqaQE= - esprima@2.7.x, esprima@^2.7.1: version "2.7.3" resolved "https://registry.yarnpkg.com/esprima/-/esprima-2.7.3.tgz#96e3b70d5779f6ad49cd032673d1c312767ba581" @@ -11832,21 +11762,16 @@ esprima@^3.1.3, esprima@~3.1.0: resolved "https://registry.yarnpkg.com/esprima/-/esprima-3.1.3.tgz#fdca51cee6133895e3c88d535ce49dbff62a4633" integrity sha1-/cpRzuYTOJXjyI1TXOSdv/YqRjM= -esprima@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.0.tgz#4499eddcd1110e0b218bacf2fa7f7f59f55ca804" - integrity sha512-oftTcaMu/EGrEIu904mWteKIv8vMuOgGYo7EhVJJN00R/EED9DCua/xxHRdYnKtcECzVg7xOWhflvJMnqcFZjw== +esprima@^4.0.0, esprima@~4.0.0: + version "4.0.1" + resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" + integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== esprima@~1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/esprima/-/esprima-1.0.4.tgz#9f557e08fc3b4d26ece9dd34f8fbf476b62585ad" integrity sha1-n1V+CPw7TSbs6d00+Pv0drYlha0= -esprima@~4.0.0: - version "4.0.1" - resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71" - integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A== - esquery@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.0.1.tgz#406c51658b1f5991a5f9b62b1dc25b00e3e5c708" @@ -12448,11 +12373,16 @@ fast-json-stable-stringify@^2.0.0: resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz#d5142c0caee6b1189f87d3a76111064f86c8bbf2" integrity sha1-1RQsDK7msRifh9OnYREGT4bIu/I= -fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.4: +fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.4, fast-levenshtein@~2.0.6: version "2.0.6" resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917" integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc= +fast-memoize@^2.5.1: + version "2.5.1" + resolved "https://registry.yarnpkg.com/fast-memoize/-/fast-memoize-2.5.1.tgz#c3519241e80552ce395e1a32dcdde8d1fd680f5d" + integrity sha512-xdmw296PCL01tMOXx9mdJSmWY29jQgxyuZdq0rEHMu+Tpe1eOEtCycoG6chzlcrWsNgpZP7oL8RiQr7+G6Bl6g== + fast-safe-stringify@^2.0.4: version "2.0.6" resolved "https://registry.yarnpkg.com/fast-safe-stringify/-/fast-safe-stringify-2.0.6.tgz#04b26106cc56681f51a044cfc0d76cf0008ac2c2" @@ -12513,17 +12443,6 @@ fb-watchman@^2.0.0: dependencies: bser "^2.0.0" -fbjs@^0.6.1: - version "0.6.1" - resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.6.1.tgz#9636b7705f5ba9684d44b72f78321254afc860f7" - integrity sha1-lja3cF9bqWhNRLcveDISVK/IYPc= - dependencies: - core-js "^1.0.0" - loose-envify "^1.0.0" - promise "^7.0.3" - ua-parser-js "^0.7.9" - whatwg-fetch "^0.9.0" - fbjs@^0.8.0, fbjs@^0.8.1, fbjs@^0.8.16: version "0.8.17" resolved "https://registry.yarnpkg.com/fbjs/-/fbjs-0.8.17.tgz#c4d598ead6949112653d6588b01a5cdcd9f90fdd" @@ -13178,15 +13097,10 @@ fp-ts@^1.0.0: resolved "https://registry.yarnpkg.com/fp-ts/-/fp-ts-1.12.0.tgz#d333310e4ac104cdcb6bea47908e381bb09978e7" integrity sha512-fWwnAgVlTsV26Ruo9nx+fxNHIm6l1puE1VJ/C0XJ3nRQJJJIgRHYw6sigB3MuNFZL1o4fpGlhwFhcbxHK0RsOA== -fp-ts@^1.14.2: - version "1.17.3" - resolved "https://registry.yarnpkg.com/fp-ts/-/fp-ts-1.17.3.tgz#5064afc4bee8ddcaea567479bfc62d527e015825" - integrity sha512-r4gHfAWaRrYPsmdzRl1U9CkpbdOi8fPg5F5KiazAadENz5DKdWEaCDPl2Tf92fvkZGD/ekZ3EHu3gtXIVcsXtA== - -fp-ts@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/fp-ts/-/fp-ts-2.0.5.tgz#9560d8a6a4f53cbda9f9b31ed8d1458e41939e07" - integrity sha512-opI5r+rVlpZE7Rhk0YtqsrmxGkbIw0dRNqGca8FEAMMnjomXotG+R9QkLQg20onx7R8qhepAn4CCOP8usma/Xw== +fp-ts@^2.3.1: + version "2.3.1" + resolved "https://registry.yarnpkg.com/fp-ts/-/fp-ts-2.3.1.tgz#8068bfcca118227932941101e062134d7ecd9119" + integrity sha512-KevPBnYt0aaJiuUzmU9YIxjrhC9AgJ8CLtLlXmwArovlNTeYM5NtEoKd86B0wHd7FIbzeE8sNXzCoYIOr7e6Iw== fragment-cache@^0.2.1: version "0.2.1" @@ -13916,7 +13830,7 @@ global@^4.4.0: min-document "^2.19.0" process "^0.11.10" -globals@^11.1.0, globals@^11.7.0: +globals@^11.1.0: version "11.7.0" resolved "https://registry.yarnpkg.com/globals/-/globals-11.7.0.tgz#a583faa43055b1aca771914bf68258e2fc125673" integrity sha512-K8BNSPySfeShBQXsahYB/AbbWruVOTyVpgoIDnl8odPpeSfP2J5QO2oLFFdl2j7GfDCtZj2bMKar2T49itTPCg== @@ -13926,6 +13840,13 @@ globals@^11.12.0: resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e" integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA== +globals@^12.1.0: + version "12.3.0" + resolved "https://registry.yarnpkg.com/globals/-/globals-12.3.0.tgz#1e564ee5c4dded2ab098b0f88f24702a3c56be13" + integrity sha512-wAfjdLgFsPZsklLJvOBUBmzYE8/CwhEqSBEMRXA3qxIiNtyqvjYurAtIfDh6chlEPUfmTY3MnZh5Hfh4q0UlIw== + dependencies: + type-fest "^0.8.1" + globals@^9.18.0, globals@^9.2.0: version "9.18.0" resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a" @@ -14175,7 +14096,7 @@ got@^8.3.1, got@^8.3.2: url-parse-lax "^3.0.0" url-to-options "^1.0.1" -graceful-fs@4.X, graceful-fs@^4.0.0, graceful-fs@^4.1.10, graceful-fs@^4.1.11, graceful-fs@^4.1.2, graceful-fs@^4.1.4, graceful-fs@^4.1.6: +graceful-fs@4.X, graceful-fs@^4.0.0, graceful-fs@^4.1.10, graceful-fs@^4.1.11, graceful-fs@^4.1.4: version "4.1.11" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.11.tgz#0e8bdfe4d1ddb8854d64e04ea7c00e2a026e5658" integrity sha1-Dovf5NHduIVNZOBOp8AOKgJuVlg= @@ -14185,6 +14106,11 @@ graceful-fs@^4.1.15, graceful-fs@^4.1.9: resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.1.15.tgz#ffb703e1066e8a0eeaa4c8b80ba9253eeefbfb00" integrity sha512-6uHUhOPEBgQ24HM+r6b/QwWfZq+yiFcipKFrOFiBEnWdy5sdzYoi+pJeQaPI5qOLRFqWmAXUPQNsielzdLoecA== +graceful-fs@^4.1.2, graceful-fs@^4.1.6: + version "4.2.3" + resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.3.tgz#4a12ff1b60376ef09862c2093edd908328be8423" + integrity sha512-a30VEBm4PEdx1dRB7MFK7BejejvCvBronbLjht+sHuGYj8PHs7M/5Z+rt5lw551vZ7yfTCj4Vuyy3mSJytDWRQ== + graceful-fs@^4.2.0, graceful-fs@^4.2.2: version "4.2.2" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.2.tgz#6f0952605d0140c1cfdb138ed005775b92d67b02" @@ -14692,7 +14618,7 @@ handle-thing@^2.0.0: resolved "https://registry.yarnpkg.com/handle-thing/-/handle-thing-2.0.0.tgz#0e039695ff50c93fc288557d696f3c1dc6776754" integrity sha512-d4sze1JNC454Wdo2fkuyzCr6aHcbL6PGGuFAz0Li/NcOm1tCHGnWDRmJP85dh9IhQErTc2svWFEX5xHIOo//kQ== -handlebars@4.5.3: +handlebars@4.5.3, handlebars@^4.0.1, handlebars@^4.1.2: version "4.5.3" resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.5.3.tgz#5cf75bd8714f7605713511a56be7c349becb0482" integrity sha512-3yPecJoJHK/4c6aZhSvxOyG4vJKDshV36VHp0iVCDVh7o9w2vwi3NSnL2MMPj3YdduqaBcu7cGbggJQM0br9xA== @@ -14703,28 +14629,6 @@ handlebars@4.5.3: optionalDependencies: uglify-js "^3.1.4" -handlebars@^4.0.1: - version "4.0.12" - resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.0.12.tgz#2c15c8a96d46da5e266700518ba8cb8d919d5bc5" - integrity sha512-RhmTekP+FZL+XNhwS1Wf+bTTZpdLougwt5pcgA1tuz6Jcx0fpH/7z0qd71RKnZHBCxIRBHfBOnio4gViPemNzA== - dependencies: - async "^2.5.0" - optimist "^0.6.1" - source-map "^0.6.1" - optionalDependencies: - uglify-js "^3.1.4" - -handlebars@^4.1.2: - version "4.1.2" - resolved "https://registry.yarnpkg.com/handlebars/-/handlebars-4.1.2.tgz#b6b37c1ced0306b221e094fc7aca3ec23b131b67" - integrity sha512-nvfrjqvt9xQ8Z/w0ijewdD/vvWDTOweBUm96NTr66Wfvo1mJenBLwcYmPs3TIBP5ruzYGD7Hx/DaM9RmhroGPw== - dependencies: - neo-async "^2.6.0" - optimist "^0.6.1" - source-map "^0.6.1" - optionalDependencies: - uglify-js "^3.1.4" - hapi-auth-cookie@^9.0.0: version "9.0.0" resolved "https://registry.yarnpkg.com/hapi-auth-cookie/-/hapi-auth-cookie-9.0.0.tgz#3b0af443334e2bd92490ddb17bed16e3e9edfd01" @@ -15094,13 +14998,6 @@ hoist-non-react-statics@^2.3.1, hoist-non-react-statics@^2.5.0, hoist-non-react- resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-2.5.5.tgz#c5903cf409c0dfd908f388e619d86b9c1174cb47" integrity sha512-rqcy4pJo55FTTLWt+bU8ukscqHeE/e9KWvsOW2b/a3afxQZhwkQdT1rPPCJ0rYXdj4vNcasY8zHTH+jF/qStxw== -hoist-non-react-statics@^3.0.0, hoist-non-react-statics@^3.3.0: - version "3.3.0" - resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.0.tgz#b09178f0122184fb95acf525daaecb4d8f45958b" - integrity sha512-0XsbTXxgiaCDYDIWFcwkmerZPSwywfUqYmwT4jzewKTQSWoE6FCMoUVOeBJWK3E/CrWbxRG3m5GzY4lnIwGRBA== - dependencies: - react-is "^16.7.0" - hoist-non-react-statics@^3.1.0: version "3.3.1" resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz#101685d3aff3b23ea213163f6e8e12f4f111e19f" @@ -15108,6 +15005,13 @@ hoist-non-react-statics@^3.1.0: dependencies: react-is "^16.7.0" +hoist-non-react-statics@^3.3.0: + version "3.3.0" + resolved "https://registry.yarnpkg.com/hoist-non-react-statics/-/hoist-non-react-statics-3.3.0.tgz#b09178f0122184fb95acf525daaecb4d8f45958b" + integrity sha512-0XsbTXxgiaCDYDIWFcwkmerZPSwywfUqYmwT4jzewKTQSWoE6FCMoUVOeBJWK3E/CrWbxRG3m5GzY4lnIwGRBA== + dependencies: + react-is "^16.7.0" + homedir-polyfill@^1.0.0, homedir-polyfill@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/homedir-polyfill/-/homedir-polyfill-1.0.1.tgz#4c2bbc8a758998feebf5ed68580f76d46768b4bc" @@ -15411,7 +15315,7 @@ icalendar@0.7.1: resolved "https://registry.yarnpkg.com/icalendar/-/icalendar-0.7.1.tgz#d0d3486795f8f1c5cf4f8cafac081b4b4e7a32ae" integrity sha1-0NNIZ5X48cXPT4yvrAgbS056Mq4= -iconv-lite@0.4, iconv-lite@0.4.24, iconv-lite@^0.4.17, iconv-lite@^0.4.22, iconv-lite@^0.4.24, iconv-lite@^0.4.4, iconv-lite@^0.4.5, iconv-lite@~0.4.13: +iconv-lite@0.4, iconv-lite@0.4.24, iconv-lite@^0.4.17, iconv-lite@^0.4.22, iconv-lite@^0.4.24, iconv-lite@^0.4.4, iconv-lite@~0.4.13: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== @@ -15847,25 +15751,6 @@ inquirer@^6.2.0: strip-ansi "^4.0.0" through "^2.3.6" -inquirer@^6.4.1: - version "6.5.2" - resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-6.5.2.tgz#ad50942375d036d327ff528c08bd5fab089928ca" - integrity sha512-cntlB5ghuB0iuO65Ovoi8ogLHiWGs/5yNrtUcKjFhSSiVeAIVpD7koaSU9RM8mpXw5YDi9RdYXGQMaOURB7ycQ== - dependencies: - ansi-escapes "^3.2.0" - chalk "^2.4.2" - cli-cursor "^2.1.0" - cli-width "^2.0.0" - external-editor "^3.0.3" - figures "^2.0.0" - lodash "^4.17.12" - mute-stream "0.0.7" - run-async "^2.2.0" - rxjs "^6.4.0" - string-width "^2.1.0" - strip-ansi "^5.1.0" - through "^2.3.6" - inquirer@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/inquirer/-/inquirer-7.0.0.tgz#9e2b032dde77da1db5db804758b8fea3a970519a" @@ -17671,17 +17556,6 @@ jssha@^2.1.0: resolved "https://registry.yarnpkg.com/jssha/-/jssha-2.3.1.tgz#147b2125369035ca4b2f7d210dc539f009b3de9a" integrity sha1-FHshJTaQNcpLL30hDcU58Amz3po= -jstransform@^11.0.3: - version "11.0.3" - resolved "https://registry.yarnpkg.com/jstransform/-/jstransform-11.0.3.tgz#09a78993e0ae4d4ef4487f6155a91f6190cb4223" - integrity sha1-CaeJk+CuTU70SH9hVakfYZDLQiM= - dependencies: - base62 "^1.1.0" - commoner "^0.10.1" - esprima-fb "^15001.1.0-dev-harmony-fb" - object-assign "^2.0.0" - source-map "^0.4.2" - jstransformer-ejs@^0.0.3: version "0.0.3" resolved "https://registry.yarnpkg.com/jstransformer-ejs/-/jstransformer-ejs-0.0.3.tgz#04d9201469274fcf260f1e7efd732d487fa234b6" @@ -17722,6 +17596,14 @@ jsx-ast-utils@^2.2.1: array-includes "^3.0.3" object.assign "^4.1.0" +jsx-ast-utils@^2.2.3: + version "2.2.3" + resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-2.2.3.tgz#8a9364e402448a3ce7f14d357738310d9248054f" + integrity sha512-EdIHFMm+1BPynpKOpdPqiOsvnIrInRGJD7bzPZdPkjitQEqpdpUuFpq4T0npZFKTiB3RhWFdGN+oqOJIdhDhQA== + dependencies: + array-includes "^3.0.3" + object.assign "^4.1.0" + jsx-to-string@^1.4.0: version "1.4.0" resolved "https://registry.yarnpkg.com/jsx-to-string/-/jsx-to-string-1.4.0.tgz#66dc34d773dab9f40fe993cff9940e5da655b705" @@ -17741,6 +17623,11 @@ jszip@^3.1.5: readable-stream "~2.3.6" set-immediate-shim "~1.0.1" +just-curry-it@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/just-curry-it/-/just-curry-it-3.1.0.tgz#ab59daed308a58b847ada166edd0a2d40766fbc5" + integrity sha512-mjzgSOFzlrurlURaHVjnQodyPNvrHrf1TbQP2XU9NSqBtHQPuHZ+Eb6TAJP7ASeJN9h9K0KXoRTs8u6ouHBKvg== + just-debounce@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/just-debounce/-/just-debounce-1.0.0.tgz#87fccfaeffc0b68cd19d55f6722943f929ea35ea" @@ -17950,10 +17837,10 @@ known-css-properties@^0.3.0: resolved "https://registry.yarnpkg.com/known-css-properties/-/known-css-properties-0.3.0.tgz#a3d135bbfc60ee8c6eacf2f7e7e6f2d4755e49a4" integrity sha512-QMQcnKAiQccfQTqtBh/qwquGZ2XK/DXND1jrcN9M8gMMy99Gwla7GQjndVUsEqIaRyP6bsFRuhwRj5poafBGJQ== -konva@^2.6.0: - version "2.6.0" - resolved "https://registry.yarnpkg.com/konva/-/konva-2.6.0.tgz#43165b95e32a4378ce532d9113c914f4998409c3" - integrity sha512-LCOoavICTD9PYoAqtWo8sbxYtCiXdgEeY7vj/Sq8b2bwFmrQr9Ak0RkD4/jxAf5fcUQRL5e1zPLyfRpVndp20A== +konva@^4.0.18: + version "4.0.18" + resolved "https://registry.yarnpkg.com/konva/-/konva-4.0.18.tgz#43e614c9b22827183506d4a6b3b474f90187b469" + integrity sha512-Tlq0v7QHr8q73xr1cKjHdQl41oHC06IOldPO+ukjt99G74NgoU0TVouvPIFpW2whA9t3xNk/+/VJcc3XPcboOw== kopy@^8.2.0: version "8.2.5" @@ -18383,6 +18270,16 @@ load-json-file@^4.0.0: pify "^3.0.0" strip-bom "^3.0.0" +load-json-file@^6.2.0: + version "6.2.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-6.2.0.tgz#5c7770b42cafa97074ca2848707c61662f4251a1" + integrity sha512-gUD/epcRms75Cw8RT1pUdHugZYM5ce64ucs2GEISABwkRsOQr0q2wm/MV2TKThycIe5e0ytRweW2RZxclogCdQ== + dependencies: + graceful-fs "^4.1.15" + parse-json "^5.0.0" + strip-bom "^4.0.0" + type-fest "^0.6.0" + load-source-map@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/load-source-map/-/load-source-map-1.0.0.tgz#318f49905ce8a709dfb7cc3f16f3efe3bcf1dd05" @@ -18392,16 +18289,11 @@ load-source-map@^1.0.0: semver "^5.3.0" source-map "^0.5.6" -loader-runner@^2.3.0, loader-runner@^2.4.0: +loader-runner@^2.3.1, loader-runner@^2.4.0: version "2.4.0" resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.4.0.tgz#ed47066bfe534d7e84c4c7b9998c2a75607d9357" integrity sha512-Jsmr89RcXGIwivFY21FcRrisYZfvLMTWx5kOLc+JTxtpBOG6xML0vzbc6SEQG2FO9/4Fc3wW4LVcB5DmGflaRw== -loader-runner@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/loader-runner/-/loader-runner-2.3.1.tgz#026f12fe7c3115992896ac02ba022ba92971b979" - integrity sha512-By6ZFY7ETWOc9RFaAIb23IjJVcM4dvJC/N57nmdz9RSkMXvAXGI7SyVlAw3v8vjtDRlqThgVDVmTnr9fqMlxkw== - loader-utils@1.2.3, loader-utils@^1.0.4, loader-utils@^1.2.3: version "1.2.3" resolved "https://registry.yarnpkg.com/loader-utils/-/loader-utils-1.2.3.tgz#1ff5dc6911c9f0a062531a4c04b609406108c2c7" @@ -18448,7 +18340,7 @@ locutus@^2.0.5: resolved "https://registry.yarnpkg.com/locutus/-/locutus-2.0.10.tgz#f903619466a98a4ab76e8b87a5854b55a743b917" integrity sha512-AZg2kCqrquMJ5FehDsBidV0qHl98NrsYtseUClzjAQ3HFnsDBJTCwGVplSQ82t9/QfgahqvTjaKcZqZkHmS0wQ== -lodash-es@^4.17.11, lodash-es@^4.17.4, lodash-es@^4.17.5, lodash-es@^4.2.1: +lodash-es@^4.17.11, lodash-es@^4.17.5, lodash-es@^4.2.1: version "4.17.15" resolved "https://registry.yarnpkg.com/lodash-es/-/lodash-es-4.17.15.tgz#21bd96839354412f23d7a10340e5eac6ee455d78" integrity sha512-rlrc3yU3+JNOpZ9zj5pQtxnx2THmvRykwL4Xlxoa8I9lHBlVbbyPhgyPMioxVZ4NqyxaVVtaJnzsyOidQIhyyQ== @@ -19323,7 +19215,7 @@ memory-fs@^0.2.0: resolved "https://registry.npmjs.org/memory-fs/-/memory-fs-0.2.0.tgz#f2bb25368bc121e391c2520de92969caee0a0290" integrity sha1-8rslNovBIeORwlIN6Slpyu4KApA= -memory-fs@^0.4.0, memory-fs@^0.4.1, memory-fs@~0.4.1: +memory-fs@^0.4.0, memory-fs@^0.4.1: version "0.4.1" resolved "https://registry.yarnpkg.com/memory-fs/-/memory-fs-0.4.1.tgz#3a9a20b8462523e447cfbc7e8bb80ed667bfc552" integrity sha1-OpoguEYlI+RHz7x+i7gO1me/xVI= @@ -19427,7 +19319,7 @@ microevent.ts@~0.1.1: resolved "https://registry.yarnpkg.com/microevent.ts/-/microevent.ts-0.1.1.tgz#70b09b83f43df5172d0205a63025bce0f7357fa0" integrity sha512-jo1OfR4TaEwd5HOrt5+tAZ9mqT4jmpNAusXtyfNzqVm9uiSYFZlKM1wYL4oU7azZW/PxQW53wM0S6OR1JHNa2g== -micromatch@3.1.10, micromatch@^3.0.4, micromatch@^3.1.10, micromatch@^3.1.4, micromatch@^3.1.8: +micromatch@3.1.10, micromatch@^3.0.4, micromatch@^3.1.10, micromatch@^3.1.4: version "3.1.10" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-3.1.10.tgz#70859bc95c9840952f359a068a3fc49f9ecfac23" integrity sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg== @@ -19791,19 +19683,6 @@ mkdirp@0.5.1, mkdirp@0.5.x, "mkdirp@>=0.5 0", mkdirp@^0.5.0, mkdirp@^0.5.1, mkdi dependencies: minimist "0.0.8" -mobx-react@^5.4.3: - version "5.4.3" - resolved "https://registry.yarnpkg.com/mobx-react/-/mobx-react-5.4.3.tgz#6709b7dd89670c40e9815914ac2ca49cc02bfb47" - integrity sha512-WC8yFlwvJ91hy8j6CrydAuFteUafcuvdITFQeHl3LRIf5ayfT/4W3M/byhEYD2BcJWejeXr8y4Rh2H26RunCRQ== - dependencies: - hoist-non-react-statics "^3.0.0" - react-lifecycles-compat "^3.0.2" - -mobx@^4.9.2: - version "4.9.4" - resolved "https://registry.yarnpkg.com/mobx/-/mobx-4.9.4.tgz#bb37a0e4e05f0b02be89ced9d23445cad73377ad" - integrity sha512-RaEpydw7D1ebp1pdFHrEMZcLk4nALAZyHAroCPQpqLzuIXIxJpLmMIe5PUZwYHqvlcWL6DVqDYCANZpPOi9iXA== - mocha-junit-reporter@^1.23.1: version "1.23.1" resolved "https://registry.yarnpkg.com/mocha-junit-reporter/-/mocha-junit-reporter-1.23.1.tgz#ba11519c0b967f404e4123dd69bc4ba022ab0f12" @@ -20339,6 +20218,11 @@ node-forge@^0.7.6: resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.7.6.tgz#fdf3b418aee1f94f0ef642cd63486c77ca9724ac" integrity sha512-sol30LUpz1jQFBjOKwbjxijiE3b6pjd74YwfD0fJOKPjF+fONKb2Yg8rYgS6+bK6VDl+/wfr4IYpC7jDzLUIfw== +node-forge@^0.9.1: + version "0.9.1" + resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.9.1.tgz#775368e6846558ab6676858a4d8c6e8d16c677b5" + integrity sha512-G6RlQt5Sb4GMBzXvhfkeFmbqR6MzhtnT7VTHuLadjkii3rdYHNdw0m8zA4BTxVIh68FicCQ2NSUANpsqkr9jvQ== + node-gyp@^3.8.0: version "3.8.0" resolved "https://registry.yarnpkg.com/node-gyp/-/node-gyp-3.8.0.tgz#540304261c330e80d0d5edce253a68cb3964218c" @@ -20412,7 +20296,7 @@ node-jose@1.1.0: util "^0.11.0" vm-browserify "0.0.4" -node-libs-browser@^2.0.0, node-libs-browser@^2.2.1: +node-libs-browser@^2.2.1: version "2.2.1" resolved "https://registry.yarnpkg.com/node-libs-browser/-/node-libs-browser-2.2.1.tgz#b64f513d18338625f90346d27b0d235e631f6425" integrity sha512-h/zcD8H9kaDZ9ALUWwlBUDo6TKF8a7qBSCSEGfjTVIYeqsioSKaAX+BN7NgiMGp6iSIXZ3PxgCu8KS3b71YK5Q== @@ -20921,7 +20805,7 @@ object.fromentries@^1.0.0: function-bind "^1.1.1" has "^1.0.1" -object.fromentries@^2.0.0, object.fromentries@^2.0.1: +object.fromentries@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/object.fromentries/-/object.fromentries-2.0.1.tgz#050f077855c7af8ae6649f45c80b16ee2d31e704" integrity sha512-PUQv8Hbg3j2QX0IQYv3iAGCbGcu4yY4KQ92/dhA4sFSixBmSmp13UpDLs6jGK8rBtbmhNNIK99LD2k293jpiGA== @@ -21107,7 +20991,7 @@ optional-js@^2.0.0: resolved "https://registry.yarnpkg.com/optional-js/-/optional-js-2.1.1.tgz#c2dc519ad119648510b4d241dbb60b1167c36a46" integrity sha512-mUS4bDngcD5kKzzRUd1HVQkr9Lzzby3fSrrPR9wOHhQiyYo+hDS5NVli5YQzGjQRQ15k5Sno4xH9pfykJdeEUA== -optionator@^0.8.1, optionator@^0.8.2: +optionator@^0.8.1: version "0.8.2" resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64" integrity sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q= @@ -21119,6 +21003,18 @@ optionator@^0.8.1, optionator@^0.8.2: type-check "~0.3.2" wordwrap "~1.0.0" +optionator@^0.8.3: + version "0.8.3" + resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495" + integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA== + dependencies: + deep-is "~0.1.3" + fast-levenshtein "~2.0.6" + levn "~0.3.0" + prelude-ls "~1.1.2" + type-check "~0.3.2" + word-wrap "~1.2.3" + ora@^0.2.3: version "0.2.3" resolved "https://registry.yarnpkg.com/ora/-/ora-0.2.3.tgz#37527d220adcd53c39b73571d754156d5db657a4" @@ -21792,12 +21688,7 @@ path-key@^3.0.0, path-key@^3.1.0: resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.0.tgz#99a10d870a803bdd5ee6f0470e58dfcd2f9a54d3" integrity sha512-8cChqz0RP6SHJkMt48FW0A7+qUOn+OsnOsVtzI59tZ8m+5bCSk7hzwET0pulwOM2YMn9J1efb07KB9l9f30SGg== -path-parse@^1.0.5: - version "1.0.5" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.5.tgz#3c1adf871ea9cd6c9431b6ea2bd74a0ff055c4c1" - integrity sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME= - -path-parse@^1.0.6: +path-parse@^1.0.5, path-parse@^1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.6.tgz#d62dbb5679405d72c4737ec58600e9ddcf06d24c" integrity sha512-GSmOT2EbHrINBf9SR7CDELwlJ8AENk3Qn7OikK4nFYAu3Ote2+JYNVvkpAEQm3/TLNEJFD/xZJjzyxg3KBWOzw== @@ -22476,7 +22367,7 @@ promise.prototype.finally@^3.1.0: es-abstract "^1.9.0" function-bind "^1.1.1" -promise@^7.0.1, promise@^7.0.3, promise@^7.1.1: +promise@^7.0.1, promise@^7.1.1: version "7.3.1" resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf" integrity sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg== @@ -23070,6 +22961,18 @@ rc@^1.0.1, rc@^1.1.6, rc@^1.2.7: minimist "^1.2.0" strip-json-comments "~2.0.1" +re-reselect@^3.4.0: + version "3.4.0" + resolved "https://registry.yarnpkg.com/re-reselect/-/re-reselect-3.4.0.tgz#0f2303f3c84394f57f0cd31fea08a1ca4840a7cd" + integrity sha512-JsecfN+JlckncVXTWFWjn0Vk6uInl8GSf4eEd9tTk5qXHlgqkPdILpnYpgZcISXNYAzvfvsCZviaDk8AxyS5sg== + +re-resizable@^6.1.1: + version "6.1.1" + resolved "https://registry.yarnpkg.com/re-resizable/-/re-resizable-6.1.1.tgz#7ff7cfe92c0b9d8b0bceaa578aadaeeff8931eaf" + integrity sha512-ngzX5xbXi9LlIghJUYZaBDkJUIMLYqO3tQ2cJZoNprCRGhfHnbyufKm51MZRIOBlLigLzPPFKBxQE8ZLezKGfA== + dependencies: + fast-memoize "^2.5.1" + react-ace@^5.5.0: version "5.10.0" resolved "https://registry.yarnpkg.com/react-ace/-/react-ace-5.10.0.tgz#e328b37ac52759f700be5afdb86ada2f5ec84c5e" @@ -23440,20 +23343,20 @@ react-is@~16.3.0: resolved "https://registry.yarnpkg.com/react-is/-/react-is-16.3.2.tgz#f4d3d0e2f5fbb6ac46450641eb2e25bf05d36b22" integrity sha512-ybEM7YOr4yBgFd6w8dJqwxegqZGJNBZl6U27HnGKuTZmDvVrD5quWOK/wAnMywiZzW+Qsk+l4X2c70+thp/A8Q== -react-konva@16.8.3: - version "16.8.3" - resolved "https://registry.yarnpkg.com/react-konva/-/react-konva-16.8.3.tgz#e55390040ea54675a0ef0d40b4fa93731e6d7b03" - integrity sha512-gU36TBxcPZANQOV5prAFnpRSNp2ikAT7zCICHTBJvOzAfa8Yhcyaey6EIrD+NTT/4y0PyGFBIkmWq6zdrlNrQg== +react-konva@16.10.1-0: + version "16.10.1-0" + resolved "https://registry.yarnpkg.com/react-konva/-/react-konva-16.10.1-0.tgz#f8cc2c95374933069e891a6c714c70d0fdc77e68" + integrity sha512-N0Zi3TcWmUxb2d7y1DUDQhRA+WIcqk54DQmmUmJSadj+fS0bg6iZDebQSEQC8dMbjnLHc/338xRT4a4718PEiw== dependencies: - react-reconciler "^0.20.1" - scheduler "^0.13.3" + react-reconciler "^0.22.1" + scheduler "^0.16.1" react-lib-adler32@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/react-lib-adler32/-/react-lib-adler32-1.0.3.tgz#63df1aed274eabcc1c5067077ea281ec30888ba7" integrity sha512-AqFqdt4cP0RPffHNjVHZ7tyIgnoSzNxgFhG8XKMXCtA1dZ72gTPO4iYFwWDKHqvE8sHS14rhltQTdbXU5G4BFA== -react-lifecycles-compat@^3.0.0, react-lifecycles-compat@^3.0.2, react-lifecycles-compat@^3.0.4: +react-lifecycles-compat@^3.0.0, react-lifecycles-compat@^3.0.4: version "3.0.4" resolved "https://registry.yarnpkg.com/react-lifecycles-compat/-/react-lifecycles-compat-3.0.4.tgz#4f1a273afdfc8f3488a8c516bfda78f872352362" integrity sha512-fBASbA6LnOU9dOU2eW7aQ8xmYBSXUIWr+UmF9b1efZBazGNO+rcXT/icdKnYm2pTwcRylVUYwW7H1PHfLekVzA== @@ -23562,15 +23465,15 @@ react-portal@^3.2.0: dependencies: prop-types "^15.5.8" -react-reconciler@^0.20.1: - version "0.20.4" - resolved "https://registry.yarnpkg.com/react-reconciler/-/react-reconciler-0.20.4.tgz#3da6a95841592f849cb4edd3d38676c86fd920b2" - integrity sha512-kxERc4H32zV2lXMg/iMiwQHOtyqf15qojvkcZ5Ja2CPkjVohHw9k70pdDBwrnQhLVetUJBSYyqU3yqrlVTOajA== +react-reconciler@^0.22.1: + version "0.22.2" + resolved "https://registry.yarnpkg.com/react-reconciler/-/react-reconciler-0.22.2.tgz#e8a10374fec8fee7c5cd0cf3cd05626f1b134d3e" + integrity sha512-MLX5Y2pNLsdXzWz/GLNhhYkdLOvxEtw2IGqVCzkiRdSFSHRjujI9gfTOQ3rV5z8toTBxSZ2qrRkRUo97mmEdhA== dependencies: loose-envify "^1.1.0" object-assign "^4.1.1" prop-types "^15.6.2" - scheduler "^0.13.6" + scheduler "^0.16.2" react-redux@^5.0.7: version "5.0.7" @@ -23597,7 +23500,7 @@ react-redux@^5.1.2: react-is "^16.6.0" react-lifecycles-compat "^3.0.0" -react-redux@^7.1.1: +react-redux@^7.1.0, react-redux@^7.1.1: version "7.1.3" resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.1.3.tgz#717a3d7bbe3a1b2d535c94885ce04cdc5a33fc79" integrity sha512-uI1wca+ECG9RoVkWQFF4jDMqmaw0/qnvaSvOoL/GA4dNxf6LoV8sUAcNDvE5NWKs4hFpn0t6wswNQnY3f7HT3w== @@ -23859,15 +23762,7 @@ react-visibility-sensor@^5.1.1: dependencies: prop-types "^15.7.2" -react@^0.14.0: - version "0.14.9" - resolved "https://registry.yarnpkg.com/react/-/react-0.14.9.tgz#9110a6497c49d44ba1c0edd317aec29c2e0d91d1" - integrity sha1-kRCmSXxJ1EuhwO3TF67CnC4NkdE= - dependencies: - envify "^3.0.0" - fbjs "^0.6.1" - -react@^16.12.0: +react@^0.14.0, react@^16.12.0, react@^16.8.3, react@^16.8.5: version "16.12.0" resolved "https://registry.yarnpkg.com/react/-/react-16.12.0.tgz#0c0a9c6a142429e3614834d5a778e18aa78a0b83" integrity sha512-fglqy3k5E+81pA8s+7K0/T3DBCF0ZDOher1elBFzF7O6arXJgzyu/FW+COxFvAWXJoJN9KIZbT2LXlukwphYTA== @@ -23876,16 +23771,6 @@ react@^16.12.0: object-assign "^4.1.1" prop-types "^15.6.2" -react@^16.8.3, react@^16.8.5: - version "16.8.6" - resolved "https://registry.yarnpkg.com/react/-/react-16.8.6.tgz#ad6c3a9614fd3a4e9ef51117f54d888da01f2bbe" - integrity sha512-pC0uMkhLaHm11ZSJULfOBqV4tIZkx87ZLvbbQYunNixAAvjnC+snJCg0XQXn9VIsttVsbZP/H/ewzgsd5fxKXw== - dependencies: - loose-envify "^1.1.0" - object-assign "^4.1.1" - prop-types "^15.6.2" - scheduler "^0.13.6" - reactcss@1.2.3, reactcss@^1.2.0: version "1.2.3" resolved "https://registry.yarnpkg.com/reactcss/-/reactcss-1.2.3.tgz#c00013875e557b1cf0dfd9a368a1c3dab3b548dd" @@ -24118,16 +24003,6 @@ realpath-native@^1.1.0: dependencies: util.promisify "^1.0.0" -recast@^0.11.17, recast@~0.11.12: - version "0.11.23" - resolved "https://registry.yarnpkg.com/recast/-/recast-0.11.23.tgz#451fd3004ab1e4df9b4e4b66376b2a21912462d3" - integrity sha1-RR/TAEqx5N+bTktmN2sqIZEkYtM= - dependencies: - ast-types "0.9.6" - esprima "~3.1.0" - private "~0.1.5" - source-map "~0.5.0" - recast@^0.14.7: version "0.14.7" resolved "https://registry.yarnpkg.com/recast/-/recast-0.14.7.tgz#4f1497c2b5826d42a66e8e3c9d80c512983ff61d" @@ -24148,6 +24023,16 @@ recast@^0.17.3: private "^0.1.8" source-map "~0.6.1" +recast@~0.11.12: + version "0.11.23" + resolved "https://registry.yarnpkg.com/recast/-/recast-0.11.23.tgz#451fd3004ab1e4df9b4e4b66376b2a21912462d3" + integrity sha1-RR/TAEqx5N+bTktmN2sqIZEkYtM= + dependencies: + ast-types "0.9.6" + esprima "~3.1.0" + private "~0.1.5" + source-map "~0.5.0" + rechoir@^0.6.2: version "0.6.2" resolved "https://registry.yarnpkg.com/rechoir/-/rechoir-0.6.2.tgz#85204b54dba82d5742e28c96756ef43af50e3384" @@ -24218,25 +24103,21 @@ redeyed@~2.1.0: dependencies: esprima "~4.0.0" -reduce-reducers@^0.1.0: - version "0.1.2" - resolved "https://registry.yarnpkg.com/reduce-reducers/-/reduce-reducers-0.1.2.tgz#fa1b4718bc5292a71ddd1e5d839c9bea9770f14b" - integrity sha1-+htHGLxSkqcd3R5dg5yb6pdw8Us= - reduce-reducers@^0.4.3: version "0.4.3" resolved "https://registry.yarnpkg.com/reduce-reducers/-/reduce-reducers-0.4.3.tgz#8e052618801cd8fc2714b4915adaa8937eb6d66c" integrity sha512-+CNMnI8QhgVMtAt54uQs3kUxC3Sybpa7Y63HR14uGLgI9/QR5ggHvpxwhGGe3wmx5V91YwqQIblN9k5lspAmGw== -redux-actions@2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/redux-actions/-/redux-actions-2.2.1.tgz#d64186b25649a13c05478547d7cd7537b892410d" - integrity sha1-1kGGslZJoTwFR4VH1811N7iSQQ0= +redux-actions@2.6.5: + version "2.6.5" + resolved "https://registry.yarnpkg.com/redux-actions/-/redux-actions-2.6.5.tgz#bdca548768ee99832a63910c276def85e821a27e" + integrity sha512-pFhEcWFTYNk7DhQgxMGnbsB1H2glqhQJRQrtPb96kD3hWiZRzXHwwmFPswg6V2MjraXRXWNmuP9P84tvdLAJmw== dependencies: - invariant "^2.2.1" - lodash "^4.13.1" - lodash-es "^4.17.4" - reduce-reducers "^0.1.0" + invariant "^2.2.4" + just-curry-it "^3.1.0" + loose-envify "^1.4.0" + reduce-reducers "^0.4.3" + to-camel-case "^1.0.0" redux-observable@^1.0.0: version "1.0.0" @@ -24838,6 +24719,11 @@ reselect@3.0.1, reselect@^3.0.1: resolved "https://registry.yarnpkg.com/reselect/-/reselect-3.0.1.tgz#efdaa98ea7451324d092b2b2163a6a1d7a9a2147" integrity sha1-79qpjqdFEyTQkrKyFjpqHXqaIUc= +reselect@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/reselect/-/reselect-4.0.0.tgz#f2529830e5d3d0e021408b246a206ef4ea4437f7" + integrity sha512-qUgANli03jjAyGlnbYVAV5vvnOmJnODyABz51RdBN7M4WaVu8mecZWgyQNkG8Yqe3KRGRt0l4K4B3XVEULC4CA== + resize-observer-polyfill@^1.5.0: version "1.5.0" resolved "https://registry.yarnpkg.com/resize-observer-polyfill/-/resize-observer-polyfill-1.5.0.tgz#660ff1d9712a2382baa2cad450a4716209f9ca69" @@ -24966,6 +24852,13 @@ resolve@^1.12.0, resolve@^1.4.0: dependencies: path-parse "^1.0.6" +resolve@^1.13.1: + version "1.14.2" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.14.2.tgz#dbf31d0fa98b1f29aa5169783b9c290cb865fea2" + integrity sha512-EjlOBLBO1kxsUxsKjLt7TAECyKW6fOh1VRkykQkKGzcBbjjPIxBqGh0jf7GJ3k/f5mxMqW3htMD3WdTUVtW8HQ== + dependencies: + path-parse "^1.0.6" + resolve@^1.5.0, resolve@^1.7.1: version "1.7.1" resolved "https://registry.npmjs.org/resolve/-/resolve-1.7.1.tgz#aadd656374fd298aee895bc026b8297418677fd3" @@ -25406,10 +25299,10 @@ saxes@^3.1.9: dependencies: xmlchars "^2.1.1" -scheduler@^0.13.3, scheduler@^0.13.6: - version "0.13.6" - resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.13.6.tgz#466a4ec332467b31a91b9bf74e5347072e4cd889" - integrity sha512-IWnObHt413ucAYKsD9J1QShUKkbKLQQHdxRyw73sw4FN26iWr3DY/H34xGPe4nmL1DwXyWmSWmMrA9TfQbE/XQ== +scheduler@^0.16.1, scheduler@^0.16.2: + version "0.16.2" + resolved "https://registry.yarnpkg.com/scheduler/-/scheduler-0.16.2.tgz#f74cd9d33eff6fc554edfb79864868e4819132c1" + integrity sha512-BqYVWqwz6s1wZMhjFvLfVR5WXP7ZY32M/wYPo04CcuPM7XZEbV2TBNW7Z0UkguPTl0dWMA59VbNXxK6q+pHItg== dependencies: loose-envify "^1.1.0" object-assign "^4.1.1" @@ -26916,6 +26809,11 @@ strip-bom@^3.0.0: resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= +strip-bom@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878" + integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w== + strip-dirs@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/strip-dirs/-/strip-dirs-2.1.0.tgz#4987736264fc344cf20f6c34aca9d13d1d4ed6c5" @@ -27468,7 +27366,7 @@ term-size@^1.2.0: dependencies: execa "^0.7.0" -terser-webpack-plugin@^1.1.0, terser-webpack-plugin@^1.2.4, terser-webpack-plugin@^1.4.1: +terser-webpack-plugin@^1.2.4, terser-webpack-plugin@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/terser-webpack-plugin/-/terser-webpack-plugin-1.4.1.tgz#61b18e40eaee5be97e771cdbb10ed1280888c2b4" integrity sha512-ZXmmfiwtCLfz8WKZyYUuuHf3dMYEjg8NrjHMb0JqHVHVOSkzp3cW2/XG1fP3tRhqEqSzMwzzRQGtAPbs4Cncxg== @@ -27497,35 +27395,16 @@ terser-webpack-plugin@^2.1.2: terser "^4.3.4" webpack-sources "^1.4.3" -terser@^4.1.2: - version "4.2.0" - resolved "https://registry.yarnpkg.com/terser/-/terser-4.2.0.tgz#4b1b5f4424b426a7a47e80d6aae45e0d7979aef0" - integrity sha512-6lPt7lZdZ/13icQJp8XasFOwZjFJkxFFIb/N1fhYEQNoNI3Ilo3KABZ9OocZvZoB39r6SiIk/0+v/bt8nZoSeA== - dependencies: - commander "^2.20.0" - source-map "~0.6.1" - source-map-support "~0.5.12" - -terser@^4.3.4: - version "4.3.4" - resolved "https://registry.yarnpkg.com/terser/-/terser-4.3.4.tgz#ad91bade95619e3434685d69efa621a5af5f877d" - integrity sha512-Kcrn3RiW8NtHBP0ssOAzwa2MsIRQ8lJWiBG/K7JgqPlomA3mtb2DEmp4/hrUA+Jujx+WZ02zqd7GYD+QRBB/2Q== +terser@^4.1.2, terser@^4.3.4: + version "4.6.1" + resolved "https://registry.yarnpkg.com/terser/-/terser-4.6.1.tgz#913e35e0d38a75285a7913ba01d753c4089ebdbd" + integrity sha512-w0f2OWFD7ka3zwetgVAhNMeyzEbj39ht2Tb0qKflw9PmW9Qbo5tjTh01QJLkhO9t9RDDQYvk+WXqpECI2C6i2A== dependencies: commander "^2.20.0" source-map "~0.6.1" source-map-support "~0.5.12" -test-exclude@^5.0.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-5.1.0.tgz#6ba6b25179d2d38724824661323b73e03c0c1de1" - integrity sha512-gwf0S2fFsANC55fSeSqpb8BYk6w3FDvwZxfNjeF6FRgvFa43r+7wRiA/Q0IxoRU37wB/LE8IQ4221BsNucTaCA== - dependencies: - arrify "^1.0.1" - minimatch "^3.0.4" - read-pkg-up "^4.0.0" - require-main-filename "^1.0.1" - -test-exclude@^5.2.3: +test-exclude@^5.0.0, test-exclude@^5.2.3: version "5.2.3" resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-5.2.3.tgz#c3d3e1e311eb7ee405e092dac10aefd09091eac0" integrity sha512-M+oxtseCFO3EDtAaGH7iiej3CBkzXqFMbzqYAACdzKui4eZA+pq3tZEwChvOdNfa7xxy8BfbmgJSIr43cC/+2g== @@ -27665,6 +27544,11 @@ timm@^1.6.1: resolved "https://registry.yarnpkg.com/timm/-/timm-1.6.1.tgz#5f8aafc932248c76caf2c6af60542a32d3c30701" integrity sha512-hqDTYi/bWuDxL2i6T3v6nrvkAQ/1Bc060GSkVEQZp02zTSTB4CHSKsOkliequCftQaNRcjRqUZmpGWs5FfhrNg== +timsort@~0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/timsort/-/timsort-0.3.0.tgz#405411a8e7e6339fe64db9a234de11dc31e02bd4" + integrity sha1-QFQRqOfmM5/mTbmiNN4R3DHgK9Q= + tiny-emitter@^2.0.0: version "2.0.2" resolved "https://registry.yarnpkg.com/tiny-emitter/-/tiny-emitter-2.0.2.tgz#82d27468aca5ade8e5fd1e6d22b57dd43ebdfb7c" @@ -27799,6 +27683,13 @@ to-arraybuffer@^1.0.0: resolved "https://registry.yarnpkg.com/to-arraybuffer/-/to-arraybuffer-1.0.1.tgz#7d229b1fcc637e466ca081180836a7aabff83f43" integrity sha1-fSKbH8xjfkZsoIEYCDanqr/4P0M= +to-camel-case@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/to-camel-case/-/to-camel-case-1.0.0.tgz#1a56054b2f9d696298ce66a60897322b6f423e46" + integrity sha1-GlYFSy+daWKYzmamCJcyK29CPkY= + dependencies: + to-space-case "^1.0.0" + to-fast-properties@^1.0.3: version "1.0.3" resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-1.0.3.tgz#b83571fa4d8c25b82e231b06e3a3055de4ca1a47" @@ -27809,6 +27700,11 @@ to-fast-properties@^2.0.0: resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e" integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4= +to-no-case@^1.0.0: + version "1.0.2" + resolved "https://registry.yarnpkg.com/to-no-case/-/to-no-case-1.0.2.tgz#c722907164ef6b178132c8e69930212d1b4aa16a" + integrity sha1-xyKQcWTvaxeBMsjmmTAhLRtKoWo= + to-object-path@^0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/to-object-path/-/to-object-path-0.3.0.tgz#297588b7b0e7e0ac08e04e672f85c1f4999e17af" @@ -27857,6 +27753,13 @@ to-source-code@^1.0.0: dependencies: is-nil "^1.0.0" +to-space-case@^1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/to-space-case/-/to-space-case-1.0.0.tgz#b052daafb1b2b29dc770cea0163e5ec0ebc9fc17" + integrity sha1-sFLar7Gysp3HcM6gFj5ewOvJ/Bc= + dependencies: + to-no-case "^1.0.0" + to-through@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/to-through/-/to-through-2.0.0.tgz#fc92adaba072647bc0b67d6b03664aa195093af6" @@ -28659,7 +28562,7 @@ typescript-fsa@^2.0.0, typescript-fsa@^2.5.0: resolved "https://registry.yarnpkg.com/typescript-fsa/-/typescript-fsa-2.5.0.tgz#1baec01b5e8f5f34c322679d1327016e9e294faf" integrity sha1-G67AG16PXzTDImedEycBbp4pT68= -typescript@3.5.3, typescript@3.7.2, typescript@^3.0.1, typescript@^3.0.3, typescript@^3.2.2, typescript@^3.3.3333, typescript@^3.4.5, typescript@~3.5.3: +typescript@3.5.3, typescript@3.7.2, typescript@^3.0.1, typescript@^3.0.3, typescript@^3.2.2, typescript@^3.3.3333, typescript@^3.4.5, typescript@~3.7.2: version "3.7.2" resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.7.2.tgz#27e489b95fa5909445e9fef5ee48d81697ad18fb" integrity sha512-ml7V7JfiN2Xwvcer+XAf2csGO1bPBdRbFCkYBczNZggrBZ9c7G3riSUeJmqEU5uOtXNPMhE3n+R4FA/3YOAWOQ== @@ -28958,9 +28861,9 @@ universal-user-agent@^2.0.0, universal-user-agent@^2.0.1: os-name "^3.0.0" universalify@^0.1.0: - version "0.1.1" - resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.1.tgz#fa71badd4437af4c148841e3b3b165f9e9e590b7" - integrity sha1-+nG63UQ3r0wUiEHjs7Fl+enlkLc= + version "0.1.2" + resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66" + integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg== unlazy-loader@^0.1.3: version "0.1.3" @@ -29229,7 +29132,7 @@ utila@^0.4.0, utila@~0.4: resolved "https://registry.yarnpkg.com/utila/-/utila-0.4.0.tgz#8a16a05d445657a3aea5eecc5b12a4fa5379772c" integrity sha1-ihagXURWV6Oupe7MWxKk+lN5dyw= -utility-types@^3.10.0: +utility-types@^3.10.0, utility-types@^3.9.0: version "3.10.0" resolved "https://registry.yarnpkg.com/utility-types/-/utility-types-3.10.0.tgz#ea4148f9a741015f05ed74fd615e1d20e6bed82b" integrity sha512-O11mqxmi7wMKCo6HKFt5AhO4BwY3VV68YU07tgxfz8zJTIxr4BpsezN49Ffwy9j3ZpwwJp4fkRwjRzq3uWE6Rg== @@ -30020,7 +29923,7 @@ warning@^4.0.2: dependencies: loose-envify "^1.0.0" -watchpack@^1.5.0, watchpack@^1.6.0: +watchpack@^1.6.0: version "1.6.0" resolved "https://registry.yarnpkg.com/watchpack/-/watchpack-1.6.0.tgz#4bc12c2ebe8aa277a71f1d3f14d685c7b446cd00" integrity sha512-i6dHe3EyLjMmDlU1/bGQpEw25XSjkJULPuAVKCbNRefQVq48yXKUpwg538F7AZTf9kyr57zj++pQFltUa5H7yA== @@ -30180,7 +30083,7 @@ webpack-sources@^1.1.0: source-list-map "^2.0.0" source-map "~0.6.1" -webpack-sources@^1.3.0, webpack-sources@^1.4.0, webpack-sources@^1.4.1, webpack-sources@^1.4.3: +webpack-sources@^1.4.0, webpack-sources@^1.4.1, webpack-sources@^1.4.3: version "1.4.3" resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-1.4.3.tgz#eedd8ec0b928fbf1cbfe994e22d2d890f330a933" integrity sha512-lgTS3Xhv1lCOKo7SA5TjKXMjpSM4sBjNV5+q2bqesbSPs5FjGmU6jjtBSkX9b4qW87vDIsCIlUPOEhbZrMdjeQ== @@ -30188,37 +30091,7 @@ webpack-sources@^1.3.0, webpack-sources@^1.4.0, webpack-sources@^1.4.1, webpack- source-list-map "^2.0.0" source-map "~0.6.1" -webpack@4.33.0: - version "4.33.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.33.0.tgz#c30fc4307db432e5c5e3333aaa7c16a15a3b277e" - integrity sha512-ggWMb0B2QUuYso6FPZKUohOgfm+Z0sVFs8WwWuSH1IAvkWs428VDNmOlAxvHGTB9Dm/qOB/qtE5cRx5y01clxw== - dependencies: - "@webassemblyjs/ast" "1.8.5" - "@webassemblyjs/helper-module-context" "1.8.5" - "@webassemblyjs/wasm-edit" "1.8.5" - "@webassemblyjs/wasm-parser" "1.8.5" - acorn "^6.0.5" - acorn-dynamic-import "^4.0.0" - ajv "^6.1.0" - ajv-keywords "^3.1.0" - chrome-trace-event "^1.0.0" - enhanced-resolve "^4.1.0" - eslint-scope "^4.0.0" - json-parse-better-errors "^1.0.2" - loader-runner "^2.3.0" - loader-utils "^1.1.0" - memory-fs "~0.4.1" - micromatch "^3.1.8" - mkdirp "~0.5.0" - neo-async "^2.5.0" - node-libs-browser "^2.0.0" - schema-utils "^1.0.0" - tapable "^1.1.0" - terser-webpack-plugin "^1.1.0" - watchpack "^1.5.0" - webpack-sources "^1.3.0" - -webpack@4.41.0, webpack@^4.41.0: +webpack@4.41.0, webpack@^4.33.0, webpack@^4.38.0, webpack@^4.41.0: version "4.41.0" resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.41.0.tgz#db6a254bde671769f7c14e90a1a55e73602fc70b" integrity sha512-yNV98U4r7wX1VJAj5kyMsu36T8RPPQntcb5fJLOsMz/pt/WrKC0Vp1bAlqPLkA1LegSwQwf6P+kAbyhRKVQ72g== @@ -30247,35 +30120,6 @@ webpack@4.41.0, webpack@^4.41.0: watchpack "^1.6.0" webpack-sources "^1.4.1" -webpack@^4.33.0, webpack@^4.38.0: - version "4.39.3" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-4.39.3.tgz#a02179d1032156b713b6ec2da7e0df9d037def50" - integrity sha512-BXSI9M211JyCVc3JxHWDpze85CvjC842EvpRsVTc/d15YJGlox7GIDd38kJgWrb3ZluyvIjgenbLDMBQPDcxYQ== - dependencies: - "@webassemblyjs/ast" "1.8.5" - "@webassemblyjs/helper-module-context" "1.8.5" - "@webassemblyjs/wasm-edit" "1.8.5" - "@webassemblyjs/wasm-parser" "1.8.5" - acorn "^6.2.1" - ajv "^6.10.2" - ajv-keywords "^3.4.1" - chrome-trace-event "^1.0.2" - enhanced-resolve "^4.1.0" - eslint-scope "^4.0.3" - json-parse-better-errors "^1.0.2" - loader-runner "^2.4.0" - loader-utils "^1.2.3" - memory-fs "^0.4.1" - micromatch "^3.1.10" - mkdirp "^0.5.1" - neo-async "^2.6.1" - node-libs-browser "^2.2.1" - schema-utils "^1.0.0" - tapable "^1.1.3" - terser-webpack-plugin "^1.4.1" - watchpack "^1.6.0" - webpack-sources "^1.4.1" - websocket-driver@>=0.5.1: version "0.7.0" resolved "https://registry.yarnpkg.com/websocket-driver/-/websocket-driver-0.7.0.tgz#0caf9d2d755d93aee049d4bdd0d3fe2cca2a24eb" @@ -30326,11 +30170,6 @@ whatwg-fetch@>=0.10.0, whatwg-fetch@^3.0.0: resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-3.0.0.tgz#fc804e458cc460009b1a2b966bc8817d2578aefb" integrity sha512-9GSJUgz1D4MfyKU7KRqwOjXCXTqWdFNvEr7eUBYchQiVc744mqK/MzXPNR2WsPkmkOa4ywfg8C2n8h+13Bey1Q== -whatwg-fetch@^0.9.0: - version "0.9.0" - resolved "https://registry.yarnpkg.com/whatwg-fetch/-/whatwg-fetch-0.9.0.tgz#0e3684c6cb9995b43efc9df03e4c365d95fd9cc0" - integrity sha1-DjaExsuZlbQ+/J3wPkw2XZX9nMA= - whatwg-mimetype@^2.2.0, whatwg-mimetype@^2.3.0: version "2.3.0" resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf" @@ -30494,6 +30333,11 @@ with@^5.0.0: acorn "^3.1.0" acorn-globals "^3.0.0" +word-wrap@~1.2.3: + version "1.2.3" + resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c" + integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ== + wordwrap@0.0.2: version "0.0.2" resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-0.0.2.tgz#b79669bb42ecb409f83d583cad52ca17eaa1643f"